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

SNO-613: Arbitrary transact from Ethereum to Polkadot #925

Closed
wants to merge 22 commits into from
Closed
Show file tree
Hide file tree
Changes from 19 commits
Commits
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
17 changes: 11 additions & 6 deletions contracts/src/Assets.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ library Assets {
// This library requires state which must be initialized in the gateway's storage.
function initialize(uint256 registerTokenFee, uint256 sendTokenFee) external {
AssetsStorage.Layout storage $ = AssetsStorage.layout();

$.registerTokenFee = registerTokenFee;
$.sendTokenFee = sendTokenFee;
}
Expand All @@ -48,12 +47,15 @@ library Assets {
AssetsStorage.Layout storage $ = AssetsStorage.layout();

_transferToAgent(assetHubAgent, token, sender, amount);

extraFee = $.sendTokenFee;

if (destinationChain == assetHubParaID) {
payload = SubstrateTypes.SendToken(address(this), token, destinationAddress, amount);
payload = SubstrateTypes.SendToken(address(this), token, destinationAddress, amount, extraFee);
} else {
payload = SubstrateTypes.SendToken(address(this), token, destinationChain, destinationAddress, amount);
payload =
SubstrateTypes.SendToken(address(this), token, destinationChain, destinationAddress, amount, extraFee);
}
extraFee = $.sendTokenFee;

emit TokenSent(sender, token, destinationChain, abi.encodePacked(destinationAddress), amount);
}
Expand All @@ -75,8 +77,10 @@ library Assets {

_transferToAgent(assetHubAgent, token, sender, amount);

payload = SubstrateTypes.SendToken(address(this), token, destinationChain, destinationAddress, amount);
extraFee = $.sendTokenFee;

payload = SubstrateTypes.SendToken(address(this), token, destinationChain, destinationAddress, amount, extraFee);

Comment on lines +81 to +83
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So if I understand this right, sendToken is a different kind of operation than an arbitrary contract call. So why is extraFee being sent here too, if it is not an arbitrary transact call being done here?

Copy link
Contributor Author

@yrong yrong Aug 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually it's some improvement for https://linear.app/snowfork/issue/SNO-582 for transfer, but relevant to the Transact change here(i.e. extra fee will consistently be used to cover the cost of XCM dispatch) so I link it as subtask as https://linear.app/snowfork/issue/SNO-613

@vgeddes Could you help to confirm if that's also your intention?

emit TokenSent(sender, token, destinationChain, abi.encodePacked(destinationAddress), amount);
}

Expand Down Expand Up @@ -105,9 +109,10 @@ library Assets {
revert InvalidToken();
}

payload = SubstrateTypes.RegisterToken(address(this), token, createTokenCallID);
extraFee = $.registerTokenFee;

payload = SubstrateTypes.RegisterToken(address(this), token, createTokenCallID, extraFee);

emit TokenRegistrationSent(token);
}
}
11 changes: 10 additions & 1 deletion contracts/src/DeployScript.sol
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,15 @@ contract DeployScript is Script {

ParaID bridgeHubParaID = ParaID.wrap(vm.envUint("BRIDGE_HUB_PARAID"));
bytes32 bridgeHubAgentID = vm.envBytes32("BRIDGE_HUB_AGENT_ID");

ParaID assetHubParaID = ParaID.wrap(vm.envUint("ASSET_HUB_PARAID"));
bytes32 assetHubAgentID = vm.envBytes32("ASSET_HUB_AGENT_ID");

ParaID templateParaID = ParaID.wrap(vm.envUint("TEMPLATE_PARAID"));
bytes32 templateAgentID = vm.envBytes32("TEMPLATE_AGENT_ID");

bytes32 create2Salt = vm.envBytes32("CREATE2_SALT");

AgentExecutor executor = new AgentExecutor();
Gateway gatewayLogic = new Gateway(
address(beefyClient),
Expand All @@ -55,7 +61,10 @@ contract DeployScript is Script {
bridgeHubAgentID,
assetHubParaID,
assetHubAgentID,
bytes2(vm.envBytes("CREATE_CALL_INDEX"))
templateParaID,
templateAgentID,
bytes2(vm.envBytes("CREATE_CALL_INDEX")),
create2Salt
);

bytes memory initParams = abi.encode(
Expand Down
98 changes: 95 additions & 3 deletions contracts/src/Gateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {ScaleCodec} from "./utils/ScaleCodec.sol";

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

contract Gateway is IGateway, IInitializable {
using Address for address;
Expand All @@ -29,6 +30,14 @@ contract Gateway is IGateway, IInitializable {
uint256 internal immutable DISPATCH_GAS;
address internal immutable AGENT_EXECUTOR;

// Todo: Could be a dynamic registry map set by destination chain
// Default params for arbitrary transact
// should be big enough to cover most of the transact cost in destination chain
// could be somehow overestimated since the surplus will be refunded
uint256 internal constant DEFAULT_EXTRA_FEE = 100_000_000_000_000;
uint64 internal constant DEFAULT_REF_TIME = 1_000_000_000;
uint64 internal constant DEFAULT_PROOF_SIZE = 100_000;

// Verification state
address internal immutable BEEFY_CLIENT;

Expand All @@ -40,7 +49,15 @@ contract Gateway is IGateway, IInitializable {
// AssetHub
ParaID internal immutable ASSET_HUB_PARA_ID;
bytes32 internal immutable ASSET_HUB_AGENT_ID;

// Template
ParaID internal immutable TEMPLATE_PARA_ID;
bytes32 internal immutable TEMPLATE_AGENT_ID;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand correctly, the template parachain is used for "testing" and illustrating how the arbitrary transact will work, right? So should "testing" code like this form part of the gateway contract? 🤔

Copy link
Contributor Author

@yrong yrong Aug 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly! Previously I did that in Initialize logic and removed in e3fab42 in consistent with your pull request.

Btw, seems you commented on outdated code.


// Call index of create token in assethub runtime
bytes2 internal immutable CREATE_TOKEN_CALL_ID;
// Salt
bytes32 internal immutable CREATE2_SALT;

error InvalidProof();
error InvalidNonce();
Expand Down Expand Up @@ -79,7 +96,10 @@ contract Gateway is IGateway, IInitializable {
bytes32 bridgeHubHubAgentID,
ParaID assetHubParaID,
bytes32 assetHubHubAgentID,
bytes2 createTokenCallID
ParaID templateParaID,
bytes32 templateAgentID,
bytes2 createTokenCallID,
bytes32 salt
) {
BEEFY_CLIENT = beefyClient;
AGENT_EXECUTOR = agentExecutor;
Expand All @@ -89,7 +109,10 @@ contract Gateway is IGateway, IInitializable {
BRIDGE_HUB_AGENT_ID = bridgeHubHubAgentID;
ASSET_HUB_PARA_ID = assetHubParaID;
ASSET_HUB_AGENT_ID = assetHubHubAgentID;
TEMPLATE_PARA_ID = templateParaID;
TEMPLATE_AGENT_ID = templateAgentID;
CREATE_TOKEN_CALL_ID = createTokenCallID;
CREATE2_SALT = salt;
}

/// @dev Submit a message from Polkadot for verification and dispatch
Expand Down Expand Up @@ -535,7 +558,7 @@ contract Gateway is IGateway, IInitializable {
$.defaultReward = defaultReward;

// Initialize an agent & channel for BridgeHub
address bridgeHubAgent = address(new Agent(BRIDGE_HUB_AGENT_ID));
address bridgeHubAgent = address(new Agent{salt:CREATE2_SALT}(BRIDGE_HUB_AGENT_ID));
$.agents[BRIDGE_HUB_AGENT_ID] = bridgeHubAgent;
$.channels[BRIDGE_HUB_PARA_ID] = Channel({
mode: OperatingMode.Normal,
Expand All @@ -547,7 +570,7 @@ contract Gateway is IGateway, IInitializable {
});

// Initialize an agent & channel for AssetHub
address assetHubAgent = address(new Agent(ASSET_HUB_AGENT_ID));
address assetHubAgent = address(new Agent{salt:CREATE2_SALT}(ASSET_HUB_AGENT_ID));
$.agents[ASSET_HUB_AGENT_ID] = assetHubAgent;
$.channels[ASSET_HUB_PARA_ID] = Channel({
mode: OperatingMode.Normal,
Expand All @@ -558,6 +581,75 @@ contract Gateway is IGateway, IInitializable {
reward: defaultReward
});

// Initialize an agent & channel for Template
address templateAgent = address(new Agent{salt:CREATE2_SALT}(TEMPLATE_AGENT_ID));
$.agents[TEMPLATE_AGENT_ID] = templateAgent;
$.channels[TEMPLATE_PARA_ID] = Channel({
mode: OperatingMode.Normal,
agent: templateAgent,
inboundNonce: 0,
outboundNonce: 0,
fee: defaultFee,
reward: defaultReward
});
// Todo: Should be configurable/upgradable include a on-chain price oracle SWAP_RATE from https://coincodex.com/convert/ethereum/polkadot/
Assets.initialize(registerTokenFee, sendTokenFee);
}

/**
* Transacts
*/

/// @inheritdoc IGateway
function transactThroughSovereign(ParaID destinationChain, bytes calldata payload) external payable {
Channel storage channel = _ensureChannel(destinationChain);
bytes memory message_payload = SubstrateTypes.Transact(
address(channel.agent), bytes1(0x03), payload, DEFAULT_EXTRA_FEE, DEFAULT_REF_TIME, DEFAULT_PROOF_SIZE
);
_submitOutbound(destinationChain, message_payload, DEFAULT_EXTRA_FEE);
}

/// @inheritdoc IGateway
function transactThroughSovereign(
ParaID destinationChain,
bytes1 originKind,
bytes calldata payload,
uint256 extraFee,
uint64 refTime,
uint64 proofSize
) external payable {
Channel storage channel = _ensureChannel(destinationChain);
bytes memory message_payload =
SubstrateTypes.Transact(address(channel.agent), originKind, payload, extraFee, refTime, proofSize);
_submitOutbound(destinationChain, message_payload, extraFee);
}

/// @inheritdoc IGateway
function transactThroughSigned(ParaID destinationChain, bytes calldata payload) external payable {
bytes memory message_payload = SubstrateTypes.Transact(
msg.sender, bytes1(0x01), payload, DEFAULT_EXTRA_FEE, DEFAULT_REF_TIME, DEFAULT_PROOF_SIZE
);
_submitOutbound(destinationChain, message_payload, 0);
}

/// @inheritdoc IGateway
function transactThroughSigned(
ParaID destinationChain,
bytes calldata payload,
uint256 extraFee,
uint64 refTime,
uint64 proofSize
) external payable {
bytes memory message_payload =
SubstrateTypes.Transact(msg.sender, bytes1(0x01), payload, extraFee, refTime, proofSize);
_submitOutbound(destinationChain, message_payload, 0);
}

/// Todo: transactThroughSigned and pay extraFee with foreign asset in dest chain which depends on https://github.com/Snowfork/snowbridge/pull/927
function transactThroughSignedPayExtraFeeWithForeignAsset(
ParaID destinationChain,
bytes calldata payload,
uint32 fee_asset_id,
uint256 fee_amount
) external payable {}
}
61 changes: 49 additions & 12 deletions contracts/src/SubstrateTypes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,15 @@ library SubstrateTypes {
* `NativeTokensMessage::Create`
*/
// solhint-disable-next-line func-name-mixedcase
function RegisterToken(address gateway, address token, bytes2 createCallIndex)
function RegisterToken(address gateway, address token, bytes2 createCallIndex, uint256 extraFee)
internal
view
returns (bytes memory)
{
return bytes.concat(
bytes1(0x00),
ScaleCodec.encodeU64(uint64(block.chainid)),
ScaleCodec.encodeU128(uint128(extraFee)),
bytes1(0x00),
SubstrateTypes.H160(gateway),
SubstrateTypes.H160(token),
Expand All @@ -76,14 +77,15 @@ library SubstrateTypes {
* `NativeTokensMessage::Mint`
*/
// solhint-disable-next-line func-name-mixedcase
function SendToken(address gateway, address token, bytes32 recipient, uint128 amount)
function SendToken(address gateway, address token, bytes32 recipient, uint128 amount, uint256 extraFee)
internal
view
returns (bytes memory)
{
return bytes.concat(
bytes1(0x00),
ScaleCodec.encodeU64(uint64(block.chainid)),
ScaleCodec.encodeU128(uint128(extraFee)),
bytes1(0x01),
SubstrateTypes.H160(gateway),
SubstrateTypes.H160(token),
Expand All @@ -93,14 +95,18 @@ library SubstrateTypes {
);
}

function SendToken(address gateway, address token, ParaID paraID, bytes32 recipient, uint128 amount)
internal
view
returns (bytes memory)
{
function SendToken(
address gateway,
address token,
ParaID paraID,
bytes32 recipient,
uint128 amount,
uint256 extraFee
) internal view returns (bytes memory) {
return bytes.concat(
bytes1(0x00),
ScaleCodec.encodeU64(uint64(block.chainid)),
ScaleCodec.encodeU128(uint128(extraFee)),
bytes1(0x01),
SubstrateTypes.H160(gateway),
SubstrateTypes.H160(token),
Expand All @@ -111,14 +117,18 @@ library SubstrateTypes {
);
}

function SendToken(address gateway, address token, ParaID paraID, address recipient, uint128 amount)
internal
view
returns (bytes memory)
{
function SendToken(
address gateway,
address token,
ParaID paraID,
address recipient,
uint128 amount,
uint256 extraFee
) internal view returns (bytes memory) {
return bytes.concat(
bytes1(0x00),
ScaleCodec.encodeU64(uint64(block.chainid)),
ScaleCodec.encodeU128(uint128(extraFee)),
bytes1(0x01),
SubstrateTypes.H160(gateway),
SubstrateTypes.H160(token),
Expand All @@ -128,4 +138,31 @@ library SubstrateTypes {
ScaleCodec.encodeU128(amount)
);
}

/**
* @dev SCALE-encodes `router_primitives::inbound::VersionedMessage` containing payload
* for arbitrary transact
*/
// solhint-disable-next-line func-name-mixedcase
function Transact(
address sender,
bytes1 originKind,
bytes calldata payload,
uint256 extraFee,
uint64 refTime,
uint64 proofSize
) internal view returns (bytes memory) {
return bytes.concat(
bytes1(0x00),
ScaleCodec.encodeU64(uint64(block.chainid)),
ScaleCodec.encodeU128(uint128(extraFee)),
bytes1(0x02),
SubstrateTypes.H160(sender),
originKind,
ScaleCodec.checkedEncodeCompactU32(payload.length),
payload,
ScaleCodec.encodeU64(refTime),
ScaleCodec.encodeU64(proofSize)
);
}
}
25 changes: 25 additions & 0 deletions contracts/src/interfaces/IGateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,29 @@ interface IGateway {
function sendToken(address token, ParaID destinationChain, address destinationAddress, uint128 amount)
external
payable;

/// @dev Send arbitrary transact with agent as origin
function transactThroughSovereign(ParaID destinationChain, bytes calldata payload) external payable;

/// @dev Send arbitrary transact with agent as origin and custom fee/weight
function transactThroughSovereign(
ParaID destinationChain,
bytes1 originKind,
bytes calldata payload,
uint256 extraFee,
uint64 refTime,
uint64 proofSize
) external payable;

/// @dev Send arbitrary transact with sender as origin
function transactThroughSigned(ParaID destinationChain, bytes calldata payload) external payable;

/// @dev Send arbitrary transact with sender as origin and custom fee/weight
function transactThroughSigned(
ParaID destinationChain,
bytes calldata payload,
uint256 extraFee,
uint64 refTime,
uint64 proofSize
) external payable;
}
Loading