Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Transact from sub to eth #1145

Open
wants to merge 25 commits into
base: bridge-next-gen
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions contracts/scripts/DeployScript.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {ChannelID, ParaID, OperatingMode} from "../src/Types.sol";
import {SafeNativeTransfer} from "../src/utils/SafeTransfer.sol";
import {stdJson} from "forge-std/StdJson.sol";
import {UD60x18, ud60x18} from "prb/math/src/UD60x18.sol";
import {HelloWorld} from "../test/mocks/HelloWorld.sol";

contract DeployScript is Script {
using SafeNativeTransfer for address payable;
Expand Down Expand Up @@ -104,6 +105,8 @@ contract DeployScript is Script {
payable(bridgeHubAgent).safeNativeTransfer(initialDeposit);
payable(assetHubAgent).safeNativeTransfer(initialDeposit);

new HelloWorld();

vm.stopBroadcast();
}
}
3 changes: 3 additions & 0 deletions contracts/scripts/FundAgent.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,15 @@ contract FundAgent is Script {

bytes32 bridgeHubAgentID = vm.envBytes32("BRIDGE_HUB_AGENT_ID");
bytes32 assetHubAgentID = vm.envBytes32("ASSET_HUB_AGENT_ID");
bytes32 penpalAgentID = vm.envBytes32("PENPAL_AGENT_ID");

address bridgeHubAgent = IGateway(gatewayAddress).agentOf(bridgeHubAgentID);
address assetHubAgent = IGateway(gatewayAddress).agentOf(assetHubAgentID);
address penpalAgent = IGateway(gatewayAddress).agentOf(penpalAgentID);

payable(bridgeHubAgent).safeNativeTransfer(initialDeposit);
payable(assetHubAgent).safeNativeTransfer(initialDeposit);
payable(penpalAgent).safeNativeTransfer(initialDeposit);

vm.stopBroadcast();
}
Expand Down
19 changes: 17 additions & 2 deletions contracts/src/AgentExecutor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,29 @@ import {SubstrateTypes} from "./SubstrateTypes.sol";

import {IERC20} from "./interfaces/IERC20.sol";
import {SafeTokenTransfer, SafeNativeTransfer} from "./utils/SafeTransfer.sol";
import {Call} from "./utils/Call.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.
contract AgentExecutor {
using SafeTokenTransfer for IERC20;
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(bytes memory data) external {
(AgentExecuteCommand command, bytes memory params) = abi.decode(data, (AgentExecuteCommand, bytes));
function execute(AgentExecuteCommand command, bytes memory params) external {
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 @@ -36,4 +43,12 @@ contract AgentExecutor {
function _transferToken(address token, address recipient, uint128 amount) internal {
IERC20(token).safeTransfer(recipient, amount);
}

/// @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();
}
}
}
11 changes: 6 additions & 5 deletions contracts/src/Assets.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,12 @@ library Assets {
IERC20(token).safeTransferFrom(sender, agent, amount);
}

function sendTokenCosts(address token, ParaID destinationChain, uint128 destinationChainFee, uint128 maxDestinationChainFee)
external
view
returns (Costs memory costs)
{
function sendTokenCosts(
address token,
ParaID destinationChain,
uint128 destinationChainFee,
uint128 maxDestinationChainFee
) external view returns (Costs memory costs) {
AssetsStorage.Layout storage $ = AssetsStorage.layout();
TokenInfo storage info = $.tokenRegistry[token];
if (!info.isRegistered) {
Expand Down
38 changes: 34 additions & 4 deletions contracts/src/Gateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ import {
Command,
MultiAddress,
Ticket,
Costs
Costs,
AgentExecuteCommand
} from "./Types.sol";
import {Upgrade} from "./Upgrade.sol";
import {IGateway} from "./interfaces/IGateway.sol";
Expand All @@ -39,18 +40,22 @@ import {
SetOperatingModeParams,
TransferNativeFromAgentParams,
SetTokenTransferFeesParams,
SetPricingParametersParams
SetPricingParametersParams,
SetSafeCallsParams
} from "./Params.sol";

import {CoreStorage} from "./storage/CoreStorage.sol";
import {PricingStorage} from "./storage/PricingStorage.sol";
import {AssetsStorage} from "./storage/AssetsStorage.sol";

import {UD60x18, ud60x18, convert} from "prb/math/src/UD60x18.sol";
import {SafeCallFilterStorage} from "./storage/SafeCallFilterStorage.sol";
import {BytesLib} from "./utils/BytesLib.sol";

contract Gateway is IGateway, IInitializable, IUpgradable {
using Address for address;
using SafeNativeTransfer for address payable;
using BytesLib for bytes;

address public immutable AGENT_EXECUTOR;

Expand Down Expand Up @@ -94,6 +99,7 @@ contract Gateway is IGateway, IInitializable, IUpgradable {
error AgentExecutionFailed(bytes returndata);
error InvalidAgentExecutionPayload();
error InvalidConstructorParams();
error NoPermission();

// Message handlers can only be dispatched by the gateway itself
modifier onlySelf() {
Expand Down Expand Up @@ -274,12 +280,24 @@ contract Gateway is IGateway, IInitializable, IUpgradable {
revert InvalidAgentExecutionPayload();
}

bytes memory call = abi.encodeCall(AgentExecutor.execute, params.payload);
(AgentExecuteCommand command, bytes memory commandParams) =
abi.decode(params.payload, (AgentExecuteCommand, bytes));
if (command == AgentExecuteCommand.Transact) {
(address target, bytes memory payload,) = abi.decode(commandParams, (address, bytes, uint64));
SafeCallFilterStorage.Layout storage $ = SafeCallFilterStorage.layout();
bytes4 selector = bytes4(payload.slice(0, 4));
if ($.safeCalls[target][selector] != true) {
revert NoPermission();
}
}
vgeddes marked this conversation as resolved.
Show resolved Hide resolved

bytes memory call = abi.encodeCall(AgentExecutor.execute, (command, commandParams));

(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 @@ -381,6 +399,16 @@ contract Gateway is IGateway, IInitializable, IUpgradable {
emit PricingParametersChanged();
}

// @dev Set safe calls of the gateway
function setSafeCalls(bytes calldata data) external onlySelf {
SafeCallFilterStorage.Layout storage filter = SafeCallFilterStorage.layout();
SetSafeCallsParams memory params = abi.decode(data, (SetSafeCallsParams));
for (uint256 i = 0; i < params.selectors.length; i++) {
filter.safeCalls[params.target][params.selectors[i]] = true;
}
emit SafeCallFilterChanged();
}

/**
* Assets
*/
Expand Down Expand Up @@ -416,7 +444,9 @@ contract Gateway is IGateway, IInitializable, IUpgradable {
uint128 amount
) external payable {
_submitOutbound(
Assets.sendToken(token, msg.sender, destinationChain, destinationAddress, destinationFee, MAX_DESTINATION_FEE, amount)
Assets.sendToken(
token, msg.sender, destinationChain, destinationAddress, destinationFee, MAX_DESTINATION_FEE, amount
)
);
}

Expand Down
8 changes: 8 additions & 0 deletions contracts/src/Params.sol
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,11 @@ struct SetPricingParametersParams {
/// @dev Fee multiplier
UD60x18 multiplier;
}

// Payload for SetSafeCalls
struct SetSafeCallsParams {
/// @dev The target contract
address target;
/// @dev The selector of the function
bytes4[] selectors;
}
3 changes: 2 additions & 1 deletion contracts/src/Types.sol
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ enum Command {
}

enum AgentExecuteCommand {
TransferToken
TransferToken,
Transact
}

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

Expand Down
18 changes: 18 additions & 0 deletions contracts/src/storage/SafeCallFilterStorage.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <[email protected]>
pragma solidity 0.8.23;

library SafeCallFilterStorage {
struct Layout {
mapping(address => mapping(bytes4 => bool)) safeCalls;
}

bytes32 internal constant SLOT = keccak256("org.snowbridge.storage.safeCallFilter");

function layout() internal pure returns (Layout storage $) {
bytes32 slot = SLOT;
assembly {
$.slot := slot
}
}
}
Loading