diff --git a/contracts/src/AgentExecutor.sol b/contracts/src/AgentExecutor.sol index f966aa697c..d550524f82 100644 --- a/contracts/src/AgentExecutor.sol +++ b/contracts/src/AgentExecutor.sol @@ -16,20 +16,15 @@ contract AgentExecutor { using SafeNativeTransfer for address payable; using Call for address; - error CallExternalFailed(); - /// @dev Execute a message which originated from the Polkadot side of the bridge. In other terms, /// the `data` parameter is constructed by the BridgeHub parachain. /// - function execute(AgentExecuteCommand command, bytes memory params) external { + function execute(bytes memory data) external { + (AgentExecuteCommand command, bytes memory params) = abi.decode(data, (AgentExecuteCommand, bytes)); if (command == AgentExecuteCommand.TransferToken) { (address token, address recipient, uint128 amount) = abi.decode(params, (address, address, uint128)); _transferToken(token, recipient, amount); } - if (command == AgentExecuteCommand.Transact) { - (address target, bytes memory payload, uint64 dynamicGas) = abi.decode(params, (address, bytes, uint64)); - _executeCall(target, payload, dynamicGas); - } } /// @dev Transfer ether to `recipient`. Unlike `_transferToken` This logic is not nested within `execute`, @@ -45,10 +40,7 @@ contract AgentExecutor { } /// @dev Call a contract at the given address, with provided bytes as payload. - function _executeCall(address target, bytes memory payload, uint64 dynamicGas) internal { - bool success = target.safeCall(dynamicGas, 0, payload); - if (!success) { - revert CallExternalFailed(); - } + function executeCall(address target, bytes memory payload, uint64 dynamicGas) external returns (bool) { + return target.safeCall(dynamicGas, 0, payload); } } diff --git a/contracts/src/Gateway.sol b/contracts/src/Gateway.sol index 914bbfaa80..afe03effe5 100644 --- a/contracts/src/Gateway.sol +++ b/contracts/src/Gateway.sol @@ -41,7 +41,7 @@ import { TransferNativeFromAgentParams, SetTokenTransferFeesParams, SetPricingParametersParams, - SetSafeCallsParams + TransactCallParams } from "./Params.sol"; import {CoreStorage} from "./storage/CoreStorage.sol"; @@ -96,7 +96,7 @@ contract Gateway is IGateway, IInitializable, IUpgradable { error AgentExecutionFailed(bytes returndata); error InvalidAgentExecutionPayload(); error InvalidConstructorParams(); - error NoPermission(); + error AgentTransactCallFailed(); // Message handlers can only be dispatched by the gateway itself modifier onlySelf() { @@ -214,6 +214,11 @@ contract Gateway is IGateway, IInitializable, IUpgradable { catch { success = false; } + } else if (message.command == Command.Transact) { + try Gateway(this).transact{gas: maxDispatchGas}(message.params) {} + catch { + success = false; + } } // Calculate a gas refund, capped to protect against huge spikes in `tx.gasprice` @@ -277,26 +282,12 @@ contract Gateway is IGateway, IInitializable, IUpgradable { revert InvalidAgentExecutionPayload(); } - (AgentExecuteCommand command, bytes memory commandParams) = - abi.decode(params.payload, (AgentExecuteCommand, bytes)); - if (command == AgentExecuteCommand.Transact) { - (address target,,) = abi.decode(commandParams, (address, bytes, uint64)); - // blacklist to check with - if ( - target == address(this) || target == agent || target == AGENT_EXECUTOR - || Assets.isTokenRegistered(target) - ) { - revert NoPermission(); - } - } - - bytes memory call = abi.encodeCall(AgentExecutor.execute, (command, commandParams)); + bytes memory call = abi.encodeCall(AgentExecutor.execute, params.payload); (bool success, bytes memory returndata) = Agent(payable(agent)).invoke(AGENT_EXECUTOR, call); if (!success) { revert AgentExecutionFailed(returndata); } - emit AgentExecuted(params.agentID); } /// @dev Create an agent for a consensus system on Polkadot @@ -398,6 +389,20 @@ contract Gateway is IGateway, IInitializable, IUpgradable { emit PricingParametersChanged(); } + // @dev Transact + function transact(bytes calldata data) external onlySelf { + TransactCallParams memory params = abi.decode(data, (TransactCallParams)); + address agent = _ensureAgent(params.agentID); + bytes memory call = + abi.encodeCall(AgentExecutor.executeCall, (params.target, params.payload, params.dynamicGas)); + (, bytes memory result) = Agent(payable(agent)).invoke(AGENT_EXECUTOR, call); + (bool success) = abi.decode(result, (bool)); + if (!success) { + revert AgentTransactCallFailed(); + } + emit TransactExecuted(params.agentID, params.target, params.payload); + } + /** * Assets */ diff --git a/contracts/src/Params.sol b/contracts/src/Params.sol index d9bcacd848..5fe2f17c4f 100644 --- a/contracts/src/Params.sol +++ b/contracts/src/Params.sol @@ -83,10 +83,14 @@ struct SetPricingParametersParams { UD60x18 multiplier; } -// Payload for SetSafeCalls -struct SetSafeCallsParams { +// Payload for TransactCall +struct TransactCallParams { + /// @dev The agent ID of the consensus system + bytes32 agentID; /// @dev The target contract address target; - /// @dev The selector of the function - bytes4[] selectors; + /// @dev Payload of the call + bytes payload; + /// @dev Max gas cost of the call + uint64 dynamicGas; } diff --git a/contracts/src/Types.sol b/contracts/src/Types.sol index 2014dbe5fb..2deaf690ad 100644 --- a/contracts/src/Types.sol +++ b/contracts/src/Types.sol @@ -84,12 +84,12 @@ enum Command { SetOperatingMode, TransferNativeFromAgent, SetTokenTransferFees, - SetPricingParameters + SetPricingParameters, + Transact } enum AgentExecuteCommand { - TransferToken, - Transact + TransferToken } /// @dev Application-level costs for a message diff --git a/contracts/src/interfaces/IGateway.sol b/contracts/src/interfaces/IGateway.sol index 65bb32775b..5bf8244cc2 100644 --- a/contracts/src/interfaces/IGateway.sol +++ b/contracts/src/interfaces/IGateway.sol @@ -20,9 +20,6 @@ interface IGateway { // Emitted when an agent has been created for a consensus system on Polkadot event AgentCreated(bytes32 agentID, address agent); - // Emitted when an agent dispatch a safe call - event AgentExecuted(bytes32 agentID); - // Emitted when a channel has been created event ChannelCreated(ChannelID indexed channelID); @@ -35,12 +32,11 @@ interface IGateway { // Emitted when pricing params updated event PricingParametersChanged(); - // Emitted when safe call filter updated - event SafeCallFilterChanged(); - // Emitted when funds are withdrawn from an agent event AgentFundsWithdrawn(bytes32 indexed agentID, address indexed recipient, uint256 amount); + event TransactExecuted(bytes32 indexed agentID, address indexed target, bytes payload); + /** * Getters */ diff --git a/contracts/test/Gateway.t.sol b/contracts/test/Gateway.t.sol index 5d1d2476a6..adc8324682 100644 --- a/contracts/test/Gateway.t.sol +++ b/contracts/test/Gateway.t.sol @@ -36,7 +36,7 @@ import { TransferNativeFromAgentParams, SetTokenTransferFeesParams, SetPricingParametersParams, - SetSafeCallsParams + TransactCallParams } from "../src/Params.sol"; import { @@ -866,67 +866,54 @@ contract GatewayTest is Test { ); } - function testAgentExecutionTransact() public { - bytes memory payload = abi.encodeWithSignature("sayHello(string)", "Clara"); + function testAgentExecutionTransactSuccess() public { + bytes memory payload = abi.encodeWithSignature("sayHello(string)", "World"); - AgentExecuteParams memory params = AgentExecuteParams({ + TransactCallParams memory params = TransactCallParams({ agentID: assetHubAgentID, - payload: abi.encode(AgentExecuteCommand.Transact, abi.encode(address(helloWorld), payload, 100000)) + target: address(helloWorld), + payload: payload, + dynamicGas: 100000 }); // Expect the HelloWorld contract to emit `SaidHello` vm.expectEmit(true, false, false, false); - emit SaidHello("Hello there, Clara"); - - MockGateway(address(gateway)).agentExecutePublic(abi.encode(params)); - } - - function testAgentExecutionTransactNoPermission() public { - bytes memory payload = abi.encodeWithSignature("invoke(address,bytes)"); - - AgentExecuteParams memory params = AgentExecuteParams({ - agentID: assetHubAgentID, - payload: abi.encode(AgentExecuteCommand.Transact, abi.encode(address(assetHubAgent), payload, 100000)) - }); + emit SaidHello("Hello there, World"); - vm.expectRevert(Gateway.NoPermission.selector); - - MockGateway(address(gateway)).agentExecutePublic(abi.encode(params)); + MockGateway(address(gateway)).transactPublic(abi.encode(params)); } function testAgentExecutionTransactFail() public { bytes memory payload = abi.encodeWithSignature("revertUnauthorized()"); - AgentExecuteParams memory params = AgentExecuteParams({ + TransactCallParams memory params = TransactCallParams({ agentID: assetHubAgentID, - payload: abi.encode(AgentExecuteCommand.Transact, abi.encode(address(helloWorld), payload, 100000)) + target: address(helloWorld), + payload: payload, + dynamicGas: 100000 }); - vm.expectRevert( - abi.encodeWithSelector( - Gateway.AgentExecutionFailed.selector, abi.encodeWithSelector(AgentExecutor.CallExternalFailed.selector) - ) - ); + vm.expectRevert(abi.encodeWithSelector(Gateway.AgentTransactCallFailed.selector)); - MockGateway(address(gateway)).agentExecutePublic(abi.encode(params)); + MockGateway(address(gateway)).transactPublic(abi.encode(params)); } function testAgentExecutionTransactRetBomb() public { bytes memory payload = abi.encodeWithSignature("retBomb()"); - AgentExecuteParams memory params = AgentExecuteParams({ + TransactCallParams memory params = TransactCallParams({ agentID: assetHubAgentID, - payload: abi.encode(AgentExecuteCommand.Transact, abi.encode(address(helloWorld), payload, 30000000)) + target: address(helloWorld), + payload: payload, + dynamicGas: 100000 }); - // Expect Gateway contract to emit `AgentExecuted` - vm.expectEmit(true, false, false, false); - emit IGateway.AgentExecuted(assetHubAgentID); + vm.expectRevert(abi.encodeWithSelector(Gateway.AgentTransactCallFailed.selector)); - MockGateway(address(gateway)).agentExecutePublic(abi.encode(params)); + MockGateway(address(gateway)).transactPublic(abi.encode(params)); } - function testAgentExecutionTransactToTransferERC20() public { + function testAgentTransferERC20WithTransactShouldBeAllowed() public { testRegisterToken(); token.transfer(address(assetHubAgent), 200); @@ -936,16 +923,12 @@ contract GatewayTest is Test { uint256 amount = 50; bytes memory payload = abi.encodeCall(IERC20.transfer, (account1, amount)); - AgentExecuteParams memory params = AgentExecuteParams({ - agentID: assetHubAgentID, - payload: abi.encode(AgentExecuteCommand.Transact, abi.encode(address(token), payload, 100000)) - }); + TransactCallParams memory params = + TransactCallParams({agentID: assetHubAgentID, target: address(token), payload: payload, dynamicGas: 100000}); - vm.expectRevert(Gateway.NoPermission.selector); - - MockGateway(address(gateway)).agentExecutePublic(abi.encode(params)); + MockGateway(address(gateway)).transactPublic(abi.encode(params)); uint256 balanceAfter = IERC20(address(token)).balanceOf(account1); - assertEq(balanceBefore, balanceAfter); + assertEq(balanceBefore + amount, balanceAfter); } } diff --git a/contracts/test/mocks/HelloWorld.sol b/contracts/test/mocks/HelloWorld.sol index fa3bdbad71..62baac6408 100644 --- a/contracts/test/mocks/HelloWorld.sol +++ b/contracts/test/mocks/HelloWorld.sol @@ -11,10 +11,6 @@ contract HelloWorld { emit SaidHello(fullMessage); } - function sayHello2(string memory _text) public { - this.sayHello(_text); - } - function revertUnauthorized() public pure { revert Unauthorized(); } diff --git a/contracts/test/mocks/MockGateway.sol b/contracts/test/mocks/MockGateway.sol index f6fe62f84a..d5a5bd64f0 100644 --- a/contracts/test/mocks/MockGateway.sol +++ b/contracts/test/mocks/MockGateway.sol @@ -76,4 +76,8 @@ contract MockGateway is Gateway { function setPricingParametersPublic(bytes calldata params) external { this.setPricingParameters(params); } + + function transactPublic(bytes calldata params) external { + this.transact(params); + } }