Skip to content
Draft
14 changes: 13 additions & 1 deletion contracts/src/AgentExecutor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pragma solidity 0.8.34;
import {IERC20} from "./interfaces/IERC20.sol";
import {SafeTokenTransfer, SafeNativeTransfer} from "./utils/SafeTransfer.sol";
import {Call} from "./utils/Call.sol";
import {CallContractParams} from "./v2/Types.sol";

/// @title Code which will run within an `Agent` using `delegatecall`.
/// @dev This is a singleton contract, meaning that all agents will execute the same code.
Expand All @@ -26,7 +27,18 @@ contract AgentExecutor {
function callContract(address target, bytes memory data, uint256 value) external {
bool success = Call.safeCall(target, data, value);
if (!success) {
revert();
Call.bubbleRevert();
}
}

// Call multiple contracts with Ether values; reverts on the first failure
function multiCall(CallContractParams[] calldata params) external {
uint256 len = params.length;
for (uint256 i; i < len; ++i) {
bool success = Call.safeCall(params[i].target, params[i].data, params[i].value);
if (!success) {
Call.bubbleRevert();
}
}
}
}
2 changes: 2 additions & 0 deletions contracts/src/Gateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,8 @@ contract Gateway is IGatewayBase, IGatewayV1, IGatewayV2, IInitializable, IUpgra
HandlersV2.mintForeignToken(command.payload);
} else if (command.kind == CommandKind.CallContract) {
HandlersV2.callContract(origin, AGENT_EXECUTOR, command.payload);
} else if (command.kind == CommandKind.MultiCall) {
HandlersV2.multiCall(origin, AGENT_EXECUTOR, command.payload);
} else {
revert IGatewayV2.InvalidCommand();
}
Expand Down
12 changes: 12 additions & 0 deletions contracts/src/utils/Call.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,18 @@ library Call {
* @param target Address to call
* @param data Calldata to pass to the call
*/
/**
* @notice Bubble up the revert reason from the most recent external call
* @dev Must be called immediately after the failed call so that returndata is still available
*/
function bubbleRevert() internal pure {
/// @solidity memory-safe-assembly
assembly {
returndatacopy(0, 0, returndatasize())
revert(0, returndatasize())
}
}

function safeCall(address target, bytes memory data, uint256 value) internal returns (bool) {
bool success;
assembly {
Expand Down
10 changes: 9 additions & 1 deletion contracts/src/v2/Handlers.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ import {
UnlockNativeTokenParams,
RegisterForeignTokenParams,
MintForeignTokenParams,
CallContractParams
CallContractParams,
MultiCallParams
} from "./Types.sol";

library HandlersV2 {
Expand Down Expand Up @@ -70,4 +71,11 @@ library HandlersV2 {
abi.encodeCall(AgentExecutor.callContract, (params.target, params.data, params.value));
Functions.invokeOnAgent(agent, executor, call);
}

function multiCall(bytes32 origin, address executor, bytes calldata data) external {
MultiCallParams memory params = abi.decode(data, (MultiCallParams));
address agent = Functions.ensureAgent(origin);
bytes memory call = abi.encodeCall(AgentExecutor.multiCall, params.calls);
Functions.invokeOnAgent(agent, executor, call);
}
}
8 changes: 8 additions & 0 deletions contracts/src/v2/Types.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ library CommandKind {
uint8 constant MintForeignToken = 4;
// Call an arbitrary solidity contract
uint8 constant CallContract = 5;
// Call multiple arbitrary solidity contracts
uint8 constant MultiCall = 6;
}

// Payload for outbound messages destined for Polkadot
Expand Down Expand Up @@ -185,6 +187,12 @@ struct CallContractParams {
uint256 value;
}

// Payload for MultiCall command. Reverts on first call failure.
struct MultiCallParams {
// Sub-calls to execute (reverts on first failure)
CallContractParams[] calls;
}

enum Network {
Polkadot
}
Loading
Loading