From 82b2d1cbab576f34170a44a978008d575acc548e Mon Sep 17 00:00:00 2001 From: "Muhammad-Jibril B.A. (Khalifa MBA)" Date: Sat, 16 Mar 2024 22:48:31 +0800 Subject: [PATCH] move `primitives` out of `blockchain/` --- .../primitives => primitives}/Cargo.toml | 0 {blockchain/primitives => primitives}/TODO.md | 0 .../primitives => primitives}/src/aleph.rs | 0 .../src/bonding/controller.rs | 344 ++--- .../src/bonding/error.rs | 56 +- .../src/bonding/ledger.rs | 890 +++++------ .../src/bonding/mod.rs | 54 +- .../primitives => primitives}/src/currency.rs | 2 +- .../src/edfis_launchpad.rs | 0 .../primitives => primitives}/src/evm.rs | 640 ++++---- .../primitives => primitives}/src/lib.rs | 0 .../primitives => primitives}/src/nft.rs | 150 +- .../src/signature.rs | 254 ++-- .../primitives => primitives}/src/task.rs | 142 +- .../primitives => primitives}/src/testing.rs | 158 +- .../primitives => primitives}/src/tests.rs | 452 +++--- .../src/unchecked_extrinsic.rs | 1350 ++++++++--------- 17 files changed, 2246 insertions(+), 2246 deletions(-) rename {blockchain/primitives => primitives}/Cargo.toml (100%) rename {blockchain/primitives => primitives}/TODO.md (100%) rename {blockchain/primitives => primitives}/src/aleph.rs (100%) rename {blockchain/primitives => primitives}/src/bonding/controller.rs (96%) rename {blockchain/primitives => primitives}/src/bonding/error.rs (97%) rename {blockchain/primitives => primitives}/src/bonding/ledger.rs (95%) rename {blockchain/primitives => primitives}/src/bonding/mod.rs (97%) rename {blockchain/primitives => primitives}/src/currency.rs (98%) rename {blockchain/primitives => primitives}/src/edfis_launchpad.rs (100%) rename {blockchain/primitives => primitives}/src/evm.rs (97%) rename {blockchain/primitives => primitives}/src/lib.rs (100%) rename {blockchain/primitives => primitives}/src/nft.rs (97%) rename {blockchain/primitives => primitives}/src/signature.rs (96%) rename {blockchain/primitives => primitives}/src/task.rs (96%) rename {blockchain/primitives => primitives}/src/testing.rs (96%) rename {blockchain/primitives => primitives}/src/tests.rs (96%) rename {blockchain/primitives => primitives}/src/unchecked_extrinsic.rs (97%) diff --git a/blockchain/primitives/Cargo.toml b/primitives/Cargo.toml similarity index 100% rename from blockchain/primitives/Cargo.toml rename to primitives/Cargo.toml diff --git a/blockchain/primitives/TODO.md b/primitives/TODO.md similarity index 100% rename from blockchain/primitives/TODO.md rename to primitives/TODO.md diff --git a/blockchain/primitives/src/aleph.rs b/primitives/src/aleph.rs similarity index 100% rename from blockchain/primitives/src/aleph.rs rename to primitives/src/aleph.rs diff --git a/blockchain/primitives/src/bonding/controller.rs b/primitives/src/bonding/controller.rs similarity index 96% rename from blockchain/primitives/src/bonding/controller.rs rename to primitives/src/bonding/controller.rs index ba9b064c..599d5384 100644 --- a/blockchain/primitives/src/bonding/controller.rs +++ b/primitives/src/bonding/controller.rs @@ -1,172 +1,172 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -use frame_support::{dispatch::DispatchResult, pallet_prelude::Member, traits::Get, Parameter, StorageMap}; -use parity_scale_codec::Codec; -use sp_runtime::DispatchError; -use sp_std::prelude::*; - -use super::error::Error; -use super::ledger::{BondingLedger, UnlockChunk}; -use crate::Balance; - -pub type BondingLedgerOf = BondingLedger< - ::Moment, - ::MaxUnbondingChunks, - ::MinBond, ->; - -pub struct BondChange { - pub new: Balance, - pub old: Balance, - pub change: Balance, -} -pub trait BondingController -where - BondingLedgerOf: Codec + Default, - frame_support::BoundedVec< - UnlockChunk<::Moment>, - ::MaxUnbondingChunks, - >: Codec, -{ - type MinBond: Get; - type MaxUnbondingChunks: Get; - type Moment: Ord + Eq + Copy; - type AccountId: Parameter + Member; - - type Ledger: StorageMap, Query = Option>>; - - fn available_balance(who: &Self::AccountId, ledger: &BondingLedgerOf) -> Balance; - fn apply_ledger(who: &Self::AccountId, ledger: &BondingLedgerOf) -> DispatchResult; - fn convert_error(err: Error) -> DispatchError; - - fn bond(who: &Self::AccountId, amount: Balance) -> Result, DispatchError> { - let ledger = Self::Ledger::get(who).unwrap_or_default(); - - let available = Self::available_balance(who, &ledger); - let bond_amount = amount.min(available); - - if bond_amount == 0 { - return Ok(None); - } - - let old_active = ledger.active(); - - let ledger = ledger.bond(bond_amount).map_err(Self::convert_error)?; - - Self::Ledger::insert(who, &ledger); - Self::apply_ledger(who, &ledger)?; - - Ok(Some(BondChange { - old: old_active, - new: ledger.active(), - change: bond_amount, - })) - } - - fn unbond(who: &Self::AccountId, amount: Balance, at: Self::Moment) -> Result, DispatchError> { - let ledger = Self::Ledger::get(who).ok_or_else(|| Self::convert_error(Error::NotBonded))?; - let old_active = ledger.active(); - - let (ledger, unbond_amount) = ledger.unbond(amount, at).map_err(Self::convert_error)?; - - if unbond_amount == 0 { - return Ok(None); - } - - Self::Ledger::insert(who, &ledger); - Self::apply_ledger(who, &ledger)?; - - Ok(Some(BondChange { - old: old_active, - new: ledger.active(), - change: unbond_amount, - })) - } - - fn unbond_instant(who: &Self::AccountId, amount: Balance) -> Result, DispatchError> { - let ledger = Self::Ledger::get(who).ok_or_else(|| Self::convert_error(Error::NotBonded))?; - let old_active = ledger.active(); - - let (ledger, unbond_amount) = ledger.unbond_instant(amount).map_err(Self::convert_error)?; - - if unbond_amount == 0 { - return Ok(None); - } - - Self::Ledger::insert(who, &ledger); - Self::apply_ledger(who, &ledger)?; - - Ok(Some(BondChange { - old: old_active, - new: ledger.active(), - change: unbond_amount, - })) - } - - fn rebond(who: &Self::AccountId, amount: Balance) -> Result, DispatchError> { - let ledger = Self::Ledger::get(who).ok_or_else(|| Self::convert_error(Error::NotBonded))?; - let old_active = ledger.active(); - - let (ledger, rebond_amount) = ledger.rebond(amount).map_err(Self::convert_error)?; - - if rebond_amount == 0 { - return Ok(None); - } - - Self::Ledger::insert(who, &ledger); - Self::apply_ledger(who, &ledger)?; - - Ok(Some(BondChange { - old: old_active, - new: ledger.active(), - change: rebond_amount, - })) - } - - fn withdraw_unbonded(who: &Self::AccountId, now: Self::Moment) -> Result, DispatchError> { - let ledger = Self::Ledger::get(who).ok_or_else(|| Self::convert_error(Error::NotBonded))?; - let old_total = ledger.total(); - - let ledger = ledger.consolidate_unlocked(now); - - let new_total = ledger.total(); - - let diff = old_total.saturating_sub(new_total); - - if diff == 0 { - return Ok(None); - } - - if new_total == 0 { - Self::Ledger::remove(who); - } else { - Self::Ledger::insert(who, &ledger); - } - - Self::apply_ledger(who, &ledger)?; - - Ok(Some(BondChange { - old: old_total, - new: new_total, - change: diff, - })) - } -} +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use frame_support::{dispatch::DispatchResult, pallet_prelude::Member, traits::Get, Parameter, StorageMap}; +use parity_scale_codec::Codec; +use sp_runtime::DispatchError; +use sp_std::prelude::*; + +use super::error::Error; +use super::ledger::{BondingLedger, UnlockChunk}; +use crate::Balance; + +pub type BondingLedgerOf = BondingLedger< + ::Moment, + ::MaxUnbondingChunks, + ::MinBond, +>; + +pub struct BondChange { + pub new: Balance, + pub old: Balance, + pub change: Balance, +} +pub trait BondingController +where + BondingLedgerOf: Codec + Default, + frame_support::BoundedVec< + UnlockChunk<::Moment>, + ::MaxUnbondingChunks, + >: Codec, +{ + type MinBond: Get; + type MaxUnbondingChunks: Get; + type Moment: Ord + Eq + Copy; + type AccountId: Parameter + Member; + + type Ledger: StorageMap, Query = Option>>; + + fn available_balance(who: &Self::AccountId, ledger: &BondingLedgerOf) -> Balance; + fn apply_ledger(who: &Self::AccountId, ledger: &BondingLedgerOf) -> DispatchResult; + fn convert_error(err: Error) -> DispatchError; + + fn bond(who: &Self::AccountId, amount: Balance) -> Result, DispatchError> { + let ledger = Self::Ledger::get(who).unwrap_or_default(); + + let available = Self::available_balance(who, &ledger); + let bond_amount = amount.min(available); + + if bond_amount == 0 { + return Ok(None); + } + + let old_active = ledger.active(); + + let ledger = ledger.bond(bond_amount).map_err(Self::convert_error)?; + + Self::Ledger::insert(who, &ledger); + Self::apply_ledger(who, &ledger)?; + + Ok(Some(BondChange { + old: old_active, + new: ledger.active(), + change: bond_amount, + })) + } + + fn unbond(who: &Self::AccountId, amount: Balance, at: Self::Moment) -> Result, DispatchError> { + let ledger = Self::Ledger::get(who).ok_or_else(|| Self::convert_error(Error::NotBonded))?; + let old_active = ledger.active(); + + let (ledger, unbond_amount) = ledger.unbond(amount, at).map_err(Self::convert_error)?; + + if unbond_amount == 0 { + return Ok(None); + } + + Self::Ledger::insert(who, &ledger); + Self::apply_ledger(who, &ledger)?; + + Ok(Some(BondChange { + old: old_active, + new: ledger.active(), + change: unbond_amount, + })) + } + + fn unbond_instant(who: &Self::AccountId, amount: Balance) -> Result, DispatchError> { + let ledger = Self::Ledger::get(who).ok_or_else(|| Self::convert_error(Error::NotBonded))?; + let old_active = ledger.active(); + + let (ledger, unbond_amount) = ledger.unbond_instant(amount).map_err(Self::convert_error)?; + + if unbond_amount == 0 { + return Ok(None); + } + + Self::Ledger::insert(who, &ledger); + Self::apply_ledger(who, &ledger)?; + + Ok(Some(BondChange { + old: old_active, + new: ledger.active(), + change: unbond_amount, + })) + } + + fn rebond(who: &Self::AccountId, amount: Balance) -> Result, DispatchError> { + let ledger = Self::Ledger::get(who).ok_or_else(|| Self::convert_error(Error::NotBonded))?; + let old_active = ledger.active(); + + let (ledger, rebond_amount) = ledger.rebond(amount).map_err(Self::convert_error)?; + + if rebond_amount == 0 { + return Ok(None); + } + + Self::Ledger::insert(who, &ledger); + Self::apply_ledger(who, &ledger)?; + + Ok(Some(BondChange { + old: old_active, + new: ledger.active(), + change: rebond_amount, + })) + } + + fn withdraw_unbonded(who: &Self::AccountId, now: Self::Moment) -> Result, DispatchError> { + let ledger = Self::Ledger::get(who).ok_or_else(|| Self::convert_error(Error::NotBonded))?; + let old_total = ledger.total(); + + let ledger = ledger.consolidate_unlocked(now); + + let new_total = ledger.total(); + + let diff = old_total.saturating_sub(new_total); + + if diff == 0 { + return Ok(None); + } + + if new_total == 0 { + Self::Ledger::remove(who); + } else { + Self::Ledger::insert(who, &ledger); + } + + Self::apply_ledger(who, &ledger)?; + + Ok(Some(BondChange { + old: old_total, + new: new_total, + change: diff, + })) + } +} diff --git a/blockchain/primitives/src/bonding/error.rs b/primitives/src/bonding/error.rs similarity index 97% rename from blockchain/primitives/src/bonding/error.rs rename to primitives/src/bonding/error.rs index 5ed6fbbd..507be8ae 100644 --- a/blockchain/primitives/src/bonding/error.rs +++ b/primitives/src/bonding/error.rs @@ -1,28 +1,28 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -use sp_runtime::RuntimeDebug; - -#[derive(PartialEq, Eq, RuntimeDebug)] -pub enum Error { - BelowMinBondThreshold, - MaxUnlockChunksExceeded, - NotBonded, -} +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use sp_runtime::RuntimeDebug; + +#[derive(PartialEq, Eq, RuntimeDebug)] +pub enum Error { + BelowMinBondThreshold, + MaxUnlockChunksExceeded, + NotBonded, +} diff --git a/blockchain/primitives/src/bonding/ledger.rs b/primitives/src/bonding/ledger.rs similarity index 95% rename from blockchain/primitives/src/bonding/ledger.rs rename to primitives/src/bonding/ledger.rs index 8999ac93..ca175004 100644 --- a/blockchain/primitives/src/bonding/ledger.rs +++ b/primitives/src/bonding/ledger.rs @@ -1,445 +1,445 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -use super::error::Error; -use crate::Balance; -use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; -use scale_info::TypeInfo; -use sp_runtime::{traits::Zero, RuntimeDebug}; - -use frame_support::pallet_prelude::*; - -/// Just a Balance/BlockNumber tuple to encode when a chunk of funds will be -/// unlocked. -#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, MaxEncodedLen, TypeInfo)] -pub struct UnlockChunk { - /// Amount of funds to be unlocked. - value: Balance, - /// Era number at which point it'll be unlocked. - unlock_at: Moment, -} - -/// The ledger of a (bonded) account. -#[derive(PartialEqNoBound, EqNoBound, CloneNoBound, Encode, Decode, RuntimeDebug, MaxEncodedLen, TypeInfo)] -#[scale_info(skip_type_params(MaxUnlockingChunks, MinBond))] -pub struct BondingLedger -where - Moment: Eq + Clone, - MaxUnlockingChunks: Get, -{ - /// The total amount of the account's balance that we are currently - /// accounting for. It's just `active` plus all the `unlocking` - /// balances. - total: Balance, - /// The total amount of the account's balance that will be at stake in - /// any forthcoming rounds. - active: Balance, - /// Any balance that is becoming free, which may eventually be - /// transferred out of the account. - unlocking: BoundedVec, MaxUnlockingChunks>, - - _phantom: PhantomData, -} - -impl BondingLedger -where - Moment: Ord + Eq + Copy, - MaxUnlockingChunks: Get, - MinBond: Get, -{ - pub fn new() -> Self { - Default::default() - } - - pub fn active(&self) -> Balance { - self.active - } - - pub fn total(&self) -> Balance { - self.total - } - - pub fn unlocking_len(&self) -> usize { - self.unlocking.len() - } - - /// Bond more funds. - pub fn bond(mut self, amount: Balance) -> Result { - self.active = self.active.saturating_add(amount); - self.total = self.total.saturating_add(amount); - self.check_min_bond()?; - Ok(self) - } - - /// Start unbonding and create new UnlockChunk. - /// Note that if the `unlock_at` is same as the last UnlockChunk, they will be merged. - pub fn unbond(mut self, amount: Balance, unlock_at: Moment) -> Result<(Self, Balance), Error> { - let amount = amount.min(self.active); - self.active = self.active.saturating_sub(amount); - self.check_min_bond()?; - self.unlocking = self - .unlocking - .try_mutate(|unlocking| { - // try merge if the last chunk unlock time is same - if let Some(last) = unlocking.last_mut() { - if last.unlock_at == unlock_at { - last.value = last.value.saturating_add(amount); - return; - } - } - // or make a new one - unlocking.push(UnlockChunk { - value: amount, - unlock_at, - }); - }) - .ok_or(Error::MaxUnlockChunksExceeded)?; - Ok((self, amount)) - } - - pub fn unbond_instant(mut self, amount: Balance) -> Result<(Self, Balance), Error> { - let amount = amount.min(self.active); - self.active = self.active.saturating_sub(amount); - self.total = self.total.saturating_sub(amount); - self.check_min_bond()?; - Ok((self, amount)) - } - - /// Remove entries from `unlocking` that are sufficiently old and reduce - /// the total by the sum of their balances. - #[must_use] - pub fn consolidate_unlocked(mut self, now: Moment) -> Self { - let mut total = self.total; - self.unlocking.retain(|chunk| { - if chunk.unlock_at > now { - true - } else { - total = total.saturating_sub(chunk.value); - false - } - }); - - self.total = total; - - self - } - - /// Re-bond funds that were scheduled for unlocking. - pub fn rebond(mut self, value: Balance) -> Result<(Self, Balance), Error> { - let mut unlocking_balance: Balance = Zero::zero(); - - self.unlocking = self - .unlocking - .try_mutate(|unlocking| { - while let Some(last) = unlocking.last_mut() { - if unlocking_balance + last.value <= value { - unlocking_balance += last.value; - self.active += last.value; - unlocking.pop(); - } else { - let diff = value - unlocking_balance; - - unlocking_balance += diff; - self.active += diff; - last.value -= diff; - } - - if unlocking_balance >= value { - break; - } - } - }) - .expect("Only popped elements from inner_vec"); - - self.check_min_bond()?; - - Ok((self, unlocking_balance)) - } - - pub fn is_empty(&self) -> bool { - self.total.is_zero() - } - - fn check_min_bond(&self) -> Result<(), Error> { - if self.active > 0 && self.active < MinBond::get() { - return Err(Error::BelowMinBondThreshold); - } - Ok(()) - } -} - -impl Default for BondingLedger -where - Moment: Ord + Eq + Copy, - MaxUnlockingChunks: Get, - MinBond: Get, -{ - fn default() -> Self { - Self { - unlocking: Default::default(), - total: Default::default(), - active: Default::default(), - _phantom: Default::default(), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use frame_support::{ - assert_err, - traits::{ConstU128, ConstU32}, - }; - use sp_runtime::bounded_vec; - - type Ledger = BondingLedger, ConstU128<10>>; - - #[test] - fn bond_works() { - let ledger = Ledger::new(); - assert!(ledger.is_empty()); - assert_err!(ledger.clone().bond(9), Error::BelowMinBondThreshold); - - let ledger = ledger.bond(10).unwrap(); - assert!(!ledger.is_empty()); - assert_eq!( - ledger, - Ledger { - total: 10, - active: 10, - unlocking: Default::default(), - _phantom: Default::default(), - } - ); - - let ledger = ledger.bond(100).unwrap(); - assert_eq!( - ledger, - Ledger { - total: 110, - active: 110, - unlocking: Default::default(), - _phantom: Default::default(), - } - ); - } - - #[test] - fn unbond_works() { - let ledger = Ledger::new(); - let ledger = ledger.bond(100).unwrap(); - assert_err!(ledger.clone().unbond(99, 2), Error::BelowMinBondThreshold); - - let (ledger, actual) = ledger.unbond(20, 2).unwrap(); - assert_eq!(actual, 20); - assert_eq!( - ledger, - Ledger { - total: 100, - active: 80, - unlocking: bounded_vec![UnlockChunk { - value: 20, - unlock_at: 2, - }], - _phantom: Default::default(), - } - ); - - let (ledger, actual) = ledger.unbond(10, 2).unwrap(); - assert_eq!(actual, 10); - assert_eq!( - ledger, - Ledger { - total: 100, - active: 70, - unlocking: bounded_vec![UnlockChunk { - value: 30, - unlock_at: 2, - }], - _phantom: Default::default(), - } - ); - - let (ledger, actual) = ledger.unbond(5, 4).unwrap(); - assert_eq!(actual, 5); - assert_eq!( - ledger, - Ledger { - total: 100, - active: 65, - unlocking: bounded_vec![ - UnlockChunk { - value: 30, - unlock_at: 2, - }, - UnlockChunk { value: 5, unlock_at: 4 } - ], - _phantom: Default::default(), - } - ); - - let ledger = ledger.consolidate_unlocked(1); - assert_eq!( - ledger, - Ledger { - total: 100, - active: 65, - unlocking: bounded_vec![ - UnlockChunk { - value: 30, - unlock_at: 2, - }, - UnlockChunk { value: 5, unlock_at: 4 } - ], - _phantom: Default::default(), - } - ); - - let ledger = ledger.consolidate_unlocked(2); - assert_eq!( - ledger, - Ledger { - total: 70, - active: 65, - unlocking: bounded_vec![UnlockChunk { value: 5, unlock_at: 4 }], - _phantom: Default::default(), - } - ); - - let (ledger, actual) = ledger.unbond(100, 6).unwrap(); - assert_eq!(actual, 65); - assert_eq!( - ledger, - Ledger { - total: 70, - active: 0, - unlocking: bounded_vec![ - UnlockChunk { value: 5, unlock_at: 4 }, - UnlockChunk { - value: 65, - unlock_at: 6, - } - ], - _phantom: Default::default(), - } - ); - - let ledger = ledger.consolidate_unlocked(4); - assert_eq!( - ledger, - Ledger { - total: 65, - active: 0, - unlocking: bounded_vec![UnlockChunk { - value: 65, - unlock_at: 6, - }], - _phantom: Default::default(), - } - ); - - let ledger = ledger.consolidate_unlocked(6); - assert_eq!( - ledger, - Ledger { - total: 0, - active: 0, - unlocking: bounded_vec![], - _phantom: Default::default(), - } - ); - assert!(ledger.is_empty()); - } - - #[test] - fn unbond_instant_works() { - let ledger = Ledger::new(); - let ledger = ledger.bond(100).unwrap(); - assert_err!(ledger.clone().unbond_instant(99), Error::BelowMinBondThreshold); - - let (ledger, actual) = ledger.unbond_instant(20).unwrap(); - assert_eq!(actual, 20); - - let (_ledger, actual) = ledger.unbond_instant(100).unwrap(); - assert_eq!(actual, 80); - } - - #[test] - fn rebond_works() { - let ledger = Ledger::new(); - - let (ledger, _) = ledger - .bond(100) - .and_then(|ledger| ledger.unbond(50, 2)) - .and_then(|(ledger, _)| ledger.unbond(50, 3)) - .unwrap(); - - assert_err!(ledger.clone().rebond(1), Error::BelowMinBondThreshold); - - let (ledger, actual) = ledger.rebond(20).unwrap(); - assert_eq!(actual, 20); - assert_eq!( - ledger, - Ledger { - total: 100, - active: 20, - unlocking: bounded_vec![ - UnlockChunk { - value: 50, - unlock_at: 2 - }, - UnlockChunk { - value: 30, - unlock_at: 3 - } - ], - _phantom: Default::default(), - } - ); - - let (ledger, actual) = ledger.rebond(40).unwrap(); - assert_eq!(actual, 40); - assert_eq!( - ledger, - Ledger { - total: 100, - active: 60, - unlocking: bounded_vec![UnlockChunk { - value: 40, - unlock_at: 2 - }], - _phantom: Default::default(), - } - ); - - let (ledger, actual) = ledger.rebond(50).unwrap(); - assert_eq!(actual, 40); - assert_eq!( - ledger, - Ledger { - total: 100, - active: 100, - unlocking: bounded_vec![], - _phantom: Default::default(), - } - ); - } -} +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use super::error::Error; +use crate::Balance; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_runtime::{traits::Zero, RuntimeDebug}; + +use frame_support::pallet_prelude::*; + +/// Just a Balance/BlockNumber tuple to encode when a chunk of funds will be +/// unlocked. +#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, MaxEncodedLen, TypeInfo)] +pub struct UnlockChunk { + /// Amount of funds to be unlocked. + value: Balance, + /// Era number at which point it'll be unlocked. + unlock_at: Moment, +} + +/// The ledger of a (bonded) account. +#[derive(PartialEqNoBound, EqNoBound, CloneNoBound, Encode, Decode, RuntimeDebug, MaxEncodedLen, TypeInfo)] +#[scale_info(skip_type_params(MaxUnlockingChunks, MinBond))] +pub struct BondingLedger +where + Moment: Eq + Clone, + MaxUnlockingChunks: Get, +{ + /// The total amount of the account's balance that we are currently + /// accounting for. It's just `active` plus all the `unlocking` + /// balances. + total: Balance, + /// The total amount of the account's balance that will be at stake in + /// any forthcoming rounds. + active: Balance, + /// Any balance that is becoming free, which may eventually be + /// transferred out of the account. + unlocking: BoundedVec, MaxUnlockingChunks>, + + _phantom: PhantomData, +} + +impl BondingLedger +where + Moment: Ord + Eq + Copy, + MaxUnlockingChunks: Get, + MinBond: Get, +{ + pub fn new() -> Self { + Default::default() + } + + pub fn active(&self) -> Balance { + self.active + } + + pub fn total(&self) -> Balance { + self.total + } + + pub fn unlocking_len(&self) -> usize { + self.unlocking.len() + } + + /// Bond more funds. + pub fn bond(mut self, amount: Balance) -> Result { + self.active = self.active.saturating_add(amount); + self.total = self.total.saturating_add(amount); + self.check_min_bond()?; + Ok(self) + } + + /// Start unbonding and create new UnlockChunk. + /// Note that if the `unlock_at` is same as the last UnlockChunk, they will be merged. + pub fn unbond(mut self, amount: Balance, unlock_at: Moment) -> Result<(Self, Balance), Error> { + let amount = amount.min(self.active); + self.active = self.active.saturating_sub(amount); + self.check_min_bond()?; + self.unlocking = self + .unlocking + .try_mutate(|unlocking| { + // try merge if the last chunk unlock time is same + if let Some(last) = unlocking.last_mut() { + if last.unlock_at == unlock_at { + last.value = last.value.saturating_add(amount); + return; + } + } + // or make a new one + unlocking.push(UnlockChunk { + value: amount, + unlock_at, + }); + }) + .ok_or(Error::MaxUnlockChunksExceeded)?; + Ok((self, amount)) + } + + pub fn unbond_instant(mut self, amount: Balance) -> Result<(Self, Balance), Error> { + let amount = amount.min(self.active); + self.active = self.active.saturating_sub(amount); + self.total = self.total.saturating_sub(amount); + self.check_min_bond()?; + Ok((self, amount)) + } + + /// Remove entries from `unlocking` that are sufficiently old and reduce + /// the total by the sum of their balances. + #[must_use] + pub fn consolidate_unlocked(mut self, now: Moment) -> Self { + let mut total = self.total; + self.unlocking.retain(|chunk| { + if chunk.unlock_at > now { + true + } else { + total = total.saturating_sub(chunk.value); + false + } + }); + + self.total = total; + + self + } + + /// Re-bond funds that were scheduled for unlocking. + pub fn rebond(mut self, value: Balance) -> Result<(Self, Balance), Error> { + let mut unlocking_balance: Balance = Zero::zero(); + + self.unlocking = self + .unlocking + .try_mutate(|unlocking| { + while let Some(last) = unlocking.last_mut() { + if unlocking_balance + last.value <= value { + unlocking_balance += last.value; + self.active += last.value; + unlocking.pop(); + } else { + let diff = value - unlocking_balance; + + unlocking_balance += diff; + self.active += diff; + last.value -= diff; + } + + if unlocking_balance >= value { + break; + } + } + }) + .expect("Only popped elements from inner_vec"); + + self.check_min_bond()?; + + Ok((self, unlocking_balance)) + } + + pub fn is_empty(&self) -> bool { + self.total.is_zero() + } + + fn check_min_bond(&self) -> Result<(), Error> { + if self.active > 0 && self.active < MinBond::get() { + return Err(Error::BelowMinBondThreshold); + } + Ok(()) + } +} + +impl Default for BondingLedger +where + Moment: Ord + Eq + Copy, + MaxUnlockingChunks: Get, + MinBond: Get, +{ + fn default() -> Self { + Self { + unlocking: Default::default(), + total: Default::default(), + active: Default::default(), + _phantom: Default::default(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use frame_support::{ + assert_err, + traits::{ConstU128, ConstU32}, + }; + use sp_runtime::bounded_vec; + + type Ledger = BondingLedger, ConstU128<10>>; + + #[test] + fn bond_works() { + let ledger = Ledger::new(); + assert!(ledger.is_empty()); + assert_err!(ledger.clone().bond(9), Error::BelowMinBondThreshold); + + let ledger = ledger.bond(10).unwrap(); + assert!(!ledger.is_empty()); + assert_eq!( + ledger, + Ledger { + total: 10, + active: 10, + unlocking: Default::default(), + _phantom: Default::default(), + } + ); + + let ledger = ledger.bond(100).unwrap(); + assert_eq!( + ledger, + Ledger { + total: 110, + active: 110, + unlocking: Default::default(), + _phantom: Default::default(), + } + ); + } + + #[test] + fn unbond_works() { + let ledger = Ledger::new(); + let ledger = ledger.bond(100).unwrap(); + assert_err!(ledger.clone().unbond(99, 2), Error::BelowMinBondThreshold); + + let (ledger, actual) = ledger.unbond(20, 2).unwrap(); + assert_eq!(actual, 20); + assert_eq!( + ledger, + Ledger { + total: 100, + active: 80, + unlocking: bounded_vec![UnlockChunk { + value: 20, + unlock_at: 2, + }], + _phantom: Default::default(), + } + ); + + let (ledger, actual) = ledger.unbond(10, 2).unwrap(); + assert_eq!(actual, 10); + assert_eq!( + ledger, + Ledger { + total: 100, + active: 70, + unlocking: bounded_vec![UnlockChunk { + value: 30, + unlock_at: 2, + }], + _phantom: Default::default(), + } + ); + + let (ledger, actual) = ledger.unbond(5, 4).unwrap(); + assert_eq!(actual, 5); + assert_eq!( + ledger, + Ledger { + total: 100, + active: 65, + unlocking: bounded_vec![ + UnlockChunk { + value: 30, + unlock_at: 2, + }, + UnlockChunk { value: 5, unlock_at: 4 } + ], + _phantom: Default::default(), + } + ); + + let ledger = ledger.consolidate_unlocked(1); + assert_eq!( + ledger, + Ledger { + total: 100, + active: 65, + unlocking: bounded_vec![ + UnlockChunk { + value: 30, + unlock_at: 2, + }, + UnlockChunk { value: 5, unlock_at: 4 } + ], + _phantom: Default::default(), + } + ); + + let ledger = ledger.consolidate_unlocked(2); + assert_eq!( + ledger, + Ledger { + total: 70, + active: 65, + unlocking: bounded_vec![UnlockChunk { value: 5, unlock_at: 4 }], + _phantom: Default::default(), + } + ); + + let (ledger, actual) = ledger.unbond(100, 6).unwrap(); + assert_eq!(actual, 65); + assert_eq!( + ledger, + Ledger { + total: 70, + active: 0, + unlocking: bounded_vec![ + UnlockChunk { value: 5, unlock_at: 4 }, + UnlockChunk { + value: 65, + unlock_at: 6, + } + ], + _phantom: Default::default(), + } + ); + + let ledger = ledger.consolidate_unlocked(4); + assert_eq!( + ledger, + Ledger { + total: 65, + active: 0, + unlocking: bounded_vec![UnlockChunk { + value: 65, + unlock_at: 6, + }], + _phantom: Default::default(), + } + ); + + let ledger = ledger.consolidate_unlocked(6); + assert_eq!( + ledger, + Ledger { + total: 0, + active: 0, + unlocking: bounded_vec![], + _phantom: Default::default(), + } + ); + assert!(ledger.is_empty()); + } + + #[test] + fn unbond_instant_works() { + let ledger = Ledger::new(); + let ledger = ledger.bond(100).unwrap(); + assert_err!(ledger.clone().unbond_instant(99), Error::BelowMinBondThreshold); + + let (ledger, actual) = ledger.unbond_instant(20).unwrap(); + assert_eq!(actual, 20); + + let (_ledger, actual) = ledger.unbond_instant(100).unwrap(); + assert_eq!(actual, 80); + } + + #[test] + fn rebond_works() { + let ledger = Ledger::new(); + + let (ledger, _) = ledger + .bond(100) + .and_then(|ledger| ledger.unbond(50, 2)) + .and_then(|(ledger, _)| ledger.unbond(50, 3)) + .unwrap(); + + assert_err!(ledger.clone().rebond(1), Error::BelowMinBondThreshold); + + let (ledger, actual) = ledger.rebond(20).unwrap(); + assert_eq!(actual, 20); + assert_eq!( + ledger, + Ledger { + total: 100, + active: 20, + unlocking: bounded_vec![ + UnlockChunk { + value: 50, + unlock_at: 2 + }, + UnlockChunk { + value: 30, + unlock_at: 3 + } + ], + _phantom: Default::default(), + } + ); + + let (ledger, actual) = ledger.rebond(40).unwrap(); + assert_eq!(actual, 40); + assert_eq!( + ledger, + Ledger { + total: 100, + active: 60, + unlocking: bounded_vec![UnlockChunk { + value: 40, + unlock_at: 2 + }], + _phantom: Default::default(), + } + ); + + let (ledger, actual) = ledger.rebond(50).unwrap(); + assert_eq!(actual, 40); + assert_eq!( + ledger, + Ledger { + total: 100, + active: 100, + unlocking: bounded_vec![], + _phantom: Default::default(), + } + ); + } +} diff --git a/blockchain/primitives/src/bonding/mod.rs b/primitives/src/bonding/mod.rs similarity index 97% rename from blockchain/primitives/src/bonding/mod.rs rename to primitives/src/bonding/mod.rs index 5d46ed68..32e00ecd 100644 --- a/blockchain/primitives/src/bonding/mod.rs +++ b/primitives/src/bonding/mod.rs @@ -1,27 +1,27 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -mod controller; -mod error; -mod ledger; - -pub use self::controller::*; -pub use self::error::*; -pub use self::ledger::*; +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +mod controller; +mod error; +mod ledger; + +pub use self::controller::*; +pub use self::error::*; +pub use self::ledger::*; diff --git a/blockchain/primitives/src/currency.rs b/primitives/src/currency.rs similarity index 98% rename from blockchain/primitives/src/currency.rs rename to primitives/src/currency.rs index 30bb940d..1dbfbbae 100644 --- a/blockchain/primitives/src/currency.rs +++ b/primitives/src/currency.rs @@ -166,7 +166,7 @@ macro_rules! create_currency_id { ]; tokens.append(&mut fa_tokens); - frame_support::assert_ok!(std::fs::write("../../predeploy-contracts/resources/tokens.json", serde_json::to_string_pretty(&tokens).unwrap())); + frame_support::assert_ok!(std::fs::write("../../blockchain/predeploy-contracts/resources/tokens.json", serde_json::to_string_pretty(&tokens).unwrap())); } } } diff --git a/blockchain/primitives/src/edfis_launchpad.rs b/primitives/src/edfis_launchpad.rs similarity index 100% rename from blockchain/primitives/src/edfis_launchpad.rs rename to primitives/src/edfis_launchpad.rs diff --git a/blockchain/primitives/src/evm.rs b/primitives/src/evm.rs similarity index 97% rename from blockchain/primitives/src/evm.rs rename to primitives/src/evm.rs index 4a18bbfe..49c32b61 100644 --- a/blockchain/primitives/src/evm.rs +++ b/primitives/src/evm.rs @@ -1,320 +1,320 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -use crate::{ - currency::{CurrencyId, CurrencyIdType, DexShareType}, - Balance, BlockNumber, Nonce, -}; -use core::ops::Range; -use hex_literal::hex; -pub use module_evm_utility::{ - ethereum::{AccessListItem, Log, TransactionAction}, - evm::ExitReason, -}; -use parity_scale_codec::{Decode, Encode}; -use scale_info::TypeInfo; -#[cfg(feature = "std")] -use serde::{Deserialize, Serialize}; -use sp_core::{H160, H256, U256}; -use sp_runtime::{traits::Zero, RuntimeDebug, SaturatedConversion}; -use sp_std::vec::Vec; - -/// Evm Address. -pub type EvmAddress = sp_core::H160; - -/// Setheum Mainnet 258 -pub const CHAIN_ID_SETHEUM_MAINNET: u64 = 787u64; -/// Setheum Damascus Testnet 2580 -pub const CHAIN_ID_DAMASCUS_TESTNET: u64 = 597u64; -/// Qingdao Devnet 2581 -pub const CHAIN_ID_QINGDAO_DEVNET: u64 = 595u64; - -// GAS MASK -const GAS_MASK: u64 = 100_000u64; -// STORAGE MASK -const STORAGE_MASK: u64 = 100u64; -// GAS LIMIT CHUNK -const GAS_LIMIT_CHUNK: u64 = 30_000u64; -// MAX GAS_LIMIT CC, log2(BLOCK_STORAGE_LIMIT) -pub const MAX_GAS_LIMIT_CC: u32 = 21u32; - -#[derive(Clone, Eq, PartialEq, Encode, Decode, Default, RuntimeDebug, TypeInfo)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -/// External input from the transaction. -pub struct Vicinity { - /// Current transaction gas price. - pub gas_price: U256, - /// Origin of the transaction. - pub origin: EvmAddress, - /// Environmental coinbase. - pub block_coinbase: Option, - /// Environmental block gas limit. Used only for testing - pub block_gas_limit: Option, - /// Environmental block difficulty. Used only for testing - pub block_difficulty: Option, - /// Environmental base fee per gas. - pub block_base_fee_per_gas: Option, - /// Environmental randomness. - pub block_randomness: Option, -} - -#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub struct ExecutionInfo { - pub exit_reason: ExitReason, - pub value: T, - pub used_gas: U256, - pub used_storage: i32, - pub logs: Vec, -} - -pub type CallInfo = ExecutionInfo>; -pub type CreateInfo = ExecutionInfo; - -#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub struct BlockLimits { - /// Max gas limit - pub max_gas_limit: u64, - /// Max storage limit - pub max_storage_limit: u32, -} - -#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub struct EstimateResourcesRequest { - /// From - pub from: Option, - /// To - pub to: Option, - /// Gas Limit - pub gas_limit: Option, - /// Storage Limit - pub storage_limit: Option, - /// Value - pub value: Option, - /// Data - pub data: Option>, - /// AccessList - pub access_list: Option>, -} - -#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub struct EthereumTransactionMessage { - pub chain_id: u64, - pub genesis: H256, - pub nonce: Nonce, - pub tip: Balance, - pub gas_price: u64, - pub gas_limit: u64, - pub storage_limit: u32, - pub action: TransactionAction, - pub value: Balance, - pub input: Vec, - pub valid_until: BlockNumber, - pub access_list: Vec, -} - -/// Ethereum precompiles -/// 0 - 0x0000000000000000000000000000000000000400 -/// Setheum precompiles -/// 0x0000000000000000000000000000000000000400 - 0x0000000000000000000000000000000000000800 -pub const PRECOMPILE_ADDRESS_START: EvmAddress = H160(hex!("0000000000000000000000000000000000000400")); -/// Predeployed system contracts (except Mirrored ERC20) -/// 0x0000000000000000000000000000000000000800 - 0x0000000000000000000000000000000000001000 -pub const PREDEPLOY_ADDRESS_START: EvmAddress = H160(hex!("0000000000000000000000000000000000000800")); -pub const MIRRORED_TOKENS_ADDRESS_START: EvmAddress = H160(hex!("0000000000000000000100000000000000000000")); -pub const MIRRORED_NFT_ADDRESS_START: u64 = 0x2000000; -/// ERC20 Holding Account used for transfer ERC20 token -pub const ERC20_HOLDING_ACCOUNT: EvmAddress = H160(hex_literal::hex!("000000000000000000ff00000000000000000000")); -/// System contract address prefix -pub const SYSTEM_CONTRACT_ADDRESS_PREFIX: [u8; 9] = [0u8; 9]; - -#[rustfmt::skip] -/// CurrencyId to H160([u8; 20]) bit encoding rule. -/// -/// Type occupies 1 byte, and data occupies 4 bytes(less than 4 bytes, right justified). -/// -/// 0x0000000000000000000000000000000000000000 -/// 0 1 2 3 4 5 6 7 8 910111213141516171819 index -/// ^^^^^^^^^^^^^^^^^^ System contract address prefix -/// ^^ CurrencyId Type: 1-Token 2-DexShare -/// 3-ForeignAsset(ignore Erc20, without the prefix of system contracts) -/// FF-Erc20 Holding Account -/// ^^ CurrencyId Type is 1-Token, Token -/// ^^^^^^^^ CurrencyId Type is 1-Token, NFT -/// ^^ CurrencyId Type is 2-DexShare, DexShare Left Type: -/// 0-Token 1-Erc20 2-ForeignAsset -/// ^^^^^^^^ CurrencyId Type is 2-DexShare, DexShare left field -/// ^^ CurrencyId Type is 2-DexShare, DexShare Right Type: -/// the same as DexShare Left Type -/// ^^^^^^^^ CurrencyId Type is 2-DexShare, DexShare right field -/// ^^^^^^^^ CurrencyId Type is 3-ForeignAsset, ForeignAssetId - -/// Check if the given `address` is a system contract. -/// -/// It's system contract if the address starts with SYSTEM_CONTRACT_ADDRESS_PREFIX. -pub fn is_system_contract(address: &EvmAddress) -> bool { - address.as_bytes().starts_with(&SYSTEM_CONTRACT_ADDRESS_PREFIX) -} - -pub const H160_POSITION_CURRENCY_ID_TYPE: usize = 9; -pub const H160_POSITION_TOKEN: usize = 19; -pub const H160_POSITION_TOKEN_NFT: Range = 16..20; -pub const H160_POSITION_DEXSHARE_LEFT_TYPE: usize = 10; -pub const H160_POSITION_DEXSHARE_LEFT_FIELD: Range = 11..15; -pub const H160_POSITION_DEXSHARE_RIGHT_TYPE: usize = 15; -pub const H160_POSITION_DEXSHARE_RIGHT_FIELD: Range = 16..20; -pub const H160_POSITION_FOREIGN_ASSET: Range = 18..20; - -/// Generate the EvmAddress from CurrencyId so that evm contracts can call the erc20 contract. -/// NOTE: Can not be used directly, need to check the erc20 is mapped. -impl TryFrom for EvmAddress { - type Error = (); - - fn try_from(val: CurrencyId) -> Result { - let mut address = [0u8; 20]; - match val { - CurrencyId::Token(token) => { - address[H160_POSITION_CURRENCY_ID_TYPE] = CurrencyIdType::Token.into(); - address[H160_POSITION_TOKEN] = token.into(); - } - CurrencyId::DexShare(left, right) => { - let left_field: u32 = left.into(); - let right_field: u32 = right.into(); - address[H160_POSITION_CURRENCY_ID_TYPE] = CurrencyIdType::DexShare.into(); - address[H160_POSITION_DEXSHARE_LEFT_TYPE] = Into::::into(left).into(); - address[H160_POSITION_DEXSHARE_LEFT_FIELD].copy_from_slice(&left_field.to_be_bytes()); - address[H160_POSITION_DEXSHARE_RIGHT_TYPE] = Into::::into(right).into(); - address[H160_POSITION_DEXSHARE_RIGHT_FIELD].copy_from_slice(&right_field.to_be_bytes()); - } - CurrencyId::Erc20(erc20) => { - address[..].copy_from_slice(erc20.as_bytes()); - } - CurrencyId::ForeignAsset(foreign_asset_id) => { - address[H160_POSITION_CURRENCY_ID_TYPE] = CurrencyIdType::ForeignAsset.into(); - address[H160_POSITION_FOREIGN_ASSET].copy_from_slice(&foreign_asset_id.to_be_bytes()); - } - }; - - Ok(EvmAddress::from_slice(&address)) - } -} - -pub fn decode_gas_price(gas_price: u64, gas_limit: u64, tx_fee_per_gas: u128) -> Option<(u128, u32)> { - // ensure gas_price >= 100 Gwei - if u128::from(gas_price) < tx_fee_per_gas { - return None; - } - - let mut tip: u128 = 0; - let mut actual_gas_price = gas_price; - const TEN_GWEI: u64 = 10_000_000_000u64; - - // tip = 10% * tip_number - let tip_number = gas_price.checked_div(TEN_GWEI)?.checked_sub(10)?; - if !tip_number.is_zero() { - actual_gas_price = gas_price.checked_sub(tip_number.checked_mul(TEN_GWEI)?)?; - tip = actual_gas_price - .checked_mul(gas_limit)? - .checked_mul(tip_number)? - .checked_div(10)? // percentage - .checked_div(1_000_000)? // SEE decimal is 12, ETH decimal is 18 - .into(); - } - - // valid_until max is u32::MAX. - let valid_until: u32 = Into::::into(actual_gas_price) - .checked_sub(tx_fee_per_gas)? - .saturated_into(); - - Some((tip, valid_until)) -} - -pub fn decode_gas_limit(gas_limit: u64) -> (u64, u32) { - let gas_and_storage: u64 = gas_limit.checked_rem(GAS_MASK).expect("constant never failed; qed"); - let actual_gas_limit: u64 = gas_and_storage - .checked_div(STORAGE_MASK) - .expect("constant never failed; qed") - .saturating_mul(GAS_LIMIT_CHUNK); - let storage_limit_number: u32 = gas_and_storage - .checked_rem(STORAGE_MASK) - .expect("constant never failed; qed") - .try_into() - .expect("STORAGE_MASK is 100, the result maximum is 99; qed"); - - let actual_storage_limit = if storage_limit_number.is_zero() { - Default::default() - } else if storage_limit_number > MAX_GAS_LIMIT_CC { - 2u32.saturating_pow(MAX_GAS_LIMIT_CC) - } else { - 2u32.saturating_pow(storage_limit_number) - }; - - (actual_gas_limit, actual_storage_limit) -} - -#[cfg(not(feature = "evm-tests"))] -mod convert { - use sp_runtime::traits::{CheckedDiv, Saturating, Zero}; - - /// Convert decimal between native(12) and EVM(18) and therefore the 1_000_000 conversion. - const DECIMALS_VALUE: u32 = 1_000_000u32; - - /// Convert decimal from native(SEE 12) to EVM(18). - pub fn convert_decimals_to_evm>(b: B) -> B { - if b.is_zero() { - return b; - } - b.saturating_mul(DECIMALS_VALUE.into()) - } - - /// Convert decimal from EVM(18) to native(SEE 12). - pub fn convert_decimals_from_evm>( - b: B, - ) -> Option { - if b.is_zero() { - return Some(b); - } - let res = b - .checked_div(&Into::::into(DECIMALS_VALUE)) - .expect("divisor is non-zero; qed"); - - if res.saturating_mul(DECIMALS_VALUE.into()) == b { - Some(res) - } else { - None - } - } -} - -#[cfg(feature = "evm-tests")] -mod convert { - pub fn convert_decimals_to_evm(b: B) -> B { - b - } - - pub fn convert_decimals_from_evm(b: B) -> Option { - Some(b) - } -} - -pub use convert::*; +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use crate::{ + currency::{CurrencyId, CurrencyIdType, DexShareType}, + Balance, BlockNumber, Nonce, +}; +use core::ops::Range; +use hex_literal::hex; +pub use module_evm_utility::{ + ethereum::{AccessListItem, Log, TransactionAction}, + evm::ExitReason, +}; +use parity_scale_codec::{Decode, Encode}; +use scale_info::TypeInfo; +#[cfg(feature = "std")] +use serde::{Deserialize, Serialize}; +use sp_core::{H160, H256, U256}; +use sp_runtime::{traits::Zero, RuntimeDebug, SaturatedConversion}; +use sp_std::vec::Vec; + +/// Evm Address. +pub type EvmAddress = sp_core::H160; + +/// Setheum Mainnet 258 +pub const CHAIN_ID_SETHEUM_MAINNET: u64 = 787u64; +/// Setheum Damascus Testnet 2580 +pub const CHAIN_ID_DAMASCUS_TESTNET: u64 = 597u64; +/// Qingdao Devnet 2581 +pub const CHAIN_ID_QINGDAO_DEVNET: u64 = 595u64; + +// GAS MASK +const GAS_MASK: u64 = 100_000u64; +// STORAGE MASK +const STORAGE_MASK: u64 = 100u64; +// GAS LIMIT CHUNK +const GAS_LIMIT_CHUNK: u64 = 30_000u64; +// MAX GAS_LIMIT CC, log2(BLOCK_STORAGE_LIMIT) +pub const MAX_GAS_LIMIT_CC: u32 = 21u32; + +#[derive(Clone, Eq, PartialEq, Encode, Decode, Default, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +/// External input from the transaction. +pub struct Vicinity { + /// Current transaction gas price. + pub gas_price: U256, + /// Origin of the transaction. + pub origin: EvmAddress, + /// Environmental coinbase. + pub block_coinbase: Option, + /// Environmental block gas limit. Used only for testing + pub block_gas_limit: Option, + /// Environmental block difficulty. Used only for testing + pub block_difficulty: Option, + /// Environmental base fee per gas. + pub block_base_fee_per_gas: Option, + /// Environmental randomness. + pub block_randomness: Option, +} + +#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct ExecutionInfo { + pub exit_reason: ExitReason, + pub value: T, + pub used_gas: U256, + pub used_storage: i32, + pub logs: Vec, +} + +pub type CallInfo = ExecutionInfo>; +pub type CreateInfo = ExecutionInfo; + +#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct BlockLimits { + /// Max gas limit + pub max_gas_limit: u64, + /// Max storage limit + pub max_storage_limit: u32, +} + +#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct EstimateResourcesRequest { + /// From + pub from: Option, + /// To + pub to: Option, + /// Gas Limit + pub gas_limit: Option, + /// Storage Limit + pub storage_limit: Option, + /// Value + pub value: Option, + /// Data + pub data: Option>, + /// AccessList + pub access_list: Option>, +} + +#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct EthereumTransactionMessage { + pub chain_id: u64, + pub genesis: H256, + pub nonce: Nonce, + pub tip: Balance, + pub gas_price: u64, + pub gas_limit: u64, + pub storage_limit: u32, + pub action: TransactionAction, + pub value: Balance, + pub input: Vec, + pub valid_until: BlockNumber, + pub access_list: Vec, +} + +/// Ethereum precompiles +/// 0 - 0x0000000000000000000000000000000000000400 +/// Setheum precompiles +/// 0x0000000000000000000000000000000000000400 - 0x0000000000000000000000000000000000000800 +pub const PRECOMPILE_ADDRESS_START: EvmAddress = H160(hex!("0000000000000000000000000000000000000400")); +/// Predeployed system contracts (except Mirrored ERC20) +/// 0x0000000000000000000000000000000000000800 - 0x0000000000000000000000000000000000001000 +pub const PREDEPLOY_ADDRESS_START: EvmAddress = H160(hex!("0000000000000000000000000000000000000800")); +pub const MIRRORED_TOKENS_ADDRESS_START: EvmAddress = H160(hex!("0000000000000000000100000000000000000000")); +pub const MIRRORED_NFT_ADDRESS_START: u64 = 0x2000000; +/// ERC20 Holding Account used for transfer ERC20 token +pub const ERC20_HOLDING_ACCOUNT: EvmAddress = H160(hex_literal::hex!("000000000000000000ff00000000000000000000")); +/// System contract address prefix +pub const SYSTEM_CONTRACT_ADDRESS_PREFIX: [u8; 9] = [0u8; 9]; + +#[rustfmt::skip] +/// CurrencyId to H160([u8; 20]) bit encoding rule. +/// +/// Type occupies 1 byte, and data occupies 4 bytes(less than 4 bytes, right justified). +/// +/// 0x0000000000000000000000000000000000000000 +/// 0 1 2 3 4 5 6 7 8 910111213141516171819 index +/// ^^^^^^^^^^^^^^^^^^ System contract address prefix +/// ^^ CurrencyId Type: 1-Token 2-DexShare +/// 3-ForeignAsset(ignore Erc20, without the prefix of system contracts) +/// FF-Erc20 Holding Account +/// ^^ CurrencyId Type is 1-Token, Token +/// ^^^^^^^^ CurrencyId Type is 1-Token, NFT +/// ^^ CurrencyId Type is 2-DexShare, DexShare Left Type: +/// 0-Token 1-Erc20 2-ForeignAsset +/// ^^^^^^^^ CurrencyId Type is 2-DexShare, DexShare left field +/// ^^ CurrencyId Type is 2-DexShare, DexShare Right Type: +/// the same as DexShare Left Type +/// ^^^^^^^^ CurrencyId Type is 2-DexShare, DexShare right field +/// ^^^^^^^^ CurrencyId Type is 3-ForeignAsset, ForeignAssetId + +/// Check if the given `address` is a system contract. +/// +/// It's system contract if the address starts with SYSTEM_CONTRACT_ADDRESS_PREFIX. +pub fn is_system_contract(address: &EvmAddress) -> bool { + address.as_bytes().starts_with(&SYSTEM_CONTRACT_ADDRESS_PREFIX) +} + +pub const H160_POSITION_CURRENCY_ID_TYPE: usize = 9; +pub const H160_POSITION_TOKEN: usize = 19; +pub const H160_POSITION_TOKEN_NFT: Range = 16..20; +pub const H160_POSITION_DEXSHARE_LEFT_TYPE: usize = 10; +pub const H160_POSITION_DEXSHARE_LEFT_FIELD: Range = 11..15; +pub const H160_POSITION_DEXSHARE_RIGHT_TYPE: usize = 15; +pub const H160_POSITION_DEXSHARE_RIGHT_FIELD: Range = 16..20; +pub const H160_POSITION_FOREIGN_ASSET: Range = 18..20; + +/// Generate the EvmAddress from CurrencyId so that evm contracts can call the erc20 contract. +/// NOTE: Can not be used directly, need to check the erc20 is mapped. +impl TryFrom for EvmAddress { + type Error = (); + + fn try_from(val: CurrencyId) -> Result { + let mut address = [0u8; 20]; + match val { + CurrencyId::Token(token) => { + address[H160_POSITION_CURRENCY_ID_TYPE] = CurrencyIdType::Token.into(); + address[H160_POSITION_TOKEN] = token.into(); + } + CurrencyId::DexShare(left, right) => { + let left_field: u32 = left.into(); + let right_field: u32 = right.into(); + address[H160_POSITION_CURRENCY_ID_TYPE] = CurrencyIdType::DexShare.into(); + address[H160_POSITION_DEXSHARE_LEFT_TYPE] = Into::::into(left).into(); + address[H160_POSITION_DEXSHARE_LEFT_FIELD].copy_from_slice(&left_field.to_be_bytes()); + address[H160_POSITION_DEXSHARE_RIGHT_TYPE] = Into::::into(right).into(); + address[H160_POSITION_DEXSHARE_RIGHT_FIELD].copy_from_slice(&right_field.to_be_bytes()); + } + CurrencyId::Erc20(erc20) => { + address[..].copy_from_slice(erc20.as_bytes()); + } + CurrencyId::ForeignAsset(foreign_asset_id) => { + address[H160_POSITION_CURRENCY_ID_TYPE] = CurrencyIdType::ForeignAsset.into(); + address[H160_POSITION_FOREIGN_ASSET].copy_from_slice(&foreign_asset_id.to_be_bytes()); + } + }; + + Ok(EvmAddress::from_slice(&address)) + } +} + +pub fn decode_gas_price(gas_price: u64, gas_limit: u64, tx_fee_per_gas: u128) -> Option<(u128, u32)> { + // ensure gas_price >= 100 Gwei + if u128::from(gas_price) < tx_fee_per_gas { + return None; + } + + let mut tip: u128 = 0; + let mut actual_gas_price = gas_price; + const TEN_GWEI: u64 = 10_000_000_000u64; + + // tip = 10% * tip_number + let tip_number = gas_price.checked_div(TEN_GWEI)?.checked_sub(10)?; + if !tip_number.is_zero() { + actual_gas_price = gas_price.checked_sub(tip_number.checked_mul(TEN_GWEI)?)?; + tip = actual_gas_price + .checked_mul(gas_limit)? + .checked_mul(tip_number)? + .checked_div(10)? // percentage + .checked_div(1_000_000)? // SEE decimal is 12, ETH decimal is 18 + .into(); + } + + // valid_until max is u32::MAX. + let valid_until: u32 = Into::::into(actual_gas_price) + .checked_sub(tx_fee_per_gas)? + .saturated_into(); + + Some((tip, valid_until)) +} + +pub fn decode_gas_limit(gas_limit: u64) -> (u64, u32) { + let gas_and_storage: u64 = gas_limit.checked_rem(GAS_MASK).expect("constant never failed; qed"); + let actual_gas_limit: u64 = gas_and_storage + .checked_div(STORAGE_MASK) + .expect("constant never failed; qed") + .saturating_mul(GAS_LIMIT_CHUNK); + let storage_limit_number: u32 = gas_and_storage + .checked_rem(STORAGE_MASK) + .expect("constant never failed; qed") + .try_into() + .expect("STORAGE_MASK is 100, the result maximum is 99; qed"); + + let actual_storage_limit = if storage_limit_number.is_zero() { + Default::default() + } else if storage_limit_number > MAX_GAS_LIMIT_CC { + 2u32.saturating_pow(MAX_GAS_LIMIT_CC) + } else { + 2u32.saturating_pow(storage_limit_number) + }; + + (actual_gas_limit, actual_storage_limit) +} + +#[cfg(not(feature = "evm-tests"))] +mod convert { + use sp_runtime::traits::{CheckedDiv, Saturating, Zero}; + + /// Convert decimal between native(12) and EVM(18) and therefore the 1_000_000 conversion. + const DECIMALS_VALUE: u32 = 1_000_000u32; + + /// Convert decimal from native(SEE 12) to EVM(18). + pub fn convert_decimals_to_evm>(b: B) -> B { + if b.is_zero() { + return b; + } + b.saturating_mul(DECIMALS_VALUE.into()) + } + + /// Convert decimal from EVM(18) to native(SEE 12). + pub fn convert_decimals_from_evm>( + b: B, + ) -> Option { + if b.is_zero() { + return Some(b); + } + let res = b + .checked_div(&Into::::into(DECIMALS_VALUE)) + .expect("divisor is non-zero; qed"); + + if res.saturating_mul(DECIMALS_VALUE.into()) == b { + Some(res) + } else { + None + } + } +} + +#[cfg(feature = "evm-tests")] +mod convert { + pub fn convert_decimals_to_evm(b: B) -> B { + b + } + + pub fn convert_decimals_from_evm(b: B) -> Option { + Some(b) + } +} + +pub use convert::*; diff --git a/blockchain/primitives/src/lib.rs b/primitives/src/lib.rs similarity index 100% rename from blockchain/primitives/src/lib.rs rename to primitives/src/lib.rs diff --git a/blockchain/primitives/src/nft.rs b/primitives/src/nft.rs similarity index 97% rename from blockchain/primitives/src/nft.rs rename to primitives/src/nft.rs index 407f803f..8c3e74af 100644 --- a/blockchain/primitives/src/nft.rs +++ b/primitives/src/nft.rs @@ -1,75 +1,75 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -use parity_scale_codec::{Decode, Encode}; -use scale_info::{build::Fields, meta_type, Path, Type, TypeInfo, TypeParameter}; -use serde::{Deserialize, Serialize}; - -use sp_runtime::RuntimeDebug; -use sp_std::{collections::btree_map::BTreeMap, prelude::*}; - -use enumflags2::{bitflags, BitFlags}; - -pub type NFTBalance = u128; -pub type CID = Vec; -pub type Attributes = BTreeMap, Vec>; - -#[bitflags] -#[repr(u8)] -#[derive(Encode, Decode, Clone, Copy, RuntimeDebug, PartialEq, Eq, TypeInfo)] -pub enum ClassProperty { - /// Is token transferable - Transferable = 0b00000001, - /// Is token burnable - Burnable = 0b00000010, - /// Is minting new tokens allowed - Mintable = 0b00000100, - /// Is class properties mutable - ClassPropertiesMutable = 0b00001000, -} - -#[derive(Clone, Copy, PartialEq, Default, RuntimeDebug, Serialize, Deserialize)] -pub struct Properties(pub BitFlags); - -impl Eq for Properties {} -impl Encode for Properties { - fn using_encoded R>(&self, f: F) -> R { - self.0.bits().using_encoded(f) - } -} -impl Decode for Properties { - fn decode(input: &mut I) -> sp_std::result::Result { - let field = u8::decode(input)?; - Ok(Self( - >::from_bits(field as u8).map_err(|_| "invalid value")?, - )) - } -} - -impl TypeInfo for Properties { - type Identity = Self; - - fn type_info() -> Type { - Type::builder() - .path(Path::new("BitFlags", module_path!())) - .type_params(vec![TypeParameter::new("T", Some(meta_type::()))]) - .composite(Fields::unnamed().field(|f| f.ty::().type_name("ClassProperty"))) - } -} +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use parity_scale_codec::{Decode, Encode}; +use scale_info::{build::Fields, meta_type, Path, Type, TypeInfo, TypeParameter}; +use serde::{Deserialize, Serialize}; + +use sp_runtime::RuntimeDebug; +use sp_std::{collections::btree_map::BTreeMap, prelude::*}; + +use enumflags2::{bitflags, BitFlags}; + +pub type NFTBalance = u128; +pub type CID = Vec; +pub type Attributes = BTreeMap, Vec>; + +#[bitflags] +#[repr(u8)] +#[derive(Encode, Decode, Clone, Copy, RuntimeDebug, PartialEq, Eq, TypeInfo)] +pub enum ClassProperty { + /// Is token transferable + Transferable = 0b00000001, + /// Is token burnable + Burnable = 0b00000010, + /// Is minting new tokens allowed + Mintable = 0b00000100, + /// Is class properties mutable + ClassPropertiesMutable = 0b00001000, +} + +#[derive(Clone, Copy, PartialEq, Default, RuntimeDebug, Serialize, Deserialize)] +pub struct Properties(pub BitFlags); + +impl Eq for Properties {} +impl Encode for Properties { + fn using_encoded R>(&self, f: F) -> R { + self.0.bits().using_encoded(f) + } +} +impl Decode for Properties { + fn decode(input: &mut I) -> sp_std::result::Result { + let field = u8::decode(input)?; + Ok(Self( + >::from_bits(field as u8).map_err(|_| "invalid value")?, + )) + } +} + +impl TypeInfo for Properties { + type Identity = Self; + + fn type_info() -> Type { + Type::builder() + .path(Path::new("BitFlags", module_path!())) + .type_params(vec![TypeParameter::new("T", Some(meta_type::()))]) + .composite(Fields::unnamed().field(|f| f.ty::().type_name("ClassProperty"))) + } +} diff --git a/blockchain/primitives/src/signature.rs b/primitives/src/signature.rs similarity index 96% rename from blockchain/primitives/src/signature.rs rename to primitives/src/signature.rs index c67e1c10..46964788 100644 --- a/blockchain/primitives/src/signature.rs +++ b/primitives/src/signature.rs @@ -1,127 +1,127 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -use parity_scale_codec::{Decode, Encode}; -use scale_info::TypeInfo; -use sp_runtime::{ - traits::{Lazy, Verify}, - AccountId32, MultiSigner, RuntimeDebug, -}; - -use sp_core::{crypto::ByteArray, ecdsa, ed25519, sr25519}; - -use sp_std::prelude::*; - -#[derive(Eq, PartialEq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] -pub enum SetheumMultiSignature { - /// An Ed25519 signature. - Ed25519(ed25519::Signature), - /// An Sr25519 signature. - Sr25519(sr25519::Signature), - /// An ECDSA/SECP256k1 signature. - Ecdsa(ecdsa::Signature), - // An Ethereum compatible SECP256k1 signature. - Ethereum([u8; 65]), - // An Ethereum SECP256k1 signature using Eip1559 for message encoding. - Eip1559([u8; 65]), - // An Ethereum SECP256k1 signature using Eip712 for message encoding. - SetheumEip712([u8; 65]), - // An Ethereum SECP256k1 signature using Eip2930 for message encoding. - Eip2930([u8; 65]), -} - -impl From for SetheumMultiSignature { - fn from(x: ed25519::Signature) -> Self { - Self::Ed25519(x) - } -} - -impl TryFrom for ed25519::Signature { - type Error = (); - fn try_from(m: SetheumMultiSignature) -> Result { - if let SetheumMultiSignature::Ed25519(x) = m { - Ok(x) - } else { - Err(()) - } - } -} - -impl From for SetheumMultiSignature { - fn from(x: sr25519::Signature) -> Self { - Self::Sr25519(x) - } -} - -impl TryFrom for sr25519::Signature { - type Error = (); - fn try_from(m: SetheumMultiSignature) -> Result { - if let SetheumMultiSignature::Sr25519(x) = m { - Ok(x) - } else { - Err(()) - } - } -} - -impl From for SetheumMultiSignature { - fn from(x: ecdsa::Signature) -> Self { - Self::Ecdsa(x) - } -} - -impl TryFrom for ecdsa::Signature { - type Error = (); - fn try_from(m: SetheumMultiSignature) -> Result { - if let SetheumMultiSignature::Ecdsa(x) = m { - Ok(x) - } else { - Err(()) - } - } -} - -impl Default for SetheumMultiSignature { - fn default() -> Self { - Self::Ed25519(ed25519::Signature([0u8; 64])) - } -} - -impl Verify for SetheumMultiSignature { - type Signer = MultiSigner; - fn verify>(&self, mut msg: L, signer: &AccountId32) -> bool { - match (self, signer) { - (Self::Ed25519(ref sig), who) => { - ed25519::Public::from_slice(who.as_ref()).map_or(false, |signer| sig.verify(msg, &signer)) - } - (Self::Sr25519(ref sig), who) => { - sr25519::Public::from_slice(who.as_ref()).map_or(false, |signer| sig.verify(msg, &signer)) - } - (Self::Ecdsa(ref sig), who) => { - let m = sp_io::hashing::blake2_256(msg.get()); - match sp_io::crypto::secp256k1_ecdsa_recover_compressed(sig.as_ref(), &m) { - Ok(pubkey) => &sp_io::hashing::blake2_256(pubkey.as_ref()) == >::as_ref(who), - _ => false, - } - } - _ => false, // Arbitrary message verification is not supported - } - } -} +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use parity_scale_codec::{Decode, Encode}; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{Lazy, Verify}, + AccountId32, MultiSigner, RuntimeDebug, +}; + +use sp_core::{crypto::ByteArray, ecdsa, ed25519, sr25519}; + +use sp_std::prelude::*; + +#[derive(Eq, PartialEq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] +pub enum SetheumMultiSignature { + /// An Ed25519 signature. + Ed25519(ed25519::Signature), + /// An Sr25519 signature. + Sr25519(sr25519::Signature), + /// An ECDSA/SECP256k1 signature. + Ecdsa(ecdsa::Signature), + // An Ethereum compatible SECP256k1 signature. + Ethereum([u8; 65]), + // An Ethereum SECP256k1 signature using Eip1559 for message encoding. + Eip1559([u8; 65]), + // An Ethereum SECP256k1 signature using Eip712 for message encoding. + SetheumEip712([u8; 65]), + // An Ethereum SECP256k1 signature using Eip2930 for message encoding. + Eip2930([u8; 65]), +} + +impl From for SetheumMultiSignature { + fn from(x: ed25519::Signature) -> Self { + Self::Ed25519(x) + } +} + +impl TryFrom for ed25519::Signature { + type Error = (); + fn try_from(m: SetheumMultiSignature) -> Result { + if let SetheumMultiSignature::Ed25519(x) = m { + Ok(x) + } else { + Err(()) + } + } +} + +impl From for SetheumMultiSignature { + fn from(x: sr25519::Signature) -> Self { + Self::Sr25519(x) + } +} + +impl TryFrom for sr25519::Signature { + type Error = (); + fn try_from(m: SetheumMultiSignature) -> Result { + if let SetheumMultiSignature::Sr25519(x) = m { + Ok(x) + } else { + Err(()) + } + } +} + +impl From for SetheumMultiSignature { + fn from(x: ecdsa::Signature) -> Self { + Self::Ecdsa(x) + } +} + +impl TryFrom for ecdsa::Signature { + type Error = (); + fn try_from(m: SetheumMultiSignature) -> Result { + if let SetheumMultiSignature::Ecdsa(x) = m { + Ok(x) + } else { + Err(()) + } + } +} + +impl Default for SetheumMultiSignature { + fn default() -> Self { + Self::Ed25519(ed25519::Signature([0u8; 64])) + } +} + +impl Verify for SetheumMultiSignature { + type Signer = MultiSigner; + fn verify>(&self, mut msg: L, signer: &AccountId32) -> bool { + match (self, signer) { + (Self::Ed25519(ref sig), who) => { + ed25519::Public::from_slice(who.as_ref()).map_or(false, |signer| sig.verify(msg, &signer)) + } + (Self::Sr25519(ref sig), who) => { + sr25519::Public::from_slice(who.as_ref()).map_or(false, |signer| sig.verify(msg, &signer)) + } + (Self::Ecdsa(ref sig), who) => { + let m = sp_io::hashing::blake2_256(msg.get()); + match sp_io::crypto::secp256k1_ecdsa_recover_compressed(sig.as_ref(), &m) { + Ok(pubkey) => &sp_io::hashing::blake2_256(pubkey.as_ref()) == >::as_ref(who), + _ => false, + } + } + _ => false, // Arbitrary message verification is not supported + } + } +} diff --git a/blockchain/primitives/src/task.rs b/primitives/src/task.rs similarity index 96% rename from blockchain/primitives/src/task.rs rename to primitives/src/task.rs index 6aa7c8ad..58d6f906 100644 --- a/blockchain/primitives/src/task.rs +++ b/primitives/src/task.rs @@ -1,71 +1,71 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . -use frame_support::weights::Weight; -use parity_scale_codec::{Decode, Encode}; -use scale_info::TypeInfo; -#[cfg(feature = "std")] -use serde::{Deserialize, Serialize}; -use sp_runtime::DispatchResult; -use sp_runtime::RuntimeDebug; - -#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub struct TaskResult { - pub result: DispatchResult, - pub used_weight: Weight, - pub finished: bool, -} - -#[macro_export] -macro_rules! define_combined_task { - ( - $(#[$meta:meta])* - $vis:vis enum $combined_name:ident { - $( - $task:ident ( $vtask:ident $(<$($generic:tt),*>)? ) - ),+ $(,)? - } - ) => { - $(#[$meta])* - $vis enum $combined_name { - $( - $task($vtask $(<$($generic),*>)?), - )* - } - - impl DispatchableTask for $combined_name { - fn dispatch(self, weight: Weight) -> TaskResult { - match self { - $( - $combined_name::$task(t) => t.dispatch(weight), - )* - } - } - } - - $( - impl From<$vtask $(<$($generic),*>)?> for $combined_name { - fn from(t: $vtask $(<$($generic),*>)?) -> Self{ - $combined_name::$task(t) - } - } - )* - }; -} +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . +use frame_support::weights::Weight; +use parity_scale_codec::{Decode, Encode}; +use scale_info::TypeInfo; +#[cfg(feature = "std")] +use serde::{Deserialize, Serialize}; +use sp_runtime::DispatchResult; +use sp_runtime::RuntimeDebug; + +#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct TaskResult { + pub result: DispatchResult, + pub used_weight: Weight, + pub finished: bool, +} + +#[macro_export] +macro_rules! define_combined_task { + ( + $(#[$meta:meta])* + $vis:vis enum $combined_name:ident { + $( + $task:ident ( $vtask:ident $(<$($generic:tt),*>)? ) + ),+ $(,)? + } + ) => { + $(#[$meta])* + $vis enum $combined_name { + $( + $task($vtask $(<$($generic),*>)?), + )* + } + + impl DispatchableTask for $combined_name { + fn dispatch(self, weight: Weight) -> TaskResult { + match self { + $( + $combined_name::$task(t) => t.dispatch(weight), + )* + } + } + } + + $( + impl From<$vtask $(<$($generic),*>)?> for $combined_name { + fn from(t: $vtask $(<$($generic),*>)?) -> Self{ + $combined_name::$task(t) + } + } + )* + }; +} diff --git a/blockchain/primitives/src/testing.rs b/primitives/src/testing.rs similarity index 96% rename from blockchain/primitives/src/testing.rs rename to primitives/src/testing.rs index b4ef7448..813e9986 100644 --- a/blockchain/primitives/src/testing.rs +++ b/primitives/src/testing.rs @@ -1,79 +1,79 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -#[doc(hidden)] -pub use orml_traits; -#[doc(hidden)] -pub use paste; - -#[macro_export] -macro_rules! mock_handler { - ( - $vis:vis struct $name:ident < $t:ty > ; - $( $rest:tt )* - ) => { - $crate::testing::paste::item! { - thread_local! { - pub static [<$name:snake:upper>]: std::cell::RefCell> = std::cell::RefCell::new(Vec::new()); - } - - $vis struct $name; - - impl $name { - - pub fn push(val: $t) { - [<$name:snake:upper>].with(|v| v.borrow_mut().push(val)); - } - - pub fn clear() { - [<$name:snake:upper>].with(|v| v.borrow_mut().clear()); - } - - pub fn get_all() { - [<$name:snake:upper>].with(|v| v.borrow().clone()); - } - - pub fn assert_eq(expected: Vec<$t>) { - [<$name:snake:upper>].with(|v| { - assert_eq!(*v.borrow(), expected); - }); - } - - pub fn assert_eq_and_clear(expected: Vec<$t>) { - Self::assert_eq(expected); - Self::clear(); - } - - pub fn assert_empty() { - Self::assert_eq(Vec::new()); - } - } - - impl $crate::testing::orml_traits::Happened<$t> for $name { - fn happened(val: &$t) { - Self::push(val.clone()); - } - } - } - - $crate::mock_handler!( $( $rest )* ); - }; - () => {}; -} +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +#[doc(hidden)] +pub use orml_traits; +#[doc(hidden)] +pub use paste; + +#[macro_export] +macro_rules! mock_handler { + ( + $vis:vis struct $name:ident < $t:ty > ; + $( $rest:tt )* + ) => { + $crate::testing::paste::item! { + thread_local! { + pub static [<$name:snake:upper>]: std::cell::RefCell> = std::cell::RefCell::new(Vec::new()); + } + + $vis struct $name; + + impl $name { + + pub fn push(val: $t) { + [<$name:snake:upper>].with(|v| v.borrow_mut().push(val)); + } + + pub fn clear() { + [<$name:snake:upper>].with(|v| v.borrow_mut().clear()); + } + + pub fn get_all() { + [<$name:snake:upper>].with(|v| v.borrow().clone()); + } + + pub fn assert_eq(expected: Vec<$t>) { + [<$name:snake:upper>].with(|v| { + assert_eq!(*v.borrow(), expected); + }); + } + + pub fn assert_eq_and_clear(expected: Vec<$t>) { + Self::assert_eq(expected); + Self::clear(); + } + + pub fn assert_empty() { + Self::assert_eq(Vec::new()); + } + } + + impl $crate::testing::orml_traits::Happened<$t> for $name { + fn happened(val: &$t) { + Self::push(val.clone()); + } + } + } + + $crate::mock_handler!( $( $rest )* ); + }; + () => {}; +} diff --git a/blockchain/primitives/src/tests.rs b/primitives/src/tests.rs similarity index 96% rename from blockchain/primitives/src/tests.rs rename to primitives/src/tests.rs index ffba29eb..9b779ee3 100644 --- a/blockchain/primitives/src/tests.rs +++ b/primitives/src/tests.rs @@ -1,226 +1,226 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -use super::*; -use crate::evm::{ - decode_gas_limit, decode_gas_price, is_system_contract, EvmAddress, MAX_GAS_LIMIT_CC, - SYSTEM_CONTRACT_ADDRESS_PREFIX, -}; -use frame_support::assert_ok; -use sp_core::H160; -use std::str::FromStr; - -#[test] -fn trading_pair_works() { - let see = CurrencyId::Token(TokenSymbol::SEE); - let setr = CurrencyId::Token(TokenSymbol::SETR); - let erc20 = CurrencyId::Erc20(EvmAddress::from_str("0x0000000000000000000000000000000000000000").unwrap()); - let see_setr_lp = CurrencyId::DexShare(DexShare::Token(TokenSymbol::SEE), DexShare::Token(TokenSymbol::SETR)); - let erc20_see_lp = CurrencyId::DexShare( - DexShare::Token(TokenSymbol::SEE), - DexShare::Erc20(EvmAddress::from_str("0x0000000000000000000000000000000000000000").unwrap()), - ); - - assert_eq!( - TradingPair::from_currency_ids(setr, see).unwrap(), - TradingPair(see, setr) - ); - assert_eq!( - TradingPair::from_currency_ids(see, setr).unwrap(), - TradingPair(see, setr) - ); - assert_eq!( - TradingPair::from_currency_ids(erc20, see).unwrap(), - TradingPair(see, erc20) - ); - assert_eq!(TradingPair::from_currency_ids(see, see), None); - - assert_eq!( - TradingPair::from_currency_ids(setr, see) - .unwrap() - .dex_share_currency_id(), - see_setr_lp - ); - assert_eq!( - TradingPair::from_currency_ids(see, erc20) - .unwrap() - .dex_share_currency_id(), - erc20_see_lp - ); -} - -#[test] -fn currency_id_try_from_vec_u8_works() { - assert_ok!( - "SEE".as_bytes().to_vec().try_into(), - CurrencyId::Token(TokenSymbol::SEE) - ); -} - -#[test] -fn currency_id_into_u32_works() { - let currency_id = DexShare::Token(TokenSymbol::SEE); - assert_eq!(Into::::into(currency_id), 0x00); - - let currency_id = DexShare::Token(TokenSymbol::USSD); - assert_eq!(Into::::into(currency_id), 0x01); - - let currency_id = DexShare::Erc20(EvmAddress::from_str("0x2000000000000000000000000000000000000000").unwrap()); - assert_eq!(Into::::into(currency_id), 0x20000000); - - let currency_id = DexShare::Erc20(EvmAddress::from_str("0x0000000000000001000000000000000000000000").unwrap()); - assert_eq!(Into::::into(currency_id), 0x01000000); - - let currency_id = DexShare::Erc20(EvmAddress::from_str("0x0000000000000000000000000000000000000001").unwrap()); - assert_eq!(Into::::into(currency_id), 0x01); - - let currency_id = DexShare::Erc20(EvmAddress::from_str("0x0000000000000000000000000000000000000000").unwrap()); - assert_eq!(Into::::into(currency_id), 0x00); -} - -#[test] -fn currency_id_try_into_evm_address_works() { - assert_eq!( - EvmAddress::try_from(CurrencyId::Token(TokenSymbol::SEE,)), - Ok(EvmAddress::from_str("0x0000000000000000000100000000000000000000").unwrap()) - ); - - assert_eq!( - EvmAddress::try_from(CurrencyId::DexShare( - DexShare::Token(TokenSymbol::SEE), - DexShare::Token(TokenSymbol::SETR), - )), - Ok(EvmAddress::from_str("0x0000000000000000000200000000000000000001").unwrap()) - ); - - // No check the erc20 is mapped - assert_eq!( - EvmAddress::try_from(CurrencyId::DexShare( - DexShare::Erc20(Default::default()), - DexShare::Erc20(Default::default()) - )), - Ok(EvmAddress::from_str("0x0000000000000000000201000000000100000000").unwrap()) - ); - - let erc20 = EvmAddress::from_str("0x1111111111111111111111111111111111111111").unwrap(); - assert_eq!(EvmAddress::try_from(CurrencyId::Erc20(erc20)), Ok(erc20)); - - assert_eq!( - EvmAddress::try_from(CurrencyId::DexShare( - DexShare::ForeignAsset(Default::default()), - DexShare::ForeignAsset(Default::default()) - )), - Ok(EvmAddress::from_str("0x0000000000000000000202000000000200000000").unwrap()) - ); -} - -#[test] -fn generate_function_selector_works() { - #[module_evm_utility_macro::generate_function_selector] - #[derive(RuntimeDebug, Eq, PartialEq)] - #[repr(u32)] - pub enum Action { - Name = "name()", - Symbol = "symbol()", - Decimals = "decimals()", - TotalSupply = "totalSupply()", - BalanceOf = "balanceOf(address)", - Transfer = "transfer(address,uint256)", - } - - assert_eq!(Action::Name as u32, 0x06fdde03_u32); - assert_eq!(Action::Symbol as u32, 0x95d89b41_u32); - assert_eq!(Action::Decimals as u32, 0x313ce567_u32); - assert_eq!(Action::TotalSupply as u32, 0x18160ddd_u32); - assert_eq!(Action::BalanceOf as u32, 0x70a08231_u32); - assert_eq!(Action::Transfer as u32, 0xa9059cbb_u32); -} - -#[test] -fn is_system_contract_works() { - assert!(is_system_contract(&H160::from_low_u64_be(0))); - assert!(is_system_contract(&H160::from_low_u64_be(u64::max_value()))); - - let mut bytes = [0u8; 20]; - bytes[SYSTEM_CONTRACT_ADDRESS_PREFIX.len() - 1] = 1u8; - - assert!(!is_system_contract(&bytes.into())); - - bytes = [0u8; 20]; - bytes[0] = 1u8; - - assert!(!is_system_contract(&bytes.into())); -} - -#[test] -fn decode_gas_price_works() { - const TX_FEE_PRE_GAS: u128 = 100_000_000_000u128; // 100 Gwei - - // tip = 0, gas_price = 0 Gwei, gas_limit = u64::MIN - assert_eq!(decode_gas_price(u64::MIN, u64::MIN, TX_FEE_PRE_GAS), None); - // tip = 0, gas_price = 99 Gwei, gas_limit = u64::MAX - assert_eq!(decode_gas_price(99_999_999_999, u64::MIN, TX_FEE_PRE_GAS), None); - // tip = 0, gas_price = 100 Gwei, gas_limit = u64::MIN - assert_eq!( - decode_gas_price(100_000_000_000, u64::MIN, TX_FEE_PRE_GAS), - Some((0, 0)) - ); - // tip = 0, gas_price = 100 Gwei, gas_limit = u64::MAX - assert_eq!( - decode_gas_price(100_000_000_000, u64::MAX, TX_FEE_PRE_GAS), - Some((0, 0)) - ); - - // tip = 0, gas_price = 105 Gwei, gas_limit = u64::MIN - assert_eq!( - decode_gas_price(105_000_000_000, u64::MIN, TX_FEE_PRE_GAS), - Some((0, u32::MAX)) - ); - // tip = 0, gas_price = 105 Gwei, gas_limit = u64::MAX - assert_eq!( - decode_gas_price(105_000_000_000, u64::MAX, TX_FEE_PRE_GAS), - Some((0, u32::MAX)) - ); - - // tip = 0, gas_price = u64::MAX, gas_limit = u64::MIN - assert_eq!( - decode_gas_price(u64::MAX, u64::MIN, TX_FEE_PRE_GAS), - Some((0, 3_709_551_615)) - ); - // tip != 0, gas_price = u64::MAX, gas_limit = 1 - assert_eq!(decode_gas_price(u64::MAX, 1, TX_FEE_PRE_GAS), None); - - // tip != 200%, gas_price = 200 Gwei, gas_limit = 10000 - assert_eq!( - decode_gas_price(200_000_000_000, 10_000, TX_FEE_PRE_GAS), - Some((1_000_000_000, 0)) - ); -} - -#[test] -fn decode_gas_limit_works() { - assert_eq!(decode_gas_limit(u64::MAX), (15_480_000, 32768)); - assert_eq!(decode_gas_limit(u64::MIN), (0, 0)); - assert_eq!( - // u64::MAX = 4294967295 - decode_gas_limit(u64::MAX / 1000 * 1000 + 199), - (15330000, 2u32.pow(MAX_GAS_LIMIT_CC)) - ); -} +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use super::*; +use crate::evm::{ + decode_gas_limit, decode_gas_price, is_system_contract, EvmAddress, MAX_GAS_LIMIT_CC, + SYSTEM_CONTRACT_ADDRESS_PREFIX, +}; +use frame_support::assert_ok; +use sp_core::H160; +use std::str::FromStr; + +#[test] +fn trading_pair_works() { + let see = CurrencyId::Token(TokenSymbol::SEE); + let setr = CurrencyId::Token(TokenSymbol::SETR); + let erc20 = CurrencyId::Erc20(EvmAddress::from_str("0x0000000000000000000000000000000000000000").unwrap()); + let see_setr_lp = CurrencyId::DexShare(DexShare::Token(TokenSymbol::SEE), DexShare::Token(TokenSymbol::SETR)); + let erc20_see_lp = CurrencyId::DexShare( + DexShare::Token(TokenSymbol::SEE), + DexShare::Erc20(EvmAddress::from_str("0x0000000000000000000000000000000000000000").unwrap()), + ); + + assert_eq!( + TradingPair::from_currency_ids(setr, see).unwrap(), + TradingPair(see, setr) + ); + assert_eq!( + TradingPair::from_currency_ids(see, setr).unwrap(), + TradingPair(see, setr) + ); + assert_eq!( + TradingPair::from_currency_ids(erc20, see).unwrap(), + TradingPair(see, erc20) + ); + assert_eq!(TradingPair::from_currency_ids(see, see), None); + + assert_eq!( + TradingPair::from_currency_ids(setr, see) + .unwrap() + .dex_share_currency_id(), + see_setr_lp + ); + assert_eq!( + TradingPair::from_currency_ids(see, erc20) + .unwrap() + .dex_share_currency_id(), + erc20_see_lp + ); +} + +#[test] +fn currency_id_try_from_vec_u8_works() { + assert_ok!( + "SEE".as_bytes().to_vec().try_into(), + CurrencyId::Token(TokenSymbol::SEE) + ); +} + +#[test] +fn currency_id_into_u32_works() { + let currency_id = DexShare::Token(TokenSymbol::SEE); + assert_eq!(Into::::into(currency_id), 0x00); + + let currency_id = DexShare::Token(TokenSymbol::USSD); + assert_eq!(Into::::into(currency_id), 0x01); + + let currency_id = DexShare::Erc20(EvmAddress::from_str("0x2000000000000000000000000000000000000000").unwrap()); + assert_eq!(Into::::into(currency_id), 0x20000000); + + let currency_id = DexShare::Erc20(EvmAddress::from_str("0x0000000000000001000000000000000000000000").unwrap()); + assert_eq!(Into::::into(currency_id), 0x01000000); + + let currency_id = DexShare::Erc20(EvmAddress::from_str("0x0000000000000000000000000000000000000001").unwrap()); + assert_eq!(Into::::into(currency_id), 0x01); + + let currency_id = DexShare::Erc20(EvmAddress::from_str("0x0000000000000000000000000000000000000000").unwrap()); + assert_eq!(Into::::into(currency_id), 0x00); +} + +#[test] +fn currency_id_try_into_evm_address_works() { + assert_eq!( + EvmAddress::try_from(CurrencyId::Token(TokenSymbol::SEE,)), + Ok(EvmAddress::from_str("0x0000000000000000000100000000000000000000").unwrap()) + ); + + assert_eq!( + EvmAddress::try_from(CurrencyId::DexShare( + DexShare::Token(TokenSymbol::SEE), + DexShare::Token(TokenSymbol::SETR), + )), + Ok(EvmAddress::from_str("0x0000000000000000000200000000000000000001").unwrap()) + ); + + // No check the erc20 is mapped + assert_eq!( + EvmAddress::try_from(CurrencyId::DexShare( + DexShare::Erc20(Default::default()), + DexShare::Erc20(Default::default()) + )), + Ok(EvmAddress::from_str("0x0000000000000000000201000000000100000000").unwrap()) + ); + + let erc20 = EvmAddress::from_str("0x1111111111111111111111111111111111111111").unwrap(); + assert_eq!(EvmAddress::try_from(CurrencyId::Erc20(erc20)), Ok(erc20)); + + assert_eq!( + EvmAddress::try_from(CurrencyId::DexShare( + DexShare::ForeignAsset(Default::default()), + DexShare::ForeignAsset(Default::default()) + )), + Ok(EvmAddress::from_str("0x0000000000000000000202000000000200000000").unwrap()) + ); +} + +#[test] +fn generate_function_selector_works() { + #[module_evm_utility_macro::generate_function_selector] + #[derive(RuntimeDebug, Eq, PartialEq)] + #[repr(u32)] + pub enum Action { + Name = "name()", + Symbol = "symbol()", + Decimals = "decimals()", + TotalSupply = "totalSupply()", + BalanceOf = "balanceOf(address)", + Transfer = "transfer(address,uint256)", + } + + assert_eq!(Action::Name as u32, 0x06fdde03_u32); + assert_eq!(Action::Symbol as u32, 0x95d89b41_u32); + assert_eq!(Action::Decimals as u32, 0x313ce567_u32); + assert_eq!(Action::TotalSupply as u32, 0x18160ddd_u32); + assert_eq!(Action::BalanceOf as u32, 0x70a08231_u32); + assert_eq!(Action::Transfer as u32, 0xa9059cbb_u32); +} + +#[test] +fn is_system_contract_works() { + assert!(is_system_contract(&H160::from_low_u64_be(0))); + assert!(is_system_contract(&H160::from_low_u64_be(u64::max_value()))); + + let mut bytes = [0u8; 20]; + bytes[SYSTEM_CONTRACT_ADDRESS_PREFIX.len() - 1] = 1u8; + + assert!(!is_system_contract(&bytes.into())); + + bytes = [0u8; 20]; + bytes[0] = 1u8; + + assert!(!is_system_contract(&bytes.into())); +} + +#[test] +fn decode_gas_price_works() { + const TX_FEE_PRE_GAS: u128 = 100_000_000_000u128; // 100 Gwei + + // tip = 0, gas_price = 0 Gwei, gas_limit = u64::MIN + assert_eq!(decode_gas_price(u64::MIN, u64::MIN, TX_FEE_PRE_GAS), None); + // tip = 0, gas_price = 99 Gwei, gas_limit = u64::MAX + assert_eq!(decode_gas_price(99_999_999_999, u64::MIN, TX_FEE_PRE_GAS), None); + // tip = 0, gas_price = 100 Gwei, gas_limit = u64::MIN + assert_eq!( + decode_gas_price(100_000_000_000, u64::MIN, TX_FEE_PRE_GAS), + Some((0, 0)) + ); + // tip = 0, gas_price = 100 Gwei, gas_limit = u64::MAX + assert_eq!( + decode_gas_price(100_000_000_000, u64::MAX, TX_FEE_PRE_GAS), + Some((0, 0)) + ); + + // tip = 0, gas_price = 105 Gwei, gas_limit = u64::MIN + assert_eq!( + decode_gas_price(105_000_000_000, u64::MIN, TX_FEE_PRE_GAS), + Some((0, u32::MAX)) + ); + // tip = 0, gas_price = 105 Gwei, gas_limit = u64::MAX + assert_eq!( + decode_gas_price(105_000_000_000, u64::MAX, TX_FEE_PRE_GAS), + Some((0, u32::MAX)) + ); + + // tip = 0, gas_price = u64::MAX, gas_limit = u64::MIN + assert_eq!( + decode_gas_price(u64::MAX, u64::MIN, TX_FEE_PRE_GAS), + Some((0, 3_709_551_615)) + ); + // tip != 0, gas_price = u64::MAX, gas_limit = 1 + assert_eq!(decode_gas_price(u64::MAX, 1, TX_FEE_PRE_GAS), None); + + // tip != 200%, gas_price = 200 Gwei, gas_limit = 10000 + assert_eq!( + decode_gas_price(200_000_000_000, 10_000, TX_FEE_PRE_GAS), + Some((1_000_000_000, 0)) + ); +} + +#[test] +fn decode_gas_limit_works() { + assert_eq!(decode_gas_limit(u64::MAX), (15_480_000, 32768)); + assert_eq!(decode_gas_limit(u64::MIN), (0, 0)); + assert_eq!( + // u64::MAX = 4294967295 + decode_gas_limit(u64::MAX / 1000 * 1000 + 199), + (15330000, 2u32.pow(MAX_GAS_LIMIT_CC)) + ); +} diff --git a/blockchain/primitives/src/unchecked_extrinsic.rs b/primitives/src/unchecked_extrinsic.rs similarity index 97% rename from blockchain/primitives/src/unchecked_extrinsic.rs rename to primitives/src/unchecked_extrinsic.rs index a86dc975..01d0e838 100644 --- a/blockchain/primitives/src/unchecked_extrinsic.rs +++ b/primitives/src/unchecked_extrinsic.rs @@ -1,675 +1,675 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program 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. - -// This program 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 this program. If not, see . - -use crate::{evm::EthereumTransactionMessage, signature::SetheumMultiSignature, to_bytes, Address, Balance}; -use frame_support::{ - dispatch::{DispatchInfo, GetDispatchInfo}, - traits::{ExtrinsicCall, Get}, -}; -use module_evm_utility::ethereum::{ - EIP1559TransactionMessage, EIP2930TransactionMessage, LegacyTransactionMessage, TransactionAction, -}; -use module_evm_utility_macro::keccak256; -use parity_scale_codec::{Decode, Encode}; -use scale_info::TypeInfo; -use sp_core::{H160, H256}; -use sp_io::{crypto::secp256k1_ecdsa_recover, hashing::keccak_256}; -use sp_runtime::{ - generic::{CheckedExtrinsic, SetheumUncheckedExtrinsic}, - traits::{self, Checkable, Convert, Extrinsic, ExtrinsicMetadata, Member, SignedExtension, Zero}, - transaction_validity::{InvalidTransaction, TransactionValidityError}, - AccountId32, RuntimeDebug, -}; -#[cfg(not(feature = "std"))] -use sp_std::alloc::format; -use sp_std::{marker::PhantomData, prelude::*}; - -#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] -#[scale_info(skip_type_params(ConvertEthTx))] -pub struct SetheumUncheckedExtrinsic( - pub SetheumUncheckedExtrinsic, - PhantomData<(ConvertEthTx, StorageDepositPerByte, TxFeePerGas)>, -); - -impl Extrinsic - for SetheumUncheckedExtrinsic -{ - type Call = Call; - - type SignaturePayload = (Address, SetheumMultiSignature, Extra); - - fn is_signed(&self) -> Option { - self.0.is_signed() - } - - fn new(function: Call, signed_data: Option) -> Option { - Some(if let Some((address, signature, extra)) = signed_data { - Self( - SetheumUncheckedExtrinsic::new_signed(function, address, signature, extra), - PhantomData, - ) - } else { - Self(SetheumUncheckedExtrinsic::new_unsigned(function), PhantomData) - }) - } -} - -impl ExtrinsicMetadata - for SetheumUncheckedExtrinsic -{ - const VERSION: u8 = SetheumUncheckedExtrinsic::::VERSION; - type SignedExtensions = Extra; -} - -impl ExtrinsicCall - for SetheumUncheckedExtrinsic -{ - fn call(&self) -> &Self::Call { - self.0.call() - } -} - -impl Checkable - for SetheumUncheckedExtrinsic -where - Call: Encode + Member, - Extra: SignedExtension, - ConvertEthTx: Convert<(Call, Extra), Result<(EthereumTransactionMessage, Extra), InvalidTransaction>>, - StorageDepositPerByte: Get, - TxFeePerGas: Get, - Lookup: traits::Lookup, -{ - type Checked = CheckedExtrinsic; - - fn check(self, lookup: &Lookup) -> Result { - let function = self.0.function.clone(); - - match self.0.signature { - Some((addr, SetheumMultiSignature::Ethereum(sig), extra)) => { - let (eth_msg, eth_extra) = ConvertEthTx::convert((function.clone(), extra))?; - log::trace!( - target: "evm", "Ethereum eth_msg: {:?}", eth_msg - ); - - if !eth_msg.access_list.len().is_zero() { - // Not yet supported, require empty - return Err(InvalidTransaction::BadProof.into()); - } - - let (tx_gas_price, tx_gas_limit) = if eth_msg.gas_price.is_zero() { - recover_sign_data(ð_msg, TxFeePerGas::get(), StorageDepositPerByte::get()) - .ok_or(InvalidTransaction::BadProof)? - } else { - // eth_call_v2, the gas_price and gas_limit are encoded. - (eth_msg.gas_price as u128, eth_msg.gas_limit as u128) - }; - - let msg = LegacyTransactionMessage { - nonce: eth_msg.nonce.into(), - gas_price: tx_gas_price.into(), - gas_limit: tx_gas_limit.into(), - action: eth_msg.action, - value: eth_msg.value.into(), - input: eth_msg.input, - chain_id: Some(eth_msg.chain_id), - }; - log::trace!( - target: "evm", "tx msg: {:?}", msg - ); - - let msg_hash = msg.hash(); // TODO: consider rewirte this to use `keccak_256` for hashing because it could be faster - - let signer = recover_signer(&sig, msg_hash.as_fixed_bytes()).ok_or(InvalidTransaction::BadProof)?; - - let account_id = lookup.lookup(Address::Address20(signer.into()))?; - let expected_account_id = lookup.lookup(addr)?; - - if account_id != expected_account_id { - return Err(InvalidTransaction::BadProof.into()); - } - - Ok(CheckedExtrinsic { - signed: Some((account_id, eth_extra)), - function, - }) - } - Some((addr, SetheumMultiSignature::Eip2930(sig), extra)) => { - let (eth_msg, eth_extra) = ConvertEthTx::convert((function.clone(), extra))?; - log::trace!( - target: "evm", "Eip2930 eth_msg: {:?}", eth_msg - ); - - let (tx_gas_price, tx_gas_limit) = if eth_msg.gas_price.is_zero() { - recover_sign_data(ð_msg, TxFeePerGas::get(), StorageDepositPerByte::get()) - .ok_or(InvalidTransaction::BadProof)? - } else { - // eth_call_v2, the gas_price and gas_limit are encoded. - (eth_msg.gas_price as u128, eth_msg.gas_limit as u128) - }; - - let msg = EIP2930TransactionMessage { - chain_id: eth_msg.chain_id, - nonce: eth_msg.nonce.into(), - gas_price: tx_gas_price.into(), - gas_limit: tx_gas_limit.into(), - action: eth_msg.action, - value: eth_msg.value.into(), - input: eth_msg.input, - access_list: eth_msg.access_list, - }; - log::trace!( - target: "evm", "tx msg: {:?}", msg - ); - - let msg_hash = msg.hash(); // TODO: consider rewirte this to use `keccak_256` for hashing because it could be faster - - let signer = recover_signer(&sig, msg_hash.as_fixed_bytes()).ok_or(InvalidTransaction::BadProof)?; - - let account_id = lookup.lookup(Address::Address20(signer.into()))?; - let expected_account_id = lookup.lookup(addr)?; - - if account_id != expected_account_id { - return Err(InvalidTransaction::BadProof.into()); - } - - Ok(CheckedExtrinsic { - signed: Some((account_id, eth_extra)), - function, - }) - } - Some((addr, SetheumMultiSignature::Eip1559(sig), extra)) => { - let (eth_msg, eth_extra) = ConvertEthTx::convert((function.clone(), extra))?; - log::trace!( - target: "evm", "Eip1559 eth_msg: {:?}", eth_msg - ); - - let (tx_gas_price, tx_gas_limit) = if eth_msg.gas_price.is_zero() { - recover_sign_data(ð_msg, TxFeePerGas::get(), StorageDepositPerByte::get()) - .ok_or(InvalidTransaction::BadProof)? - } else { - // eth_call_v2, the gas_price and gas_limit are encoded. - (eth_msg.gas_price as u128, eth_msg.gas_limit as u128) - }; - - // tip = priority_fee * gas_limit - let priority_fee = eth_msg.tip.checked_div(eth_msg.gas_limit.into()).unwrap_or_default(); - - let msg = EIP1559TransactionMessage { - chain_id: eth_msg.chain_id, - nonce: eth_msg.nonce.into(), - max_priority_fee_per_gas: priority_fee.into(), - max_fee_per_gas: tx_gas_price.into(), - gas_limit: tx_gas_limit.into(), - action: eth_msg.action, - value: eth_msg.value.into(), - input: eth_msg.input, - access_list: eth_msg.access_list, - }; - log::trace!( - target: "evm", "tx msg: {:?}", msg - ); - - let msg_hash = msg.hash(); // TODO: consider rewirte this to use `keccak_256` for hashing because it could be faster - - let signer = recover_signer(&sig, msg_hash.as_fixed_bytes()).ok_or(InvalidTransaction::BadProof)?; - - let account_id = lookup.lookup(Address::Address20(signer.into()))?; - let expected_account_id = lookup.lookup(addr)?; - - if account_id != expected_account_id { - return Err(InvalidTransaction::BadProof.into()); - } - - Ok(CheckedExtrinsic { - signed: Some((account_id, eth_extra)), - function, - }) - } - Some((addr, SetheumMultiSignature::SetheumEip712(sig), extra)) => { - let (eth_msg, eth_extra) = ConvertEthTx::convert((function.clone(), extra))?; - log::trace!( - target: "evm", "SetheumEip712 eth_msg: {:?}", eth_msg - ); - - let signer = verify_eip712_signature(eth_msg, sig).ok_or(InvalidTransaction::BadProof)?; - - let account_id = lookup.lookup(Address::Address20(signer.into()))?; - let expected_account_id = lookup.lookup(addr)?; - - if account_id != expected_account_id { - return Err(InvalidTransaction::BadProof.into()); - } - - Ok(CheckedExtrinsic { - signed: Some((account_id, eth_extra)), - function, - }) - } - _ => self.0.check(lookup), - } - } - - #[cfg(feature = "try-runtime")] - fn unchecked_into_checked_i_know_what_i_am_doing( - self, - _lookup: &Lookup, - ) -> Result { - unreachable!(); - } -} - -impl GetDispatchInfo - for SetheumUncheckedExtrinsic -where - Call: GetDispatchInfo, - Extra: SignedExtension, -{ - fn get_dispatch_info(&self) -> DispatchInfo { - self.0.get_dispatch_info() - } -} - -impl serde::Serialize - for SetheumUncheckedExtrinsic -{ - fn serialize(&self, seq: S) -> Result - where - S: ::serde::Serializer, - { - self.0.serialize(seq) - } -} - -impl<'a, Call: Decode, Extra: SignedExtension, ConvertEthTx, StorageDepositPerByte, TxFeePerGas> serde::Deserialize<'a> - for SetheumUncheckedExtrinsic -{ - fn deserialize(de: D) -> Result - where - D: serde::Deserializer<'a>, - { - let r = sp_core::bytes::deserialize(de)?; - Decode::decode(&mut &r[..]).map_err(|e| serde::de::Error::custom(format!("Decode error: {}", e))) - } -} - -fn recover_signer(sig: &[u8; 65], msg_hash: &[u8; 32]) -> Option { - secp256k1_ecdsa_recover(sig, msg_hash) - .map(|pubkey| H160::from(H256::from_slice(&keccak_256(&pubkey)))) - .ok() -} - -fn verify_eip712_signature(eth_msg: EthereumTransactionMessage, sig: [u8; 65]) -> Option { - let domain_hash = keccak256!("EIP712Domain(string name,string version,uint256 chainId,bytes32 salt)"); - let access_list_type_hash = keccak256!("AccessList(address address,uint256[] storageKeys)"); - let tx_type_hash = keccak256!("Transaction(string action,address to,uint256 nonce,uint256 tip,bytes data,uint256 value,uint256 gasLimit,uint256 storageLimit,AccessList[] accessList,uint256 validUntil)AccessList(address address,uint256[] storageKeys)"); - - let mut domain_seperator_msg = domain_hash.to_vec(); - domain_seperator_msg.extend_from_slice(keccak256!("Setheum EVM")); // name - domain_seperator_msg.extend_from_slice(keccak256!("1")); // version - domain_seperator_msg.extend_from_slice(&to_bytes(eth_msg.chain_id)); // chain id - domain_seperator_msg.extend_from_slice(eth_msg.genesis.as_bytes()); // salt - let domain_separator = keccak_256(domain_seperator_msg.as_slice()); - - let mut tx_msg = tx_type_hash.to_vec(); - match eth_msg.action { - TransactionAction::Call(to) => { - tx_msg.extend_from_slice(keccak256!("Call")); - tx_msg.extend_from_slice(H256::from(to).as_bytes()); - } - TransactionAction::Create => { - tx_msg.extend_from_slice(keccak256!("Create")); - tx_msg.extend_from_slice(H256::default().as_bytes()); - } - } - tx_msg.extend_from_slice(&to_bytes(eth_msg.nonce)); - tx_msg.extend_from_slice(&to_bytes(eth_msg.tip)); - tx_msg.extend_from_slice(&keccak_256(eth_msg.input.as_slice())); - tx_msg.extend_from_slice(&to_bytes(eth_msg.value)); - tx_msg.extend_from_slice(&to_bytes(eth_msg.gas_limit)); - tx_msg.extend_from_slice(&to_bytes(eth_msg.storage_limit)); - - let mut access_list: Vec<[u8; 32]> = Vec::new(); - eth_msg.access_list.iter().for_each(|v| { - let mut access_list_msg = access_list_type_hash.to_vec(); - access_list_msg.extend_from_slice(&to_bytes(v.address.as_bytes())); - access_list_msg.extend_from_slice(&keccak_256( - &v.storage_keys.iter().map(|v| v.as_bytes()).collect::>().concat(), - )); - access_list.push(keccak_256(access_list_msg.as_slice())); - }); - tx_msg.extend_from_slice(&keccak_256(&access_list.concat())); - tx_msg.extend_from_slice(&to_bytes(eth_msg.valid_until)); - - let mut msg = b"\x19\x01".to_vec(); - msg.extend_from_slice(&domain_separator); - msg.extend_from_slice(&keccak_256(tx_msg.as_slice())); - - let msg_hash = keccak_256(msg.as_slice()); - - recover_signer(&sig, &msg_hash) -} - -fn recover_sign_data( - eth_msg: &EthereumTransactionMessage, - ts_fee_per_gas: u128, - storage_deposit_per_byte: u128, -) -> Option<(u128, u128)> { - // tx_gas_price = tx_fee_per_gas + block_period << 16 + storage_entry_limit - // tx_gas_limit = gas_limit + storage_entry_deposit / tx_fee_per_gas * storage_entry_limit - let block_period = eth_msg.valid_until.saturating_div(30); - // u16: max value 0xffff * 64 = 4194240 bytes = 4MB - let storage_entry_limit: u16 = eth_msg.storage_limit.saturating_div(64).try_into().ok()?; - let storage_entry_deposit = storage_deposit_per_byte.saturating_mul(64); - let tx_gas_price = ts_fee_per_gas - .checked_add(Into::::into(block_period).checked_shl(16)?)? - .checked_add(storage_entry_limit.into())?; - // There is a loss of precision here, so the order of calculation must be guaranteed - // must ensure storage_deposit / tx_fee_per_gas * storage_limit - let tx_gas_limit = storage_entry_deposit - .checked_div(ts_fee_per_gas) - .expect("divisor is non-zero; qed") - .checked_mul(storage_entry_limit.into())? - .checked_add(eth_msg.gas_limit.into())?; - - Some((tx_gas_price, tx_gas_limit)) -} - -#[cfg(test)] -mod tests { - use super::*; - use hex_literal::hex; - use module_evm_utility::ethereum::AccessListItem; - use sp_core::U256; - use std::{ops::Add, str::FromStr}; - - #[test] - fn verify_eip712_should_works() { - let sender = Some(H160::from_str("0x14791697260E4c9A71f18484C9f997B308e59325").unwrap()); - // access_list = vec![] - let msg = EthereumTransactionMessage { - chain_id: 595, - genesis: H256::from_str("0xafb55f3937d1377c23b8f351315b2792f5d2753bb95420c191d2dc70ad7196e8").unwrap(), - nonce: 0, - tip: 2, - gas_price: 0, - gas_limit: 2100000, - storage_limit: 20000, - action: TransactionAction::Create, - value: 0, - input: vec![0x01], - valid_until: 105, - access_list: vec![], - }; - let sign = hex!("c30a85ee9218af4e2892c82d65a8a7fbeee75c010973d42cee2e52309449d687056c09cf486a16d58d23b0ebfed63a0276d5fb1a464f645dc7607147a37f7a211c"); - assert_eq!(verify_eip712_signature(msg, sign), sender); - - // access_list.storage_keys = vec![] - let msg = EthereumTransactionMessage { - chain_id: 595, - genesis: H256::from_str("0xafb55f3937d1377c23b8f351315b2792f5d2753bb95420c191d2dc70ad7196e8").unwrap(), - nonce: 0, - tip: 2, - gas_price: 0, - gas_limit: 2100000, - storage_limit: 20000, - action: TransactionAction::Create, - value: 0, - input: vec![0x01], - valid_until: 105, - access_list: vec![AccessListItem { - address: hex!("0000000000000000000000000000000000000000").into(), - storage_keys: vec![], - }], - }; - let sign = hex!("a94da7159e29f2a0c9aec08eb62cbb6eefd6ee277960a3c96b183b53201687ce19f1fd9c2cfdace8730fd5249ea11e57701cd0cc20386bbd9d3df5092fe218851c"); - assert_eq!(verify_eip712_signature(msg, sign), sender); - - let msg = EthereumTransactionMessage { - chain_id: 595, - genesis: H256::from_str("0xafb55f3937d1377c23b8f351315b2792f5d2753bb95420c191d2dc70ad7196e8").unwrap(), - nonce: 0, - tip: 2, - gas_price: 0, - gas_limit: 2100000, - storage_limit: 20000, - action: TransactionAction::Create, - value: 0, - input: vec![0x01], - valid_until: 105, - access_list: vec![AccessListItem { - address: hex!("0000000000000000000000000000000000000000").into(), - storage_keys: vec![ - H256::from_str("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef").unwrap(), - H256::from_str("0x0000000000111111111122222222223333333333444444444455555555556666").unwrap(), - H256::from_str("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef").unwrap(), - ], - }], - }; - let sign = hex!("dca9701b77bac69e5a88c7f040a6fa0a051f97305619e66e9182bf3416ca2d0e7b730cb732e2f747754f6b9307d78ce611aabb3692ea48314670a6a8c447dc9b1c"); - assert_eq!(verify_eip712_signature(msg.clone(), sign), sender); - - let mut new_msg = msg.clone(); - new_msg.nonce += 1; - assert_ne!(verify_eip712_signature(new_msg, sign), sender); - - let mut new_msg = msg.clone(); - new_msg.tip += 1; - assert_ne!(verify_eip712_signature(new_msg, sign), sender); - - let mut new_msg = msg.clone(); - new_msg.gas_limit += 1; - assert_ne!(verify_eip712_signature(new_msg, sign), sender); - - let mut new_msg = msg.clone(); - new_msg.storage_limit += 1; - assert_ne!(verify_eip712_signature(new_msg, sign), sender); - - let mut new_msg = msg.clone(); - new_msg.action = TransactionAction::Call(H160::from_str("0x1111111111222222222233333333334444444444").unwrap()); - assert_ne!(verify_eip712_signature(new_msg, sign), sender); - - let mut new_msg = msg.clone(); - new_msg.value += 1; - assert_ne!(verify_eip712_signature(new_msg, sign), sender); - - let mut new_msg = msg.clone(); - new_msg.input = vec![0x00]; - assert_ne!(verify_eip712_signature(new_msg, sign), sender); - - let mut new_msg = msg.clone(); - new_msg.chain_id += 1; - assert_ne!(verify_eip712_signature(new_msg, sign), sender); - - let mut new_msg = msg.clone(); - new_msg.genesis = Default::default(); - assert_ne!(verify_eip712_signature(new_msg, sign), sender); - - let mut new_msg = msg.clone(); - new_msg.access_list = vec![AccessListItem { - address: hex!("bb9bc244d798123fde783fcc1c72d3bb8c189413").into(), - storage_keys: vec![], - }]; - assert_ne!(verify_eip712_signature(new_msg, sign), sender); - - let mut new_msg = msg; - new_msg.valid_until += 1; - assert_ne!(verify_eip712_signature(new_msg, sign), sender); - } - - #[test] - fn verify_eth_should_works() { - let msg = LegacyTransactionMessage { - nonce: U256::from(1), - gas_price: U256::from("0x640000006a"), - gas_limit: U256::from(21000), - action: TransactionAction::Call(H160::from_str("0x1111111111222222222233333333334444444444").unwrap()), - value: U256::from(123123), - input: vec![], - chain_id: Some(595), - }; - - let sign = hex!("f84345a6459785986a1b2df711fe02597d70c1393757a243f8f924ea541d2ecb51476de1aa437cd820d59e1d9836e37e643fec711fe419464e637cab592918751c"); - let sender = Some(H160::from_str("0x14791697260E4c9A71f18484C9f997B308e59325").unwrap()); - - assert_eq!(recover_signer(&sign, msg.hash().as_fixed_bytes()), sender); - - let mut new_msg = msg.clone(); - new_msg.nonce = new_msg.nonce.add(U256::one()); - assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); - - let mut new_msg = msg.clone(); - new_msg.gas_price = new_msg.gas_price.add(U256::one()); - assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); - - let mut new_msg = msg.clone(); - new_msg.gas_limit = new_msg.gas_limit.add(U256::one()); - assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); - - let mut new_msg = msg.clone(); - new_msg.action = TransactionAction::Create; - assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); - - let mut new_msg = msg.clone(); - new_msg.value = new_msg.value.add(U256::one()); - assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); - - let mut new_msg = msg.clone(); - new_msg.input = vec![0x00]; - assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); - - let mut new_msg = msg; - new_msg.chain_id = None; - assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); - } - - #[test] - fn verify_eth_1559_should_works() { - let msg = EIP1559TransactionMessage { - chain_id: 595, - nonce: U256::from(1), - max_priority_fee_per_gas: U256::from(1), - max_fee_per_gas: U256::from("0x640000006a"), - gas_limit: U256::from(21000), - action: TransactionAction::Call(H160::from_str("0x1111111111222222222233333333334444444444").unwrap()), - value: U256::from(123123), - input: vec![], - access_list: vec![], - }; - - let sign = hex!("e88df53d4d66cb7a4f54ea44a44942b9b7f4fb4951525d416d3f7d24755a1f817734270872b103ac04c59d74f4dacdb8a6eff09a6638bd95dad1fa3eda921d891b"); - let sender = Some(H160::from_str("0x14791697260E4c9A71f18484C9f997B308e59325").unwrap()); - - assert_eq!(recover_signer(&sign, msg.hash().as_fixed_bytes()), sender); - - let mut new_msg = msg.clone(); - new_msg.chain_id = new_msg.chain_id.add(1u64); - assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); - - let mut new_msg = msg.clone(); - new_msg.nonce = new_msg.nonce.add(U256::one()); - assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); - - let mut new_msg = msg.clone(); - new_msg.max_priority_fee_per_gas = new_msg.max_priority_fee_per_gas.add(U256::one()); - assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); - - let mut new_msg = msg.clone(); - new_msg.max_fee_per_gas = new_msg.max_fee_per_gas.add(U256::one()); - assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); - - let mut new_msg = msg.clone(); - new_msg.gas_limit = new_msg.gas_limit.add(U256::one()); - assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); - - let mut new_msg = msg.clone(); - new_msg.action = TransactionAction::Create; - assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); - - let mut new_msg = msg.clone(); - new_msg.value = new_msg.value.add(U256::one()); - assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); - - let mut new_msg = msg.clone(); - new_msg.input = vec![0x00]; - assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); - - let mut new_msg = msg; - new_msg.access_list = vec![AccessListItem { - address: hex!("bb9bc244d798123fde783fcc1c72d3bb8c189413").into(), - storage_keys: vec![], - }]; - assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); - } - - #[test] - fn recover_sign_data_should_works() { - let mut msg = EthereumTransactionMessage { - chain_id: 595, - genesis: Default::default(), - nonce: 1, - tip: 0, - gas_price: 0, - gas_limit: 2100000, - storage_limit: 64000, - action: TransactionAction::Call(H160::from_str("0x1111111111222222222233333333334444444444").unwrap()), - value: 0, - input: vec![], - access_list: vec![], - valid_until: 30, - }; - - let ts_fee_per_gas = 200u128.saturating_mul(10u128.saturating_pow(9)) & !0xffff; - let storage_deposit_per_byte = 100_000_000_000_000u128; - - assert_eq!( - recover_sign_data(&msg, ts_fee_per_gas, storage_deposit_per_byte), - Some((200000013288, 34100000)) - ); - msg.valid_until = 3600030; - assert_eq!( - recover_sign_data(&msg, ts_fee_per_gas, storage_deposit_per_byte), - Some((207864333288, 34100000)) - ); - msg.valid_until = u32::MAX; - assert_eq!( - recover_sign_data(&msg, ts_fee_per_gas, storage_deposit_per_byte), - Some((9582499136488, 34100000)) - ); - - // check storage_limit max is 0xffff * 64 + 63 - msg.storage_limit = 0xffff * 64 + 64; - assert_eq!(recover_sign_data(&msg, ts_fee_per_gas, storage_deposit_per_byte), None); - - msg.storage_limit = 0xffff * 64 + 63; - assert_eq!( - recover_sign_data(&msg, ts_fee_per_gas, storage_deposit_per_byte), - Some((9582499201023, 2099220000)) - ); - - assert_eq!( - recover_sign_data(&msg, ts_fee_per_gas, u128::MAX), - Some((9582499201023, 111502054267125439094838181151820)) - ); - - assert_eq!(recover_sign_data(&msg, u128::MAX, storage_deposit_per_byte), None); - - assert_eq!(recover_sign_data(&msg, u128::MAX, u128::MAX), None); - } -} +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +use crate::{evm::EthereumTransactionMessage, signature::SetheumMultiSignature, to_bytes, Address, Balance}; +use frame_support::{ + dispatch::{DispatchInfo, GetDispatchInfo}, + traits::{ExtrinsicCall, Get}, +}; +use module_evm_utility::ethereum::{ + EIP1559TransactionMessage, EIP2930TransactionMessage, LegacyTransactionMessage, TransactionAction, +}; +use module_evm_utility_macro::keccak256; +use parity_scale_codec::{Decode, Encode}; +use scale_info::TypeInfo; +use sp_core::{H160, H256}; +use sp_io::{crypto::secp256k1_ecdsa_recover, hashing::keccak_256}; +use sp_runtime::{ + generic::{CheckedExtrinsic, SetheumUncheckedExtrinsic}, + traits::{self, Checkable, Convert, Extrinsic, ExtrinsicMetadata, Member, SignedExtension, Zero}, + transaction_validity::{InvalidTransaction, TransactionValidityError}, + AccountId32, RuntimeDebug, +}; +#[cfg(not(feature = "std"))] +use sp_std::alloc::format; +use sp_std::{marker::PhantomData, prelude::*}; + +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[scale_info(skip_type_params(ConvertEthTx))] +pub struct SetheumUncheckedExtrinsic( + pub SetheumUncheckedExtrinsic, + PhantomData<(ConvertEthTx, StorageDepositPerByte, TxFeePerGas)>, +); + +impl Extrinsic + for SetheumUncheckedExtrinsic +{ + type Call = Call; + + type SignaturePayload = (Address, SetheumMultiSignature, Extra); + + fn is_signed(&self) -> Option { + self.0.is_signed() + } + + fn new(function: Call, signed_data: Option) -> Option { + Some(if let Some((address, signature, extra)) = signed_data { + Self( + SetheumUncheckedExtrinsic::new_signed(function, address, signature, extra), + PhantomData, + ) + } else { + Self(SetheumUncheckedExtrinsic::new_unsigned(function), PhantomData) + }) + } +} + +impl ExtrinsicMetadata + for SetheumUncheckedExtrinsic +{ + const VERSION: u8 = SetheumUncheckedExtrinsic::::VERSION; + type SignedExtensions = Extra; +} + +impl ExtrinsicCall + for SetheumUncheckedExtrinsic +{ + fn call(&self) -> &Self::Call { + self.0.call() + } +} + +impl Checkable + for SetheumUncheckedExtrinsic +where + Call: Encode + Member, + Extra: SignedExtension, + ConvertEthTx: Convert<(Call, Extra), Result<(EthereumTransactionMessage, Extra), InvalidTransaction>>, + StorageDepositPerByte: Get, + TxFeePerGas: Get, + Lookup: traits::Lookup, +{ + type Checked = CheckedExtrinsic; + + fn check(self, lookup: &Lookup) -> Result { + let function = self.0.function.clone(); + + match self.0.signature { + Some((addr, SetheumMultiSignature::Ethereum(sig), extra)) => { + let (eth_msg, eth_extra) = ConvertEthTx::convert((function.clone(), extra))?; + log::trace!( + target: "evm", "Ethereum eth_msg: {:?}", eth_msg + ); + + if !eth_msg.access_list.len().is_zero() { + // Not yet supported, require empty + return Err(InvalidTransaction::BadProof.into()); + } + + let (tx_gas_price, tx_gas_limit) = if eth_msg.gas_price.is_zero() { + recover_sign_data(ð_msg, TxFeePerGas::get(), StorageDepositPerByte::get()) + .ok_or(InvalidTransaction::BadProof)? + } else { + // eth_call_v2, the gas_price and gas_limit are encoded. + (eth_msg.gas_price as u128, eth_msg.gas_limit as u128) + }; + + let msg = LegacyTransactionMessage { + nonce: eth_msg.nonce.into(), + gas_price: tx_gas_price.into(), + gas_limit: tx_gas_limit.into(), + action: eth_msg.action, + value: eth_msg.value.into(), + input: eth_msg.input, + chain_id: Some(eth_msg.chain_id), + }; + log::trace!( + target: "evm", "tx msg: {:?}", msg + ); + + let msg_hash = msg.hash(); // TODO: consider rewirte this to use `keccak_256` for hashing because it could be faster + + let signer = recover_signer(&sig, msg_hash.as_fixed_bytes()).ok_or(InvalidTransaction::BadProof)?; + + let account_id = lookup.lookup(Address::Address20(signer.into()))?; + let expected_account_id = lookup.lookup(addr)?; + + if account_id != expected_account_id { + return Err(InvalidTransaction::BadProof.into()); + } + + Ok(CheckedExtrinsic { + signed: Some((account_id, eth_extra)), + function, + }) + } + Some((addr, SetheumMultiSignature::Eip2930(sig), extra)) => { + let (eth_msg, eth_extra) = ConvertEthTx::convert((function.clone(), extra))?; + log::trace!( + target: "evm", "Eip2930 eth_msg: {:?}", eth_msg + ); + + let (tx_gas_price, tx_gas_limit) = if eth_msg.gas_price.is_zero() { + recover_sign_data(ð_msg, TxFeePerGas::get(), StorageDepositPerByte::get()) + .ok_or(InvalidTransaction::BadProof)? + } else { + // eth_call_v2, the gas_price and gas_limit are encoded. + (eth_msg.gas_price as u128, eth_msg.gas_limit as u128) + }; + + let msg = EIP2930TransactionMessage { + chain_id: eth_msg.chain_id, + nonce: eth_msg.nonce.into(), + gas_price: tx_gas_price.into(), + gas_limit: tx_gas_limit.into(), + action: eth_msg.action, + value: eth_msg.value.into(), + input: eth_msg.input, + access_list: eth_msg.access_list, + }; + log::trace!( + target: "evm", "tx msg: {:?}", msg + ); + + let msg_hash = msg.hash(); // TODO: consider rewirte this to use `keccak_256` for hashing because it could be faster + + let signer = recover_signer(&sig, msg_hash.as_fixed_bytes()).ok_or(InvalidTransaction::BadProof)?; + + let account_id = lookup.lookup(Address::Address20(signer.into()))?; + let expected_account_id = lookup.lookup(addr)?; + + if account_id != expected_account_id { + return Err(InvalidTransaction::BadProof.into()); + } + + Ok(CheckedExtrinsic { + signed: Some((account_id, eth_extra)), + function, + }) + } + Some((addr, SetheumMultiSignature::Eip1559(sig), extra)) => { + let (eth_msg, eth_extra) = ConvertEthTx::convert((function.clone(), extra))?; + log::trace!( + target: "evm", "Eip1559 eth_msg: {:?}", eth_msg + ); + + let (tx_gas_price, tx_gas_limit) = if eth_msg.gas_price.is_zero() { + recover_sign_data(ð_msg, TxFeePerGas::get(), StorageDepositPerByte::get()) + .ok_or(InvalidTransaction::BadProof)? + } else { + // eth_call_v2, the gas_price and gas_limit are encoded. + (eth_msg.gas_price as u128, eth_msg.gas_limit as u128) + }; + + // tip = priority_fee * gas_limit + let priority_fee = eth_msg.tip.checked_div(eth_msg.gas_limit.into()).unwrap_or_default(); + + let msg = EIP1559TransactionMessage { + chain_id: eth_msg.chain_id, + nonce: eth_msg.nonce.into(), + max_priority_fee_per_gas: priority_fee.into(), + max_fee_per_gas: tx_gas_price.into(), + gas_limit: tx_gas_limit.into(), + action: eth_msg.action, + value: eth_msg.value.into(), + input: eth_msg.input, + access_list: eth_msg.access_list, + }; + log::trace!( + target: "evm", "tx msg: {:?}", msg + ); + + let msg_hash = msg.hash(); // TODO: consider rewirte this to use `keccak_256` for hashing because it could be faster + + let signer = recover_signer(&sig, msg_hash.as_fixed_bytes()).ok_or(InvalidTransaction::BadProof)?; + + let account_id = lookup.lookup(Address::Address20(signer.into()))?; + let expected_account_id = lookup.lookup(addr)?; + + if account_id != expected_account_id { + return Err(InvalidTransaction::BadProof.into()); + } + + Ok(CheckedExtrinsic { + signed: Some((account_id, eth_extra)), + function, + }) + } + Some((addr, SetheumMultiSignature::SetheumEip712(sig), extra)) => { + let (eth_msg, eth_extra) = ConvertEthTx::convert((function.clone(), extra))?; + log::trace!( + target: "evm", "SetheumEip712 eth_msg: {:?}", eth_msg + ); + + let signer = verify_eip712_signature(eth_msg, sig).ok_or(InvalidTransaction::BadProof)?; + + let account_id = lookup.lookup(Address::Address20(signer.into()))?; + let expected_account_id = lookup.lookup(addr)?; + + if account_id != expected_account_id { + return Err(InvalidTransaction::BadProof.into()); + } + + Ok(CheckedExtrinsic { + signed: Some((account_id, eth_extra)), + function, + }) + } + _ => self.0.check(lookup), + } + } + + #[cfg(feature = "try-runtime")] + fn unchecked_into_checked_i_know_what_i_am_doing( + self, + _lookup: &Lookup, + ) -> Result { + unreachable!(); + } +} + +impl GetDispatchInfo + for SetheumUncheckedExtrinsic +where + Call: GetDispatchInfo, + Extra: SignedExtension, +{ + fn get_dispatch_info(&self) -> DispatchInfo { + self.0.get_dispatch_info() + } +} + +impl serde::Serialize + for SetheumUncheckedExtrinsic +{ + fn serialize(&self, seq: S) -> Result + where + S: ::serde::Serializer, + { + self.0.serialize(seq) + } +} + +impl<'a, Call: Decode, Extra: SignedExtension, ConvertEthTx, StorageDepositPerByte, TxFeePerGas> serde::Deserialize<'a> + for SetheumUncheckedExtrinsic +{ + fn deserialize(de: D) -> Result + where + D: serde::Deserializer<'a>, + { + let r = sp_core::bytes::deserialize(de)?; + Decode::decode(&mut &r[..]).map_err(|e| serde::de::Error::custom(format!("Decode error: {}", e))) + } +} + +fn recover_signer(sig: &[u8; 65], msg_hash: &[u8; 32]) -> Option { + secp256k1_ecdsa_recover(sig, msg_hash) + .map(|pubkey| H160::from(H256::from_slice(&keccak_256(&pubkey)))) + .ok() +} + +fn verify_eip712_signature(eth_msg: EthereumTransactionMessage, sig: [u8; 65]) -> Option { + let domain_hash = keccak256!("EIP712Domain(string name,string version,uint256 chainId,bytes32 salt)"); + let access_list_type_hash = keccak256!("AccessList(address address,uint256[] storageKeys)"); + let tx_type_hash = keccak256!("Transaction(string action,address to,uint256 nonce,uint256 tip,bytes data,uint256 value,uint256 gasLimit,uint256 storageLimit,AccessList[] accessList,uint256 validUntil)AccessList(address address,uint256[] storageKeys)"); + + let mut domain_seperator_msg = domain_hash.to_vec(); + domain_seperator_msg.extend_from_slice(keccak256!("Setheum EVM")); // name + domain_seperator_msg.extend_from_slice(keccak256!("1")); // version + domain_seperator_msg.extend_from_slice(&to_bytes(eth_msg.chain_id)); // chain id + domain_seperator_msg.extend_from_slice(eth_msg.genesis.as_bytes()); // salt + let domain_separator = keccak_256(domain_seperator_msg.as_slice()); + + let mut tx_msg = tx_type_hash.to_vec(); + match eth_msg.action { + TransactionAction::Call(to) => { + tx_msg.extend_from_slice(keccak256!("Call")); + tx_msg.extend_from_slice(H256::from(to).as_bytes()); + } + TransactionAction::Create => { + tx_msg.extend_from_slice(keccak256!("Create")); + tx_msg.extend_from_slice(H256::default().as_bytes()); + } + } + tx_msg.extend_from_slice(&to_bytes(eth_msg.nonce)); + tx_msg.extend_from_slice(&to_bytes(eth_msg.tip)); + tx_msg.extend_from_slice(&keccak_256(eth_msg.input.as_slice())); + tx_msg.extend_from_slice(&to_bytes(eth_msg.value)); + tx_msg.extend_from_slice(&to_bytes(eth_msg.gas_limit)); + tx_msg.extend_from_slice(&to_bytes(eth_msg.storage_limit)); + + let mut access_list: Vec<[u8; 32]> = Vec::new(); + eth_msg.access_list.iter().for_each(|v| { + let mut access_list_msg = access_list_type_hash.to_vec(); + access_list_msg.extend_from_slice(&to_bytes(v.address.as_bytes())); + access_list_msg.extend_from_slice(&keccak_256( + &v.storage_keys.iter().map(|v| v.as_bytes()).collect::>().concat(), + )); + access_list.push(keccak_256(access_list_msg.as_slice())); + }); + tx_msg.extend_from_slice(&keccak_256(&access_list.concat())); + tx_msg.extend_from_slice(&to_bytes(eth_msg.valid_until)); + + let mut msg = b"\x19\x01".to_vec(); + msg.extend_from_slice(&domain_separator); + msg.extend_from_slice(&keccak_256(tx_msg.as_slice())); + + let msg_hash = keccak_256(msg.as_slice()); + + recover_signer(&sig, &msg_hash) +} + +fn recover_sign_data( + eth_msg: &EthereumTransactionMessage, + ts_fee_per_gas: u128, + storage_deposit_per_byte: u128, +) -> Option<(u128, u128)> { + // tx_gas_price = tx_fee_per_gas + block_period << 16 + storage_entry_limit + // tx_gas_limit = gas_limit + storage_entry_deposit / tx_fee_per_gas * storage_entry_limit + let block_period = eth_msg.valid_until.saturating_div(30); + // u16: max value 0xffff * 64 = 4194240 bytes = 4MB + let storage_entry_limit: u16 = eth_msg.storage_limit.saturating_div(64).try_into().ok()?; + let storage_entry_deposit = storage_deposit_per_byte.saturating_mul(64); + let tx_gas_price = ts_fee_per_gas + .checked_add(Into::::into(block_period).checked_shl(16)?)? + .checked_add(storage_entry_limit.into())?; + // There is a loss of precision here, so the order of calculation must be guaranteed + // must ensure storage_deposit / tx_fee_per_gas * storage_limit + let tx_gas_limit = storage_entry_deposit + .checked_div(ts_fee_per_gas) + .expect("divisor is non-zero; qed") + .checked_mul(storage_entry_limit.into())? + .checked_add(eth_msg.gas_limit.into())?; + + Some((tx_gas_price, tx_gas_limit)) +} + +#[cfg(test)] +mod tests { + use super::*; + use hex_literal::hex; + use module_evm_utility::ethereum::AccessListItem; + use sp_core::U256; + use std::{ops::Add, str::FromStr}; + + #[test] + fn verify_eip712_should_works() { + let sender = Some(H160::from_str("0x14791697260E4c9A71f18484C9f997B308e59325").unwrap()); + // access_list = vec![] + let msg = EthereumTransactionMessage { + chain_id: 595, + genesis: H256::from_str("0xafb55f3937d1377c23b8f351315b2792f5d2753bb95420c191d2dc70ad7196e8").unwrap(), + nonce: 0, + tip: 2, + gas_price: 0, + gas_limit: 2100000, + storage_limit: 20000, + action: TransactionAction::Create, + value: 0, + input: vec![0x01], + valid_until: 105, + access_list: vec![], + }; + let sign = hex!("c30a85ee9218af4e2892c82d65a8a7fbeee75c010973d42cee2e52309449d687056c09cf486a16d58d23b0ebfed63a0276d5fb1a464f645dc7607147a37f7a211c"); + assert_eq!(verify_eip712_signature(msg, sign), sender); + + // access_list.storage_keys = vec![] + let msg = EthereumTransactionMessage { + chain_id: 595, + genesis: H256::from_str("0xafb55f3937d1377c23b8f351315b2792f5d2753bb95420c191d2dc70ad7196e8").unwrap(), + nonce: 0, + tip: 2, + gas_price: 0, + gas_limit: 2100000, + storage_limit: 20000, + action: TransactionAction::Create, + value: 0, + input: vec![0x01], + valid_until: 105, + access_list: vec![AccessListItem { + address: hex!("0000000000000000000000000000000000000000").into(), + storage_keys: vec![], + }], + }; + let sign = hex!("a94da7159e29f2a0c9aec08eb62cbb6eefd6ee277960a3c96b183b53201687ce19f1fd9c2cfdace8730fd5249ea11e57701cd0cc20386bbd9d3df5092fe218851c"); + assert_eq!(verify_eip712_signature(msg, sign), sender); + + let msg = EthereumTransactionMessage { + chain_id: 595, + genesis: H256::from_str("0xafb55f3937d1377c23b8f351315b2792f5d2753bb95420c191d2dc70ad7196e8").unwrap(), + nonce: 0, + tip: 2, + gas_price: 0, + gas_limit: 2100000, + storage_limit: 20000, + action: TransactionAction::Create, + value: 0, + input: vec![0x01], + valid_until: 105, + access_list: vec![AccessListItem { + address: hex!("0000000000000000000000000000000000000000").into(), + storage_keys: vec![ + H256::from_str("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef").unwrap(), + H256::from_str("0x0000000000111111111122222222223333333333444444444455555555556666").unwrap(), + H256::from_str("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef").unwrap(), + ], + }], + }; + let sign = hex!("dca9701b77bac69e5a88c7f040a6fa0a051f97305619e66e9182bf3416ca2d0e7b730cb732e2f747754f6b9307d78ce611aabb3692ea48314670a6a8c447dc9b1c"); + assert_eq!(verify_eip712_signature(msg.clone(), sign), sender); + + let mut new_msg = msg.clone(); + new_msg.nonce += 1; + assert_ne!(verify_eip712_signature(new_msg, sign), sender); + + let mut new_msg = msg.clone(); + new_msg.tip += 1; + assert_ne!(verify_eip712_signature(new_msg, sign), sender); + + let mut new_msg = msg.clone(); + new_msg.gas_limit += 1; + assert_ne!(verify_eip712_signature(new_msg, sign), sender); + + let mut new_msg = msg.clone(); + new_msg.storage_limit += 1; + assert_ne!(verify_eip712_signature(new_msg, sign), sender); + + let mut new_msg = msg.clone(); + new_msg.action = TransactionAction::Call(H160::from_str("0x1111111111222222222233333333334444444444").unwrap()); + assert_ne!(verify_eip712_signature(new_msg, sign), sender); + + let mut new_msg = msg.clone(); + new_msg.value += 1; + assert_ne!(verify_eip712_signature(new_msg, sign), sender); + + let mut new_msg = msg.clone(); + new_msg.input = vec![0x00]; + assert_ne!(verify_eip712_signature(new_msg, sign), sender); + + let mut new_msg = msg.clone(); + new_msg.chain_id += 1; + assert_ne!(verify_eip712_signature(new_msg, sign), sender); + + let mut new_msg = msg.clone(); + new_msg.genesis = Default::default(); + assert_ne!(verify_eip712_signature(new_msg, sign), sender); + + let mut new_msg = msg.clone(); + new_msg.access_list = vec![AccessListItem { + address: hex!("bb9bc244d798123fde783fcc1c72d3bb8c189413").into(), + storage_keys: vec![], + }]; + assert_ne!(verify_eip712_signature(new_msg, sign), sender); + + let mut new_msg = msg; + new_msg.valid_until += 1; + assert_ne!(verify_eip712_signature(new_msg, sign), sender); + } + + #[test] + fn verify_eth_should_works() { + let msg = LegacyTransactionMessage { + nonce: U256::from(1), + gas_price: U256::from("0x640000006a"), + gas_limit: U256::from(21000), + action: TransactionAction::Call(H160::from_str("0x1111111111222222222233333333334444444444").unwrap()), + value: U256::from(123123), + input: vec![], + chain_id: Some(595), + }; + + let sign = hex!("f84345a6459785986a1b2df711fe02597d70c1393757a243f8f924ea541d2ecb51476de1aa437cd820d59e1d9836e37e643fec711fe419464e637cab592918751c"); + let sender = Some(H160::from_str("0x14791697260E4c9A71f18484C9f997B308e59325").unwrap()); + + assert_eq!(recover_signer(&sign, msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg.clone(); + new_msg.nonce = new_msg.nonce.add(U256::one()); + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg.clone(); + new_msg.gas_price = new_msg.gas_price.add(U256::one()); + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg.clone(); + new_msg.gas_limit = new_msg.gas_limit.add(U256::one()); + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg.clone(); + new_msg.action = TransactionAction::Create; + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg.clone(); + new_msg.value = new_msg.value.add(U256::one()); + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg.clone(); + new_msg.input = vec![0x00]; + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg; + new_msg.chain_id = None; + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + } + + #[test] + fn verify_eth_1559_should_works() { + let msg = EIP1559TransactionMessage { + chain_id: 595, + nonce: U256::from(1), + max_priority_fee_per_gas: U256::from(1), + max_fee_per_gas: U256::from("0x640000006a"), + gas_limit: U256::from(21000), + action: TransactionAction::Call(H160::from_str("0x1111111111222222222233333333334444444444").unwrap()), + value: U256::from(123123), + input: vec![], + access_list: vec![], + }; + + let sign = hex!("e88df53d4d66cb7a4f54ea44a44942b9b7f4fb4951525d416d3f7d24755a1f817734270872b103ac04c59d74f4dacdb8a6eff09a6638bd95dad1fa3eda921d891b"); + let sender = Some(H160::from_str("0x14791697260E4c9A71f18484C9f997B308e59325").unwrap()); + + assert_eq!(recover_signer(&sign, msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg.clone(); + new_msg.chain_id = new_msg.chain_id.add(1u64); + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg.clone(); + new_msg.nonce = new_msg.nonce.add(U256::one()); + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg.clone(); + new_msg.max_priority_fee_per_gas = new_msg.max_priority_fee_per_gas.add(U256::one()); + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg.clone(); + new_msg.max_fee_per_gas = new_msg.max_fee_per_gas.add(U256::one()); + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg.clone(); + new_msg.gas_limit = new_msg.gas_limit.add(U256::one()); + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg.clone(); + new_msg.action = TransactionAction::Create; + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg.clone(); + new_msg.value = new_msg.value.add(U256::one()); + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg.clone(); + new_msg.input = vec![0x00]; + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg; + new_msg.access_list = vec![AccessListItem { + address: hex!("bb9bc244d798123fde783fcc1c72d3bb8c189413").into(), + storage_keys: vec![], + }]; + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + } + + #[test] + fn recover_sign_data_should_works() { + let mut msg = EthereumTransactionMessage { + chain_id: 595, + genesis: Default::default(), + nonce: 1, + tip: 0, + gas_price: 0, + gas_limit: 2100000, + storage_limit: 64000, + action: TransactionAction::Call(H160::from_str("0x1111111111222222222233333333334444444444").unwrap()), + value: 0, + input: vec![], + access_list: vec![], + valid_until: 30, + }; + + let ts_fee_per_gas = 200u128.saturating_mul(10u128.saturating_pow(9)) & !0xffff; + let storage_deposit_per_byte = 100_000_000_000_000u128; + + assert_eq!( + recover_sign_data(&msg, ts_fee_per_gas, storage_deposit_per_byte), + Some((200000013288, 34100000)) + ); + msg.valid_until = 3600030; + assert_eq!( + recover_sign_data(&msg, ts_fee_per_gas, storage_deposit_per_byte), + Some((207864333288, 34100000)) + ); + msg.valid_until = u32::MAX; + assert_eq!( + recover_sign_data(&msg, ts_fee_per_gas, storage_deposit_per_byte), + Some((9582499136488, 34100000)) + ); + + // check storage_limit max is 0xffff * 64 + 63 + msg.storage_limit = 0xffff * 64 + 64; + assert_eq!(recover_sign_data(&msg, ts_fee_per_gas, storage_deposit_per_byte), None); + + msg.storage_limit = 0xffff * 64 + 63; + assert_eq!( + recover_sign_data(&msg, ts_fee_per_gas, storage_deposit_per_byte), + Some((9582499201023, 2099220000)) + ); + + assert_eq!( + recover_sign_data(&msg, ts_fee_per_gas, u128::MAX), + Some((9582499201023, 111502054267125439094838181151820)) + ); + + assert_eq!(recover_sign_data(&msg, u128::MAX, storage_deposit_per_byte), None); + + assert_eq!(recover_sign_data(&msg, u128::MAX, u128::MAX), None); + } +}