--- a/crates/evm-coder-macros/src/solidity_interface.rs +++ b/crates/evm-coder-macros/src/solidity_interface.rs @@ -57,7 +57,7 @@ fn expand_interface_id(&self) -> proc_macro2::TokenStream { let pascal_call_name = &self.pascal_call_name; quote! { - interface_id ^= #pascal_call_name::interface_id(); + interface_id ^= u32::from_be_bytes(#pascal_call_name::interface_id()); } } @@ -540,18 +540,18 @@ fn expand_const(&self) -> proc_macro2::TokenStream { let screaming_name = &self.screaming_name; - let selector = self.selector; + let selector = u32::to_be_bytes(self.selector); let selector_str = &self.selector_str; quote! { #[doc = #selector_str] - const #screaming_name: u32 = #selector; + const #screaming_name: ::evm_coder::types::bytes4 = [#(#selector,)*]; } } fn expand_interface_id(&self) -> proc_macro2::TokenStream { let screaming_name = &self.screaming_name; quote! { - interface_id ^= Self::#screaming_name; + interface_id ^= u32::from_be_bytes(Self::#screaming_name); } } @@ -831,14 +831,14 @@ #( #consts )* - pub fn interface_id() -> u32 { + pub fn interface_id() -> ::evm_coder::types::bytes4 { let mut interface_id = 0; #(#interface_id)* #(#inline_interface_id)* - interface_id + u32::to_be_bytes(interface_id) } - pub fn supports_interface(interface_id: u32) -> bool { - interface_id != 0xffffff && ( + pub fn supports_interface(interface_id: ::evm_coder::types::bytes4) -> bool { + interface_id != u32::to_be_bytes(0xffffff) && ( interface_id == ::evm_coder::ERC165Call::INTERFACE_ID || interface_id == Self::interface_id() #( @@ -884,7 +884,7 @@ } } impl #gen_ref ::evm_coder::Call for #call_name #gen_ref { - fn parse(method_id: u32, reader: &mut ::evm_coder::abi::AbiReader) -> ::evm_coder::execution::Result> { + fn parse(method_id: ::evm_coder::types::bytes4, reader: &mut ::evm_coder::abi::AbiReader) -> ::evm_coder::execution::Result> { use ::evm_coder::abi::AbiRead; match method_id { ::evm_coder::ERC165Call::INTERFACE_ID => return Ok( --- a/crates/evm-coder-macros/src/to_log.rs +++ b/crates/evm-coder-macros/src/to_log.rs @@ -199,7 +199,7 @@ use evm_coder::solidity::*; use core::fmt::Write; let interface = SolidityInterface { - selector: 0, + selector: [0; 4], name: #solidity_name, is: &[], functions: (#( --- a/crates/evm-coder/src/abi.rs +++ b/crates/evm-coder/src/abi.rs @@ -26,7 +26,7 @@ use crate::{ execution::{Error, ResultWithPostInfo, WithPostDispatchInfo}, - types::string, + types::{string, self}, }; use crate::execution::Result; @@ -46,7 +46,7 @@ offset: 0, } } - pub fn new_call(buf: &'i [u8]) -> Result<(u32, Self)> { + pub fn new_call(buf: &'i [u8]) -> Result<(types::bytes4, Self)> { if buf.len() < 4 { return Err(Error::Error(ExitError::OutOfOffset)); } @@ -54,7 +54,7 @@ method_id.copy_from_slice(&buf[0..4]); Ok(( - u32::from_be_bytes(method_id), + method_id, Self { buf, subresult_offset: 4, @@ -63,25 +63,50 @@ )) } - fn read_padleft(&mut self) -> Result<[u8; S]> { - if self.buf.len() - self.offset < ABI_ALIGNMENT { + fn read_pad(buf: &[u8], offset: usize, pad_start: usize, pad_size: usize, block_start: usize, block_size: usize) -> Result<[u8; S]> { + if buf.len() - offset < ABI_ALIGNMENT { return Err(Error::Error(ExitError::OutOfOffset)); } let mut block = [0; S]; // Verify padding is empty - if !self.buf[self.offset..self.offset + ABI_ALIGNMENT - S] + if !buf[pad_start..pad_size] .iter() .all(|&v| v == 0) { return Err(Error::Error(ExitError::InvalidRange)); } block.copy_from_slice( - &self.buf[self.offset + ABI_ALIGNMENT - S..self.offset + ABI_ALIGNMENT], + &buf[block_start..block_size], ); + Ok(block) + } + + fn read_padleft(&mut self) -> Result<[u8; S]> { + let offset = self.offset; self.offset += ABI_ALIGNMENT; - Ok(block) + Self::read_pad( + self.buf, + offset, + offset, + offset + ABI_ALIGNMENT - S, + offset + ABI_ALIGNMENT - S, + offset + ABI_ALIGNMENT + ) } + fn read_padright(&mut self) -> Result<[u8; S]> { + let offset = self.offset; + self.offset += ABI_ALIGNMENT; + Self::read_pad( + self.buf, + offset, + offset + S, + offset + ABI_ALIGNMENT, + offset, + offset + S + ) + } + pub fn address(&mut self) -> Result { Ok(H160(self.read_padleft()?)) } @@ -96,7 +121,7 @@ } pub fn bytes4(&mut self) -> Result<[u8; 4]> { - self.read_padleft() + self.read_padright() } pub fn bytes(&mut self) -> Result> { @@ -268,6 +293,7 @@ impl_abi_readable!(u64, uint64); impl_abi_readable!(u128, uint128); impl_abi_readable!(U256, uint256); +impl_abi_readable!([u8; 4], bytes4); impl_abi_readable!(H160, address); impl_abi_readable!(Vec, bytes); impl_abi_readable!(bool, bool); @@ -466,7 +492,7 @@ " )) .unwrap(); - assert_eq!(call, 0x50bb4e7f); + assert_eq!(call, u32::to_be_bytes(0x50bb4e7f)); assert_eq!( format!("{:?}", decoder.address().unwrap()), "0xad2c0954693c2b5404b7e50967d3481bea432374" @@ -505,7 +531,7 @@ " )) .unwrap(); - assert_eq!(call, 0x36543006); + assert_eq!(call, u32::to_be_bytes(0x36543006)); let _ = decoder.address().unwrap(); let data = as AbiRead>>::abi_read(&mut decoder).unwrap(); --- a/crates/evm-coder/src/lib.rs +++ b/crates/evm-coder/src/lib.rs @@ -44,7 +44,7 @@ pub type uint128 = u128; pub type uint256 = U256; - pub type bytes4 = u32; + pub type bytes4 = [u8; 4]; pub type topic = H256; @@ -71,7 +71,7 @@ } pub trait Call: Sized { - fn parse(selector: u32, input: &mut AbiReader) -> execution::Result>; + fn parse(selector: types::bytes4, input: &mut AbiReader) -> execution::Result>; } pub type Weight = u64; @@ -93,11 +93,11 @@ } impl ERC165Call { - pub const INTERFACE_ID: types::bytes4 = 0x01ffc9a7; + pub const INTERFACE_ID: types::bytes4 = u32::to_be_bytes(0x01ffc9a7); } impl Call for ERC165Call { - fn parse(selector: u32, input: &mut AbiReader) -> execution::Result> { + fn parse(selector: types::bytes4, input: &mut AbiReader) -> execution::Result> { if selector != Self::INTERFACE_ID { return Ok(None); } --- a/crates/evm-coder/src/solidity.rs +++ b/crates/evm-coder/src/solidity.rs @@ -103,6 +103,7 @@ uint64 => "uint64" true = "0", uint128 => "uint128" true = "0", uint256 => "uint256" true = "0", + bytes4 => "bytes4" true = "bytes4(0)", address => "address" true = "0x0000000000000000000000000000000000000000", string => "string" false = "\"\"", bytes => "bytes" false = "hex\"\"", @@ -473,7 +474,7 @@ } pub struct SolidityInterface { - pub selector: u32, + pub selector: bytes4, pub name: &'static str, pub is: &'static [&'static str], pub functions: F, @@ -486,8 +487,9 @@ out: &mut impl fmt::Write, tc: &TypeCollector, ) -> fmt::Result { - if self.selector != 0 { - writeln!(out, "// Selector: {:0>8x}", self.selector)?; + const ZERO_BYTES: [u8; 4] = [0; 4]; + if self.selector != ZERO_BYTES { + writeln!(out, "// Selector: {:0>8x}", u32::from_be_bytes(self.selector))?; } if is_impl { write!(out, "contract ")?; --- a/tests/src/eth/base.test.ts +++ b/tests/src/eth/base.test.ts @@ -14,11 +14,13 @@ // You should have received a copy of the GNU General Public License // along with Unique Network. If not, see . -import {collectionIdToAddress, createEthAccount, createEthAccountWithBalance, deployFlipper, ethBalanceViaSub, GAS_ARGS, itWeb3, recordEthFee} from './util/helpers'; +import {collectionIdToAddress, createEthAccount, createEthAccountWithBalance, deployFlipper, ethBalanceViaSub, GAS_ARGS, itWeb3, recordEthFee, usingWeb3} from './util/helpers'; import {expect} from 'chai'; import {createCollectionExpectSuccess, createItemExpectSuccess, UNIQUE} from '../util/helpers'; import nonFungibleAbi from './nonFungibleAbi.json'; import privateKey from '../substrate/privateKey'; +import {Contract} from 'web3-eth-contract'; +import Web3 from 'web3'; describe('Contract calls', () => { itWeb3('Call of simple contract fee is less than 0.2 UNQ', async ({web3, api}) => { @@ -59,3 +61,53 @@ expect(Math.abs(fee - expectedFee)).to.be.lessThan(tolerance); }); }); + +describe('ERC165 tests', async () => { + // https://eips.ethereum.org/EIPS/eip-165 + + let collection: number; + let minter: string; + + function contract(web3: Web3): Contract { + return new web3.eth.Contract(nonFungibleAbi as any, collectionIdToAddress(collection), {from: minter, ...GAS_ARGS}); + } + + before(async () => { + await usingWeb3 (async (web3) => { + collection = await createCollectionExpectSuccess(); + minter = createEthAccount(web3); + }); + }); + + itWeb3('interfaceID == 0xffffffff always false', async ({web3}) => { + expect(await contract(web3).methods.supportsInterface('0xffffffff').call()).to.be.false; + }); + + itWeb3('ERC721 support', async ({web3}) => { + expect(await contract(web3).methods.supportsInterface('0x58800161').call()).to.be.true; + }); + + itWeb3('ERC721Metadata support', async ({web3}) => { + expect(await contract(web3).methods.supportsInterface('0x5b5e139f').call()).to.be.true; + }); + + itWeb3('ERC721Mintable support', async ({web3}) => { + expect(await contract(web3).methods.supportsInterface('0x68ccfe89').call()).to.be.true; + }); + + itWeb3('ERC721Enumerable support', async ({web3}) => { + expect(await contract(web3).methods.supportsInterface('0x780e9d63').call()).to.be.true; + }); + + itWeb3('ERC721UniqueExtensions support', async ({web3}) => { + expect(await contract(web3).methods.supportsInterface('0xe562194d').call()).to.be.true; + }); + + itWeb3('ERC721Burnable support', async ({web3}) => { + expect(await contract(web3).methods.supportsInterface('0x42966c68').call()).to.be.true; + }); + + itWeb3('ERC165 support', async ({web3}) => { + expect(await contract(web3).methods.supportsInterface('0x01ffc9a7').call()).to.be.true; + }); +}); --- a/tests/src/eth/nonFungibleAbi.json +++ b/tests/src/eth/nonFungibleAbi.json @@ -466,9 +466,9 @@ { "inputs": [ { - "internalType": "uint32", + "internalType": "bytes4", "name": "interfaceId", - "type": "uint32" + "type": "bytes4" } ], "name": "supportsInterface",