Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.

Commit 76ba7fd

Browse files
svyatoniktomusdrwHCastano
authored
Claim funds on Substrate chain by providing proof of funds locking on PoA chain (#91)
* ethereum exchange module * continue * continue * added tests for exchange module * moved * remove println * move again * fixes * removed redundant deps * cargo fmt * fund_locks_transaction_decode_works * cargo fmt --all * fix error processing * added some tracing to bridge modules * more tests * more tests * cargo fmt --all * kovan.rs -> exchange.rs * Update bin/node/runtime/src/exchange.rs Co-authored-by: Tomasz Drwięga <[email protected]> * added assumption doc * Airdrop -> DepositInto * AsIs -> Identity * OnTransactionSubmitted * Transfers::Key = Id * typo * Update bin/node/runtime/src/exchange.rs Co-authored-by: Tomasz Drwięga <[email protected]> * block+tx+proof -> proof { block, tx, proof } * cargo fmt --all * docs * check <-> verify * parse hex * extracted exchange primitives to separate crate * added docs to runtime::exchange module * Update bin/node/runtime/src/exchange.rs Co-authored-by: Hernando Castano <[email protected]> * typo * Update modules/currency-exchange/Cargo.toml Co-authored-by: Hernando Castano <[email protected]> * add docs to currency-exchange module * change tests names * cargo fmt --all * Update bin/node/runtime/src/exchange.rs Co-authored-by: Hernando Castano <[email protected]> * Update bin/node/runtime/src/exchange.rs Co-authored-by: Hernando Castano <[email protected]> * Update bin/node/runtime/src/exchange.rs Co-authored-by: Hernando Castano <[email protected]> * Update bin/node/runtime/src/exchange.rs Co-authored-by: Hernando Castano <[email protected]> * Update bin/node/runtime/src/exchange.rs Co-authored-by: Hernando Castano <[email protected]> * fixed verify_transaction_finalized for siblings of finalized blocks * cargo fmt --all * added double spend note * cargo fmt --all Co-authored-by: Tomasz Drwięga <[email protected]> Co-authored-by: Hernando Castano <[email protected]>
1 parent 1c17b5c commit 76ba7fd

File tree

16 files changed

+1332
-28
lines changed

16 files changed

+1332
-28
lines changed

Cargo.lock

Lines changed: 31 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ members = [
66
"modules/substrate",
77
"modules/ethereum",
88
"modules/ethereum-contract/builtin",
9+
"modules/currency-exchange",
910
"relays/ethereum",
1011
"relays/substrate",
1112
]

bin/node/runtime/Cargo.toml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ repository = "https://github.com/paritytech/parity-bridges-common/"
99
[dependencies]
1010
hex-literal = "0.2"
1111

12+
[dev-dependencies]
13+
ethereum-tx-sign = "3.0"
14+
1215
[dependencies.codec]
1316
package = "parity-scale-codec"
1417
version = "1.0.0"
@@ -38,6 +41,11 @@ version = "0.1.0"
3841
default-features = false
3942
path = "../../../modules/ethereum"
4043

44+
[dependencies.pallet-bridge-currency-exchange]
45+
version = "0.1.0"
46+
default-features = false
47+
path = "../../../modules/currency-exchange"
48+
4149
[dependencies.frame-support]
4250
version = "2.0.0-rc1"
4351
default-features = false
@@ -116,6 +124,11 @@ version = "0.1.0"
116124
default-features = false
117125
path = "../../../primitives/ethereum-poa"
118126

127+
[dependencies.sp-currency-exchange]
128+
version = "0.1.0"
129+
default-features = false
130+
path = "../../../primitives/currency-exchange"
131+
119132
[dependencies.sp-consensus-aura]
120133
version = "0.8.0-rc1"
121134
default-features = false
@@ -194,6 +207,7 @@ std = [
194207
"pallet-aura/std",
195208
"pallet-balances/std",
196209
"pallet-bridge-eth-poa/std",
210+
"pallet-bridge-currency-exchange/std",
197211
"codec/std",
198212
"frame-executive/std",
199213
"frame-support/std",
@@ -205,6 +219,7 @@ std = [
205219
"sp-api/std",
206220
"sp-block-builder/std",
207221
"sp-bridge-eth-poa/std",
222+
"sp-currency-exchange/std",
208223
"sp-consensus-aura/std",
209224
"sp-core/std",
210225
"sp-inherents/std",

bin/node/runtime/src/exchange.rs

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
// Copyright 2019-2020 Parity Technologies (UK) Ltd.
2+
// This file is part of Parity Bridges Common.
3+
4+
// Parity Bridges Common is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
9+
// Parity Bridges Common is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU General Public License for more details.
13+
14+
// You should have received a copy of the GNU General Public License
15+
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
16+
17+
//! Support for PoA -> Substrate native tokens exchange.
18+
//!
19+
//! If you want to exchange native PoA tokens for native Substrate
20+
//! chain tokens, you need to:
21+
//! 1) send some PoA tokens to `LOCK_FUNDS_ADDRESS` address on PoA chain. Data field of
22+
//! the transaction must be SCALE-encoded id of Substrate account that will receive
23+
//! funds on Substrate chain;
24+
//! 2) wait until the 'lock funds' transaction is mined on PoA chain;
25+
//! 3) wait until the block containing the 'lock funds' transaction is finalized on PoA chain;
26+
//! 4) wait until the required PoA header and its finality are provided
27+
//! to the PoA -> Substrate bridge module (it can be provided by you);
28+
//! 5) receive tokens by providing proof-of-inclusion of PoA transaction.
29+
30+
use codec::{Decode, Encode};
31+
use frame_support::RuntimeDebug;
32+
use hex_literal::hex;
33+
use pallet_bridge_currency_exchange::Blockchain;
34+
use sp_bridge_eth_poa::transaction_decode;
35+
use sp_currency_exchange::{
36+
Error as ExchangeError, LockFundsTransaction, MaybeLockFundsTransaction, Result as ExchangeResult,
37+
};
38+
use sp_std::vec::Vec;
39+
40+
/// Ethereum address where locked PoA funds must be sent to.
41+
const LOCK_FUNDS_ADDRESS: [u8; 20] = hex!("DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF");
42+
43+
/// Ethereum transaction inclusion proof.
44+
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug)]
45+
pub struct EthereumTransactionInclusionProof {
46+
/// Hash of the block with transaction.
47+
pub block: sp_core::H256,
48+
/// Index of the transaction within the block.
49+
pub index: u64,
50+
/// The proof itself (right now it is all RLP-encoded transactions of the block).
51+
pub proof: Vec<Vec<u8>>,
52+
}
53+
54+
/// We uniquely identify transfer by the pair (sender, nonce).
55+
///
56+
/// The assumption is that this pair will never appear more than once in
57+
/// transactions included into finalized blocks. This is obviously true
58+
/// for any existing eth-like chain (that keep current tx format), because
59+
/// otherwise transaction can be replayed over and over.
60+
#[derive(Encode, Decode, PartialEq, RuntimeDebug)]
61+
pub struct EthereumTransactionTag {
62+
/// Account that has locked funds.
63+
pub account: [u8; 20],
64+
/// Lock transaction nonce.
65+
pub nonce: sp_core::U256,
66+
}
67+
68+
/// Eth blockchain from runtime perspective.
69+
pub struct EthBlockchain;
70+
71+
impl Blockchain for EthBlockchain {
72+
type Transaction = Vec<u8>;
73+
type TransactionInclusionProof = EthereumTransactionInclusionProof;
74+
75+
fn verify_transaction_inclusion_proof(proof: &Self::TransactionInclusionProof) -> Option<Self::Transaction> {
76+
let is_transaction_finalized =
77+
crate::BridgeEthPoA::verify_transaction_finalized(proof.block, proof.index, &proof.proof);
78+
79+
if !is_transaction_finalized {
80+
return None;
81+
}
82+
83+
proof.proof.get(proof.index as usize).cloned()
84+
}
85+
}
86+
87+
/// Eth transaction from runtime perspective.
88+
pub struct EthTransaction;
89+
90+
impl MaybeLockFundsTransaction for EthTransaction {
91+
type Transaction = Vec<u8>;
92+
type Id = EthereumTransactionTag;
93+
type Recipient = crate::AccountId;
94+
type Amount = crate::Balance;
95+
96+
fn parse(
97+
raw_tx: &Self::Transaction,
98+
) -> ExchangeResult<LockFundsTransaction<Self::Id, Self::Recipient, Self::Amount>> {
99+
let tx = transaction_decode(raw_tx).map_err(|_| ExchangeError::InvalidTransaction)?;
100+
101+
// we only accept transactions sending funds directly to the pre-configured address
102+
if tx.to != Some(LOCK_FUNDS_ADDRESS.into()) {
103+
frame_support::debug::error!(
104+
target: "runtime",
105+
"Failed to parse fund locks transaction. Invalid peer recipient: {:?}",
106+
tx.to,
107+
);
108+
109+
return Err(ExchangeError::InvalidTransaction);
110+
}
111+
112+
let mut recipient_raw = sp_core::H256::default();
113+
match tx.payload.len() {
114+
32 => recipient_raw.as_fixed_bytes_mut().copy_from_slice(&tx.payload),
115+
len => {
116+
frame_support::debug::error!(
117+
target: "runtime",
118+
"Failed to parse fund locks transaction. Invalid recipient length: {}",
119+
len,
120+
);
121+
122+
return Err(ExchangeError::InvalidRecipient);
123+
}
124+
}
125+
let amount = tx.value.low_u128();
126+
127+
if tx.value != amount.into() {
128+
frame_support::debug::error!(
129+
target: "runtime",
130+
"Failed to parse fund locks transaction. Invalid amount: {}",
131+
tx.value,
132+
);
133+
134+
return Err(ExchangeError::InvalidAmount);
135+
}
136+
137+
Ok(LockFundsTransaction {
138+
id: EthereumTransactionTag {
139+
account: *tx.sender.as_fixed_bytes(),
140+
nonce: tx.nonce,
141+
},
142+
recipient: crate::AccountId::from(*recipient_raw.as_fixed_bytes()),
143+
amount,
144+
})
145+
}
146+
}
147+
148+
#[cfg(test)]
149+
mod tests {
150+
use super::*;
151+
use hex_literal::hex;
152+
153+
fn ferdie() -> crate::AccountId {
154+
hex!("1cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c").into()
155+
}
156+
157+
fn prepare_ethereum_transaction(editor: impl Fn(&mut ethereum_tx_sign::RawTransaction)) -> Vec<u8> {
158+
// prepare tx for OpenEthereum private dev chain:
159+
// chain id is 0x11
160+
// sender secret is 0x4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7
161+
let chain_id = 0x11_u64;
162+
let signer = hex!("4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7");
163+
let ferdie_id = ferdie();
164+
let ferdie_raw: &[u8; 32] = ferdie_id.as_ref();
165+
let mut eth_tx = ethereum_tx_sign::RawTransaction {
166+
nonce: 0.into(),
167+
to: Some(LOCK_FUNDS_ADDRESS.into()),
168+
value: 100.into(),
169+
gas: 100_000.into(),
170+
gas_price: 100_000.into(),
171+
data: ferdie_raw.to_vec(),
172+
};
173+
editor(&mut eth_tx);
174+
eth_tx.sign(&signer.into(), &chain_id)
175+
}
176+
177+
#[test]
178+
fn valid_transaction_accepted() {
179+
assert_eq!(
180+
EthTransaction::parse(&prepare_ethereum_transaction(|_| {})),
181+
Ok(LockFundsTransaction {
182+
id: EthereumTransactionTag {
183+
account: hex!("00a329c0648769a73afac7f9381e08fb43dbea72"),
184+
nonce: 0.into(),
185+
},
186+
recipient: ferdie(),
187+
amount: 100,
188+
}),
189+
);
190+
}
191+
192+
#[test]
193+
fn invalid_transaction_rejected() {
194+
assert_eq!(
195+
EthTransaction::parse(&Vec::new()),
196+
Err(ExchangeError::InvalidTransaction),
197+
);
198+
}
199+
200+
#[test]
201+
fn transaction_with_invalid_peer_recipient_rejected() {
202+
assert_eq!(
203+
EthTransaction::parse(&prepare_ethereum_transaction(|tx| {
204+
tx.to = None;
205+
})),
206+
Err(ExchangeError::InvalidTransaction),
207+
);
208+
}
209+
210+
#[test]
211+
fn transaction_with_invalid_recipient_rejected() {
212+
assert_eq!(
213+
EthTransaction::parse(&prepare_ethereum_transaction(|tx| {
214+
tx.data.clear();
215+
})),
216+
Err(ExchangeError::InvalidRecipient),
217+
);
218+
}
219+
220+
#[test]
221+
fn transaction_with_invalid_amount_rejected() {
222+
assert_eq!(
223+
EthTransaction::parse(&prepare_ethereum_transaction(|tx| {
224+
tx.value = sp_core::U256::from(u128::max_value()) + sp_core::U256::from(1);
225+
})),
226+
Err(ExchangeError::InvalidAmount),
227+
);
228+
}
229+
}

0 commit comments

Comments
 (0)