Tài nguyên đang được tải lên... tải...

Bắt đầu phát triển web3 dễ dàng dựa trên Ethereum sử dụng FMZ

Tác giả:FMZ~Lydia, Tạo: 2023-06-25 09:17:53, Cập nhật: 2024-11-11 22:34:49

0000000000164f2434262e1cc", transactionHash: 0x6aa8d80daf42f442591e7530e31323d05e1d6dd9f9f9b9c102e157d89810c048, địa chỉ: 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2, blockHash: 0xcd3d567c9bd02a4549b1de0dc638ab5523e847c3c156b096424f56c633000fd9 {, { địa chỉ: 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2, blockHash: 0xcd3d567c9bd02a4549b1de0dc638ab5523e847c3c156b096424f56c633000fd9, blockNumber: 0x109b1cd, logIndex: 0xde, đóng bỏ: sai, topics: [0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65, 0x00000000000000000000ef1c6e67703c7bd7107eed8303fbe6ec2554bf6b], dữ liệu: 0x00000000000000000000000000000000000000000000000000000164f2434262e1cc, transactionHash: 0x6aa8d80daf42f442591e7530e31323d05e1d6dd9f9f9b9c102e157d89810c048, định chỉ giao dịch: 0x91


We can see that there are various events in the logs data, if we only care about ```Transfer``` events, we need to filter out the ```Transfer``` events in these data.

### Retrieving Logs

The Ethereum log is divided into two parts: 1. ```topics```; 2. ```data```.

- ```topics```
  Taking the results of the code run for the ```eth_getLogs``` section test as an example, the data in the ```topics``` field is:

  ```desc
  "topics": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x00000000000000000000000012b791bb27b3a4ee958b5a435fea7d49ec076e9c", "0x000000000000000000000000ef1c6e67703c7bd7107eed8303fbe6ec2554bf6b"],  

Giá trị củatopicstrường là một cấu trúc mảng được sử dụng để mô tả sự kiện. Nó được chỉ định rằng chiều dài (mảng) của nó không thể vượt quá 4 và phần tử đầu tiên là ký hiệu hash của sự kiện. Trong nền tảng giao dịch lượng tử FMZ, chúng ta có thể tính hash chữ ký này bằng cách sử dụngEncodechức năng, sử dụng mã sau:

function main() {
    var eventFunction = "Transfer(address,address,uint256)"
    var eventHash = Encode("keccak256", "string", "hex", eventFunction)
    Log("eventHash:", "0x" + eventHash)
    // eventHash: 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
}

Tính toánkeccak256giá trị băm (đã mã hóa hex) củaTransfer(address,address,uint256)0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef.

Giá trị củatopicstrường là một cấu trúc mảng, với phần tử thứ hai, và phần tử thứ ba, tương ứng:

  • Địa chỉ gửifrom

  • Địa chỉ nhậnto

  • data

    Dữ liệu trongdatatrường là:

    "data": "0x0000000000000000000000000000000000000000000000000164f2434262e1cc",
    

    Một số tham số trong trường hợp (các tham số không có tuyên bố lập chỉ mục trong mã Solidity của hợp đồng thông minh) được lưu trữ trongdata section.

    Phân tích dữ liệu0x0000000000000000000000000000000000000000000000000164f2434262e1cc

    function toAmount(s, decimals) {
        return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString())
    }
    
    function main() {
        var value = "0x0000000000000000000000000000000000000000000000000164f2434262e1cc"
        Log(toAmount(value, 0) / 1e18)  // 0.10047146239950075
    }
    

    Dữ liệu này được lấy như 0,10047146239950075 vàdatalà số tiền chuyển nhượng tương ứng.


Những điều trên đã được giải thích, thực hành và sẵn sàng để đi.

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() {
    // getBlockNumber
    var blockNumber = exchange.IO("api", "eth", "eth_blockNumber")
    Log("blockNumber:", blockNumber)

    // get logs
    var fromBlock = "0x" + (toAmount(blockNumber, 0) - 1).toString(16)
    var toBlock = "0x" + toAmount(blockNumber, 0).toString(16)
    var params = {
        "fromBlock" : fromBlock,
        "toBlock" : toBlock,
        "address" : "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
    }
    var logs = exchange.IO("api", "eth", "eth_getLogs", params)

    // Traverse logs
    var eventFunction = "Transfer(address,address,uint256)"
    var eventHash = "0x" + Encode("keccak256", "string", "hex", eventFunction)
    Log("eventHash:", eventHash)

    var counter = 0
    for (var i = logs.length - 1; i >= 0 && counter < 10; i--) {
        if (logs[i].topics[0] == eventHash) {
            Log("Event Transfer, data:", toAmount(logs[i].data, 0) / 1e18, ", blockNumber:", toAmount(logs[i].blockNumber, 0), ", transactionHash:", logs[i].transactionHash,
              ", log:", logs[i])
            counter++
        }
    }
}

Kiểm tra đi.https://etherscan.io/:

img

Kết quả của mã thử nghiệm chạy trong công cụ gỡ lỗi FMZ:

img

Dữ liệu trongfrom, toCác trường cũng có thể được phân tích tùy thuộc vào nhu cầu tại thời điểm truy xuất, ví dụ:

function main() {
    var from = "0x00000000000000000000000012b791bb27b3a4ee958b5a435fea7d49ec076e9c"
    var address = "0x" + exchange.IO("encodePacked", "address", from)
    Log("address:", address)
}

Kết quả chạy:

Địa chỉ: 0x12b791bb27b3a4ee958b5a435fea7d49ec076e9c

Nghe các sự kiện hợp đồng

Kể từ khicông cụ gỡ lỗitrong phần này, chúng tôi sử dụng nền tảng giao dịch FMZ Quant để tạo giao dịch trực tiếp để kiểm tra.

Ở đây chúng ta sử dụng Ethereum mainnet, và chúng ta lắng ngheTransfer(address,address,uint256)sự kiện củaUSDTDựa trên những gì chúng tôi đã học được trong bài học trước, chúng tôi đã thiết kế và viết một ví dụ liên tục lắng nghe các sự kiện của một hợp đồng thông minh nhất định:

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 addEventListener(contractAddress, event, callBack) {
    var self = {}
    self.eventHash = "0x" + Encode("keccak256", "string", "hex", event)
    self.contractAddress = contractAddress
    self.latestBlockNumber = 0
    self.fromBlockNumber = 0
    self.firstBlockNumber = 0
    /* TODO: test
    self.isFirst = true 
    */ 

    self.getBlockNumber = function() {
        var maxTry = 10
        for (var i = 0; i < maxTry; i++) {
            var ret = exchange.IO("api", "eth", "eth_blockNumber")
            if (ret) {
                return toAmount(ret, 0)
            }
            Sleep(5000)
        }
        throw "getBlockNumber failed"
    }

    self.run = function() {
        var currBlockNumber = self.getBlockNumber()
        var fromBlock = "0x" + self.fromBlockNumber.toString(16)
        var toBlock = "0x" + currBlockNumber.toString(16)
        var params = {
            "fromBlock" : fromBlock, 
            "toBlock" : toBlock, 
            "address" : self.contractAddress, 
            "topics" : [self.eventHash]
        }
        // Log("fromBlockNumber:", self.fromBlockNumber, ", currBlockNumber:", currBlockNumber, "#FF0000")
        
        var logs = exchange.IO("api", "eth", "eth_getLogs", params)
        if (!logs) {
            return 
        }

        for (var i = 0; i < logs.length; i++) {
            if (toAmount(logs[i].blockNumber, 0) > self.latestBlockNumber) {
                /* TODO: test
                if (self.isFirst) {
                    self.firstBlockNumber = toAmount(logs[i].blockNumber, 0)
                    Log("firstBlockNumber:", self.firstBlockNumber)
                    self.isFirst = false 
                }
                */

                callBack(logs[i])
            }
        }

        self.latestBlockNumber = currBlockNumber
        self.fromBlockNumber = self.latestBlockNumber - 1
    }

    self.latestBlockNumber = self.getBlockNumber()
    self.fromBlockNumber = self.latestBlockNumber - 1

    return self
}

var listener = null 
function main() {
    var event = "Transfer(address,address,uint256)"
    var contractAddress = "0xdac17f958d2ee523a2206206994597c13d831ec7"
    var decimals = exchange.IO("api", contractAddress, "decimals")
    Log(exchange.IO("api", contractAddress, "name"), " decimals:", decimals)

    listener = addEventListener(contractAddress, event, function(log) {        
        var fromAddress = "0x" + exchange.IO("encodePacked", "address", log.topics[1])
        var toAddress = "0x" + exchange.IO("encodePacked", "address", log.topics[2])
        Log("Transfer:", fromAddress, "->", toAddress, ", value:", toAmount(log.data, decimals), ", blockNumber:", toAmount(log.blockNumber, 0))
        
        /* TODO: test
        arrLog.push(log)
        */
    })

    while (true) {
        listener.run()
        Sleep(5000)
    }
}

/* TODO: test
var arrLog = []
function onexit() {
    Log("End the run and verify the record")
    var firstBlockNumber = listener.firstBlockNumber
    var endBlockNumber = listener.latestBlockNumber

    Log("getLogs, from:", firstBlockNumber, " -> to:", endBlockNumber)
    var fromBlock = "0x" + (firstBlockNumber).toString(16)
    var toBlock = "0x" + (endBlockNumber).toString(16)
    var params = {
        "fromBlock" : fromBlock,
        "toBlock" : toBlock,
        "topics" : ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"],
        "address" : "0xdac17f958d2ee523a2206206994597c13d831ec7"
    }
    var logs = exchange.IO("api", "eth", "eth_getLogs", params)

    Log("arrLog:", arrLog.length)
    Log("logs:", logs.length)

    if (arrLog.length != logs.length) {
        Log("Length varies!")
        return 
    }
    
    for (var i = 0; i < arrLog.length; i++) {
        Log("Determine the blockNumber:", logs[i].blockNumber == arrLog[i].blockNumber, ", Determine from:", logs[i].topics[1] == arrLog[i].topics[1], 
            "Determine to:", logs[i].topics[2] == arrLog[i].topics[2])
    }
}
*/

Chạy trên giao dịch trực tiếp:

img

Đối với kết quả thực hiện, một phần xác thực (TODO: test) cũng được viết trong mã.Transfersự kiện của hợp đồng USDT được theo dõi liên tục và dữ liệu được ghi lại và so sánh giữa dữ liệu này và dữ liệu sự kiện thu được một lần có thể quan sát thấy rằng dữ liệu phù hợp với:

img

Quét sự kiện

Dựa trên bài học trướcListening to contract events, chúng ta mở rộng nó bằng cách thêm các bộ lọc vào quá trình nghe để nghe chuyển giao đến và từ các địa chỉ được chỉ định.topicsVì vậy, chúng tôi thiết kế một quy tắc lọc với[[A1, A2, ...An], null, [C1], D]ví dụ.

  1. [A1, A2, ...An]tương ứng với dữ liệu tại vị trítopics[0].
  2. Nulltương ứng với dữ liệu tại vị trítopics[1].
  3. [C1]tương ứng với dữ liệu tại vị trítopics[2].
  4. Dtương ứng với dữ liệu tại vị trítopics[3].
  • Nếu một phần tử trong cấu trúc điều kiện được đặtnullcó nghĩa là nó không được lọc, ví dụ:nulltương ứng vớitopics[1]và bất kỳ sự khớp giá trị.
  • Nếu phần tử trong cấu trúc điều kiện đặt một giá trị duy nhất chỉ ra rằng vị trí phải phù hợp, ví dụ:[C1]tương ứng vớitopics[2]hoặcDtương ứng vớitopics[3], và các nhật ký không phù hợp được lọc.
  • Nếu phần tử trong cấu trúc điều kiện là một mảng, điều đó có nghĩa là ít nhất một phần tử trong mảng nên khớp, ví dụ:[A1, A2, ...An]tương ứng vớitopics[0], [A1, A2, ...An]với bất kỳ một trong số họ phù hợptopics[0], thì các bản ghi sẽ không được lọc.

Nghe chuyển tiền USDT từ sàn giao dịch

Giám sátUSDTcác giao dịch được chuyển từ và sang sàn giao dịch Binance:

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 addEventListener(contractAddress, event, callBack) {
    var self = {}
    self.eventHash = "0x" + Encode("keccak256", "string", "hex", event)
    self.contractAddress = contractAddress
    self.latestBlockNumber = 0
    self.fromBlockNumber = 0
    self.firstBlockNumber = 0
    self.filters = []
    
    self.setFilter = function(filterCondition) {
        if (filterCondition.length > 4) {
            throw "filterCondition error"
        }

        self.filters.push(filterCondition)
        Log("Set filter conditions:", filterCondition)
    }

    self.getTokenBalanceOfWallet = function(walletAddress, tokenAddress, tokenDecimals) {
        var balance = exchange.IO("api", tokenAddress, "balanceOf", walletAddress)
        if (balance) {
            return toAmount(balance, tokenDecimals)
        }
        return null
    }

    self.getBlockNumber = function() {
        var maxTry = 10
        for (var i = 0; i < maxTry; i++) {
            var ret = exchange.IO("api", "eth", "eth_blockNumber")
            if (ret) {
                return toAmount(ret, 0)
            }
            Sleep(5000)
        }
        throw "getBlockNumber failed"
    }

    self.run = function() {
        var currBlockNumber = self.getBlockNumber()
        var fromBlock = "0x" + self.fromBlockNumber.toString(16)
        var toBlock = "0x" + currBlockNumber.toString(16)
        var params = {
            "fromBlock" : fromBlock, 
            "toBlock" : toBlock, 
            "address" : self.contractAddress, 
            "topics" : [self.eventHash]
        }
        
        var logs = exchange.IO("api", "eth", "eth_getLogs", params)
        if (!logs) {
            return 
        }

        for (var i = 0; i < logs.length; i++) {
            if (toAmount(logs[i].blockNumber, 0) > self.latestBlockNumber) {
                // Check the filter condition, and execute the judgment if the filter condition is set
                if (self.filters.length != 0) {
                    // Initial filter marker
                    var isFilter = true 
                    // Traverse filter condition setting
                    for (var j = 0; j < self.filters.length; j++) {
                        // Take a filter setting, e.g: [[A1, A2, ...An], null, [C1], D]
                        var cond = self.filters[j]

                        // Traverse the filter setting
                        var final = true
                        for (var topicsIndex = 0; topicsIndex < cond.length; topicsIndex++) {
                            // Take one of the conditions in the filter setting, if it is the first condition: i.e. the data to be compared with topics[0]
                            var condValue = cond[topicsIndex]

                            // Data in the logs
                            if (topicsIndex > logs[i].topics.length - 1) {
                                continue 
                            }
                            var topicsEleValue = logs[i].topics[topicsIndex]
                            // If it's a Transfer event, you need to handle the from and to
                            if (logs[i].topics[0] == "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef") {
                                if (topicsIndex == 1 || topicsIndex == 2) {
                                    topicsEleValue = "0x" + exchange.IO("encodePacked", "address", topicsEleValue)
                                }
                            }

                            // If the condValue type is an array, it means that there are multiple comparison conditions in this position, and the multiple condition comparison is a logical or relationship
                            if (Array.isArray(condValue) && condValue.length > 1) {
                                // Determine condValue[0] == topicsEleValue || condValue[1] == topicsEleValue
                                final = final && condValue.some(element => element === topicsEleValue)
                            }else if (condValue === null) {
                                final = final && true
                            } else {
                                final = final && (condValue === topicsEleValue)
                            }
                        }
                        
                        if (final) {
                            isFilter = false 
                        }
                    }
                    
                    if (isFilter) {
                        continue
                    }
                }
                callBack(logs[i])
            }
        }

        self.latestBlockNumber = currBlockNumber
        self.fromBlockNumber = self.latestBlockNumber - 1
    }

    self.latestBlockNumber = self.getBlockNumber()
    self.fromBlockNumber = self.latestBlockNumber - 1

    return self
}

var listener = null 
function main() {
    // Initial clean-up log
    LogReset(1)
    LogProfitReset()

    var event = "Transfer(address,address,uint256)"                          // Listening to events
    var contractAddress = "0xdac17f958d2ee523a2206206994597c13d831ec7"       // USDT contract address
    var decimals = exchange.IO("api", contractAddress, "decimals")           // Get the precision information of USDT token
    var accountBinanceAddress = "0x28C6c06298d514Db089934071355E5743bf21d60" // Binance hot wallet address
    accountBinanceAddress = accountBinanceAddress.toLowerCase()              // Addresses are handled in lowercase
    Log(exchange.IO("api", contractAddress, "name"), " decimals:", decimals)

    // Creating a listener object
    listener = addEventListener(contractAddress, event, function(log) {
        var fromAddress = "0x" + exchange.IO("encodePacked", "address", log.topics[1])
        var toAddress = "0x" + exchange.IO("encodePacked", "address", log.topics[2])
        if (fromAddress == accountBinanceAddress) {
            Log("Binance transfer out - ", " Transfer:", fromAddress, "->", toAddress, ", value:", toAmount(log.data, decimals), ", blockNumber:", toAmount(log.blockNumber, 0), "#CD32CD")
        } else if (toAddress == accountBinanceAddress) {
            Log("Binance transfer in - ", " Transfer:", fromAddress, "->", toAddress, ", value:", toAmount(log.data, decimals), ", blockNumber:", toAmount(log.blockNumber, 0), "#FF0000")
        }        
    })

    // Set up event filtering
    listener.setFilter([null, accountBinanceAddress, null])   // Binance -> USDT
    listener.setFilter([null, null, accountBinanceAddress])   // USDT -> Binance
    
    var preBalance = 0
    while (true) {
        listener.run()
        var balance = listener.getTokenBalanceOfWallet(accountBinanceAddress, contractAddress, decimals)
        if (balance) {
            var direction = ""
            if (preBalance != 0 && preBalance > balance) {
                direction = " ↓ " + (preBalance - balance) + "#CD32CD"
            } else if (preBalance != 0 && preBalance < balance) {
                direction = " ↑ " + (balance - preBalance) + "#FF0000"
            }
            Log("Binance wallet address:", accountBinanceAddress, " balance:", balance, direction)
            LogProfit(balance, "&")   // Drawing only, no log printing
            preBalance = balance
        }
        LogStatus(_D(), "Binance wallet address:", accountBinanceAddress, ", balance:", balance)
        Sleep(5000 * 3)
    }
}

Mã trên được chạy trong giao dịch trực tiếp:

img

Trong bài học này, chúng tôi đã giới thiệu cách thiết kế bộ lọc sự kiện.USDTBạn có thể sửa đổi và mở rộng chương trình mẫu này để nghe bất kỳ sự kiện nào bạn quan tâm, để xem những giao dịch mớismart moneyđã thực hiện, những gì các mặt hàng mớiNFTCác ông trùm đã vội vàng, v.v.

Chuyển đổi đơn vị

Nhiều tính toán liên quan đến Ethereum có giá trị vượt quá số nguyên an toàn tối đa củaJavaScriptDo đó, một số phương pháp cần thiết trên nền tảng giao dịch lượng tử FMZ để xử lý các giá trị lớn, mà chúng tôi đã sử dụng cụ thể trong các khóa học trước đây và đã không đề cập chi tiết.

In số nguyên an toàn tối đa được xác định trongJavaScriptngôn ngữ:

function main() {
    Log("Number.MAX_SAFE_INTEGER:", Number.MAX_SAFE_INTEGER)
}

Kết quả chạy:

Số.MAX_SAFE_INTEGER: 9007199254740991

BigInt

Đơn vị nhỏ nhất được định nghĩa trong Ethereum là1wei, và định nghĩa1Gweibằng với1000000000 wei. 1Gweikhông thực sự là một số rất lớn trong các tính toán liên quan đến Ethereum, và một số dữ liệu lớn hơn nhiều.Number.MAX_SAFE_INTEGER: 9007199254740991.

Tại FMZ Quant Trading Platform, chúng tôi sử dụng các nền tảngBigIntsử dụng các cấu trúcBigInt()để xây dựngBigIntBạn có thể xây dựngBigIntđối tượng sử dụng số, chuỗi số hexadecimal như các tham số.toString()phương phápBigIntđối tượng để xuất dữ liệu được đại diện bởi đối tượng dưới dạng chuỗi.

Các hoạt động được hỗ trợ bởiBigIntMục tiêu là:

  • Thêm:+
  • Phân trừ:-
  • Xử nhân:*
  • Bộ phận:/
  • Hoạt động modulo:%
  • Hoạt động năng lượng:*

Xem các ví dụ mã sau:

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))
}

Kiểm tra công cụ gỡ lỗi:

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

CácBigFloatđối tượng được sử dụng tương tự nhưBigIntđối tượng để đại diện cho các số dấu phẩy động với các giá trị lớn hơn, và nó cũng hỗ trợ cộng, trừ, nhân và chia. CácBigFloatđối tượng hỗ trợtoFixed() method.

Xem ví dụ mã sau:

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))
}

Kiểm tra công cụ gỡ lỗi:

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

CácBigDecimalobject tương thích với giá trị số nguyên và giá trị dấu phẩy động, và hỗ trợ khởi tạo vớiBigIntđối tượng vàBigFloatvà nó cũng hỗ trợ cộng, trừ, nhân và chia.

Xem ví dụ mã sau:

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())
}

Chạy trong công cụ gỡ lỗi:

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

Chuyển đổi đơn vị

Hai chức năng sau:toAmount(), toInnerAmount()chúng tôi đã sử dụng nhiều lần trong các khóa học trước đây, hai chức năng này chủ yếu được sử dụng cho chuyển đổi chính xác dữ liệu.

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)
}

CáctoAmount()hàm chuyển đổi (giảm) một biếnstheo tham số độ chính xácdecimalsTrong phát triển thực tế web3, nó thường là cần thiết để đối phó với một số dữ liệu hexadecimal chuỗi. Chúng tôi đã gặp phải điều này thường xuyên trong các khóa học trước đây của chúng tôi, ví dụ:datadữ liệu thực địa trongTransfer(address,address,uint256)sự kiện hợp đồng thông minh:

{
	"data": "0x00000000000000000000000000000000000000000000000001c1a55000000000",
	"topics": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x0000000000000000000000006b75d8af000000e20b7a7ddf000ba900b4009a80", "0x000000000000000000000000bcb095c1f9c3dc02e834976706c87dee5d0f1fb6"],
	"transactionHash": "0x27f9bf5abe3148169b4b85a83e1de32bd50eb81ecc52e5af006157d93353e4c4",
	"transactionIndex": "0x0",
	"removed": false,
	"address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
	"blockHash": "0x847be24a7b159c292bda030a011dfec89487b70e71eed486969b032d6ef04bad",
	"blockNumber": "0x109b1cc",
	"logIndex": "0x0"
}

Khi xử lý dữ liệu"data": "0x00000000000000000000000000000000000000000000000001c1a55000000000", chúng ta sử dụngtoAmount()Điều này xử lý được thiết kế để làm một công việc tốt của việc chuyển đổi dữ liệu trường dữ liệu vào giá trị có thể đọc.

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 token ETH, như chúng ta biết, là1e18 wei, nếu chúng ta có được một dữ liệu126564027559051260trongwei, làm thế nào để chuyển đổi nó thành token ETH? Sử dụngtoAmount(, 18)hàm là một phương pháp chuyển đổi rất đơn giản.toInnerAmount()chức năng là hoạt động ngược lại củatoAmount()chức năng (tùy thuộc vào độ chính xác, phóng to), và nó là dễ dàng để chuyển đổi dữ liệu bằng cách sử dụng hai chức năng này.

Điều quan trọng cần lưu ý là phạm vi an toàn giá trị số nguyên trong ngôn ngữ JavaScript,Number.MAX_SAFE_INTEGER, và ví dụ sau đây minh họa một vấn đề ẩn khi chuyển đổi dữ liệu:

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))
}

Có thể chạy trong công cụ gỡ lỗi:

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

Thông qua quan sát, chúng tôi thấy rằng:

Log("Convert", innerAmount, "to hexadecimal:", innerAmount.toString(16))

Dòng mã này tương ứng với đầu ra log:Converting 10000000000000000 to hex: 10000000000000000, mà không được chuyển đổi chính xác. Lý do là tự nhiên rằng 10000000000000000 là ngoàiNumber.MAX_SAFE_INTEGER.

Nhưng khi giá trị thập phân nằm trong phạm vi an toàn, tức là, nhỏ hơnNumber.MAX_SAFE_INTEGER, cáctoString(16)hàm chuyển đổi nó một cách chính xác một lần nữa, ví dụ:

function main() {
    var value = 1000
    Log("Convert value to hexadecimal:", "0x" + value.toString(16))   // 0x3e8
    Log("Convert 0x3e8 to decimal:", Number("0x3e8"))               // 1000
}

Trong blockchain, thậm chí0.01ETH chuyển đổi thành giá trị10000000000000000trongweisẽ vượt quáNumber.MAX_SAFE_INTEGER``, so a safer conversion for such cases is:BigInt ((100000000000000000000).toString ((16) ``.

Các cuộc gọi mô phỏng

Thực hiện các giao dịch và gọiWritePhương pháp hợp đồng thông minh trên Ethereum tốn một lượng khí đốt nhất định và đôi khi nó thất bại.

eth_call

Phương pháp RPC của Ethereumeth_call: nó có thể mô phỏng một giao dịch và trả về kết quả của một giao dịch có thể xảy ra, nhưng nó không thực sự thực hiện giao dịch trên blockchain.

Cáceth_callphương pháp có 2 tham số, một là một cấu trúc từ điển,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
}

Các thông số thứ hai làblockNumber: bạn có thể vượt qua nhãnlatest/pending/earliest, vv:

/* 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
*/

Tiếp theo, chúng ta lấy phương pháp hợp đồng thông minhapprovetransfergọi của tokenDAInhư một ví dụ cho các cuộc gọi mô phỏng, và môi trường thử nghiệm sau đây là mạng Ethereum chính.

Ghi âm cuộc gọi mô phỏng

Chúng ta đều quen thuộc vớiapproveCác phương pháp này được sử dụng trong các khóa học trước đây, vì hợp đồng ERC20 đã được tích hợp vào nền tảng FMZ ABI, không cần phải đăng ký ABI của hợp đồng thông minh để được gọi bởi mô phỏng.

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)
}

Mã trong ví dụ đầu tiên mã hóaapprove(address,uint256)phương pháp và tham số, và giá trị tham số0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffcủaapproveCác hợp đồng thông minh được cung cấp tại địa chỉ0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45tức là hợp đồng router choUniswap V3Cuối cùng là phương pháp Ethereum RPCeth_callbạn có thể thấy rằng cácgasPricegascác lĩnh vực trongtransactionObjectcác tham số có thể bị bỏ qua.

Công cụ gỡ lỗi được chạy và mô phỏng gọi phương thức phê duyệt để ủy quyền thành công (nó không thực sự ủy quyền):

2023-06-09 11:58:39		Info	ret: 0x0000000000000000000000000000000000000000000000000000000000000001
2023-06-09 11:58:39		Info	ERC20 token DAI approve encode, data: 095ea7b300000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc45ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff

Nó cũng có thể mô phỏng một số kịch bản thất bại, khi chúng ta điều chỉnhgasPricegascác thông số, nếu ETH trong ví không đủ để thanh toán phí xăng, một lỗi sẽ được báo cáo::

không đủ tiền

Khi giá xăng được đặt quá thấp, một lỗi sẽ được báo cáo:

khí nội tại quá thấp: có 21000, muốn 21944 (được cung cấp khí 21000)

Chuyển đổi cuộc gọi mô phỏng

Chúng tôi quen thuộc với ERC20stransferphương pháp, cho phép bạn chuyển token ERC20 đến một địa chỉ ví nhất định, vì vậy hãy thử mô phỏng chuyển 1000 DAI đến 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 
}

Vì tôi không có token DAI trong ví thử nghiệm này, chạy nó trong công cụ gỡ lỗi báo cáo lỗi sau một cách bất ngờ:

thực hiện đảo ngược: Dai/không đủ số dư

Kiểm tra địa chỉ ví của Vitalik Buterin:0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045Vì vậy, hãy điều chỉnh hướng chuyển giao của cuộc gọi mô phỏng và mô phỏng việc chuyển giao 1000 DAI từ Vitalik Buterin cho chúng tôi.

Thay đổi mã, nơi những thay đổi tôi đã thực hiện bình luận:

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)
}

Kiểm tra công cụ gỡ lỗi:

2023-06-09 13:34:31		Info	0x0000000000000000000000000000000000000000000000000000000000000001
2023-06-09 13:34:31		Info	Transfer amount: 1000 DAI, use toInnerAmount convert to: 1000000000000000000000

Sử dụng nền tảng giao dịch FMZ Quant, rất dễ dàng mô phỏng kết quả giao dịch và tránh mất phí khí không cần thiết từ việc gửi các giao dịch có khả năng thất bại. Chúng tôi đã sử dụng mã ví dụ từ chương này của khóa học để mô phỏng cuộc gọi chuyển tiền vào ví của Vitalik Buterin và ví của Vitalik Buterin để chuyển tiền cho chúng tôi.eth_callSử dụng trí tưởng tượng của bạn, những gì bạn sẽ sử dụngeth_callphương pháp cho?

Xác định các hợp đồng ERC721

Chúng ta biết rằng các token như ETH và BTC là các token đồng nhất, và token trong ví của bạn không khác với token trong ví của tôi. Nhưng có nhiều thứ trên thế giới không đồng nhất, chẳng hạn như bất động sản, đồ cổ, tác phẩm nghệ thuật ảo, v.v. Chúng không thể được đại diện bằng các token đồng nhất trong trừu tượng. Do đó, có tiêu chuẩn ERC721 để trừu tượng các đối tượng không đồng nhất, và có NFT và các khái niệm liên quan. Vì vậy, trong số nhiều hợp đồng thông minh được triển khai trên Ethereum, làm thế nào để chúng ta xác định hợp đồng thông minh nào là hợp đồng thông minh tiêu chuẩn ERC721?

Để xác định ERC721, điều quan trọng là phải biết tiêu chuẩn ERC165 trước.

ERC165

Với tiêu chuẩn ERC165, một hợp đồng thông minh có thể tuyên bố các giao diện mà nó hỗ trợ cho các hợp đồng khác để kiểm tra.supportsInterface(bytes4 interfaceId), tham sốinterfaceIdlà ID giao diện để được truy vấn. Nếu hợp đồng thực hiện interfaceId trả về một giá trị đúng boolean, nếu không nó trả về một giá trị sai.

Ở đây chúng ta sẽ nói về cách nàyinterfaceIdđược tính toán và mã hóa cụ thể.

Tiêu chuẩn ERC165cho thấy một ví dụ:

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;
    }
}

Đối với chữ ký chức năng của giao diện (bao gồm tên chức năng và danh sách các loại tham số) để thực hiện một hoạt động không giống nhau, đối với hợp đồng giao diện ERC165 khi hợp đồng chỉ có một chức năng:

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);
}

Mã nhận diện giao diện cho giao diện này là 0x01ffc9a7. Bạn có thể tính toán bằng cách chạy bytes4 ((keccak256 ((supportsInterface ((bytes4) )); hoặc sử dụng hợp đồng Selector ở trên.

Tính toán chữ ký hàm trực tiếp và lấy 4 byte đầu tiên của nó để đếninterfaceId.

function main() {
    var ret = Encode("keccak256", "string", "hex", "supportsInterface(bytes4)")
    Log("supportsInterface(bytes4) interfaceId:", "0x" + ret.slice(0, 8))
}

Các thử nghiệm có thể được chạy trong công cụ gỡ lỗi tại:

2023-06-13 14:53:35		Info	supportsInterface(bytes4) interfaceId: 0x01ffc9a7

Có thể thấy rằng kết quả tính toán phù hợp với mô tả trongTiêu chuẩn ERC165 document.

ERC721

Tiếp theo hãy xem định nghĩa giao diện của tiêu chuẩn hợp đồng ERC721:

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);
}

Nếu chúng ta muốn xác định xem một hợp đồng thông minh là một hợp đồng ERC721, trước tiên chúng ta cần biếtinterfaceIdcủa hợp đồng ERC721 trước khi chúng ta có thể cố gắng sử dụngsupportsInterface(bytes4 interfaceId)Trong các khóa học trước đây, chúng tôi đã làm quen với một số khái niệm của tiêu chuẩn ERC165 và thuật toán để tính toáninterfaceId, và chúng ta viết mã để tính toán trực tiếp:

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)
}

Mã sử dụngEncode()chức năng để tính toán chữ ký chức năng (thekeccak256thuật toán), và để tính toán trong ví dụ mã ở trên, xác định các thông số đầu ra củaEncode()chức năng như"raw", hàm trả vềArrayBufferloạiJavaScriptngôn ngữ. Để thực hiện một^(iso-or) hoạt động trên haiArrayBufferđối tượng, bạn cần phải tạo ra mộtTypedArrayquan điểm dựa trênArrayBufferđối tượng, sau đó lặp lại thông qua các dữ liệu trong đó và thực hiện các hoạt động iso-or một một.

Chạy trong công cụ gỡ lỗi:

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

Có thể thấy rằng các kết quả tính toán phù hợp với những kết quả được mô tả trongeip-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);
...

Với ID giao diện ERC721, chúng ta có thể xác định nếu một hợp đồng là một hợp đồng tiêu chuẩn ERC721 hoặc không.BAYCTrước tiên chúng ta cần đăng ký ABI, và vì chúng ta chỉ gọi ba phương thức sau đây, chúng ta có thể đăng ký ba phương thức này:

  • hỗ trợ Interface ((interfaceId)
  • biểu tượng ((()
  • tên

Mã cụ thể là như sau:

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)
}

Các thử nghiệm có thể được chạy trong công cụ gỡ lỗi:

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

Có liên quan

Thêm nữa