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

Mở rộng web3 dựa trên Ethereum với FMZ

Tác giả:Những nhà phát minh định lượng - những giấc mơ nhỏ, Tạo: 2023-03-28 13:32:48, Cập nhật: 2024-11-11 22:28:24

từBlock: từBlock, toBlock: toBlock, địa chỉ : tự hợp đồng topics: [self.eventHash] } // Log (( từBlockNumber:, 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 = không chức năng chính (() { var event = Transfer ((address,address,uint256)) Địa chỉ hợp đồng = 0xdac17f958d2ee523a2206206994597c13d831ec7 var số thập phân =exchange.IO("api", contractAddress, decimal) Lưu trữexchange.IO("api", contractAddress, name), " số thập phân:", số thập phân)

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 (bấm kết thúc chạy, xác nhận ghi lại) 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("长度不等!")
    return 
}

for (var i = 0; i < arrLog.length; i++) {
    Log("判断blockNumber:", logs[i].blockNumber == arrLog[i].blockNumber, ",判断from:", logs[i].topics[1] == arrLog[i].topics[1], 
        "判断to:", logs[i].topics[2] == arrLog[i].topics[2])
}

} */


实盘运行:

![img](/upload/asset/16ffd65adc050d33056d.png)

对于执行结果,代码中也编写了验证部分(TODO: test)。经过简单验证可以看到持续监控USDT合约的```Transfer```事件并且记录数据,用这个数据和一次性获取的事件数据对比可以观察出数据一致:

![img](/upload/asset/16e07390a11a606276b1.png)

### 事件过滤

在上一节课程「监听合约事件」的基础上,我们拓展一下,在监听的过程中增加过滤器,监听指定地址的转入转出。当智能合约创建日志时(即释放事件),日志数据中```topics```最多包含4条信息。所以我们设计一个过滤规则,以```[[A1, A2, ...An], null, [C1], D]```为例子。

1、```[A1, A2, ...An]```对应```topics[0]```位置的数据。
2、```null```对应```topics[1]```位置的数据。
3、```[C1]```对应```topics[2]```位置的数据。
4、```D```对应```topics[3]```位置的数据。

- 如果条件结构中的元素设置```null```表示不被过滤,例如```null```对应```topics[1]```,任何值都匹配。
- 如果条件结构中的元素设置单个值表示该位置必须匹配,例如```[C1]```对应```topics[2]```或者```D```对应```topics[3]```,不匹配的日志被过滤。
- 如果条件结构中的元素是一个数组,表示数组中的元素至少有一个要匹配,例如```[A1, A2, ...An]```对应```topics[0]```,```[A1, A2, ...An]```中有任意一个和```topics[0]```匹配,则日志不会被过滤。

**监听交易所的USDT转账**

监控从币安交易所转出、转入币安交易所```USDT```的交易:

```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 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("设置过滤条件:", 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) {
                // 检查过滤条件,设置了过滤条件则执行判断
                if (self.filters.length != 0) {
                    // 初始过滤标记
                    var isFilter = true 
                    // 遍历过滤条件设置
                    for (var j = 0; j < self.filters.length; j++) {
                        // 取一个过滤设置,例如:[[A1, A2, ...An], null, [C1], D]
                        var cond = self.filters[j]

                        // 遍历这个过滤设置
                        var final = true
                        for (var topicsIndex = 0; topicsIndex < cond.length; topicsIndex++) {
                            // 拿到这个过滤设置中的某一个条件,如果是第一个条件:即要和topics[0]对比的数据
                            var condValue = cond[topicsIndex]

                            // 日志中的数据
                            if (topicsIndex > logs[i].topics.length - 1) {
                                continue 
                            }
                            var topicsEleValue = logs[i].topics[topicsIndex]
                            // 如果是Transfer事件,需要处理from和to
                            if (logs[i].topics[0] == "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef") {
                                if (topicsIndex == 1 || topicsIndex == 2) {
                                    topicsEleValue = "0x" + exchange.IO("encodePacked", "address", topicsEleValue)
                                }
                            }

                            // 如果condValue类型是数组,表示该位置的对比条件有多个,多个条件对比是逻辑或关系
                            if (Array.isArray(condValue) && condValue.length > 1) {
                                // 判断 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() {
    // 初始清理日志
    LogReset(1)
    LogProfitReset()

    var event = "Transfer(address,address,uint256)"                          // 监听事件
    var contractAddress = "0xdac17f958d2ee523a2206206994597c13d831ec7"       // USDT合约地址
    var decimals = exchange.IO("api", contractAddress, "decimals")           // 获取USDT token的精度信息
    var accountBinanceAddress = "0x28C6c06298d514Db089934071355E5743bf21d60" // Binance 热钱包地址
    accountBinanceAddress = accountBinanceAddress.toLowerCase()              // 地址处理为小写
    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])
        if (fromAddress == accountBinanceAddress) {
            Log("币安转出 - ", " Transfer:", fromAddress, "->", toAddress, ", value:", toAmount(log.data, decimals), ", blockNumber:", toAmount(log.blockNumber, 0), "#CD32CD")
        } else if (toAddress == accountBinanceAddress) {
            Log("转入币安 - ", " Transfer:", fromAddress, "->", toAddress, ", value:", toAmount(log.data, decimals), ", blockNumber:", toAmount(log.blockNumber, 0), "#FF0000")
        }        
    })

    // 设置事件过滤
    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("币安钱包地址:", accountBinanceAddress, " 余额:", balance, direction)
            LogProfit(balance, "&")   // 只画图,不打印日志
            preBalance = balance
        }
        LogStatus(_D(), "币安钱包地址:", accountBinanceAddress, ", 余额:", balance)
        Sleep(5000 * 3)
    }
}

Các mã trên chạy trên ổ đĩa thực:

img

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 và sử dụng nó để nghe lén các ví nóng liên quan đến sàn giao dịch Bitcoin.USDTBạn có thể sửa đổi, mở rộng chương trình mô hình này để nghe bất kỳ sự kiện nào bạn quan tâm và xem.smart moneyNhững giao dịch mới nào đã được thực hiện?NFTNhững dự án mới mà Đà Nẵng đã tung ra, v.v.

Chuyển đổi đơn vị

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

inJavaScriptSố nguyên tối đa an toàn được định nghĩa trong ngôn ngữ:

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

Kết quả:

Số.MAX_SAFE_INTEGER: 9007199254740991

BigInt

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

Trong các nhà phát minh, chúng tôi sử dụng các nền tảng giao dịch định lượng.BigIntCác đối tượng biểu thị các dữ liệu số nguyên siêu lớn này; sử dụng các hàm xây dựngBigInt()Để xây dựngBigIntĐối tượng. Có thể sử dụng các giá trị số, các chuỗi giá trị số 16 chữ số để xây dựng các tham sốBigIntĐối tượng.BigIntđối tượngtoString()Phương pháp xuất dữ liệu được biểu thị bởi đối tượng dưới dạng chuỗi.

BigIntCác hoạt động được hỗ trợ bởi đối tượng:

  • Các công thức cộng:+
  • Phân trừ các phép tính:-
  • Lập toán nhân:*
  • Không tính toán:/
  • Xác định hình thức:%
  • Xét nghiệm:**

Ví dụ về mã sau:

function main() {
    // 1Gwei的十进制表示
    var oneGwei = 1000000000

    // 1Gwei的十进制转换为十六进制表示
    var oneGweiForHex = "0x" + oneGwei.toString(16)

    Log("oneGwei : ", oneGwei)
    Log("oneGweiForHex : ", oneGweiForHex)

    // 构造BigInt对象
    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("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))
}

Các công cụ thử nghiệm:

2023-06-08 11:39:50		信息	Number.MAX_SAFE_INTEGER * 2 : 18014398509481982
2023-06-08 11:39:50		信息	Number.MAX_SAFE_INTEGER : 9007199254740991
2023-06-08 11:39:50		信息	100 的平方根 : 10
2023-06-08 11:39:50		信息	1Gwei ** 2 : 1000000000000000000
2023-06-08 11:39:50		信息	(1Gwei + 1) % 1Gwei : 1
2023-06-08 11:39:50		信息	1Gwei + 1Gwei : 2000000000
2023-06-08 11:39:50		信息	1Gwei - 1Gwei : 0
2023-06-08 11:39:50		信息	1Gwei * 1Gwei : 1000000000000000000
2023-06-08 11:39:50		信息	1Gwei / 1Gwei : 1
2023-06-08 11:39:50		信息	oneGweiForHex : 0x3b9aca00
2023-06-08 11:39:50		信息	oneGwei : 1000000000

BigFloat

BigFloatđối tượng vàBigIntCác đối tượng sử dụng tương tự, được sử dụng để biểu thị các số chấm nổi lớn hơn, cũng hỗ trợ phép tính cộng trừ.BigFloatHỗ trợ đối tượngtoFixed()Phương pháp.

Ví dụ về 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))
}

Các công cụ thử nghiệm:

2023-06-08 13:56:44		信息	pi / 2.0 : 1.57
2023-06-08 13:56:44		信息	pi * 2.0 : 6.28
2023-06-08 13:56:44		信息	pi - oneGweiForHex : -999999996.86
2023-06-08 13:56:44		信息	pi + oneGwei : 1000000003.14

BigDecimal

BigDecimalĐối tượng tương thích với các giá trị nguyên tố, giá trị tròn, hỗ trợ sử dụngBigIntCác đối tượng:BigFloatCác đối tượng được khởi tạo, cũng hỗ trợ các hoạt động cộng trừ nhân.

Ví dụ về 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())
}

Các công cụ này được sử dụng trong:

2023-06-08 14:52:53		信息	pi / pi : 1
2023-06-08 14:52:53		信息	2.0 * pi : 6.283
2023-06-08 14:52:53		信息	oneGwei - pi : 999999996.8585
2023-06-08 14:52:53		信息	oneGwei + pi : 1000000003.1415
2023-06-08 14:52:53		信息	BigFloat(pi) : 3.1415
2023-06-08 14:52:53		信息	BigInt(oneGwei) : 1e+9
2023-06-08 14:52:53		信息	oneGweiForHex : 1e+9
2023-06-08 14:52:53		信息	oneGwei : 1e+9
2023-06-08 14:52:53		信息	pi : 3.14

Chuyển đổi đơn vị

Sau đây là hai hàm:toAmount()toInnerAmount()Chúng ta đã sử dụng nhiều lần trong các bài học trước đây, hai hàm này chủ yếu được sử dụng để 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)
}

toAmount()Một hàm sẽ thay đổi một biếnsTheo các thông số chính xácdecimalsThực hiện chuyển đổi. Trong sự phát triển thực tế của web3, thường cần phải xử lý dữ liệu thứ sáu trên một số chuỗi. Chúng ta thường gặp trong các bài học trước đây, ví dụ như các hợp đồng thông minh.Transfer(address,address,uint256)Trong sự kiệndataDữ liệu từ trường:

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

Xử lý dữ liệu"data": "0x00000000000000000000000000000000000000000000000001c1a55000000000"Và sử dụngtoAmount()Các chức năng.dataDữ liệu trường được chuyển đổi thành các giá trị có thể đọc đượ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))  // 打印出 0.12656402755905127
}

Một token ETH chúng ta biết là1e18 weiNếu chúng ta có mộtweiDữ liệu đơn vị126564027559051260Làm thế nào để đổi thành số ETH? Sử dụngtoAmount(, 18)Các hàm có thể được chuyển đổi một cách đơn giản.toInnerAmount()Và hàm này làtoAmount()Hoạt động ngược của hàm (theo độ chính xác, tăng cường), sử dụng hai hàm này rất thuận tiện để chuyển đổi dữ liệu.

Điều cần lưu ý là phạm vi an toàn số nguyên trong ngôn ngữ JavaScript, đó làNumber.MAX_SAFE_INTEGERVí dụ sau đây cho thấy một vấn đề ẩn trong quá trình 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)
    
    // 十进制数值 10000000000000000 -> 十六进制数值 0x2386f26fc10000
    Log("转换", innerAmount, "为十六进制:", innerAmount.toString(16))
    Log("转换", BigInt(10000000000000000).toString(10), "为十六进制:", BigInt(10000000000000000).toString(16))
    
    Log("0x" + BigInt(10000000000000000).toString(16), "转换为10进制:", toAmount("0x" + BigInt(10000000000000000).toString(16), 0))
}

Có thể chạy trong các công cụ chỉnh sửa:

2023-06-15 16:21:40		信息	0x2386f26fc10000 转换为10进制: 10000000000000000
2023-06-15 16:21:40		信息	转换 10000000000000000 为十六进制: 2386f26fc10000
2023-06-15 16:21:40		信息	转换 10000000000000000 为十六进制: 10000000000000000
2023-06-15 16:21:40		信息	typeof(innerAmount): number , innerAmount: 10000000000000000
2023-06-15 16:21:40		信息	innerAmount: 10000000000000000
2023-06-15 16:21:40		信息	Number.MAX_SAFE_INTEGER: 9007199254740991

Những người tham gia cuộc thi này đã có những câu trả lời rất nhiều.

Log("转换", innerAmount, "为十六进制:", innerAmount.toString(16))

Trong khi đó, một số trang web khác cũng có thể được sử dụng.转换 10000000000000000 为十六进制: 10000000000000000Vì vậy, chúng ta có thể sử dụng một số cách khác nhau, nhưng không có chuyển đổi chính xác.Number.MAX_SAFE_INTEGER

Nhưng khi một số thập phân nằm trong phạm vi an toàn, đó là nhỏ hơnNumber.MAX_SAFE_INTEGERNhững người khác cũng có thể làm điều đó.toString(16)Phương pháp chuyển đổi hàm là bình thường, ví dụ:

function main() {
    var value = 1000
    Log("把value转换为十六进制:", "0x" + value.toString(16))   // 0x3e8
    Log("把0x3e8转换为十进制:", Number("0x3e8"))               // 1000
}

Trong lĩnh vực blockchain, ngay cả khi0.01Một ETH được đổi thànhweiGiá trị của đơn vị10000000000000000Và sẽ vượt quaNumber.MAX_SAFE_INTEGERVì vậy, việc chuyển đổi an toàn hơn trong trường hợp này là:BigInt(10000000000000000).toString(16)

Gọi tương tự

Các giao dịch được thực hiện trên Ethereum, gọi các hợp đồng thông minhWritePhương pháp này đòi hỏi phải tiêu thụ một số chi phí khí đốt và đôi khi có nguy cơ thất bại. Việc biết những giao dịch có thể thất bại trước khi gửi giao dịch hoặc gọi rất quan trọng.

eth_call

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

eth_callCác phương thức có hai tham số, một là một cấu trúc từ điển, và hai 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 tham số thứ hai làblockNumberCó thể đăng thẻ:latest/pending/earliestVâng.

/* 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 sẽ sử dụng token.DAICách hợp đồng thông minhapprovetransferMột ví dụ cho việc gọi tương tự, môi trường thử nghiệm sau đây là mạng Ethereum.

Ghi chú:

Đối với hợp đồng ERC20approvePhương pháp này rất quen thuộc với tất cả chúng ta, và chúng ta đã thực hành trong các bài học trước đây. Bởi vì hợp đồng ERC20 đã được tích hợp trong nền tảng FMZ ABI, bạn không cần đăng ký các hợp đồng thông minh ABI mà bạn muốn bắt chước.

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

Trong ví dụ này, mã đầu tiên sẽ làapprove(address,uint256)Các phương pháp, các tham số được mã hóa.approveGiá trị tham số của phương pháp0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffChỉ số tối đa cho phép. Cho phép hợp đồng thông minh, địa chỉ0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45Có nghĩa làUniswap V3Các phương pháp RPC cuối cùng được gọi là Ethereumeth_callBạn có thể xem hình ảnh trên.transactionObjectTrong tham sốgasPricegasCác trường có thể bỏ qua.

Công cụ gỡ lỗi chạy, giả định gọi approve phương pháp ủy quyền thành công (không thực sự ủy quyền):

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

Chúng ta có thể mô phỏng một số kịch bản thất bại khi chúng ta điều chỉnh.gasPricegasTrong một số trường hợp, bạn có thể sử dụng ETH để trả tiền điện.

không đủ tiền

Khi đặt giá gas quá thấp, sẽ có lỗi:

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

Analogue call transfer

Đối với ERC20transferMột phương pháp mà chúng tôi không quen thuộc là chuyển token ERC20 cho một địa chỉ ví, và chúng tôi thử mô phỏng chuyển 1000 DAI cho V.

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

    // 转账给V神
    var decimals_DAI = exchange.IO("api", contractAddress_DAI, "decimals")
    var transferAmount = toInnerAmount(1000, decimals_DAI)
    Log("转账金额:", 1000, "DAI, 使用 toInnerAmount 转换为:", 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ì ví thử nghiệm của tôi không có token DAI, kết quả mà tôi mong đợi khi chạy trong công cụ debugging là sai:

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

Hãy xem địa chỉ ví của V:0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045Vì vậy, chúng ta hãy điều chỉnh hướng chuyển tiền của cuộc gọi tương tự, giả sử V-God chuyển tiền cho chúng ta 1000 DAI.

Tôi đã viết một chú thích ở chỗ đã được sửa đổi:

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("转账金额:", 1000, "DAI, 使用 toInnerAmount 转换为:", transferAmount)

    // encode transfer
    var data = exchange.IO("encode", contractAddress_DAI, "transfer(address,uint256)",
        wallet, transferAmount)     // 使用wallet变量作为参数,转账接收方地址改为我自己

    var transactionObject = {
        "from" : walletVitalik,     // 使用walletVitalik变量作为from字段的值,模拟这个调用是由V神钱包地址发出
        "to" : contractAddress_DAI,
        "data" : "0x" + data,
    }
    var blockNumber = "latest"
    
    var ret = exchange.IO("api", "eth", "eth_call", transactionObject, blockNumber)
    Log(ret)
}

Các công cụ thử nghiệm:

2023-06-09 13:34:31		信息	0x0000000000000000000000000000000000000000000000000000000000000001
2023-06-09 13:34:31		信息	转账金额: 1000 DAI, 使用 toInnerAmount 转换为: 1000000000000000000000

Sử dụng nền tảng giao dịch định lượng của nhà phát minh, bạn có thể dễ dàng mô phỏng kết quả của giao dịch và tránh việc gửi các giao dịch có thể thất bại gây ra tổn thất chi phí khí không cần thiết. Chúng tôi đã sử dụng mã ví dụ trong bài học trong chương này để mô phỏng việc chuyển tiền đến ví của V và cuộc gọi chuyển tiền từ ví của V đến chúng tôi.eth_callCác phương pháp còn nhiều hơn nữa. Sử dụng trí tưởng tượng của bạn, bạn sẽ có được nhiều hơn.eth_callCác phương pháp này được sử dụng ở đâu?

Nhận dạng hợp đồng ERC721

Chúng ta biết rằng các token như ETH, BTC đều thuộc về các token đồng nhất, và các token trong tay bạn cũng không khác với các token trong tay tôi. Nhưng có rất nhiều thứ khác nhau trên thế giới, chẳng hạn như: bất động sản, đồ cổ, nghệ thuật ảo, v.v. không thể được thể hiện trừu tượng bằng các token đồng nhất. 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 nhận ra những hợp đồng thông minh nào là hợp đồng thông minh theo tiêu chuẩn ERC721?

Để nhận ra ERC721, bạn phải hiểu về tiêu chuẩn ERC165.

ERC165

Thông qua tiêu chuẩn ERC165, một hợp đồng thông minh có thể tuyên bố giao diện mà nó hỗ trợ để kiểm tra các hợp đồng khác.supportsInterface(bytes4 interfaceId), tham sốinterfaceIdID giao diện đang được truy vấn. Nếu hợp đồng thực hiện ID giao diện này trả về giá trị True của boolean, nếu không trả về giá trị False.

Sau đây chúng ta sẽ nói về điều này.interfaceIdCác công ty khác cũng có thể sử dụng các công cụ này.

ERC165 tiêu chuẩnĐây là 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ý hàm của giao diện (bao gồm danh sách tên hàm và loại tham số), đối với hợp đồng giao diện ERC165 chỉ có một hàm:

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.

Nếu bạn tính chữ ký của hàm trực tiếp, bạn lấy 4 byte đầu tiên của nó và bạn sẽ nhận đượcinterfaceId

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

Bạn có thể chạy các bài kiểm tra trong công cụ chỉnh sửa:

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

Bạn có thể thấy kết quả tính toán vàERC165 tiêu chuẩnCác tài liệu mô tả phù hợp.

ERC721

Tiếp theo chúng ta 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 muốn xác định một hợp đồng thông minh là hợp đồng ERC721, trước tiên chúng ta cần biếtinterfaceIdVà sau đó bạn có thể thử sử dụng nó.supportsInterface(bytes4 interfaceId)Các bài học trước đây chúng tôi đã làm quen với một số khái niệm và tính toán của tiêu chuẩn ERC165.interfaceIdChúng tôi viết mã trực tiếp để tính toán:

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 "错误:数组中元素个数为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 "错误:TypeArray view长度不同"
                    }

                    for (var index = 0; index < ret.length; index++) {
                        ret[index] ^= viewData[index]
                    }
                }
            }
            ret = Encode("raw", "raw", "hex", ret.buffer)
        }
    } else {
        throw "错误:参数需要数组类型。"
    }

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

sử dụng trong mãEncode()Chức năng để tính chữ ký chức năngkeccak256Các thuật toán) cho các phép tính trong ví dụ mã trên, chỉ địnhEncode()Các tham số đầu ra của hàm là"raw"Chức năng này trả vềJavaScriptNgôn ngữArrayBufferLoại. Nếu chúng ta tính haiArrayBufferđối tượng được thực hiện^(Hoặc) tính toán, cần dựa trênArrayBufferXây dựng đối tượngTypedArrayCác công cụ này được sử dụng để tạo ra một khung hình, sau đó đi qua các dữ liệu trong đó và thực hiện các phép tính hoặc tính toán riêng lẻ.

Các công cụ này được sử dụng trong:

2023-06-13 15:04:09		信息	0x80ac58cd

Bạn có thể thấy kết quả vàeip-721Một số người cho rằng,

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 một hợp đồng là hợp đồng theo tiêu chuẩn ERC721 hay không.BAYCĐể thử nghiệm, đây là một hợp đồng theo ERC721, trước tiên chúng ta cần đăng ký ABI, vì chúng ta chỉ gọi ba phương thức sau đây, chỉ có thể đăng ký ba phương thức này:

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

Các mã cụ thể như sau:

function main() {
    // ERC721的合约地址,这里用的BAYC
    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接口Id,在之前的课程中计算得出
    var interfaceId = "0x80ac58cd"

    // 注册ABI
    exchange.IO("abi", testContractAddress, testABI)

    // 调用supportsInterface方法
    var isErc721 = exchange.IO("api", testContractAddress, "supportsInterface", interfaceId)

    // 输出信息
    Log("合约地址:", testContractAddress)
    Log("合约名称:", exchange.IO("api", testContractAddress, "name"))
    Log("合约代号:", exchange.IO("api", testContractAddress, "symbol"))
    Log("合约是否为ERC721标准:", isErc721)
}

Bạn có thể chạy các bài kiểm tra trong công cụ chỉnh sửa:

2023-06-13 16:32:57		信息	合约是否为ERC721标准: true
2023-06-13 16:32:57		信息	合约代号: BAYC
2023-06-13 16:32:57		信息	合约名称: BoredApeYachtClub
2023-06-13 16:32:57		信息	合约地址: 0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d

Xác định địa chỉ0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13dCác công ty đã ký hợp đồng với tiêu chuẩn ERC721.

Trong bài nói chuyện này, chúng tôi đã giới thiệu cách đánh giá hợp đồng ERC721, và các hợp đồng ERC20 không hỗ trợ tiêu chuẩn ERC165 sẽ được xác định bằng một cách khác. Bạn có biết cách kiểm tra một hợp đồng có phải là tiêu chuẩn ERC20 hay không?

Mã hóa calldata

Cái gì?calldataTheo cách hiểu của tác giả, mô tả đơn giản và phổ biến ở đây là:

"Calldata" là mã hóa cho một hàm gọi hoặc tham số trong Ethereum, "calldata" được mã hóa theo quy định của ABI (Application Binary Interface) theo hợp đồng.

Ví dụ, chúng ta có thể sử dụng các hợp đồng ERC20 mà chúng ta đã học trong các bài học trước.balanceOftransferCác phương thức gọi, cùng với các tham số khi gọi được mã hóa thành mộtcalldata❖ Trong một số trường hợp ứng dụng như:Sự tương tác giữa các hợp đồngTrong trường hợp này, nó sẽ rất hữu ích.calldataCó rất nhiều ứng dụng khác, tất nhiên, và chúng tôi đã liệt kê tất cả ở đây.

Làm thế nào để mã hóa một cuộc gọi hợp đồng thông minh và nhận đượccalldata

Các nhà phát minh có thể sử dụng nền tảng giao dịch định lượngexchange.IO("encode", ...)Có mã cho các cuộc gọi chức năng hợp đồng thông minh và rất đơn giản để sử dụng.exchange.IO("encode", ...)Các tham số đầu tiên của hàm là một chuỗi cố định."encode"; tham số thứ hai là địa chỉ của hợp đồng thông minh; tham số thứ ba là tên phương thức hợp đồng thông minh để mã hóa; các tham số còn lại được truyền vào các giá trị tham số cụ thể của phương thức hợp đồng thông minh để mã hóa.

eth_sendRawTransaction

Khi chúng ta mã hóa một cuộc gọi của một phương pháp hợp đồng thông minh và tạo ra một tương ứngcalldataKhi dữ liệu, nếu phương pháp hợp đồng thông minh này là phương pháp Write (tức là: viết các hoạt động), chúng ta cần phải tạo racalldataDữ liệu như một trường dữ liệu giao dịch, sau đó sử dụng phương pháp RPC của Ethereumeth_sendRawTransactionGửi một yêu cầu với dữ liệu nguyên liệu chứa giao dịch này đến mạng Ethereum.

eth_sendRawTransactionPhương pháp chỉ có một tham số.data

dữ liệu: Giao dịch đã ký (thường được ký bằng thư viện, sử dụng khóa riêng của bạn)

Cái này.dataParameter là dữ liệu giao dịch sau khi tính toán chữ ký, cấu trúc dữ liệu giao dịch của Ethereum chủ yếu có các trường sau:

{
    "nonce": "0x1",                         // 交易发送方的账户交易次数
    "gasPrice": "0x12a05f200",              // 交易的Gas价格
    "gasLimit": "0x5208",                   // 交易的Gas限制
    "to": "0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2",    // 目标合约地址或接收方地址
    "value": "0x4563918244F40000",          // 转账的以太币数量
    "data": "0x0123456789ABCDEF",           // 要发送给合约的数据
}

Làm thế nào để đăng ký giao dịch Ethereum?

Trong các nhà phát minh định lượng sàn giao dịch chúng tôi sử dụngEncode()Các hàm để thực hiện tính toán chữ ký, ví dụ cụ thể chúng tôi viết trong bài học tiếp theo "Executing Write method calldata".

Thực hiện phương thức Read calldata

Đối với phương pháp ReadcalldataTrong khi đó, các nhà nghiên cứu đã nghiên cứu về cách thực hiện RPC bằng cách sử dụng các phương pháp RPC mà chúng tôi đã học trước đây:eth_callChúng tôi đã giải thích trước đây.eth_callCách RPC của Ethereum chỉ làm hợp đồng thông minh.WriteMô tả phương pháp sử dụng trong chương nàycalldataChúng ta sẽ sử dụng WETH để thực hiện các cuộc gọi của các phương thức.balanceOfCách đọc số dư token WETH trong ví hiện tại.

Chúng tôi đã sử dụng công cụ chỉnh sửa để thử nghiệm trên Ethereum:

function toAmount(s, decimals) {
    return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString())
}

function main() {
    // WETH合约的ABI
    var abiWETH = `[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"guy","type":"address"},{"name":"wad","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"src","type":"address"},{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"wad","type":"uint256"}],"name":"withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"deposit","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":true,"name":"guy","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":true,"name":"dst","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"dst","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Withdrawal","type":"event"}]`

    // WETH合约地址
    var wethAddress = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"

    // 注册WETH合约的ABI
    exchange.IO("abi", wethAddress, abiWETH)

    // 当前配置的交易所对象的钱包地址
    var walletAddress = exchange.IO("address")

    // 编码WETH合约的deposit方法调用
    var calldataForDeposit = exchange.IO("encode", wethAddress, "balanceOf(address)", walletAddress)
    Log("calldataForDeposit:", "0x" + calldataForDeposit)

    // 构造transaction,作为eth_call的第一个参数
    var transaction = {
        "from" : walletAddress,
        "to" : wethAddress,
        "data" : "0x" + calldataForDeposit,
    }

    // eth_call的第二个参数
    var blockNumber = "latest"

    // 使用eth_call调用
    var ret = exchange.IO("api", "eth", "eth_call", transaction, blockNumber)
    var wethBalance = exchange.IO("decode", "uint256", ret)   // 可以使用exchange.IO("decode", ...) 函数解码
    Log("wethBalance:", toAmount(wethBalance, 18))            // 从以wei为单位,换算成WETH个数为单位
}

Các công cụ này được sử dụng trong:

2023-06-15 11:51:31		信息	wethBalance: 0.015
2023-06-15 11:51:31		信息	calldataForDeposit: 0x70a082310000000000000000000000006b3f11d807809b0b1e5e3243df04a280d9f94bf4

Nếu các phương pháp hợp đồng thông minh có giá trị trả lại, bạn có thể sử dụngexchange.IO("decode", ...)Phân mã hóa hàm.calldataMột số người đã sử dụng các phương pháp này để gọi trực tiếp các hợp đồng thông minh.balanceOfCách này cũng tương tự, tôi lấy cái ví thử nghiệm của tôi và số dư WETH là 0.015 WETH.

Thực hiện phương pháp Write calldata

Để thực hiện calldata của phương pháp Write, bạn cần sử dụng phương pháp RPC:eth_sendRawTransactionTôi không biết.

Chúng tôi đã sử dụng công cụ chỉnh sửa để thử nghiệm trên Ethereum:

function toAmount(s, decimals) {
    return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString())
}

function toInnerAmount(s, decimals) {
    return (BigDecimal(s)*BigDecimal(Math.pow(10, decimals))).toFixed(0)
}

function main() {
    // WETH合约的ABI
    var abiWETH = `[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"guy","type":"address"},{"name":"wad","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"src","type":"address"},{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"wad","type":"uint256"}],"name":"withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"deposit","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":true,"name":"guy","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed

Thêm nữa