Unit conversions
Many of the calculations related to Ethereum have values that exceed the maximum safe integer of the JavaScript language. Therefore, some methods are needed on the FMZ Quant Trading Platform to handle large values, which we have used specifically in previous courses and have not covered in detail. This section will discuss this aspect in detail.
Print the maximum safe integer defined in the JavaScript language:
function main() {
Log("Number.MAX_SAFE_INTEGER:", Number.MAX_SAFE_INTEGER)
}
Running results:
Number.MAX_SAFE_INTEGER: 9007199254740991
BigInt
The smallest unit defined in Ethereum is 1wei, and the definition 1Gwei is equal to 1000000000 wei. 1Gwei is not really a very large number in Ethereum-related calculations, and some data is much larger than it. So these data with very large values can easily exceed Number.MAX_SAFE_INTEGER: 9007199254740991.
At FMZ Quant Trading Platform, we use the platform's BigInt object to represent these very large integer data. Use the constructor BigInt() to construct the BigInt object. You can construct BigInt objects using numeric, hexadecimal numeric strings as parameters. Use the toString() method of BigInt object to output the data represented by the object as a string.
The operations supported by the BigInt object are:
- Addition:
+
- Subtraction:
-
- Multiplication:
*
- Division:
/
- Modulo operations:
%
- Power operations:
*
Refer to the following code examples:
function main() {
// Decimal representation of 1Gwei
var oneGwei = 1000000000
// Decimal to hexadecimal conversion of 1Gwei
var oneGweiForHex = "0x" + oneGwei.toString(16)
Log("oneGwei : ", oneGwei)
Log("oneGweiForHex : ", oneGweiForHex)
// Constructing BigInt objects
Log("1Gwei / 1Gwei : ", (BigInt(oneGwei) / BigInt(oneGweiForHex)).toString(10))
Log("1Gwei * 1Gwei : ", (BigInt(oneGwei) * BigInt(oneGweiForHex)).toString(10))
Log("1Gwei - 1Gwei : ", (BigInt(oneGwei) - BigInt(oneGweiForHex)).toString(10))
Log("1Gwei + 1Gwei : ", (BigInt(oneGwei) + BigInt(oneGweiForHex)).toString(10))
Log("(1Gwei + 1) % 1Gwei : ", (BigInt(oneGwei + 1) % BigInt(oneGweiForHex)).toString(10))
Log("1Gwei ** 2 : ", (BigInt(oneGwei) ** BigInt(2)).toString(10))
Log("The square root of 100 : ", (BigInt(100) ** BigFloat(0.5)).toString(10))
Log("Number.MAX_SAFE_INTEGER : ", BigInt(Number.MAX_SAFE_INTEGER).toString(10))
Log("Number.MAX_SAFE_INTEGER * 2 : ", (BigInt(Number.MAX_SAFE_INTEGER) * BigInt("2")).toString(10))
}
Debugging tool testing:
2023-06-08 11:39:50 Info Number.MAX_SAFE_INTEGER * 2 : 18014398509481982
2023-06-08 11:39:50 Info Number.MAX_SAFE_INTEGER : 9007199254740991
2023-06-08 11:39:50 Info The square root of 100 : 10
2023-06-08 11:39:50 Info 1Gwei ** 2 : 1000000000000000000
2023-06-08 11:39:50 Info (1Gwei + 1) % 1Gwei : 1
2023-06-08 11:39:50 Info 1Gwei + 1Gwei : 2000000000
2023-06-08 11:39:50 Info 1Gwei - 1Gwei : 0
2023-06-08 11:39:50 Info 1Gwei * 1Gwei : 1000000000000000000
2023-06-08 11:39:50 Info 1Gwei / 1Gwei : 1
2023-06-08 11:39:50 Info oneGweiForHex : 0x3b9aca00
2023-06-08 11:39:50 Info oneGwei : 1000000000
BigFloat
The BigFloat object is used similarly to the BigInt object to represent floating point numbers with larger values, and it also supports addition, subtraction, multiplication and division.
The BigFloat object supports the toFixed() method.
Refer to the following code example:
function main() {
var pi = 3.14
var oneGwei = "1000000000"
var oneGweiForHex = "0x3b9aca00"
Log("pi + oneGwei : ", (BigFloat(pi) + BigFloat(oneGwei)).toFixed(2))
Log("pi - oneGweiForHex : ", (BigFloat(pi) - BigFloat(oneGweiForHex)).toFixed(2))
Log("pi * 2.0 : ", (BigFloat(pi) * BigFloat(2.0)).toFixed(2))
Log("pi / 2.0 : ", (BigFloat(pi) / BigFloat(2.0)).toFixed(2))
}
Debugging tool testing:
2023-06-08 13:56:44 Info pi / 2.0 : 1.57
2023-06-08 13:56:44 Info pi * 2.0 : 6.28
2023-06-08 13:56:44 Info pi - oneGweiForHex : -999999996.86
2023-06-08 13:56:44 Info pi + oneGwei : 1000000003.14
BigDecimal
The BigDecimal object is compatible with integer values and floating point values, and supports initialization with the BigInt object and the BigFloat object, and it also supports addition, subtraction, multiplication and division.
Refer to the following code example:
function main() {
var pi = 3.1415
var oneGwei = 1000000000
var oneGweiForHex = "0x3b9aca00"
Log("pi : ", BigDecimal(pi).toFixed(2))
Log("oneGwei : ", BigDecimal(oneGwei).toString())
Log("oneGweiForHex : ", BigDecimal(BigInt(oneGweiForHex)).toString())
Log("BigInt(oneGwei) : ", BigDecimal(BigInt(oneGwei)).toString())
Log("BigFloat(pi) : ", BigDecimal(BigFloat(pi)).toFixed(4))
Log("oneGwei + pi : ", (BigDecimal(oneGwei) + BigDecimal(pi)).toString())
Log("oneGwei - pi : ", (BigDecimal(oneGwei) - BigDecimal(pi)).toString())
Log("2.0 * pi : ", (BigDecimal(2.0) * BigDecimal(pi)).toString())
Log("pi / pi : ", (BigDecimal(pi) / BigDecimal(pi)).toString())
}
Running in the debugging tool:
2023-06-08 14:52:53 Info pi / pi : 1
2023-06-08 14:52:53 Info 2.0 * pi : 6.283
2023-06-08 14:52:53 Info oneGwei - pi : 999999996.8585
2023-06-08 14:52:53 Info oneGwei + pi : 1000000003.1415
2023-06-08 14:52:53 Info BigFloat(pi) : 3.1415
2023-06-08 14:52:53 Info BigInt(oneGwei) : 1e+9
2023-06-08 14:52:53 Info oneGweiForHex : 1e+9
2023-06-08 14:52:53 Info oneGwei : 1e+9
2023-06-08 14:52:53 Info pi : 3.14
Unit conversions
The following two functions: toAmount(), toInnerAmount() we have used many times in previous courses, these two functions are mainly used for data precision conversion.
function toAmount(s, decimals) {
return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString())
}
function toInnerAmount(n, decimals) {
return (BigDecimal(n) * BigDecimal(Math.pow(10, decimals))).toFixed(0)
}
The toAmount() function converts (reduces) a variable s according to the precision parameter decimals. In web3 practical development, it is often necessary to deal with some chained hexadecimal data.
We have often encountered this in our previous courses, for example, the data field data in the Transfer(address,address,uint256) event of a smart contract:
{
"data": "0x00000000000000000000000000000000000000000000000001c1a55000000000",
"topics": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x0000000000000000000000006b75d8af000000e20b7a7ddf000ba900b4009a80", "0x000000000000000000000000bcb095c1f9c3dc02e834976706c87dee5d0f1fb6"],
"transactionHash": "0x27f9bf5abe3148169b4b85a83e1de32bd50eb81ecc52e5af006157d93353e4c4",
"transactionIndex": "0x0",
"removed": false,
"address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
"blockHash": "0x847be24a7b159c292bda030a011dfec89487b70e71eed486969b032d6ef04bad",
"blockNumber": "0x109b1cc",
"logIndex": "0x0"
}
When processing data "data": "0x00000000000000000000000000000000000000000000000001c1a55000000000", we use the toAmount() function. This processing is designed to do a good job of converting data field data to readable values.
function toAmount(s, decimals) {
return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString())
}
function main() {
var data = "0x00000000000000000000000000000000000000000000000001c1a55000000000"
Log(toAmount(data, 18)) // Print out 0.12656402755905127
}
1 ETH token, as we know, is 1e18 wei, if we get a data 126564027559051260 in wei, how to convert it to ETH tokens?
Using the toAmount(, 18) function is a very simple conversion method. The toInnerAmount() function is the reverse operation of the toAmount() function (depending on the precision, zoom in), and it is easy to convert the data using these two functions.
It is important to note the integer value safety range in the JavaScript language, Number.MAX_SAFE_INTEGER, and the following example illustrates a hidden problem when converting data:
function toAmount(s, decimals) {
return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString())
}
function toInnerAmount(n, decimals) {
return (BigDecimal(n) * BigDecimal(Math.pow(10, decimals))).toFixed(0)
}
function main() {
var amount = 0.01
var innerAmount = Number(toInnerAmount(amount, 18))
Log("Number.MAX_SAFE_INTEGER:", Number.MAX_SAFE_INTEGER) // 9007199254740991
Log("innerAmount:", innerAmount) // 10000000000000000
Log("typeof(innerAmount):", typeof(innerAmount), ", innerAmount:", innerAmount)
// Decimal value 10000000000000000 -> Hexadecimal value 0x2386f26fc10000
Log("Convert", innerAmount, "to hexadecimal:", innerAmount.toString(16))
Log("Convert", BigInt(10000000000000000).toString(10), "to hexadecimal:", BigInt(10000000000000000).toString(16))
Log("0x" + BigInt(10000000000000000).toString(16), "Convert to decimal:", toAmount("0x" + BigInt(10000000000000000).toString(16), 0))
}
It is possible to run in the debugging tool:
2023-06-15 16:21:40 Info Convert 0x2386f26fc10000 to decimal: 10000000000000000
2023-06-15 16:21:40 Info Convert 10000000000000000 to hexadecimal: 2386f26fc10000
2023-06-15 16:21:40 Info Convert 10000000000000000 to hexadecimal: 10000000000000000
2023-06-15 16:21:40 Info typeof(innerAmount): number , innerAmount: 10000000000000000
2023-06-15 16:21:40 Info innerAmount: 10000000000000000
2023-06-15 16:21:40 Info Number.MAX_SAFE_INTEGER: 9007199254740991
Through observation we found that:
Log("Convert", innerAmount, "to hexadecimal:", innerAmount.toString(16))
This line of code corresponds to the log output: Converting 10000000000000000 to hex: 10000000000000000, which is not converted correctly. The reason is naturally that 10000000000000000 is beyond Number.MAX_SAFE_INTEGER.
But when the decimal value is within the safe range, i.e., less than Number.MAX_SAFE_INTEGER, the toString(16) function converts it properly again, for example:
function main() {
var value = 1000
Log("Convert value to hexadecimal:", "0x" + value.toString(16)) // 0x3e8
Log("Convert 0x3e8 to decimal:", Number("0x3e8")) // 1000
}
In blockchain, even 0.01 ETH converted to a value of 10000000000000000 in wei will exceed Number.MAX_SAFE_INTEGER``, so a safer conversion for such cases is: BigInt(10000000000000000).toString(16)```.
Simulation Calls
Executing transactions and calling the Write method of smart contracts on Ethereum costs a certain amount of gas and sometimes it fails. It is important to know which transactions are likely to fail before sending them and calling them. There are simulated calls on Ethereum for testing.
eth_call
Ethereum's RPC method eth_call: it can simulate a transaction and return the result of a possible transaction, but it does not actually execute the transaction on the blockchain.
The eth_call method has 2 parameters, the first one is a dictionary structure, transactionObject:
// transactionObject
{
"from" : ..., // The address from which the transaction is sent
"to" : ..., // The address to which the transaction is addressed
"gas" : ..., // The integer of gas provided for the transaction execution
"gasPrice" : ..., // The integer of gasPrice used for each paid gas encoded as hexadecimal
"value" : ..., // The integer of value sent with this transaction encoded as hexadecimal
"data" : ..., // The hash of the method signature and encoded parameters. For more information, see the Contract ABI description in the Solidity documentation
}
The second parameter is blockNumber: you can pass the label latest/pending/earliest, etc:
/* blockNumber
The block number in hexadecimal format or the string latest, earliest, pending, safe or
finalized (safe and finalized tags are only supported on Ethereum, Gnosis, Arbitrum,
Arbitrum Nova and Avalanche C-chain), see the default block parameter description in
the official Ethereum documentation
*/
Next, we take the smart contract method approve and transfer calls of the token DAI as an example for simulation calls, and the following test environment is the main Ethereum network.
Simulation call approve
We are all familiar with the approve method for ERC20 contracts, and we have practiced it in previous courses. Since the ERC20 contract is already built into the FMZ platform ABI, there is no need to register the ABI of the smart contract to be called by the simulation.
function main() {
var contractAddressUniswapV3SwapRouterV2 = "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45"
var contractAddress_DAI = "0x6b175474e89094c44da98b954eedeac495271d0f"
var wallet = exchange.IO("address")
// encode approve
var data = exchange.IO("encode", contractAddress_DAI, "approve(address,uint256)",
contractAddressUniswapV3SwapRouterV2, "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
Log("ERC20 token DAI approve encode, data:", data)
var transactionObject = {
"from" : wallet,
"to" : contractAddress_DAI,
// "gasPrice" : "0x" + parseInt("21270894680").toString(16),
// "gas" : "0x" + parseInt("21000").toString(16),
"data" : "0x" + data,
}
var blockNumber = "latest"
var ret = exchange.IO("api", "eth", "eth_call", transactionObject, blockNumber)
Log("ret:", ret)
}
The code in the example first encodes the approve(address,uint256) method and parameters, and the parameter value 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff of the approve method indicates the maximum number of authorizations. Authorization is given to the smart contract at address 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45 i.e. the router contract for Uniswap V3. Finally the Ethereum RPC method eth_call is called for simulation. You can see that the gasPrice and gas fields in the transactionObject parameters can be omitted.
The debugging tool is run and the simulation calls the approve method to authorize successfully (it does not authorize actually):
2023-06-09 11:58:39 Info ret: 0x0000000000000000000000000000000000000000000000000000000000000001
2023-06-09 11:58:39 Info ERC20 token DAI approve encode, data: 095ea7b300000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc45ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
It is also possible to simulate some failure scenarios, when we adjust the gasPrice and gas parameters, if the ETH in the wallet is not enough to pay the gas fee, an error will be reported::
insufficient funds
When the gas cost is set too low, an error will be reported:
intrinsic gas too low: have 21000, want 21944 (supplied gas 21000)
Simulation call transfer
We are familiar with ERC20's transfer method, which allows you to transfer ERC20 tokens to a certain wallet address, so let's try to simulate a transfer of 1000 DAI to Vitalik Buterin.
function toInnerAmount(n, decimals) {
return (BigDecimal(n) * BigDecimal(Math.pow(10, decimals))).toFixed(0)
}
function main() {
var walletVitalik = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
var contractAddress_DAI = "0x6b175474e89094c44da98b954eedeac495271d0f"
var wallet = exchange.IO("address")
// transfer to Vitalik Buterin
var decimals_DAI = exchange.IO("api", contractAddress_DAI, "decimals")
var transferAmount = toInnerAmount(1000, decimals_DAI)
Log("Transfer amount:", 1000, "DAI, use toInnerAmount convert to:", transferAmount)
// encode transfer
var data = exchange.IO("encode", contractAddress_DAI, "transfer(address,uint256)",
walletVitalik, transferAmount)
var transactionObject = {
"from" : wallet,
"to" : contractAddress_DAI,
"data" : "0x" + data,
}
var blockNumber = "latest"
var ret = exchange.IO("api", "eth", "eth_call", transactionObject, blockNumber)
return ret
}
Since I don't have DAI tokens in this test wallet, running it in the debug tool reported the following error unexpectedly:
execution reverted: Dai/insufficient-balance
Check the wallet address of Vitalik Buterin: 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045, it is clear that this wallet has DAI tokens. So let's adjust the transfer direction of the simulation call and simulate the transfer of 1000 DAI from Vitalik Buterin to us.
Modify the code, where the changes I made comments:
function toInnerAmount(n, decimals) {
return (BigDecimal(n) * BigDecimal(Math.pow(10, decimals))).toFixed(0)
}
function main() {
var walletVitalik = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
var contractAddress_DAI = "0x6b175474e89094c44da98b954eedeac495271d0f"
var wallet = exchange.IO("address")
var decimals_DAI = exchange.IO("api", contractAddress_DAI, "decimals")
var transferAmount = toInnerAmount(1000, decimals_DAI)
Log("Transfer amount:", 1000, "DAI, use toInnerAmount convert to:", transferAmount)
// encode transfer
var data = exchange.IO("encode", contractAddress_DAI, "transfer(address,uint256)",
wallet, transferAmount) // Use the wallet variable as a parameter and change the transfer recipient's address to my own
var transactionObject = {
"from" : walletVitalik, // Use the walletVitalik variable as the value of the from field to simulate that the call was made from the Vitalik Buterin's wallet address
"to" : contractAddress_DAI,
"data" : "0x" + data,
}
var blockNumber = "latest"
var ret = exchange.IO("api", "eth", "eth_call", transactionObject, blockNumber)
Log(ret)
}
Debugging tool test:
2023-06-09 13:34:31 Info 0x0000000000000000000000000000000000000000000000000000000000000001
2023-06-09 13:34:31 Info Transfer amount: 1000 DAI, use toInnerAmount convert to: 1000000000000000000000
Using the FMZ Quant Trading Platform, it is easy to simulate the results of transactions and avoid unnecessary loss of gas fees from sending potentially failed transactions. We used the example code from this chapter of the course to simulate the call to transfer money to Vitalik Buterin's wallet and Vitalik Buterin's wallet to transfer money to us. Of course, there are many more uses for this eth_call method. Use your imagination, what would you use the eth_call method for?
Identify ERC721 Contracts
We know that tokens like ETH and BTC are homogenized tokens, and the token in your wallet is not different from the token in my wallet. But there are many things in the world that are not homogeneous, such as real estate, antiques, virtual artwork, etc. These cannot be represented by homogeneous tokens in abstraction. Therefore, there is the ERC721 standard to abstract non-homogeneous objects, and there is NFT and related concepts.
So among the many smart contracts deployed on Ethereum, how do we identify which smart contracts are ERC721 standard smart contracts?
To identify ERC721, it is important to know the ERC165 standard first.
ERC165
With the ERC165 standard, a smart contract can declare the interfaces it supports for other contracts to check. An ERC165 interface contract has only one function: supportsInterface(bytes4 interfaceId), the parameter interfaceId is the interface Id to be queried. If the contract implements the interfaceId returns a boolean true value, otherwise it returns a false value.
Here we are going to talk about how this interfaceId is calculated and encoded specifically.
ERC165 Standard shows an example:
pragma solidity ^0.4.20;
interface Solidity101 {
function hello() external pure;
function world(int) external pure;
}
contract Selector {
function calculateSelector() public pure returns (bytes4) {
Solidity101 i;
return i.hello.selector ^ i.world.selector;
}
}
For the function signature of the interface (consisting of a function name and a list of parameter types) to perform a dissimilarity operation, for an ERC165 interface contract where the contract has only one function:
pragma solidity ^0.4.20;
interface ERC165 {
/// @notice Query if a contract implements an interface
/// @param interfaceID The interface identifier, as specified in ERC-165
/// @dev Interface identification is specified in ERC-165. This function
/// uses less than 30,000 gas.
/// @return `true` if the contract implements `interfaceID` and
/// `interfaceID` is not 0xffffffff, `false` otherwise
function supportsInterface(bytes4 interfaceID) external view returns (bool);
}
The interface identifier for this interface is 0x01ffc9a7. You can calculate this by running bytes4(keccak256('supportsInterface(bytes4)')); or using the Selector contract above.
Calculate the function signature directly and take its first 4 bytes to arrive at interfaceId.
function main() {
var ret = Encode("keccak256", "string", "hex", "supportsInterface(bytes4)")
Log("supportsInterface(bytes4) interfaceId:", "0x" + ret.slice(0, 8))
}
Tests can be run in the debug tool at:
2023-06-13 14:53:35 Info supportsInterface(bytes4) interfaceId: 0x01ffc9a7
It can be seen that the calculated results are consistent with the description in the ERC165 Standard document.
ERC721
Next let's look at the interface definition of the ERC721 contract standard:
interface ERC721 /* is ERC165 */ {
event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);
event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId);
event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);
function balanceOf(address _owner) external view returns (uint256);
function ownerOf(uint256 _tokenId) external view returns (address);
function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable;
function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;
function transferFrom(address _from, address _to, uint256 _tokenId) external payable;
function approve(address _approved, uint256 _tokenId) external payable;
function setApprovalForAll(address _operator, bool _approved) external;
function getApproved(uint256 _tokenId) external view returns (address);
function isApprovedForAll(address _owner, address _operator) external view returns (bool);
}
If we want to determine whether a smart contract is an ERC721 contract, first we need to know the interfaceId of the ERC721 contract before we can try to use the supportsInterface(bytes4 interfaceId) method to determine it. In previous courses, we have familiarized us with some concepts of the ERC165 standard and the algorithm for calculating the interfaceId, and we write code to calculate directly:
function calcSelector(arrSelector) {
var ret = null
if (Array.isArray(arrSelector)) {
if (arrSelector.length == 1) {
ret = Encode("keccak256", "string", "hex", arrSelector[0])
} else if (arrSelector.length == 0) {
throw "Error: the number of elements in the array is 0"
} else {
var viewEncodeData = null
for (var i = 0; i < arrSelector.length; i++) {
if (i == 0) {
ret = new Uint8Array(Encode("keccak256", "string", "raw", arrSelector))
} else {
viewData = new Uint8Array(Encode("keccak256", "string", "raw", arrSelector))
if (viewData.length != ret.length) {
throw "Error: TypeArray view length is different"
}
for (var index = 0; index < ret.length; index++) {
ret[index] ^= viewData[index]
}
}
}
ret = Encode("raw", "raw", "hex", ret.buffer)
}
} else {
throw "Error: The parameter requires an array type."
}
return "0x" + ret.slice(0, 8)
}
function main() {
// supportsInterface(bytes4): 0x01ffc9a7
// var ret = calcSelector(["supportsInterface(bytes4)"])
// ERC721Metadata: 0x5b5e139f
/*
var arrSelector = [
"name()",
"symbol()",
"tokenURI(uint256)"
]
var ret = calcSelector(arrSelector)
*/
// ERC721: 0x80ac58cd
// /*
var arrSelector = [
"balanceOf(address)",
"ownerOf(uint256)",
"safeTransferFrom(address,address,uint256,bytes)",
"safeTransferFrom(address,address,uint256)",
"transferFrom(address,address,uint256)",
"approve(address,uint256)",
"setApprovalForAll(address,bool)",
"getApproved(uint256)",
"isApprovedForAll(address,address)",
]
var ret = calcSelector(arrSelector)
// */
Log(ret)
}
The code uses the Encode() function for function signature calculation (the keccak256 algorithm), and for the calculation in the code example above, specifying the output parameter of the Encode() function as "raw", the function returns the ArrayBuffer type of JavaScript language.
To perform a ^ (iso-or) operation on two ArrayBuffer objects, you need to create a TypedArray view based on the ArrayBuffer object, then iterate through the data in it and perform the iso-or operation one by one.
Run in the debugging tool:
2023-06-13 15:04:09 Info 0x80ac58cd
It can be seen that the calculated results are consistent with those described in eip-721.
pragma solidity ^0.4.20;
/// @title ERC-721 Non-Fungible Token Standard/// @dev See https://eips.ethereum.org/EIPS/eip-721/// Note: the ERC-165 identifier for this interface is 0x80ac58cd.interface ERC721 /* is ERC165 */ {
/// @dev This emits when ownership of any NFT changes by any mechanism.
/// This event emits when NFTs are created (`from` == 0) and destroyed
/// (`to` == 0). Exception: during contract creation, any number of NFTs
/// may be created and assigned without emitting Transfer. At the time of
/// any transfer, the approved address for that NFT (if any) is reset to none.
event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);
...
With the ERC721 interface Id, we can determine if a contract is an ERC721 standard contract or not. We use BAYC to do the test, which is a contract that follows ERC721. First we need to register the ABI, and since we only call the following three methods, we can register these three methods:
- supportsInterface(interfaceId)
- symbol()
- name()
The specific codes are as follows:
function main() {
// Contract address for ERC721, BAYC is used here
var testContractAddress = "0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d"
var testABI = `[{
"inputs": [{
"internalType": "bytes4",
"name": "interfaceId",
"type": "bytes4"
}],
"name": "supportsInterface",
"outputs": [{
"internalType": "bool",
"name": "",
"type": "bool"
}],
"stateMutability": "view",
"type": "function"
}, {
"inputs": [],
"name": "symbol",
"outputs": [{
"internalType": "string",
"name": "",
"type": "string"
}],
"stateMutability": "view",
"type": "function"
}, {
"inputs": [],
"name": "name",
"outputs": [{
"internalType": "string",
"name": "",
"type": "string"
}],
"stateMutability": "view",
"type": "function"
}]`
// ERC721 Interface Id, calculated in the previous course
var interfaceId = "0x80ac58cd"
// Register ABI
exchange.IO("abi", testContractAddress, testABI)
// Call the supportsInterface method
var isErc721 = exchange.IO("api", testContractAddress, "supportsInterface", interfaceId)
// Output Information
Log("Contract address:", testContractAddress)
Log("Contract name:", exchange.IO("api", testContractAddress, "name"))
Log("Contract code:", exchange.IO("api", testContractAddress, "symbol"))
Log("Whether the contract is ERC721 standard:", isErc721)
}
Tests can be run in the debugging tool:
2023-06-13 16:32:57 Info Whether the contract is ERC721 standard: true
2023-06-13 16:32:57 Info Contract code: BAYC
2023-06-13 16:32:57 Info Contract name: BoredApeYachtClub
2023-06-13 16:32:57 Info Contract address: 0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d
The contract with the address 0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d is determined to be ERC721 standard.
In this part, we introduced how to determine ERC721 contracts, so contracts like ERC20, which do not support the ERC165 standard, will have to be identified in another way. Do you know how to check if a contract is ERC20 standard?
Encoding calldata
What is calldata? By the author's understanding, a simple layman's description here is:
The "calldata" is the encoding of a function call or parameter in Ethereum, and the "calldata" is encoded according to the ABI (Application Binary Interface) specification of the contract.
For example, we can encode the balanceOf and transfer method calls of the ERC20 contract we studied in the previous course, together with the parameters of the calls, into a calldata. In some application scenarios, such as interaction between contracts, this scenario will use calldata, and of course there are many other application scenarios that are not listed here.
How to code a smart contract function call to get calldata?
In the FMZ Quant Trading Platform, you can use exchange.IO("encode", ...) to encode smart contract function calls, the use of exchange.IO("encode", ...) is very simple. The first parameter of the function is the fixed string "encode"; the second parameter is the address of the smart contract; the third parameter is the name of the smart contract method to be encoded; the rest of the parameters are passed to the specific parameter value of the smart contract method to be encoded.
eth_sendRawTransaction
When we encode a smart contract method call and generate the corresponding calldata data, if this smart contract method is a Write method (i.e.: write operation), we need to use the generated calldata data as the data field of the transaction and then use the Ethereum RPC method eth_ sendRawTransaction to send a request containing the raw data of that transaction to the Ethereum network.
The eth_sendRawTransaction method has only one parameter, data:
data: The signed transaction (typically signed with a library, using your private key)
The data parameter is a transaction data after the signature calculation, and the transaction data structure of Ethereum has the following main fields:
{
"nonce": "0x1", // Number of transactions on the account of the sender of the transaction
"gasPrice": "0x12a05f200", // Traded Gas price
"gasLimit": "0x5208", // Gas limit for trading
"to": "0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2", // Target contract address or recipient address
"value": "0x4563918244F40000", // Number of Ethereum transferred
"data": "0x0123456789ABCDEF", // Data to send to the contract
}
How to sign an Ethereum transaction?
In the FMZ Quant Trading Platform, we use the Encode() function to perform the signature calculation, the specific example we write in the subsequent course "Execute Write method calldata".
To be continued...