Skip to content

Commit cfedc19

Browse files
authored
EVM Fungible App (#81)
* Update types for EVM bridge * EVM Fungible App * Fix warnings * Fix warnings * Fix clippy * Saturating ops * Fix review comments * Remove Native asset kind * Use single app contract * Remove Native asset kind * Change type * Fix wasm build * Add channel address to commitment * Add evm block to base fee info * Add base fee update check to channel * Fix build
1 parent 37ff779 commit cfedc19

File tree

11 files changed

+2237
-8
lines changed

11 files changed

+2237
-8
lines changed

Cargo.lock

+28
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pallets/channel/src/inbound/mod.rs

+18-4
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,8 @@ pub mod pallet {
174174
ContractExists,
175175
/// Call encoding failed.
176176
CallEncodeFailed,
177+
/// Invalid base fee update.
178+
InvalidBaseFeeUpdate,
177179
}
178180

179181
impl<T: Config> Pallet<T> {
@@ -312,7 +314,11 @@ pub mod pallet {
312314
T::EVMFeeHandler::on_fee_paid(chain_id, status_report.relayer, fee_paid)
313315
}
314316
bridge_types::evm::Commitment::BaseFeeUpdate(update) => {
315-
T::EVMFeeHandler::update_base_fee(chain_id, update.new_base_fee)
317+
T::EVMFeeHandler::update_base_fee(
318+
chain_id,
319+
update.new_base_fee,
320+
update.evm_block_number,
321+
)
316322
}
317323
bridge_types::evm::Commitment::Outbound(_) => {
318324
frame_support::fail!(Error::<T>::InvalidCommitment);
@@ -331,14 +337,22 @@ pub mod pallet {
331337
let network_id = GenericNetworkId::EVM(chain_id);
332338
match commitment {
333339
bridge_types::evm::Commitment::Inbound(inbound_commitment) => {
334-
Self::ensure_evm_channel(chain_id, inbound_commitment.source)?;
340+
Self::ensure_evm_channel(chain_id, inbound_commitment.channel)?;
335341
Self::ensure_channel_nonce(network_id, inbound_commitment.nonce)?;
336342
}
337343
bridge_types::evm::Commitment::StatusReport(status_report) => {
338-
Self::ensure_evm_channel(chain_id, status_report.source)?;
344+
Self::ensure_evm_channel(chain_id, status_report.channel)?;
339345
Self::ensure_reported_nonce(network_id, status_report.nonce)?;
340346
}
341-
bridge_types::evm::Commitment::BaseFeeUpdate(_) => {}
347+
bridge_types::evm::Commitment::BaseFeeUpdate(update) => {
348+
if !T::EVMFeeHandler::can_update_base_fee(
349+
chain_id,
350+
update.new_base_fee,
351+
update.evm_block_number,
352+
) {
353+
return Err(Error::<T>::InvalidBaseFeeUpdate.into());
354+
}
355+
}
342356
bridge_types::evm::Commitment::Outbound(_) => {
343357
frame_support::fail!(Error::<T>::InvalidCommitment);
344358
}

pallets/evm-fungible-app/Cargo.toml

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
[package]
2+
name = "evm-fungible-app"
3+
version = "0.1.1"
4+
authors = ["Snowfork <[email protected]>"]
5+
edition = "2021"
6+
7+
[package.metadata.docs.rs]
8+
targets = ["x86_64-unknown-linux-gnu"]
9+
10+
[dependencies]
11+
serde = { version = "1.0.130", optional = true }
12+
codec = { version = "3", package = "parity-scale-codec", default-features = false, features = [
13+
"derive",
14+
] }
15+
scale-info = { version = "2", default-features = false, features = ["derive"] }
16+
hex = { package = "rustc-hex", version = "2.1.0", default-features = false }
17+
rlp = { version = "0.5.2", default-features = false }
18+
hex-literal = { version = "0.4.1", default-features = false }
19+
20+
frame-benchmarking = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.38", default-features = false, optional = true }
21+
frame-support = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.38", default-features = false }
22+
frame-system = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.38", default-features = false }
23+
sp-core = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.38", default-features = false }
24+
sp-std = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.38", default-features = false }
25+
sp-io = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.38", default-features = false }
26+
sp-runtime = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.38", default-features = false }
27+
28+
traits = { git = "https://github.com/open-web3-stack/open-runtime-module-library.git", branch = "polkadot-v0.9.38", package = "orml-traits", default-features = false, optional = true }
29+
currencies = { git = "https://github.com/open-web3-stack/open-runtime-module-library.git", branch = "polkadot-v0.9.38", package = "orml-currencies", default-features = false, optional = true }
30+
dispatch = { path = "../dispatch", default-features = false, optional = true }
31+
32+
ethabi = { git = "https://github.com/sora-xor/ethabi.git", branch = "sora-v1.6.0", package = "ethabi", default-features = false }
33+
34+
bridge-types = { path = "../types", default-features = false }
35+
36+
[dev-dependencies]
37+
dispatch = { path = "../dispatch" }
38+
sp-keyring = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.38" }
39+
sp-keystore = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.38" }
40+
pallet-balances = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.38" }
41+
tokens = { git = "https://github.com/open-web3-stack/open-runtime-module-library.git", branch = "polkadot-v0.9.38", package = "orml-tokens" }
42+
traits = { git = "https://github.com/open-web3-stack/open-runtime-module-library.git", branch = "polkadot-v0.9.38", package = "orml-traits" }
43+
currencies = { git = "https://github.com/open-web3-stack/open-runtime-module-library.git", branch = "polkadot-v0.9.38", package = "orml-currencies" }
44+
hex-literal = { version = "0.4.1" }
45+
bridge-types = { path = "../types", features = ["test"] }
46+
47+
[features]
48+
default = ["std"]
49+
std = [
50+
"serde",
51+
"hex/std",
52+
"codec/std",
53+
"scale-info/std",
54+
"frame-support/std",
55+
"frame-system/std",
56+
"sp-core/std",
57+
"sp-std/std",
58+
"sp-io/std",
59+
"sp-runtime/std",
60+
"bridge-types/std",
61+
"frame-benchmarking/std",
62+
"ethabi/std",
63+
"traits/std",
64+
"currencies/std",
65+
"dispatch/std",
66+
"rlp/std",
67+
]
68+
runtime-benchmarks = [
69+
"frame-benchmarking",
70+
"frame-support/runtime-benchmarks",
71+
"frame-system/runtime-benchmarks",
72+
"traits",
73+
"currencies",
74+
"dispatch",
75+
"dispatch/runtime-benchmarks",
76+
]
77+
78+
try-runtime = ["frame-support/try-runtime"]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
// This file is part of the SORA network and Polkaswap app.
2+
3+
// Copyright (c) 2020, 2021, Polka Biome Ltd. All rights reserved.
4+
// SPDX-License-Identifier: BSD-4-Clause
5+
6+
// Redistribution and use in source and binary forms, with or without modification,
7+
// are permitted provided that the following conditions are met:
8+
9+
// Redistributions of source code must retain the above copyright notice, this list
10+
// of conditions and the following disclaimer.
11+
// Redistributions in binary form must reproduce the above copyright notice, this
12+
// list of conditions and the following disclaimer in the documentation and/or other
13+
// materials provided with the distribution.
14+
//
15+
// All advertising materials mentioning features or use of this software must display
16+
// the following acknowledgement: This product includes software developed by Polka Biome
17+
// Ltd., SORA, and Polkaswap.
18+
//
19+
// Neither the name of the Polka Biome Ltd. nor the names of its contributors may be used
20+
// to endorse or promote products derived from this software without specific prior written permission.
21+
22+
// THIS SOFTWARE IS PROVIDED BY Polka Biome Ltd. AS IS AND ANY EXPRESS OR IMPLIED WARRANTIES,
23+
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24+
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Polka Biome Ltd. BE LIABLE FOR ANY
25+
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26+
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
27+
// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
28+
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
29+
// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30+
31+
//! ERC20App pallet benchmarking
32+
33+
use crate::*;
34+
use bridge_types::evm::AdditionalEVMInboundData;
35+
use bridge_types::traits::BridgeAssetRegistry;
36+
use bridge_types::traits::EVMBridgeWithdrawFee;
37+
use bridge_types::types::AssetKind;
38+
use bridge_types::types::CallOriginOutput;
39+
use bridge_types::EVMChainId;
40+
use bridge_types::H256;
41+
use currencies::Pallet as Currencies;
42+
use frame_benchmarking::{account, benchmarks, whitelisted_caller};
43+
use frame_support::traits::UnfilteredDispatchable;
44+
use frame_system::RawOrigin;
45+
use sp_std::prelude::*;
46+
use traits::MultiCurrency;
47+
48+
pub const BASE_NETWORK_ID: EVMChainId = EVMChainId::repeat_byte(1);
49+
50+
benchmarks! {
51+
where_clause {where
52+
<T as frame_system::Config>::RuntimeOrigin: From<dispatch::RawOrigin<CallOriginOutput<EVMChainId, H256, AdditionalEVMInboundData>>>,
53+
AssetNameOf<T>: From<Vec<u8>>,
54+
AssetSymbolOf<T>: From<Vec<u8>>,
55+
BalanceOf<T>: From<u128>,
56+
T: currencies::Config,
57+
Currencies<T>: MultiCurrency<T::AccountId, CurrencyId = AssetIdOf<T>, Balance = BalanceOf<T>>
58+
}
59+
60+
burn {
61+
let asset_id = <T as Config>::AssetRegistry::register_asset(BASE_NETWORK_ID.into(), b"ETH".to_vec().into(), b"ETH".to_vec().into())?;
62+
crate::Pallet::<T>::register_network_with_existing_asset(RawOrigin::Root.into(), BASE_NETWORK_ID, H160::repeat_byte(1), asset_id.clone(), 18).unwrap();
63+
let caller: T::AccountId = whitelisted_caller();
64+
let recipient = H160::repeat_byte(2);
65+
let amount = 1000u128;
66+
67+
Currencies::<T>::deposit(asset_id.clone(), &caller, amount.into())?;
68+
}: burn(RawOrigin::Signed(caller.clone()), BASE_NETWORK_ID, asset_id.clone(), recipient, amount.into())
69+
verify {
70+
assert_eq!(Currencies::<T>::free_balance(asset_id, &caller), 0u128.into());
71+
}
72+
73+
// Benchmark `mint` extrinsic under worst case conditions:
74+
// * `mint` successfully adds amount to recipient account
75+
mint {
76+
let asset_id = <T as Config>::AssetRegistry::register_asset(BASE_NETWORK_ID.into(), b"ETH".to_vec().into(), b"ETH".to_vec().into())?;
77+
crate::Pallet::<T>::register_network_with_existing_asset(RawOrigin::Root.into(), BASE_NETWORK_ID, H160::repeat_byte(1), asset_id.clone(), 18).unwrap();
78+
let asset_kind = AssetKinds::<T>::get(BASE_NETWORK_ID, &asset_id).unwrap();
79+
let caller = AppAddresses::<T>::get(BASE_NETWORK_ID).unwrap();
80+
let origin = dispatch::RawOrigin::new(CallOriginOutput {network_id: BASE_NETWORK_ID, additional: AdditionalEVMInboundData{source: caller}, ..Default::default()});
81+
82+
let recipient: T::AccountId = account("recipient", 0, 0);
83+
let sender = H160::zero();
84+
let amount = 500u128;
85+
86+
let call = Call::<T>::mint { token: H160::zero(), sender, recipient: recipient.clone(), amount: amount.into()};
87+
88+
}: { call.dispatch_bypass_filter(origin.into())? }
89+
verify {
90+
assert_eq!(Currencies::<T>::free_balance(asset_id, &recipient), amount.into());
91+
}
92+
93+
register_network {
94+
let address = H160::repeat_byte(98);
95+
let network_id = BASE_NETWORK_ID;
96+
let asset_name = b"ETH".to_vec();
97+
let asset_symbol = b"ETH".to_vec();
98+
assert!(!AppAddresses::<T>::contains_key(network_id));
99+
}: _(RawOrigin::Root, network_id, address, asset_symbol.into(), asset_name.into(), 18)
100+
verify {
101+
assert!(AppAddresses::<T>::contains_key(network_id));
102+
}
103+
104+
register_network_with_existing_asset {
105+
let address = H160::repeat_byte(98);
106+
let network_id = BASE_NETWORK_ID;
107+
let asset_id = <T as Config>::AssetRegistry::register_asset(BASE_NETWORK_ID.into(), b"ETH".to_vec().into(), b"ETH".to_vec().into())?;
108+
assert!(!AppAddresses::<T>::contains_key(network_id));
109+
}: _(RawOrigin::Root, network_id, address, asset_id, 18)
110+
verify {
111+
assert!(AppAddresses::<T>::contains_key(network_id));
112+
}
113+
114+
claim_relayer_fees {
115+
let asset_id = <T as Config>::AssetRegistry::register_asset(BASE_NETWORK_ID.into(), b"ETH".to_vec().into(), b"ETH".to_vec().into())?;
116+
crate::Pallet::<T>::register_network_with_existing_asset(RawOrigin::Root.into(), BASE_NETWORK_ID, H160::repeat_byte(1), asset_id.clone(), 18).unwrap();
117+
let caller: T::AccountId = whitelisted_caller();
118+
let claimer: T::AccountId = account("claimer", 0, 0);
119+
let address = H160::repeat_byte(98);
120+
let message = crate::Pallet::<T>::get_claim_prehashed_message(BASE_NETWORK_ID, &claimer);
121+
let pk = sp_io::crypto::ecdsa_generate(11u32.into(), None);
122+
let signature = sp_io::crypto::ecdsa_sign_prehashed(11u32.into(), &pk, &message.0).unwrap();
123+
124+
// We need to have full public key to get Ethereum address, but sp_core public key don't have such conversion method.
125+
let pk = sp_io::crypto::secp256k1_ecdsa_recover(&signature.0, &message.0).map_err(|_| "Failed to recover signature").unwrap();
126+
let relayer = H160::from_slice(&sp_io::hashing::keccak_256(&pk)[12..]);
127+
128+
let network_id = BASE_NETWORK_ID;
129+
crate::Pallet::<T>::update_base_fee(BASE_NETWORK_ID, 10u64.into(), 1u64);
130+
Currencies::<T>::deposit(asset_id.clone(), &caller, 1_000_000_000_000_000_000u128.into())?;
131+
crate::Pallet::<T>::withdraw_transfer_fee(&caller, BASE_NETWORK_ID, asset_id.clone())?;
132+
crate::Pallet::<T>::on_fee_paid(BASE_NETWORK_ID, relayer, 100u64.into());
133+
}: _(RawOrigin::Signed(claimer.clone()), network_id, relayer, signature)
134+
verify {
135+
assert_eq!(Currencies::<T>::free_balance(asset_id, &claimer), 100u128.into());
136+
}
137+
138+
register_existing_sidechain_asset {
139+
let base_asset_id = <T as Config>::AssetRegistry::register_asset(BASE_NETWORK_ID.into(), b"ETH".to_vec().into(), b"ETH".to_vec().into())?;
140+
crate::Pallet::<T>::register_network_with_existing_asset(RawOrigin::Root.into(), BASE_NETWORK_ID, H160::repeat_byte(1), base_asset_id, 18).unwrap();
141+
let asset_id = <T as Config>::AssetRegistry::register_asset(BASE_NETWORK_ID.into(), b"ETH".to_vec().into(), b"ETH".to_vec().into())?;
142+
let token = H160::repeat_byte(2);
143+
assert!(!AssetsByAddresses::<T>::contains_key(BASE_NETWORK_ID, token));
144+
}: _(RawOrigin::Root, BASE_NETWORK_ID, token, asset_id, 18)
145+
verify {
146+
assert!(AssetsByAddresses::<T>::contains_key(BASE_NETWORK_ID, token));
147+
}
148+
149+
register_sidechain_asset {
150+
let base_asset_id = <T as Config>::AssetRegistry::register_asset(BASE_NETWORK_ID.into(), b"ETH".to_vec().into(), b"ETH".to_vec().into())?;
151+
crate::Pallet::<T>::register_network_with_existing_asset(RawOrigin::Root.into(), BASE_NETWORK_ID, H160::repeat_byte(1), base_asset_id, 18).unwrap();
152+
let asset_id = <T as Config>::AssetRegistry::register_asset(BASE_NETWORK_ID.into(), b"ETH".to_vec().into(), b"ETH".to_vec().into())?;
153+
let token = H160::repeat_byte(2);
154+
let asset_name = b"ETH".to_vec();
155+
let asset_symbol = b"ETH".to_vec();
156+
assert!(!AssetsByAddresses::<T>::contains_key(BASE_NETWORK_ID, token));
157+
}: _(RawOrigin::Root, BASE_NETWORK_ID, token, asset_symbol.into(), asset_name.into(), 18)
158+
verify {
159+
assert!(AssetsByAddresses::<T>::contains_key(BASE_NETWORK_ID, token));
160+
}
161+
162+
register_thischain_asset {
163+
let base_asset_id = <T as Config>::AssetRegistry::register_asset(BASE_NETWORK_ID.into(), b"ETH".to_vec().into(), b"ETH".to_vec().into())?;
164+
crate::Pallet::<T>::register_network_with_existing_asset(RawOrigin::Root.into(), BASE_NETWORK_ID, H160::repeat_byte(1), base_asset_id, 18).unwrap();
165+
let asset_id = <T as Config>::AssetRegistry::register_asset(BASE_NETWORK_ID.into(), b"ETH".to_vec().into(), b"ETH".to_vec().into())?;
166+
}: _(RawOrigin::Root, BASE_NETWORK_ID, asset_id)
167+
verify {
168+
}
169+
170+
register_asset_internal {
171+
let base_asset_id = <T as Config>::AssetRegistry::register_asset(BASE_NETWORK_ID.into(), b"ETH".to_vec().into(), b"ETH".to_vec().into())?;
172+
crate::Pallet::<T>::register_network_with_existing_asset(RawOrigin::Root.into(), BASE_NETWORK_ID, H160::repeat_byte(1), base_asset_id, 18).unwrap();
173+
let asset_id = <T as Config>::AssetRegistry::register_asset(BASE_NETWORK_ID.into(), b"DAI".to_vec().into(), b"DAI".to_vec().into())?;
174+
let who = AppAddresses::<T>::get(BASE_NETWORK_ID).unwrap();
175+
let origin = dispatch::RawOrigin::new(CallOriginOutput {network_id: BASE_NETWORK_ID, additional: AdditionalEVMInboundData{source: who}, ..Default::default()});
176+
let address = H160::repeat_byte(98);
177+
assert!(!TokenAddresses::<T>::contains_key(BASE_NETWORK_ID, &asset_id));
178+
}: _(origin, asset_id.clone(), address)
179+
verify {
180+
assert_eq!(AssetKinds::<T>::get(BASE_NETWORK_ID, &asset_id), Some(AssetKind::Thischain));
181+
assert!(TokenAddresses::<T>::contains_key(BASE_NETWORK_ID, &asset_id));
182+
}
183+
184+
impl_benchmark_test_suite!(Pallet, crate::mock::new_tester(), crate::mock::Test,);
185+
}

0 commit comments

Comments
 (0)