Skip to content

Commit

Permalink
Add eHMND ERC20 precompile (#746)
Browse files Browse the repository at this point in the history
* Add basic implementation for total_supply and balance_of calls

* Integrate initial precompile-evm-balances-erc20 implementation into humanode-runtime

* Add metadata erc20 methods

* Add approvals related logic

* Add transfer related method

* Add transferFrom related method

* Add event logs

* Add docs to interface actions

* Fix some clippy

* Add docs for approves logic

* Fix typos

* Remove unnecesary docs

* Introduce helper functions to simplify code

* Some refactoring

* Update features snapshot

* Fix docs

* Add ERC20 solidity interface

* Rename erc20 metadata at frontier precompiles

* Add mocked environment

* Add metadata related tests

* Add balance_of_works and total_supply_works tests

* Add approve_works test

* Rename some tests

* Add transfer_works test

* Add transfer_from_works test

* Rename symbol

Co-authored-by: MOZGIII <[email protected]>

* Edit name erc20 metadata

Co-authored-by: MOZGIII <[email protected]>

* Introduce pallet-erc20 to store approvals data

* Use approvals logic from pallet-erc20 at precompile

* Use currency config instead of pallet-evm-balances config

* Add erc20 related logic into pallet-erc20

* Add utility aliases

* Integrate pallet-erc20 logic into precompile

* Fix mock at pallet-erc20

* Fix mock at precompile

* Remove redundant dependency

* Rename pallet-erc20 to pallet-token-wrapper

* Rename precompile-evm-balances-erc20 into precompile-token-wrapper

* Integrate introduced changes into humanode-runtime

* Use wrapped-token instead for token-wrapper

* Rename EvmBalancesErc20 into WrappedEvmBalances

* Add total_supply_works test to pallet-wrapped-token

* Improve approvals related test at pallet-wrapped-token

* Add transfer_works test

* Add transfer_from_works test

* Add fails tests for transfer from logic

* Add with_storage_layer usage at pallet-wrapped-token

* Fix features

* Use approvals aliases

* Rename pallet-wrapped-token into pallet-erc20-support

* Some renaming and docs improvements

* Update features snapshot

* Add comments for tests at pallet-erc20-support

* Improve dispatch error handling

* Add more tests at precompile-erc20-support

* Rename precompile-erc20-support into precompile-native-currency

* Rename Erc20EvmBalancesMetadata into EvmBalancesErc20Metadata

* Rename Erc20EvmBalances into EvmBalancesErc20Support

* Revert "Use approvals aliases"

This reverts commit 654dc65.

* Properly handle transferFrom logic

* Improve dispatch error handling

* Add approve_overwrite_works test

* Fix transfer

* Add tests to check approve logic with sending full approved balance

* Add approve_approval_value_more_than_balance_works test

---------

Co-authored-by: MOZGIII <[email protected]>
  • Loading branch information
dmitrylavrenov and MOZGIII authored Sep 18, 2023
1 parent 81af4ca commit c58d5a3
Show file tree
Hide file tree
Showing 14 changed files with 1,859 additions and 5 deletions.
39 changes: 37 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions crates/humanode-runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pallet-bootnodes = { path = "../pallet-bootnodes", default-features = false }
pallet-chain-properties = { path = "../pallet-chain-properties", default-features = false }
pallet-chain-start-moment = { path = "../pallet-chain-start-moment", default-features = false }
pallet-currency-swap = { path = "../pallet-currency-swap", default-features = false }
pallet-erc20-support = { path = "../pallet-erc20-support", default-features = false }
pallet-ethereum-chain-id = { path = "../pallet-ethereum-chain-id", default-features = false }
pallet-evm-accounts-mapping = { path = "../pallet-evm-accounts-mapping", default-features = false }
pallet-humanode-session = { path = "../pallet-humanode-session", default-features = false }
Expand All @@ -32,6 +33,7 @@ pallet-vesting = { path = "../pallet-vesting", default-features = false }
precompile-bioauth = { path = "../precompile-bioauth", default-features = false }
precompile-currency-swap = { path = "../precompile-currency-swap", default-features = false }
precompile-evm-accounts-mapping = { path = "../precompile-evm-accounts-mapping", default-features = false }
precompile-native-currency = { path = "../precompile-native-currency", default-features = false }
precompile-utils = { path = "../precompile-utils", default-features = false }
primitives-auth-ticket = { path = "../primitives-auth-ticket", default-features = false }
primitives-currency-swap-proxy = { path = "../primitives-currency-swap-proxy", default-features = false }
Expand Down Expand Up @@ -164,6 +166,7 @@ std = [
"pallet-chain-properties/std",
"pallet-chain-start-moment/std",
"pallet-currency-swap/std",
"pallet-erc20-support/std",
"pallet-ethereum-chain-id/std",
"pallet-ethereum/std",
"pallet-evm-accounts-mapping/std",
Expand All @@ -190,6 +193,7 @@ std = [
"precompile-bioauth/std",
"precompile-currency-swap/std",
"precompile-evm-accounts-mapping/std",
"precompile-native-currency/std",
"precompile-utils/std",
"primitives-auth-ticket/std",
"primitives-currency-swap-proxy/std",
Expand Down Expand Up @@ -234,6 +238,7 @@ try-runtime = [
"pallet-chain-properties/try-runtime",
"pallet-chain-start-moment/try-runtime",
"pallet-currency-swap/try-runtime",
"pallet-erc20-support/try-runtime",
"pallet-ethereum-chain-id/try-runtime",
"pallet-ethereum/try-runtime",
"pallet-evm-accounts-mapping/try-runtime",
Expand Down
13 changes: 11 additions & 2 deletions crates/humanode-runtime/src/frontier_precompiles.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
use frame_support::traits::Currency;
use pallet_evm::{Precompile, PrecompileHandle, PrecompileResult, PrecompileSet};
use pallet_evm_precompile_modexp::Modexp;
use pallet_evm_precompile_sha3fips::Sha3FIPS256;
use pallet_evm_precompile_simple::{ECRecover, ECRecoverPublicKey, Identity, Ripemd160, Sha256};
use precompile_bioauth::Bioauth;
use precompile_currency_swap::CurrencySwap;
use precompile_evm_accounts_mapping::EvmAccountsMapping;
use sp_core::H160;
use precompile_native_currency::NativeCurrency;
use sp_core::{H160, U256};
use sp_std::marker::PhantomData;

use crate::{currency_swap, AccountId, ConstU64, EvmAccountId};
Expand All @@ -23,7 +25,7 @@ where
R: pallet_evm::Config,
{
pub fn used_addresses() -> sp_std::vec::Vec<H160> {
sp_std::vec![1_u64, 2, 3, 4, 5, 1024, 1025, 2048, 2049, 2304]
sp_std::vec![1_u64, 2, 3, 4, 5, 1024, 1025, 2048, 2049, 2050, 2304]
.into_iter()
.map(hash)
.collect()
Expand All @@ -35,6 +37,12 @@ where
R: pallet_evm::Config,
R: pallet_bioauth::Config,
R: pallet_evm_accounts_mapping::Config,
R: pallet_evm_balances::Config,
R: pallet_erc20_support::Config,
<R as pallet_erc20_support::Config>::AccountId: From<H160>,
<<R as pallet_erc20_support::Config>::Currency as Currency<
<R as pallet_erc20_support::Config>::AccountId,
>>::Balance: Into<U256> + TryFrom<U256>,
R::ValidatorPublicKey: for<'a> TryFrom<&'a [u8]> + Eq,
{
fn execute(&self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {
Expand All @@ -51,6 +59,7 @@ where
// Humanode precompiles:
a if a == hash(2048) => Some(Bioauth::<R>::execute(handle)),
a if a == hash(2049) => Some(EvmAccountsMapping::<R>::execute(handle)),
a if a == hash(2050) => Some(NativeCurrency::<R, ConstU64<200>>::execute(handle)),
a if a == hash(2304) => Some(CurrencySwap::<
currency_swap::EvmToNativeOneToOne,
EvmAccountId,
Expand Down
23 changes: 23 additions & 0 deletions crates/humanode-runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -788,6 +788,28 @@ impl pallet_balanced_currency_swap_bridges_initializer::Config for Runtime {
type WeightInfo = ();
}

pub struct EvmBalancesErc20Metadata;

impl pallet_erc20_support::Metadata for EvmBalancesErc20Metadata {
fn name() -> &'static str {
"Wrapped eHMND"
}

fn symbol() -> &'static str {
"WeHMND"
}

fn decimals() -> u8 {
18
}
}

impl pallet_erc20_support::Config for Runtime {
type AccountId = EvmAccountId;
type Currency = EvmBalances;
type Metadata = EvmBalancesErc20Metadata;
}

// Create the runtime by composing the FRAME pallets that were previously
// configured.
construct_runtime!(
Expand Down Expand Up @@ -832,6 +854,7 @@ construct_runtime!(
EvmToNativeSwapBridgePot: pallet_pot::<Instance5> = 34,
CurrencySwap: pallet_currency_swap = 35,
BalancedCurrencySwapBridgesInitializer: pallet_balanced_currency_swap_bridges_initializer = 36,
EvmBalancesErc20Support: pallet_erc20_support = 37,
}
);

Expand Down
20 changes: 20 additions & 0 deletions crates/pallet-erc20-support/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "pallet-erc20-support"
version = "0.1.0"
edition = "2021"
publish = false

[dependencies]
codec = { workspace = true, package = "parity-scale-codec", features = ["derive"] }
frame-support = { workspace = true }
frame-system = { workspace = true }
scale-info = { workspace = true, features = ["derive"] }

[dev-dependencies]
pallet-balances = { workspace = true }
sp-core = { workspace = true }

[features]
default = ["std"]
std = ["codec/std", "frame-support/std", "frame-system/std", "pallet-balances/std", "scale-info/std", "sp-core/std"]
try-runtime = ["frame-support/try-runtime", "frame-system/try-runtime", "pallet-balances/try-runtime"]
165 changes: 165 additions & 0 deletions crates/pallet-erc20-support/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
//! A substrate pallet that exposes currency instance using the ERC20 interface standard..
#![cfg_attr(not(feature = "std"), no_std)]

use frame_support::{
sp_runtime::{traits::CheckedSub, DispatchResult},
storage::with_storage_layer,
traits::{Currency, StorageVersion},
};
pub use pallet::*;

#[cfg(test)]
mod mock;

#[cfg(test)]
mod tests;

/// Metadata of an ERC20 token.
pub trait Metadata {
/// Returns the name of the token.
fn name() -> &'static str;

/// Returns the symbol of the token.
fn symbol() -> &'static str;

/// Returns the decimals places of the token.
fn decimals() -> u8;
}

/// The current storage version.
const STORAGE_VERSION: StorageVersion = StorageVersion::new(0);

/// Utility alias for easy access to the [`Config::AccountId`].
type AccountIdOf<T, I> = <T as Config<I>>::AccountId;

/// Utility alias for easy access to the [`Currency::Balance`] of the [`Config::Currency`] type.
type BalanceOf<T, I> = <<T as Config<I>>::Currency as Currency<AccountIdOf<T, I>>>::Balance;

// We have to temporarily allow some clippy lints. Later on we'll send patches to substrate to
// fix them at their end.
#[allow(clippy::missing_docs_in_private_items)]
#[frame_support::pallet]
pub mod pallet {
use frame_support::{pallet_prelude::*, sp_runtime::traits::MaybeDisplay, sp_std::fmt::Debug};

use super::*;

#[pallet::pallet]
#[pallet::storage_version(STORAGE_VERSION)]
#[pallet::generate_store(pub(super) trait Store)]
pub struct Pallet<T, I = ()>(_);

/// Configuration trait of this pallet.
#[pallet::config]
pub trait Config<I: 'static = ()>: frame_system::Config {
/// The user account identifier type.
type AccountId: Parameter
+ Member
+ MaybeSerializeDeserialize
+ Debug
+ MaybeDisplay
+ Ord
+ MaxEncodedLen;

/// The currency to be exposed as ERC20 token.
type Currency: Currency<AccountIdOf<Self, I>>;

/// Interface into ERC20 metadata implementation.
type Metadata: Metadata;
}

/// ERC20-style approvals data.
/// (Owner => Allowed => Amount).
#[pallet::storage]
#[pallet::getter(fn approvals)]
pub type Approvals<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
_,
Blake2_128Concat,
AccountIdOf<T, I>,
Blake2_128Concat,
AccountIdOf<T, I>,
BalanceOf<T, I>,
ValueQuery,
>;

/// Possible errors.
#[pallet::error]
pub enum Error<T, I = ()> {
/// Spender can't transfer tokens more than allowed.
SpendMoreThanAllowed,
}
}

impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// Returns the amount of tokens in existence.
pub fn total_supply() -> BalanceOf<T, I> {
T::Currency::total_issuance()
}

/// Returns the amount of tokens owned by provided account.
pub fn balance_of(owner: &AccountIdOf<T, I>) -> BalanceOf<T, I> {
T::Currency::total_balance(owner)
}

/// Returns the remaining number of tokens that spender will be allowed to spend on behalf of
/// owner. This is zero by default.
pub fn allowance(owner: &AccountIdOf<T, I>, spender: &AccountIdOf<T, I>) -> BalanceOf<T, I> {
<Approvals<T, I>>::get(owner, spender)
}

/// Sets amount as the allowance of spender over the caller’s tokens.
pub fn approve(owner: AccountIdOf<T, I>, spender: AccountIdOf<T, I>, amount: BalanceOf<T, I>) {
<Approvals<T, I>>::insert(owner, spender, amount);
}

/// Moves amount tokens from the caller’s account to recipient.
pub fn transfer(
caller: AccountIdOf<T, I>,
recipient: AccountIdOf<T, I>,
amount: BalanceOf<T, I>,
) -> DispatchResult {
with_storage_layer(move || {
T::Currency::transfer(
&caller,
&recipient,
amount,
frame_support::traits::ExistenceRequirement::AllowDeath,
)?;

Ok(())
})
}

/// Moves amount tokens from sender to recipient using the allowance mechanism,
/// amount is then deducted from the caller’s allowance.
pub fn transfer_from(
caller: AccountIdOf<T, I>,
sender: AccountIdOf<T, I>,
recipient: AccountIdOf<T, I>,
amount: BalanceOf<T, I>,
) -> DispatchResult {
with_storage_layer(move || {
<Approvals<T, I>>::mutate(sender.clone(), caller, |entry| {
// Remove "value" from allowed, exit if underflow.
let allowed = entry
.checked_sub(&amount)
.ok_or(Error::<T, I>::SpendMoreThanAllowed)?;

// Update allowed value.
*entry = allowed;

Ok::<(), Error<T, I>>(())
})?;

T::Currency::transfer(
&sender,
&recipient,
amount,
frame_support::traits::ExistenceRequirement::AllowDeath,
)?;

Ok(())
})
}
}
Loading

0 comments on commit c58d5a3

Please sign in to comment.