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 5 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
16 changes: 11 additions & 5 deletions contracts/src/Assets.sol
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,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 +78,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 +110,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);
}
}
30 changes: 30 additions & 0 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 Down Expand Up @@ -410,6 +411,35 @@ contract Gateway is IGateway, IInitializable {
_transferNativeFromAgent(agent, payable(params.recipient), params.amount);
}

/**
* Transacts
*/

// Send arbitrary transact
function sendTransact(ParaID destinationChain, bytes calldata payload) external payable {
// Todo: Default allow to be set by governance
// Default value should be big enough to cover the weight cost in destinationChain
// could be somehow overestimated since the surplus will be refunded
uint256 extraFee = 100_000_000_000_000;
uint64 default_ref_time = 1_000_000_000;
uint64 default_proof_size = 100_000;
bytes memory message_payload =
SubstrateTypes.Transact(address(this), payload, extraFee, default_ref_time, default_proof_size);
_submitOutbound(destinationChain, message_payload, extraFee);
}

// Send arbitrary transact with customize weight
function sendTransact(
ParaID destinationChain,
bytes calldata payload,
uint256 extraFee,
uint64 refTime,
uint64 proofSize
) external payable {
bytes memory message_payload = SubstrateTypes.Transact(address(this), payload, extraFee, refTime, proofSize);
_submitOutbound(destinationChain, message_payload, extraFee);
}

/**
* Assets
*/
Expand Down
57 changes: 45 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,27 @@ 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 gateway, 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(gateway),
ScaleCodec.checkedEncodeCompactU32(payload.length),
payload,
ScaleCodec.encodeU64(refTime),
ScaleCodec.encodeU64(proofSize)
);
}
}
12 changes: 12 additions & 0 deletions contracts/src/interfaces/IGateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,16 @@ interface IGateway {
function sendToken(address token, ParaID destinationChain, address destinationAddress, uint128 amount)
external
payable;

/// @dev Send arbitrary transact
function sendTransact(ParaID destinationChain, bytes calldata payload) external payable;

/// @dev Send arbitrary transact with customize weight
function sendTransact(
ParaID destinationChain,
bytes calldata payload,
uint256 extraFee,
uint64 refTime,
uint64 proofSize
) external payable;
}
6 changes: 3 additions & 3 deletions contracts/test/Gateway.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,7 @@ contract GatewayTest is Test {

vm.expectEmit(true, false, false, false);
emit OutboundMessageAccepted(
assetHubParaID, 1, SubstrateTypes.RegisterToken(address(gateway), address(token), bytes2(0x3500))
assetHubParaID, 1, SubstrateTypes.RegisterToken(address(gateway), address(token), bytes2(0x3500), 0)
);

IGateway(address(gateway)).registerToken{value: 2 ether}(address(token));
Expand All @@ -488,7 +488,7 @@ contract GatewayTest is Test {
// Expect the gateway to emit `OutboundMessageAccepted`
vm.expectEmit(true, false, false, false);
emit OutboundMessageAccepted(
assetHubParaID, 1, SubstrateTypes.SendToken(address(gateway), address(token), destPara, destAddress, 1)
assetHubParaID, 1, SubstrateTypes.SendToken(address(gateway), address(token), destPara, destAddress, 1, 0)
);

IGateway(address(gateway)).sendToken{value: 2 ether}(address(token), destPara, destAddress, 1);
Expand All @@ -508,7 +508,7 @@ contract GatewayTest is Test {
// Expect the gateway to emit `OutboundMessageAccepted`
vm.expectEmit(true, false, false, false);
emit OutboundMessageAccepted(
assetHubParaID, 1, SubstrateTypes.SendToken(address(gateway), address(token), destAddress, 1)
assetHubParaID, 1, SubstrateTypes.SendToken(address(gateway), address(token), destAddress, 1, 0)
);

IGateway(address(gateway)).sendToken{value: 2 ether}(address(token), destPara, destAddress, 1);
Expand Down
10 changes: 10 additions & 0 deletions contracts/test/mocks/GatewayUpgradeMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,14 @@ contract GatewayUpgradeMock is IGateway, IInitializable {
(uint256 d0, uint256 d1) = abi.decode(data, (uint256, uint256));
emit Initialized(d0, d1);
}

function sendTransact(ParaID destinationChain, bytes calldata payload) external payable {}

function sendTransact(
ParaID destinationChain,
bytes calldata payload,
uint256 extraFee,
uint64 refTime,
uint64 proofSize
) external payable {}
}
4 changes: 2 additions & 2 deletions parachain/pallets/inbound-queue/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,9 +198,9 @@ fn parse_dest(message: Message) -> ParaId {
// The originating channel address for the messages below
const GATEWAY_ADDRESS: [u8; 20] = hex!["EDa338E4dC46038493b885327842fD3E301CaB39"];

const OUTBOUND_QUEUE_EVENT_LOG: [u8; 253] = hex!(
const OUTBOUND_QUEUE_EVENT_LOG: [u8; 286] = hex!(
"
f8fb94eda338e4dc46038493b885327842fd3e301cab39f842a0d56f1b8dfd3ba41f19c499ceec5f9546f61befa5f10398a75d7dba53a219fecea000000000000000000000000000000000000000000000000000000000000003e8b8a0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000034000f000000000000000057a2d4ff0c3866d96556884bf09fecdd7ccd530c87d1f7fdfee7f651fabc8bfcb6e086c278b77a7d3500000000000000000000000000
f9011b94eda338e4dc46038493b885327842fd3e301cab39f842a0d56f1b8dfd3ba41f19c499ceec5f9546f61befa5f10398a75d7dba53a219fecea000000000000000000000000000000000000000000000000000000000000003e8b8c0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000044000f000000000000000000000000000000000000000000000000eda338e4dc46038493b885327842fd3e301cab3987d1f7fdfee7f651fabc8bfcb6e086c278b77a7d350000000000000000000000000000000000000000000000000000000000
"
);

Expand Down
66 changes: 60 additions & 6 deletions parachain/primitives/router/src/inbound/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use frame_support::{traits::ContainsPair, weights::Weight};
use sp_core::{Get, RuntimeDebug, H160};
use sp_io::hashing::blake2_256;
use sp_runtime::MultiAddress;
use sp_std::prelude::*;
use sp_std::{cmp::max, prelude::*};
use xcm::v3::{prelude::*, Junction::AccountKey20};
use xcm_executor::traits::ConvertLocation;

Expand All @@ -27,6 +27,8 @@ pub enum VersionedMessage {
pub struct MessageV1 {
/// EIP-155 chain id of the origin Ethereum network
pub chain_id: u64,
/// The fee to cover xcm buy_execution
pub fee: u128,
/// The command originating from the Gateway contract
pub message: Command,
}
Expand All @@ -53,6 +55,17 @@ pub enum Command {
/// Amount to transfer
amount: u128,
},
/// call arbitrary transact in another parachain
Transact {
/// The address of the gateway
gateway: H160,
/// The payload of the transact
payload: Vec<u8>,
/// The ref_time part of weight
ref_time: u64,
/// The proof_size part of weight
proof_size: u64,
},
}

/// Destination for bridged tokens
Expand All @@ -72,22 +85,63 @@ pub enum Destination {

impl From<MessageV1> for Xcm<()> {
fn from(val: MessageV1) -> Self {
val.message.convert(val.chain_id)
val.message.convert(val.chain_id, val.fee)
}
}

impl Command {
pub fn convert(self, chain_id: u64) -> Xcm<()> {
pub fn convert(self, chain_id: u64, fee: u128) -> Xcm<()> {
let network = NetworkId::Ethereum { chain_id };
// TODO (SNO-582): The fees need to be made configurable and must match the weight
// required by the generated XCM script when executed on the foreign chain.
let buy_execution_fee_amount = 2_000_000_000;
// Todo: Params need to change as configurable from polkadot governance
// Reference from https://coincodex.com/convert/ethereum/polkadot/
const SWAP_RATE: u128 = 367;
// Sanity base fee applies to all xcm calls
const BASE_FEE: u128 = 2_000_000_000;

let buy_execution_fee_amount =
max(BASE_FEE, fee.saturating_div(1000000u128.saturating_div(SWAP_RATE)));
yrong marked this conversation as resolved.
Show resolved Hide resolved

let buy_execution_fee = MultiAsset {
id: Concrete(MultiLocation::parent()),
fun: Fungible(buy_execution_fee_amount),
};

match self {
Command::Transact { gateway, payload, ref_time, proof_size } => {
let origin_location = Junction::AccountKey20 { network: None, key: gateway.into() };
alistair-singh marked this conversation as resolved.
Show resolved Hide resolved

let weight_limit: Weight = Weight::from_parts(ref_time, proof_size);

let instructions: Vec<Instruction<()>> = vec![
UniversalOrigin(GlobalConsensus(network)),
DescendOrigin(X1(origin_location)),
WithdrawAsset(buy_execution_fee.clone().into()),
BuyExecution { fees: buy_execution_fee.clone(), weight_limit: Unlimited },
SetAppendix(
vec![
RefundSurplus,
DepositAsset {
assets: buy_execution_fee.into(),
beneficiary: (
Parent,
Parent,
GlobalConsensus(network),
origin_location,
)
.into(),
},
]
.into(),
),
Transact {
origin_kind: OriginKind::Xcm,
yrong marked this conversation as resolved.
Show resolved Hide resolved
require_weight_at_most: weight_limit,
call: payload.into(),
},
ExpectTransactStatus(MaybeErrorCode::Success),
];
instructions.into()
},
Command::RegisterToken { gateway, token, create_call_index } => {
let owner = GlobalConsensusEthereumAccountConvertsFor::<[u8; 32]>::from_params(
&chain_id,
Expand Down
Loading