/******************************************************************************* * (c) 2022 Zondax AG * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ********************************************************************************/ // THIS CODE WAS SECURITY REVIEWED BY KUDELSKI SECURITY, BUT NOT FORMALLY AUDITED // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.17; import "./Misc.sol"; import "../types/CommonTypes.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; /// @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 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(CommonTypes.FilActorId 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(); /// @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 the actor is not found error ActorNotFound(); /// @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 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 value, bool static_call ) internal returns (int256, bytes memory) { if (actor_address.length < 2) { revert InvalidAddress(actor_address); } validatePrecompileCall(CALL_ACTOR_ADDRESS, 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`. // // Specifically: // // - `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), value, static_call ? READ_ONLY_FLAG : DEFAULT_FLAG, codec, raw_request, actor_address) ); if (!success) { revert FailToCallActor(); } return readRespData(data); } /// @notice allows to interact with an specific actor by its id (uint64) /// @param target actor id (uint64) 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 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( CommonTypes.FilActorId target, uint256 method_num, uint64 codec, bytes memory raw_request, uint256 value, bool static_call ) internal returns (int256, bytes memory) { validatePrecompileCall(CALL_ACTOR_ID, value); (bool success, bytes memory data) = address(CALL_ACTOR_ID).delegatecall( abi.encode(uint64(method_num), value, static_call ? READ_ONLY_FLAG : DEFAULT_FLAG, codec, raw_request, target) ); if (!success) { revert FailToCallActor(); } return readRespData(data); } /// @notice Readonly version of CallByID /// @param target actor id (uint64) 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 /// @return payload (in bytes) with the actual response data (without codec or response code) function callByIDReadOnly( CommonTypes.FilActorId target, uint256 method_num, uint64 codec, bytes memory raw_request ) internal view returns (int256, bytes memory) { function(CommonTypes.FilActorId, uint256, uint64, bytes memory, uint256, bool) internal view returns (int256, bytes memory) callFn; function(CommonTypes.FilActorId, uint256, uint64, bytes memory, uint256, bool) internal returns (int256, bytes memory) helper = callByID; assembly { callFn := helper } return callFn(target, method_num, codec, raw_request, 0, true); } /// @notice allows to run some generic validations before calling the precompile actor /// @param addr precompile actor address to run check to /// @param value tokens to be transferred to the called actor function validatePrecompileCall(address addr, uint256 value) internal view { uint balance = address(this).balance; if (balance < value) { revert NotEnoughBalance(balance, value); } bool actorExists = Misc.addressExists(addr); if (!actorExists) { revert ActorNotFound(); } } /// @notice allows to interact with an non-singleton actors by its id (uint64) /// @param target actor id (uint64) 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 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( CommonTypes.FilActorId target, uint256 method_num, uint64 codec, bytes memory raw_request, uint256 value, bool static_call ) internal returns (int256, bytes memory) { if (CommonTypes.FilActorId.unwrap(target) < 100) { revert InvalidActorID(target); } return callByID(target, method_num, codec, raw_request, value, static_call); } /// @notice Readonly version of CallNonSingletonByID /// @param target actor id (uint64) 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 /// @dev it requires the id to be bigger than 99, as singleton actors are smaller than that function callNonSingletonByIDReadOnly( CommonTypes.FilActorId target, uint256 method_num, uint64 codec, bytes memory raw_request ) internal view returns (int256, bytes memory) { if (CommonTypes.FilActorId.unwrap(target) < 100) { revert InvalidActorID(target); } return callByIDReadOnly(target, method_num, codec, raw_request); } /// @notice parse the response an actor returned /// @notice it will validate the return code (success) and the codec (valid one) /// @param raw_response raw data (bytes) the actor returned /// @return exit code if an error occured, 0 otherwise /// @return the actual raw data (payload, in bytes) to be parsed according to the actor and method called function readRespData(bytes memory raw_response) internal pure returns (int256, bytes memory) { (int256 exit, uint64 return_codec, bytes memory return_value) = abi.decode(raw_response, (int256, uint64, bytes)); if (return_codec == Misc.NONE_CODEC) { if (return_value.length != 0) { revert InvalidResponseLength(); } } else if (return_codec == Misc.CBOR_CODEC || return_codec == Misc.DAG_CBOR_CODEC) { if (return_value.length == 0) { revert InvalidResponseLength(); } } else { revert InvalidCodec(return_codec); } return (exit, return_value); } }