Skip to content

Commit

Permalink
Create token implementation (paritytech#846)
Browse files Browse the repository at this point in the history
* add statemine parachain

* Revert "add statemine parachain"

This reverts commit 2637a3fcb4a7437a551b441911aa497ff7bb2814.

* fix token address

* add statemine

* fix vars

* remove statemine for now

* fix test

* completed sovereign account

* refund surplus

* updated cumulus

* updated polkadot and cumulus deps

* update cumulus

* update contracts

* adjust weights

* added buy execution

* added withdraw asset

* add tests

* encode owner as multi address

* fixes for trapped assets

* rustfmt

* drop fees

* add tests

* rustfmt

* update cumulus

* fix asset transfer

* remove extra refund surplus

* updated config

* update cumulus

* pr feedback

* update cumulus

* changed bridgehub para id to 1002

* Revert "changed bridgehub para id to 1002"

This reverts commit 680c5304a3f169158d23d875cc628e54f4d91f1d.

* update cumulus
  • Loading branch information
alistair-singh authored Jun 23, 2023
1 parent f045039 commit 4cb7148
Show file tree
Hide file tree
Showing 9 changed files with 279 additions and 77 deletions.
2 changes: 1 addition & 1 deletion core/packages/test/config/launch-config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ cumulus_based = true
command = "{{output_bin_dir}}/polkadot-parachain"
rpc_port = 8082
ws_port = 12144
args = ["--force-authoring", "-lparachain=debug,xcm=trace,runtime::bridge-assets-transfer=trace"]
args = ["--force-authoring", "-lparachain=debug,xcm=trace,runtime::bridge-assets-transfer=trace,runtime::assets=trace,runtime::bridge-transfer=trace"]


# Statemine -> BridgeHub
Expand Down
97 changes: 48 additions & 49 deletions parachain/Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions parachain/primitives/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ std = [
"codec/std",
"scale-info/std",
"frame-support/std",
"frame-system/std",
"polkadot-parachain/std",
"sp-std/std",
"sp-core/std",
Expand Down
1 change: 1 addition & 0 deletions parachain/primitives/router/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ std = [
"codec/std",
"scale-info/std",
"frame-support/std",
"frame-system/std",
"sp-core/std",
"sp-io/std",
"sp-runtime/std",
Expand Down
224 changes: 207 additions & 17 deletions parachain/primitives/router/src/inbound/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
use codec::{Decode, Encode};
use core::marker::PhantomData;
use frame_support::{traits::ContainsPair, weights::Weight};
use sp_core::{RuntimeDebug, H160};
use sp_core::{Get, RuntimeDebug, H160};
use sp_io::hashing::blake2_256;
use sp_runtime::MultiAddress;
use sp_std::prelude::*;
use xcm::v3::{prelude::*, Junction::AccountKey20};
use xcm_executor::traits::ConvertLocation;
Expand Down Expand Up @@ -84,6 +85,12 @@ impl UpgradeProxyMessage {
impl NativeTokensMessage {
pub fn convert(self, chain_id: u64) -> Result<Xcm<()>, ConvertError> {
let network = NetworkId::Ethereum { chain_id };
let buy_execution_fee_amount = 2_000_000_000; //TODO: WeightToFee::weight_to_fee(&Weight::from_parts(100_000_000, 18_000));
let buy_execution_fee = MultiAsset {
id: Concrete(MultiLocation::parent()),
fun: Fungible(buy_execution_fee_amount),
};

match self {
NativeTokensMessage::Create {
origin,
Expand All @@ -99,32 +106,81 @@ impl NativeTokensMessage {
origin.as_fixed_bytes(),
);

let origin_location = Junction::AccountKey20 { network: None, key: origin.into() };

let asset_id = Self::convert_token_address(network, origin, token);
let instructions: Vec<Instruction<()>> = vec![
UniversalOrigin(GlobalConsensus(network)),
DescendOrigin(X1(Junction::AccountKey20 { network: None, key: origin.into() })),
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,
require_weight_at_most: Weight::from_parts(40_000_000_000, 8000),
call: (create_call_index, asset_id, owner, MINIMUM_DEPOSIT).encode().into(),
require_weight_at_most: Weight::from_parts(400_000_000, 8_000),
call: (
create_call_index,
asset_id,
MultiAddress::<[u8; 32], ()>::Id(owner),
MINIMUM_DEPOSIT,
)
.encode()
.into(),
},
ExpectTransactStatus(MaybeErrorCode::Success),
Transact {
origin_kind: OriginKind::SovereignAccount,
require_weight_at_most: Weight::from_parts(20_000_000_000, 8000),
require_weight_at_most: Weight::from_parts(200_000_000, 8_000),
call: (set_metadata_call_index, asset_id, name, symbol, decimals)
.encode()
.into(),
},
ExpectTransactStatus(MaybeErrorCode::Success),
];
Ok(instructions.into())
},
NativeTokensMessage::Mint { origin, token, dest, recipient, amount } => {
let asset =
MultiAsset::from((Self::convert_token_address(network, origin, token), amount));

let origin_location = Junction::AccountKey20 { network: None, key: origin.into() };

let mut instructions: Vec<Instruction<()>> = vec![
UniversalOrigin(GlobalConsensus(network)),
DescendOrigin(X1(Junction::AccountKey20 { network: None, key: origin.into() })),
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(),
),
ReserveAssetDeposited(vec![asset.clone()].into()),
ClearOrigin,
];
Expand Down Expand Up @@ -157,26 +213,25 @@ impl NativeTokensMessage {

// Convert ERC20 token address to a Multilocation that can be understood by Assets Hub.
fn convert_token_address(network: NetworkId, origin: H160, token: H160) -> MultiLocation {
return MultiLocation {
MultiLocation {
parents: 2,
interior: X3(
GlobalConsensus(network),
AccountKey20 { network: None, key: origin.into() },
AccountKey20 { network: None, key: token.into() },
),
};
}
}
}

pub struct FromEthereumGlobalConsensus;
impl ContainsPair<MultiLocation, MultiLocation> for FromEthereumGlobalConsensus {
fn contains(a: &MultiLocation, b: &MultiLocation) -> bool {
let a_network_id = a.interior().global_consensus();
if let Ok(Ethereum { .. }) = a_network_id {
b.interior().global_consensus() == a_network_id
} else {
false
}
pub struct FromEthereumGlobalConsensus<EthereumBridgeLocation>(PhantomData<EthereumBridgeLocation>);
impl<EthereumBridgeLocation> ContainsPair<MultiLocation, MultiLocation>
for FromEthereumGlobalConsensus<EthereumBridgeLocation>
where
EthereumBridgeLocation: Get<MultiLocation>,
{
fn contains(asset: &MultiLocation, origin: &MultiLocation) -> bool {
origin == &EthereumBridgeLocation::get() && asset.starts_with(origin)
}
}

Expand All @@ -203,3 +258,138 @@ impl<AccountId> GlobalConsensusEthereumAccountConvertsFor<AccountId> {
(b"ethereum", chain_id, key).using_encoded(blake2_256)
}
}

#[cfg(test)]
mod tests {
use super::{FromEthereumGlobalConsensus, GlobalConsensusEthereumAccountConvertsFor};
use frame_support::{parameter_types, traits::ContainsPair};
use hex_literal::hex;
use sp_core::crypto::Ss58Codec;
use xcm::v3::prelude::*;
use xcm_executor::traits::ConvertLocation;

const CONTRACT_ADDRESS: [u8; 20] = hex!("D184c103F7acc340847eEE82a0B909E3358bc28d");
const NETWORK: NetworkId = Ethereum { chain_id: 15 };
const SS58_FORMAT: u16 = 2;
const EXPECTED_SOVEREIGN_KEY: [u8; 32] =
hex!("5d6987649e0dac78ddf852eb0f1b1d1bf2be9623d81cb16c17cfa145948bb6dc");
const EXPECTED_SOVEREIGN_ADDRESS: &'static str =
"EgoKVgdhGVz41LyP2jckLrmXjnD35xitaX221ktZjQ2Xsxw";

parameter_types! {
pub EthereumNetwork: NetworkId = NETWORK;
pub EthereumLocation: MultiLocation = MultiLocation::new(2, X2(GlobalConsensus(EthereumNetwork::get()), AccountKey20 { network: None, key: CONTRACT_ADDRESS }));
}

#[test]
fn test_contract_location_without_network_converts_successfully() {
let contract_location = MultiLocation {
parents: 2,
interior: X2(
GlobalConsensus(NETWORK),
AccountKey20 { network: None, key: CONTRACT_ADDRESS },
),
};

let account = GlobalConsensusEthereumAccountConvertsFor::<[u8; 32]>::convert_location(
&contract_location,
)
.unwrap();
let address = frame_support::sp_runtime::AccountId32::new(account)
.to_ss58check_with_version(SS58_FORMAT.into());
assert_eq!(account, EXPECTED_SOVEREIGN_KEY);
assert_eq!(address, EXPECTED_SOVEREIGN_ADDRESS);

println!("SS58: {}\nBytes: {:?}", address, account);
}

#[test]
fn test_contract_location_with_network_converts_successfully() {
let contract_location = MultiLocation {
parents: 2,
interior: X2(
GlobalConsensus(NETWORK),
AccountKey20 { network: Some(NETWORK), key: CONTRACT_ADDRESS },
),
};

let account = GlobalConsensusEthereumAccountConvertsFor::<[u8; 32]>::convert_location(
&contract_location,
)
.unwrap();
let address = frame_support::sp_runtime::AccountId32::new(account)
.to_ss58check_with_version(SS58_FORMAT.into());
assert_eq!(account, EXPECTED_SOVEREIGN_KEY);
assert_eq!(address, EXPECTED_SOVEREIGN_ADDRESS);

println!("SS58: {}\nBytes: {:?}", address, account);
}

#[test]
fn test_contract_location_with_incorrect_location_fails_convert() {
let contract_location =
MultiLocation { parents: 2, interior: X2(GlobalConsensus(Polkadot), Parachain(1000)) };

assert_eq!(
GlobalConsensusEthereumAccountConvertsFor::<[u8; 32]>::convert_location(
&contract_location
),
None,
);
}

#[test]
fn test_from_ethereum_global_consensus_with_containing_asset_yields_true() {
let origin = MultiLocation {
parents: 2,
interior: X2(
GlobalConsensus(NETWORK),
AccountKey20 { network: None, key: CONTRACT_ADDRESS },
),
};
let asset = MultiLocation {
parents: 2,
interior: X3(
GlobalConsensus(NETWORK),
AccountKey20 { network: None, key: CONTRACT_ADDRESS },
AccountKey20 {
network: None,
key: [0; 20],
},
),
};
assert!(FromEthereumGlobalConsensus::<EthereumLocation>::contains(&asset, &origin));
}

#[test]
fn test_from_ethereum_global_consensus_without_containing_asset_yields_false() {
let origin = MultiLocation {
parents: 2,
interior: X2(
GlobalConsensus(NETWORK),
AccountKey20 { network: None, key: CONTRACT_ADDRESS },
),
};
let asset =
MultiLocation { parents: 2, interior: X2(GlobalConsensus(Polkadot), Parachain(1000)) };
assert!(!FromEthereumGlobalConsensus::<EthereumLocation>::contains(&asset, &origin));
}

#[test]
fn test_from_ethereum_global_consensus_without_bridge_origin_yields_false() {
let origin =
MultiLocation { parents: 2, interior: X2(GlobalConsensus(Polkadot), Parachain(1000)) };
let asset = MultiLocation {
parents: 2,
interior: X3(
GlobalConsensus(NETWORK),
AccountKey20 { network: None, key: CONTRACT_ADDRESS },
AccountKey20 {
network: None,
key: [0; 20],
},
),
};
assert!(!FromEthereumGlobalConsensus::<EthereumLocation>::contains(&asset, &origin));
}
}
12 changes: 6 additions & 6 deletions parachain/tools/call-index/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ fn main() {
if f.metadata().unwrap().is_file() {
// Only process Rust files:
if !f.path().to_str().unwrap().ends_with(".rs") {
continue
continue;
}
// Exclude the pallet-ui tests:
if f.path().to_str().unwrap().contains("pallet_ui") {
continue
continue;
}

let content = std::fs::read_to_string(f.path()).unwrap();
Expand All @@ -31,12 +31,12 @@ fn main() {
if let Some(m) = m {
// Skip if there is already a call index before or after:
if i > 0 && content.lines().nth(i - 1).unwrap().contains("pallet::call_index") {
continue
continue;
}
if i + 1 < content.lines().count() &&
content.lines().nth(i + 1).unwrap().contains("pallet::call_index")
if i + 1 < content.lines().count()
&& content.lines().nth(i + 1).unwrap().contains("pallet::call_index")
{
continue
continue;
}

println!("{}:{} index {}", f.path().display(), i, call_index);
Expand Down
1 change: 1 addition & 0 deletions smoketest/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ ethers = { git = "https://github.com/gakonst/ethers-rs", default-features = fals

[dev-dependencies]
xcm = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.42" }
hex-literal = "0.4.1"
16 changes: 13 additions & 3 deletions smoketest/tests/lock_tokens.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use hex_literal::hex;
use snowbridge_smoketest::contracts::{native_tokens, weth9};
use std::{sync::Arc, time::Duration};

Expand All @@ -11,16 +12,19 @@ use ethers::{

use codec::Encode;

use xcm::v3::MultiLocation;
use xcm::v3::{MultiLocation, Junction, Junctions::X1};

// The deployment addresses of the following contracts are stable, unless we modify the order in
// contracts are deployed in DeployScript.sol.
const ETHEREUM_API: &str = "http://localhost:8545";
const ETHEREUM_KEY: &str = "0x5e002a1af63fd31f1c25258f3082dc889762664cb8f218d86da85dff8b07b342";
const NATIVE_TOKENS_CONTRACT: &str = "0x8cF6147918A5CBb672703F879f385036f8793a24";
const TOKEN_VAULT_CONTRACT: &str = "0xB8EA8cB425d85536b158d661da1ef0895Bb92F1D";
const NATIVE_TOKENS_CONTRACT: &str = "0x8cF6147918A5CBb672703F879f385036f8793a24";
const WETH_CONTRACT: &str = "0x440eDFFA1352B13227e8eE646f3Ea37456deC701";

// SS58: DE14BzQ1bDXWPKeLoAqdLAm1GpyAWaWF1knF74cEZeomTBM
const FERDIE: [u8; 32] = hex!("1cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c");

#[tokio::test]
async fn test_lock_tokens() {
let provider = Provider::<Http>::try_from(ETHEREUM_API)
Expand Down Expand Up @@ -66,7 +70,13 @@ async fn test_lock_tokens() {
.unwrap();
assert_eq!(receipt.status.unwrap().as_u64(), 1u64);

let recipient = MultiLocation::default().encode();
let recipient = MultiLocation {
parents: 0,
interior: X1(Junction::AccountId32{
network: None,
id: FERDIE,
})
}.encode();

// Lock tokens into vault
let value1: u128 = U256::from(value).low_u128();
Expand Down

0 comments on commit 4cb7148

Please sign in to comment.