difftreelog
Merge pull request #160 from UniqueNetwork/feature/erc-mintable-burnable
in: master
Implement ERC721Burnable/ERC721Mintable
13 files changed
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -5852,34 +5852,34 @@
[[package]]
name = "pallet-scheduler"
version = "3.0.0"
+source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.8#74101dc21cfffb4c2d014fcc28edc166d5ca1b16"
dependencies = [
"frame-benchmarking",
"frame-support",
"frame-system",
"log",
"parity-scale-codec",
- "serde",
- "sp-core",
"sp-io",
"sp-runtime",
"sp-std",
- "substrate-test-utils",
- "up-sponsorship",
]
[[package]]
name = "pallet-scheduler"
version = "3.0.0"
-source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.8#74101dc21cfffb4c2d014fcc28edc166d5ca1b16"
dependencies = [
"frame-benchmarking",
"frame-support",
"frame-system",
"log",
"parity-scale-codec",
+ "serde",
+ "sp-core",
"sp-io",
"sp-runtime",
"sp-std",
+ "substrate-test-utils",
+ "up-sponsorship",
]
[[package]]
@@ -10695,13 +10695,13 @@
[[package]]
name = "substrate-wasm-builder"
version = "4.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "93a3d51ad6abbc408b03ea962062bfcc959b438a318d7d4bedd181e1effd0610"
+source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.8#74101dc21cfffb4c2d014fcc28edc166d5ca1b16"
dependencies = [
"ansi_term 0.12.1",
"atty",
"build-helper",
- "cargo_metadata 0.12.3",
+ "cargo_metadata 0.13.1",
+ "sp-maybe-compressed-blob",
"tempfile",
"toml",
"walkdir",
@@ -10711,13 +10711,13 @@
[[package]]
name = "substrate-wasm-builder"
version = "4.0.0"
-source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.8#74101dc21cfffb4c2d014fcc28edc166d5ca1b16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93a3d51ad6abbc408b03ea962062bfcc959b438a318d7d4bedd181e1effd0610"
dependencies = [
"ansi_term 0.12.1",
"atty",
"build-helper",
- "cargo_metadata 0.13.1",
- "sp-maybe-compressed-blob",
+ "cargo_metadata 0.12.3",
"tempfile",
"toml",
"walkdir",
crates/evm-coder-macros/src/solidity_interface.rsdiffbeforeafterboth--- a/crates/evm-coder-macros/src/solidity_interface.rs
+++ b/crates/evm-coder-macros/src/solidity_interface.rs
@@ -73,6 +73,20 @@
}
}
}
+
+ fn expand_generator(&self) -> proc_macro2::TokenStream {
+ let pascal_call_name = &self.pascal_call_name;
+ quote! {
+ #pascal_call_name::generate_solidity_interface(out_set, is_impl);
+ }
+ }
+
+ fn expand_event_generator(&self) -> proc_macro2::TokenStream {
+ let name = &self.name;
+ quote! {
+ #name::generate_solidity_interface(out_set, is_impl);
+ }
+ }
}
#[derive(Default)]
@@ -109,12 +123,15 @@
struct MethodArg {
name: Ident,
+ camel_name: String,
ty: Ident,
}
impl MethodArg {
fn try_from(value: &PatType) -> syn::Result<Self> {
+ let name = parse_ident_from_pat(&value.pat)?.clone();
Ok(Self {
- name: parse_ident_from_pat(&value.pat)?.clone(),
+ camel_name: cases::camelcase::to_camel_case(&name.to_string()),
+ name,
ty: parse_ident_from_type(&value.ty, false)?.clone(),
})
}
@@ -168,10 +185,10 @@
}
fn expand_solidity_argument(&self) -> proc_macro2::TokenStream {
- let name = &self.name.to_string();
+ let camel_name = &self.camel_name.to_string();
let ty = &self.ty;
quote! {
- <NamedArgument<#ty>>::new(#name)
+ <NamedArgument<#ty>>::new(#camel_name)
}
}
}
@@ -397,7 +414,11 @@
};
let result = &self.result;
- let args = self.args.iter().map(MethodArg::expand_solidity_argument);
+ let args = self
+ .args
+ .iter()
+ .filter(|a| !a.is_special())
+ .map(MethodArg::expand_solidity_argument);
quote! {
SolidityFunction {
@@ -475,6 +496,24 @@
let call_variants_this = self.methods.iter().map(Method::expand_variant_call);
let solidity_functions = self.methods.iter().map(Method::expand_solidity_function);
+ // TODO: Inline inline_is
+ let solidity_is = self
+ .info
+ .is
+ .0
+ .iter()
+ .chain(self.info.inline_is.0.iter())
+ .map(|is| is.name.to_string());
+ let solidity_events_is = self.info.events.0.iter().map(|is| is.name.to_string());
+ let solidity_generators = self
+ .info
+ .is
+ .0
+ .iter()
+ .chain(self.info.inline_is.0.iter())
+ .map(Is::expand_generator);
+ let solidity_event_generators = self.info.events.0.iter().map(Is::expand_event_generator);
+
// let methods = self.methods.iter().map(Method::solidity_def);
quote! {
@@ -505,18 +544,40 @@
)*
)
}
- pub fn generate_solidity_interface() -> string {
+ pub fn generate_solidity_interface(out_set: &mut sp_std::collections::btree_set::BTreeSet<string>, is_impl: bool) {
use evm_coder::solidity::*;
use core::fmt::Write;
let interface = SolidityInterface {
name: #solidity_name,
+ is: &["Dummy", #(
+ #solidity_is,
+ )* #(
+ #solidity_events_is,
+ )* ],
functions: (#(
#solidity_functions,
)*),
};
+ if is_impl {
+ out_set.insert("// Common stubs holder\ncontract Dummy {\n\tuint8 dummy;\n\tstring stub_error = \"this contract is implemented in native\";\n}\n".into());
+ } else {
+ out_set.insert("// Common stubs holder\ninterface Dummy {\n}\n".into());
+ }
+ #(
+ #solidity_generators
+ )*
+ #(
+ #solidity_event_generators
+ )*
+
let mut out = string::new();
- let _ = interface.format(&mut out);
- out
+ // In solidity interface usage (is) should be preceeded by interface definition
+ // This comment helps to sort it in a set
+ if #solidity_name.starts_with("Inline") {
+ out.push_str("// Inline\n");
+ }
+ let _ = interface.format(is_impl, &mut out);
+ out_set.insert(out);
}
}
impl ::evm_coder::Call for #call_name {
crates/evm-coder-macros/src/to_log.rsdiffbeforeafterboth--- a/crates/evm-coder-macros/src/to_log.rs
+++ b/crates/evm-coder-macros/src/to_log.rs
@@ -1,3 +1,4 @@
+use inflector::cases;
use syn::{Data, DeriveInput, Field, Fields, Ident, Variant, spanned::Spanned};
use std::fmt::Write;
use quote::quote;
@@ -6,6 +7,7 @@
struct EventField {
name: Ident,
+ camel_name: String,
ty: Ident,
indexed: bool,
}
@@ -24,10 +26,18 @@
}
Ok(Self {
name: name.to_owned(),
+ camel_name: cases::camelcase::to_camel_case(&name.to_string()),
ty: ty.to_owned(),
indexed,
})
}
+ fn expand_solidity_argument(&self) -> proc_macro2::TokenStream {
+ let camel_name = &self.camel_name;
+ let ty = &self.ty;
+ quote! {
+ <NamedArgument<#ty>>::new(#camel_name)
+ }
+ }
}
struct Event {
@@ -116,6 +126,21 @@
)*];
}
}
+
+ fn expand_solidity_function(&self) -> proc_macro2::TokenStream {
+ let name = self.name.to_string();
+ let args = self.fields.iter().map(EventField::expand_solidity_argument);
+ quote! {
+ SolidityEvent {
+ name: #name,
+ args: (
+ #(
+ #args,
+ )*
+ ),
+ }
+ }
+ }
}
pub struct Events {
@@ -144,12 +169,30 @@
let consts = self.events.iter().map(Event::expand_consts);
let serializers = self.events.iter().map(Event::expand_serializers);
+ let solidity_name = self.name.to_string();
+ let solidity_functions = self.events.iter().map(Event::expand_solidity_function);
quote! {
impl #name {
#(
#consts
)*
+
+ pub fn generate_solidity_interface(out_set: &mut sp_std::collections::btree_set::BTreeSet<string>, is_impl: bool) {
+ use evm_coder::solidity::*;
+ use core::fmt::Write;
+ let interface = SolidityInterface {
+ name: #solidity_name,
+ is: &[],
+ functions: (#(
+ #solidity_functions,
+ )*),
+ };
+ let mut out = string::new();
+ out.push_str("// Inline\n");
+ let _ = interface.format(is_impl, &mut out);
+ out_set.insert(out);
+ }
}
#[automatically_derived]
crates/evm-coder/src/abi.rsdiffbeforeafterboth--- a/crates/evm-coder/src/abi.rs
+++ b/crates/evm-coder/src/abi.rs
@@ -19,11 +19,16 @@
#[derive(Clone)]
pub struct AbiReader<'i> {
buf: &'i [u8],
+ subresult_offset: usize,
offset: usize,
}
impl<'i> AbiReader<'i> {
pub fn new(buf: &'i [u8]) -> Self {
- Self { buf, offset: 0 }
+ Self {
+ buf,
+ subresult_offset: 0,
+ offset: 0,
+ }
}
pub fn new_call(buf: &'i [u8]) -> Result<(u32, Self)> {
if buf.len() < 4 {
@@ -32,11 +37,18 @@
let mut method_id = [0; 4];
method_id.copy_from_slice(&buf[0..4]);
- Ok((u32::from_be_bytes(method_id), Self { buf, offset: 4 }))
+ Ok((
+ u32::from_be_bytes(method_id),
+ Self {
+ buf,
+ subresult_offset: 4,
+ offset: 4,
+ },
+ ))
}
fn read_padleft<const S: usize>(&mut self) -> Result<[u8; S]> {
- if self.buf.len() - self.offset < 32 {
+ if self.buf.len() - self.offset < ABI_ALIGNMENT {
return Err(Error::Error(ExitError::OutOfOffset));
}
let mut block = [0; S];
@@ -79,6 +91,9 @@
}
Ok(subresult.buf[subresult.offset..subresult.offset + length].into())
}
+ pub fn string(&mut self) -> Result<string> {
+ string::from_utf8(self.bytes()?).map_err(|_| Error::Error(ExitError::InvalidRange))
+ }
pub fn uint32(&mut self) -> Result<u32> {
Ok(u32::from_be_bytes(self.read_padleft()?))
@@ -103,9 +118,13 @@
fn subresult(&mut self) -> Result<AbiReader<'i>> {
let offset = self.read_usize()?;
+ if offset + self.subresult_offset > self.buf.len() {
+ return Err(Error::Error(ExitError::InvalidRange));
+ }
Ok(AbiReader {
buf: self.buf,
- offset: offset + self.offset,
+ subresult_offset: offset + self.subresult_offset,
+ offset: offset + self.subresult_offset,
})
}
@@ -196,7 +215,7 @@
for (static_offset, part) in self.dynamic_part {
let part_offset = self.static_part.len();
- let encoded_dynamic_offset = usize::to_be_bytes(part_offset - static_offset);
+ let encoded_dynamic_offset = usize::to_be_bytes(part_offset);
self.static_part[static_offset + ABI_ALIGNMENT - encoded_dynamic_offset.len()
..static_offset + ABI_ALIGNMENT]
.copy_from_slice(&encoded_dynamic_offset);
@@ -226,6 +245,7 @@
impl_abi_readable!(H160, address);
impl_abi_readable!(Vec<u8>, bytes);
impl_abi_readable!(bool, bool);
+impl_abi_readable!(string, string);
pub trait AbiWrite {
fn abi_write(&self, writer: &mut AbiWriter);
@@ -285,3 +305,54 @@
writer
}}
}
+
+#[cfg(test)]
+pub mod test {
+ use super::{AbiReader, AbiWriter};
+ use hex_literal::hex;
+
+ #[test]
+ fn dynamic_after_static() {
+ let mut encoder = AbiWriter::new();
+ encoder.bool(&true);
+ encoder.string("test");
+ let encoded = encoder.finish();
+
+ let mut encoder = AbiWriter::new();
+ encoder.bool(&true);
+ // Offset to subresult
+ encoder.uint32(&(32 * 2));
+ // Len of "test"
+ encoder.uint32(&4);
+ encoder.write_padright(&[b't', b'e', b's', b't']);
+ let alternative_encoded = encoder.finish();
+
+ assert_eq!(encoded, alternative_encoded);
+
+ let mut decoder = AbiReader::new(&encoded);
+ assert_eq!(decoder.bool().unwrap(), true);
+ assert_eq!(decoder.string().unwrap(), "test");
+ }
+
+ #[test]
+ fn mint_sample() {
+ let (call, mut decoder) = AbiReader::new_call(&hex!(
+ "
+ 50bb4e7f
+ 000000000000000000000000ad2c0954693c2b5404b7e50967d3481bea432374
+ 0000000000000000000000000000000000000000000000000000000000000001
+ 0000000000000000000000000000000000000000000000000000000000000060
+ 0000000000000000000000000000000000000000000000000000000000000008
+ 5465737420555249000000000000000000000000000000000000000000000000
+ "
+ ))
+ .unwrap();
+ assert_eq!(call, 0x50bb4e7f);
+ assert_eq!(
+ format!("{:?}", decoder.address().unwrap()),
+ "0xad2c0954693c2b5404b7e50967d3481bea432374"
+ );
+ assert_eq!(decoder.uint32().unwrap(), 1);
+ assert_eq!(decoder.string().unwrap(), "Test URI");
+ }
+}
crates/evm-coder/src/solidity.rsdiffbeforeafterboth--- a/crates/evm-coder/src/solidity.rs
+++ b/crates/evm-coder/src/solidity.rs
@@ -6,37 +6,44 @@
pub trait SolidityTypeName: 'static {
fn solidity_name(writer: &mut impl fmt::Write) -> fmt::Result;
+ fn solidity_default(writer: &mut impl fmt::Write) -> fmt::Result;
fn is_void() -> bool {
false
}
}
macro_rules! solidity_type_name {
- ($($ty:ident => $name:expr),* $(,)?) => {
+ ($($ty:ident => $name:literal = $default:literal),* $(,)?) => {
$(
impl SolidityTypeName for $ty {
fn solidity_name(writer: &mut impl core::fmt::Write) -> core::fmt::Result {
write!(writer, $name)
}
+ fn solidity_default(writer: &mut impl core::fmt::Write) -> core::fmt::Result {
+ write!(writer, $default)
+ }
}
)*
};
}
solidity_type_name! {
- uint8 => "uint8",
- uint32 => "uint32",
- uint128 => "uint128",
- uint256 => "uint256",
- address => "address",
- string => "memory string",
- bytes => "memory bytes",
- bool => "bool",
+ uint8 => "uint8" = "0",
+ uint32 => "uint32" = "0",
+ uint128 => "uint128" = "0",
+ uint256 => "uint256" = "0",
+ address => "address" = "0x0000000000000000000000000000000000000000",
+ string => "string memory" = "\"\"",
+ bytes => "bytes memory" = "hex\"\"",
+ bool => "bool" = "false",
}
impl SolidityTypeName for void {
fn solidity_name(_writer: &mut impl fmt::Write) -> fmt::Result {
Ok(())
}
+ fn solidity_default(_writer: &mut impl fmt::Write) -> fmt::Result {
+ Ok(())
+ }
fn is_void() -> bool {
true
}
@@ -44,6 +51,8 @@
pub trait SolidityArguments {
fn solidity_name(&self, writer: &mut impl fmt::Write) -> fmt::Result;
+ fn solidity_get(&self, writer: &mut impl fmt::Write) -> fmt::Result;
+ fn solidity_default(&self, writer: &mut impl fmt::Write) -> fmt::Result;
fn is_empty(&self) -> bool {
self.len() == 0
}
@@ -61,6 +70,12 @@
Ok(())
}
}
+ fn solidity_get(&self, _writer: &mut impl fmt::Write) -> fmt::Result {
+ Ok(())
+ }
+ fn solidity_default(&self, writer: &mut impl fmt::Write) -> fmt::Result {
+ T::solidity_default(writer)
+ }
fn len(&self) -> usize {
if T::is_void() {
0
@@ -87,6 +102,12 @@
Ok(())
}
}
+ fn solidity_get(&self, writer: &mut impl fmt::Write) -> fmt::Result {
+ writeln!(writer, "\t\t{};", self.0)
+ }
+ fn solidity_default(&self, writer: &mut impl fmt::Write) -> fmt::Result {
+ T::solidity_default(writer)
+ }
fn len(&self) -> usize {
if T::is_void() {
0
@@ -100,6 +121,12 @@
fn solidity_name(&self, _writer: &mut impl fmt::Write) -> fmt::Result {
Ok(())
}
+ fn solidity_get(&self, _writer: &mut impl fmt::Write) -> fmt::Result {
+ Ok(())
+ }
+ fn solidity_default(&self, _writer: &mut impl fmt::Write) -> fmt::Result {
+ Ok(())
+ }
fn len(&self) -> usize {
0
}
@@ -122,13 +149,43 @@
)* );
Ok(())
}
+ fn solidity_get(&self, writer: &mut impl fmt::Write) -> fmt::Result {
+ for_tuples!( #(
+ Tuple.solidity_get(writer)?;
+ )* );
+ Ok(())
+ }
+ fn solidity_default(&self, writer: &mut impl fmt::Write) -> fmt::Result {
+ if self.is_empty() {
+ Ok(())
+ } else if self.len() == 1 {
+ for_tuples!( #(
+ Tuple.solidity_default(writer)?;
+ )* );
+ Ok(())
+ } else {
+ write!(writer, "(")?;
+ let mut first = true;
+ for_tuples!( #(
+ if !Tuple.is_empty() {
+ if !first {
+ write!(writer, ", ")?;
+ }
+ first = false;
+ Tuple.solidity_name(writer)?;
+ }
+ )* );
+ write!(writer, ")")?;
+ Ok(())
+ }
+ }
fn len(&self) -> usize {
for_tuples!( #( Tuple.len() )+* )
}
}
pub trait SolidityFunctions {
- fn solidity_name(&self, writer: &mut impl fmt::Write) -> fmt::Result;
+ fn solidity_name(&self, is_impl: bool, writer: &mut impl fmt::Write) -> fmt::Result;
}
pub enum SolidityMutability {
@@ -143,10 +200,15 @@
pub mutability: SolidityMutability,
}
impl<A: SolidityArguments, R: SolidityArguments> SolidityFunctions for SolidityFunction<A, R> {
- fn solidity_name(&self, writer: &mut impl fmt::Write) -> fmt::Result {
- write!(writer, "function {}(", self.name)?;
+ fn solidity_name(&self, is_impl: bool, writer: &mut impl fmt::Write) -> fmt::Result {
+ write!(writer, "\tfunction {}(", self.name)?;
self.args.solidity_name(writer)?;
- write!(writer, ") external")?;
+ write!(writer, ")")?;
+ if is_impl {
+ write!(writer, " public")?;
+ } else {
+ write!(writer, " external")?;
+ }
match &self.mutability {
SolidityMutability::Pure => write!(writer, " pure")?,
SolidityMutability::View => write!(writer, " view")?,
@@ -157,7 +219,25 @@
self.result.solidity_name(writer)?;
write!(writer, ")")?;
}
- writeln!(writer, ";")
+ if is_impl {
+ writeln!(writer, " {{")?;
+ writeln!(writer, "\t\trequire(false, stub_error);")?;
+ self.args.solidity_get(writer)?;
+ match &self.mutability {
+ SolidityMutability::Pure => {}
+ SolidityMutability::View => writeln!(writer, "\t\tdummy;")?,
+ SolidityMutability::Mutable => writeln!(writer, "\t\tdummy = 0;")?,
+ }
+ if !self.result.is_empty() {
+ write!(writer, "\t\treturn ")?;
+ self.result.solidity_default(writer)?;
+ writeln!(writer, ";")?;
+ }
+ writeln!(writer, "\t}}")?;
+ } else {
+ writeln!(writer, ";")?;
+ }
+ Ok(())
}
}
@@ -165,10 +245,10 @@
impl SolidityFunctions for Tuple {
for_tuples!( where #( Tuple: SolidityFunctions ),* );
- fn solidity_name(&self, writer: &mut impl fmt::Write) -> fmt::Result {
+ fn solidity_name(&self, is_impl: bool, writer: &mut impl fmt::Write) -> fmt::Result {
let mut first = false;
for_tuples!( #(
- Tuple.solidity_name(writer)?;
+ Tuple.solidity_name(is_impl, writer)?;
)* );
Ok(())
}
@@ -176,14 +256,43 @@
pub struct SolidityInterface<F: SolidityFunctions> {
pub name: &'static str,
+ pub is: &'static [&'static str],
pub functions: F,
}
impl<F: SolidityFunctions> SolidityInterface<F> {
- pub fn format(&self, out: &mut impl fmt::Write) -> fmt::Result {
- writeln!(out, "interface {} {{", self.name)?;
- self.functions.solidity_name(out)?;
+ pub fn format(&self, is_impl: bool, out: &mut impl fmt::Write) -> fmt::Result {
+ if is_impl {
+ write!(out, "contract ")?;
+ } else {
+ write!(out, "interface ")?;
+ }
+ write!(out, "{}", self.name)?;
+ if !self.is.is_empty() {
+ write!(out, " is")?;
+ for (i, n) in self.is.iter().enumerate() {
+ if i != 0 {
+ write!(out, ",")?;
+ }
+ write!(out, " {}", n)?;
+ }
+ }
+ writeln!(out, " {{")?;
+ self.functions.solidity_name(is_impl, out)?;
writeln!(out, "}}")?;
Ok(())
}
}
+
+pub struct SolidityEvent<A> {
+ pub name: &'static str,
+ pub args: A,
+}
+
+impl<A: SolidityArguments> SolidityFunctions for SolidityEvent<A> {
+ fn solidity_name(&self, _is_impl: bool, writer: &mut impl fmt::Write) -> fmt::Result {
+ write!(writer, "\tevent {}(", self.name)?;
+ self.args.solidity_name(writer)?;
+ writeln!(writer, ");")
+ }
+}
pallets/nft/src/eth/erc.rsdiffbeforeafterboth--- a/pallets/nft/src/eth/erc.rs
+++ b/pallets/nft/src/eth/erc.rs
@@ -1,12 +1,15 @@
use core::char::{decode_utf16, REPLACEMENT_CHARACTER};
use evm_coder::{ToLog, execution::Result, solidity, solidity_interface, types::*};
+use nft_data_structs::{CreateItemData, CreateNftData};
use core::convert::TryInto;
-use alloc::format;
-use crate::{Allowances, Module, Balance, CollectionHandle, CollectionMode, Config, NftItemList};
-use frame_support::storage::StorageDoubleMap;
+use crate::{
+ Allowances, Module, Balance, CollectionHandle, CollectionMode, Config, NftItemList,
+ ItemListIndex,
+};
+use frame_support::storage::{StorageMap, StorageDoubleMap};
use pallet_evm::AddressMapping;
use super::account::CrossAccountId;
-use sp_std::vec::Vec;
+use sp_std::{vec, vec::Vec};
#[solidity_interface(name = "ERC165")]
impl<T: Config> CollectionHandle<T> {
@@ -42,9 +45,15 @@
#[solidity_interface(name = "ERC721Metadata", inline_is(InlineNameSymbol))]
impl<T: Config> CollectionHandle<T> {
+ #[solidity(rename_selector = "tokenURI")]
fn token_uri(&self, token_id: uint256) -> Result<string> {
- // TODO: We should standartize url prefix, maybe via offchain schema?
- Ok(format!("unique.network/{}/{}", self.id, token_id))
+ let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;
+ Ok(string::from_utf8_lossy(
+ &<NftItemList<T>>::get(self.id, token_id)
+ .ok_or("token not found")?
+ .const_data,
+ )
+ .into())
}
}
@@ -178,6 +187,93 @@
}
}
+#[solidity_interface(name = "ERC721Burnable")]
+impl<T: Config> CollectionHandle<T> {
+ fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {
+ let caller = T::CrossAccountId::from_eth(caller);
+ let token_id = token_id.try_into().map_err(|_| "amount overflow")?;
+
+ <Module<T>>::burn_item_internal(&caller, &self, token_id, 1).map_err(|_| "burn error")?;
+ Ok(())
+ }
+}
+
+#[derive(ToLog)]
+pub enum ERC721MintableEvents {
+ #[allow(dead_code)]
+ MintingFinished {},
+}
+
+#[solidity_interface(name = "ERC721Mintable", events(ERC721MintableEvents))]
+impl<T: Config> CollectionHandle<T> {
+ fn minting_finished(&self) -> Result<bool> {
+ Ok(false)
+ }
+
+ fn mint(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {
+ let caller = T::CrossAccountId::from_eth(caller);
+ let to = T::CrossAccountId::from_eth(to);
+ let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;
+ if <ItemListIndex>::get(self.id)
+ .checked_add(1)
+ .ok_or("item id overflow")?
+ != token_id
+ {
+ return Err("item id should be next".into());
+ }
+
+ <Module<T>>::create_item_internal(
+ &caller,
+ &self,
+ &to,
+ CreateItemData::NFT(CreateNftData {
+ const_data: vec![].try_into().unwrap(),
+ variable_data: vec![].try_into().unwrap(),
+ }),
+ )
+ .map_err(|_| "mint error")?;
+ Ok(true)
+ }
+
+ #[solidity(rename_selector = "mintWithTokenURI")]
+ fn mint_with_token_uri(
+ &mut self,
+ caller: caller,
+ to: address,
+ token_id: uint256,
+ token_uri: string,
+ ) -> Result<bool> {
+ let caller = T::CrossAccountId::from_eth(caller);
+ let to = T::CrossAccountId::from_eth(to);
+ let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;
+ if <ItemListIndex>::get(self.id)
+ .checked_add(1)
+ .ok_or("item id overflow")?
+ != token_id
+ {
+ return Err("item id should be next".into());
+ }
+
+ <Module<T>>::create_item_internal(
+ &caller,
+ &self,
+ &to,
+ CreateItemData::NFT(CreateNftData {
+ const_data: Vec::<u8>::from(token_uri)
+ .try_into()
+ .map_err(|_| "token uri is too long")?,
+ variable_data: vec![].try_into().unwrap(),
+ }),
+ )
+ .map_err(|_| "mint error")?;
+ Ok(true)
+ }
+
+ fn finish_minting(&mut self, _caller: caller) -> Result<bool> {
+ Err("not implementable".into())
+ }
+}
+
#[solidity_interface(name = "ERC721UniqueExtensions")]
impl<T: Config> CollectionHandle<T> {
#[solidity(rename_selector = "transfer")]
@@ -196,6 +292,13 @@
.map_err(|_| "transfer error")?;
Ok(())
}
+
+ fn next_token_id(&self) -> Result<uint256> {
+ Ok(ItemListIndex::get(self.id)
+ .checked_add(1)
+ .ok_or("item id overflow")?
+ .into())
+ }
}
#[solidity_interface(
@@ -205,7 +308,9 @@
ERC721,
ERC721Metadata,
ERC721Enumerable,
- ERC721UniqueExtensions
+ ERC721UniqueExtensions,
+ ERC721Mintable,
+ ERC721Burnable,
)
)]
impl<T: Config> CollectionHandle<T> {}
@@ -297,3 +402,32 @@
#[solidity_interface(name = "UniqueFungible", is(ERC165, ERC20))]
impl<T: Config> CollectionHandle<T> {}
+
+macro_rules! generate_code {
+ ($name:ident, $decl:ident, $is_impl:literal) => {
+ #[test]
+ #[ignore]
+ fn $name() {
+ use sp_std::collections::btree_set::BTreeSet;
+ let mut out = BTreeSet::new();
+ $decl::generate_solidity_interface(&mut out, $is_impl);
+ println!("=== SNIP START ===");
+ println!("// SPDX-License-Identifier: OTHER");
+ println!("// This code is automatically generated with `cargo test --package pallet-nft -- eth::erc::{} --exact --nocapture --ignored`", stringify!(name));
+ println!();
+ println!("pragma solidity >=0.8.0 <0.9.0;");
+ println!();
+ for b in out {
+ println!("{}", b);
+ }
+ println!("=== SNIP END ===");
+ }
+ };
+}
+
+// Not a tests, but code generators
+generate_code!(nft_impl, UniqueNFTCall, true);
+generate_code!(nft_iface, UniqueNFTCall, false);
+
+generate_code!(fungible_impl, UniqueFungibleCall, true);
+generate_code!(fungible_iface, UniqueFungibleCall, false);
pallets/nft/src/eth/stubs/ERC20.bindiffbeforeafterbothbinary blob — no preview
pallets/nft/src/eth/stubs/ERC20.soldiffbeforeafterboth--- a/pallets/nft/src/eth/stubs/ERC20.sol
+++ b/pallets/nft/src/eth/stubs/ERC20.sol
@@ -1,69 +1,94 @@
// SPDX-License-Identifier: OTHER
+// This code is automatically generated with `cargo test --package pallet-nft -- eth::erc::name --exact --nocapture --ignored`
pragma solidity >=0.8.0 <0.9.0;
-contract ERC20 {
- uint8 _dummy = 0;
- string stub_error = "this contract does not exists, code for collections is implemented at pallet side";
+// Common stubs holder
+contract Dummy {
+ uint8 dummy;
+ string stub_error = "this contract is implemented in native";
+}
- // 0x18160ddd
- function totalSupply() external view returns (uint256) {
+// Inline
+contract ERC20Events {
+ event Transfer(address from, address to, uint256 value);
+ event Approval(address owner, address spender, uint256 value);
+}
+
+// Inline
+contract InlineNameSymbol is Dummy {
+ function name() public view returns (string memory) {
require(false, stub_error);
- _dummy;
- return 0;
+ dummy;
+ return "";
}
+ function symbol() public view returns (string memory) {
+ require(false, stub_error);
+ dummy;
+ return "";
+ }
+}
- // 0x70a08231
- function balanceOf(address account) external view returns (uint256) {
+// Inline
+contract InlineTotalSupply is Dummy {
+ function totalSupply() public view returns (uint256) {
require(false, stub_error);
- account;
- _dummy;
+ dummy;
return 0;
}
+}
- // 0xa9059cbb
- function transfer(address recipient, uint256 amount) external returns (bool) {
+contract ERC165 is Dummy {
+ function supportsInterface(uint32 interfaceId) public view returns (bool) {
require(false, stub_error);
- recipient;
- amount;
- _dummy = 0;
+ interfaceId;
+ dummy;
return false;
}
+}
- // 0xdd62ed3e
- function allowance(address owner, address spender) external view returns (uint256) {
+contract ERC20 is Dummy, InlineNameSymbol, InlineTotalSupply, ERC20Events {
+ function decimals() public view returns (uint8) {
require(false, stub_error);
+ dummy;
+ return 0;
+ }
+ function balanceOf(address owner) public view returns (uint256) {
+ require(false, stub_error);
owner;
- spender;
- return _dummy;
+ dummy;
+ return 0;
+ }
+ function transfer(address to, uint256 amount) public returns (bool) {
+ require(false, stub_error);
+ to;
+ amount;
+ dummy = 0;
+ return false;
}
-
- // 0x095ea7b3
- function approve(address spender, uint256 amount) external returns (bool) {
+ function transferFrom(address from, address to, uint256 amount) public returns (bool) {
require(false, stub_error);
- spender;
+ from;
+ to;
amount;
- _dummy = 0;
+ dummy = 0;
return false;
}
-
- // 0x23b872dd
- function transferFrom(address sender, address recipient, uint256 amount) external returns (bool) {
+ function approve(address spender, uint256 amount) public returns (bool) {
require(false, stub_error);
- sender;
- recipient;
+ spender;
amount;
- _dummy = 0;
+ dummy = 0;
return false;
}
+ function allowance(address owner, address spender) public view returns (uint256) {
+ require(false, stub_error);
+ owner;
+ spender;
+ dummy;
+ return 0;
+ }
+}
- // While ERC165 is not required by spec of ERC20, better implement it
- // 0x01ffc9a7
- function supportsInterface(bytes4 interfaceID) public pure returns (bool) {
- return
- // ERC20
- interfaceID == 0x36372b07 ||
- // ERC165
- interfaceID == 0x01ffc9a7;
- }
+contract UniqueFungible is Dummy, ERC165, ERC20 {
}
\ No newline at end of file
pallets/nft/src/eth/stubs/ERC721.bindiffbeforeafterbothbinary blob — no preview
pallets/nft/src/eth/stubs/ERC721.soldiffbeforeafterboth--- a/pallets/nft/src/eth/stubs/ERC721.sol
+++ b/pallets/nft/src/eth/stubs/ERC721.sol
@@ -1,163 +1,194 @@
// SPDX-License-Identifier: OTHER
+// This code is automatically generated with `cargo test --package pallet-nft -- eth::erc::name --exact --nocapture --ignored`
pragma solidity >=0.8.0 <0.9.0;
-contract ERC721 {
- uint8 _dummy = 0;
- address _dummy_addr = 0x0000000000000000000000000000000000000000;
- string _dummy_string = "";
- string stub_error =
- "this contract does not exists, code for collections is implemented at pallet side";
+// Common stubs holder
+contract Dummy {
+ uint8 dummy;
+ string stub_error = "this contract is implemented in native";
+}
- event Transfer(
- address indexed from,
- address indexed to,
- uint256 indexed tokenId
- );
+// Inline
+contract ERC721Events {
+ event Transfer(address from, address to, uint256 tokenId);
+ event Approval(address owner, address approved, uint256 tokenId);
+ event ApprovalForAll(address owner, address operator, bool approved);
+}
- event Approval(
- address indexed owner,
- address indexed approved,
- uint256 indexed tokenId
- );
+// Inline
+contract ERC721MintableEvents {
+ event MintingFinished();
+}
- event ApprovalForAll(
- address indexed owner,
- address indexed operator,
- bool approved
- );
+// Inline
+contract InlineNameSymbol is Dummy {
+ function name() public view returns (string memory) {
+ require(false, stub_error);
+ dummy;
+ return "";
+ }
+ function symbol() public view returns (string memory) {
+ require(false, stub_error);
+ dummy;
+ return "";
+ }
+}
- // 0x18160ddd
- function totalSupply() external view returns (uint256) {
- require(false, stub_error);
- return 0;
- }
+// Inline
+contract InlineTotalSupply is Dummy {
+ function totalSupply() public view returns (uint256) {
+ require(false, stub_error);
+ dummy;
+ return 0;
+ }
+}
- function name() external view returns (string memory res_name) {
- require(false, stub_error);
- res_name = _dummy_string;
- }
+contract ERC165 is Dummy {
+ function supportsInterface(uint32 interfaceId) public view returns (bool) {
+ require(false, stub_error);
+ interfaceId;
+ dummy;
+ return false;
+ }
+}
- function symbol() external view returns (string memory res_symbol) {
- require(false, stub_error);
- res_symbol = _dummy_string;
- }
+contract ERC721 is Dummy, ERC165, ERC721Events {
+ function balanceOf(address owner) public view returns (uint256) {
+ require(false, stub_error);
+ owner;
+ dummy;
+ return 0;
+ }
+ function ownerOf(uint256 tokenId) public view returns (address) {
+ require(false, stub_error);
+ tokenId;
+ dummy;
+ return 0x0000000000000000000000000000000000000000;
+ }
+ function safeTransferFromWithData(address from, address to, uint256 tokenId, bytes memory data) public {
+ require(false, stub_error);
+ from;
+ to;
+ tokenId;
+ data;
+ dummy = 0;
+ }
+ function safeTransferFrom(address from, address to, uint256 tokenId) public {
+ require(false, stub_error);
+ from;
+ to;
+ tokenId;
+ dummy = 0;
+ }
+ function transferFrom(address from, address to, uint256 tokenId) public {
+ require(false, stub_error);
+ from;
+ to;
+ tokenId;
+ dummy = 0;
+ }
+ function approve(address approved, uint256 tokenId) public {
+ require(false, stub_error);
+ approved;
+ tokenId;
+ dummy = 0;
+ }
+ function setApprovalForAll(address operator, bool approved) public {
+ require(false, stub_error);
+ operator;
+ approved;
+ dummy = 0;
+ }
+ function getApproved(uint256 tokenId) public view returns (address) {
+ require(false, stub_error);
+ tokenId;
+ dummy;
+ return 0x0000000000000000000000000000000000000000;
+ }
+ function isApprovedForAll(address owner, address operator) public view returns (address) {
+ require(false, stub_error);
+ owner;
+ operator;
+ dummy;
+ return 0x0000000000000000000000000000000000000000;
+ }
+}
- function tokenURI(uint256 tokenId) external view returns (string memory) {
- require(false, stub_error);
- tokenId;
- return _dummy_string;
- }
+contract ERC721Burnable is Dummy {
+ function burn(uint256 tokenId) public {
+ require(false, stub_error);
+ tokenId;
+ dummy = 0;
+ }
+}
- function tokenByIndex(uint256 index) external view returns (uint256) {
- require(false, stub_error);
- index;
+contract ERC721Enumerable is Dummy, InlineTotalSupply {
+ function tokenByIndex(uint256 index) public view returns (uint256) {
+ require(false, stub_error);
+ index;
+ dummy;
return 0;
- }
-
- function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256) {
- require(false, stub_error);
+ }
+ function tokenOfOwnerByIndex(address owner, uint256 index) public view returns (uint256) {
+ require(false, stub_error);
owner;
index;
+ dummy;
return 0;
- }
+ }
+}
- // 0x70a08231
- function balanceOf(address owner) external view returns (uint256) {
- require(false, stub_error);
- owner;
- return 0;
- }
+contract ERC721Metadata is Dummy, InlineNameSymbol {
+ function tokenURI(uint256 tokenId) public view returns (string memory) {
+ require(false, stub_error);
+ tokenId;
+ dummy;
+ return "";
+ }
+}
- // 0x6352211e
- function ownerOf(uint256 tokenId) external view returns (address) {
- require(false, stub_error);
- tokenId;
- return _dummy_addr;
- }
+contract ERC721Mintable is Dummy, ERC721MintableEvents {
+ function mintingFinished() public view returns (bool) {
+ require(false, stub_error);
+ dummy;
+ return false;
+ }
+ function mint(address to, uint256 tokenId) public returns (bool) {
+ require(false, stub_error);
+ to;
+ tokenId;
+ dummy = 0;
+ return false;
+ }
+ function mintWithTokenURI(address to, uint256 tokenId, string memory tokenUri) public returns (bool) {
+ require(false, stub_error);
+ to;
+ tokenId;
+ tokenUri;
+ dummy = 0;
+ return false;
+ }
+ function finishMinting() public returns (bool) {
+ require(false, stub_error);
+ dummy = 0;
+ return false;
+ }
+}
- // 0xb88d4fde
- function safeTransferFrom(
- address from,
- address to,
- uint256 tokenId,
- bytes calldata data
- ) external payable {
- require(false, stub_error);
- from;
- to;
- tokenId;
- data;
- }
-
- // 0x42842e0e
- function safeTransferFrom(
- address from,
- address to,
- uint256 tokenId
- ) external payable {
- require(false, stub_error);
- from;
- to;
- tokenId;
- }
-
- // 0x23b872dd
- function transferFrom(
- address from,
- address to,
- uint256 tokenId
- ) external payable {
- require(false, stub_error);
- from;
- to;
- tokenId;
- }
+contract ERC721UniqueExtensions is Dummy {
+ function transfer(address to, uint256 tokenId) public {
+ require(false, stub_error);
+ to;
+ tokenId;
+ dummy = 0;
+ }
+ function nextTokenId() public view returns (uint256) {
+ require(false, stub_error);
+ dummy;
+ return 0;
+ }
+}
- // 0x095ea7b3
- function approve(address approved, uint256 tokenId) external payable {
- require(false, stub_error);
- approved;
- tokenId;
- }
-
- // 0xa22cb465
- function setApprovalForAll(address operator, bool approved) external {
- require(false, stub_error);
- operator;
- approved;
- _dummy = 0;
- }
-
- // 0x081812fc
- function getApproved(uint256 tokenId) external view returns (address) {
- require(false, stub_error);
- tokenId;
- return _dummy_addr;
- }
-
- // 0xe985e9c5
- function isApprovedForAll(address owner, address operator)
- external
- view
- returns (bool)
- {
- require(false, stub_error);
- owner;
- operator;
- return false;
- }
-
- // 0x01ffc9a7
- function supportsInterface(bytes4 interfaceID) public pure returns (bool) {
- return
- // ERC721
- interfaceID == 0x80ac58cd ||
- // ERC721Metadata
- interfaceID == 0x5b5e139f ||
- // ERC721Enumerable
- interfaceID == 0x780e9d63 ||
- // ERC165
- interfaceID == 0x01ffc9a7;
- }
-}
+contract UniqueNFT is Dummy, ERC165, ERC721, ERC721Metadata, ERC721Enumerable, ERC721UniqueExtensions, ERC721Mintable, ERC721Burnable {
+}
\ No newline at end of file
pallets/nft/src/lib.rsdiffbeforeafterboth--- a/pallets/nft/src/lib.rs
+++ b/pallets/nft/src/lib.rs
@@ -177,6 +177,8 @@
OutOfGas,
/// Collection settings not allowing items transferring
TransferNotAllowed,
+ /// Can't transfer tokens to ethereum zero address
+ AddressIsZero,
}
}
@@ -1248,6 +1250,11 @@
owner: &T::CrossAccountId,
data: CreateItemData,
) -> DispatchResult {
+ ensure!(
+ owner != &T::CrossAccountId::from_eth(H160([0; 20])),
+ Error::<T>::AddressIsZero
+ );
+
Self::can_create_items_in_collection(collection, sender, owner, 1)?;
Self::validate_create_item_args(collection, &data)?;
Self::create_item_no_validation(collection, owner, data)?;
@@ -1262,6 +1269,11 @@
item_id: TokenId,
value: u128,
) -> DispatchResult {
+ ensure!(
+ recipient != &T::CrossAccountId::from_eth(H160([0; 20])),
+ Error::<T>::AddressIsZero
+ );
+
// Limits check
Self::is_correct_transfer(target_collection, recipient)?;
@@ -1829,6 +1841,11 @@
<NftItemList<T>>::remove(collection_id, item_id);
<VariableMetaDataBasket<T>>::remove(collection_id, item_id);
+ collection.log(ERC721Events::Transfer {
+ from: *item.owner.as_eth(),
+ to: H160::default(),
+ token_id: item_id.into(),
+ })?;
Self::deposit_event(RawEvent::ItemDestroyed(collection.id, item_id));
Ok(())
}
tests/src/eth/nonFungible.test.tsdiffbeforeafterboth1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56import privateKey from '../substrate/privateKey';7import { approveExpectSuccess, createCollectionExpectSuccess, createItemExpectSuccess, transferExpectSuccess, transferFromExpectSuccess, UNIQUE } from '../util/helpers';8import { collectionIdToAddress, createEthAccount, createEthAccountWithBalance, GAS_ARGS, itWeb3, normalizeEvents, recordEthFee, recordEvents, subToEth, transferBalanceToEth } from './util/helpers';9import nonFungibleAbi from './nonFungibleAbi.json';10import { expect } from 'chai';11import waitNewBlocks from '../substrate/wait-new-blocks';1213describe('NFT: Information getting', () => {14 itWeb3('totalSupply', async ({ api, web3 }) => {15 const collection = await createCollectionExpectSuccess({16 mode: { type: 'NFT' },17 });18 const alice = privateKey('//Alice');19 const caller = await createEthAccountWithBalance(api, web3);2021 await createItemExpectSuccess(alice, collection, 'NFT', { substrate: alice.address });2223 const address = collectionIdToAddress(collection);24 const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS});25 const totalSupply = await contract.methods.totalSupply().call();2627 // FIXME: always equals to 0, because this method is not implemented28 expect(totalSupply).to.equal('0');29 });3031 itWeb3('balanceOf', async ({ api, web3 }) => {32 const collection = await createCollectionExpectSuccess({33 mode: { type: 'NFT' },34 });35 const alice = privateKey('//Alice');3637 const caller = await createEthAccountWithBalance(api, web3);38 await createItemExpectSuccess(alice, collection, 'NFT', { ethereum: caller });39 await createItemExpectSuccess(alice, collection, 'NFT', { ethereum: caller });40 await createItemExpectSuccess(alice, collection, 'NFT', { ethereum: caller });4142 const address = collectionIdToAddress(collection);43 const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS});44 const balance = await contract.methods.balanceOf(caller).call();4546 expect(balance).to.equal('3');47 });4849 itWeb3('ownerOf', async ({ api, web3 }) => {50 const collection = await createCollectionExpectSuccess({51 mode: { type: 'NFT' },52 });53 const alice = privateKey('//Alice');5455 const caller = await createEthAccountWithBalance(api, web3);56 const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', { ethereum: caller });5758 const address = collectionIdToAddress(collection);59 const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS});60 const owner = await contract.methods.ownerOf(tokenId).call();6162 expect(owner).to.equal(caller);63 });64});6566describe('NFT: Plain calls', () => {67 itWeb3('Can perform approve()', async ({ web3, api }) => {68 const collection = await createCollectionExpectSuccess({69 mode: { type: 'NFT' },70 });71 const alice = privateKey('//Alice');7273 const owner = createEthAccount(web3);74 await transferBalanceToEth(api, alice, owner, 999999999999999);7576 const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', { ethereum: owner });7778 const spender = createEthAccount(web3);7980 const address = collectionIdToAddress(collection);81 const contract = new web3.eth.Contract(nonFungibleAbi as any, address);8283 {84 const result = await contract.methods.approve(spender, tokenId).send({ from: owner, gas: '0x1000000', gasPrice: '0x01' });85 const events = normalizeEvents(result.events);8687 expect(events).to.be.deep.equal([88 {89 address,90 event: 'Approval',91 args: {92 owner,93 approved: spender,94 tokenId: tokenId.toString(),95 },96 },97 ]);98 }99 });100101 itWeb3('Can perform transferFrom()', async ({ web3, api }) => {102 const collection = await createCollectionExpectSuccess({103 mode: { type: 'NFT' },104 });105 const alice = privateKey('//Alice');106107 const owner = createEthAccount(web3);108 await transferBalanceToEth(api, alice, owner, 999999999999999);109110 const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', { ethereum: owner });111112 const spender = createEthAccount(web3);113 await transferBalanceToEth(api, alice, spender, 999999999999999);114115 const receiver = createEthAccount(web3);116117 const address = collectionIdToAddress(collection);118 const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: owner, ...GAS_ARGS});119120 await contract.methods.approve(spender, tokenId).send({ from: owner });121122 {123 const result = await contract.methods.transferFrom(owner, receiver, tokenId).send({ from: spender });124 const events = normalizeEvents(result.events);125 expect(events).to.be.deep.equal([126 {127 address,128 event: 'Transfer',129 args: {130 from: owner,131 to: receiver,132 tokenId: tokenId.toString(),133 },134 },135 ]);136 }137138 {139 const balance = await contract.methods.balanceOf(receiver).call();140 expect(+balance).to.equal(1);141 }142143 {144 const balance = await contract.methods.balanceOf(owner).call();145 expect(+balance).to.equal(0);146 }147 });148149 itWeb3('Can perform transfer()', async ({ web3, api }) => {150 const collection = await createCollectionExpectSuccess({151 mode: { type: 'NFT' },152 });153 const alice = privateKey('//Alice');154155 const owner = createEthAccount(web3);156 await transferBalanceToEth(api, alice, owner, 999999999999999);157158 const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', { ethereum: owner });159160 const receiver = createEthAccount(web3);161 await transferBalanceToEth(api, alice, receiver, 999999999999999);162163 const address = collectionIdToAddress(collection);164 const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: owner, ...GAS_ARGS});165166 {167 const result = await contract.methods.transfer(receiver, tokenId).send({ from: owner });168 await waitNewBlocks(api, 1);169 const events = normalizeEvents(result.events);170 expect(events).to.be.deep.equal([171 {172 address,173 event: 'Transfer',174 args: {175 from: owner,176 to: receiver,177 tokenId: tokenId.toString(),178 },179 },180 ]);181 }182183 {184 const balance = await contract.methods.balanceOf(owner).call();185 expect(+balance).to.equal(0);186 }187188 {189 const balance = await contract.methods.balanceOf(receiver).call();190 expect(+balance).to.equal(1);191 }192 });193});194195describe('NFT: Fees', () => {196 itWeb3('approve() call fee is less than 0.2UNQ', async ({ web3, api }) => {197 const collection = await createCollectionExpectSuccess({198 mode: { type: 'NFT' },199 });200 const alice = privateKey('//Alice');201202 const owner = await createEthAccountWithBalance(api, web3);203 const spender = createEthAccount(web3);204205 const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', { ethereum: owner });206207 const address = collectionIdToAddress(collection);208 const contract = new web3.eth.Contract(nonFungibleAbi as any, address, { from: owner, ...GAS_ARGS });209210 const cost = await recordEthFee(api, owner, () => contract.methods.approve(spender, tokenId).send({ from: owner }));211 expect(cost < BigInt(0.2 * Number(UNIQUE)));212 });213214 itWeb3('transferFrom() call fee is less than 0.2UNQ', async ({ web3, api }) => {215 const collection = await createCollectionExpectSuccess({216 mode: { type: 'NFT' },217 });218 const alice = privateKey('//Alice');219 220 const owner = await createEthAccountWithBalance(api, web3);221 const spender = await createEthAccountWithBalance(api, web3);222223 const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', { ethereum: owner });224225 const address = collectionIdToAddress(collection);226 const contract = new web3.eth.Contract(nonFungibleAbi as any, address, { from: owner, ...GAS_ARGS });227228 await contract.methods.approve(spender, tokenId).send({ from: owner });229230 const cost = await recordEthFee(api, spender, () => contract.methods.transferFrom(owner, spender, tokenId).send({ from: spender }));231 expect(cost < BigInt(0.2 * Number(UNIQUE)));232 });233234 itWeb3('transfer() call fee is less than 0.2UNQ', async ({ web3, api }) => {235 const collection = await createCollectionExpectSuccess({236 mode: { type: 'NFT' },237 });238 const alice = privateKey('//Alice');239240 const owner = await createEthAccountWithBalance(api, web3);241 const receiver = createEthAccount(web3);242243 const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', { ethereum: owner });244245 const address = collectionIdToAddress(collection);246 const contract = new web3.eth.Contract(nonFungibleAbi as any, address, { from: owner, ...GAS_ARGS });247248 const cost = await recordEthFee(api, owner, () => contract.methods.transfer(receiver, tokenId).send({ from: owner }));249 expect(cost < BigInt(0.2 * Number(UNIQUE)));250 });251});252253describe('NFT: Substrate calls', () => {254 itWeb3('Events emitted for approve()', async ({ web3 }) => {255 const collection = await createCollectionExpectSuccess({256 mode: { type: 'NFT' },257 });258 const alice = privateKey('//Alice');259260 const receiver = createEthAccount(web3);261262 const tokenId = await createItemExpectSuccess(alice, collection, 'NFT');263264 const address = collectionIdToAddress(collection);265 const contract = new web3.eth.Contract(nonFungibleAbi as any, address);266267 const events = await recordEvents(contract, async () => {268 await approveExpectSuccess(collection, tokenId, alice, { ethereum: receiver }, 1);269 });270271 expect(events).to.be.deep.equal([272 {273 address,274 event: 'Approval',275 args: {276 owner: subToEth(alice.address),277 approved: receiver,278 tokenId: tokenId.toString(),279 },280 },281 ]);282 });283284 itWeb3('Events emitted for transferFrom()', async ({ web3 }) => {285 const collection = await createCollectionExpectSuccess({286 mode: { type: 'NFT' },287 });288 const alice = privateKey('//Alice');289 const bob = privateKey('//Bob');290291 const receiver = createEthAccount(web3);292293 const tokenId = await createItemExpectSuccess(alice, collection, 'NFT');294 await approveExpectSuccess(collection, tokenId, alice, bob, 1);295296 const address = collectionIdToAddress(collection);297 const contract = new web3.eth.Contract(nonFungibleAbi as any, address);298299 const events = await recordEvents(contract, async () => {300 await transferFromExpectSuccess(collection, tokenId, bob, alice, { ethereum: receiver }, 1, 'NFT');301 });302303 expect(events).to.be.deep.equal([304 {305 address,306 event: 'Transfer',307 args: {308 from: subToEth(alice.address),309 to: receiver,310 tokenId: tokenId.toString(),311 },312 },313 ]);314 });315316 itWeb3('Events emitted for transfer()', async ({ web3 }) => {317 const collection = await createCollectionExpectSuccess({318 mode: { type: 'NFT' },319 });320 const alice = privateKey('//Alice');321322 const receiver = createEthAccount(web3);323324 const tokenId = await createItemExpectSuccess(alice, collection, 'NFT');325326 const address = collectionIdToAddress(collection);327 const contract = new web3.eth.Contract(nonFungibleAbi as any, address);328329 const events = await recordEvents(contract, async () => {330 await transferExpectSuccess(collection, tokenId, alice, { ethereum: receiver }, 1, 'NFT');331 });332333 expect(events).to.be.deep.equal([334 {335 address,336 event: 'Transfer',337 args: {338 from: subToEth(alice.address),339 to: receiver,340 tokenId: tokenId.toString(),341 },342 },343 ]);344 });345});1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56import privateKey from '../substrate/privateKey';7import { approveExpectSuccess, burnItemExpectSuccess, createCollectionExpectSuccess, createItemExpectSuccess, transferExpectSuccess, transferFromExpectSuccess, UNIQUE } from '../util/helpers';8import { collectionIdToAddress, createEthAccount, createEthAccountWithBalance, GAS_ARGS, itWeb3, normalizeEvents, recordEthFee, recordEvents, subToEth, transferBalanceToEth } from './util/helpers';9import { evmToAddress } from '@polkadot/util-crypto';10import nonFungibleAbi from './nonFungibleAbi.json';11import { expect } from 'chai';12import waitNewBlocks from '../substrate/wait-new-blocks';13import { submitTransactionAsync } from '../substrate/substrate-api';1415describe('NFT: Information getting', () => {16 itWeb3('totalSupply', async ({ api, web3 }) => {17 const collection = await createCollectionExpectSuccess({18 mode: { type: 'NFT' },19 });20 const alice = privateKey('//Alice');21 const caller = await createEthAccountWithBalance(api, web3);2223 await createItemExpectSuccess(alice, collection, 'NFT', { substrate: alice.address });2425 const address = collectionIdToAddress(collection);26 const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS});27 const totalSupply = await contract.methods.totalSupply().call();2829 // FIXME: always equals to 0, because this method is not implemented30 expect(totalSupply).to.equal('0');31 });3233 itWeb3('balanceOf', async ({ api, web3 }) => {34 const collection = await createCollectionExpectSuccess({35 mode: { type: 'NFT' },36 });37 const alice = privateKey('//Alice');3839 const caller = await createEthAccountWithBalance(api, web3);40 await createItemExpectSuccess(alice, collection, 'NFT', { ethereum: caller });41 await createItemExpectSuccess(alice, collection, 'NFT', { ethereum: caller });42 await createItemExpectSuccess(alice, collection, 'NFT', { ethereum: caller });4344 const address = collectionIdToAddress(collection);45 const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS});46 const balance = await contract.methods.balanceOf(caller).call();4748 expect(balance).to.equal('3');49 });5051 itWeb3('ownerOf', async ({ api, web3 }) => {52 const collection = await createCollectionExpectSuccess({53 mode: { type: 'NFT' },54 });55 const alice = privateKey('//Alice');5657 const caller = await createEthAccountWithBalance(api, web3);58 const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', { ethereum: caller });5960 const address = collectionIdToAddress(collection);61 const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS});62 const owner = await contract.methods.ownerOf(tokenId).call();6364 expect(owner).to.equal(caller);65 });66});6768describe('NFT: Plain calls', () => {69 itWeb3('Can perform mint()', async ({ web3, api }) => {70 const collection = await createCollectionExpectSuccess({71 mode: { type: 'NFT' },72 });73 const alice = privateKey('//Alice');7475 const caller = await createEthAccountWithBalance(api, web3);76 const changeAdminTx = api.tx.nft.addCollectionAdmin(collection, { ethereum: caller });77 await submitTransactionAsync(alice, changeAdminTx);78 const receiver = createEthAccount(web3);7980 const address = collectionIdToAddress(collection);81 const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS});8283 {84 const nextTokenId = await contract.methods.nextTokenId().call();85 expect(nextTokenId).to.be.equal('1');86 const result = await contract.methods.mintWithTokenURI(87 receiver,88 nextTokenId,89 'Test URI',90 ).send({from: caller});91 const events = normalizeEvents(result.events);9293 expect(events).to.be.deep.equal([94 {95 address,96 event: 'Transfer',97 args: {98 from: '0x0000000000000000000000000000000000000000',99 to: receiver,100 tokenId: nextTokenId,101 },102 },103 ]);104105 await waitNewBlocks(api, 1);106 expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI');107 }108 });109110 itWeb3('Can perform burn()', async ({ web3, api }) => {111 const collection = await createCollectionExpectSuccess({112 mode: {type: 'NFT'},113 });114 const alice = privateKey('//Alice');115116 const owner = await createEthAccountWithBalance(api, web3);117118 const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', { ethereum: owner });119 120 const address = collectionIdToAddress(collection);121 const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: owner, ...GAS_ARGS});122123 {124 const result = await contract.methods.burn(tokenId).send({ from: owner });125 const events = normalizeEvents(result.events);126 127 expect(events).to.be.deep.equal([128 {129 address,130 event: 'Transfer',131 args: {132 from: owner,133 to: '0x0000000000000000000000000000000000000000',134 tokenId: tokenId.toString(),135 },136 },137 ]);138 }139 });140141 itWeb3('Can perform approve()', async ({ web3, api }) => {142 const collection = await createCollectionExpectSuccess({143 mode: { type: 'NFT' },144 });145 const alice = privateKey('//Alice');146147 const owner = createEthAccount(web3);148 await transferBalanceToEth(api, alice, owner, 999999999999999);149150 const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', { ethereum: owner });151152 const spender = createEthAccount(web3);153154 const address = collectionIdToAddress(collection);155 const contract = new web3.eth.Contract(nonFungibleAbi as any, address);156157 {158 const result = await contract.methods.approve(spender, tokenId).send({ from: owner, gas: '0x1000000', gasPrice: '0x01' });159 const events = normalizeEvents(result.events);160161 expect(events).to.be.deep.equal([162 {163 address,164 event: 'Approval',165 args: {166 owner,167 approved: spender,168 tokenId: tokenId.toString(),169 },170 },171 ]);172 }173 });174175 itWeb3('Can perform transferFrom()', async ({ web3, api }) => {176 const collection = await createCollectionExpectSuccess({177 mode: { type: 'NFT' },178 });179 const alice = privateKey('//Alice');180181 const owner = createEthAccount(web3);182 await transferBalanceToEth(api, alice, owner, 999999999999999);183184 const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', { ethereum: owner });185186 const spender = createEthAccount(web3);187 await transferBalanceToEth(api, alice, spender, 999999999999999);188189 const receiver = createEthAccount(web3);190191 const address = collectionIdToAddress(collection);192 const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: owner, ...GAS_ARGS});193194 await contract.methods.approve(spender, tokenId).send({ from: owner });195196 {197 const result = await contract.methods.transferFrom(owner, receiver, tokenId).send({ from: spender });198 const events = normalizeEvents(result.events);199 expect(events).to.be.deep.equal([200 {201 address,202 event: 'Transfer',203 args: {204 from: owner,205 to: receiver,206 tokenId: tokenId.toString(),207 },208 },209 ]);210 }211212 {213 const balance = await contract.methods.balanceOf(receiver).call();214 expect(+balance).to.equal(1);215 }216217 {218 const balance = await contract.methods.balanceOf(owner).call();219 expect(+balance).to.equal(0);220 }221 });222223 itWeb3('Can perform transfer()', async ({ web3, api }) => {224 const collection = await createCollectionExpectSuccess({225 mode: { type: 'NFT' },226 });227 const alice = privateKey('//Alice');228229 const owner = createEthAccount(web3);230 await transferBalanceToEth(api, alice, owner, 999999999999999);231232 const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', { ethereum: owner });233234 const receiver = createEthAccount(web3);235 await transferBalanceToEth(api, alice, receiver, 999999999999999);236237 const address = collectionIdToAddress(collection);238 const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: owner, ...GAS_ARGS});239240 {241 const result = await contract.methods.transfer(receiver, tokenId).send({ from: owner });242 await waitNewBlocks(api, 1);243 const events = normalizeEvents(result.events);244 expect(events).to.be.deep.equal([245 {246 address,247 event: 'Transfer',248 args: {249 from: owner,250 to: receiver,251 tokenId: tokenId.toString(),252 },253 },254 ]);255 }256257 {258 const balance = await contract.methods.balanceOf(owner).call();259 expect(+balance).to.equal(0);260 }261262 {263 const balance = await contract.methods.balanceOf(receiver).call();264 expect(+balance).to.equal(1);265 }266 });267});268269describe('NFT: Fees', () => {270 itWeb3('approve() call fee is less than 0.2UNQ', async ({ web3, api }) => {271 const collection = await createCollectionExpectSuccess({272 mode: { type: 'NFT' },273 });274 const alice = privateKey('//Alice');275276 const owner = await createEthAccountWithBalance(api, web3);277 const spender = createEthAccount(web3);278279 const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', { ethereum: owner });280281 const address = collectionIdToAddress(collection);282 const contract = new web3.eth.Contract(nonFungibleAbi as any, address, { from: owner, ...GAS_ARGS });283284 const cost = await recordEthFee(api, owner, () => contract.methods.approve(spender, tokenId).send({ from: owner }));285 expect(cost < BigInt(0.2 * Number(UNIQUE)));286 });287288 itWeb3('transferFrom() call fee is less than 0.2UNQ', async ({ web3, api }) => {289 const collection = await createCollectionExpectSuccess({290 mode: { type: 'NFT' },291 });292 const alice = privateKey('//Alice');293 294 const owner = await createEthAccountWithBalance(api, web3);295 const spender = await createEthAccountWithBalance(api, web3);296297 const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', { ethereum: owner });298299 const address = collectionIdToAddress(collection);300 const contract = new web3.eth.Contract(nonFungibleAbi as any, address, { from: owner, ...GAS_ARGS });301302 await contract.methods.approve(spender, tokenId).send({ from: owner });303304 const cost = await recordEthFee(api, spender, () => contract.methods.transferFrom(owner, spender, tokenId).send({ from: spender }));305 expect(cost < BigInt(0.2 * Number(UNIQUE)));306 });307308 itWeb3('transfer() call fee is less than 0.2UNQ', async ({ web3, api }) => {309 const collection = await createCollectionExpectSuccess({310 mode: { type: 'NFT' },311 });312 const alice = privateKey('//Alice');313314 const owner = await createEthAccountWithBalance(api, web3);315 const receiver = createEthAccount(web3);316317 const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', { ethereum: owner });318319 const address = collectionIdToAddress(collection);320 const contract = new web3.eth.Contract(nonFungibleAbi as any, address, { from: owner, ...GAS_ARGS });321322 const cost = await recordEthFee(api, owner, () => contract.methods.transfer(receiver, tokenId).send({ from: owner }));323 expect(cost < BigInt(0.2 * Number(UNIQUE)));324 });325});326327describe('NFT: Substrate calls', () => {328 itWeb3('Events emitted for mint()', async ({ web3 }) => {329 const collection = await createCollectionExpectSuccess({330 mode: { type: 'NFT' },331 });332 const alice = privateKey('//Alice');333334 const address = collectionIdToAddress(collection);335 const contract = new web3.eth.Contract(nonFungibleAbi as any, address);336337 let tokenId: number;338 const events = await recordEvents(contract, async () => {339 tokenId = await createItemExpectSuccess(alice, collection, 'NFT');340 });341342 expect(events).to.be.deep.equal([343 {344 address,345 event: 'Transfer',346 args: {347 from: '0x0000000000000000000000000000000000000000',348 to: subToEth(alice.address),349 tokenId: tokenId!.toString(),350 },351 },352 ]);353 });354355 itWeb3('Events emitted for burn()', async ({ web3 }) => {356 const collection = await createCollectionExpectSuccess({357 mode: { type: 'NFT' },358 });359 const alice = privateKey('//Alice');360361 const address = collectionIdToAddress(collection);362 const contract = new web3.eth.Contract(nonFungibleAbi as any, address);363364 const tokenId = await createItemExpectSuccess(alice, collection, 'NFT');365 const events = await recordEvents(contract, async () => {366 await burnItemExpectSuccess(alice, collection, tokenId);367 });368369 expect(events).to.be.deep.equal([370 {371 address,372 event: 'Transfer',373 args: {374 from: subToEth(alice.address),375 to: '0x0000000000000000000000000000000000000000',376 tokenId: tokenId.toString(),377 },378 },379 ]);380 });381382 itWeb3('Events emitted for approve()', async ({ web3 }) => {383 const collection = await createCollectionExpectSuccess({384 mode: { type: 'NFT' },385 });386 const alice = privateKey('//Alice');387388 const receiver = createEthAccount(web3);389390 const tokenId = await createItemExpectSuccess(alice, collection, 'NFT');391392 const address = collectionIdToAddress(collection);393 const contract = new web3.eth.Contract(nonFungibleAbi as any, address);394395 const events = await recordEvents(contract, async () => {396 await approveExpectSuccess(collection, tokenId, alice, { ethereum: receiver }, 1);397 });398399 expect(events).to.be.deep.equal([400 {401 address,402 event: 'Approval',403 args: {404 owner: subToEth(alice.address),405 approved: receiver,406 tokenId: tokenId.toString(),407 },408 },409 ]);410 });411412 itWeb3('Events emitted for transferFrom()', async ({ web3 }) => {413 const collection = await createCollectionExpectSuccess({414 mode: { type: 'NFT' },415 });416 const alice = privateKey('//Alice');417 const bob = privateKey('//Bob');418419 const receiver = createEthAccount(web3);420421 const tokenId = await createItemExpectSuccess(alice, collection, 'NFT');422 await approveExpectSuccess(collection, tokenId, alice, bob, 1);423424 const address = collectionIdToAddress(collection);425 const contract = new web3.eth.Contract(nonFungibleAbi as any, address);426427 const events = await recordEvents(contract, async () => {428 await transferFromExpectSuccess(collection, tokenId, bob, alice, { ethereum: receiver }, 1, 'NFT');429 });430431 expect(events).to.be.deep.equal([432 {433 address,434 event: 'Transfer',435 args: {436 from: subToEth(alice.address),437 to: receiver,438 tokenId: tokenId.toString(),439 },440 },441 ]);442 });443444 itWeb3('Events emitted for transfer()', async ({ web3 }) => {445 const collection = await createCollectionExpectSuccess({446 mode: { type: 'NFT' },447 });448 const alice = privateKey('//Alice');449450 const receiver = createEthAccount(web3);451452 const tokenId = await createItemExpectSuccess(alice, collection, 'NFT');453454 const address = collectionIdToAddress(collection);455 const contract = new web3.eth.Contract(nonFungibleAbi as any, address);456457 const events = await recordEvents(contract, async () => {458 await transferExpectSuccess(collection, tokenId, alice, { ethereum: receiver }, 1, 'NFT');459 });460461 expect(events).to.be.deep.equal([462 {463 address,464 event: 'Transfer',465 args: {466 from: subToEth(alice.address),467 to: receiver,468 tokenId: tokenId.toString(),469 },470 },471 ]);472 });473});tests/src/eth/nonFungibleAbi.jsondiffbeforeafterboth--- a/tests/src/eth/nonFungibleAbi.json
+++ b/tests/src/eth/nonFungibleAbi.json
@@ -50,6 +50,12 @@
"type": "event"
},
{
+ "anonymous": true,
+ "inputs": [],
+ "name": "MintingFinished",
+ "type": "event"
+ },
+ {
"anonymous": false,
"inputs": [
{
@@ -89,7 +95,7 @@
],
"name": "approve",
"outputs": [],
- "stateMutability": "payable",
+ "stateMutability": "nonpayable",
"type": "function"
},
{
@@ -119,6 +125,32 @@
"type": "uint256"
}
],
+ "name": "burn",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "finishMinting",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "tokenId",
+ "type": "uint256"
+ }
+ ],
"name": "getApproved",
"outputs": [
{
@@ -146,11 +178,77 @@
"name": "isApprovedForAll",
"outputs": [
{
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "to",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "tokenId",
+ "type": "uint256"
+ }
+ ],
+ "name": "mint",
+ "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"
+ }
+ ],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "mintingFinished",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
"stateMutability": "view",
"type": "function"
},
@@ -160,7 +258,7 @@
"outputs": [
{
"internalType": "string",
- "name": "res_name",
+ "name": "",
"type": "string"
}
],
@@ -168,6 +266,19 @@
"type": "function"
},
{
+ "inputs": [],
+ "name": "nextTokenId",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
"inputs": [
{
"internalType": "uint256",
@@ -206,7 +317,7 @@
],
"name": "safeTransferFrom",
"outputs": [],
- "stateMutability": "payable",
+ "stateMutability": "nonpayable",
"type": "function"
},
{
@@ -232,9 +343,9 @@
"type": "bytes"
}
],
- "name": "safeTransferFrom",
+ "name": "safeTransferFromWithData",
"outputs": [],
- "stateMutability": "payable",
+ "stateMutability": "nonpayable",
"type": "function"
},
{
@@ -258,9 +369,9 @@
{
"inputs": [
{
- "internalType": "bytes4",
- "name": "interfaceID",
- "type": "bytes4"
+ "internalType": "uint32",
+ "name": "interfaceId",
+ "type": "uint32"
}
],
"name": "supportsInterface",
@@ -271,7 +382,7 @@
"type": "bool"
}
],
- "stateMutability": "pure",
+ "stateMutability": "view",
"type": "function"
},
{
@@ -280,7 +391,7 @@
"outputs": [
{
"internalType": "string",
- "name": "res_symbol",
+ "name": "",
"type": "string"
}
],
@@ -366,11 +477,6 @@
"inputs": [
{
"internalType": "address",
- "name": "from",
- "type": "address"
- },
- {
- "internalType": "address",
"name": "to",
"type": "address"
},
@@ -380,15 +486,20 @@
"type": "uint256"
}
],
- "name": "transferFrom",
+ "name": "transfer",
"outputs": [],
- "stateMutability": "payable",
+ "stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
+ "name": "from",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
"name": "to",
"type": "address"
},
@@ -398,9 +509,9 @@
"type": "uint256"
}
],
- "name": "transfer",
+ "name": "transferFrom",
"outputs": [],
- "stateMutability": "payable",
+ "stateMutability": "nonpayable",
"type": "function"
}
]
\ No newline at end of file