Get Started with web3 Development Easily Based on Ethereum Using FMZ (6)

Get Started with web3 Development Easily Based on Ethereum Using FMZ (6)
Atom
16.04.2024
FMZ


## 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:

```javascript
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:

```javascript
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:

```run
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:

```javascript
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:

```javascript
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:

```javascript
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:

```run
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.

```javascript
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:

```desc
{
"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.

```javascript
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:

```javascript
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:

```run
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:

```javascript
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:

```javascript
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```:

```javascript
// 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:

```javascript
/* 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.

```javascript
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):

```run
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.

```javascript
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:

```javascript
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:

```javascript
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](https://eips.ethereum.org/EIPS/eip-165) shows an example:

```solidity
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:

```solidity
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```.

```javascript
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:

```run
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](https://eips.ethereum.org/EIPS/eip-165) document.

### ERC721

Next let's look at the interface definition of the ERC721 contract standard:

```solidity
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:

```javascript
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[i]))
} else {
viewData = new Uint8Array(Encode("keccak256", "string", "raw", arrSelector[i]))

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:

```run
2023-06-13 15:04:09 Info 0x80ac58cd
```

It can be seen that the calculated results are consistent with those described in [eip-721](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md).

```solidity
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:

```javascript
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:

```run
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:

```javascript
{
"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...



Спасибо:




Добавить файлы через драг-н-дроп, , или вставить из буфера обмена.

loading
clippy