Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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/DeployLocal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,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 DeployLocal is Script {
using SafeNativeTransfer for address payable;
Expand Down Expand Up @@ -105,6 +106,8 @@ contract DeployLocal is Script {
payable(bridgeHubAgent).safeNativeTransfer(initialDeposit);
payable(assetHubAgent).safeNativeTransfer(initialDeposit);

// Deploy HelloWorld for testing transact
new HelloWorld();
// Deploy MockGatewayV2 for testing
new MockGatewayV2();

Expand Down
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
8 changes: 8 additions & 0 deletions contracts/src/AgentExecutor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ 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;

/// @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.
Expand All @@ -36,4 +38,10 @@ 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.
/// The safeCall here performs a low level call without copying any returndata for the return bomb attack
function executeCall(address target, bytes memory payload, uint64 maxDispatchGas) external returns (bool) {
return target.safeCall(maxDispatchGas, 0, payload);
}
}
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
88 changes: 72 additions & 16 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,7 +40,8 @@ import {
SetOperatingModeParams,
TransferNativeFromAgentParams,
SetTokenTransferFeesParams,
SetPricingParametersParams
SetPricingParametersParams,
TransactCallParams
} from "./Params.sol";

import {CoreStorage} from "./storage/CoreStorage.sol";
Expand Down Expand Up @@ -94,6 +96,7 @@ contract Gateway is IGateway, IInitializable, IUpgradable {
error AgentExecutionFailed(bytes returndata);
error InvalidAgentExecutionPayload();
error InvalidConstructorParams();
error AgentTransactCallFailed();

// Message handlers can only be dispatched by the gateway itself
modifier onlySelf() {
Expand Down Expand Up @@ -165,6 +168,9 @@ contract Gateway is IGateway, IInitializable, IUpgradable {

bool success = true;

uint256 gasUsedForTransact;
address agentForTransact;

// Dispatch message to a handler
if (message.command == Command.AgentExecute) {
try Gateway(this).agentExecute{gas: maxDispatchGas}(message.params) {}
Expand Down Expand Up @@ -211,21 +217,17 @@ contract Gateway is IGateway, IInitializable, IUpgradable {
catch {
success = false;
}
} else if (message.command == Command.Transact) {
try Gateway(this).transact{gas: maxDispatchGas}(message.params) returns (address _agent, uint256 _gasUsed) {
gasUsedForTransact = _gasUsed;
agentForTransact = _agent;
} catch {
success = false;
}
}

// Calculate a gas refund, capped to protect against huge spikes in `tx.gasprice`
// that could drain funds unnecessarily. During these spikes, relayers should back off.
uint256 gasUsed = _transactionBaseGas() + (startGas - gasleft());
uint256 refund = gasUsed * Math.min(tx.gasprice, message.maxFeePerGas);

// Add the reward to the refund amount. If the sum is more than the funds available
// in the channel agent, then reduce the total amount
uint256 amount = Math.min(refund + message.reward, address(channel.agent).balance);

// Do the payment if there funds available in the agent
if (amount > _dustThreshold()) {
_transferNativeFromAgent(channel.agent, payable(msg.sender), amount);
}
// Refund relayer
_refundRelayer(message, startGas, address(channel.agent), agentForTransact, gasUsedForTransact);

emit IGateway.InboundMessageDispatched(message.channelID, message.nonce, message.id, success);
}
Expand Down Expand Up @@ -381,6 +383,25 @@ contract Gateway is IGateway, IInitializable, IUpgradable {
emit PricingParametersChanged();
}

// @dev Transact
function transact(bytes calldata data) external onlySelf returns (address, uint256) {
uint256 gasLeftBefore = gasleft();

TransactCallParams memory params = abi.decode(data, (TransactCallParams));
address agent = _ensureAgent(params.agentID);
bytes memory call =
abi.encodeCall(AgentExecutor.executeCall, (params.target, params.payload, params.maxDispatchGas));
(, bytes memory result) = Agent(payable(agent)).invoke(AGENT_EXECUTOR, call);
(bool success) = abi.decode(result, (bool));
if (!success) {
revert AgentTransactCallFailed();
}
emit Transacted(params.agentID, params.target, keccak256(params.payload));

uint256 gasUsed = gasLeftBefore - gasleft();
return (agent, gasUsed);
}

/**
* Assets
*/
Expand Down Expand Up @@ -416,7 +437,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 Expand Up @@ -541,6 +564,39 @@ contract Gateway is IGateway, IInitializable, IUpgradable {
return 21000 * tx.gasprice;
}

/// @dev Refund relayer from both channel agent and transact agent
function _refundRelayer(
InboundMessage calldata message,
uint256 startGas,
address agent,
address agentForTransact,
uint256 gasUsedForTransact
) internal {
// Calculate a gas refund, capped to protect against huge spikes in `tx.gasprice`
// that could drain funds unnecessarily. During these spikes, relayers should back off.
uint256 gasUsed = _transactionBaseGas() + (startGas - gasleft());
if (message.command == Command.Transact) {
// User agent pays for the transact dispatch
uint256 transactFee = Math.min(gasUsedForTransact * tx.gasprice, address(agentForTransact).balance);
if (transactFee > _dustThreshold()) {
_transferNativeFromAgent(agentForTransact, payable(msg.sender), transactFee);
}
// gas used for the channel agent should not include the cost for transact
gasUsed = gasUsed - gasUsedForTransact;
}

uint256 refund = gasUsed * Math.min(tx.gasprice, message.maxFeePerGas);

// Add the reward to the refund amount. If the sum is more than the funds available
// in the channel agent, then reduce the total amount
uint256 amount = Math.min(refund + message.reward, agent.balance);

// Do the payment if there funds available in the agent
if (amount > _dustThreshold()) {
_transferNativeFromAgent(agent, payable(msg.sender), amount);
}
}

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

// Payload for TransactCall
struct TransactCallParams {
/// @dev The agent ID of the consensus system
bytes32 agentID;
/// @dev The target contract
address target;
/// @dev Payload of the call
bytes payload;
/// @dev Max gas cost of the call
uint64 maxDispatchGas;
}
3 changes: 2 additions & 1 deletion contracts/src/Types.sol
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ enum Command {
SetOperatingMode,
TransferNativeFromAgent,
SetTokenTransferFees,
SetPricingParameters
SetPricingParameters,
Transact
}

enum AgentExecuteCommand {
Expand Down
2 changes: 2 additions & 0 deletions contracts/src/interfaces/IGateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ interface IGateway {
// Emitted when funds are withdrawn from an agent
event AgentFundsWithdrawn(bytes32 indexed agentID, address indexed recipient, uint256 amount);

event Transacted(bytes32 indexed agentID, address indexed target, bytes32 payloadHash);

/**
* Getters
*/
Expand Down
Loading