Skip to content

Commit

Permalink
Allow arbitrary transact
Browse files Browse the repository at this point in the history
  • Loading branch information
yrong committed Apr 25, 2024
1 parent b89cc6c commit 606e867
Show file tree
Hide file tree
Showing 8 changed files with 69 additions and 89 deletions.
16 changes: 4 additions & 12 deletions contracts/src/AgentExecutor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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`,
Expand All @@ -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);
}
}
39 changes: 22 additions & 17 deletions contracts/src/Gateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import {
TransferNativeFromAgentParams,
SetTokenTransferFeesParams,
SetPricingParametersParams,
SetSafeCallsParams
TransactCallParams
} from "./Params.sol";

import {CoreStorage} from "./storage/CoreStorage.sol";
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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`
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
*/
Expand Down
12 changes: 8 additions & 4 deletions contracts/src/Params.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
6 changes: 3 additions & 3 deletions contracts/src/Types.sol
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,12 @@ enum Command {
SetOperatingMode,
TransferNativeFromAgent,
SetTokenTransferFees,
SetPricingParameters
SetPricingParameters,
Transact
}

enum AgentExecuteCommand {
TransferToken,
Transact
TransferToken
}

/// @dev Application-level costs for a message
Expand Down
8 changes: 2 additions & 6 deletions contracts/src/interfaces/IGateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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
*/
Expand Down
69 changes: 26 additions & 43 deletions contracts/test/Gateway.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import {
TransferNativeFromAgentParams,
SetTokenTransferFeesParams,
SetPricingParametersParams,
SetSafeCallsParams
TransactCallParams
} from "../src/Params.sol";

import {
Expand Down Expand Up @@ -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);
Expand All @@ -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);
}
}
4 changes: 0 additions & 4 deletions contracts/test/mocks/HelloWorld.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,6 @@ contract HelloWorld {
emit SaidHello(fullMessage);
}

function sayHello2(string memory _text) public {
this.sayHello(_text);
}

function revertUnauthorized() public pure {
revert Unauthorized();
}
Expand Down
4 changes: 4 additions & 0 deletions contracts/test/mocks/MockGateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

0 comments on commit 606e867

Please sign in to comment.