difftreelog
test added for substrate `EthCrossAccount` variant
in: master
2 files changed
pallets/refungible/src/erc_token.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Refungible Pallet EVM API for token pieces18//!19//! Provides ERC-20 standart support implementation and EVM API for unique extensions for Refungible Pallet.20//! Method implementations are mostly doing parameter conversion and calling Nonfungible Pallet methods.2122extern crate alloc;2324#[cfg(not(feature = "std"))]25use alloc::format;2627use core::{28 char::{REPLACEMENT_CHARACTER, decode_utf16},29 convert::TryInto,30 ops::Deref,31};32use evm_coder::{33 abi::AbiType, ToLog, execution::*, generate_stubgen, solidity_interface, types::*, weight,34};35use pallet_common::{36 CommonWeightInfo,37 erc::{CommonEvmHandler, PrecompileResult},38 eth::{collection_id_to_address, EthCrossAccount},39};40use pallet_evm::{account::CrossAccountId, PrecompileHandle};41use pallet_evm_coder_substrate::{call, dispatch_to_evm, WithRecorder};42use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};43use sp_std::vec::Vec;44use up_data_structs::TokenId;4546use crate::{47 Allowance, Balance, common::CommonWeights, Config, Pallet, RefungibleHandle, SelfWeightOf,48 TotalSupply, weights::WeightInfo,49};5051pub struct RefungibleTokenHandle<T: Config>(pub RefungibleHandle<T>, pub TokenId);5253#[solidity_interface(name = ERC1633)]54impl<T: Config> RefungibleTokenHandle<T> {55 fn parent_token(&self) -> Result<address> {56 Ok(collection_id_to_address(self.id))57 }5859 fn parent_token_id(&self) -> Result<uint256> {60 Ok(self.1.into())61 }62}6364#[derive(ToLog)]65pub enum ERC20Events {66 /// @dev This event is emitted when the amount of tokens (value) is sent67 /// from the from address to the to address. In the case of minting new68 /// tokens, the transfer is usually from the 0 address while in the case69 /// of burning tokens the transfer is to 0.70 Transfer {71 #[indexed]72 from: address,73 #[indexed]74 to: address,75 value: uint256,76 },77 /// @dev This event is emitted when the amount of tokens (value) is approved78 /// by the owner to be used by the spender.79 Approval {80 #[indexed]81 owner: address,82 #[indexed]83 spender: address,84 value: uint256,85 },86}8788/// @title Standard ERC20 token89///90/// @dev Implementation of the basic standard token.91/// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md92#[solidity_interface(name = ERC20, events(ERC20Events))]93impl<T: Config> RefungibleTokenHandle<T> {94 /// @return the name of the token.95 fn name(&self) -> Result<string> {96 Ok(decode_utf16(self.name.iter().copied())97 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))98 .collect::<string>())99 }100101 /// @return the symbol of the token.102 fn symbol(&self) -> Result<string> {103 Ok(string::from_utf8_lossy(&self.token_prefix).into())104 }105106 /// @dev Total number of tokens in existence107 fn total_supply(&self) -> Result<uint256> {108 self.consume_store_reads(1)?;109 Ok(<TotalSupply<T>>::get((self.id, self.1)).into())110 }111112 /// @dev Not supported113 fn decimals(&self) -> Result<uint8> {114 // Decimals aren't supported for refungible tokens115 Ok(0)116 }117118 /// @dev Gets the balance of the specified address.119 /// @param owner The address to query the balance of.120 /// @return An uint256 representing the amount owned by the passed address.121 fn balance_of(&self, owner: address) -> Result<uint256> {122 self.consume_store_reads(1)?;123 let owner = T::CrossAccountId::from_eth(owner);124 let balance = <Balance<T>>::get((self.id, self.1, owner));125 Ok(balance.into())126 }127128 /// @dev Transfer token for a specified address129 /// @param to The address to transfer to.130 /// @param amount The amount to be transferred.131 #[weight(<CommonWeights<T>>::transfer())]132 fn transfer(&mut self, caller: caller, to: address, amount: uint256) -> Result<bool> {133 let caller = T::CrossAccountId::from_eth(caller);134 let to = T::CrossAccountId::from_eth(to);135 let amount = amount.try_into().map_err(|_| "amount overflow")?;136 let budget = self137 .recorder138 .weight_calls_budget(<StructureWeight<T>>::find_parent());139140 <Pallet<T>>::transfer(self, &caller, &to, self.1, amount, &budget)141 .map_err(dispatch_to_evm::<T>)?;142 Ok(true)143 }144145 /// @dev Transfer tokens from one address to another146 /// @param from address The address which you want to send tokens from147 /// @param to address The address which you want to transfer to148 /// @param amount uint256 the amount of tokens to be transferred149 #[weight(<CommonWeights<T>>::transfer_from())]150 fn transfer_from(151 &mut self,152 caller: caller,153 from: address,154 to: address,155 amount: uint256,156 ) -> Result<bool> {157 let caller = T::CrossAccountId::from_eth(caller);158 let from = T::CrossAccountId::from_eth(from);159 let to = T::CrossAccountId::from_eth(to);160 let amount = amount.try_into().map_err(|_| "amount overflow")?;161 let budget = self162 .recorder163 .weight_calls_budget(<StructureWeight<T>>::find_parent());164165 <Pallet<T>>::transfer_from(self, &caller, &from, &to, self.1, amount, &budget)166 .map_err(dispatch_to_evm::<T>)?;167 Ok(true)168 }169170 /// @dev Approve the passed address to spend the specified amount of tokens on behalf of `msg.sender`.171 /// Beware that changing an allowance with this method brings the risk that someone may use both the old172 /// and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this173 /// race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards:174 /// https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729175 /// @param spender The address which will spend the funds.176 /// @param amount The amount of tokens to be spent.177 #[weight(<SelfWeightOf<T>>::approve())]178 fn approve(&mut self, caller: caller, spender: address, amount: uint256) -> Result<bool> {179 let caller = T::CrossAccountId::from_eth(caller);180 let spender = T::CrossAccountId::from_eth(spender);181 let amount = amount.try_into().map_err(|_| "amount overflow")?;182183 <Pallet<T>>::set_allowance(self, &caller, &spender, self.1, amount)184 .map_err(dispatch_to_evm::<T>)?;185 Ok(true)186 }187188 /// @dev Function to check the amount of tokens that an owner allowed to a spender.189 /// @param owner address The address which owns the funds.190 /// @param spender address The address which will spend the funds.191 /// @return A uint256 specifying the amount of tokens still available for the spender.192 fn allowance(&self, owner: address, spender: address) -> Result<uint256> {193 self.consume_store_reads(1)?;194 let owner = T::CrossAccountId::from_eth(owner);195 let spender = T::CrossAccountId::from_eth(spender);196197 Ok(<Allowance<T>>::get((self.id, self.1, owner, spender)).into())198 }199}200201#[solidity_interface(name = ERC20UniqueExtensions)]202impl<T: Config> RefungibleTokenHandle<T>203where204 T::AccountId: From<[u8; 32]>,205{206 /// @dev Function that burns an amount of the token of a given account,207 /// deducting from the sender's allowance for said account.208 /// @param from The account whose tokens will be burnt.209 /// @param amount The amount that will be burnt.210 #[weight(<SelfWeightOf<T>>::burn_from())]211 fn burn_from(&mut self, caller: caller, from: address, amount: uint256) -> Result<bool> {212 let caller = T::CrossAccountId::from_eth(caller);213 let from = T::CrossAccountId::from_eth(from);214 let amount = amount.try_into().map_err(|_| "amount overflow")?;215 let budget = self216 .recorder217 .weight_calls_budget(<StructureWeight<T>>::find_parent());218219 <Pallet<T>>::burn_from(self, &caller, &from, self.1, amount, &budget)220 .map_err(dispatch_to_evm::<T>)?;221 Ok(true)222 }223224 /// @dev Function that burns an amount of the token of a given account,225 /// deducting from the sender's allowance for said account.226 /// @param from The account whose tokens will be burnt.227 /// @param amount The amount that will be burnt.228 #[weight(<SelfWeightOf<T>>::burn_from())]229 fn burn_from_cross(230 &mut self,231 caller: caller,232 from: EthCrossAccount,233 amount: uint256,234 ) -> Result<bool> {235 let caller = T::CrossAccountId::from_eth(caller);236 let from = from.into_sub_cross_account::<T>()?;237 let amount = amount.try_into().map_err(|_| "amount overflow")?;238 let budget = self239 .recorder240 .weight_calls_budget(<StructureWeight<T>>::find_parent());241242 <Pallet<T>>::burn_from(self, &caller, &from, self.1, amount, &budget)243 .map_err(dispatch_to_evm::<T>)?;244 Ok(true)245 }246247 /// @dev Approve the passed address to spend the specified amount of tokens on behalf of `msg.sender`.248 /// Beware that changing an allowance with this method brings the risk that someone may use both the old249 /// and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this250 /// race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards:251 /// https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729252 /// @param spender The crossaccount which will spend the funds.253 /// @param amount The amount of tokens to be spent.254 #[weight(<SelfWeightOf<T>>::approve())]255 fn approve_cross(256 &mut self,257 caller: caller,258 spender: EthCrossAccount,259 amount: uint256,260 ) -> Result<bool> {261 let caller = T::CrossAccountId::from_eth(caller);262 let spender = spender.into_sub_cross_account::<T>()?;263 let amount = amount.try_into().map_err(|_| "amount overflow")?;264265 <Pallet<T>>::set_allowance(self, &caller, &spender, self.1, amount)266 .map_err(dispatch_to_evm::<T>)?;267 Ok(true)268 }269 /// @dev Function that changes total amount of the tokens.270 /// Throws if `msg.sender` doesn't owns all of the tokens.271 /// @param amount New total amount of the tokens.272 #[weight(<SelfWeightOf<T>>::repartition_item())]273 fn repartition(&mut self, caller: caller, amount: uint256) -> Result<bool> {274 let caller = T::CrossAccountId::from_eth(caller);275 let amount = amount.try_into().map_err(|_| "amount overflow")?;276277 <Pallet<T>>::repartition(self, &caller, self.1, amount).map_err(dispatch_to_evm::<T>)?;278 Ok(true)279 }280281 /// @dev Transfer token for a specified address282 /// @param to The crossaccount to transfer to.283 /// @param amount The amount to be transferred.284 #[weight(<CommonWeights<T>>::transfer())]285 fn transfer_cross(286 &mut self,287 caller: caller,288 to: EthCrossAccount,289 amount: uint256,290 ) -> Result<bool> {291 let caller = T::CrossAccountId::from_eth(caller);292 let to = to.into_sub_cross_account::<T>()?;293 let amount = amount.try_into().map_err(|_| "amount overflow")?;294 let budget = self295 .recorder296 .weight_calls_budget(<StructureWeight<T>>::find_parent());297298 <Pallet<T>>::transfer(self, &caller, &to, self.1, amount, &budget)299 .map_err(dispatch_to_evm::<T>)?;300 Ok(true)301 }302303 /// @dev Transfer tokens from one address to another304 /// @param from address The address which you want to send tokens from305 /// @param to address The address which you want to transfer to306 /// @param amount uint256 the amount of tokens to be transferred307 #[weight(<CommonWeights<T>>::transfer_from())]308 fn transfer_from_cross(309 &mut self,310 caller: caller,311 from: EthCrossAccount,312 to: EthCrossAccount,313 amount: uint256,314 ) -> Result<bool> {315 let caller = T::CrossAccountId::from_eth(caller);316 let from = from.into_sub_cross_account::<T>()?;317 let to = to.into_sub_cross_account::<T>()?;318 let amount = amount.try_into().map_err(|_| "amount overflow")?;319 let budget = self320 .recorder321 .weight_calls_budget(<StructureWeight<T>>::find_parent());322323 <Pallet<T>>::transfer_from(self, &caller, &from, &to, self.1, amount, &budget)324 .map_err(dispatch_to_evm::<T>)?;325 Ok(true)326 }327}328329impl<T: Config> RefungibleTokenHandle<T> {330 pub fn into_inner(self) -> RefungibleHandle<T> {331 self.0332 }333 pub fn common_mut(&mut self) -> &mut RefungibleHandle<T> {334 &mut self.0335 }336}337338impl<T: Config> WithRecorder<T> for RefungibleTokenHandle<T> {339 fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {340 self.0.recorder()341 }342 fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {343 self.0.into_recorder()344 }345}346347impl<T: Config> Deref for RefungibleTokenHandle<T> {348 type Target = RefungibleHandle<T>;349350 fn deref(&self) -> &Self::Target {351 &self.0352 }353}354355#[solidity_interface(356 name = UniqueRefungibleToken,357 is(ERC20, ERC20UniqueExtensions, ERC1633)358)]359impl<T: Config> RefungibleTokenHandle<T> where T::AccountId: From<[u8; 32]> {}360361generate_stubgen!(gen_impl, UniqueRefungibleTokenCall<()>, true);362generate_stubgen!(gen_iface, UniqueRefungibleTokenCall<()>, false);363364impl<T: Config> CommonEvmHandler for RefungibleTokenHandle<T>365where366 T::AccountId: From<[u8; 32]>,367{368 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueRefungibleToken.raw");369370 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {371 call::<T, UniqueRefungibleTokenCall<T>, _, _>(handle, self)372 }373}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Refungible Pallet EVM API for token pieces18//!19//! Provides ERC-20 standart support implementation and EVM API for unique extensions for Refungible Pallet.20//! Method implementations are mostly doing parameter conversion and calling Nonfungible Pallet methods.2122extern crate alloc;2324#[cfg(not(feature = "std"))]25use alloc::format;2627use core::{28 char::{REPLACEMENT_CHARACTER, decode_utf16},29 convert::TryInto,30 ops::Deref,31};32use evm_coder::{33 abi::AbiType, ToLog, execution::*, generate_stubgen, solidity_interface, types::*, weight,34};35use pallet_common::{36 CommonWeightInfo,37 erc::{CommonEvmHandler, PrecompileResult},38 eth::{collection_id_to_address, EthCrossAccount},39};40use pallet_evm::{account::CrossAccountId, PrecompileHandle};41use pallet_evm_coder_substrate::{call, dispatch_to_evm, WithRecorder};42use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};43use sp_std::vec::Vec;44use up_data_structs::TokenId;4546use crate::{47 Allowance, Balance, common::CommonWeights, Config, Pallet, RefungibleHandle, SelfWeightOf,48 TotalSupply, weights::WeightInfo,49};5051pub struct RefungibleTokenHandle<T: Config>(pub RefungibleHandle<T>, pub TokenId);5253#[solidity_interface(name = ERC1633)]54impl<T: Config> RefungibleTokenHandle<T> {55 fn parent_token(&self) -> Result<address> {56 Ok(collection_id_to_address(self.id))57 }5859 fn parent_token_id(&self) -> Result<uint256> {60 Ok(self.1.into())61 }62}6364#[derive(ToLog)]65pub enum ERC20Events {66 /// @dev This event is emitted when the amount of tokens (value) is sent67 /// from the from address to the to address. In the case of minting new68 /// tokens, the transfer is usually from the 0 address while in the case69 /// of burning tokens the transfer is to 0.70 Transfer {71 #[indexed]72 from: address,73 #[indexed]74 to: address,75 value: uint256,76 },77 /// @dev This event is emitted when the amount of tokens (value) is approved78 /// by the owner to be used by the spender.79 Approval {80 #[indexed]81 owner: address,82 #[indexed]83 spender: address,84 value: uint256,85 },86}8788/// @title Standard ERC20 token89///90/// @dev Implementation of the basic standard token.91/// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md92#[solidity_interface(name = ERC20, events(ERC20Events))]93impl<T: Config> RefungibleTokenHandle<T> {94 /// @return the name of the token.95 fn name(&self) -> Result<string> {96 Ok(decode_utf16(self.name.iter().copied())97 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))98 .collect::<string>())99 }100101 /// @return the symbol of the token.102 fn symbol(&self) -> Result<string> {103 Ok(string::from_utf8_lossy(&self.token_prefix).into())104 }105106 /// @dev Total number of tokens in existence107 fn total_supply(&self) -> Result<uint256> {108 self.consume_store_reads(1)?;109 Ok(<TotalSupply<T>>::get((self.id, self.1)).into())110 }111112 /// @dev Not supported113 fn decimals(&self) -> Result<uint8> {114 // Decimals aren't supported for refungible tokens115 Ok(0)116 }117118 /// @dev Gets the balance of the specified address.119 /// @param owner The address to query the balance of.120 /// @return An uint256 representing the amount owned by the passed address.121 fn balance_of(&self, owner: address) -> Result<uint256> {122 self.consume_store_reads(1)?;123 let owner = T::CrossAccountId::from_eth(owner);124 let balance = <Balance<T>>::get((self.id, self.1, owner));125 Ok(balance.into())126 }127128 /// @dev Transfer token for a specified address129 /// @param to The address to transfer to.130 /// @param amount The amount to be transferred.131 #[weight(<CommonWeights<T>>::transfer())]132 fn transfer(&mut self, caller: caller, to: address, amount: uint256) -> Result<bool> {133 let caller = T::CrossAccountId::from_eth(caller);134 let to = T::CrossAccountId::from_eth(to);135 let amount = amount.try_into().map_err(|_| "amount overflow")?;136 let budget = self137 .recorder138 .weight_calls_budget(<StructureWeight<T>>::find_parent());139140 <Pallet<T>>::transfer(self, &caller, &to, self.1, amount, &budget)141 .map_err(dispatch_to_evm::<T>)?;142 Ok(true)143 }144145 /// @dev Transfer tokens from one address to another146 /// @param from address The address which you want to send tokens from147 /// @param to address The address which you want to transfer to148 /// @param amount uint256 the amount of tokens to be transferred149 #[weight(<CommonWeights<T>>::transfer_from())]150 fn transfer_from(151 &mut self,152 caller: caller,153 from: address,154 to: address,155 amount: uint256,156 ) -> Result<bool> {157 let caller = T::CrossAccountId::from_eth(caller);158 let from = T::CrossAccountId::from_eth(from);159 let to = T::CrossAccountId::from_eth(to);160 let amount = amount.try_into().map_err(|_| "amount overflow")?;161 let budget = self162 .recorder163 .weight_calls_budget(<StructureWeight<T>>::find_parent());164165 <Pallet<T>>::transfer_from(self, &caller, &from, &to, self.1, amount, &budget)166 .map_err(dispatch_to_evm::<T>)?;167 Ok(true)168 }169170 /// @dev Approve the passed address to spend the specified amount of tokens on behalf of `msg.sender`.171 /// Beware that changing an allowance with this method brings the risk that someone may use both the old172 /// and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this173 /// race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards:174 /// https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729175 /// @param spender The address which will spend the funds.176 /// @param amount The amount of tokens to be spent.177 #[weight(<SelfWeightOf<T>>::approve())]178 fn approve(&mut self, caller: caller, spender: address, amount: uint256) -> Result<bool> {179 let caller = T::CrossAccountId::from_eth(caller);180 let spender = T::CrossAccountId::from_eth(spender);181 let amount = amount.try_into().map_err(|_| "amount overflow")?;182183 <Pallet<T>>::set_allowance(self, &caller, &spender, self.1, amount)184 .map_err(dispatch_to_evm::<T>)?;185 Ok(true)186 }187188 /// @dev Function to check the amount of tokens that an owner allowed to a spender.189 /// @param owner address The address which owns the funds.190 /// @param spender address The address which will spend the funds.191 /// @return A uint256 specifying the amount of tokens still available for the spender.192 fn allowance(&self, owner: address, spender: address) -> Result<uint256> {193 self.consume_store_reads(1)?;194 let owner = T::CrossAccountId::from_eth(owner);195 let spender = T::CrossAccountId::from_eth(spender);196197 Ok(<Allowance<T>>::get((self.id, self.1, owner, spender)).into())198 }199}200201#[solidity_interface(name = ERC20UniqueExtensions)]202impl<T: Config> RefungibleTokenHandle<T>203where204 T::AccountId: From<[u8; 32]>,205{206 /// @dev Function that burns an amount of the token of a given account,207 /// deducting from the sender's allowance for said account.208 /// @param from The account whose tokens will be burnt.209 /// @param amount The amount that will be burnt.210 #[weight(<SelfWeightOf<T>>::burn_from())]211 fn burn_from(&mut self, caller: caller, from: address, amount: uint256) -> Result<bool> {212 let caller = T::CrossAccountId::from_eth(caller);213 let from = T::CrossAccountId::from_eth(from);214 let amount = amount.try_into().map_err(|_| "amount overflow")?;215 let budget = self216 .recorder217 .weight_calls_budget(<StructureWeight<T>>::find_parent());218219 <Pallet<T>>::burn_from(self, &caller, &from, self.1, amount, &budget)220 .map_err(dispatch_to_evm::<T>)?;221 Ok(true)222 }223224 /// @dev Function that burns an amount of the token of a given account,225 /// deducting from the sender's allowance for said account.226 /// @param from The account whose tokens will be burnt.227 /// @param amount The amount that will be burnt.228 #[weight(<SelfWeightOf<T>>::burn_from())]229 fn burn_from_cross(230 &mut self,231 caller: caller,232 from: EthCrossAccount,233 amount: uint256,234 ) -> Result<bool> {235 let caller = T::CrossAccountId::from_eth(caller);236 let from = from.into_sub_cross_account::<T>()?;237 let amount = amount.try_into().map_err(|_| "amount overflow")?;238 let budget = self239 .recorder240 .weight_calls_budget(<StructureWeight<T>>::find_parent());241242 <Pallet<T>>::burn_from(self, &caller, &from, self.1, amount, &budget)243 .map_err(dispatch_to_evm::<T>)?;244 Ok(true)245 }246247 /// @dev Approve the passed address to spend the specified amount of tokens on behalf of `msg.sender`.248 /// Beware that changing an allowance with this method brings the risk that someone may use both the old249 /// and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this250 /// race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards:251 /// https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729252 /// @param spender The crossaccount which will spend the funds.253 /// @param amount The amount of tokens to be spent.254 #[weight(<SelfWeightOf<T>>::approve())]255 fn approve_cross(256 &mut self,257 caller: caller,258 spender: EthCrossAccount,259 amount: uint256,260 ) -> Result<bool> {261 let caller = T::CrossAccountId::from_eth(caller);262 let spender = spender.into_sub_cross_account::<T>()?;263 let amount = amount.try_into().map_err(|_| "amount overflow")?;264265 <Pallet<T>>::set_allowance(self, &caller, &spender, self.1, amount)266 .map_err(dispatch_to_evm::<T>)?;267 Ok(true)268 }269 /// @dev Function that changes total amount of the tokens.270 /// Throws if `msg.sender` doesn't owns all of the tokens.271 /// @param amount New total amount of the tokens.272 #[weight(<SelfWeightOf<T>>::repartition_item())]273 fn repartition(&mut self, caller: caller, amount: uint256) -> Result<bool> {274 let caller = T::CrossAccountId::from_eth(caller);275 let amount = amount.try_into().map_err(|_| "amount overflow")?;276277 <Pallet<T>>::repartition(self, &caller, self.1, amount).map_err(dispatch_to_evm::<T>)?;278 Ok(true)279 }280281 /// @dev Transfer token for a specified address282 /// @param to The crossaccount to transfer to.283 /// @param amount The amount to be transferred.284 #[weight(<CommonWeights<T>>::transfer())]285 fn transfer_cross(286 &mut self,287 caller: caller,288 to: EthCrossAccount,289 amount: uint256,290 ) -> Result<bool> {291 let caller = T::CrossAccountId::from_eth(caller);292 let to = to.into_sub_cross_account::<T>()?;293 let amount = amount.try_into().map_err(|_| "amount overflow")?;294 let budget = self295 .recorder296 .weight_calls_budget(<StructureWeight<T>>::find_parent());297298 <Pallet<T>>::transfer(self, &caller, &to, self.1, amount, &budget)299 .map_err(dispatch_to_evm::<T>)?;300 Ok(true)301 }302303 /// @dev Transfer tokens from one address to another304 /// @param from The address which you want to send tokens from305 /// @param to The address which you want to transfer to306 /// @param amount the amount of tokens to be transferred307 #[weight(<CommonWeights<T>>::transfer_from())]308 fn transfer_from_cross(309 &mut self,310 caller: caller,311 from: EthCrossAccount,312 to: EthCrossAccount,313 amount: uint256,314 ) -> Result<bool> {315 let caller = T::CrossAccountId::from_eth(caller);316 let from = from.into_sub_cross_account::<T>()?;317 let to = to.into_sub_cross_account::<T>()?;318 let amount = amount.try_into().map_err(|_| "amount overflow")?;319 let budget = self320 .recorder321 .weight_calls_budget(<StructureWeight<T>>::find_parent());322323 <Pallet<T>>::transfer_from(self, &caller, &from, &to, self.1, amount, &budget)324 .map_err(dispatch_to_evm::<T>)?;325 Ok(true)326 }327}328329impl<T: Config> RefungibleTokenHandle<T> {330 pub fn into_inner(self) -> RefungibleHandle<T> {331 self.0332 }333 pub fn common_mut(&mut self) -> &mut RefungibleHandle<T> {334 &mut self.0335 }336}337338impl<T: Config> WithRecorder<T> for RefungibleTokenHandle<T> {339 fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {340 self.0.recorder()341 }342 fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {343 self.0.into_recorder()344 }345}346347impl<T: Config> Deref for RefungibleTokenHandle<T> {348 type Target = RefungibleHandle<T>;349350 fn deref(&self) -> &Self::Target {351 &self.0352 }353}354355#[solidity_interface(356 name = UniqueRefungibleToken,357 is(ERC20, ERC20UniqueExtensions, ERC1633)358)]359impl<T: Config> RefungibleTokenHandle<T> where T::AccountId: From<[u8; 32]> {}360361generate_stubgen!(gen_impl, UniqueRefungibleTokenCall<()>, true);362generate_stubgen!(gen_iface, UniqueRefungibleTokenCall<()>, false);363364impl<T: Config> CommonEvmHandler for RefungibleTokenHandle<T>365where366 T::AccountId: From<[u8; 32]>,367{368 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueRefungibleToken.raw");369370 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {371 call::<T, UniqueRefungibleTokenCall<T>, _, _>(handle, self)372 }373}tests/src/eth/reFungibleToken.test.tsdiffbeforeafterboth--- a/tests/src/eth/reFungibleToken.test.ts
+++ b/tests/src/eth/reFungibleToken.test.ts
@@ -163,7 +163,10 @@
itEth('Can perform approveCross()', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor);
const spender = helper.eth.createAccount();
- const spenderCross = helper.ethCrossAccount.fromAddress(spender);
+ const spenderSub = (await helper.arrange.createAccounts([1n], donor))[0];
+ const spenderCrossEth = helper.ethCrossAccount.fromAddress(spender);
+ const spenderCrossSub = helper.ethCrossAccount.fromKeyringPair(spenderSub);
+
const collection = await helper.rft.mintCollection(alice);
const {tokenId} = await collection.mintToken(alice, 200n, {Ethereum: owner});
@@ -172,7 +175,7 @@
const contract = helper.ethNativeContract.rftToken(tokenAddress, owner);
{
- const result = await contract.methods.approveCross(spenderCross, 100).send({from: owner});
+ const result = await contract.methods.approveCross(spenderCrossEth, 100).send({from: owner});
const event = result.events.Approval;
expect(event.address).to.be.equal(tokenAddress);
expect(event.returnValues.owner).to.be.equal(owner);
@@ -184,6 +187,25 @@
const allowance = await contract.methods.allowance(owner, spender).call();
expect(+allowance).to.equal(100);
}
+
+
+ {
+ const result = await contract.methods.approveCross(spenderCrossSub, 100).send({from: owner});
+ const event = result.events.Approval;
+ expect(event.address).to.be.equal(tokenAddress);
+ expect(event.returnValues.owner).to.be.equal(owner);
+ expect(event.returnValues.spender).to.be.equal(helper.address.substrateToEth(spenderSub.address));
+ expect(event.returnValues.value).to.be.equal('100');
+ }
+
+ {
+ const allowance = await collection.getTokenApprovedPieces(tokenId, {Ethereum: owner}, {Substrate: spenderSub.address});
+ expect(allowance).to.equal(100n);
+ }
+
+ {
+ //TO-DO expect with future allowanceCross(owner, spenderCrossEth).call()
+ }
});
itEth('Can perform transferFrom()', async ({helper}) => {
@@ -229,17 +251,20 @@
const ownerCross = helper.ethCrossAccount.fromAddress(owner);
const spender = await helper.eth.createAccountWithBalance(donor);
const receiver = helper.eth.createAccount();
- const receiverCross = helper.ethCrossAccount.fromAddress(receiver);
+ const receiverSub = (await helper.arrange.createAccounts([1n], donor))[0];
+ const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiver);
+ const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(receiverSub);
+
const collection = await helper.rft.mintCollection(alice);
const {tokenId} = await collection.mintToken(alice, 200n, {Ethereum: owner});
const tokenAddress = helper.ethAddress.fromTokenId(collection.collectionId, tokenId);
const contract = helper.ethNativeContract.rftToken(tokenAddress, owner);
-
- await contract.methods.approve(spender, 100).send();
+ await contract.methods.approve(spender, 100).send({from: owner});
+
{
- const result = await contract.methods.transferFromCross(ownerCross, receiverCross, 49).send({from: spender});
+ const result = await contract.methods.transferFromCross(ownerCross, receiverCrossEth, 49).send({from: spender});
let event = result.events.Transfer;
expect(event.address).to.be.equal(tokenAddress);
expect(event.returnValues.from).to.be.equal(owner);
@@ -262,6 +287,31 @@
const balance = await contract.methods.balanceOf(owner).call();
expect(+balance).to.equal(151);
}
+
+ {
+ const result = await contract.methods.transferFromCross(ownerCross, receiverCrossSub, 51).send({from: spender});
+ let event = result.events.Transfer;
+ expect(event.address).to.be.equal(tokenAddress);
+ expect(event.returnValues.from).to.be.equal(owner);
+ expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(receiverSub.address));
+ expect(event.returnValues.value).to.be.equal('51');
+
+ event = result.events.Approval;
+ expect(event.address).to.be.equal(tokenAddress);
+ expect(event.returnValues.owner).to.be.equal(owner);
+ expect(event.returnValues.spender).to.be.equal(spender);
+ expect(event.returnValues.value).to.be.equal('0');
+ }
+
+ {
+ const balance = await collection.getTokenBalance(tokenId, {Substrate: receiverSub.address});
+ expect(balance).to.equal(51n);
+ }
+
+ {
+ const balance = await contract.methods.balanceOf(owner).call();
+ expect(+balance).to.equal(100);
+ }
});
itEth('Can perform transfer()', async ({helper}) => {
@@ -336,7 +386,9 @@
itEth('Can perform transferCross()', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor);
const receiver = helper.eth.createAccount();
- const receiverCross = helper.ethCrossAccount.fromAddress(receiver);
+ const receiverSub = (await helper.arrange.createAccounts([1n], donor))[0];
+ const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiver);
+ const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(receiverSub);
const collection = await helper.rft.mintCollection(alice);
const {tokenId} = await collection.mintToken(alice, 200n, {Ethereum: owner});
@@ -344,7 +396,7 @@
const contract = helper.ethNativeContract.rftToken(tokenAddress, owner);
{
- const result = await contract.methods.transferCross(receiverCross, 50).send({from: owner});
+ const result = await contract.methods.transferCross(receiverCrossEth, 50).send({from: owner});
const event = result.events.Transfer;
expect(event.address).to.be.equal(tokenAddress);
expect(event.returnValues.from).to.be.equal(owner);
@@ -361,6 +413,25 @@
const balance = await contract.methods.balanceOf(receiver).call();
expect(+balance).to.equal(50);
}
+
+ {
+ const result = await contract.methods.transferCross(receiverCrossSub, 50).send({from: owner});
+ const event = result.events.Transfer;
+ expect(event.address).to.be.equal(tokenAddress);
+ expect(event.returnValues.from).to.be.equal(owner);
+ expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(receiverSub.address));
+ expect(event.returnValues.value).to.be.equal('50');
+ }
+
+ {
+ const balance = await contract.methods.balanceOf(owner).call();
+ expect(+balance).to.equal(100);
+ }
+
+ {
+ const balance = await collection.getTokenBalance(tokenId, {Substrate: receiverSub.address});
+ expect(balance).to.equal(50n);
+ }
});
itEth('Can perform repartition()', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor);
@@ -451,21 +522,37 @@
itEth('Can perform burnFromCross()', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor);
+ const ownerSub = (await helper.arrange.createAccounts([10n], donor))[0];
const ownerCross = helper.ethCrossAccount.fromAddress(owner);
const spender = await helper.eth.createAccountWithBalance(donor);
- const spenderCross = helper.ethCrossAccount.fromAddress(spender);
+
+ const spenderCrossEth = helper.ethCrossAccount.fromAddress(spender);
+ const ownerSubCross = helper.ethCrossAccount.fromKeyringPair(ownerSub);
const collection = await helper.rft.mintCollection(alice);
const {tokenId} = await collection.mintToken(alice, 200n, {Ethereum: owner});
+
const tokenAddress = helper.ethAddress.fromTokenId(collection.collectionId, tokenId);
const contract = helper.ethNativeContract.rftToken(tokenAddress, owner);
- await contract.methods.approveCross(spenderCross, 100).send({from: owner});
+ {
+ await contract.methods.approveCross(spenderCrossEth, 100).send({from: owner});
+
+ await expect(contract.methods.burnFromCross(ownerCross, 50).send({from: spender})).to.be.fulfilled;
+ await expect(contract.methods.burnFromCross(ownerCross, 100).send({from: spender})).to.be.rejected;
+ expect(await contract.methods.balanceOf(owner).call({from: owner})).to.be.equal('150');
+ }
+ {
+ const {tokenId} = await collection.mintToken(alice, 200n, {Substrate: ownerSub.address});
+ await collection.approveToken(ownerSub, tokenId, {Ethereum: spender}, 100n);
+ const tokenAddress = helper.ethAddress.fromTokenId(collection.collectionId, tokenId);
+ const contract = helper.ethNativeContract.rftToken(tokenAddress, owner);
- await expect(contract.methods.burnFromCross(ownerCross, 50).send({from: spender})).to.be.fulfilled;
- await expect(contract.methods.burnFromCross(ownerCross, 100).send({from: spender})).to.be.rejected;
- expect(await contract.methods.balanceOf(owner).call({from: owner})).to.be.equal('150');
+ await expect(contract.methods.burnFromCross(ownerSubCross, 50).send({from: spender})).to.be.fulfilled;
+ await expect(contract.methods.burnFromCross(ownerSubCross, 100).send({from: spender})).to.be.rejected;
+ expect(await collection.getTokenBalance(tokenId, {Substrate: ownerSub.address})).to.be.equal(150n);
+ }
});
});