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.rsdiffbeforeafterboth1//! TODO: I misunterstood therminology, abi IS rlp encoded, so2//! this module should be replaced with rlp crate34#![allow(dead_code)]56#[cfg(not(feature = "std"))]7use alloc::{8 string::{String, ToString},9 vec::Vec,10};11use evm_core::ExitError;12use primitive_types::{H160, U256};1314use crate::{execution::Error, types::string};15use crate::execution::Result;1617const ABI_ALIGNMENT: usize = 32;1819#[derive(Clone)]20pub struct AbiReader<'i> {21 buf: &'i [u8],22 offset: usize,23}24impl<'i> AbiReader<'i> {25 pub fn new(buf: &'i [u8]) -> Self {26 Self { buf, offset: 0 }27 }28 pub fn new_call(buf: &'i [u8]) -> Result<(u32, Self)> {29 if buf.len() < 4 {30 return Err(Error::Error(ExitError::OutOfOffset));31 }32 let mut method_id = [0; 4];33 method_id.copy_from_slice(&buf[0..4]);3435 Ok((u32::from_be_bytes(method_id), Self { buf, offset: 4 }))36 }3738 fn read_padleft<const S: usize>(&mut self) -> Result<[u8; S]> {39 if self.buf.len() - self.offset < 32 {40 return Err(Error::Error(ExitError::OutOfOffset));41 }42 let mut block = [0; S];43 // Verify padding is empty44 if !self.buf[self.offset..self.offset + ABI_ALIGNMENT - S]45 .iter()46 .all(|&v| v == 0)47 {48 return Err(Error::Error(ExitError::InvalidRange));49 }50 block.copy_from_slice(51 &self.buf[self.offset + ABI_ALIGNMENT - S..self.offset + ABI_ALIGNMENT],52 );53 self.offset += ABI_ALIGNMENT;54 Ok(block)55 }5657 pub fn address(&mut self) -> Result<H160> {58 Ok(H160(self.read_padleft()?))59 }6061 pub fn bool(&mut self) -> Result<bool> {62 let data: [u8; 1] = self.read_padleft()?;63 match data[0] {64 0 => Ok(false),65 1 => Ok(true),66 _ => Err(Error::Error(ExitError::InvalidRange)),67 }68 }6970 pub fn bytes4(&mut self) -> Result<[u8; 4]> {71 self.read_padleft()72 }7374 pub fn bytes(&mut self) -> Result<Vec<u8>> {75 let mut subresult = self.subresult()?;76 let length = subresult.read_usize()?;77 if subresult.buf.len() <= subresult.offset + length {78 return Err(Error::Error(ExitError::OutOfOffset));79 }80 Ok(subresult.buf[subresult.offset..subresult.offset + length].into())81 }8283 pub fn uint32(&mut self) -> Result<u32> {84 Ok(u32::from_be_bytes(self.read_padleft()?))85 }8687 pub fn uint128(&mut self) -> Result<u128> {88 Ok(u128::from_be_bytes(self.read_padleft()?))89 }9091 pub fn uint256(&mut self) -> Result<U256> {92 let buf: [u8; 32] = self.read_padleft()?;93 Ok(U256::from_big_endian(&buf))94 }9596 pub fn uint64(&mut self) -> Result<u64> {97 Ok(u64::from_be_bytes(self.read_padleft()?))98 }99100 pub fn read_usize(&mut self) -> Result<usize> {101 Ok(usize::from_be_bytes(self.read_padleft()?))102 }103104 fn subresult(&mut self) -> Result<AbiReader<'i>> {105 let offset = self.read_usize()?;106 Ok(AbiReader {107 buf: self.buf,108 offset: offset + self.offset,109 })110 }111112 pub fn is_finished(&self) -> bool {113 self.buf.len() == self.offset114 }115}116117#[derive(Default)]118pub struct AbiWriter {119 static_part: Vec<u8>,120 dynamic_part: Vec<(usize, AbiWriter)>,121}122impl AbiWriter {123 pub fn new() -> Self {124 Self::default()125 }126 pub fn new_call(method_id: u32) -> Self {127 let mut val = Self::new();128 val.static_part.extend(&method_id.to_be_bytes());129 val130 }131132 fn write_padleft(&mut self, block: &[u8]) {133 assert!(block.len() <= ABI_ALIGNMENT);134 self.static_part135 .extend(&[0; ABI_ALIGNMENT][0..ABI_ALIGNMENT - block.len()]);136 self.static_part.extend(block);137 }138139 fn write_padright(&mut self, bytes: &[u8]) {140 assert!(bytes.len() <= ABI_ALIGNMENT);141 self.static_part.extend(bytes);142 self.static_part143 .extend(&[0; ABI_ALIGNMENT][0..ABI_ALIGNMENT - bytes.len()]);144 }145146 pub fn address(&mut self, address: &H160) {147 self.write_padleft(&address.0)148 }149150 pub fn bool(&mut self, value: &bool) {151 self.write_padleft(&[if *value { 1 } else { 0 }])152 }153154 pub fn uint8(&mut self, value: &u8) {155 self.write_padleft(&[*value])156 }157158 pub fn uint32(&mut self, value: &u32) {159 self.write_padleft(&u32::to_be_bytes(*value))160 }161162 pub fn uint128(&mut self, value: &u128) {163 self.write_padleft(&u128::to_be_bytes(*value))164 }165166 pub fn uint256(&mut self, value: &U256) {167 let mut out = [0; 32];168 value.to_big_endian(&mut out);169 self.write_padleft(&out)170 }171172 pub fn write_usize(&mut self, value: &usize) {173 self.write_padleft(&usize::to_be_bytes(*value))174 }175176 pub fn write_subresult(&mut self, result: Self) {177 self.dynamic_part.push((self.static_part.len(), result));178 // Empty block, to be filled later179 self.write_padleft(&[]);180 }181182 pub fn memory(&mut self, value: &[u8]) {183 let mut sub = Self::new();184 sub.write_usize(&value.len());185 for chunk in value.chunks(ABI_ALIGNMENT) {186 sub.write_padright(chunk);187 }188 self.write_subresult(sub);189 }190191 pub fn string(&mut self, value: &str) {192 self.memory(value.as_bytes())193 }194195 pub fn finish(mut self) -> Vec<u8> {196 for (static_offset, part) in self.dynamic_part {197 let part_offset = self.static_part.len();198199 let encoded_dynamic_offset = usize::to_be_bytes(part_offset - static_offset);200 self.static_part[static_offset + ABI_ALIGNMENT - encoded_dynamic_offset.len()201 ..static_offset + ABI_ALIGNMENT]202 .copy_from_slice(&encoded_dynamic_offset);203 self.static_part.extend(part.finish())204 }205 self.static_part206 }207}208209pub trait AbiRead<T> {210 fn abi_read(&mut self) -> Result<T>;211}212213macro_rules! impl_abi_readable {214 ($ty:ty, $method:ident) => {215 impl AbiRead<$ty> for AbiReader<'_> {216 fn abi_read(&mut self) -> Result<$ty> {217 self.$method()218 }219 }220 };221}222223impl_abi_readable!(u32, uint32);224impl_abi_readable!(u128, uint128);225impl_abi_readable!(U256, uint256);226impl_abi_readable!(H160, address);227impl_abi_readable!(Vec<u8>, bytes);228impl_abi_readable!(bool, bool);229230pub trait AbiWrite {231 fn abi_write(&self, writer: &mut AbiWriter);232}233234macro_rules! impl_abi_writeable {235 ($ty:ty, $method:ident) => {236 impl AbiWrite for $ty {237 fn abi_write(&self, writer: &mut AbiWriter) {238 writer.$method(&self)239 }240 }241 };242}243244impl_abi_writeable!(u8, uint8);245impl_abi_writeable!(u32, uint32);246impl_abi_writeable!(u128, uint128);247impl_abi_writeable!(U256, uint256);248impl_abi_writeable!(H160, address);249impl_abi_writeable!(bool, bool);250impl_abi_writeable!(&str, string);251impl AbiWrite for &string {252 fn abi_write(&self, writer: &mut AbiWriter) {253 writer.string(self)254 }255}256257impl AbiWrite for () {258 fn abi_write(&self, _writer: &mut AbiWriter) {}259}260261#[macro_export]262macro_rules! abi_decode {263 ($reader:expr, $($name:ident: $typ:ident),+ $(,)?) => {264 $(265 let $name = $reader.$typ()?;266 )+267 }268}269#[macro_export]270macro_rules! abi_encode {271 ($($typ:ident($value:expr)),* $(,)?) => {{272 #[allow(unused_mut)]273 let mut writer = ::evm_coder::abi::AbiWriter::new();274 $(275 writer.$typ($value);276 )*277 writer278 }};279 (call $val:expr; $($typ:ident($value:expr)),* $(,)?) => {{280 #[allow(unused_mut)]281 let mut writer = ::evm_coder::abi::AbiWriter::new_call($val);282 $(283 writer.$typ($value);284 )*285 writer286 }}287}1//! TODO: I misunterstood therminology, abi IS rlp encoded, so2//! this module should be replaced with rlp crate34#![allow(dead_code)]56#[cfg(not(feature = "std"))]7use alloc::{8 string::{String, ToString},9 vec::Vec,10};11use evm_core::ExitError;12use primitive_types::{H160, U256};1314use crate::{execution::Error, types::string};15use crate::execution::Result;1617const ABI_ALIGNMENT: usize = 32;1819#[derive(Clone)]20pub struct AbiReader<'i> {21 buf: &'i [u8],22 subresult_offset: usize,23 offset: usize,24}25impl<'i> AbiReader<'i> {26 pub fn new(buf: &'i [u8]) -> Self {27 Self {28 buf,29 subresult_offset: 0,30 offset: 0,31 }32 }33 pub fn new_call(buf: &'i [u8]) -> Result<(u32, Self)> {34 if buf.len() < 4 {35 return Err(Error::Error(ExitError::OutOfOffset));36 }37 let mut method_id = [0; 4];38 method_id.copy_from_slice(&buf[0..4]);3940 Ok((41 u32::from_be_bytes(method_id),42 Self {43 buf,44 subresult_offset: 4,45 offset: 4,46 },47 ))48 }4950 fn read_padleft<const S: usize>(&mut self) -> Result<[u8; S]> {51 if self.buf.len() - self.offset < ABI_ALIGNMENT {52 return Err(Error::Error(ExitError::OutOfOffset));53 }54 let mut block = [0; S];55 // Verify padding is empty56 if !self.buf[self.offset..self.offset + ABI_ALIGNMENT - S]57 .iter()58 .all(|&v| v == 0)59 {60 return Err(Error::Error(ExitError::InvalidRange));61 }62 block.copy_from_slice(63 &self.buf[self.offset + ABI_ALIGNMENT - S..self.offset + ABI_ALIGNMENT],64 );65 self.offset += ABI_ALIGNMENT;66 Ok(block)67 }6869 pub fn address(&mut self) -> Result<H160> {70 Ok(H160(self.read_padleft()?))71 }7273 pub fn bool(&mut self) -> Result<bool> {74 let data: [u8; 1] = self.read_padleft()?;75 match data[0] {76 0 => Ok(false),77 1 => Ok(true),78 _ => Err(Error::Error(ExitError::InvalidRange)),79 }80 }8182 pub fn bytes4(&mut self) -> Result<[u8; 4]> {83 self.read_padleft()84 }8586 pub fn bytes(&mut self) -> Result<Vec<u8>> {87 let mut subresult = self.subresult()?;88 let length = subresult.read_usize()?;89 if subresult.buf.len() <= subresult.offset + length {90 return Err(Error::Error(ExitError::OutOfOffset));91 }92 Ok(subresult.buf[subresult.offset..subresult.offset + length].into())93 }94 pub fn string(&mut self) -> Result<string> {95 string::from_utf8(self.bytes()?).map_err(|_| Error::Error(ExitError::InvalidRange))96 }9798 pub fn uint32(&mut self) -> Result<u32> {99 Ok(u32::from_be_bytes(self.read_padleft()?))100 }101102 pub fn uint128(&mut self) -> Result<u128> {103 Ok(u128::from_be_bytes(self.read_padleft()?))104 }105106 pub fn uint256(&mut self) -> Result<U256> {107 let buf: [u8; 32] = self.read_padleft()?;108 Ok(U256::from_big_endian(&buf))109 }110111 pub fn uint64(&mut self) -> Result<u64> {112 Ok(u64::from_be_bytes(self.read_padleft()?))113 }114115 pub fn read_usize(&mut self) -> Result<usize> {116 Ok(usize::from_be_bytes(self.read_padleft()?))117 }118119 fn subresult(&mut self) -> Result<AbiReader<'i>> {120 let offset = self.read_usize()?;121 if offset + self.subresult_offset > self.buf.len() {122 return Err(Error::Error(ExitError::InvalidRange));123 }124 Ok(AbiReader {125 buf: self.buf,126 subresult_offset: offset + self.subresult_offset,127 offset: offset + self.subresult_offset,128 })129 }130131 pub fn is_finished(&self) -> bool {132 self.buf.len() == self.offset133 }134}135136#[derive(Default)]137pub struct AbiWriter {138 static_part: Vec<u8>,139 dynamic_part: Vec<(usize, AbiWriter)>,140}141impl AbiWriter {142 pub fn new() -> Self {143 Self::default()144 }145 pub fn new_call(method_id: u32) -> Self {146 let mut val = Self::new();147 val.static_part.extend(&method_id.to_be_bytes());148 val149 }150151 fn write_padleft(&mut self, block: &[u8]) {152 assert!(block.len() <= ABI_ALIGNMENT);153 self.static_part154 .extend(&[0; ABI_ALIGNMENT][0..ABI_ALIGNMENT - block.len()]);155 self.static_part.extend(block);156 }157158 fn write_padright(&mut self, bytes: &[u8]) {159 assert!(bytes.len() <= ABI_ALIGNMENT);160 self.static_part.extend(bytes);161 self.static_part162 .extend(&[0; ABI_ALIGNMENT][0..ABI_ALIGNMENT - bytes.len()]);163 }164165 pub fn address(&mut self, address: &H160) {166 self.write_padleft(&address.0)167 }168169 pub fn bool(&mut self, value: &bool) {170 self.write_padleft(&[if *value { 1 } else { 0 }])171 }172173 pub fn uint8(&mut self, value: &u8) {174 self.write_padleft(&[*value])175 }176177 pub fn uint32(&mut self, value: &u32) {178 self.write_padleft(&u32::to_be_bytes(*value))179 }180181 pub fn uint128(&mut self, value: &u128) {182 self.write_padleft(&u128::to_be_bytes(*value))183 }184185 pub fn uint256(&mut self, value: &U256) {186 let mut out = [0; 32];187 value.to_big_endian(&mut out);188 self.write_padleft(&out)189 }190191 pub fn write_usize(&mut self, value: &usize) {192 self.write_padleft(&usize::to_be_bytes(*value))193 }194195 pub fn write_subresult(&mut self, result: Self) {196 self.dynamic_part.push((self.static_part.len(), result));197 // Empty block, to be filled later198 self.write_padleft(&[]);199 }200201 pub fn memory(&mut self, value: &[u8]) {202 let mut sub = Self::new();203 sub.write_usize(&value.len());204 for chunk in value.chunks(ABI_ALIGNMENT) {205 sub.write_padright(chunk);206 }207 self.write_subresult(sub);208 }209210 pub fn string(&mut self, value: &str) {211 self.memory(value.as_bytes())212 }213214 pub fn finish(mut self) -> Vec<u8> {215 for (static_offset, part) in self.dynamic_part {216 let part_offset = self.static_part.len();217218 let encoded_dynamic_offset = usize::to_be_bytes(part_offset);219 self.static_part[static_offset + ABI_ALIGNMENT - encoded_dynamic_offset.len()220 ..static_offset + ABI_ALIGNMENT]221 .copy_from_slice(&encoded_dynamic_offset);222 self.static_part.extend(part.finish())223 }224 self.static_part225 }226}227228pub trait AbiRead<T> {229 fn abi_read(&mut self) -> Result<T>;230}231232macro_rules! impl_abi_readable {233 ($ty:ty, $method:ident) => {234 impl AbiRead<$ty> for AbiReader<'_> {235 fn abi_read(&mut self) -> Result<$ty> {236 self.$method()237 }238 }239 };240}241242impl_abi_readable!(u32, uint32);243impl_abi_readable!(u128, uint128);244impl_abi_readable!(U256, uint256);245impl_abi_readable!(H160, address);246impl_abi_readable!(Vec<u8>, bytes);247impl_abi_readable!(bool, bool);248impl_abi_readable!(string, string);249250pub trait AbiWrite {251 fn abi_write(&self, writer: &mut AbiWriter);252}253254macro_rules! impl_abi_writeable {255 ($ty:ty, $method:ident) => {256 impl AbiWrite for $ty {257 fn abi_write(&self, writer: &mut AbiWriter) {258 writer.$method(&self)259 }260 }261 };262}263264impl_abi_writeable!(u8, uint8);265impl_abi_writeable!(u32, uint32);266impl_abi_writeable!(u128, uint128);267impl_abi_writeable!(U256, uint256);268impl_abi_writeable!(H160, address);269impl_abi_writeable!(bool, bool);270impl_abi_writeable!(&str, string);271impl AbiWrite for &string {272 fn abi_write(&self, writer: &mut AbiWriter) {273 writer.string(self)274 }275}276277impl AbiWrite for () {278 fn abi_write(&self, _writer: &mut AbiWriter) {}279}280281#[macro_export]282macro_rules! abi_decode {283 ($reader:expr, $($name:ident: $typ:ident),+ $(,)?) => {284 $(285 let $name = $reader.$typ()?;286 )+287 }288}289#[macro_export]290macro_rules! abi_encode {291 ($($typ:ident($value:expr)),* $(,)?) => {{292 #[allow(unused_mut)]293 let mut writer = ::evm_coder::abi::AbiWriter::new();294 $(295 writer.$typ($value);296 )*297 writer298 }};299 (call $val:expr; $($typ:ident($value:expr)),* $(,)?) => {{300 #[allow(unused_mut)]301 let mut writer = ::evm_coder::abi::AbiWriter::new_call($val);302 $(303 writer.$typ($value);304 )*305 writer306 }}307}308309#[cfg(test)]310pub mod test {311 use super::{AbiReader, AbiWriter};312 use hex_literal::hex;313314 #[test]315 fn dynamic_after_static() {316 let mut encoder = AbiWriter::new();317 encoder.bool(&true);318 encoder.string("test");319 let encoded = encoder.finish();320321 let mut encoder = AbiWriter::new();322 encoder.bool(&true);323 // Offset to subresult324 encoder.uint32(&(32 * 2));325 // Len of "test"326 encoder.uint32(&4);327 encoder.write_padright(&[b't', b'e', b's', b't']);328 let alternative_encoded = encoder.finish();329330 assert_eq!(encoded, alternative_encoded);331332 let mut decoder = AbiReader::new(&encoded);333 assert_eq!(decoder.bool().unwrap(), true);334 assert_eq!(decoder.string().unwrap(), "test");335 }336337 #[test]338 fn mint_sample() {339 let (call, mut decoder) = AbiReader::new_call(&hex!(340 "341 50bb4e7f342 000000000000000000000000ad2c0954693c2b5404b7e50967d3481bea432374343 0000000000000000000000000000000000000000000000000000000000000001344 0000000000000000000000000000000000000000000000000000000000000060345 0000000000000000000000000000000000000000000000000000000000000008346 5465737420555249000000000000000000000000000000000000000000000000347 "348 ))349 .unwrap();350 assert_eq!(call, 0x50bb4e7f);351 assert_eq!(352 format!("{:?}", decoder.address().unwrap()),353 "0xad2c0954693c2b5404b7e50967d3481bea432374"354 );355 assert_eq!(decoder.uint32().unwrap(), 1);356 assert_eq!(decoder.string().unwrap(), "Test URI");357 }358}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.tsdiffbeforeafterboth--- a/tests/src/eth/nonFungible.test.ts
+++ b/tests/src/eth/nonFungible.test.ts
@@ -4,11 +4,13 @@
//
import privateKey from '../substrate/privateKey';
-import { approveExpectSuccess, createCollectionExpectSuccess, createItemExpectSuccess, transferExpectSuccess, transferFromExpectSuccess, UNIQUE } from '../util/helpers';
+import { approveExpectSuccess, burnItemExpectSuccess, createCollectionExpectSuccess, createItemExpectSuccess, transferExpectSuccess, transferFromExpectSuccess, UNIQUE } from '../util/helpers';
import { collectionIdToAddress, createEthAccount, createEthAccountWithBalance, GAS_ARGS, itWeb3, normalizeEvents, recordEthFee, recordEvents, subToEth, transferBalanceToEth } from './util/helpers';
+import { evmToAddress } from '@polkadot/util-crypto';
import nonFungibleAbi from './nonFungibleAbi.json';
import { expect } from 'chai';
import waitNewBlocks from '../substrate/wait-new-blocks';
+import { submitTransactionAsync } from '../substrate/substrate-api';
describe('NFT: Information getting', () => {
itWeb3('totalSupply', async ({ api, web3 }) => {
@@ -64,6 +66,78 @@
});
describe('NFT: Plain calls', () => {
+ itWeb3('Can perform mint()', async ({ web3, api }) => {
+ const collection = await createCollectionExpectSuccess({
+ mode: { type: 'NFT' },
+ });
+ const alice = privateKey('//Alice');
+
+ const caller = await createEthAccountWithBalance(api, web3);
+ const changeAdminTx = api.tx.nft.addCollectionAdmin(collection, { ethereum: caller });
+ await submitTransactionAsync(alice, changeAdminTx);
+ const receiver = createEthAccount(web3);
+
+ const address = collectionIdToAddress(collection);
+ const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS});
+
+ {
+ const nextTokenId = await contract.methods.nextTokenId().call();
+ expect(nextTokenId).to.be.equal('1');
+ const result = await contract.methods.mintWithTokenURI(
+ receiver,
+ nextTokenId,
+ 'Test URI',
+ ).send({from: caller});
+ const events = normalizeEvents(result.events);
+
+ expect(events).to.be.deep.equal([
+ {
+ address,
+ event: 'Transfer',
+ args: {
+ from: '0x0000000000000000000000000000000000000000',
+ to: receiver,
+ tokenId: nextTokenId,
+ },
+ },
+ ]);
+
+ await waitNewBlocks(api, 1);
+ expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI');
+ }
+ });
+
+ itWeb3('Can perform burn()', async ({ web3, api }) => {
+ const collection = await createCollectionExpectSuccess({
+ mode: {type: 'NFT'},
+ });
+ const alice = privateKey('//Alice');
+
+ const owner = await createEthAccountWithBalance(api, web3);
+
+ const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', { ethereum: owner });
+
+ const address = collectionIdToAddress(collection);
+ const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: owner, ...GAS_ARGS});
+
+ {
+ const result = await contract.methods.burn(tokenId).send({ from: owner });
+ const events = normalizeEvents(result.events);
+
+ expect(events).to.be.deep.equal([
+ {
+ address,
+ event: 'Transfer',
+ args: {
+ from: owner,
+ to: '0x0000000000000000000000000000000000000000',
+ tokenId: tokenId.toString(),
+ },
+ },
+ ]);
+ }
+ });
+
itWeb3('Can perform approve()', async ({ web3, api }) => {
const collection = await createCollectionExpectSuccess({
mode: { type: 'NFT' },
@@ -251,6 +325,60 @@
});
describe('NFT: Substrate calls', () => {
+ itWeb3('Events emitted for mint()', async ({ web3 }) => {
+ const collection = await createCollectionExpectSuccess({
+ mode: { type: 'NFT' },
+ });
+ const alice = privateKey('//Alice');
+
+ const address = collectionIdToAddress(collection);
+ const contract = new web3.eth.Contract(nonFungibleAbi as any, address);
+
+ let tokenId: number;
+ const events = await recordEvents(contract, async () => {
+ tokenId = await createItemExpectSuccess(alice, collection, 'NFT');
+ });
+
+ expect(events).to.be.deep.equal([
+ {
+ address,
+ event: 'Transfer',
+ args: {
+ from: '0x0000000000000000000000000000000000000000',
+ to: subToEth(alice.address),
+ tokenId: tokenId!.toString(),
+ },
+ },
+ ]);
+ });
+
+ itWeb3('Events emitted for burn()', async ({ web3 }) => {
+ const collection = await createCollectionExpectSuccess({
+ mode: { type: 'NFT' },
+ });
+ const alice = privateKey('//Alice');
+
+ const address = collectionIdToAddress(collection);
+ const contract = new web3.eth.Contract(nonFungibleAbi as any, address);
+
+ const tokenId = await createItemExpectSuccess(alice, collection, 'NFT');
+ const events = await recordEvents(contract, async () => {
+ await burnItemExpectSuccess(alice, collection, tokenId);
+ });
+
+ expect(events).to.be.deep.equal([
+ {
+ address,
+ event: 'Transfer',
+ args: {
+ from: subToEth(alice.address),
+ to: '0x0000000000000000000000000000000000000000',
+ tokenId: tokenId.toString(),
+ },
+ },
+ ]);
+ });
+
itWeb3('Events emitted for approve()', async ({ web3 }) => {
const collection = await createCollectionExpectSuccess({
mode: { type: 'NFT' },
@@ -342,4 +470,4 @@
},
]);
});
-});
\ No newline at end of file
+});
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