difftreelog
feat calculate signature in compile time
in: master
11 files changed
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1096,6 +1096,26 @@
checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3"
[[package]]
+name = "const_format"
+version = "0.2.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "939dc9e2eb9077e0679d2ce32de1ded8531779360b003b4a972a7a39ec263495"
+dependencies = [
+ "const_format_proc_macros",
+]
+
+[[package]]
+name = "const_format_proc_macros"
+version = "0.2.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef196d5d972878a48da7decb7686eded338b4858fbabeed513d63a7c98b2b82d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
name = "constant_time_eq"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2353,6 +2373,7 @@
version = "0.1.3"
dependencies = [
"concat-idents",
+ "const_format",
"ethereum",
"evm-coder-procedural",
"evm-core 0.35.0 (git+https://github.com/uniquenetwork/evm?branch=unique-polkadot-v0.9.30)",
@@ -2362,6 +2383,7 @@
"impl-trait-for-tuples",
"pallet-evm 6.0.0-dev (git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27-fee-limit)",
"primitive-types",
+ "sha3-const",
"similar-asserts",
"sp-std 4.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.30)",
]
@@ -11085,6 +11107,12 @@
]
[[package]]
+name = "sha3-const"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af9625d558d174dbdc711248b479271c0fdca39e9d3d67f6e890b3d4251fbf8e"
+
+[[package]]
name = "sharded-slab"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
crates/evm-coder/Cargo.tomldiffbeforeafterboth--- a/crates/evm-coder/Cargo.toml
+++ b/crates/evm-coder/Cargo.toml
@@ -5,6 +5,10 @@
edition = "2021"
[dependencies]
+const_format = { version = "0.2.26", default-features = false }
+sha3-const = { version = "0.1.0", default-features = false }
+# Ethereum uses keccak (=sha3) for selectors
+# sha3 = "0.10.1"
# evm-coder reexports those proc-macro
evm-coder-procedural = { path = "./procedural" }
# Evm uses primitive-types for H160, H256 and others
crates/evm-coder/procedural/src/solidity_interface.rsdiffbeforeafterboth--- a/crates/evm-coder/procedural/src/solidity_interface.rs
+++ b/crates/evm-coder/procedural/src/solidity_interface.rs
@@ -20,6 +20,7 @@
// about Procedural Macros in Rust book:
// https://doc.rust-lang.org/reference/procedural-macros.html
+use proc_macro2::{TokenStream, Group};
use quote::{quote, ToTokens};
use inflector::cases;
use std::fmt::Write;
@@ -328,6 +329,7 @@
}
}
+#[derive(Debug)]
enum AbiType {
// type
Plain(Ident),
@@ -473,6 +475,7 @@
}
}
+#[derive(Debug)]
struct MethodArg {
name: Ident,
camel_name: String,
@@ -722,13 +725,25 @@
}
}
+ fn expand_selector(&self) -> proc_macro2::TokenStream {
+ let custom_signature = self.expand_custom_signature();
+ quote! {
+ {
+ let a = ::evm_coder::sha3_const::Keccak256::new()
+ .update(#custom_signature.as_bytes())
+ .finalize();
+ [a[0], a[1], a[2], a[3]]
+ }
+ }
+ }
+
fn expand_const(&self) -> proc_macro2::TokenStream {
let screaming_name = &self.screaming_name;
- let selector = u32::to_be_bytes(self.selector);
let selector_str = &self.selector_str;
+ let selector = &self.expand_selector();
quote! {
#[doc = #selector_str]
- const #screaming_name: ::evm_coder::types::bytes4 = [#(#selector,)*];
+ const #screaming_name: ::evm_coder::types::bytes4 = #selector;
}
}
@@ -831,6 +846,59 @@
}
}
+ fn expand_custom_signature(&self) -> proc_macro2::TokenStream {
+ let mut first_comma = true;
+ let mut custom_signature = TokenStream::new();
+ let mut template = self.camel_name.clone() + "(";
+ self.args
+ .iter()
+ .filter_map(|a| {
+ if a.is_special() {
+ return None;
+ };
+
+ match a.ty {
+ AbiType::Plain(ref ident) => Some(ident),
+ _ => None,
+ }
+ })
+ .for_each(|ident| {
+ if !first_comma {
+ custom_signature.extend(quote!(,));
+ template.push(',');
+ } else {
+ first_comma = false;
+ };
+ template.push_str("{}");
+ let ident_str = ident.to_string();
+ match ident_str.as_str() {
+ "address" | "uint8" | "uint16" | "uint32" | "uint64" | "uint128"
+ | "uint256" | "bytes4" | "topic" | "string" | "bytes" | "void" | "caller"
+ | "bool" | "" => {
+ custom_signature.extend(quote!(#ident_str));
+ }
+ _ => {
+ custom_signature.extend(quote! {
+ #ident::SIGNATURE_STRING
+ });
+ }
+ }
+ });
+
+ template.push(')');
+ let mut template = quote!(#template);
+ template.extend(quote!(,));
+ template.extend(custom_signature);
+ let custom_signature_group = Group::new(proc_macro2::Delimiter::Parenthesis, template);
+ let mut custom_signature = quote! {
+ ::evm_coder::const_format::formatcp!
+ };
+ custom_signature.extend(custom_signature_group.to_token_stream());
+
+ // println!("!!!!! {}", custom_signature);
+ custom_signature
+ }
+
fn expand_solidity_function(&self) -> proc_macro2::TokenStream {
let camel_name = &self.camel_name;
let mutability = match self.mutability {
@@ -847,15 +915,17 @@
.map(MethodArg::expand_solidity_argument);
let docs = &self.docs;
let selector_str = &self.selector_str;
- let selector = self.selector;
+ let selector = &self.expand_selector();
let hide = self.hide;
+ let custom_signature = self.expand_custom_signature();
let is_payable = self.has_value_args;
quote! {
SolidityFunction {
docs: &[#(#docs),*],
selector_str: #selector_str,
- selector: #selector,
hide: #hide,
+ selector: u32::from_be_bytes(#selector),
+ custom_signature: #custom_signature,
name: #camel_name,
mutability: #mutability,
is_payable: #is_payable,
crates/evm-coder/src/lib.rsdiffbeforeafterboth--- a/crates/evm-coder/src/lib.rs
+++ b/crates/evm-coder/src/lib.rs
@@ -90,6 +90,8 @@
pub use evm_coder_procedural::solidity;
/// See [`solidity_interface`]
pub use evm_coder_procedural::weight;
+pub use const_format;
+pub use sha3_const;
/// Derives [`ToLog`] for enum
///
@@ -227,6 +229,14 @@
}
}
+ impl SignatureString for EthCrossAccount {
+ const SIGNATURE_STRING: &'static str = "(address,uint256)";
+ }
+
+ pub trait SignatureString {
+ const SIGNATURE_STRING: &'static str;
+ }
+
/// Convert `CrossAccountId` to `uint256`.
pub fn convert_cross_account_to_uint256<T: pallet_evm::account::Config>(
from: &T::CrossAccountId,
@@ -348,6 +358,18 @@
assert_eq!(fn_selector!(transfer(address, uint256)), 0xa9059cbb);
}
+ // #[test]
+ // fn function_selector_generation_1() {
+ // assert_eq!(
+ // fn_selector!(transferFromCrossAccountToCrossAccount(
+ // EthCrossAccount,
+ // EthCrossAccount,
+ // uint256
+ // )),
+ // 2543295963
+ // );
+ // }
+
#[test]
fn event_topic_generation() {
assert_eq!(
crates/evm-coder/src/solidity.rsdiffbeforeafterboth--- a/crates/evm-coder/src/solidity.rs
+++ b/crates/evm-coder/src/solidity.rs
@@ -35,6 +35,12 @@
use crate::types::*;
#[derive(Default)]
+pub struct FunctionSelectorMaker {
+ pub name: string,
+ pub args: Vec<fn() -> string>,
+}
+
+#[derive(Default)]
pub struct TypeCollector {
/// Code => id
/// id ordering is required to perform topo-sort on the resulting data
@@ -482,11 +488,25 @@
View,
Mutable,
}
+
+// fn fn_selector_str(input: &str) -> u32 {
+// use sha3::Digest;
+// let mut hasher = sha3::Keccak256::new();
+// hasher.update(input.as_bytes());
+// let result = hasher.finalize();
+
+// let mut selector_bytes = [0; 4];
+// selector_bytes.copy_from_slice(&result[0..4]);
+
+// u32::from_be_bytes(selector_bytes)
+// }
+
pub struct SolidityFunction<A, R> {
pub docs: &'static [&'static str],
pub selector_str: &'static str,
pub selector: u32,
pub hide: bool,
+ pub custom_signature: &'static str,
pub name: &'static str,
pub args: A,
pub result: R,
@@ -512,7 +532,7 @@
writeln!(
writer,
"\t{hide_comment}/// or in textual repr: {}",
- self.selector_str
+ self.custom_signature
)?;
write!(writer, "\t{hide_comment}function {}(", self.name)?;
self.args.solidity_name(writer, tc)?;
pallets/evm-contract-helpers/Cargo.tomldiffbeforeafterboth--- a/pallets/evm-contract-helpers/Cargo.toml
+++ b/pallets/evm-contract-helpers/Cargo.toml
@@ -27,7 +27,7 @@
evm-coder = { default-features = false, path = '../../crates/evm-coder' }
pallet-common = { default-features = false, path = '../../pallets/common' }
pallet-evm-coder-substrate = { default-features = false, path = '../../pallets/evm-coder-substrate' }
-pallet-evm-transaction-payment = { default-features = false, path = '../../pallets/evm-transaction-payment' }
+pallet-evm-transaction-payment = { default-features = false, path = '../../pallets/evm-transaction-payment' }
up-data-structs = { default-features = false, path = '../../primitives/data-structs', features = [
'serde1',
] }
pallets/evm-contract-helpers/src/eth.rsdiffbeforeafterboth--- a/pallets/evm-contract-helpers/src/eth.rs
+++ b/pallets/evm-contract-helpers/src/eth.rs
@@ -16,6 +16,8 @@
//! Implementation of magic contract
+extern crate alloc;
+use alloc::{format, string::ToString};
use core::marker::PhantomData;
use evm_coder::{
abi::AbiWriter, execution::Result, generate_stubgen, solidity_interface, types::*, ToLog,
pallets/fungible/src/erc.rsdiffbeforeafterboth--- a/pallets/fungible/src/erc.rs
+++ b/pallets/fungible/src/erc.rs
@@ -16,6 +16,8 @@
//! ERC-20 standart support implementation.
+extern crate alloc;
+use alloc::{format, string::ToString};
use core::char::{REPLACEMENT_CHARACTER, decode_utf16};
use core::convert::TryInto;
use evm_coder::{ToLog, execution::*, generate_stubgen, solidity_interface, types::*, weight};
pallets/nonfungible/src/erc.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/erc.rs
+++ b/pallets/nonfungible/src/erc.rs
@@ -20,6 +20,7 @@
//! Method implementations are mostly doing parameter conversion and calling Nonfungible Pallet methods.
extern crate alloc;
+use alloc::{format, string::ToString};
use core::{
char::{REPLACEMENT_CHARACTER, decode_utf16},
convert::TryInto,
pallets/refungible/Cargo.tomldiffbeforeafterboth--- a/pallets/refungible/Cargo.toml
+++ b/pallets/refungible/Cargo.toml
@@ -26,7 +26,9 @@
struct-versioning = { path = "../../crates/struct-versioning" }
up-data-structs = { default-features = false, path = '../../primitives/data-structs' }
ethereum = { version = "0.12.0", default-features = false }
-scale-info = { version = "2.0.1", default-features = false, features = ["derive",] }
+scale-info = { version = "2.0.1", default-features = false, features = [
+ "derive",
+] }
derivative = { version = "2.2.0", features = ["use_core"] }
[features]
pallets/refungible/src/erc.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 tokens18//!19//! Provides ERC-721 standart support implementation and EVM API for unique extensions for Refungible Pallet.20//! Method implementations are mostly doing parameter conversion and calling Refungible Pallet methods.2122extern crate alloc;2324use alloc::{format, string::ToString};25use core::{26 char::{REPLACEMENT_CHARACTER, decode_utf16},27 convert::TryInto,28};29use evm_coder::{ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*, weight};30use frame_support::{BoundedBTreeMap, BoundedVec};31use pallet_common::{32 CollectionHandle, CollectionPropertyPermissions,33 erc::{CommonEvmHandler, CollectionCall, static_property::key},34 eth::convert_tuple_to_cross_account,35};36use pallet_evm::{account::CrossAccountId, PrecompileHandle};37use pallet_evm_coder_substrate::{call, dispatch_to_evm};38use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};39use sp_core::H160;40use sp_std::{collections::btree_map::BTreeMap, vec::Vec, vec};41use up_data_structs::{42 CollectionId, CollectionPropertiesVec, mapping::TokenAddressMapping, Property, PropertyKey,43 PropertyKeyPermission, PropertyPermission, TokenId,44};4546use crate::{47 AccountBalance, Balance, Config, CreateItemData, Pallet, RefungibleHandle, SelfWeightOf,48 TokenProperties, TokensMinted, TotalSupply, weights::WeightInfo,49};5051pub const ADDRESS_FOR_PARTIALLY_OWNED_TOKENS: H160 = H160::repeat_byte(0xff);5253/// @title A contract that allows to set and delete token properties and change token property permissions.54#[solidity_interface(name = TokenProperties)]55impl<T: Config> RefungibleHandle<T> {56 /// @notice Set permissions for token property.57 /// @dev Throws error if `msg.sender` is not admin or owner of the collection.58 /// @param key Property key.59 /// @param isMutable Permission to mutate property.60 /// @param collectionAdmin Permission to mutate property by collection admin if property is mutable.61 /// @param tokenOwner Permission to mutate property by token owner if property is mutable.62 fn set_token_property_permission(63 &mut self,64 caller: caller,65 key: string,66 is_mutable: bool,67 collection_admin: bool,68 token_owner: bool,69 ) -> Result<()> {70 let caller = T::CrossAccountId::from_eth(caller);71 <Pallet<T>>::set_token_property_permissions(72 self,73 &caller,74 vec![PropertyKeyPermission {75 key: <Vec<u8>>::from(key)76 .try_into()77 .map_err(|_| "too long key")?,78 permission: PropertyPermission {79 mutable: is_mutable,80 collection_admin,81 token_owner,82 },83 }],84 )85 .map_err(dispatch_to_evm::<T>)86 }8788 /// @notice Set token property value.89 /// @dev Throws error if `msg.sender` has no permission to edit the property.90 /// @param tokenId ID of the token.91 /// @param key Property key.92 /// @param value Property value.93 fn set_property(94 &mut self,95 caller: caller,96 token_id: uint256,97 key: string,98 value: bytes,99 ) -> Result<()> {100 let caller = T::CrossAccountId::from_eth(caller);101 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;102 let key = <Vec<u8>>::from(key)103 .try_into()104 .map_err(|_| "key too long")?;105 let value = value.0.try_into().map_err(|_| "value too long")?;106107 let nesting_budget = self108 .recorder109 .weight_calls_budget(<StructureWeight<T>>::find_parent());110111 <Pallet<T>>::set_token_property(112 self,113 &caller,114 TokenId(token_id),115 Property { key, value },116 &nesting_budget,117 )118 .map_err(dispatch_to_evm::<T>)119 }120121 /// @notice Set token properties value.122 /// @dev Throws error if `msg.sender` has no permission to edit the property.123 /// @param tokenId ID of the token.124 /// @param properties settable properties125 fn set_properties(126 &mut self,127 caller: caller,128 token_id: uint256,129 properties: Vec<(string, bytes)>,130 ) -> Result<()> {131 let caller = T::CrossAccountId::from_eth(caller);132 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;133134 let nesting_budget = self135 .recorder136 .weight_calls_budget(<StructureWeight<T>>::find_parent());137138 let properties = properties139 .into_iter()140 .map(|(key, value)| {141 let key = <Vec<u8>>::from(key)142 .try_into()143 .map_err(|_| "key too large")?;144145 let value = value.0.try_into().map_err(|_| "value too large")?;146147 Ok(Property { key, value })148 })149 .collect::<Result<Vec<_>>>()?;150151 <Pallet<T>>::set_token_properties(152 self,153 &caller,154 TokenId(token_id),155 properties.into_iter(),156 <Pallet<T>>::token_exists(&self, TokenId(token_id)),157 &nesting_budget,158 )159 .map_err(dispatch_to_evm::<T>)160 }161162 /// @notice Delete token property value.163 /// @dev Throws error if `msg.sender` has no permission to edit the property.164 /// @param tokenId ID of the token.165 /// @param key Property key.166 fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {167 let caller = T::CrossAccountId::from_eth(caller);168 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;169 let key = <Vec<u8>>::from(key)170 .try_into()171 .map_err(|_| "key too long")?;172173 let nesting_budget = self174 .recorder175 .weight_calls_budget(<StructureWeight<T>>::find_parent());176177 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)178 .map_err(dispatch_to_evm::<T>)179 }180181 /// @notice Get token property value.182 /// @dev Throws error if key not found183 /// @param tokenId ID of the token.184 /// @param key Property key.185 /// @return Property value bytes186 fn property(&self, token_id: uint256, key: string) -> Result<bytes> {187 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;188 let key = <Vec<u8>>::from(key)189 .try_into()190 .map_err(|_| "key too long")?;191192 let props = <TokenProperties<T>>::get((self.id, token_id));193 let prop = props.get(&key).ok_or("key not found")?;194195 Ok(prop.to_vec().into())196 }197}198199#[derive(ToLog)]200pub enum ERC721Events {201 /// @dev This event emits when NFTs are created (`from` == 0) and destroyed202 /// (`to` == 0). Exception: during contract creation, any number of RFTs203 /// may be created and assigned without emitting Transfer.204 Transfer {205 #[indexed]206 from: address,207 #[indexed]208 to: address,209 #[indexed]210 token_id: uint256,211 },212 /// @dev Not supported213 Approval {214 #[indexed]215 owner: address,216 #[indexed]217 approved: address,218 #[indexed]219 token_id: uint256,220 },221 /// @dev Not supported222 #[allow(dead_code)]223 ApprovalForAll {224 #[indexed]225 owner: address,226 #[indexed]227 operator: address,228 approved: bool,229 },230}231232#[derive(ToLog)]233pub enum ERC721UniqueMintableEvents {234 /// @dev Not supported235 #[allow(dead_code)]236 MintingFinished {},237}238239#[solidity_interface(name = ERC721Metadata)]240impl<T: Config> RefungibleHandle<T>241where242 T::AccountId: From<[u8; 32]>,243{244 /// @notice A descriptive name for a collection of NFTs in this contract245 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`246 #[solidity(hide, rename_selector = "name")]247 fn name_proxy(&self) -> Result<string> {248 self.name()249 }250251 /// @notice An abbreviated name for NFTs in this contract252 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`253 #[solidity(hide, rename_selector = "symbol")]254 fn symbol_proxy(&self) -> Result<string> {255 self.symbol()256 }257258 /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.259 ///260 /// @dev If the token has a `url` property and it is not empty, it is returned.261 /// Else If the collection does not have a property with key `schemaName` or its value is not equal to `ERC721Metadata`, it return an error `tokenURI not set`.262 /// If the collection property `baseURI` is empty or absent, return "" (empty string)263 /// otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix264 /// otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).265 ///266 /// @return token's const_metadata267 #[solidity(rename_selector = "tokenURI")]268 fn token_uri(&self, token_id: uint256) -> Result<string> {269 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;270271 match get_token_property(self, token_id_u32, &key::url()).as_deref() {272 Err(_) | Ok("") => (),273 Ok(url) => {274 return Ok(url.into());275 }276 };277278 let base_uri =279 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())280 .map(BoundedVec::into_inner)281 .map(string::from_utf8)282 .transpose()283 .map_err(|e| {284 Error::Revert(alloc::format!(285 "Can not convert value \"baseURI\" to string with error \"{}\"",286 e287 ))288 })?;289290 let base_uri = match base_uri.as_deref() {291 None | Some("") => {292 return Ok("".into());293 }294 Some(base_uri) => base_uri.into(),295 };296297 Ok(298 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {299 Err(_) | Ok("") => base_uri,300 Ok(suffix) => base_uri + suffix,301 },302 )303 }304}305306/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension307/// @dev See https://eips.ethereum.org/EIPS/eip-721308#[solidity_interface(name = ERC721Enumerable)]309impl<T: Config> RefungibleHandle<T> {310 /// @notice Enumerate valid RFTs311 /// @param index A counter less than `totalSupply()`312 /// @return The token identifier for the `index`th NFT,313 /// (sort order not specified)314 fn token_by_index(&self, index: uint256) -> Result<uint256> {315 Ok(index)316 }317318 /// Not implemented319 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {320 // TODO: Not implemetable321 Err("not implemented".into())322 }323324 /// @notice Count RFTs tracked by this contract325 /// @return A count of valid RFTs tracked by this contract, where each one of326 /// them has an assigned and queryable owner not equal to the zero address327 fn total_supply(&self) -> Result<uint256> {328 self.consume_store_reads(1)?;329 Ok(<Pallet<T>>::total_supply(self).into())330 }331}332333/// @title ERC-721 Non-Fungible Token Standard334/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md335#[solidity_interface(name = ERC721, events(ERC721Events))]336impl<T: Config> RefungibleHandle<T> {337 /// @notice Count all RFTs assigned to an owner338 /// @dev RFTs assigned to the zero address are considered invalid, and this339 /// function throws for queries about the zero address.340 /// @param owner An address for whom to query the balance341 /// @return The number of RFTs owned by `owner`, possibly zero342 fn balance_of(&self, owner: address) -> Result<uint256> {343 self.consume_store_reads(1)?;344 let owner = T::CrossAccountId::from_eth(owner);345 let balance = <AccountBalance<T>>::get((self.id, owner));346 Ok(balance.into())347 }348349 /// @notice Find the owner of an RFT350 /// @dev RFTs assigned to zero address are considered invalid, and queries351 /// about them do throw.352 /// Returns special 0xffffffffffffffffffffffffffffffffffffffff address for353 /// the tokens that are partially owned.354 /// @param tokenId The identifier for an RFT355 /// @return The address of the owner of the RFT356 fn owner_of(&self, token_id: uint256) -> Result<address> {357 self.consume_store_reads(2)?;358 let token = token_id.try_into()?;359 let owner = <Pallet<T>>::token_owner(self.id, token);360 Ok(owner361 .map(|address| *address.as_eth())362 .unwrap_or_else(|| ADDRESS_FOR_PARTIALLY_OWNED_TOKENS))363 }364365 /// @dev Not implemented366 fn safe_transfer_from_with_data(367 &mut self,368 _from: address,369 _to: address,370 _token_id: uint256,371 _data: bytes,372 ) -> Result<void> {373 // TODO: Not implemetable374 Err("not implemented".into())375 }376377 /// @dev Not implemented378 fn safe_transfer_from(379 &mut self,380 _from: address,381 _to: address,382 _token_id: uint256,383 ) -> Result<void> {384 // TODO: Not implemetable385 Err("not implemented".into())386 }387388 /// @notice Transfer ownership of an RFT -- THE CALLER IS RESPONSIBLE389 /// TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE390 /// THEY MAY BE PERMANENTLY LOST391 /// @dev Throws unless `msg.sender` is the current owner or an authorized392 /// operator for this RFT. Throws if `from` is not the current owner. Throws393 /// if `to` is the zero address. Throws if `tokenId` is not a valid RFT.394 /// Throws if RFT pieces have multiple owners.395 /// @param from The current owner of the NFT396 /// @param to The new owner397 /// @param tokenId The NFT to transfer398 #[weight(<SelfWeightOf<T>>::transfer_from_creating_removing())]399 fn transfer_from(400 &mut self,401 caller: caller,402 from: address,403 to: address,404 token_id: uint256,405 ) -> Result<void> {406 let caller = T::CrossAccountId::from_eth(caller);407 let from = T::CrossAccountId::from_eth(from);408 let to = T::CrossAccountId::from_eth(to);409 let token = token_id.try_into()?;410 let budget = self411 .recorder412 .weight_calls_budget(<StructureWeight<T>>::find_parent());413414 let balance = balance(&self, token, &from)?;415 ensure_single_owner(&self, token, balance)?;416417 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, balance, &budget)418 .map_err(dispatch_to_evm::<T>)?;419420 Ok(())421 }422423 /// @dev Not implemented424 fn approve(&mut self, _caller: caller, _approved: address, _token_id: uint256) -> Result<void> {425 Err("not implemented".into())426 }427428 /// @dev Not implemented429 fn set_approval_for_all(430 &mut self,431 _caller: caller,432 _operator: address,433 _approved: bool,434 ) -> Result<void> {435 // TODO: Not implemetable436 Err("not implemented".into())437 }438439 /// @dev Not implemented440 fn get_approved(&self, _token_id: uint256) -> Result<address> {441 // TODO: Not implemetable442 Err("not implemented".into())443 }444445 /// @dev Not implemented446 fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {447 // TODO: Not implemetable448 Err("not implemented".into())449 }450}451452/// Returns amount of pieces of `token` that `owner` have453pub fn balance<T: Config>(454 collection: &RefungibleHandle<T>,455 token: TokenId,456 owner: &T::CrossAccountId,457) -> Result<u128> {458 collection.consume_store_reads(1)?;459 let balance = <Balance<T>>::get((collection.id, token, &owner));460 Ok(balance)461}462463/// Throws if `owner_balance` is lower than total amount of `token` pieces464pub fn ensure_single_owner<T: Config>(465 collection: &RefungibleHandle<T>,466 token: TokenId,467 owner_balance: u128,468) -> Result<()> {469 collection.consume_store_reads(1)?;470 let total_supply = <TotalSupply<T>>::get((collection.id, token));471 if total_supply != owner_balance {472 return Err("token has multiple owners".into());473 }474 Ok(())475}476477/// @title ERC721 Token that can be irreversibly burned (destroyed).478#[solidity_interface(name = ERC721Burnable)]479impl<T: Config> RefungibleHandle<T> {480 /// @notice Burns a specific ERC721 token.481 /// @dev Throws unless `msg.sender` is the current RFT owner, or an authorized482 /// operator of the current owner.483 /// @param tokenId The RFT to approve484 #[weight(<SelfWeightOf<T>>::burn_item_fully())]485 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {486 let caller = T::CrossAccountId::from_eth(caller);487 let token = token_id.try_into()?;488489 let balance = balance(&self, token, &caller)?;490 ensure_single_owner(&self, token, balance)?;491492 <Pallet<T>>::burn(self, &caller, token, balance).map_err(dispatch_to_evm::<T>)?;493 Ok(())494 }495}496497/// @title ERC721 minting logic.498#[solidity_interface(name = ERC721UniqueMintable, events(ERC721UniqueMintableEvents))]499impl<T: Config> RefungibleHandle<T> {500 fn minting_finished(&self) -> Result<bool> {501 Ok(false)502 }503504 /// @notice Function to mint token.505 /// @param to The new owner506 /// @return uint256 The id of the newly minted token507 #[weight(<SelfWeightOf<T>>::create_item())]508 fn mint(&mut self, caller: caller, to: address) -> Result<uint256> {509 let token_id: uint256 = <TokensMinted<T>>::get(self.id)510 .checked_add(1)511 .ok_or("item id overflow")?512 .into();513 self.mint_check_id(caller, to, token_id)?;514 Ok(token_id)515 }516517 /// @notice Function to mint token.518 /// @dev `tokenId` should be obtained with `nextTokenId` method,519 /// unlike standard, you can't specify it manually520 /// @param to The new owner521 /// @param tokenId ID of the minted RFT522 #[solidity(hide, rename_selector = "mint")]523 #[weight(<SelfWeightOf<T>>::create_item())]524 fn mint_check_id(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {525 let caller = T::CrossAccountId::from_eth(caller);526 let to = T::CrossAccountId::from_eth(to);527 let token_id: u32 = token_id.try_into()?;528 let budget = self529 .recorder530 .weight_calls_budget(<StructureWeight<T>>::find_parent());531532 if <TokensMinted<T>>::get(self.id)533 .checked_add(1)534 .ok_or("item id overflow")?535 != token_id536 {537 return Err("item id should be next".into());538 }539540 let users = [(to.clone(), 1)]541 .into_iter()542 .collect::<BTreeMap<_, _>>()543 .try_into()544 .unwrap();545 <Pallet<T>>::create_item(546 self,547 &caller,548 CreateItemData::<T::CrossAccountId> {549 users,550 properties: CollectionPropertiesVec::default(),551 },552 &budget,553 )554 .map_err(dispatch_to_evm::<T>)?;555556 Ok(true)557 }558559 /// @notice Function to mint token with the given tokenUri.560 /// @param to The new owner561 /// @param tokenUri Token URI that would be stored in the NFT properties562 /// @return uint256 The id of the newly minted token563 #[solidity(rename_selector = "mintWithTokenURI")]564 #[weight(<SelfWeightOf<T>>::create_item())]565 fn mint_with_token_uri(566 &mut self,567 caller: caller,568 to: address,569 token_uri: string,570 ) -> Result<uint256> {571 let token_id: uint256 = <TokensMinted<T>>::get(self.id)572 .checked_add(1)573 .ok_or("item id overflow")?574 .into();575 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;576 Ok(token_id)577 }578579 /// @notice Function to mint token with the given tokenUri.580 /// @dev `tokenId` should be obtained with `nextTokenId` method,581 /// unlike standard, you can't specify it manually582 /// @param to The new owner583 /// @param tokenId ID of the minted RFT584 /// @param tokenUri Token URI that would be stored in the RFT properties585 #[solidity(hide, rename_selector = "mintWithTokenURI")]586 #[weight(<SelfWeightOf<T>>::create_item())]587 fn mint_with_token_uri_check_id(588 &mut self,589 caller: caller,590 to: address,591 token_id: uint256,592 token_uri: string,593 ) -> Result<bool> {594 let key = key::url();595 let permission = get_token_permission::<T>(self.id, &key)?;596 if !permission.collection_admin {597 return Err("Operation is not allowed".into());598 }599600 let caller = T::CrossAccountId::from_eth(caller);601 let to = T::CrossAccountId::from_eth(to);602 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;603 let budget = self604 .recorder605 .weight_calls_budget(<StructureWeight<T>>::find_parent());606607 if <TokensMinted<T>>::get(self.id)608 .checked_add(1)609 .ok_or("item id overflow")?610 != token_id611 {612 return Err("item id should be next".into());613 }614615 let mut properties = CollectionPropertiesVec::default();616 properties617 .try_push(Property {618 key,619 value: token_uri620 .into_bytes()621 .try_into()622 .map_err(|_| "token uri is too long")?,623 })624 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;625626 let users = [(to.clone(), 1)]627 .into_iter()628 .collect::<BTreeMap<_, _>>()629 .try_into()630 .unwrap();631 <Pallet<T>>::create_item(632 self,633 &caller,634 CreateItemData::<T::CrossAccountId> { users, properties },635 &budget,636 )637 .map_err(dispatch_to_evm::<T>)?;638 Ok(true)639 }640641 /// @dev Not implemented642 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {643 Err("not implementable".into())644 }645}646647fn get_token_property<T: Config>(648 collection: &CollectionHandle<T>,649 token_id: u32,650 key: &up_data_structs::PropertyKey,651) -> Result<string> {652 collection.consume_store_reads(1)?;653 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))654 .map_err(|_| Error::Revert("Token properties not found".into()))?;655 if let Some(property) = properties.get(key) {656 return Ok(string::from_utf8_lossy(property).into());657 }658659 Err("Property tokenURI not found".into())660}661662fn get_token_permission<T: Config>(663 collection_id: CollectionId,664 key: &PropertyKey,665) -> Result<PropertyPermission> {666 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)667 .map_err(|_| Error::Revert("No permissions for collection".into()))?;668 let a = token_property_permissions669 .get(key)670 .map(Clone::clone)671 .ok_or_else(|| {672 let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();673 Error::Revert(alloc::format!("No permission for key {}", key))674 })?;675 Ok(a)676}677678/// @title Unique extensions for ERC721.679#[solidity_interface(name = ERC721UniqueExtensions)]680impl<T: Config> RefungibleHandle<T>681where682 T::AccountId: From<[u8; 32]>,683{684 /// @notice A descriptive name for a collection of NFTs in this contract685 fn name(&self) -> Result<string> {686 Ok(decode_utf16(self.name.iter().copied())687 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))688 .collect::<string>())689 }690691 /// @notice An abbreviated name for NFTs in this contract692 fn symbol(&self) -> Result<string> {693 Ok(string::from_utf8_lossy(&self.token_prefix).into())694 }695696 /// @notice Transfer ownership of an RFT697 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`698 /// is the zero address. Throws if `tokenId` is not a valid RFT.699 /// Throws if RFT pieces have multiple owners.700 /// @param to The new owner701 /// @param tokenId The RFT to transfer702 #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]703 fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {704 let caller = T::CrossAccountId::from_eth(caller);705 let to = T::CrossAccountId::from_eth(to);706 let token = token_id.try_into()?;707 let budget = self708 .recorder709 .weight_calls_budget(<StructureWeight<T>>::find_parent());710711 let balance = balance(self, token, &caller)?;712 ensure_single_owner(self, token, balance)?;713714 <Pallet<T>>::transfer(self, &caller, &to, token, balance, &budget)715 .map_err(dispatch_to_evm::<T>)?;716 Ok(())717 }718719 /// @notice Transfer ownership of an RFT720 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`721 /// is the zero address. Throws if `tokenId` is not a valid RFT.722 /// Throws if RFT pieces have multiple owners.723 /// @param to The new owner724 /// @param tokenId The RFT to transfer725 #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]726 fn transfer_from_cross(727 &mut self,728 caller: caller,729 from: (address, uint256),730 to: (address, uint256),731 token_id: uint256,732 ) -> Result<void> {733 let caller = T::CrossAccountId::from_eth(caller);734 // let from = convert_tuple_to_cross_account::<T>(from)?;735 // let to = convert_tuple_to_cross_account::<T>(to)?;736 // let token_id = token_id.try_into()?;737 // let budget = self738 // .recorder739 // .weight_calls_budget(<StructureWeight<T>>::find_parent());740741 // let balance = balance(self, token_id, &from)?;742 // ensure_single_owner(self, token_id, balance)?;743744 // Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, balance, &budget)745 // .map_err(dispatch_to_evm::<T>)?;746 Ok(())747 }748749 /// @notice Burns a specific ERC721 token.750 /// @dev Throws unless `msg.sender` is the current owner or an authorized751 /// operator for this RFT. Throws if `from` is not the current owner. Throws752 /// if `to` is the zero address. Throws if `tokenId` is not a valid RFT.753 /// Throws if RFT pieces have multiple owners.754 /// @param from The current owner of the RFT755 /// @param tokenId The RFT to transfer756 #[weight(<SelfWeightOf<T>>::burn_from())]757 fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {758 let caller = T::CrossAccountId::from_eth(caller);759 let from = T::CrossAccountId::from_eth(from);760 let token = token_id.try_into()?;761 let budget = self762 .recorder763 .weight_calls_budget(<StructureWeight<T>>::find_parent());764765 let balance = balance(self, token, &from)?;766 ensure_single_owner(self, token, balance)?;767768 <Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)769 .map_err(dispatch_to_evm::<T>)?;770 Ok(())771 }772773 /// @notice Burns a specific ERC721 token.774 /// @dev Throws unless `msg.sender` is the current owner or an authorized775 /// operator for this RFT. Throws if `from` is not the current owner. Throws776 /// if `to` is the zero address. Throws if `tokenId` is not a valid RFT.777 /// Throws if RFT pieces have multiple owners.778 /// @param from The current owner of the RFT779 /// @param tokenId The RFT to transfer780 #[weight(<SelfWeightOf<T>>::burn_from())]781 fn burn_from_cross(782 &mut self,783 caller: caller,784 from: (address, uint256),785 token_id: uint256,786 ) -> Result<void> {787 let caller = T::CrossAccountId::from_eth(caller);788 let from = convert_tuple_to_cross_account::<T>(from)?;789 let token = token_id.try_into()?;790 let budget = self791 .recorder792 .weight_calls_budget(<StructureWeight<T>>::find_parent());793794 let balance = balance(self, token, &from)?;795 ensure_single_owner(self, token, balance)?;796797 <Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)798 .map_err(dispatch_to_evm::<T>)?;799 Ok(())800 }801802 /// @notice Returns next free RFT ID.803 fn next_token_id(&self) -> Result<uint256> {804 self.consume_store_reads(1)?;805 Ok(<TokensMinted<T>>::get(self.id)806 .checked_add(1)807 .ok_or("item id overflow")?808 .into())809 }810811 /// @notice Function to mint multiple tokens.812 /// @dev `tokenIds` should be an array of consecutive numbers and first number813 /// should be obtained with `nextTokenId` method814 /// @param to The new owner815 /// @param tokenIds IDs of the minted RFTs816 #[solidity(hide)]817 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]818 fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {819 let caller = T::CrossAccountId::from_eth(caller);820 let to = T::CrossAccountId::from_eth(to);821 let mut expected_index = <TokensMinted<T>>::get(self.id)822 .checked_add(1)823 .ok_or("item id overflow")?;824 let budget = self825 .recorder826 .weight_calls_budget(<StructureWeight<T>>::find_parent());827828 let total_tokens = token_ids.len();829 for id in token_ids.into_iter() {830 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;831 if id != expected_index {832 return Err("item id should be next".into());833 }834 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;835 }836 let users = [(to.clone(), 1)]837 .into_iter()838 .collect::<BTreeMap<_, _>>()839 .try_into()840 .unwrap();841 let create_item_data = CreateItemData::<T::CrossAccountId> {842 users,843 properties: CollectionPropertiesVec::default(),844 };845 let data = (0..total_tokens)846 .map(|_| create_item_data.clone())847 .collect();848849 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)850 .map_err(dispatch_to_evm::<T>)?;851 Ok(true)852 }853854 /// @notice Function to mint multiple tokens with the given tokenUris.855 /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive856 /// numbers and first number should be obtained with `nextTokenId` method857 /// @param to The new owner858 /// @param tokens array of pairs of token ID and token URI for minted tokens859 #[solidity(hide, rename_selector = "mintBulkWithTokenURI")]860 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]861 fn mint_bulk_with_token_uri(862 &mut self,863 caller: caller,864 to: address,865 tokens: Vec<(uint256, string)>,866 ) -> Result<bool> {867 let key = key::url();868 let caller = T::CrossAccountId::from_eth(caller);869 let to = T::CrossAccountId::from_eth(to);870 let mut expected_index = <TokensMinted<T>>::get(self.id)871 .checked_add(1)872 .ok_or("item id overflow")?;873 let budget = self874 .recorder875 .weight_calls_budget(<StructureWeight<T>>::find_parent());876877 let mut data = Vec::with_capacity(tokens.len());878 let users: BoundedBTreeMap<_, _, _> = [(to.clone(), 1)]879 .into_iter()880 .collect::<BTreeMap<_, _>>()881 .try_into()882 .unwrap();883 for (id, token_uri) in tokens {884 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;885 if id != expected_index {886 return Err("item id should be next".into());887 }888 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;889890 let mut properties = CollectionPropertiesVec::default();891 properties892 .try_push(Property {893 key: key.clone(),894 value: token_uri895 .into_bytes()896 .try_into()897 .map_err(|_| "token uri is too long")?,898 })899 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;900901 let create_item_data = CreateItemData::<T::CrossAccountId> {902 users: users.clone(),903 properties,904 };905 data.push(create_item_data);906 }907908 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)909 .map_err(dispatch_to_evm::<T>)?;910 Ok(true)911 }912913 /// Returns EVM address for refungible token914 ///915 /// @param token ID of the token916 fn token_contract_address(&self, token: uint256) -> Result<address> {917 Ok(T::EvmTokenAddressMapping::token_to_address(918 self.id,919 token.try_into().map_err(|_| "token id overflow")?,920 ))921 }922}923924#[solidity_interface(925 name = UniqueRefungible,926 is(927 ERC721,928 ERC721Enumerable,929 ERC721UniqueExtensions,930 ERC721UniqueMintable,931 ERC721Burnable,932 ERC721Metadata(if(this.flags.erc721metadata)),933 Collection(via(common_mut returns CollectionHandle<T>)),934 TokenProperties,935 )936)]937impl<T: Config> RefungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}938939// Not a tests, but code generators940generate_stubgen!(gen_impl, UniqueRefungibleCall<()>, true);941generate_stubgen!(gen_iface, UniqueRefungibleCall<()>, false);942943impl<T: Config> CommonEvmHandler for RefungibleHandle<T>944where945 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,946{947 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueRefungible.raw");948 fn call(949 self,950 handle: &mut impl PrecompileHandle,951 ) -> Option<pallet_common::erc::PrecompileResult> {952 call::<T, UniqueRefungibleCall<T>, _, _>(handle, self)953 }954}