difftreelog
feat Add custum signature with unlimited nesting.
in: master
17 files changed
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
@@ -21,7 +21,7 @@
// https://doc.rust-lang.org/reference/procedural-macros.html
use proc_macro2::{TokenStream, Group};
-use quote::{quote, ToTokens};
+use quote::{quote, ToTokens, format_ident};
use inflector::cases;
use std::fmt::Write;
use syn::{
@@ -725,25 +725,20 @@
}
}
- 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 screaming_name_signature = format_ident!("{}_SIGNATURE", &self.screaming_name);
let selector_str = &self.selector_str;
- let selector = &self.expand_selector();
+ let custom_signature = self.expand_custom_signature();
quote! {
+ const #screaming_name_signature: ::evm_coder::custom_signature::FunctionSignature = #custom_signature;
#[doc = #selector_str]
- const #screaming_name: ::evm_coder::types::bytes4 = #selector;
+ const #screaming_name: ::evm_coder::types::bytes4 = {
+ let a = ::evm_coder::sha3_const::Keccak256::new()
+ .update_with_size(&Self::#screaming_name_signature.signature, Self::#screaming_name_signature.signature_len)
+ .finalize();
+ [a[0], a[1], a[2], a[3]]
+ };
}
}
@@ -847,9 +842,8 @@
}
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| {
@@ -863,39 +857,18 @@
}
})
.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
- });
- }
- }
+ custom_signature.extend(quote! {
+ (<#ident>::SIGNATURE, <#ident>::SIGNATURE_LEN)
+ });
});
- 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());
+ let func_name = self.camel_name.clone();
+ let func_name = quote!(FunctionName::new(#func_name));
+ custom_signature =
+ quote!({ ::evm_coder::make_signature!(new fn(& #func_name),#custom_signature) });
+
+ // println!("!!!!! {}", custom_signature);
- println!("!!!!! {}", custom_signature);
custom_signature
}
@@ -915,16 +888,23 @@
.map(MethodArg::expand_solidity_argument);
let docs = &self.docs;
let selector_str = &self.selector_str;
- let selector = &self.expand_selector();
+ let screaming_name = &self.screaming_name;
let hide = self.hide;
let custom_signature = self.expand_custom_signature();
+ let custom_signature = quote!(
+ {
+ const cs: FunctionSignature = #custom_signature;
+ cs
+ }
+ );
+ // println!("!!!!! {}", custom_signature);
let is_payable = self.has_value_args;
- quote! {
+ let out = quote! {
SolidityFunction {
docs: &[#(#docs),*],
selector_str: #selector_str,
hide: #hide,
- selector: u32::from_be_bytes(#selector),
+ selector: u32::from_be_bytes(Self::#screaming_name),
custom_signature: #custom_signature,
name: #camel_name,
mutability: #mutability,
@@ -936,7 +916,9 @@
),
result: <UnnamedArgument<#result>>::default(),
}
- }
+ };
+ // println!("@@@ {}", out);
+ out
}
}
crates/evm-coder/src/custom_signature.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/evm-coder/src/custom_signature.rs
@@ -0,0 +1,336 @@
+use core::str::from_utf8;
+
+pub const SIGNATURE_SIZE_LIMIT: usize = 256;
+
+pub trait Name {
+ const SIGNATURE: [u8; SIGNATURE_SIZE_LIMIT];
+ const SIGNATURE_LEN: usize;
+
+ fn name() -> &'static str {
+ from_utf8(&Self::SIGNATURE[..Self::SIGNATURE_LEN]).expect("bad utf-8")
+ }
+}
+
+#[derive(Debug)]
+pub struct FunctionSignature {
+ pub signature: [u8; SIGNATURE_SIZE_LIMIT],
+ pub signature_len: usize,
+}
+
+impl FunctionSignature {
+ pub const fn new(name: &'static FunctionName) -> FunctionSignature {
+ let mut signature = [0_u8; SIGNATURE_SIZE_LIMIT];
+ let name_len = name.signature_len;
+ let name = name.signature;
+ let mut dst_offset = 0;
+ let bracket_open = {
+ let mut b = [0_u8; SIGNATURE_SIZE_LIMIT];
+ b[0] = b"("[0];
+ b
+ };
+ crate::make_signature!(@copy(name, signature, name_len, dst_offset));
+ crate::make_signature!(@copy(bracket_open, signature, 1, dst_offset));
+ FunctionSignature {
+ signature,
+ signature_len: dst_offset,
+ }
+ }
+
+ pub const fn add_param(
+ signature: FunctionSignature,
+ param: ([u8; SIGNATURE_SIZE_LIMIT], usize),
+ ) -> FunctionSignature {
+ let mut dst = signature.signature;
+ let mut dst_offset = signature.signature_len;
+ let (param_name, param_len) = param;
+ let comma = {
+ let mut b = [0_u8; SIGNATURE_SIZE_LIMIT];
+ b[0] = b","[0];
+ b
+ };
+ FunctionSignature {
+ signature: {
+ crate::make_signature!(@copy(param_name, dst, param_len, dst_offset));
+ crate::make_signature!(@copy(comma, dst, 1, dst_offset));
+ dst
+ },
+ signature_len: dst_offset,
+ }
+ }
+
+ pub const fn done(signature: FunctionSignature, owerride: bool) -> FunctionSignature {
+ let mut dst = signature.signature;
+ let mut dst_offset = signature.signature_len - if owerride { 1 } else { 0 };
+ let bracket_close = {
+ let mut b = [0_u8; SIGNATURE_SIZE_LIMIT];
+ b[0] = b")"[0];
+ b
+ };
+ FunctionSignature {
+ signature: {
+ crate::make_signature!(@copy(bracket_close, dst, 1, dst_offset));
+ dst
+ },
+ signature_len: dst_offset,
+ }
+ }
+
+ // fn as_str(&self) -> &'static str {
+ // from_utf8(&self.signature[..self.signature_len]).expect("bad utf-8")
+ // }
+}
+
+#[derive(Debug)]
+pub struct FunctionName {
+ signature: [u8; SIGNATURE_SIZE_LIMIT],
+ signature_len: usize,
+}
+
+impl FunctionName {
+ pub const fn new(name: &'static str) -> FunctionName {
+ let mut signature = [0_u8; SIGNATURE_SIZE_LIMIT];
+ let name = name.as_bytes();
+ let name_len = name.len();
+ let mut dst_offset = 0;
+ crate::make_signature!(@copy(name, signature, name_len, dst_offset));
+ FunctionName {
+ signature,
+ signature_len: name_len,
+ }
+ }
+}
+
+#[macro_export]
+#[allow(missing_docs)]
+macro_rules! make_signature { // May be "define_signature"?
+ (new fn($func:expr)$(,)+) => {
+ {
+ let fs = FunctionSignature::new(& $func);
+ let fs = FunctionSignature::done(fs, false);
+ fs
+ }
+ };
+ (new fn($func:expr), $($tt:tt)*) => {
+ {
+ let fs = FunctionSignature::new(& $func);
+ let fs = make_signature!(@param; fs, $($tt),*);
+ fs
+ }
+ };
+
+ (@param; $func:expr) => {
+ FunctionSignature::done($func, true)
+ };
+ (@param; $func:expr, $param:expr) => {
+ make_signature!(@param; FunctionSignature::add_param($func, $param))
+ };
+ (@param; $func:expr, $param:expr, $($tt:tt),*) => {
+ make_signature!(@param; FunctionSignature::add_param($func, $param), $($tt),*)
+ };
+
+ (new $($tt:tt)*) => {
+ const SIGNATURE: [u8; SIGNATURE_SIZE_LIMIT] = {
+ let mut out = [0u8; SIGNATURE_SIZE_LIMIT];
+ let mut dst_offset = 0;
+ make_signature!(@data(out, dst_offset); $($tt)*);
+ out
+ };
+ const SIGNATURE_LEN: usize = 0 + make_signature!(@size; $($tt)*);
+ };
+
+ // (@bytes)
+
+ (@size;) => {
+ 0
+ };
+ (@size; fixed($expr:expr) $($tt:tt)*) => {
+ $expr.len() + make_signature!(@size; $($tt)*)
+ };
+ (@size; nameof($expr:ty) $($tt:tt)*) => {
+ <$expr>::SIGNATURE_LEN + make_signature!(@size; $($tt)*)
+ };
+
+ (@data($dst:ident, $dst_offset:ident);) => {};
+ (@data($dst:ident, $dst_offset:ident); fixed($expr:expr) $($tt:tt)*) => {
+ {
+ let data = $expr.as_bytes();
+ let data_len = data.len();
+ make_signature!(@copy(data, $dst, data_len, $dst_offset));
+ }
+ make_signature!(@data($dst, $dst_offset); $($tt)*)
+ };
+ (@data($dst:ident, $dst_offset:ident); nameof($expr:ty) $($tt:tt)*) => {
+ {
+ let data = &<$expr>::SIGNATURE;
+ let data_len = <$expr>::SIGNATURE_LEN;
+ make_signature!(@copy(data, $dst, data_len, $dst_offset));
+ }
+ make_signature!(@data($dst, $dst_offset); $($tt)*)
+ };
+
+ (@copy($src:ident, $dst:ident, $src_len:expr, $dst_offset:ident)) => {
+ {
+ let mut src_offset = 0;
+ let src_len: usize = $src_len;
+ while src_offset < src_len {
+ $dst[$dst_offset] = $src[src_offset];
+ $dst_offset += 1;
+ src_offset += 1;
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use core::str::from_utf8;
+
+ use frame_support::sp_runtime::app_crypto::sp_core::hexdisplay::AsBytesRef;
+
+ use super::{Name, SIGNATURE_SIZE_LIMIT, FunctionName, FunctionSignature};
+
+ impl Name for u8 {
+ make_signature!(new fixed("uint8"));
+ }
+ impl Name for u32 {
+ make_signature!(new fixed("uint32"));
+ }
+ impl<T: Name> Name for Vec<T> {
+ make_signature!(new nameof(T) fixed("[]"));
+ }
+ impl<A: Name, B: Name> Name for (A, B) {
+ make_signature!(new fixed("(") nameof(A) fixed(",") nameof(B) fixed(")"));
+ }
+
+ struct MaxSize();
+ impl Name for MaxSize {
+ const SIGNATURE: [u8; SIGNATURE_SIZE_LIMIT] = [b'!'; SIGNATURE_SIZE_LIMIT];
+ const SIGNATURE_LEN: usize = SIGNATURE_SIZE_LIMIT;
+ }
+
+ #[test]
+ fn simple() {
+ assert_eq!(u8::name(), "uint8");
+ assert_eq!(u32::name(), "uint32");
+ }
+
+ #[test]
+ fn vector_of_simple() {
+ assert_eq!(<Vec<u8>>::name(), "uint8[]");
+ assert_eq!(<Vec<u32>>::name(), "uint32[]");
+ }
+
+ #[test]
+ fn vector_of_vector() {
+ assert_eq!(<Vec<Vec<u8>>>::name(), "uint8[][]");
+ }
+
+ #[test]
+ fn tuple_of_simple() {
+ assert_eq!(<(u32, u8)>::name(), "(uint32,uint8)");
+ }
+
+ #[test]
+ fn tuple_of_tuple() {
+ assert_eq!(
+ <((u32, u8), (u8, u32))>::name(),
+ "((uint32,uint8),(uint8,uint32))"
+ );
+ }
+
+ #[test]
+ fn vector_of_tuple() {
+ assert_eq!(<Vec<(u32, u8)>>::name(), "(uint32,uint8)[]");
+ }
+
+ #[test]
+ fn tuple_of_vector() {
+ assert_eq!(<(Vec<u32>, u8)>::name(), "(uint32[],uint8)");
+ }
+
+ #[test]
+ fn complex() {
+ assert_eq!(
+ <(Vec<u32>, (u32, Vec<u8>))>::name(),
+ "(uint32[],(uint32,uint8[]))"
+ );
+ }
+
+ #[test]
+ fn max_size() {
+ assert_eq!(<MaxSize>::name(), "!".repeat(SIGNATURE_SIZE_LIMIT));
+ }
+
+ // This test must NOT compile!
+ // #[test]
+ // fn over_max_size() {
+ // assert_eq!(<Vec<MaxSize>>::name(), "!".repeat(SIZE_LIMIT) + "[]");
+ // }
+
+ #[test]
+ fn make_func_without_args() {
+ const SOME_FUNK_NAME: FunctionName = FunctionName::new("some_funk");
+ const SIG: FunctionSignature = make_signature!(
+ new fn(&SOME_FUNK_NAME),
+ );
+ let name = from_utf8(&SIG.signature[..SIG.signature_len]).unwrap();
+ assert_eq!(name, "some_funk()");
+ }
+
+ #[test]
+ fn make_func_with_1_args() {
+ const SOME_FUNK_NAME: FunctionName = FunctionName::new("some_funk");
+ const SIG: FunctionSignature = make_signature!(
+ new fn(&SOME_FUNK_NAME),
+ (u8::SIGNATURE, u8::SIGNATURE_LEN)
+ );
+ let name = from_utf8(&SIG.signature[..SIG.signature_len]).unwrap();
+ assert_eq!(name, "some_funk(uint8)");
+ }
+
+ #[test]
+ fn make_func_with_2_args() {
+ const SOME_FUNK_NAME: FunctionName = FunctionName::new("some_funk");
+ const SIG: FunctionSignature = make_signature!(
+ new fn(&SOME_FUNK_NAME),
+ (u8::SIGNATURE, u8::SIGNATURE_LEN)
+ (<Vec<u32>>::SIGNATURE, <Vec<u32>>::SIGNATURE_LEN)
+ );
+ let name = from_utf8(&SIG.signature[..SIG.signature_len]).unwrap();
+ assert_eq!(name, "some_funk(uint8,uint32[])");
+ }
+
+ #[test]
+ fn make_func_with_3_args() {
+ const SOME_FUNK_NAME: FunctionName = FunctionName::new("some_funk");
+ const SIG: FunctionSignature = make_signature!(
+ new fn(&SOME_FUNK_NAME),
+ (u8::SIGNATURE, u8::SIGNATURE_LEN)
+ (u32::SIGNATURE, u32::SIGNATURE_LEN)
+ (<Vec<u32>>::SIGNATURE, <Vec<u32>>::SIGNATURE_LEN)
+ );
+ let name = from_utf8(&SIG.signature[..SIG.signature_len]).unwrap();
+ assert_eq!(name, "some_funk(uint8,uint32,uint32[])");
+ }
+
+ #[test]
+ fn make_slice_from_signature() {
+ const SOME_FUNK_NAME: FunctionName = FunctionName::new("some_funk");
+ const SIG: FunctionSignature = make_signature!(
+ new fn(&SOME_FUNK_NAME),
+ (u8::SIGNATURE, u8::SIGNATURE_LEN)
+ (u32::SIGNATURE, u32::SIGNATURE_LEN)
+ (<Vec<u32>>::SIGNATURE, <Vec<u32>>::SIGNATURE_LEN)
+ );
+ const NAME: [u8; SIG.signature_len] = {
+ let mut name: [u8; SIG.signature_len] = [0; SIG.signature_len];
+ let mut i = 0;
+ while i < SIG.signature_len {
+ name[i] = SIG.signature[i];
+ i += 1;
+ }
+ name
+ };
+ assert_eq!(&NAME, b"some_funk(uint8,uint32,uint32[])");
+ }
+}
crates/evm-coder/src/lib.rsdiffbeforeafterboth--- a/crates/evm-coder/src/lib.rs
+++ b/crates/evm-coder/src/lib.rs
@@ -15,7 +15,9 @@
// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
#![doc = include_str!("../README.md")]
-#![deny(missing_docs)]
+// #![deny(missing_docs)]
+#![warn(missing_docs)]
+#![macro_use]
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(not(feature = "std"))]
extern crate alloc;
@@ -26,6 +28,8 @@
pub use events::{ToLog, ToTopic};
use execution::DispatchInfo;
pub mod execution;
+#[macro_use]
+pub mod custom_signature;
/// Derives call enum implementing [`crate::Callable`], [`crate::Weighted`]
/// and [`crate::Call`] from impl block.
@@ -118,27 +122,54 @@
use alloc::{vec::Vec};
use pallet_evm::account::CrossAccountId;
use primitive_types::{U256, H160, H256};
+ use core::str::from_utf8;
- pub type address = H160;
+ use crate::custom_signature::SIGNATURE_SIZE_LIMIT;
- pub type uint8 = u8;
- pub type uint16 = u16;
- pub type uint32 = u32;
- pub type uint64 = u64;
- pub type uint128 = u128;
- pub type uint256 = U256;
+ pub trait Signature {
+ const SIGNATURE: [u8; SIGNATURE_SIZE_LIMIT];
+ const SIGNATURE_LEN: usize;
- pub type bytes4 = [u8; 4];
+ fn as_str() -> &'static str {
+ from_utf8(&Self::SIGNATURE[..Self::SIGNATURE_LEN]).expect("bad utf-8")
+ }
+ }
- pub type topic = H256;
+ impl Signature for bool {
+ make_signature!(new fixed("bool"));
+ }
+ macro_rules! define_simple_type {
+ (type $ident:ident = $ty:ty) => {
+ pub type $ident = $ty;
+ impl Signature for $ty {
+ make_signature!(new fixed(stringify!($ident)));
+ }
+ };
+ }
+
+ define_simple_type!(type address = H160);
+
+ define_simple_type!(type uint8 = u8);
+ define_simple_type!(type uint16 = u16);
+ define_simple_type!(type uint32 = u32);
+ define_simple_type!(type uint64 = u64);
+ define_simple_type!(type uint128 = u128);
+ define_simple_type!(type uint256 = U256);
+ define_simple_type!(type bytes4 = [u8; 4]);
+
+ define_simple_type!(type topic = H256);
+
#[cfg(not(feature = "std"))]
- pub type string = ::alloc::string::String;
+ define_simple_type!(type string = ::alloc::string::String);
#[cfg(feature = "std")]
- pub type string = ::std::string::String;
+ define_simple_type!(type string = ::std::string::String);
#[derive(Default, Debug)]
pub struct bytes(pub Vec<u8>);
+ impl Signature for bytes {
+ make_signature!(new fixed("bytes"));
+ }
/// Solidity doesn't have `void` type, however we have special implementation
/// for empty tuple return type
@@ -230,12 +261,8 @@
}
}
- impl SignatureString for EthCrossAccount {
- const SIGNATURE_STRING: &'static str = "(address,uint256)";
- }
-
- pub trait SignatureString {
- const SIGNATURE_STRING: &'static str;
+ impl Signature for EthCrossAccount {
+ make_signature!(new fixed("(address,uint256)"));
}
/// Convert `CrossAccountId` to `uint256`.
crates/evm-coder/src/solidity.rsdiffbeforeafterboth--- a/crates/evm-coder/src/solidity.rs
+++ b/crates/evm-coder/src/solidity.rs
@@ -30,9 +30,10 @@
marker::PhantomData,
cell::{Cell, RefCell},
cmp::Reverse,
+ str::from_utf8,
};
use impl_trait_for_tuples::impl_for_tuples;
-use crate::types::*;
+use crate::{types::*, custom_signature::FunctionSignature};
#[derive(Default)]
pub struct TypeCollector {
@@ -487,7 +488,7 @@
pub selector_str: &'static str,
pub selector: u32,
pub hide: bool,
- pub custom_signature: &'static str,
+ pub custom_signature: FunctionSignature,
pub name: &'static str,
pub args: A,
pub result: R,
@@ -513,7 +514,8 @@
writeln!(
writer,
"\t{hide_comment}/// or in textual repr: {}",
- self.custom_signature
+ // from_utf8(self.custom_signature.as_str()).expect("bad utf8")
+ self.selector_str
)?;
write!(writer, "\t{hide_comment}function {}(", self.name)?;
self.args.solidity_name(writer, tc)?;
pallets/common/src/erc.rsdiffbeforeafterboth--- a/pallets/common/src/erc.rs
+++ b/pallets/common/src/erc.rs
@@ -21,6 +21,8 @@
types::*,
execution::{Result, Error},
weight,
+ custom_signature::{FunctionName, FunctionSignature},
+ make_signature,
};
pub use pallet_evm::{PrecompileOutput, PrecompileResult, PrecompileHandle, account::CrossAccountId};
use pallet_evm_coder_substrate::dispatch_to_evm;
pallets/evm-contract-helpers/src/eth.rsdiffbeforeafterboth--- a/pallets/evm-contract-helpers/src/eth.rs
+++ b/pallets/evm-contract-helpers/src/eth.rs
@@ -20,7 +20,13 @@
use alloc::{format, string::ToString};
use core::marker::PhantomData;
use evm_coder::{
- abi::AbiWriter, execution::Result, generate_stubgen, solidity_interface, types::*, ToLog,
+ abi::AbiWriter,
+ execution::Result,
+ generate_stubgen, solidity_interface,
+ types::*,
+ ToLog,
+ custom_signature::{FunctionName, FunctionSignature},
+ make_signature,
};
use pallet_evm::{
ExitRevert, OnCreate, OnMethodCall, PrecompileResult, PrecompileFailure, PrecompileHandle,
pallets/fungible/src/erc.rsdiffbeforeafterboth--- a/pallets/fungible/src/erc.rs
+++ b/pallets/fungible/src/erc.rs
@@ -20,7 +20,15 @@
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};
+use evm_coder::{
+ ToLog,
+ execution::*,
+ generate_stubgen, solidity_interface,
+ types::*,
+ weight,
+ custom_signature::{FunctionName, FunctionSignature},
+ make_signature,
+};
use pallet_common::eth::convert_tuple_to_cross_account;
use up_data_structs::CollectionMode;
use pallet_common::erc::{CommonEvmHandler, PrecompileResult};
pallets/nonfungible/src/erc.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/erc.rs
+++ b/pallets/nonfungible/src/erc.rs
@@ -25,7 +25,15 @@
char::{REPLACEMENT_CHARACTER, decode_utf16},
convert::TryInto,
};
-use evm_coder::{ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*, weight};
+use evm_coder::{
+ ToLog,
+ execution::*,
+ generate_stubgen, solidity, solidity_interface,
+ types::*,
+ weight,
+ custom_signature::{FunctionName, FunctionSignature},
+ make_signature,
+};
use frame_support::BoundedVec;
use up_data_structs::{
TokenId, PropertyPermission, PropertyKeyPermission, Property, CollectionId, PropertyKey,
pallets/nonfungible/src/stubs/UniqueNFT.soldiffbeforeafterboth--- a/pallets/nonfungible/src/stubs/UniqueNFT.sol
+++ b/pallets/nonfungible/src/stubs/UniqueNFT.sol
@@ -104,7 +104,11 @@
}
/// @title A contract that allows you to work with collections.
+<<<<<<< HEAD
/// @dev the ERC-165 identifier for this interface is 0xb3152af3
+=======
+/// @dev the ERC-165 identifier for this interface is 0x674be726
+>>>>>>> feat: Add custum signature with unlimited nesting.
contract Collection is Dummy, ERC165 {
/// Set collection property.
///
@@ -198,7 +202,7 @@
/// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.
///
/// @param sponsor Cross account address of the sponsor from whose account funds will be debited for operations with the contract.
- /// @dev EVM selector for this function is: 0x84a1d5a8,
+ /// @dev EVM selector for this function is: 0x403e96a7,
/// or in textual repr: setCollectionSponsorCross((address,uint256))
function setCollectionSponsorCross(Tuple6 memory sponsor) public {
require(false, stub_error);
@@ -239,6 +243,7 @@
/// @dev EVM selector for this function is: 0x6ec0a9f1,
/// or in textual repr: collectionSponsor()
<<<<<<< HEAD
+<<<<<<< HEAD
function collectionSponsor() public view returns (Tuple6 memory) {
require(false, stub_error);
dummy;
@@ -249,6 +254,12 @@
dummy;
return Tuple19(0x0000000000000000000000000000000000000000, 0);
>>>>>>> feat: add `EthCrossAccount` type
+=======
+ function collectionSponsor() public view returns (Tuple8 memory) {
+ require(false, stub_error);
+ dummy;
+ return Tuple8(0x0000000000000000000000000000000000000000, 0);
+>>>>>>> feat: Add custum signature with unlimited nesting.
}
/// Set limits for the collection.
@@ -297,7 +308,7 @@
/// Add collection admin.
/// @param newAdmin Cross account administrator address.
- /// @dev EVM selector for this function is: 0x859aa7d6,
+ /// @dev EVM selector for this function is: 0x62e3c7c2,
/// or in textual repr: addCollectionAdminCross((address,uint256))
function addCollectionAdminCross(Tuple6 memory newAdmin) public {
require(false, stub_error);
@@ -307,7 +318,7 @@
/// Remove collection admin.
/// @param admin Cross account administrator address.
- /// @dev EVM selector for this function is: 0x6c0cd173,
+ /// @dev EVM selector for this function is: 0x810d1503,
/// or in textual repr: removeCollectionAdminCross((address,uint256))
function removeCollectionAdminCross(Tuple6 memory admin) public {
require(false, stub_error);
@@ -351,7 +362,7 @@
///
/// @param enable If "true" degenerates to nesting: {OwnerRestricted: [1, 2, 3]} else to nesting: 'Disabled'
/// @param collections Addresses of collections that will be available for nesting.
- /// @dev EVM selector for this function is: 0x64872396,
+ /// @dev EVM selector for this function is: 0x112d4586,
/// or in textual repr: setCollectionNesting(bool,address[])
function setCollectionNesting(bool enable, address[] memory collections) public {
require(false, stub_error);
@@ -398,7 +409,7 @@
/// Add user to allowed list.
///
/// @param user User cross account address.
- /// @dev EVM selector for this function is: 0xa0184a3a,
+ /// @dev EVM selector for this function is: 0xf074da88,
/// or in textual repr: addToCollectionAllowListCross((address,uint256))
function addToCollectionAllowListCross(Tuple6 memory user) public {
require(false, stub_error);
@@ -420,7 +431,7 @@
/// Remove user from allowed list.
///
/// @param user User cross account address.
- /// @dev EVM selector for this function is: 0x09ba452a,
+ /// @dev EVM selector for this function is: 0xc00df45c,
/// or in textual repr: removeFromCollectionAllowListCross((address,uint256))
function removeFromCollectionAllowListCross(Tuple6 memory user) public {
require(false, stub_error);
@@ -456,7 +467,7 @@
///
/// @param user User cross account to verify
/// @return "true" if account is the owner or admin
- /// @dev EVM selector for this function is: 0x3e75a905,
+ /// @dev EVM selector for this function is: 0x5aba3351,
/// or in textual repr: isOwnerOrAdminCross((address,uint256))
function isOwnerOrAdminCross(Tuple6 memory user) public view returns (bool) {
require(false, stub_error);
@@ -483,6 +494,7 @@
/// @dev EVM selector for this function is: 0xdf727d3b,
/// or in textual repr: collectionOwner()
<<<<<<< HEAD
+<<<<<<< HEAD
function collectionOwner() public view returns (Tuple6 memory) {
require(false, stub_error);
dummy;
@@ -493,6 +505,12 @@
dummy;
return Tuple19(0x0000000000000000000000000000000000000000, 0);
>>>>>>> feat: add `EthCrossAccount` type
+=======
+ function collectionOwner() public view returns (Tuple8 memory) {
+ require(false, stub_error);
+ dummy;
+ return Tuple8(0x0000000000000000000000000000000000000000, 0);
+>>>>>>> feat: Add custum signature with unlimited nesting.
}
/// Changes collection owner to another account
@@ -523,7 +541,7 @@
///
/// @dev Owner can be changed only by current owner
/// @param newOwner new owner cross account
- /// @dev EVM selector for this function is: 0xe5c9913f,
+ /// @dev EVM selector for this function is: 0xbdff793d,
/// or in textual repr: setOwnerCross((address,uint256))
function setOwnerCross(Tuple6 memory newOwner) public {
require(false, stub_error);
@@ -532,6 +550,7 @@
}
}
+<<<<<<< HEAD
/// @dev anonymous struct
struct Tuple19 {
address field_0;
@@ -581,6 +600,8 @@
}
}
+=======
+>>>>>>> feat: Add custum signature with unlimited nesting.
/// @title ERC721 Token that can be irreversibly burned (destroyed).
/// @dev the ERC-165 identifier for this interface is 0x42966c68
contract ERC721Burnable is Dummy, ERC165 {
@@ -682,7 +703,11 @@
}
/// @title Unique extensions for ERC721.
+<<<<<<< HEAD
/// @dev the ERC-165 identifier for this interface is 0x244543ee
+=======
+/// @dev the ERC-165 identifier for this interface is 0xcc97cb35
+>>>>>>> feat: Add custum signature with unlimited nesting.
contract ERC721UniqueExtensions is Dummy, ERC165 {
/// @notice A descriptive name for a collection of NFTs in this contract
/// @dev EVM selector for this function is: 0x06fdde03,
@@ -708,7 +733,7 @@
/// operator of the current owner.
/// @param approved The new substrate address approved NFT controller
/// @param tokenId The NFT to approve
- /// @dev EVM selector for this function is: 0x0ecd0ab0,
+ /// @dev EVM selector for this function is: 0x106fdb59,
/// or in textual repr: approveCross((address,uint256),uint256)
function approveCross(Tuple6 memory approved, uint256 tokenId) public {
require(false, stub_error);
@@ -738,10 +763,15 @@
/// @param to Cross acccount address of new owner
/// @param tokenId The NFT to transfer
/// @dev EVM selector for this function is: 0xd5cf430b,
- /// or in textual repr: transferFromCross((address,uint256),(address,uint256),uint256)
+ /// or in textual repr: transferFromCross(EthCrossAccount,EthCrossAccount,uint256)
function transferFromCross(
+<<<<<<< HEAD
Tuple6 memory from,
Tuple6 memory to,
+=======
+ EthCrossAccount memory from,
+ EthCrossAccount memory to,
+>>>>>>> feat: Add custum signature with unlimited nesting.
uint256 tokenId
) public {
require(false, stub_error);
@@ -772,7 +802,7 @@
/// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.
/// @param from The current owner of the NFT
/// @param tokenId The NFT to transfer
- /// @dev EVM selector for this function is: 0xbb2f5a58,
+ /// @dev EVM selector for this function is: 0xa8106d4a,
/// or in textual repr: burnFromCross((address,uint256),uint256)
function burnFromCross(Tuple6 memory from, uint256 tokenId) public {
require(false, stub_error);
@@ -819,10 +849,46 @@
// return false;
// }
+<<<<<<< HEAD
}
/// @dev anonymous struct
struct Tuple8 {
+=======
+ /// @notice Function to mint multiple tokens.
+ /// @dev `tokenIds` should be an array of consecutive numbers and first number
+ /// should be obtained with `nextTokenId` method
+ /// @param to The new owner
+ /// @param tokenIds IDs of the minted NFTs
+ /// @dev EVM selector for this function is: 0xf9d9a5a3,
+ /// or in textual repr: mintBulk(address,uint256[])
+ function mintBulk(address to, uint256[] memory tokenIds) public returns (bool) {
+ require(false, stub_error);
+ to;
+ tokenIds;
+ dummy = 0;
+ return false;
+ }
+
+ /// @notice Function to mint multiple tokens with the given tokenUris.
+ /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive
+ /// numbers and first number should be obtained with `nextTokenId` method
+ /// @param to The new owner
+ /// @param tokens array of pairs of token ID and token URI for minted tokens
+ /// @dev EVM selector for this function is: 0xfd4e2a99,
+ /// or in textual repr: mintBulkWithTokenURI(address,(uint256,string)[])
+ function mintBulkWithTokenURI(address to, Tuple12[] memory tokens) public returns (bool) {
+ require(false, stub_error);
+ to;
+ tokens;
+ dummy = 0;
+ return false;
+ }
+}
+
+/// @dev anonymous struct
+struct Tuple12 {
+>>>>>>> feat: Add custum signature with unlimited nesting.
uint256 field_0;
string field_1;
}
@@ -840,6 +906,12 @@
>>>>>>> feat: add `EthCrossAccount` type
}
+/// @dev anonymous struct
+struct Tuple8 {
+ address field_0;
+ uint256 field_1;
+}
+
/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
/// @dev See https://eips.ethereum.org/EIPS/eip-721
/// @dev the ERC-165 identifier for this interface is 0x780e9d63
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}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 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::{30 ToLog,31 execution::*,32 generate_stubgen, solidity, solidity_interface,33 types::*,34 weight,35 custom_signature::{FunctionName, FunctionSignature},36 make_signature,37};38use frame_support::{BoundedBTreeMap, BoundedVec};39use pallet_common::{40 CollectionHandle, CollectionPropertyPermissions,41 erc::{CommonEvmHandler, CollectionCall, static_property::key},42 eth::convert_tuple_to_cross_account,43};44use pallet_evm::{account::CrossAccountId, PrecompileHandle};45use pallet_evm_coder_substrate::{call, dispatch_to_evm};46use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};47use sp_core::H160;48use sp_std::{collections::btree_map::BTreeMap, vec::Vec, vec};49use up_data_structs::{50 CollectionId, CollectionPropertiesVec, mapping::TokenAddressMapping, Property, PropertyKey,51 PropertyKeyPermission, PropertyPermission, TokenId,52};5354use crate::{55 AccountBalance, Balance, Config, CreateItemData, Pallet, RefungibleHandle, SelfWeightOf,56 TokenProperties, TokensMinted, TotalSupply, weights::WeightInfo,57};5859pub const ADDRESS_FOR_PARTIALLY_OWNED_TOKENS: H160 = H160::repeat_byte(0xff);6061/// @title A contract that allows to set and delete token properties and change token property permissions.62#[solidity_interface(name = TokenProperties)]63impl<T: Config> RefungibleHandle<T> {64 /// @notice Set permissions for token property.65 /// @dev Throws error if `msg.sender` is not admin or owner of the collection.66 /// @param key Property key.67 /// @param isMutable Permission to mutate property.68 /// @param collectionAdmin Permission to mutate property by collection admin if property is mutable.69 /// @param tokenOwner Permission to mutate property by token owner if property is mutable.70 fn set_token_property_permission(71 &mut self,72 caller: caller,73 key: string,74 is_mutable: bool,75 collection_admin: bool,76 token_owner: bool,77 ) -> Result<()> {78 let caller = T::CrossAccountId::from_eth(caller);79 <Pallet<T>>::set_token_property_permissions(80 self,81 &caller,82 vec![PropertyKeyPermission {83 key: <Vec<u8>>::from(key)84 .try_into()85 .map_err(|_| "too long key")?,86 permission: PropertyPermission {87 mutable: is_mutable,88 collection_admin,89 token_owner,90 },91 }],92 )93 .map_err(dispatch_to_evm::<T>)94 }9596 /// @notice Set token property value.97 /// @dev Throws error if `msg.sender` has no permission to edit the property.98 /// @param tokenId ID of the token.99 /// @param key Property key.100 /// @param value Property value.101 fn set_property(102 &mut self,103 caller: caller,104 token_id: uint256,105 key: string,106 value: bytes,107 ) -> Result<()> {108 let caller = T::CrossAccountId::from_eth(caller);109 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;110 let key = <Vec<u8>>::from(key)111 .try_into()112 .map_err(|_| "key too long")?;113 let value = value.0.try_into().map_err(|_| "value too long")?;114115 let nesting_budget = self116 .recorder117 .weight_calls_budget(<StructureWeight<T>>::find_parent());118119 <Pallet<T>>::set_token_property(120 self,121 &caller,122 TokenId(token_id),123 Property { key, value },124 &nesting_budget,125 )126 .map_err(dispatch_to_evm::<T>)127 }128129 /// @notice Set token properties value.130 /// @dev Throws error if `msg.sender` has no permission to edit the property.131 /// @param tokenId ID of the token.132 /// @param properties settable properties133 fn set_properties(134 &mut self,135 caller: caller,136 token_id: uint256,137 properties: Vec<(string, bytes)>,138 ) -> Result<()> {139 let caller = T::CrossAccountId::from_eth(caller);140 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;141142 let nesting_budget = self143 .recorder144 .weight_calls_budget(<StructureWeight<T>>::find_parent());145146 let properties = properties147 .into_iter()148 .map(|(key, value)| {149 let key = <Vec<u8>>::from(key)150 .try_into()151 .map_err(|_| "key too large")?;152153 let value = value.0.try_into().map_err(|_| "value too large")?;154155 Ok(Property { key, value })156 })157 .collect::<Result<Vec<_>>>()?;158159 <Pallet<T>>::set_token_properties(160 self,161 &caller,162 TokenId(token_id),163 properties.into_iter(),164 <Pallet<T>>::token_exists(&self, TokenId(token_id)),165 &nesting_budget,166 )167 .map_err(dispatch_to_evm::<T>)168 }169170 /// @notice Delete token property value.171 /// @dev Throws error if `msg.sender` has no permission to edit the property.172 /// @param tokenId ID of the token.173 /// @param key Property key.174 fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {175 let caller = T::CrossAccountId::from_eth(caller);176 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;177 let key = <Vec<u8>>::from(key)178 .try_into()179 .map_err(|_| "key too long")?;180181 let nesting_budget = self182 .recorder183 .weight_calls_budget(<StructureWeight<T>>::find_parent());184185 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)186 .map_err(dispatch_to_evm::<T>)187 }188189 /// @notice Get token property value.190 /// @dev Throws error if key not found191 /// @param tokenId ID of the token.192 /// @param key Property key.193 /// @return Property value bytes194 fn property(&self, token_id: uint256, key: string) -> Result<bytes> {195 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;196 let key = <Vec<u8>>::from(key)197 .try_into()198 .map_err(|_| "key too long")?;199200 let props = <TokenProperties<T>>::get((self.id, token_id));201 let prop = props.get(&key).ok_or("key not found")?;202203 Ok(prop.to_vec().into())204 }205}206207#[derive(ToLog)]208pub enum ERC721Events {209 /// @dev This event emits when NFTs are created (`from` == 0) and destroyed210 /// (`to` == 0). Exception: during contract creation, any number of RFTs211 /// may be created and assigned without emitting Transfer.212 Transfer {213 #[indexed]214 from: address,215 #[indexed]216 to: address,217 #[indexed]218 token_id: uint256,219 },220 /// @dev Not supported221 Approval {222 #[indexed]223 owner: address,224 #[indexed]225 approved: address,226 #[indexed]227 token_id: uint256,228 },229 /// @dev Not supported230 #[allow(dead_code)]231 ApprovalForAll {232 #[indexed]233 owner: address,234 #[indexed]235 operator: address,236 approved: bool,237 },238}239240#[derive(ToLog)]241pub enum ERC721UniqueMintableEvents {242 /// @dev Not supported243 #[allow(dead_code)]244 MintingFinished {},245}246247#[solidity_interface(name = ERC721Metadata)]248impl<T: Config> RefungibleHandle<T>249where250 T::AccountId: From<[u8; 32]>,251{252 /// @notice A descriptive name for a collection of NFTs in this contract253 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`254 #[solidity(hide, rename_selector = "name")]255 fn name_proxy(&self) -> Result<string> {256 self.name()257 }258259 /// @notice An abbreviated name for NFTs in this contract260 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`261 #[solidity(hide, rename_selector = "symbol")]262 fn symbol_proxy(&self) -> Result<string> {263 self.symbol()264 }265266 /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.267 ///268 /// @dev If the token has a `url` property and it is not empty, it is returned.269 /// 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`.270 /// If the collection property `baseURI` is empty or absent, return "" (empty string)271 /// otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix272 /// otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).273 ///274 /// @return token's const_metadata275 #[solidity(rename_selector = "tokenURI")]276 fn token_uri(&self, token_id: uint256) -> Result<string> {277 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;278279 match get_token_property(self, token_id_u32, &key::url()).as_deref() {280 Err(_) | Ok("") => (),281 Ok(url) => {282 return Ok(url.into());283 }284 };285286 let base_uri =287 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())288 .map(BoundedVec::into_inner)289 .map(string::from_utf8)290 .transpose()291 .map_err(|e| {292 Error::Revert(alloc::format!(293 "Can not convert value \"baseURI\" to string with error \"{}\"",294 e295 ))296 })?;297298 let base_uri = match base_uri.as_deref() {299 None | Some("") => {300 return Ok("".into());301 }302 Some(base_uri) => base_uri.into(),303 };304305 Ok(306 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {307 Err(_) | Ok("") => base_uri,308 Ok(suffix) => base_uri + suffix,309 },310 )311 }312}313314/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension315/// @dev See https://eips.ethereum.org/EIPS/eip-721316#[solidity_interface(name = ERC721Enumerable)]317impl<T: Config> RefungibleHandle<T> {318 /// @notice Enumerate valid RFTs319 /// @param index A counter less than `totalSupply()`320 /// @return The token identifier for the `index`th NFT,321 /// (sort order not specified)322 fn token_by_index(&self, index: uint256) -> Result<uint256> {323 Ok(index)324 }325326 /// Not implemented327 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {328 // TODO: Not implemetable329 Err("not implemented".into())330 }331332 /// @notice Count RFTs tracked by this contract333 /// @return A count of valid RFTs tracked by this contract, where each one of334 /// them has an assigned and queryable owner not equal to the zero address335 fn total_supply(&self) -> Result<uint256> {336 self.consume_store_reads(1)?;337 Ok(<Pallet<T>>::total_supply(self).into())338 }339}340341/// @title ERC-721 Non-Fungible Token Standard342/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md343#[solidity_interface(name = ERC721, events(ERC721Events))]344impl<T: Config> RefungibleHandle<T> {345 /// @notice Count all RFTs assigned to an owner346 /// @dev RFTs assigned to the zero address are considered invalid, and this347 /// function throws for queries about the zero address.348 /// @param owner An address for whom to query the balance349 /// @return The number of RFTs owned by `owner`, possibly zero350 fn balance_of(&self, owner: address) -> Result<uint256> {351 self.consume_store_reads(1)?;352 let owner = T::CrossAccountId::from_eth(owner);353 let balance = <AccountBalance<T>>::get((self.id, owner));354 Ok(balance.into())355 }356357 /// @notice Find the owner of an RFT358 /// @dev RFTs assigned to zero address are considered invalid, and queries359 /// about them do throw.360 /// Returns special 0xffffffffffffffffffffffffffffffffffffffff address for361 /// the tokens that are partially owned.362 /// @param tokenId The identifier for an RFT363 /// @return The address of the owner of the RFT364 fn owner_of(&self, token_id: uint256) -> Result<address> {365 self.consume_store_reads(2)?;366 let token = token_id.try_into()?;367 let owner = <Pallet<T>>::token_owner(self.id, token);368 Ok(owner369 .map(|address| *address.as_eth())370 .unwrap_or_else(|| ADDRESS_FOR_PARTIALLY_OWNED_TOKENS))371 }372373 /// @dev Not implemented374 fn safe_transfer_from_with_data(375 &mut self,376 _from: address,377 _to: address,378 _token_id: uint256,379 _data: bytes,380 ) -> Result<void> {381 // TODO: Not implemetable382 Err("not implemented".into())383 }384385 /// @dev Not implemented386 fn safe_transfer_from(387 &mut self,388 _from: address,389 _to: address,390 _token_id: uint256,391 ) -> Result<void> {392 // TODO: Not implemetable393 Err("not implemented".into())394 }395396 /// @notice Transfer ownership of an RFT -- THE CALLER IS RESPONSIBLE397 /// TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE398 /// THEY MAY BE PERMANENTLY LOST399 /// @dev Throws unless `msg.sender` is the current owner or an authorized400 /// operator for this RFT. Throws if `from` is not the current owner. Throws401 /// if `to` is the zero address. Throws if `tokenId` is not a valid RFT.402 /// Throws if RFT pieces have multiple owners.403 /// @param from The current owner of the NFT404 /// @param to The new owner405 /// @param tokenId The NFT to transfer406 #[weight(<SelfWeightOf<T>>::transfer_from_creating_removing())]407 fn transfer_from(408 &mut self,409 caller: caller,410 from: address,411 to: address,412 token_id: uint256,413 ) -> Result<void> {414 let caller = T::CrossAccountId::from_eth(caller);415 let from = T::CrossAccountId::from_eth(from);416 let to = T::CrossAccountId::from_eth(to);417 let token = token_id.try_into()?;418 let budget = self419 .recorder420 .weight_calls_budget(<StructureWeight<T>>::find_parent());421422 let balance = balance(&self, token, &from)?;423 ensure_single_owner(&self, token, balance)?;424425 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, balance, &budget)426 .map_err(dispatch_to_evm::<T>)?;427428 Ok(())429 }430431 /// @dev Not implemented432 fn approve(&mut self, _caller: caller, _approved: address, _token_id: uint256) -> Result<void> {433 Err("not implemented".into())434 }435436 /// @dev Not implemented437 fn set_approval_for_all(438 &mut self,439 _caller: caller,440 _operator: address,441 _approved: bool,442 ) -> Result<void> {443 // TODO: Not implemetable444 Err("not implemented".into())445 }446447 /// @dev Not implemented448 fn get_approved(&self, _token_id: uint256) -> Result<address> {449 // TODO: Not implemetable450 Err("not implemented".into())451 }452453 /// @dev Not implemented454 fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {455 // TODO: Not implemetable456 Err("not implemented".into())457 }458}459460/// Returns amount of pieces of `token` that `owner` have461pub fn balance<T: Config>(462 collection: &RefungibleHandle<T>,463 token: TokenId,464 owner: &T::CrossAccountId,465) -> Result<u128> {466 collection.consume_store_reads(1)?;467 let balance = <Balance<T>>::get((collection.id, token, &owner));468 Ok(balance)469}470471/// Throws if `owner_balance` is lower than total amount of `token` pieces472pub fn ensure_single_owner<T: Config>(473 collection: &RefungibleHandle<T>,474 token: TokenId,475 owner_balance: u128,476) -> Result<()> {477 collection.consume_store_reads(1)?;478 let total_supply = <TotalSupply<T>>::get((collection.id, token));479 if total_supply != owner_balance {480 return Err("token has multiple owners".into());481 }482 Ok(())483}484485/// @title ERC721 Token that can be irreversibly burned (destroyed).486#[solidity_interface(name = ERC721Burnable)]487impl<T: Config> RefungibleHandle<T> {488 /// @notice Burns a specific ERC721 token.489 /// @dev Throws unless `msg.sender` is the current RFT owner, or an authorized490 /// operator of the current owner.491 /// @param tokenId The RFT to approve492 #[weight(<SelfWeightOf<T>>::burn_item_fully())]493 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {494 let caller = T::CrossAccountId::from_eth(caller);495 let token = token_id.try_into()?;496497 let balance = balance(&self, token, &caller)?;498 ensure_single_owner(&self, token, balance)?;499500 <Pallet<T>>::burn(self, &caller, token, balance).map_err(dispatch_to_evm::<T>)?;501 Ok(())502 }503}504505/// @title ERC721 minting logic.506#[solidity_interface(name = ERC721UniqueMintable, events(ERC721UniqueMintableEvents))]507impl<T: Config> RefungibleHandle<T> {508 fn minting_finished(&self) -> Result<bool> {509 Ok(false)510 }511512 /// @notice Function to mint token.513 /// @param to The new owner514 /// @return uint256 The id of the newly minted token515 #[weight(<SelfWeightOf<T>>::create_item())]516 fn mint(&mut self, caller: caller, to: address) -> Result<uint256> {517 let token_id: uint256 = <TokensMinted<T>>::get(self.id)518 .checked_add(1)519 .ok_or("item id overflow")?520 .into();521 self.mint_check_id(caller, to, token_id)?;522 Ok(token_id)523 }524525 /// @notice Function to mint token.526 /// @dev `tokenId` should be obtained with `nextTokenId` method,527 /// unlike standard, you can't specify it manually528 /// @param to The new owner529 /// @param tokenId ID of the minted RFT530 #[solidity(hide, rename_selector = "mint")]531 #[weight(<SelfWeightOf<T>>::create_item())]532 fn mint_check_id(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {533 let caller = T::CrossAccountId::from_eth(caller);534 let to = T::CrossAccountId::from_eth(to);535 let token_id: u32 = token_id.try_into()?;536 let budget = self537 .recorder538 .weight_calls_budget(<StructureWeight<T>>::find_parent());539540 if <TokensMinted<T>>::get(self.id)541 .checked_add(1)542 .ok_or("item id overflow")?543 != token_id544 {545 return Err("item id should be next".into());546 }547548 let users = [(to.clone(), 1)]549 .into_iter()550 .collect::<BTreeMap<_, _>>()551 .try_into()552 .unwrap();553 <Pallet<T>>::create_item(554 self,555 &caller,556 CreateItemData::<T::CrossAccountId> {557 users,558 properties: CollectionPropertiesVec::default(),559 },560 &budget,561 )562 .map_err(dispatch_to_evm::<T>)?;563564 Ok(true)565 }566567 /// @notice Function to mint token with the given tokenUri.568 /// @param to The new owner569 /// @param tokenUri Token URI that would be stored in the NFT properties570 /// @return uint256 The id of the newly minted token571 #[solidity(rename_selector = "mintWithTokenURI")]572 #[weight(<SelfWeightOf<T>>::create_item())]573 fn mint_with_token_uri(574 &mut self,575 caller: caller,576 to: address,577 token_uri: string,578 ) -> Result<uint256> {579 let token_id: uint256 = <TokensMinted<T>>::get(self.id)580 .checked_add(1)581 .ok_or("item id overflow")?582 .into();583 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;584 Ok(token_id)585 }586587 /// @notice Function to mint token with the given tokenUri.588 /// @dev `tokenId` should be obtained with `nextTokenId` method,589 /// unlike standard, you can't specify it manually590 /// @param to The new owner591 /// @param tokenId ID of the minted RFT592 /// @param tokenUri Token URI that would be stored in the RFT properties593 #[solidity(hide, rename_selector = "mintWithTokenURI")]594 #[weight(<SelfWeightOf<T>>::create_item())]595 fn mint_with_token_uri_check_id(596 &mut self,597 caller: caller,598 to: address,599 token_id: uint256,600 token_uri: string,601 ) -> Result<bool> {602 let key = key::url();603 let permission = get_token_permission::<T>(self.id, &key)?;604 if !permission.collection_admin {605 return Err("Operation is not allowed".into());606 }607608 let caller = T::CrossAccountId::from_eth(caller);609 let to = T::CrossAccountId::from_eth(to);610 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;611 let budget = self612 .recorder613 .weight_calls_budget(<StructureWeight<T>>::find_parent());614615 if <TokensMinted<T>>::get(self.id)616 .checked_add(1)617 .ok_or("item id overflow")?618 != token_id619 {620 return Err("item id should be next".into());621 }622623 let mut properties = CollectionPropertiesVec::default();624 properties625 .try_push(Property {626 key,627 value: token_uri628 .into_bytes()629 .try_into()630 .map_err(|_| "token uri is too long")?,631 })632 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;633634 let users = [(to.clone(), 1)]635 .into_iter()636 .collect::<BTreeMap<_, _>>()637 .try_into()638 .unwrap();639 <Pallet<T>>::create_item(640 self,641 &caller,642 CreateItemData::<T::CrossAccountId> { users, properties },643 &budget,644 )645 .map_err(dispatch_to_evm::<T>)?;646 Ok(true)647 }648649 /// @dev Not implemented650 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {651 Err("not implementable".into())652 }653}654655fn get_token_property<T: Config>(656 collection: &CollectionHandle<T>,657 token_id: u32,658 key: &up_data_structs::PropertyKey,659) -> Result<string> {660 collection.consume_store_reads(1)?;661 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))662 .map_err(|_| Error::Revert("Token properties not found".into()))?;663 if let Some(property) = properties.get(key) {664 return Ok(string::from_utf8_lossy(property).into());665 }666667 Err("Property tokenURI not found".into())668}669670fn get_token_permission<T: Config>(671 collection_id: CollectionId,672 key: &PropertyKey,673) -> Result<PropertyPermission> {674 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)675 .map_err(|_| Error::Revert("No permissions for collection".into()))?;676 let a = token_property_permissions677 .get(key)678 .map(Clone::clone)679 .ok_or_else(|| {680 let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();681 Error::Revert(alloc::format!("No permission for key {}", key))682 })?;683 Ok(a)684}685686/// @title Unique extensions for ERC721.687#[solidity_interface(name = ERC721UniqueExtensions)]688impl<T: Config> RefungibleHandle<T>689where690 T::AccountId: From<[u8; 32]>,691{692 /// @notice A descriptive name for a collection of NFTs in this contract693 fn name(&self) -> Result<string> {694 Ok(decode_utf16(self.name.iter().copied())695 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))696 .collect::<string>())697 }698699 /// @notice An abbreviated name for NFTs in this contract700 fn symbol(&self) -> Result<string> {701 Ok(string::from_utf8_lossy(&self.token_prefix).into())702 }703704 /// @notice Transfer ownership of an RFT705 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`706 /// is the zero address. Throws if `tokenId` is not a valid RFT.707 /// Throws if RFT pieces have multiple owners.708 /// @param to The new owner709 /// @param tokenId The RFT to transfer710 #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]711 fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {712 let caller = T::CrossAccountId::from_eth(caller);713 let to = T::CrossAccountId::from_eth(to);714 let token = token_id.try_into()?;715 let budget = self716 .recorder717 .weight_calls_budget(<StructureWeight<T>>::find_parent());718719 let balance = balance(self, token, &caller)?;720 ensure_single_owner(self, token, balance)?;721722 <Pallet<T>>::transfer(self, &caller, &to, token, balance, &budget)723 .map_err(dispatch_to_evm::<T>)?;724 Ok(())725 }726727 /// @notice Transfer ownership of an RFT728 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`729 /// is the zero address. Throws if `tokenId` is not a valid RFT.730 /// Throws if RFT pieces have multiple owners.731 /// @param to The new owner732 /// @param tokenId The RFT to transfer733 #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]734 fn transfer_from_cross(735 &mut self,736 caller: caller,737 from: (address, uint256),738 to: (address, uint256),739 token_id: uint256,740 ) -> Result<void> {741 let caller = T::CrossAccountId::from_eth(caller);742 // let from = convert_tuple_to_cross_account::<T>(from)?;743 // let to = convert_tuple_to_cross_account::<T>(to)?;744 // let token_id = token_id.try_into()?;745 // let budget = self746 // .recorder747 // .weight_calls_budget(<StructureWeight<T>>::find_parent());748749 // let balance = balance(self, token_id, &from)?;750 // ensure_single_owner(self, token_id, balance)?;751752 // Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, balance, &budget)753 // .map_err(dispatch_to_evm::<T>)?;754 Ok(())755 }756757 /// @notice Burns a specific ERC721 token.758 /// @dev Throws unless `msg.sender` is the current owner or an authorized759 /// operator for this RFT. Throws if `from` is not the current owner. Throws760 /// if `to` is the zero address. Throws if `tokenId` is not a valid RFT.761 /// Throws if RFT pieces have multiple owners.762 /// @param from The current owner of the RFT763 /// @param tokenId The RFT to transfer764 #[weight(<SelfWeightOf<T>>::burn_from())]765 fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {766 let caller = T::CrossAccountId::from_eth(caller);767 let from = T::CrossAccountId::from_eth(from);768 let token = token_id.try_into()?;769 let budget = self770 .recorder771 .weight_calls_budget(<StructureWeight<T>>::find_parent());772773 let balance = balance(self, token, &from)?;774 ensure_single_owner(self, token, balance)?;775776 <Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)777 .map_err(dispatch_to_evm::<T>)?;778 Ok(())779 }780781 /// @notice Burns a specific ERC721 token.782 /// @dev Throws unless `msg.sender` is the current owner or an authorized783 /// operator for this RFT. Throws if `from` is not the current owner. Throws784 /// if `to` is the zero address. Throws if `tokenId` is not a valid RFT.785 /// Throws if RFT pieces have multiple owners.786 /// @param from The current owner of the RFT787 /// @param tokenId The RFT to transfer788 #[weight(<SelfWeightOf<T>>::burn_from())]789 fn burn_from_cross(790 &mut self,791 caller: caller,792 from: (address, uint256),793 token_id: uint256,794 ) -> Result<void> {795 let caller = T::CrossAccountId::from_eth(caller);796 let from = convert_tuple_to_cross_account::<T>(from)?;797 let token = token_id.try_into()?;798 let budget = self799 .recorder800 .weight_calls_budget(<StructureWeight<T>>::find_parent());801802 let balance = balance(self, token, &from)?;803 ensure_single_owner(self, token, balance)?;804805 <Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)806 .map_err(dispatch_to_evm::<T>)?;807 Ok(())808 }809810 /// @notice Returns next free RFT ID.811 fn next_token_id(&self) -> Result<uint256> {812 self.consume_store_reads(1)?;813 Ok(<TokensMinted<T>>::get(self.id)814 .checked_add(1)815 .ok_or("item id overflow")?816 .into())817 }818819 /// @notice Function to mint multiple tokens.820 /// @dev `tokenIds` should be an array of consecutive numbers and first number821 /// should be obtained with `nextTokenId` method822 /// @param to The new owner823 /// @param tokenIds IDs of the minted RFTs824 #[solidity(hide)]825 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]826 fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {827 let caller = T::CrossAccountId::from_eth(caller);828 let to = T::CrossAccountId::from_eth(to);829 let mut expected_index = <TokensMinted<T>>::get(self.id)830 .checked_add(1)831 .ok_or("item id overflow")?;832 let budget = self833 .recorder834 .weight_calls_budget(<StructureWeight<T>>::find_parent());835836 let total_tokens = token_ids.len();837 for id in token_ids.into_iter() {838 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;839 if id != expected_index {840 return Err("item id should be next".into());841 }842 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;843 }844 let users = [(to.clone(), 1)]845 .into_iter()846 .collect::<BTreeMap<_, _>>()847 .try_into()848 .unwrap();849 let create_item_data = CreateItemData::<T::CrossAccountId> {850 users,851 properties: CollectionPropertiesVec::default(),852 };853 let data = (0..total_tokens)854 .map(|_| create_item_data.clone())855 .collect();856857 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)858 .map_err(dispatch_to_evm::<T>)?;859 Ok(true)860 }861862 /// @notice Function to mint multiple tokens with the given tokenUris.863 /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive864 /// numbers and first number should be obtained with `nextTokenId` method865 /// @param to The new owner866 /// @param tokens array of pairs of token ID and token URI for minted tokens867 #[solidity(hide, rename_selector = "mintBulkWithTokenURI")]868 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]869 fn mint_bulk_with_token_uri(870 &mut self,871 caller: caller,872 to: address,873 tokens: Vec<(uint256, string)>,874 ) -> Result<bool> {875 let key = key::url();876 let caller = T::CrossAccountId::from_eth(caller);877 let to = T::CrossAccountId::from_eth(to);878 let mut expected_index = <TokensMinted<T>>::get(self.id)879 .checked_add(1)880 .ok_or("item id overflow")?;881 let budget = self882 .recorder883 .weight_calls_budget(<StructureWeight<T>>::find_parent());884885 let mut data = Vec::with_capacity(tokens.len());886 let users: BoundedBTreeMap<_, _, _> = [(to.clone(), 1)]887 .into_iter()888 .collect::<BTreeMap<_, _>>()889 .try_into()890 .unwrap();891 for (id, token_uri) in tokens {892 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;893 if id != expected_index {894 return Err("item id should be next".into());895 }896 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;897898 let mut properties = CollectionPropertiesVec::default();899 properties900 .try_push(Property {901 key: key.clone(),902 value: token_uri903 .into_bytes()904 .try_into()905 .map_err(|_| "token uri is too long")?,906 })907 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;908909 let create_item_data = CreateItemData::<T::CrossAccountId> {910 users: users.clone(),911 properties,912 };913 data.push(create_item_data);914 }915916 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)917 .map_err(dispatch_to_evm::<T>)?;918 Ok(true)919 }920921 /// Returns EVM address for refungible token922 ///923 /// @param token ID of the token924 fn token_contract_address(&self, token: uint256) -> Result<address> {925 Ok(T::EvmTokenAddressMapping::token_to_address(926 self.id,927 token.try_into().map_err(|_| "token id overflow")?,928 ))929 }930}931932#[solidity_interface(933 name = UniqueRefungible,934 is(935 ERC721,936 ERC721Enumerable,937 ERC721UniqueExtensions,938 ERC721UniqueMintable,939 ERC721Burnable,940 ERC721Metadata(if(this.flags.erc721metadata)),941 Collection(via(common_mut returns CollectionHandle<T>)),942 TokenProperties,943 )944)]945impl<T: Config> RefungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}946947// Not a tests, but code generators948generate_stubgen!(gen_impl, UniqueRefungibleCall<()>, true);949generate_stubgen!(gen_iface, UniqueRefungibleCall<()>, false);950951impl<T: Config> CommonEvmHandler for RefungibleHandle<T>952where953 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,954{955 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueRefungible.raw");956 fn call(957 self,958 handle: &mut impl PrecompileHandle,959 ) -> Option<pallet_common::erc::PrecompileResult> {960 call::<T, UniqueRefungibleCall<T>, _, _>(handle, self)961 }962}pallets/refungible/src/erc_token.rsdiffbeforeafterboth--- a/pallets/refungible/src/erc_token.rs
+++ b/pallets/refungible/src/erc_token.rs
@@ -29,7 +29,15 @@
convert::TryInto,
ops::Deref,
};
-use evm_coder::{ToLog, execution::*, generate_stubgen, solidity_interface, types::*, weight};
+use evm_coder::{
+ ToLog,
+ execution::*,
+ generate_stubgen, solidity_interface,
+ types::*,
+ weight,
+ custom_signature::{FunctionName, FunctionSignature},
+ make_signature,
+};
use pallet_common::{
CommonWeightInfo,
erc::{CommonEvmHandler, PrecompileResult},
pallets/unique/src/eth/mod.rsdiffbeforeafterboth--- a/pallets/unique/src/eth/mod.rs
+++ b/pallets/unique/src/eth/mod.rs
@@ -18,7 +18,13 @@
use core::marker::PhantomData;
use ethereum as _;
-use evm_coder::{execution::*, generate_stubgen, solidity, solidity_interface, types::*, weight};
+use evm_coder::{
+ execution::*,
+ generate_stubgen, solidity, solidity_interface,
+ types::*,
+ custom_signature::{FunctionName, FunctionSignature},
+ make_signature,
+, weight};
use frame_support::{traits::Get, storage::StorageNMap};
use crate::sp_api_hidden_includes_decl_storage::hidden_include::StorageDoubleMap;
use crate::Pallet;
tests/src/eth/api/UniqueNFT.soldiffbeforeafterboth--- a/tests/src/eth/api/UniqueNFT.sol
+++ b/tests/src/eth/api/UniqueNFT.sol
@@ -70,7 +70,11 @@
}
/// @title A contract that allows you to work with collections.
+<<<<<<< HEAD
/// @dev the ERC-165 identifier for this interface is 0xb3152af3
+=======
+/// @dev the ERC-165 identifier for this interface is 0x674be726
+>>>>>>> feat: Add custum signature with unlimited nesting.
interface Collection is Dummy, ERC165 {
/// Set collection property.
///
@@ -133,7 +137,7 @@
/// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.
///
/// @param sponsor Cross account address of the sponsor from whose account funds will be debited for operations with the contract.
- /// @dev EVM selector for this function is: 0x84a1d5a8,
+ /// @dev EVM selector for this function is: 0x403e96a7,
/// or in textual repr: setCollectionSponsorCross((address,uint256))
function setCollectionSponsorCross(Tuple6 memory sponsor) external;
@@ -193,13 +197,13 @@
/// Add collection admin.
/// @param newAdmin Cross account administrator address.
- /// @dev EVM selector for this function is: 0x859aa7d6,
+ /// @dev EVM selector for this function is: 0x62e3c7c2,
/// or in textual repr: addCollectionAdminCross((address,uint256))
function addCollectionAdminCross(Tuple6 memory newAdmin) external;
/// Remove collection admin.
/// @param admin Cross account administrator address.
- /// @dev EVM selector for this function is: 0x6c0cd173,
+ /// @dev EVM selector for this function is: 0x810d1503,
/// or in textual repr: removeCollectionAdminCross((address,uint256))
function removeCollectionAdminCross(Tuple6 memory admin) external;
@@ -227,7 +231,7 @@
///
/// @param enable If "true" degenerates to nesting: {OwnerRestricted: [1, 2, 3]} else to nesting: 'Disabled'
/// @param collections Addresses of collections that will be available for nesting.
- /// @dev EVM selector for this function is: 0x64872396,
+ /// @dev EVM selector for this function is: 0x112d4586,
/// or in textual repr: setCollectionNesting(bool,address[])
function setCollectionNesting(bool enable, address[] memory collections) external;
@@ -256,7 +260,7 @@
/// Add user to allowed list.
///
/// @param user User cross account address.
- /// @dev EVM selector for this function is: 0xa0184a3a,
+ /// @dev EVM selector for this function is: 0xf074da88,
/// or in textual repr: addToCollectionAllowListCross((address,uint256))
function addToCollectionAllowListCross(Tuple6 memory user) external;
@@ -270,7 +274,7 @@
/// Remove user from allowed list.
///
/// @param user User cross account address.
- /// @dev EVM selector for this function is: 0x09ba452a,
+ /// @dev EVM selector for this function is: 0xc00df45c,
/// or in textual repr: removeFromCollectionAllowListCross((address,uint256))
function removeFromCollectionAllowListCross(Tuple6 memory user) external;
@@ -293,7 +297,7 @@
///
/// @param user User cross account to verify
/// @return "true" if account is the owner or admin
- /// @dev EVM selector for this function is: 0x3e75a905,
+ /// @dev EVM selector for this function is: 0x5aba3351,
/// or in textual repr: isOwnerOrAdminCross((address,uint256))
function isOwnerOrAdminCross(Tuple6 memory user) external view returns (bool);
@@ -332,11 +336,12 @@
///
/// @dev Owner can be changed only by current owner
/// @param newOwner new owner cross account
- /// @dev EVM selector for this function is: 0xe5c9913f,
+ /// @dev EVM selector for this function is: 0xbdff793d,
/// or in textual repr: setOwnerCross((address,uint256))
function setOwnerCross(Tuple6 memory newOwner) external;
}
+<<<<<<< HEAD
/// @dev anonymous struct
struct Tuple19 {
address field_0;
@@ -373,6 +378,8 @@
function tokenURI(uint256 tokenId) external view returns (string memory);
}
+=======
+>>>>>>> feat: Add custum signature with unlimited nesting.
/// @title ERC721 Token that can be irreversibly burned (destroyed).
/// @dev the ERC-165 identifier for this interface is 0x42966c68
interface ERC721Burnable is Dummy, ERC165 {
@@ -438,7 +445,11 @@
}
/// @title Unique extensions for ERC721.
+<<<<<<< HEAD
/// @dev the ERC-165 identifier for this interface is 0x244543ee
+=======
+/// @dev the ERC-165 identifier for this interface is 0xcc97cb35
+>>>>>>> feat: Add custum signature with unlimited nesting.
interface ERC721UniqueExtensions is Dummy, ERC165 {
/// @notice A descriptive name for a collection of NFTs in this contract
/// @dev EVM selector for this function is: 0x06fdde03,
@@ -456,7 +467,7 @@
/// operator of the current owner.
/// @param approved The new substrate address approved NFT controller
/// @param tokenId The NFT to approve
- /// @dev EVM selector for this function is: 0x0ecd0ab0,
+ /// @dev EVM selector for this function is: 0x106fdb59,
/// or in textual repr: approveCross((address,uint256),uint256)
function approveCross(Tuple6 memory approved, uint256 tokenId) external;
@@ -476,10 +487,15 @@
/// @param to Cross acccount address of new owner
/// @param tokenId The NFT to transfer
/// @dev EVM selector for this function is: 0xd5cf430b,
- /// or in textual repr: transferFromCross((address,uint256),(address,uint256),uint256)
+ /// or in textual repr: transferFromCross(EthCrossAccount,EthCrossAccount,uint256)
function transferFromCross(
+<<<<<<< HEAD
Tuple6 memory from,
Tuple6 memory to,
+=======
+ EthCrossAccount memory from,
+ EthCrossAccount memory to,
+>>>>>>> feat: Add custum signature with unlimited nesting.
uint256 tokenId
) external;
@@ -499,7 +515,7 @@
/// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.
/// @param from The current owner of the NFT
/// @param tokenId The NFT to transfer
- /// @dev EVM selector for this function is: 0xbb2f5a58,
+ /// @dev EVM selector for this function is: 0xa8106d4a,
/// or in textual repr: burnFromCross((address,uint256),uint256)
function burnFromCross(Tuple6 memory from, uint256 tokenId) external;
@@ -507,6 +523,7 @@
/// @dev EVM selector for this function is: 0x75794a3c,
/// or in textual repr: nextTokenId()
function nextTokenId() external view returns (uint256);
+<<<<<<< HEAD
// /// @notice Function to mint multiple tokens.
// /// @dev `tokenIds` should be an array of consecutive numbers and first number
// /// should be obtained with `nextTokenId` method
@@ -528,6 +545,30 @@
/// @dev anonymous struct
struct Tuple8 {
+=======
+
+ /// @notice Function to mint multiple tokens.
+ /// @dev `tokenIds` should be an array of consecutive numbers and first number
+ /// should be obtained with `nextTokenId` method
+ /// @param to The new owner
+ /// @param tokenIds IDs of the minted NFTs
+ /// @dev EVM selector for this function is: 0xf9d9a5a3,
+ /// or in textual repr: mintBulk(address,uint256[])
+ function mintBulk(address to, uint256[] memory tokenIds) external returns (bool);
+
+ /// @notice Function to mint multiple tokens with the given tokenUris.
+ /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive
+ /// numbers and first number should be obtained with `nextTokenId` method
+ /// @param to The new owner
+ /// @param tokens array of pairs of token ID and token URI for minted tokens
+ /// @dev EVM selector for this function is: 0xfd4e2a99,
+ /// or in textual repr: mintBulkWithTokenURI(address,(uint256,string)[])
+ function mintBulkWithTokenURI(address to, Tuple12[] memory tokens) external returns (bool);
+}
+
+/// @dev anonymous struct
+struct Tuple12 {
+>>>>>>> feat: Add custum signature with unlimited nesting.
uint256 field_0;
string field_1;
}
@@ -538,6 +579,12 @@
uint256 field_1;
}
+/// @dev anonymous struct
+struct Tuple8 {
+ address field_0;
+ uint256 field_1;
+}
+
/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
/// @dev See https://eips.ethereum.org/EIPS/eip-721
/// @dev the ERC-165 identifier for this interface is 0x780e9d63
tests/src/eth/nonFungible.test.tsdiffbeforeafterboth--- a/tests/src/eth/nonFungible.test.ts
+++ b/tests/src/eth/nonFungible.test.ts
@@ -409,7 +409,7 @@
expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));
});
- itEth('Can perform transferFromCross()', async ({helper, privateKey}) => {
+ itEth.only('Can perform transferFromCross()', async ({helper, privateKey}) => {
const alice = privateKey('//Alice');
const collection = await helper.nft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'});
tests/src/eth/nonFungibleAbi.jsondiffbeforeafterboth--- a/tests/src/eth/nonFungibleAbi.json
+++ b/tests/src/eth/nonFungibleAbi.json
@@ -251,10 +251,14 @@
{ "internalType": "uint256", "name": "field_1", "type": "uint256" }
],
<<<<<<< HEAD
+<<<<<<< HEAD
"internalType": "struct Tuple6",
=======
"internalType": "struct Tuple19",
>>>>>>> feat: add `EthCrossAccount` type
+=======
+ "internalType": "struct Tuple8",
+>>>>>>> feat: Add custum signature with unlimited nesting.
"name": "",
"type": "tuple"
}
@@ -263,6 +267,7 @@
"type": "function"
},
{
+<<<<<<< HEAD
"inputs": [
{ "internalType": "string[]", "name": "keys", "type": "string[]" }
],
@@ -278,6 +283,11 @@
"type": "tuple[]"
}
],
+=======
+ "inputs": [{ "internalType": "string", "name": "key", "type": "string" }],
+ "name": "collectionProperty",
+ "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }],
+>>>>>>> feat: Add custum signature with unlimited nesting.
"stateMutability": "view",
"type": "function"
},
@@ -298,10 +308,14 @@
{ "internalType": "uint256", "name": "field_1", "type": "uint256" }
],
<<<<<<< HEAD
+<<<<<<< HEAD
"internalType": "struct Tuple6",
=======
"internalType": "struct Tuple19",
>>>>>>> feat: add `EthCrossAccount` type
+=======
+ "internalType": "struct Tuple8",
+>>>>>>> feat: Add custum signature with unlimited nesting.
"name": "",
"type": "tuple"
}
@@ -324,6 +338,7 @@
"type": "function"
},
{
+<<<<<<< HEAD
"inputs": [
{ "internalType": "string[]", "name": "keys", "type": "string[]" }
],
@@ -333,6 +348,8 @@
"type": "function"
},
{
+=======
+>>>>>>> feat: Add custum signature with unlimited nesting.
"inputs": [{ "internalType": "string", "name": "key", "type": "string" }],
"name": "deleteCollectionProperty",
"outputs": [],
@@ -409,19 +426,64 @@
"type": "function"
},
{
+<<<<<<< HEAD
"inputs": [{ "internalType": "address", "name": "to", "type": "address" }],
"name": "mint",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+=======
+ "inputs": [
+ { "internalType": "address", "name": "to", "type": "address" },
+ { "internalType": "uint256", "name": "tokenId", "type": "uint256" }
+ ],
+ "name": "mint",
+ "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+>>>>>>> feat: Add custum signature with unlimited nesting.
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "address", "name": "to", "type": "address" },
+<<<<<<< HEAD
{ "internalType": "string", "name": "tokenUri", "type": "string" }
],
"name": "mintWithTokenURI",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+=======
+ { "internalType": "uint256[]", "name": "tokenIds", "type": "uint256[]" }
+ ],
+ "name": "mintBulk",
+ "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "address", "name": "to", "type": "address" },
+ {
+ "components": [
+ { "internalType": "uint256", "name": "field_0", "type": "uint256" },
+ { "internalType": "string", "name": "field_1", "type": "string" }
+ ],
+ "internalType": "struct Tuple12[]",
+ "name": "tokens",
+ "type": "tuple[]"
+ }
+ ],
+ "name": "mintBulkWithTokenURI",
+ "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "address", "name": "to", "type": "address" },
+ { "internalType": "uint256", "name": "tokenId", "type": "uint256" },
+ { "internalType": "string", "name": "tokenUri", "type": "string" }
+ ],
+ "name": "mintWithTokenURI",
+ "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+>>>>>>> feat: Add custum signature with unlimited nesting.
"stateMutability": "nonpayable",
"type": "function"
},
@@ -614,6 +676,7 @@
},
{
"inputs": [
+<<<<<<< HEAD
{
"components": [
{ "internalType": "string", "name": "field_0", "type": "string" },
@@ -623,6 +686,10 @@
"name": "properties",
"type": "tuple[]"
}
+=======
+ { "internalType": "string", "name": "key", "type": "string" },
+ { "internalType": "bytes", "name": "value", "type": "bytes" }
+>>>>>>> feat: Add custum signature with unlimited nesting.
],
"name": "setCollectionProperties",
"outputs": [],
@@ -667,6 +734,7 @@
},
{
"inputs": [
+<<<<<<< HEAD
{
"components": [
{ "internalType": "address", "name": "field_0", "type": "address" },
@@ -676,6 +744,9 @@
"name": "newOwner",
"type": "tuple"
}
+=======
+ { "internalType": "address", "name": "newOwner", "type": "address" }
+>>>>>>> feat: Add custum signature with unlimited nesting.
],
"name": "setOwnerCross",
"outputs": [],
@@ -687,8 +758,13 @@
{ "internalType": "uint256", "name": "tokenId", "type": "uint256" },
{
"components": [
+<<<<<<< HEAD
{ "internalType": "string", "name": "field_0", "type": "string" },
{ "internalType": "bytes", "name": "field_1", "type": "bytes" }
+=======
+ { "internalType": "address", "name": "field_0", "type": "address" },
+ { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+>>>>>>> feat: Add custum signature with unlimited nesting.
],
"internalType": "struct Tuple19[]",
"name": "properties",
@@ -799,8 +875,13 @@
"inputs": [
{
"components": [
+<<<<<<< HEAD
{ "internalType": "address", "name": "field_0", "type": "address" },
{ "internalType": "uint256", "name": "field_1", "type": "uint256" }
+=======
+ { "internalType": "address", "name": "eth", "type": "address" },
+ { "internalType": "uint256", "name": "sub", "type": "uint256" }
+>>>>>>> feat: Add custum signature with unlimited nesting.
],
<<<<<<< HEAD
"internalType": "struct Tuple6",
@@ -812,8 +893,13 @@
},
{
"components": [
+<<<<<<< HEAD
{ "internalType": "address", "name": "field_0", "type": "address" },
{ "internalType": "uint256", "name": "field_1", "type": "uint256" }
+=======
+ { "internalType": "address", "name": "eth", "type": "address" },
+ { "internalType": "uint256", "name": "sub", "type": "uint256" }
+>>>>>>> feat: Add custum signature with unlimited nesting.
],
<<<<<<< HEAD
"internalType": "struct Tuple6",
tests/src/eth/util/playgrounds/types.tsdiffbeforeafterboth--- a/tests/src/eth/util/playgrounds/types.ts
+++ b/tests/src/eth/util/playgrounds/types.ts
@@ -14,10 +14,8 @@
args: { [key: string]: string }
};
export interface TEthCrossAccount {
- readonly 0: string,
- readonly 1: string | Uint8Array,
- readonly field_0: string,
- readonly field_1: string | Uint8Array,
+ readonly eth: string,
+ readonly sub: string | Uint8Array,
}
export type EthProperty = string[];
tests/src/eth/util/playgrounds/unique.dev.tsdiffbeforeafterboth--- a/tests/src/eth/util/playgrounds/unique.dev.ts
+++ b/tests/src/eth/util/playgrounds/unique.dev.ts
@@ -364,19 +364,15 @@
export class EthCrossAccountGroup extends EthGroupBase {
fromAddress(address: TEthereumAccount): TEthCrossAccount {
return {
- 0: address,
- 1: '0',
- field_0: address,
- field_1: '0',
+ eth: address,
+ sub: '0',
};
}
fromKeyringPair(keyring: IKeyringPair): TEthCrossAccount {
return {
- 0: '0x0000000000000000000000000000000000000000',
- 1: keyring.addressRaw,
- field_0: '0x0000000000000000000000000000000000000000',
- field_1: keyring.addressRaw,
+ eth: '0x0000000000000000000000000000000000000000',
+ sub: keyring.addressRaw,
};
}
}
@@ -429,7 +425,6 @@
ethAddress: EthAddressGroup;
ethNativeContract: NativeContractGroup;
ethContract: ContractGroup;
- ethCrossAccount: EthCrossAccountGroup;
ethProperty: EthPropertyGroup;
constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {