Skip to content

Commit

Permalink
Claim funds on Substrate chain by providing proof of funds locking on…
Browse files Browse the repository at this point in the history
… PoA chain (paritytech#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]>
  • Loading branch information
3 people authored and bkchr committed Apr 10, 2024
1 parent a7e7c89 commit 7294ea4
Show file tree
Hide file tree
Showing 14 changed files with 1,300 additions and 26 deletions.
15 changes: 15 additions & 0 deletions bridges/bin/node/runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ repository = "https://github.com/paritytech/parity-bridges-common/"
[dependencies]
hex-literal = "0.2"

[dev-dependencies]
ethereum-tx-sign = "3.0"

[dependencies.codec]
package = "parity-scale-codec"
version = "1.0.0"
Expand Down Expand Up @@ -38,6 +41,11 @@ version = "0.1.0"
default-features = false
path = "../../../modules/ethereum"

[dependencies.pallet-bridge-currency-exchange]
version = "0.1.0"
default-features = false
path = "../../../modules/currency-exchange"

[dependencies.frame-support]
version = "2.0.0-rc1"
default-features = false
Expand Down Expand Up @@ -116,6 +124,11 @@ version = "0.1.0"
default-features = false
path = "../../../primitives/ethereum-poa"

[dependencies.sp-currency-exchange]
version = "0.1.0"
default-features = false
path = "../../../primitives/currency-exchange"

[dependencies.sp-consensus-aura]
version = "0.8.0-rc1"
default-features = false
Expand Down Expand Up @@ -194,6 +207,7 @@ std = [
"pallet-aura/std",
"pallet-balances/std",
"pallet-bridge-eth-poa/std",
"pallet-bridge-currency-exchange/std",
"codec/std",
"frame-executive/std",
"frame-support/std",
Expand All @@ -205,6 +219,7 @@ std = [
"sp-api/std",
"sp-block-builder/std",
"sp-bridge-eth-poa/std",
"sp-currency-exchange/std",
"sp-consensus-aura/std",
"sp-core/std",
"sp-inherents/std",
Expand Down
229 changes: 229 additions & 0 deletions bridges/bin/node/runtime/src/exchange.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
// Copyright 2019-2020 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.

// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.

//! Support for PoA -> Substrate native tokens exchange.
//!
//! If you want to exchange native PoA tokens for native Substrate
//! chain tokens, you need to:
//! 1) send some PoA tokens to `LOCK_FUNDS_ADDRESS` address on PoA chain. Data field of
//! the transaction must be SCALE-encoded id of Substrate account that will receive
//! funds on Substrate chain;
//! 2) wait until the 'lock funds' transaction is mined on PoA chain;
//! 3) wait until the block containing the 'lock funds' transaction is finalized on PoA chain;
//! 4) wait until the required PoA header and its finality are provided
//! to the PoA -> Substrate bridge module (it can be provided by you);
//! 5) receive tokens by providing proof-of-inclusion of PoA transaction.
use codec::{Decode, Encode};
use frame_support::RuntimeDebug;
use hex_literal::hex;
use pallet_bridge_currency_exchange::Blockchain;
use sp_bridge_eth_poa::transaction_decode;
use sp_currency_exchange::{
Error as ExchangeError, LockFundsTransaction, MaybeLockFundsTransaction, Result as ExchangeResult,
};
use sp_std::vec::Vec;

/// Ethereum address where locked PoA funds must be sent to.
const LOCK_FUNDS_ADDRESS: [u8; 20] = hex!("DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF");

/// Ethereum transaction inclusion proof.
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug)]
pub struct EthereumTransactionInclusionProof {
/// Hash of the block with transaction.
pub block: sp_core::H256,
/// Index of the transaction within the block.
pub index: u64,
/// The proof itself (right now it is all RLP-encoded transactions of the block).
pub proof: Vec<Vec<u8>>,
}

/// We uniquely identify transfer by the pair (sender, nonce).
///
/// The assumption is that this pair will never appear more than once in
/// transactions included into finalized blocks. This is obviously true
/// for any existing eth-like chain (that keep current tx format), because
/// otherwise transaction can be replayed over and over.
#[derive(Encode, Decode, PartialEq, RuntimeDebug)]
pub struct EthereumTransactionTag {
/// Account that has locked funds.
pub account: [u8; 20],
/// Lock transaction nonce.
pub nonce: sp_core::U256,
}

/// Eth blockchain from runtime perspective.
pub struct EthBlockchain;

impl Blockchain for EthBlockchain {
type Transaction = Vec<u8>;
type TransactionInclusionProof = EthereumTransactionInclusionProof;

fn verify_transaction_inclusion_proof(proof: &Self::TransactionInclusionProof) -> Option<Self::Transaction> {
let is_transaction_finalized =
crate::BridgeEthPoA::verify_transaction_finalized(proof.block, proof.index, &proof.proof);

if !is_transaction_finalized {
return None;
}

proof.proof.get(proof.index as usize).cloned()
}
}

/// Eth transaction from runtime perspective.
pub struct EthTransaction;

impl MaybeLockFundsTransaction for EthTransaction {
type Transaction = Vec<u8>;
type Id = EthereumTransactionTag;
type Recipient = crate::AccountId;
type Amount = crate::Balance;

fn parse(
raw_tx: &Self::Transaction,
) -> ExchangeResult<LockFundsTransaction<Self::Id, Self::Recipient, Self::Amount>> {
let tx = transaction_decode(raw_tx).map_err(|_| ExchangeError::InvalidTransaction)?;

// we only accept transactions sending funds directly to the pre-configured address
if tx.to != Some(LOCK_FUNDS_ADDRESS.into()) {
frame_support::debug::error!(
target: "runtime",
"Failed to parse fund locks transaction. Invalid peer recipient: {:?}",
tx.to,
);

return Err(ExchangeError::InvalidTransaction);
}

let mut recipient_raw = sp_core::H256::default();
match tx.payload.len() {
32 => recipient_raw.as_fixed_bytes_mut().copy_from_slice(&tx.payload),
len => {
frame_support::debug::error!(
target: "runtime",
"Failed to parse fund locks transaction. Invalid recipient length: {}",
len,
);

return Err(ExchangeError::InvalidRecipient);
}
}
let amount = tx.value.low_u128();

if tx.value != amount.into() {
frame_support::debug::error!(
target: "runtime",
"Failed to parse fund locks transaction. Invalid amount: {}",
tx.value,
);

return Err(ExchangeError::InvalidAmount);
}

Ok(LockFundsTransaction {
id: EthereumTransactionTag {
account: *tx.sender.as_fixed_bytes(),
nonce: tx.nonce,
},
recipient: crate::AccountId::from(*recipient_raw.as_fixed_bytes()),
amount,
})
}
}

#[cfg(test)]
mod tests {
use super::*;
use hex_literal::hex;

fn ferdie() -> crate::AccountId {
hex!("1cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c").into()
}

fn prepare_ethereum_transaction(editor: impl Fn(&mut ethereum_tx_sign::RawTransaction)) -> Vec<u8> {
// prepare tx for OpenEthereum private dev chain:
// chain id is 0x11
// sender secret is 0x4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7
let chain_id = 0x11_u64;
let signer = hex!("4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7");
let ferdie_id = ferdie();
let ferdie_raw: &[u8; 32] = ferdie_id.as_ref();
let mut eth_tx = ethereum_tx_sign::RawTransaction {
nonce: 0.into(),
to: Some(LOCK_FUNDS_ADDRESS.into()),
value: 100.into(),
gas: 100_000.into(),
gas_price: 100_000.into(),
data: ferdie_raw.to_vec(),
};
editor(&mut eth_tx);
eth_tx.sign(&signer.into(), &chain_id)
}

#[test]
fn valid_transaction_accepted() {
assert_eq!(
EthTransaction::parse(&prepare_ethereum_transaction(|_| {})),
Ok(LockFundsTransaction {
id: EthereumTransactionTag {
account: hex!("00a329c0648769a73afac7f9381e08fb43dbea72"),
nonce: 0.into(),
},
recipient: ferdie(),
amount: 100,
}),
);
}

#[test]
fn invalid_transaction_rejected() {
assert_eq!(
EthTransaction::parse(&Vec::new()),
Err(ExchangeError::InvalidTransaction),
);
}

#[test]
fn transaction_with_invalid_peer_recipient_rejected() {
assert_eq!(
EthTransaction::parse(&prepare_ethereum_transaction(|tx| {
tx.to = None;
})),
Err(ExchangeError::InvalidTransaction),
);
}

#[test]
fn transaction_with_invalid_recipient_rejected() {
assert_eq!(
EthTransaction::parse(&prepare_ethereum_transaction(|tx| {
tx.data.clear();
})),
Err(ExchangeError::InvalidRecipient),
);
}

#[test]
fn transaction_with_invalid_amount_rejected() {
assert_eq!(
EthTransaction::parse(&prepare_ethereum_transaction(|tx| {
tx.value = sp_core::U256::from(u128::max_value()) + sp_core::U256::from(1);
})),
Err(ExchangeError::InvalidAmount),
);
}
}
46 changes: 45 additions & 1 deletion bridges/bin/node/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
#[cfg(feature = "std")]
include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs"));

mod exchange;

pub mod kovan;

use codec::{Decode, Encode};
Expand All @@ -47,11 +49,12 @@ use sp_version::RuntimeVersion;
// A few exports that help ease life for downstream crates.
pub use frame_support::{
construct_runtime, parameter_types,
traits::{KeyOwnerProofSystem, Randomness},
traits::{Currency, ExistenceRequirement, KeyOwnerProofSystem, Randomness},
weights::{IdentityFee, RuntimeDbWeight, Weight},
StorageValue,
};
pub use pallet_balances::Call as BalancesCall;
pub use pallet_bridge_currency_exchange::Call as BridgeCurrencyExchangeCall;
pub use pallet_bridge_eth_poa::Call as BridgeEthPoACall;
pub use pallet_timestamp::Call as TimestampCall;
#[cfg(any(feature = "std", test))]
Expand Down Expand Up @@ -223,6 +226,46 @@ impl pallet_bridge_eth_poa::Trait for Runtime {
type OnHeadersSubmitted = ();
}

impl pallet_bridge_currency_exchange::Trait for Runtime {
type OnTransactionSubmitted = ();
type PeerBlockchain = exchange::EthBlockchain;
type PeerMaybeLockFundsTransaction = exchange::EthTransaction;
type RecipientsMap = sp_currency_exchange::IdentityRecipients<AccountId>;
type Amount = Balance;
type CurrencyConverter = sp_currency_exchange::IdentityCurrencyConverter<Balance>;
type DepositInto = DepositInto;
}

pub struct DepositInto;

impl sp_currency_exchange::DepositInto for DepositInto {
type Recipient = AccountId;
type Amount = Balance;

fn deposit_into(recipient: Self::Recipient, amount: Self::Amount) -> sp_currency_exchange::Result<()> {
<pallet_balances::Module<Runtime> as Currency<AccountId>>::deposit_into_existing(&recipient, amount)
.map(|_| {
frame_support::debug::trace!(
target: "runtime",
"Deposited {} to {:?}",
amount,
recipient,
);
})
.map_err(|e| {
frame_support::debug::error!(
target: "runtime",
"Deposit of {} to {:?} has failed with: {:?}",
amount,
recipient,
e
);

sp_currency_exchange::Error::DepositFailed
})
}
}

impl pallet_grandpa::Trait for Runtime {
type Event = Event;
type Call = Call;
Expand Down Expand Up @@ -364,6 +407,7 @@ construct_runtime!(
Sudo: pallet_sudo::{Module, Call, Config<T>, Storage, Event<T>},
Session: pallet_session::{Module, Call, Storage, Event, Config<T>},
BridgeEthPoA: pallet_bridge_eth_poa::{Module, Call, Config, Storage, ValidateUnsigned},
BridgeCurrencyExchange: pallet_bridge_currency_exchange::{Module, Call},
}
);

Expand Down
Loading

0 comments on commit 7294ea4

Please sign in to comment.