Skip to content

Commit be44f24

Browse files
author
zhizming-zhong
authored
Bidirectional token transfer (#740)
* Transfer from parachain to ethereum Use ChainSafe's solution to support cross-chain transfers from parachains to ether * integration tests * launch scripts * build chainBridge * give execution permission * build chainBridge * new test script * integration test compatible with docker * create dir if not exist * build docker image * change log path * update filterMode * remove useless code * adjust code * add README.md * adjust code * give execution permission * remove license comment * update scripts
1 parent 9c63f03 commit be44f24

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+75717
-455
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,5 @@ ts-tests/package-lock.json
1515
tags.lock
1616

1717
recipe.json
18+
19+
gethdata

Makefile

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ build-node-tryruntime:
8282
cargo build --locked --features try-runtime --release
8383

8484
# launch a local network
85+
.PHONY: launch-docker-bridge
86+
launch-docker-bridge:
87+
@./scripts/launch-local-bridge-docker.sh
8588

8689
.PHONY: launch-docker-litentry ## Launch a local litentry-parachain network with docker
8790
launch-docker-litentry: generate-docker-compose-litentry
@@ -99,19 +102,23 @@ launch-binary-litentry:
99102
launch-binary-litmus:
100103
@./scripts/launch-local-binary.sh litmus
101104

105+
.PHONY: launch-binary-rococo ## Launch a local rococo-parachain network with binaries
106+
launch-binary-rococo:
107+
@./scripts/launch-local-binary.sh rococo
108+
102109
# run tests
103110

104111
.PHONY: test-cargo-all ## cargo test --all
105112
test-cargo-all:
106113
@cargo test --release --all
107114

108115
.PHONY: test-ts-docker-litentry ## Run litentry ts tests with docker without clean-up
109-
test-ts-docker-litentry: launch-docker-litentry
110-
@./scripts/run-ts-test.sh
116+
test-ts-docker-litentry: launch-docker-litentry launch-docker-bridge
117+
@./scripts/run-ts-test.sh bridge
111118

112119
.PHONY: test-ts-docker-litmus ## Run litmus ts tests with docker without clean-up
113-
test-ts-docker-litmus: launch-docker-litmus
114-
@./scripts/run-ts-test.sh
120+
test-ts-docker-litmus: launch-docker-litmus launch-docker-bridge
121+
@./scripts/run-ts-test.sh bridge
115122

116123
.PHONY: test-ts-binary-litentry ## Run litentry ts tests with binary without clean-up
117124
test-ts-binary-litentry: launch-binary-litentry

docker/bridge.dockerfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
FROM golang:1.18
2+
3+
RUN go install github.com/litentry/ChainBridge/cmd/chainbridge@dev

pallets/bridge-transfer/src/lib.rs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@
1717
// Ensure we're `no_std` when compiling for Wasm.
1818
#![cfg_attr(not(feature = "std"), no_std)]
1919

20+
pub use pallet::*;
21+
2022
#[cfg(test)]
2123
mod mock;
2224
#[cfg(test)]
2325
mod tests;
2426

25-
pub use pallet::*;
26-
2727
#[frame_support::pallet]
2828
pub mod pallet {
2929
use frame_support::{
@@ -32,6 +32,7 @@ pub mod pallet {
3232
};
3333
use frame_system::pallet_prelude::*;
3434
use sp_runtime::traits::CheckedAdd;
35+
use sp_std::vec::Vec;
3536

3637
pub use pallet_bridge as bridge;
3738

@@ -68,7 +69,6 @@ pub mod pallet {
6869
}
6970

7071
#[pallet::event]
71-
// #[pallet::generate_deposit(pub(super) fn deposit_event)]
7272
pub enum Event<T: Config> {}
7373

7474
#[pallet::error]
@@ -92,6 +92,20 @@ pub mod pallet {
9292

9393
#[pallet::call]
9494
impl<T: Config> Pallet<T> {
95+
/// Transfers some amount of the native token to some recipient on a (whitelisted)
96+
/// destination chain.
97+
#[pallet::weight(195_000_000)]
98+
pub fn transfer_native(
99+
origin: OriginFor<T>,
100+
amount: bridge::BalanceOf<T>,
101+
recipient: Vec<u8>,
102+
dest_id: bridge::BridgeChainId,
103+
) -> DispatchResult {
104+
let source = ensure_signed(origin)?;
105+
let resource_id = T::NativeTokenResourceId::get();
106+
<bridge::Pallet<T>>::transfer_fungible(source, dest_id, resource_id, recipient, amount)
107+
}
108+
95109
/// Executes a simple currency transfer using the bridge account as the source
96110
#[pallet::weight(195_000_000)]
97111
pub fn transfer(

pallets/bridge-transfer/src/mock.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,14 +101,17 @@ impl pallet_balances::Config for Test {
101101
parameter_types! {
102102
pub const TestChainId: u8 = 5;
103103
pub const ProposalLifetime: u64 = 100;
104+
pub const TreasuryAccount:u64 = 0x8;
104105
}
105106

106107
impl bridge::Config for Test {
107108
type Event = Event;
108109
type BridgeCommitteeOrigin = frame_system::EnsureRoot<Self::AccountId>;
109110
type Proposal = Call;
110111
type BridgeChainId = TestChainId;
112+
type Currency = Balances;
111113
type ProposalLifetime = ProposalLifetime;
114+
type TreasuryAccount = TreasuryAccount;
112115
}
113116

114117
parameter_types! {
@@ -151,7 +154,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities {
151154
.assimilate_storage(&mut t)
152155
.unwrap();
153156
let mut ext = sp_io::TestExternalities::new(t);
154-
ext.execute_with(|| System::set_block_number(1));
157+
ext.execute_with(|| frame_system::Pallet::<Test>::set_block_number(1));
155158
ext
156159
}
157160

pallets/bridge-transfer/src/tests.rs

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ use super::{
2020
bridge,
2121
mock::{
2222
assert_events, balances, new_test_ext, Balances, Bridge, BridgeTransfer, Call, Event,
23-
NativeTokenResourceId, Origin, ProposalLifetime, Test, ENDOWED_BALANCE, MAXIMUM_ISSURANCE,
24-
RELAYER_A, RELAYER_B, RELAYER_C,
23+
NativeTokenResourceId, Origin, ProposalLifetime, Test, TreasuryAccount, ENDOWED_BALANCE,
24+
MAXIMUM_ISSURANCE, RELAYER_A, RELAYER_B, RELAYER_C,
2525
},
2626
*,
2727
};
@@ -68,6 +68,35 @@ fn transfer() {
6868
})
6969
}
7070

71+
#[test]
72+
fn transfer_native() {
73+
new_test_ext().execute_with(|| {
74+
let dest_bridge_id: bridge::BridgeChainId = 0;
75+
let resource_id = NativeTokenResourceId::get();
76+
let dest_account: Vec<u8> = vec![1];
77+
assert_ok!(pallet_bridge::Pallet::<Test>::update_fee(Origin::root(), dest_bridge_id, 10));
78+
assert_ok!(pallet_bridge::Pallet::<Test>::whitelist_chain(Origin::root(), dest_bridge_id));
79+
assert_ok!(Pallet::<Test>::transfer_native(
80+
Origin::signed(RELAYER_A),
81+
100,
82+
dest_account.clone(),
83+
dest_bridge_id
84+
));
85+
assert_eq!(pallet_balances::Pallet::<Test>::free_balance(&TreasuryAccount::get()), 10);
86+
assert_eq!(
87+
pallet_balances::Pallet::<Test>::free_balance(&RELAYER_A),
88+
ENDOWED_BALANCE - 100
89+
);
90+
assert_events(vec![Event::Bridge(bridge::Event::FungibleTransfer(
91+
dest_bridge_id,
92+
1,
93+
resource_id,
94+
100 - 10,
95+
dest_account,
96+
))]);
97+
})
98+
}
99+
71100
#[test]
72101
fn mint_overflow() {
73102
new_test_ext().execute_with(|| {

pallets/bridge/src/lib.rs

Lines changed: 102 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ pub use pallet::*;
2828
#[frame_support::pallet]
2929
pub mod pallet {
3030
use codec::{Decode, Encode, EncodeLike};
31+
use frame_support::traits::{Currency, ExistenceRequirement::AllowDeath, WithdrawReasons};
3132
pub use frame_support::{
3233
pallet_prelude::*, traits::StorageVersion, weights::GetDispatchInfo, PalletId, Parameter,
3334
};
@@ -36,10 +37,9 @@ pub mod pallet {
3637
{self as system},
3738
};
3839
use scale_info::TypeInfo;
39-
pub use sp_core::U256;
4040
use sp_runtime::{
4141
traits::{AccountIdConversion, Dispatchable},
42-
RuntimeDebug,
42+
RuntimeDebug, SaturatedConversion,
4343
};
4444
use sp_std::prelude::*;
4545

@@ -49,6 +49,8 @@ pub mod pallet {
4949
pub type BridgeChainId = u8;
5050
pub type DepositNonce = u64;
5151
pub type ResourceId = [u8; 32];
52+
pub type BalanceOf<T> =
53+
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
5254

5355
/// Helper function to concatenate a chain ID and some bytes to produce a resource ID.
5456
/// The common format is (31 bytes unique ID + 1 byte chain ID).
@@ -83,7 +85,7 @@ pub mod pallet {
8385

8486
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
8587
pub enum BridgeEvent {
86-
FungibleTransfer(BridgeChainId, DepositNonce, ResourceId, U256, Vec<u8>),
88+
FungibleTransfer(BridgeChainId, DepositNonce, ResourceId, u128, Vec<u8>),
8789
NonFungibleTransfer(BridgeChainId, DepositNonce, ResourceId, Vec<u8>, Vec<u8>, Vec<u8>),
8890
GenericTransfer(BridgeChainId, DepositNonce, ResourceId, Vec<u8>),
8991
}
@@ -154,8 +156,14 @@ pub mod pallet {
154156
#[pallet::constant]
155157
type BridgeChainId: Get<BridgeChainId>;
156158

159+
/// Currency impl
160+
type Currency: Currency<Self::AccountId>;
161+
157162
#[pallet::constant]
158163
type ProposalLifetime: Get<Self::BlockNumber>;
164+
165+
/// Treasury account to receive assets fee
166+
type TreasuryAccount: Get<Self::AccountId>;
159167
}
160168

161169
#[pallet::event]
@@ -171,7 +179,7 @@ pub mod pallet {
171179
RelayerRemoved(T::AccountId),
172180
/// FungibleTransfer is for relaying fungibles (dest_id, nonce, resource_id, amount,
173181
/// recipient)
174-
FungibleTransfer(BridgeChainId, DepositNonce, ResourceId, U256, Vec<u8>),
182+
FungibleTransfer(BridgeChainId, DepositNonce, ResourceId, u128, Vec<u8>),
175183
/// NonFungibleTransfer is for relaying NFTs (dest_id, nonce, resource_id, token_id,
176184
/// recipient, metadata)
177185
NonFungibleTransfer(BridgeChainId, DepositNonce, ResourceId, Vec<u8>, Vec<u8>, Vec<u8>),
@@ -189,6 +197,8 @@ pub mod pallet {
189197
ProposalSucceeded(BridgeChainId, DepositNonce),
190198
/// Execution of call failed
191199
ProposalFailed(BridgeChainId, DepositNonce),
200+
/// Update bridge transfer fee
201+
FeeUpdated { dest_id: BridgeChainId, fee: BalanceOf<T> },
192202
}
193203

194204
#[pallet::error]
@@ -223,6 +233,16 @@ pub mod pallet {
223233
ProposalAlreadyComplete,
224234
/// Lifetime of proposal has been exceeded
225235
ProposalExpired,
236+
/// Too expensive fee for withdrawn asset
237+
FeeTooExpensive,
238+
/// No fee with the ID was found
239+
FeeDoesNotExist,
240+
/// Balance too low to withdraw
241+
InsufficientBalance,
242+
243+
CannotPayAsFee,
244+
245+
NonceOverFlow,
226246
}
227247

228248
#[pallet::storage]
@@ -261,10 +281,17 @@ pub mod pallet {
261281
#[pallet::getter(fn resources)]
262282
pub type Resources<T> = StorageMap<_, Blake2_256, ResourceId, Vec<u8>>;
263283

284+
// ChainBridge Service(https://github.com/litentry/ChainBridge) read this storage for each block,
285+
// and if this storage has value, it will perform cross-chain transfer.
286+
// For more details, see at: https://github.com/litentry/ChainBridge/blob/main/chains/substrate/listener.go#L186-L237
264287
#[pallet::storage]
265288
#[pallet::getter(fn bridge_events)]
266289
pub type BridgeEvents<T> = StorageValue<_, Vec<BridgeEvent>, ValueQuery>;
267290

291+
#[pallet::storage]
292+
#[pallet::getter(fn bridge_fee)]
293+
pub type BridgeFee<T: Config> = StorageMap<_, Twox64Concat, BridgeChainId, BalanceOf<T>>;
294+
268295
#[pallet::hooks]
269296
impl<T: Config> Hooks<T::BlockNumber> for Pallet<T> {
270297
fn on_initialize(_n: T::BlockNumber) -> Weight {
@@ -352,6 +379,23 @@ pub mod pallet {
352379
Self::unregister_relayer(v)
353380
}
354381

382+
/// Change extra bridge transfer fee that user should pay
383+
///
384+
/// # <weight>
385+
/// - O(1) lookup and insert
386+
/// # </weight>
387+
#[pallet::weight(195_000_000)]
388+
pub fn update_fee(
389+
origin: OriginFor<T>,
390+
dest_id: BridgeChainId,
391+
fee: BalanceOf<T>,
392+
) -> DispatchResult {
393+
T::BridgeCommitteeOrigin::ensure_origin(origin)?;
394+
BridgeFee::<T>::insert(dest_id, fee);
395+
Self::deposit_event(Event::FeeUpdated { dest_id, fee });
396+
Ok(())
397+
}
398+
355399
/// Commits a vote in favour of the provided proposal.
356400
///
357401
/// If a proposal with the given nonce and source chain ID does not already exist, it will
@@ -448,6 +492,16 @@ pub mod pallet {
448492
Self::chains(id).is_some()
449493
}
450494

495+
/// Increments the deposit nonce for the specified chain ID
496+
fn bump_nonce(id: BridgeChainId) -> Result<DepositNonce, Error<T>> {
497+
let nonce = Self::chains(id).unwrap_or_default();
498+
let new_nonce = nonce.checked_add(1u64).ok_or(Error::<T>::NonceOverFlow);
499+
if new_nonce.is_ok() {
500+
ChainNonces::<T>::insert(id, new_nonce.as_ref().unwrap());
501+
}
502+
new_nonce
503+
}
504+
451505
// *** Admin methods ***
452506

453507
/// Set a new voting threshold
@@ -603,6 +657,50 @@ pub mod pallet {
603657
Self::deposit_event(Event::ProposalRejected(src_id, nonce));
604658
Ok(())
605659
}
660+
661+
/// Initiates a transfer of a fungible asset out of the chain. This should be called by
662+
/// another pallet.
663+
pub fn transfer_fungible(
664+
sender: T::AccountId,
665+
dest_id: BridgeChainId,
666+
resource_id: ResourceId,
667+
to: Vec<u8>,
668+
amount: BalanceOf<T>,
669+
) -> DispatchResult {
670+
ensure!(Self::chain_whitelisted(dest_id), Error::<T>::ChainNotWhitelisted);
671+
let fee: BalanceOf<T> =
672+
BridgeFee::<T>::get(dest_id).ok_or(Error::<T>::CannotPayAsFee)?;
673+
// No need to transfer to to dest chains if it's not enough to pay fee.
674+
ensure!(amount > fee, Error::<T>::FeeTooExpensive);
675+
676+
let actual_amount = amount - fee;
677+
// Ensure we have sufficient free balance
678+
let balance: BalanceOf<T> = T::Currency::free_balance(&sender);
679+
ensure!(balance >= amount, Error::<T>::InsufficientBalance);
680+
681+
T::Currency::withdraw(&sender, actual_amount, WithdrawReasons::TRANSFER, AllowDeath)?;
682+
T::Currency::burn(actual_amount);
683+
684+
// transfer fee to treasury
685+
T::Currency::transfer(&sender, &T::TreasuryAccount::get(), fee, AllowDeath)?;
686+
687+
let nonce = Self::bump_nonce(dest_id)?;
688+
BridgeEvents::<T>::append(BridgeEvent::FungibleTransfer(
689+
dest_id,
690+
nonce,
691+
resource_id,
692+
actual_amount.saturated_into::<u128>(),
693+
to.clone(),
694+
));
695+
Self::deposit_event(Event::FungibleTransfer(
696+
dest_id,
697+
nonce,
698+
resource_id,
699+
actual_amount.saturated_into::<u128>(),
700+
to,
701+
));
702+
Ok(())
703+
}
606704
}
607705

608706
/// Simple ensure origin for the bridge account

0 commit comments

Comments
 (0)