Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions Cargo.lock

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

46 changes: 46 additions & 0 deletions crates/precompile-currency-swap/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
[package]
name = "precompile-currency-swap"
version = "0.1.0"
edition = "2021"
publish = false

[dependencies]
precompile-utils = { path = "../precompile-utils", default-features = false }
primitives-currency-swap = { path = "../primitives-currency-swap", default-features = false }

codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] }
fp-evm = { git = "https://github.com/humanode-network/frontier", branch = "locked/polkadot-v0.9.38", default-features = false }
frame-support = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "locked/polkadot-v0.9.38" }
num_enum = { version = "0.6.0", default-features = false }
pallet-evm = { git = "https://github.com/humanode-network/frontier", branch = "locked/polkadot-v0.9.38", default-features = false }
scale-info = { version = "2.5.0", default-features = false, features = ["derive"] }
sp-core = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "locked/polkadot-v0.9.38" }

[dev-dependencies]
frame-system = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "locked/polkadot-v0.9.38" }
hex-literal = "0.4"
mockall = "0.11"
pallet-balances = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "locked/polkadot-v0.9.38" }
pallet-evm = { default-features = false, git = "https://github.com/humanode-network/frontier", branch = "locked/polkadot-v0.9.38" }
pallet-evm-balances = { default-features = false, git = "https://github.com/humanode-network/frontier", branch = "locked/polkadot-v0.9.38" }
pallet-evm-system = { default-features = false, git = "https://github.com/humanode-network/frontier", branch = "locked/polkadot-v0.9.38" }
pallet-timestamp = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "locked/polkadot-v0.9.38" }

[features]
default = ["std"]
std = [
"codec/std",
"fp-evm/std",
"frame-support/std",
"frame-system/std",
"num_enum/std",
"pallet-balances/std",
"pallet-evm-balances/std",
"pallet-evm-system/std",
"pallet-evm/std",
"pallet-timestamp/std",
"precompile-utils/std",
"primitives-currency-swap/std",
"scale-info/std",
"sp-core/std",
]
22 changes: 22 additions & 0 deletions crates/precompile-currency-swap/CurrencySwap.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: UNLICENSED

pragma solidity >=0.7.0 <0.9.0;

/**
* @title Currency Swap Interface
*
* An interface enabling swapping the funds from EVM accounts to
* native Substrate accounts.
*
* Address: 0x0000000000000000000000000000000000000900
*/
interface CurrencySwap {
/**
* Transfer the funds from an EVM account to native substrate account.
* Selector: 76467cbd
*
* @param nativeAddress The native address to send the funds to.
* @return success Whether or not the swap was successful.
*/
function swap(bytes32 nativeAddress) external payable returns (bool success);
}
146 changes: 146 additions & 0 deletions crates/precompile-currency-swap/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
//! A precompile to swap EVM tokens with native chain tokens.

#![cfg_attr(not(feature = "std"), no_std)]

use frame_support::{
sp_runtime,
sp_std::{marker::PhantomData, prelude::*},
traits::tokens::currency::Currency,
};
use pallet_evm::{
ExitError, ExitRevert, Precompile, PrecompileFailure, PrecompileHandle, PrecompileOutput,
PrecompileResult,
};
use precompile_utils::{succeed, EvmDataWriter, EvmResult, PrecompileHandleExt};
use sp_core::{Get, H160, H256, U256};

#[cfg(test)]
mod mock;

#[cfg(test)]
mod tests;

/// Possible actions for this interface.
#[precompile_utils::generate_function_selector]
#[derive(Debug, PartialEq)]
pub enum Action {
/// Swap EVM tokens to native tokens.
Swap = "swap(bytes32)",
}

/// Exposes the currency swap interface to EVM.
pub struct CurrencySwap<CurrencySwapT, AccountIdFrom, AccountIdTo, GasCost>(
PhantomData<(CurrencySwapT, AccountIdFrom, AccountIdTo, GasCost)>,
)
where
AccountIdFrom: From<H160>,
AccountIdTo: From<[u8; 32]>,
CurrencySwapT: primitives_currency_swap::CurrencySwap<AccountIdFrom, AccountIdTo>,
FromBalanceFor<CurrencySwapT, AccountIdFrom, AccountIdTo>: TryFrom<U256>,
GasCost: Get<u64>;

impl<CurrencySwapT, AccountIdFrom, AccountIdTo, GasCost> Precompile
for CurrencySwap<CurrencySwapT, AccountIdFrom, AccountIdTo, GasCost>
where
AccountIdFrom: From<H160>,
AccountIdTo: From<[u8; 32]>,
CurrencySwapT: primitives_currency_swap::CurrencySwap<AccountIdFrom, AccountIdTo>,
FromBalanceFor<CurrencySwapT, AccountIdFrom, AccountIdTo>: TryFrom<U256>,
GasCost: Get<u64>,
{
fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult {
handle.record_cost(GasCost::get())?;

let selector = handle
.read_selector()
.map_err(|_| PrecompileFailure::Error {
exit_status: ExitError::Other("invalid function selector".into()),
})?;

match selector {
Action::Swap => Self::swap(handle),
}
}
}

/// Utility alias for easy access to the [`Currency::Balance`] of
/// the [`primitives_currency_swap::CurrencySwap::From`] type.
type FromBalanceFor<T, AccountIdFrom, AccountIdTo> =
<FromCurrencyFor<T, AccountIdFrom, AccountIdTo> as Currency<AccountIdFrom>>::Balance;

/// Utility alias for easy access to [`primitives_currency_swap::CurrencySwap::From`] type.
type FromCurrencyFor<T, AccountIdFrom, AccountIdTo> =
<T as primitives_currency_swap::CurrencySwap<AccountIdFrom, AccountIdTo>>::From;

impl<CurrencySwapT, AccountIdFrom, AccountIdTo, GasCost>
CurrencySwap<CurrencySwapT, AccountIdFrom, AccountIdTo, GasCost>
where
AccountIdFrom: From<H160>,
AccountIdTo: From<[u8; 32]>,
CurrencySwapT: primitives_currency_swap::CurrencySwap<AccountIdFrom, AccountIdTo>,
FromBalanceFor<CurrencySwapT, AccountIdFrom, AccountIdTo>: TryFrom<U256>,
GasCost: Get<u64>,
{
/// Swap EVM tokens to native tokens.
fn swap(handle: &mut impl PrecompileHandle) -> EvmResult<PrecompileOutput> {
let mut input = handle.read_input()?;

let fp_evm::Context {
address,
apparent_value: value,
..
} = handle.context();

// Here we must withdraw from self (i.e. from the precompile address, not from the caller
// address), since the funds have already been transferred to us (precompile) as this point.
let from: AccountIdFrom = (*address).into();

let value: FromBalanceFor<CurrencySwapT, AccountIdFrom, AccountIdTo> =
(*value).try_into().map_err(|_| PrecompileFailure::Error {
exit_status: ExitError::Other("value is out of bounds".into()),
})?;

input
.expect_arguments(1)
.map_err(|_| PrecompileFailure::Error {
exit_status: ExitError::Other("exactly one argument is expected".into()),
})?;

let to: H256 = input.read()?;
let to: [u8; 32] = to.into();
let to: AccountIdTo = to.into();

let junk_data = input.read_till_end()?;
if !junk_data.is_empty() {
return Err(PrecompileFailure::Error {
exit_status: ExitError::Other("junk at the end of input".into()),
});
}

let imbalance = CurrencySwapT::From::withdraw(
&from,
value,
frame_support::traits::WithdrawReasons::TRANSFER,
frame_support::traits::ExistenceRequirement::AllowDeath,
)
.map_err(|error| match error {
sp_runtime::DispatchError::Token(sp_runtime::TokenError::NoFunds) => {
PrecompileFailure::Error {
exit_status: ExitError::OutOfFund,
}
}
_ => PrecompileFailure::Error {
exit_status: ExitError::Other("unable to withdraw funds".into()),
},
})?;

let imbalance = CurrencySwapT::swap(imbalance).map_err(|_| PrecompileFailure::Revert {
exit_status: ExitRevert::Reverted,
output: "unable to swap the currency".into(),
})?;

CurrencySwapT::To::resolve_creating(&to, imbalance);

Ok(succeed(EvmDataWriter::new().write(true).build()))
}
}
Loading