Skip to content

Commit

Permalink
create custom errors for different scenarios (Zondax#307)
Browse files Browse the repository at this point in the history
* feat: create custom errors for different scenarios
* feat: add inline comments and use custom errors in more places
* feat: add more inline comments to lib
  • Loading branch information
emmanuelm41 authored and longfeiWan9 committed Mar 7, 2023
1 parent c18ea2f commit de912da
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 59 deletions.
4 changes: 3 additions & 1 deletion contracts/v0.8/AccountAPI.sol
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ library AccountAPI {
bytes memory raw_request = params.serializeAuthenticateMessageParams();

bytes memory data = Actor.callNonSingletonByID(actorId, AccountTypes.AuthenticateMessageMethodNum, Misc.CBOR_CODEC, raw_request, 0, true);
require(data.length == 0, Actor.UNEXPECTED_RESPONSE_MESSAGE);
if (data.length != 0) {
revert Actor.InvalidResponseLength(data);
}
}
}
7 changes: 4 additions & 3 deletions contracts/v0.8/MarketAPI.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,10 @@ library MarketAPI {
function addBalance(bytes memory provider_or_client, uint256 value) internal {
bytes memory raw_request = provider_or_client.serializeAddress();

bytes memory result = Actor.callByID(MarketTypes.ActorID, MarketTypes.AddBalanceMethodNum, Misc.CBOR_CODEC, raw_request, value, false);

require(result.length == 0, Actor.UNEXPECTED_RESPONSE_MESSAGE);
bytes memory data = Actor.callByID(MarketTypes.ActorID, MarketTypes.AddBalanceMethodNum, Misc.CBOR_CODEC, raw_request, value, false);
if (data.length != 0) {
revert Actor.InvalidResponseLength(data);
}
}

/// @notice Attempt to withdraw the specified amount from the balance held in escrow.
Expand Down
35 changes: 21 additions & 14 deletions contracts/v0.8/MinerAPI.sol
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,9 @@ library MinerAPI {
bytes memory raw_request = addr.serializeAddress();

bytes memory result = Actor.callNonSingletonByID(actorId, MinerTypes.ChangeOwnerAddressMethodNum, Misc.CBOR_CODEC, raw_request, 0, false);

require(result.length == 0, Actor.UNEXPECTED_RESPONSE_MESSAGE);
if (result.length != 0) {
revert Actor.InvalidResponseLength(result);
}
}

/// @param actorId The miner actor id you want to interact with
Expand Down Expand Up @@ -109,8 +110,9 @@ library MinerAPI {
bytes memory raw_request = params.serializeChangeBeneficiaryParams();

bytes memory result = Actor.callNonSingletonByID(actorId, MinerTypes.ChangeBeneficiaryMethodNum, Misc.CBOR_CODEC, raw_request, 0, false);

require(result.length == 0, Actor.UNEXPECTED_RESPONSE_MESSAGE);
if (result.length != 0) {
revert Actor.InvalidResponseLength(result);
}
}

/// @param actorId The miner actor id you want to interact with
Expand All @@ -129,44 +131,49 @@ library MinerAPI {
bytes memory raw_request = params.serializeChangeWorkerAddressParams();

bytes memory result = Actor.callNonSingletonByID(actorId, MinerTypes.ChangeWorkerAddressMethodNum, Misc.CBOR_CODEC, raw_request, 0, false);

require(result.length == 0, Actor.UNEXPECTED_RESPONSE_MESSAGE);
if (result.length != 0) {
revert Actor.InvalidResponseLength(result);
}
}

/// @param actorId The miner actor id you want to interact with
function changePeerId(uint64 actorId, MinerTypes.ChangePeerIDParams memory params) internal {
bytes memory raw_request = params.serializeChangePeerIDParams();

bytes memory result = Actor.callNonSingletonByID(actorId, MinerTypes.ChangePeerIDMethodNum, Misc.CBOR_CODEC, raw_request, 0, false);

require(result.length == 0, Actor.UNEXPECTED_RESPONSE_MESSAGE);
if (result.length != 0) {
revert Actor.InvalidResponseLength(result);
}
}

/// @param actorId The miner actor id you want to interact with
function changeMultiaddresses(uint64 actorId, MinerTypes.ChangeMultiaddrsParams memory params) internal {
bytes memory raw_request = params.serializeChangeMultiaddrsParams();

bytes memory result = Actor.callNonSingletonByID(actorId, MinerTypes.ChangeMultiaddrsMethodNum, Misc.CBOR_CODEC, raw_request, 0, false);

require(result.length == 0, Actor.UNEXPECTED_RESPONSE_MESSAGE);
if (result.length != 0) {
revert Actor.InvalidResponseLength(result);
}
}

/// @param actorId The miner actor id you want to interact with
function repayDebt(uint64 actorId) internal {
bytes memory raw_request = new bytes(0);

bytes memory result = Actor.callNonSingletonByID(actorId, MinerTypes.RepayDebtMethodNum, Misc.NONE_CODEC, raw_request, 0, false);

require(result.length == 0, Actor.UNEXPECTED_RESPONSE_MESSAGE);
if (result.length != 0) {
revert Actor.InvalidResponseLength(result);
}
}

/// @param actorId The miner actor id you want to interact with
function confirmChangeWorkerAddress(uint64 actorId) internal {
bytes memory raw_request = new bytes(0);

bytes memory result = Actor.callNonSingletonByID(actorId, MinerTypes.ConfirmChangeWorkerAddressMethodNum, Misc.NONE_CODEC, raw_request, 0, false);

require(result.length == 0, Actor.UNEXPECTED_RESPONSE_MESSAGE);
if (result.length != 0) {
revert Actor.InvalidResponseLength(result);
}
}

/// @param actorId The miner actor id you want to interact with
Expand Down
10 changes: 6 additions & 4 deletions contracts/v0.8/SendAPI.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,18 @@ library SendAPI {
/// @param value tokens to be transferred to the receiver
function send(uint64 receiverActorId, uint256 value) internal {
bytes memory result = Actor.callByID(receiverActorId, 0, Misc.NONE_CODEC, new bytes(0), value, false);

require(result.length == 0, Actor.UNEXPECTED_RESPONSE_MESSAGE);
if (result.length != 0) {
revert Actor.InvalidResponseLength(result);
}
}

/// @notice send token to a specific actor
/// @param addr The address (bytes format) you want to send funds to
/// @param value tokens to be transferred to the receiver
function send(bytes memory addr, uint256 value) internal {
bytes memory result = Actor.callByAddress(addr, 0, Misc.NONE_CODEC, new bytes(0), value, false);

require(result.length == 0, Actor.UNEXPECTED_RESPONSE_MESSAGE);
if (result.length != 0) {
revert Actor.InvalidResponseLength(result);
}
}
}
5 changes: 3 additions & 2 deletions contracts/v0.8/VerifRegAPI.sol
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@ library VerifRegAPI {
bytes memory raw_request = params.serializeAddVerifiedClientParams();

bytes memory result = Actor.callByID(VerifRegTypes.ActorID, VerifRegTypes.AddVerifiedClientMethodNum, Misc.CBOR_CODEC, raw_request, 0, false);

require(result.length == 0, Actor.UNEXPECTED_RESPONSE_MESSAGE);
if (result.length != 0) {
revert Actor.InvalidResponseLength(result);
}
}

/// @notice remove the expired DataCap allocations and reclaimed those DataCap token back to Client. If the allocation amount is not specified, all expired DataCap allocation will be removed.
Expand Down
105 changes: 70 additions & 35 deletions contracts/v0.8/utils/Actor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,39 +18,68 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.17;

import "../external/Strings.sol";

import "./Misc.sol";

/// @title Call actors utilities library, meant to interact with Filecoin builtin actors
/// @author Zondax AG
library Actor {
/// @notice precompile address for the call_actor precompile
address constant CALL_ACTOR_ADDRESS = 0xfe00000000000000000000000000000000000003;

/// @notice precompile address for the call_actor_id precompile
address constant CALL_ACTOR_ID = 0xfe00000000000000000000000000000000000005;
string constant CALL_ERROR_MESSAGE = "actor call failed";
string constant UNEXPECTED_RESPONSE_MESSAGE = "unexpected response received";

uint64 constant READ_ONLY_FLAG = 0x00000001; // https://github.com/filecoin-project/ref-fvm/blob/master/shared/src/sys/mod.rs#L60
/// @notice flag used to indicate that the call_actor or call_actor_id should perform a static_call to the desired actor
uint64 constant READ_ONLY_FLAG = 0x00000001;

/// @notice flag used to indicate that the call_actor or call_actor_id should perform a delegate_call to the desired actor
uint64 constant DEFAULT_FLAG = 0x00000000;

/// @notice the provided address is not valid
error InvalidAddress(bytes addr);

/// @notice the smart contract has no enough balance to transfer
error NotEnoughBalance(uint256 balance, uint256 value);

/// @notice the provided actor id is not valid
error InvalidActorID(uint64 actorId);

/// @notice an error happened trying to call the actor
error FailToCallActor();

/// @notice the response received is not correct. In some case no response is expected and we received one, or a response was indeed expected and we received none.
error InvalidResponseLength(bytes response);

/// @notice the codec received is not valid
error InvalidCodec(uint64);

/// @notice the called actor returned an error as part of its expected behaviour
error ActorError(int256 errorCode);

/// @notice allows to interact with an specific actor by its address (bytes format)
/// @param actor_address actor address (bytes format) to interact with
/// @param method_num id of the method from the actor to call
/// @param codec how the request data passed as argument is encoded
/// @param raw_request encoded arguments to be passed in the call
/// @param amount tokens to be transfered to the called actor
/// @param static_call indicates if the call will be allaed to change the actor state or not (just read the state)
/// @param value tokens to be transferred to the called actor
/// @param static_call indicates if the call will be allowed to change the actor state or not (just read the state)
/// @return payload (in bytes) with the actual response data (without codec or response code)
function callByAddress(
bytes memory actor_address,
uint256 method_num,
uint64 codec,
bytes memory raw_request,
uint256 amount,
uint256 value,
bool static_call
) internal returns (bytes memory) {
require(actor_address.length > 1, "invalid actor_address");
require(address(this).balance >= amount, "not enough balance");
if (actor_address.length < 2) {
revert InvalidAddress(actor_address);
}

uint balance = address(this).balance;
if (balance < value) {
revert NotEnoughBalance(balance, value);
}

// We have to delegate-call the call-actor precompile because the call-actor precompile will
// call the target actor on our behalf. This will _not_ delegate to the target `actor_address`.
Expand All @@ -60,9 +89,11 @@ library Actor {
// - `static_call == false`: `CALLER (you) --(DELEGATECALL)-> CALL_ACTOR_PRECOMPILE --(CALL)-> actor_address
// - `static_call == true`: `CALLER (you) --(DELEGATECALL)-> CALL_ACTOR_PRECOMPILE --(STATICCALL)-> actor_address
(bool success, bytes memory data) = address(CALL_ACTOR_ADDRESS).delegatecall(
abi.encode(uint64(method_num), amount, static_call ? READ_ONLY_FLAG : DEFAULT_FLAG, codec, raw_request, actor_address)
abi.encode(uint64(method_num), value, static_call ? READ_ONLY_FLAG : DEFAULT_FLAG, codec, raw_request, actor_address)
);
require(success == true, CALL_ERROR_MESSAGE);
if (!success) {
revert FailToCallActor();
}

return readRespData(data);
}
Expand All @@ -72,23 +103,28 @@ library Actor {
/// @param method_num id of the method from the actor to call
/// @param codec how the request data passed as argument is encoded
/// @param raw_request encoded arguments to be passed in the call
/// @param amount tokens to be transferred to the called actor
/// @param value tokens to be transferred to the called actor
/// @param static_call indicates if the call will be allowed to change the actor state or not (just read the state)
/// @return payload (in bytes) with the actual response data (without codec or response code)
function callByID(
uint64 actor_id,
uint256 method_num,
uint64 codec,
bytes memory raw_request,
uint256 amount,
uint256 value,
bool static_call
) internal returns (bytes memory) {
require(address(this).balance >= amount, "not enough balance");
uint balance = address(this).balance;
if (balance < value) {
revert NotEnoughBalance(balance, value);
}

(bool success, bytes memory data) = address(CALL_ACTOR_ID).delegatecall(
abi.encode(uint64(method_num), amount, static_call ? READ_ONLY_FLAG : DEFAULT_FLAG, codec, raw_request, actor_id)
abi.encode(uint64(method_num), value, static_call ? READ_ONLY_FLAG : DEFAULT_FLAG, codec, raw_request, actor_id)
);
require(success == true, CALL_ERROR_MESSAGE);
if (!success) {
revert FailToCallActor();
}

return readRespData(data);
}
Expand All @@ -98,19 +134,22 @@ library Actor {
/// @param method_num id of the method from the actor to call
/// @param codec how the request data passed as argument is encoded
/// @param raw_request encoded arguments to be passed in the call
/// @param amount tokens to be transfered to the called actor
/// @param value tokens to be transfered to the called actor
/// @param static_call indicates if the call will be allowed to change the actor state or not (just read the state)
/// @dev it requires the id to be bigger than 99, as singleton actors are smaller than that
function callNonSingletonByID(
uint64 actor_id,
uint256 method_num,
uint64 codec,
bytes memory raw_request,
uint256 amount,
uint256 value,
bool static_call
) internal returns (bytes memory) {
require(actor_id >= 100, "actor id is not valid");
return callByID(actor_id, method_num, codec, raw_request, amount, static_call);
if (actor_id < 100) {
revert InvalidActorID(actor_id);
}

return callByID(actor_id, method_num, codec, raw_request, value, static_call);
}

/// @notice parse the response an actor returned
Expand All @@ -121,25 +160,21 @@ library Actor {
(int256 exit, uint64 return_codec, bytes memory return_value) = abi.decode(raw_response, (int256, uint64, bytes));

if (return_codec == Misc.NONE_CODEC) {
require(return_value.length == 0, "response length should be 0");
if (return_value.length != 0) {
revert InvalidResponseLength(return_value);
}
} else if (return_codec == Misc.CBOR_CODEC || return_codec == Misc.DAG_CBOR_CODEC) {
require(return_value.length > 0, "response length should greater than 0");
if (return_value.length == 0) {
revert InvalidResponseLength(return_value);
}
} else {
require(false, "invalid response codec");
revert InvalidCodec(return_codec);
}

require(exit == 0, getErrorCodeMsg(exit));
if (exit != 0) {
revert ActorError(exit);
}

return return_value;
}

/// @notice converts exit code to string message
/// @param exit_code the actual exit code
/// @return message based on the exit code
function getErrorCodeMsg(int256 exit_code) internal pure returns (string memory) {
return
exit_code >= 0
? string(abi.encodePacked("actor error code ", Strings.toString(uint256(exit_code))))
: string(abi.encodePacked("actor error code -", Strings.toString(Misc.abs(exit_code))));
}
}

0 comments on commit de912da

Please sign in to comment.