--- a/Makefile +++ b/Makefile @@ -36,8 +36,8 @@ PACKAGE=pallet-nonfungible NAME=erc::gen_impl OUTPUT=$(NONFUNGIBLE_EVM_STUBS)/$@ ./.maintain/scripts/generate_sol.sh UniqueRefungibleToken.sol: - PACKAGE=pallet-refungible NAME=erc20::gen_iface OUTPUT=$(TESTS_API)/$@ ./.maintain/scripts/generate_sol.sh - PACKAGE=pallet-refungible NAME=erc20::gen_impl OUTPUT=$(REFUNGIBLE_EVM_STUBS)/$@ ./.maintain/scripts/generate_sol.sh + PACKAGE=pallet-refungible NAME=erc_token::gen_iface OUTPUT=$(TESTS_API)/$@ ./.maintain/scripts/generate_sol.sh + PACKAGE=pallet-refungible NAME=erc_token::gen_impl OUTPUT=$(REFUNGIBLE_EVM_STUBS)/$@ ./.maintain/scripts/generate_sol.sh ContractHelpers.sol: PACKAGE=pallet-evm-contract-helpers NAME=eth::contract_helpers_iface OUTPUT=$(TESTS_API)/$@ ./.maintain/scripts/generate_sol.sh --- /dev/null +++ b/pallets/refungible/src/erc.rs @@ -0,0 +1,32 @@ +extern crate alloc; +use evm_coder::{generate_stubgen, solidity_interface, types::*}; + +use pallet_common::{CollectionHandle, erc::CollectionCall, erc::CommonEvmHandler}; + +use pallet_evm::PrecompileHandle; +use pallet_evm_coder_substrate::call; + +use crate::{Config, RefungibleHandle}; + +#[solidity_interface( + name = "UniqueRFT", + is(via("CollectionHandle", common_mut, Collection),) +)] +impl RefungibleHandle where T::AccountId: From<[u8; 32]> {} + +// Not a tests, but code generators +generate_stubgen!(gen_impl, UniqueRFTCall<()>, true); +generate_stubgen!(gen_iface, UniqueRFTCall<()>, false); + +impl CommonEvmHandler for RefungibleHandle +where + T::AccountId: From<[u8; 32]>, +{ + const CODE: &'static [u8] = include_bytes!("./stubs/UniqueRefungible.raw"); + fn call( + self, + handle: &mut impl PrecompileHandle, + ) -> Option { + call::, _, _>(handle, self) + } +} --- a/pallets/refungible/src/erc20.rs +++ /dev/null @@ -1,204 +0,0 @@ -// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. -// This file is part of Unique Network. - -// Unique Network 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. - -// Unique Network 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 Unique Network. If not, see . - -extern crate alloc; -use core::{ - char::{REPLACEMENT_CHARACTER, decode_utf16}, - convert::TryInto, - ops::Deref, -}; -use evm_coder::{ToLog, execution::*, generate_stubgen, solidity_interface, types::*, weight}; -use pallet_common::{ - CommonWeightInfo, - erc::{CommonEvmHandler, PrecompileResult}, -}; -use pallet_evm::{account::CrossAccountId, PrecompileHandle}; -use pallet_evm_coder_substrate::{call, dispatch_to_evm, WithRecorder}; -use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _}; -use sp_std::vec::Vec; -use up_data_structs::{CollectionMode, TokenId}; - -use crate::{ - Allowance, Balance, common::CommonWeights, Config, erc721::UniqueRFTCall, Pallet, - RefungibleHandle, SelfWeightOf, weights::WeightInfo, TotalSupply, -}; - -pub struct RefungibleTokenHandle(pub RefungibleHandle, pub TokenId); - -#[derive(ToLog)] -pub enum ERC20Events { - Transfer { - #[indexed] - from: address, - #[indexed] - to: address, - value: uint256, - }, - Approval { - #[indexed] - owner: address, - #[indexed] - spender: address, - value: uint256, - }, -} - -#[solidity_interface(name = "ERC20", events(ERC20Events))] -impl RefungibleTokenHandle { - fn name(&self) -> Result { - Ok(decode_utf16(self.name.iter().copied()) - .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER)) - .collect::()) - } - fn symbol(&self) -> Result { - Ok(string::from_utf8_lossy(&self.token_prefix).into()) - } - fn total_supply(&self) -> Result { - self.consume_store_reads(1)?; - Ok(>::get((self.id, self.1)).into()) - } - - fn decimals(&self) -> Result { - Ok(if let CollectionMode::Fungible(decimals) = &self.mode { - *decimals - } else { - unreachable!() - }) - } - fn balance_of(&self, owner: address) -> Result { - self.consume_store_reads(1)?; - let owner = T::CrossAccountId::from_eth(owner); - let balance = >::get((self.id, self.1, owner)); - Ok(balance.into()) - } - #[weight(>::transfer())] - fn transfer(&mut self, caller: caller, to: address, amount: uint256) -> Result { - let caller = T::CrossAccountId::from_eth(caller); - let to = T::CrossAccountId::from_eth(to); - let amount = amount.try_into().map_err(|_| "amount overflow")?; - let budget = self - .recorder - .weight_calls_budget(>::find_parent()); - - >::transfer(self, &caller, &to, self.1, amount, &budget) - .map_err(|_| "transfer error")?; - Ok(true) - } - #[weight(>::transfer_from())] - fn transfer_from( - &mut self, - caller: caller, - from: address, - to: address, - amount: uint256, - ) -> Result { - let caller = T::CrossAccountId::from_eth(caller); - let from = T::CrossAccountId::from_eth(from); - let to = T::CrossAccountId::from_eth(to); - let amount = amount.try_into().map_err(|_| "amount overflow")?; - let budget = self - .recorder - .weight_calls_budget(>::find_parent()); - - >::transfer_from(self, &caller, &from, &to, self.1, amount, &budget) - .map_err(dispatch_to_evm::)?; - Ok(true) - } - #[weight(>::approve())] - fn approve(&mut self, caller: caller, spender: address, amount: uint256) -> Result { - let caller = T::CrossAccountId::from_eth(caller); - let spender = T::CrossAccountId::from_eth(spender); - let amount = amount.try_into().map_err(|_| "amount overflow")?; - - >::set_allowance(self, &caller, &spender, self.1, amount) - .map_err(dispatch_to_evm::)?; - Ok(true) - } - fn allowance(&self, owner: address, spender: address) -> Result { - self.consume_store_reads(1)?; - let owner = T::CrossAccountId::from_eth(owner); - let spender = T::CrossAccountId::from_eth(spender); - - Ok(>::get((self.id, self.1, owner, spender)).into()) - } -} - -#[solidity_interface(name = "ERC20UniqueExtensions")] -impl RefungibleTokenHandle { - #[weight(>::burn_from())] - fn burn_from(&mut self, caller: caller, from: address, amount: uint256) -> Result { - let caller = T::CrossAccountId::from_eth(caller); - let from = T::CrossAccountId::from_eth(from); - let amount = amount.try_into().map_err(|_| "amount overflow")?; - let budget = self - .recorder - .weight_calls_budget(>::find_parent()); - - >::burn_from(self, &caller, &from, self.1, amount, &budget) - .map_err(dispatch_to_evm::)?; - Ok(true) - } -} - -impl RefungibleTokenHandle { - pub fn into_inner(self) -> RefungibleHandle { - self.0 - } - pub fn common_mut(&mut self) -> &mut RefungibleHandle { - &mut self.0 - } -} - -impl WithRecorder for RefungibleTokenHandle { - fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder { - self.0.recorder() - } - fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder { - self.0.into_recorder() - } -} - -impl Deref for RefungibleTokenHandle { - type Target = RefungibleHandle; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -#[solidity_interface( - name = "UniqueRefungibleToken", - is( - ERC20, - ERC20UniqueExtensions, - via("RefungibleHandle", common_mut, UniqueRFT) - ) -)] -impl RefungibleTokenHandle where T::AccountId: From<[u8; 32]> {} - -generate_stubgen!(gen_impl, UniqueRefungibleTokenCall<()>, true); -generate_stubgen!(gen_iface, UniqueRefungibleTokenCall<()>, false); - -impl CommonEvmHandler for RefungibleTokenHandle -where - T::AccountId: From<[u8; 32]>, -{ - const CODE: &'static [u8] = include_bytes!("./stubs/UniqueRefungibleToken.raw"); - - fn call(self, handle: &mut impl PrecompileHandle) -> Option { - call::, _, _>(handle, self) - } -} --- a/pallets/refungible/src/erc721.rs +++ /dev/null @@ -1,32 +0,0 @@ -extern crate alloc; -use evm_coder::{generate_stubgen, solidity_interface, types::*}; - -use pallet_common::{CollectionHandle, erc::CollectionCall, erc::CommonEvmHandler}; - -use pallet_evm::PrecompileHandle; -use pallet_evm_coder_substrate::call; - -use crate::{Config, RefungibleHandle}; - -#[solidity_interface( - name = "UniqueRFT", - is(via("CollectionHandle", common_mut, Collection),) -)] -impl RefungibleHandle where T::AccountId: From<[u8; 32]> {} - -// Not a tests, but code generators -generate_stubgen!(gen_impl, UniqueRFTCall<()>, true); -generate_stubgen!(gen_iface, UniqueRFTCall<()>, false); - -impl CommonEvmHandler for RefungibleHandle -where - T::AccountId: From<[u8; 32]>, -{ - const CODE: &'static [u8] = include_bytes!("./stubs/UniqueRefungible.raw"); - fn call( - self, - handle: &mut impl PrecompileHandle, - ) -> Option { - call::, _, _>(handle, self) - } -} --- /dev/null +++ b/pallets/refungible/src/erc_token.rs @@ -0,0 +1,195 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network 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. + +// Unique Network 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 Unique Network. If not, see . + +extern crate alloc; +use core::{ + char::{REPLACEMENT_CHARACTER, decode_utf16}, + convert::TryInto, + ops::Deref, +}; +use evm_coder::{ToLog, execution::*, generate_stubgen, solidity_interface, types::*, weight}; +use pallet_common::{ + CommonWeightInfo, + erc::{CommonEvmHandler, PrecompileResult}, +}; +use pallet_evm::{account::CrossAccountId, PrecompileHandle}; +use pallet_evm_coder_substrate::{call, dispatch_to_evm, WithRecorder}; +use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _}; +use sp_std::vec::Vec; +use up_data_structs::TokenId; + +use crate::{ + Allowance, Balance, common::CommonWeights, Config, Pallet, RefungibleHandle, SelfWeightOf, + weights::WeightInfo, TotalSupply, +}; + +pub struct RefungibleTokenHandle(pub RefungibleHandle, pub TokenId); + +#[derive(ToLog)] +pub enum ERC20Events { + Transfer { + #[indexed] + from: address, + #[indexed] + to: address, + value: uint256, + }, + Approval { + #[indexed] + owner: address, + #[indexed] + spender: address, + value: uint256, + }, +} + +#[solidity_interface(name = "ERC20", events(ERC20Events))] +impl RefungibleTokenHandle { + fn name(&self) -> Result { + Ok(decode_utf16(self.name.iter().copied()) + .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER)) + .collect::()) + } + fn symbol(&self) -> Result { + Ok(string::from_utf8_lossy(&self.token_prefix).into()) + } + fn total_supply(&self) -> Result { + self.consume_store_reads(1)?; + Ok(>::get((self.id, self.1)).into()) + } + + fn decimals(&self) -> Result { + // Decimals aren't supported for refungible tokens + Ok(0) + } + + fn balance_of(&self, owner: address) -> Result { + self.consume_store_reads(1)?; + let owner = T::CrossAccountId::from_eth(owner); + let balance = >::get((self.id, self.1, owner)); + Ok(balance.into()) + } + #[weight(>::transfer())] + fn transfer(&mut self, caller: caller, to: address, amount: uint256) -> Result { + let caller = T::CrossAccountId::from_eth(caller); + let to = T::CrossAccountId::from_eth(to); + let amount = amount.try_into().map_err(|_| "amount overflow")?; + let budget = self + .recorder + .weight_calls_budget(>::find_parent()); + + >::transfer(self, &caller, &to, self.1, amount, &budget) + .map_err(|_| "transfer error")?; + Ok(true) + } + #[weight(>::transfer_from())] + fn transfer_from( + &mut self, + caller: caller, + from: address, + to: address, + amount: uint256, + ) -> Result { + let caller = T::CrossAccountId::from_eth(caller); + let from = T::CrossAccountId::from_eth(from); + let to = T::CrossAccountId::from_eth(to); + let amount = amount.try_into().map_err(|_| "amount overflow")?; + let budget = self + .recorder + .weight_calls_budget(>::find_parent()); + + >::transfer_from(self, &caller, &from, &to, self.1, amount, &budget) + .map_err(dispatch_to_evm::)?; + Ok(true) + } + #[weight(>::approve())] + fn approve(&mut self, caller: caller, spender: address, amount: uint256) -> Result { + let caller = T::CrossAccountId::from_eth(caller); + let spender = T::CrossAccountId::from_eth(spender); + let amount = amount.try_into().map_err(|_| "amount overflow")?; + + >::set_allowance(self, &caller, &spender, self.1, amount) + .map_err(dispatch_to_evm::)?; + Ok(true) + } + fn allowance(&self, owner: address, spender: address) -> Result { + self.consume_store_reads(1)?; + let owner = T::CrossAccountId::from_eth(owner); + let spender = T::CrossAccountId::from_eth(spender); + + Ok(>::get((self.id, self.1, owner, spender)).into()) + } +} + +#[solidity_interface(name = "ERC20UniqueExtensions")] +impl RefungibleTokenHandle { + #[weight(>::burn_from())] + fn burn_from(&mut self, caller: caller, from: address, amount: uint256) -> Result { + let caller = T::CrossAccountId::from_eth(caller); + let from = T::CrossAccountId::from_eth(from); + let amount = amount.try_into().map_err(|_| "amount overflow")?; + let budget = self + .recorder + .weight_calls_budget(>::find_parent()); + + >::burn_from(self, &caller, &from, self.1, amount, &budget) + .map_err(dispatch_to_evm::)?; + Ok(true) + } +} + +impl RefungibleTokenHandle { + pub fn into_inner(self) -> RefungibleHandle { + self.0 + } + pub fn common_mut(&mut self) -> &mut RefungibleHandle { + &mut self.0 + } +} + +impl WithRecorder for RefungibleTokenHandle { + fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder { + self.0.recorder() + } + fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder { + self.0.into_recorder() + } +} + +impl Deref for RefungibleTokenHandle { + type Target = RefungibleHandle; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[solidity_interface(name = "UniqueRefungibleToken", is(ERC20, ERC20UniqueExtensions,))] +impl RefungibleTokenHandle where T::AccountId: From<[u8; 32]> {} + +generate_stubgen!(gen_impl, UniqueRefungibleTokenCall<()>, true); +generate_stubgen!(gen_iface, UniqueRefungibleTokenCall<()>, false); + +impl CommonEvmHandler for RefungibleTokenHandle +where + T::AccountId: From<[u8; 32]>, +{ + const CODE: &'static [u8] = include_bytes!("./stubs/UniqueRefungibleToken.raw"); + + fn call(self, handle: &mut impl PrecompileHandle) -> Option { + call::, _, _>(handle, self) + } +} --- a/pallets/refungible/src/lib.rs +++ b/pallets/refungible/src/lib.rs @@ -87,7 +87,7 @@ #![cfg_attr(not(feature = "std"), no_std)] -use crate::erc20::ERC20Events; +use crate::erc_token::ERC20Events; use codec::{Encode, Decode, MaxEncodedLen}; use core::ops::Deref; @@ -110,8 +110,8 @@ #[cfg(feature = "runtime-benchmarks")] pub mod benchmarking; pub mod common; -pub mod erc20; -pub mod erc721; +pub mod erc; +pub mod erc_token; pub mod weights; pub(crate) type SelfWeightOf = ::WeightInfo; --- a/runtime/common/src/dispatch.rs +++ b/runtime/common/src/dispatch.rs @@ -25,7 +25,9 @@ pub use pallet_common::dispatch::CollectionDispatch; use pallet_fungible::{Pallet as PalletFungible, FungibleHandle}; use pallet_nonfungible::{Pallet as PalletNonfungible, NonfungibleHandle}; -use pallet_refungible::{Pallet as PalletRefungible, RefungibleHandle, erc20::RefungibleTokenHandle}; +use pallet_refungible::{ + Pallet as PalletRefungible, RefungibleHandle, erc_token::RefungibleTokenHandle, +}; use up_data_structs::{ CollectionMode, CreateCollectionData, MAX_DECIMAL_POINTS, mapping::TokenAddressMapping, }; --- a/tests/src/eth/reFungibleToken.test.ts +++ b/tests/src/eth/reFungibleToken.test.ts @@ -55,6 +55,22 @@ expect(balance).to.equal('200'); }); + + itWeb3('decimals', async ({api, web3, privateKeyWrapper}) => { + const alice = privateKeyWrapper('//Alice'); + + const collectionId = (await createCollection(api, alice, {name: 'token name', mode: {type: 'ReFungible'}})).collectionId; + + const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + + const tokenId = (await createRefungibleToken(api, alice, collectionId, 200n, {Ethereum: caller})).itemId; + + const address = tokenIdToAddress(collectionId, tokenId); + const contract = new web3.eth.Contract(reFungibleTokenAbi as any, address, {from: caller, ...GAS_ARGS}); + const decimals = await contract.methods.decimals().call(); + + expect(decimals).to.equal('0'); + }); }); describe('Refungible: Plain calls', () => {