326 lines
11 KiB
Rust
326 lines
11 KiB
Rust
// This file is part of Bizinikiwi.
|
|
|
|
// Copyright (C) Parity Technologies (UK) Ltd.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// Ensure we're `no_std` when compiling for Wasm.
|
|
#![cfg_attr(not(feature = "std"), no_std)]
|
|
|
|
extern crate alloc;
|
|
|
|
use alloc::vec::Vec;
|
|
use core::marker::PhantomData;
|
|
use pez_ethereum_standards::{
|
|
IERC20,
|
|
IERC20::{IERC20Calls, IERC20Events},
|
|
};
|
|
use pezpallet_assets::{weights::WeightInfo, Call, Config, TransferFlags};
|
|
use pezpallet_revive::precompiles::{
|
|
alloy::{
|
|
self,
|
|
primitives::IntoLogData,
|
|
sol_types::{Revert, SolCall},
|
|
},
|
|
AddressMapper, AddressMatcher, Error, Ext, Precompile, RuntimeCosts, H160, H256,
|
|
};
|
|
|
|
#[cfg(test)]
|
|
mod mock;
|
|
#[cfg(test)]
|
|
mod tests;
|
|
|
|
/// Mean of extracting the asset id from the precompile address.
|
|
pub trait AssetIdExtractor {
|
|
type AssetId;
|
|
/// Extracts the asset id from the address.
|
|
fn asset_id_from_address(address: &[u8; 20]) -> Result<Self::AssetId, Error>;
|
|
}
|
|
|
|
/// The configuration of a pezpallet-assets precompile.
|
|
pub trait AssetPrecompileConfig {
|
|
/// The Address matcher used by the precompile.
|
|
const MATCHER: AddressMatcher;
|
|
|
|
/// The [`AssetIdExtractor`] used by the precompile.
|
|
type AssetIdExtractor: AssetIdExtractor;
|
|
}
|
|
|
|
/// An `AssetIdExtractor` that stores the asset id directly inside the address.
|
|
pub struct InlineAssetIdExtractor;
|
|
|
|
impl AssetIdExtractor for InlineAssetIdExtractor {
|
|
type AssetId = u32;
|
|
fn asset_id_from_address(addr: &[u8; 20]) -> Result<Self::AssetId, Error> {
|
|
let bytes: [u8; 4] = addr[0..4].try_into().expect("slice is 4 bytes; qed");
|
|
let index = u32::from_be_bytes(bytes);
|
|
return Ok(index.into());
|
|
}
|
|
}
|
|
|
|
/// A precompile configuration that uses a prefix [`AddressMatcher`].
|
|
pub struct InlineIdConfig<const PREFIX: u16>;
|
|
|
|
impl<const P: u16> AssetPrecompileConfig for InlineIdConfig<P> {
|
|
const MATCHER: AddressMatcher = AddressMatcher::Prefix(core::num::NonZero::new(P).unwrap());
|
|
type AssetIdExtractor = InlineAssetIdExtractor;
|
|
}
|
|
|
|
/// An ERC20 precompile.
|
|
pub struct ERC20<Runtime, PrecompileConfig, Instance = ()> {
|
|
_phantom: PhantomData<(Runtime, PrecompileConfig, Instance)>,
|
|
}
|
|
|
|
impl<Runtime, PrecompileConfig, Instance: 'static> Precompile
|
|
for ERC20<Runtime, PrecompileConfig, Instance>
|
|
where
|
|
PrecompileConfig: AssetPrecompileConfig,
|
|
Runtime: crate::Config<Instance> + pezpallet_revive::Config,
|
|
<<PrecompileConfig as AssetPrecompileConfig>::AssetIdExtractor as AssetIdExtractor>::AssetId:
|
|
Into<<Runtime as Config<Instance>>::AssetId>,
|
|
Call<Runtime, Instance>: Into<<Runtime as pezpallet_revive::Config>::RuntimeCall>,
|
|
alloy::primitives::U256: TryInto<<Runtime as Config<Instance>>::Balance>,
|
|
|
|
// Note can't use From as it's not implemented for alloy::primitives::U256 for unsigned types
|
|
alloy::primitives::U256: TryFrom<<Runtime as Config<Instance>>::Balance>,
|
|
{
|
|
type T = Runtime;
|
|
type Interface = IERC20::IERC20Calls;
|
|
const MATCHER: AddressMatcher = PrecompileConfig::MATCHER;
|
|
const HAS_CONTRACT_INFO: bool = false;
|
|
|
|
fn call(
|
|
address: &[u8; 20],
|
|
input: &Self::Interface,
|
|
env: &mut impl Ext<T = Self::T>,
|
|
) -> Result<Vec<u8>, Error> {
|
|
let asset_id = PrecompileConfig::AssetIdExtractor::asset_id_from_address(address)?.into();
|
|
|
|
match input {
|
|
IERC20Calls::transfer(_) | IERC20Calls::approve(_) | IERC20Calls::transferFrom(_)
|
|
if env.is_read_only() =>
|
|
Err(Error::Error(pezpallet_revive::Error::<Self::T>::StateChangeDenied.into())),
|
|
|
|
IERC20Calls::transfer(call) => Self::transfer(asset_id, call, env),
|
|
IERC20Calls::totalSupply(_) => Self::total_supply(asset_id, env),
|
|
IERC20Calls::balanceOf(call) => Self::balance_of(asset_id, call, env),
|
|
IERC20Calls::allowance(call) => Self::allowance(asset_id, call, env),
|
|
IERC20Calls::approve(call) => Self::approve(asset_id, call, env),
|
|
IERC20Calls::transferFrom(call) => Self::transfer_from(asset_id, call, env),
|
|
}
|
|
}
|
|
}
|
|
|
|
const ERR_INVALID_CALLER: &str = "Invalid caller";
|
|
const ERR_BALANCE_CONVERSION_FAILED: &str = "Balance conversion failed";
|
|
|
|
impl<Runtime, PrecompileConfig, Instance: 'static> ERC20<Runtime, PrecompileConfig, Instance>
|
|
where
|
|
PrecompileConfig: AssetPrecompileConfig,
|
|
Runtime: crate::Config<Instance> + pezpallet_revive::Config,
|
|
<<PrecompileConfig as AssetPrecompileConfig>::AssetIdExtractor as AssetIdExtractor>::AssetId:
|
|
Into<<Runtime as Config<Instance>>::AssetId>,
|
|
Call<Runtime, Instance>: Into<<Runtime as pezpallet_revive::Config>::RuntimeCall>,
|
|
alloy::primitives::U256: TryInto<<Runtime as Config<Instance>>::Balance>,
|
|
|
|
// Note can't use From as it's not implemented for alloy::primitives::U256 for unsigned types
|
|
alloy::primitives::U256: TryFrom<<Runtime as Config<Instance>>::Balance>,
|
|
{
|
|
/// Get the caller as an `H160` address.
|
|
fn caller(env: &mut impl Ext<T = Runtime>) -> Result<H160, Error> {
|
|
env.caller()
|
|
.account_id()
|
|
.map(<Runtime as pezpallet_revive::Config>::AddressMapper::to_address)
|
|
.map_err(|_| Error::Revert(Revert { reason: ERR_INVALID_CALLER.into() }))
|
|
}
|
|
|
|
/// Convert a `U256` value to the balance type of the pallet.
|
|
fn to_balance(
|
|
value: alloy::primitives::U256,
|
|
) -> Result<<Runtime as Config<Instance>>::Balance, Error> {
|
|
value
|
|
.try_into()
|
|
.map_err(|_| Error::Revert(Revert { reason: ERR_BALANCE_CONVERSION_FAILED.into() }))
|
|
}
|
|
|
|
/// Convert a balance to a `U256` value.
|
|
/// Note this is needed cause From is not implemented for unsigned integer types
|
|
fn to_u256(
|
|
value: <Runtime as Config<Instance>>::Balance,
|
|
) -> Result<alloy::primitives::U256, Error> {
|
|
alloy::primitives::U256::try_from(value)
|
|
.map_err(|_| Error::Revert(Revert { reason: ERR_BALANCE_CONVERSION_FAILED.into() }))
|
|
}
|
|
|
|
/// Deposit an event to the runtime.
|
|
fn deposit_event(env: &mut impl Ext<T = Runtime>, event: IERC20Events) -> Result<(), Error> {
|
|
let (topics, data) = event.into_log_data().split();
|
|
let topics = topics.into_iter().map(|v| H256(v.0)).collect::<Vec<_>>();
|
|
env.gas_meter_mut().charge(RuntimeCosts::DepositEvent {
|
|
num_topic: topics.len() as u32,
|
|
len: topics.len() as u32,
|
|
})?;
|
|
env.deposit_event(topics, data.to_vec());
|
|
Ok(())
|
|
}
|
|
|
|
/// Execute the transfer call.
|
|
fn transfer(
|
|
asset_id: <Runtime as Config<Instance>>::AssetId,
|
|
call: &IERC20::transferCall,
|
|
env: &mut impl Ext<T = Runtime>,
|
|
) -> Result<Vec<u8>, Error> {
|
|
env.charge(<Runtime as Config<Instance>>::WeightInfo::transfer())?;
|
|
|
|
let from = Self::caller(env)?;
|
|
let dest = <Runtime as pezpallet_revive::Config>::AddressMapper::to_account_id(
|
|
&call.to.into_array().into(),
|
|
);
|
|
|
|
let f = TransferFlags { keep_alive: false, best_effort: false, burn_dust: false };
|
|
pezpallet_assets::Pallet::<Runtime, Instance>::do_transfer(
|
|
asset_id,
|
|
&<Runtime as pezpallet_revive::Config>::AddressMapper::to_account_id(&from),
|
|
&dest,
|
|
Self::to_balance(call.value)?,
|
|
None,
|
|
f,
|
|
)?;
|
|
|
|
Self::deposit_event(
|
|
env,
|
|
IERC20Events::Transfer(IERC20::Transfer {
|
|
from: from.0.into(),
|
|
to: call.to,
|
|
value: call.value,
|
|
}),
|
|
)?;
|
|
|
|
return Ok(IERC20::transferCall::abi_encode_returns(&true));
|
|
}
|
|
|
|
/// Execute the total supply call.
|
|
fn total_supply(
|
|
asset_id: <Runtime as Config<Instance>>::AssetId,
|
|
env: &mut impl Ext<T = Runtime>,
|
|
) -> Result<Vec<u8>, Error> {
|
|
use pezframe_support::traits::fungibles::Inspect;
|
|
env.charge(<Runtime as Config<Instance>>::WeightInfo::total_issuance())?;
|
|
|
|
let value =
|
|
Self::to_u256(pezpallet_assets::Pallet::<Runtime, Instance>::total_issuance(asset_id))?;
|
|
return Ok(IERC20::totalSupplyCall::abi_encode_returns(&value));
|
|
}
|
|
|
|
/// Execute the balance_of call.
|
|
fn balance_of(
|
|
asset_id: <Runtime as Config<Instance>>::AssetId,
|
|
call: &IERC20::balanceOfCall,
|
|
env: &mut impl Ext<T = Runtime>,
|
|
) -> Result<Vec<u8>, Error> {
|
|
env.charge(<Runtime as Config<Instance>>::WeightInfo::balance())?;
|
|
let account = call.account.into_array().into();
|
|
let account = <Runtime as pezpallet_revive::Config>::AddressMapper::to_account_id(&account);
|
|
let value =
|
|
Self::to_u256(pezpallet_assets::Pallet::<Runtime, Instance>::balance(asset_id, account))?;
|
|
return Ok(IERC20::balanceOfCall::abi_encode_returns(&value));
|
|
}
|
|
|
|
/// Execute the allowance call.
|
|
fn allowance(
|
|
asset_id: <Runtime as Config<Instance>>::AssetId,
|
|
call: &IERC20::allowanceCall,
|
|
env: &mut impl Ext<T = Runtime>,
|
|
) -> Result<Vec<u8>, Error> {
|
|
env.charge(<Runtime as Config<Instance>>::WeightInfo::allowance())?;
|
|
use pezframe_support::traits::fungibles::approvals::Inspect;
|
|
let owner = call.owner.into_array().into();
|
|
let owner = <Runtime as pezpallet_revive::Config>::AddressMapper::to_account_id(&owner);
|
|
|
|
let spender = call.spender.into_array().into();
|
|
let spender = <Runtime as pezpallet_revive::Config>::AddressMapper::to_account_id(&spender);
|
|
let value = Self::to_u256(pezpallet_assets::Pallet::<Runtime, Instance>::allowance(
|
|
asset_id, &owner, &spender,
|
|
))?;
|
|
|
|
return Ok(IERC20::balanceOfCall::abi_encode_returns(&value));
|
|
}
|
|
|
|
/// Execute the approve call.
|
|
fn approve(
|
|
asset_id: <Runtime as Config<Instance>>::AssetId,
|
|
call: &IERC20::approveCall,
|
|
env: &mut impl Ext<T = Runtime>,
|
|
) -> Result<Vec<u8>, Error> {
|
|
env.charge(<Runtime as Config<Instance>>::WeightInfo::approve_transfer())?;
|
|
let owner = Self::caller(env)?;
|
|
let spender = call.spender.into_array().into();
|
|
let spender = <Runtime as pezpallet_revive::Config>::AddressMapper::to_account_id(&spender);
|
|
|
|
pezpallet_assets::Pallet::<Runtime, Instance>::do_approve_transfer(
|
|
asset_id,
|
|
&<Runtime as pezpallet_revive::Config>::AddressMapper::to_account_id(&owner),
|
|
&spender,
|
|
Self::to_balance(call.value)?,
|
|
)?;
|
|
|
|
Self::deposit_event(
|
|
env,
|
|
IERC20Events::Approval(IERC20::Approval {
|
|
owner: owner.0.into(),
|
|
spender: call.spender,
|
|
value: call.value,
|
|
}),
|
|
)?;
|
|
|
|
return Ok(IERC20::approveCall::abi_encode_returns(&true));
|
|
}
|
|
|
|
/// Execute the transfer_from call.
|
|
fn transfer_from(
|
|
asset_id: <Runtime as Config<Instance>>::AssetId,
|
|
call: &IERC20::transferFromCall,
|
|
env: &mut impl Ext<T = Runtime>,
|
|
) -> Result<Vec<u8>, Error> {
|
|
env.charge(<Runtime as Config<Instance>>::WeightInfo::transfer_approved())?;
|
|
let spender = Self::caller(env)?;
|
|
let spender = <Runtime as pezpallet_revive::Config>::AddressMapper::to_account_id(&spender);
|
|
|
|
let from = call.from.into_array().into();
|
|
let from = <Runtime as pezpallet_revive::Config>::AddressMapper::to_account_id(&from);
|
|
|
|
let to = call.to.into_array().into();
|
|
let to = <Runtime as pezpallet_revive::Config>::AddressMapper::to_account_id(&to);
|
|
|
|
pezpallet_assets::Pallet::<Runtime, Instance>::do_transfer_approved(
|
|
asset_id,
|
|
&from,
|
|
&spender,
|
|
&to,
|
|
Self::to_balance(call.value)?,
|
|
)?;
|
|
|
|
Self::deposit_event(
|
|
env,
|
|
IERC20Events::Transfer(IERC20::Transfer {
|
|
from: call.from,
|
|
to: call.to,
|
|
value: call.value,
|
|
}),
|
|
)?;
|
|
|
|
return Ok(IERC20::transferFromCall::abi_encode_returns(&true));
|
|
}
|
|
}
|