Sumber dimuat naik... memuat...

Pengembangan web3 berasaskan Ethereum dengan FMZ

Penulis:Pencipta Kuantiti - Impian Kecil, Dicipta: 2023-03-28 13:32:48, Dikemas kini: 2024-11-11 22:28:24

dariBlok: dariBlok, toBlock: toBlock, alamat : sendiri.kontrakAlamat, topics : [self.eventHash] { C: $ 00FFFF } // Log ((dariBlockNumber:, 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 pendengar = sifar Fungsi utama var event = Transfer ((alamat,alamat,uint256) var kontrakAlamat = 0xdac17f958d2ee523a2206206994597c13d831ec7 var perpuluhan =exchange.IO("api", kontrakAlamat, desimal) Log (((exchange.IO("api", kontrakAlamat, nama), " desimal:", desimal)

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: ujian var arrLog = [] fungsi onexit ((() { Log (menamatkan pelaksanaan, menyemak log) 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)
    }
}

Kod di atas berfungsi pada cakera sebenar:

img

img

Dalam kursus ini, kami menerangkan cara mereka merancang penapis peristiwa.USDTAnda boleh mengubah, mengembangkan, dan memperluaskan program ini untuk memantau apa sahaja yang anda minat.smart moneyApakah perjanjian baru yang dibuat?NFTApakah projek baharu yang telah ditanam di Da Nang dan sebagainya?

Pengubahsuaian unit

Dalam banyak perhitungan yang berkaitan dengan Ethereum, jumlahnya melebihiJavaScriptJumlah bulat maksimum yang selamat dalam bahasa. Oleh itu, terdapat beberapa kaedah yang diperlukan untuk memproses bilangan besar di platform perdagangan kuantiti pencipta, yang juga telah kita gunakan secara khusus dalam kursus sebelumnya, tanpa dijelaskan secara terperinci.

PercetakanJavaScriptBilangan bulat keselamatan maksimum yang ditakrifkan dalam bahasa:

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

Hasilnya:

Nombor.MAX_SAFE_INTEGER: 9007199254740991

BigInt

Unit terkecil dalam Ethereum adalah1weiDefinisi1Gweisama dengan1000000000 wei1GweiDalam perhitungan yang berkaitan dengan Ethereum, ia sebenarnya bukan nombor yang besar, dan beberapa data jauh lebih besar daripada itu.Number.MAX_SAFE_INTEGER: 9007199254740991

Di dalam platform pencipta kuantiti, kita menggunakan platform yang mempunyai ciri-ciri yang berbeza.BigIntObjek untuk mewakili data bilangan bulat yang sangat besar ini; menggunakan fungsi pembinaanBigInt()Untuk membinaBigIntObjek. Ia boleh digunakan untuk membina parameter dengan menggunakan nombor, baris nombor 16 digitBigIntObjek ─ kegunaanBigIntobjektoString()Kaedah mengeluarkan data yang diwakili oleh objek dalam bentuk rentetan.

BigIntOperasi yang disokong oleh objek:

  • Pengiraan perkalian:+
  • Pengurangan perhitungan:-
  • Perkalian:*
  • Di samping itu, pengiraan:/
  • Pengiraan bentuk:%
  • Permulaan:**

Lihat contoh kod berikut:

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

Percubaan alat debugging:

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

BigFloatObjek danBigIntPenggunaan objek yang serupa digunakan untuk menunjukkan bilangan titik terapung yang lebih besar, dan juga menyokong operasi penggandaan tambahan dan pengurangan.BigFloatSokongan objektoFixed()Cara-cara.

Lihat contoh kod berikut:

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

Percubaan alat debugging:

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

BigDecimalObjek yang serasi dengan bilangan bulat, nilai titik terapung, menyokong penggunaanBigIntObjek:BigFloatObjek dimulakan, juga menyokong operasi penggandaan tambah pengurangan.

Lihat contoh kod berikut:

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

Perisian penyempurnaan dijalankan di:

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

Pengubahsuaian unit

Fungsi berikut:toAmount()toInnerAmount()Kami telah menggunakan kedua-dua fungsi ini beberapa kali dalam kursus sebelum ini, yang digunakan terutamanya untuk penukaran ketepatan data.

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

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

toAmount()Fungsi ini akan mengubah satu pemboleh ubah.sPerhatikan parameter ketepatan.decimalsMelakukan konversi. Dalam pembangunan web3 yang sebenar, sering kali perlu memproses data 16 digit pada beberapa rantaian. Ini adalah satu perkara yang sering kita lihat dalam kursus sebelum ini, contohnya kontrak pintar.Transfer(address,address,uint256)Dalam kes inidataData medan:

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

Pengolahan data"data": "0x00000000000000000000000000000000000000000000000001c1a55000000000"Pergilah ke sini.toAmount()Fungsi. Reka bentuk pemprosesan seperti ini boleh digunakan dengan baik.dataData medan ditukar kepada nilai yang boleh dibaca.

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
}

Satu token ETH yang kita tahu ialah1e18 weiJika kita dapat satu,weiData dalam unit126564027559051260Bagaimana untuk menukarnya kepada jumlah token ETH? PenggunaantoAmount(, 18)Fungsi boleh ditukar dengan mudah.toInnerAmount()Fungsi ini ialahtoAmount()Operasi kebalik fungsi (mengikut ketepatan, pembesaran), menggunakan kedua-dua fungsi ini adalah mudah untuk menukar data.

Yang perlu diperhatikan ialah julat keselamatan integer dalam bahasa JavaScript, iaituNumber.MAX_SAFE_INTEGERContoh berikut menunjukkan masalah yang lebih tersembunyi semasa penukaran data:

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

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

function main() {
    var amount = 0.01
    var innerAmount = Number(toInnerAmount(amount, 18))       

    Log("Number.MAX_SAFE_INTEGER:", Number.MAX_SAFE_INTEGER)  // 9007199254740991
    Log("innerAmount:", innerAmount)                          // 10000000000000000

    Log("typeof(innerAmount):", typeof(innerAmount), ", innerAmount:", innerAmount)
    
    // 十进制数值 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))
}

Ia boleh dijalankan dalam alat debugging:

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

Di samping itu, kita juga boleh melihat bagaimana ia berlaku.

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

Hasil log yang sepadan dengan baris kod ini ialah:转换 10000000000000000 为十六进制: 10000000000000000Jika kita lihat pada gambar di atas, kita akan melihat bahawa kita tidak dapat mengubahnya dengan betul.Number.MAX_SAFE_INTEGER

Tetapi apabila nilai desimal berada dalam had keselamatan, iaitu kurang daripadaNumber.MAX_SAFE_INTEGERSaya tidak tahu apa yang berlaku.toString(16)Pertukaran fungsi adalah normal, contohnya:

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

Dalam bidang blockchain walaupun0.01Satu ETH ditukar denganweiNilai bagi unit10000000000000000dan akan melampauiNumber.MAX_SAFE_INTEGEROleh itu, pemindahan yang lebih selamat untuk situasi seperti ini adalah:BigInt(10000000000000000).toString(16)

Panggilan analog

Perkhidmatan yang digunakan untuk menjalankan transaksi dan memanggil kontrak pintar di EthereumWriteKaedah memerlukan perbelanjaan gas tertentu dan kadang-kadang berisiko gagal. Mengetahui apa yang mungkin gagal sebelum menghantar transaksi atau memanggil sangat penting. Kaedah panggilan analog digunakan untuk ujian di Ethereum.

eth_call

Kaedah RPC Ethereumeth_call: boleh mensimulasikan transaksi dan mengembalikan hasil transaksi yang mungkin, tetapi tidak benar-benar melaksanakan transaksi di blockchain.

eth_callTerdapat dua parameter untuk kaedah ini, parameter pertama adalah struktur kamus, dan parameter kedua adalah struktur bahasa.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
}

Parameter kedua ialahblockNumberTag boleh dihantar:latest/pending/earliestAntara lain:

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

Kemudian kita akan menggunakan token.DAICara Kontrak PintarapprovetransferContoh panggilan adalah panggilan analog, di mana persekitaran ujian berikut adalah Ethereum.

Panggilan analog approve

Untuk kontrak ERC20approveKaedah ini sudah biasa bagi kita semua, dan telah kita lakukan dalam kursus sebelumnya. Oleh kerana kontrak ERC20 telah dibina di dalam platform FMZ ABI, anda tidak perlu mendaftarkan kontrak pintar ABI yang akan anda panggil untuk diimulasikan.

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

Kod dalam contoh ini akan mulakan denganapprove(address,uint256)Cara, parameter, kod, dan sebagainya.approveNilai parameter kaedah0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffTunjukkan jumlah maksimum kelayakan.0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45Maksudnya,Uniswap V3Kontrak penghalaan. Cara terakhir untuk memanggil Ethereum RPCeth_callSimulasi.transactionObjectDalam parametergasPricegasBidang boleh dihapuskan.

Alat debugging berjalan, panggilan analog untuk meluluskan kaedah keizinan berjaya (tidak benar-benar dibenarkan):

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

Kita juga boleh meniru beberapa senario kegagalan apabila kita menyesuaikangasPricedangasApabila parameter, jika ETH dalam dompet tidak mencukupi untuk membayar gas, ia akan membuat kesalahan:

dana yang tidak mencukupi

Apabila harga gas ditetapkan terlalu rendah, ia akan memberikan kesilapan:

gas intrinsik terlalu rendah: mempunyai 21000, mahu 21944 (disediakan gas 21000)

Panggilan analog untuk transfer

Untuk ERC20transferCara yang tidak kita kenal, ini adalah cara untuk memindahkan token ERC20 ke alamat dompet, dan kita cuba meniru pemindahan 1000 DAI kepada 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 
}

Oleh kerana dompet ujian saya tidak mempunyai token DAI, hasil yang dijangkakan dalam alat debugging adalah salah:

pelaksanaan terbalik: Dai/tidak mencukupi-saldo

Lihat alamat dompet ini:0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045Jadi kita akan menyamakan arah pemindahan yang digunakan untuk panggilan analog, sebagai contoh, V Tuhan memberi kita pemindahan 1000 DAI.

Saya telah membuat nota di mana saya telah mengubah kod:

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

Percubaan alat debugging:

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

Dengan menggunakan platform dagangan kuantifikasi pencipta, hasil dagangan dapat disimulasikan dengan mudah dan mengelakkan kehilangan kos gas yang tidak perlu untuk menghantar dagangan yang mungkin gagal. Kami menggunakan kod contoh dalam kursus bab ini untuk mensimulasikan pemindahan ke dompet V, panggilan pemindahan dompet V kepada kami.eth_callCara yang lebih baik. Gunakan imaginasi anda dan anda akan mendapat lebih banyak.eth_callDi mana kaedah ini digunakan?

Mengenali kontrak ERC721

Kita tahu bahawa token seperti ETH, BTC dan lain-lain adalah token homogen, dan token yang anda pegang tidak berbeza dengan yang saya pegang. Tetapi terdapat banyak perkara di dunia yang tidak berkualiti, seperti harta tanah, barang antik, seni maya, dan lain-lain yang tidak dapat diwakili secara abstrak dengan token homogen. Oleh itu, dengan standard ERC721 untuk mengabstrak objek yang tidak homogen, terdapat NFT dan konsep yang berkaitan. Oleh itu, bagaimana kita mengenali kontrak pintar yang sesuai dengan standard ERC721 daripada banyak kontrak pintar yang digunakan di Ethereum?

Untuk mengenalpasti ERC721, anda perlu memahami standard ERC165.

ERC165

Dengan standard ERC165, kontrak pintar boleh menyatakan antara muka yang disokongnya untuk pemeriksaan kontrak lain. Kontrak antara muka ERC165 hanya mempunyai satu fungsi:supportsInterface(bytes4 interfaceId), parameterinterfaceIdId antara muka yang akan ditanyakan. Jika kontrak melaksanakan ID antara muka itu, nilai Bull adalah benar, jika tidak, nilai palsu.

Di bawah ini, kita akan membincangkannya.interfaceIdDi sini, kita akan membincangkan bagaimana ia boleh dikira dan dikodkan.

Standard ERC165Di sini adalah contohnya:

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

Untuk tanda tangan fungsi antara muka (yang terdiri daripada senarai nama fungsi dan jenis parameter) yang berbeza atau dikira, untuk kontrak antara muka ERC165 yang hanya mempunyai satu fungsi:

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

Pengiktirafan antara muka untuk antara muka ini adalah 0x01ffc9a7. Anda boleh mengira ini dengan menjalankan bytes4 ((keccak256 ((supportsInterface ((bytes4) )); atau menggunakan kontrak Selector di atas.

Jika kita hitung secara langsung tanda tangan fungsi, dan kita ambil 4 byte pertama, kita akan dapatinterfaceId

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

Perisian ini boleh menjalankan ujian:

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

Anda boleh lihat hasil pengiraan danStandard ERC165Perkataan ini sepadan dengan dokumen.

ERC721

Seterusnya kita lihat definisi antara muka dalam piawaian kontrak 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);
}

Jika kita mahu menilai sama ada kontrak pintar adalah kontrak ERC721, kita perlu tahu apa yang berlaku pada kontrak ERC721.interfaceIdKemudian anda boleh cuba menggunakannya.supportsInterface(bytes4 interfaceId)Penghakiman metodologi, dalam kursus sebelumnya kita sudah terbiasa dengan beberapa konsep dan pengiraan standard ERC165interfaceIdIni adalah algoritma yang kita tulis secara langsung dalam kod:

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

digunakan dalam kodEncode()Fungsi untuk mengira tandatangan fungsikeccak256Algoritma), untuk perhitungan dalam contoh kod di atas, tentukanEncode()Parameter output fungsi ialah"raw"Fungsi ini akan mengembalikanJavaScriptBahasaArrayBufferJenis. Jika anda mahu untuk dua.ArrayBufferObjek dijalankan^Permulaan (diferen atau) operasi, perlu berdasarkanArrayBufferObjek diciptaTypedArrayPemandangan, kemudian melalui data di dalamnya, melakukan pengukuran atau pengiraan secara individu.

Perisian penyempurnaan dijalankan di:

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

Anda boleh lihat hasil pengiraan daneip-721Keserasian yang diceritakan di atas.

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

Dengan ID antara muka ERC721, kita boleh menilai sama ada kontrak adalah kontrak mengikut piawaian ERC721.BAYCUntuk melakukan ujian, ini adalah kontrak yang mengikuti ERC721, pertama kita perlu mendaftarkan ABI, kerana kita hanya memanggil tiga kaedah berikut, hanya tiga kaedah ini boleh didaftarkan:

  • menyokong Interface ((interfaceId)
  • lambang ((()
  • nama (()

Kode spesifik adalah sebagai berikut:

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

Perisian ini boleh menjalankan ujian:

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

Menentukan Alamat0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13dKontrak ini adalah standard ERC721.

Dalam perbincangan ini, kami telah membincangkan bagaimana untuk menilai kontrak ERC721, tetapi kontrak seperti ERC20 yang tidak menyokong standard ERC165 perlu dikenal pasti dengan cara yang berbeza. Adakah anda tahu bagaimana untuk memeriksa kontrak untuk standard ERC20?

Kod calldata

Apa itu?calldataMenurut pengarangnya, gambaran sederhana yang popular di sini ialah:

"Calldata" adalah kod panggilan atau parameter untuk fungsi dalam Ethereum, "calldata" dikodkan mengikut spesifikasi ABI ("Application Binary Interface") kontrak.

Sebagai contoh, kita boleh menggunakan kontrak ERC20 yang kita pelajari dalam kursus sebelumnya.balanceOftransferPanggilan kaedah, bersama dengan parameter yang dipanggil, dikodkan sebagaicalldata❖ Dalam beberapa kes, contohnya:Interaksi Antara KontrakIni adalah satu-satunya cara yang boleh digunakan.calldataDi sini, saya akan senaraikan beberapa daripada mereka.

Bagaimana untuk membuat panggilan kontrak pintar untuk mendapatkancalldata

Platform perdagangan kuantitatif di pencipta boleh digunakanexchange.IO("encode", ...)Menggunakan kod untuk panggilan fungsi kontrak pintar adalah sangat mudah.exchange.IO("encode", ...)Parameter pertama fungsi adalah tali tetap."encode"; parameter kedua adalah alamat kontrak pintar; parameter ketiga adalah nama kaedah kontrak pintar yang akan dikodkan; parameter selebihnya dihantar ke nilai parameter khusus kaedah kontrak pintar yang akan dikodkan.

Eth_sendRawTransaction

Apabila kita mengkodkan panggilan untuk kaedah kontrak pintar dan menghasilkancalldataApabila data, jika kaedah kontrak pintar ini adalah kaedah Write (iaitu: menulis operasi), kita perlu untuk menghasilkancalldataData sebagai medan data transaksi dan kemudian menggunakan kaedah RPC Ethereumeth_sendRawTransactionMenghantar permintaan data mentah yang mengandungi transaksi ke rangkaian Ethereum.

eth_sendRawTransactionHanya ada satu parameter untuk kaedah ini.data

data: Transaksi yang ditandatangani (biasanya ditandatangani dengan perpustakaan, menggunakan kunci peribadi anda)

Yang inidataParameter adalah data transaksi yang dihitung selepas tandatangan, dan struktur data transaksi Ethereum terdiri daripada bidang berikut:

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

Bagaimana untuk mendaftar untuk pertukaran Ethereum?

Kami menggunakan platform perdagangan kuantiti pada penciptaEncode()Fungsi untuk melakukan pengiraan tandatangan, contohnya kita tulis dalam kursus seterusnya "Mengeksekusi kaedah menulis untuk memanggil data".

Melakukan kaedah Read calldata

Untuk kaedah ReadcalldataPergerakan ini dilakukan dengan menggunakan kaedah RPC yang telah kita pelajari sebelum ini:eth_callKita akan cuba untuk menghidupkan semula.eth_callRPC Ethereum ini hanya buat kontrak pintar.WriteContoh kaedah yang digunakan dalam bab inicalldataCara menunjukkan panggilan kaedah Read kontrak pintar. Kita akan menggunakan kontrak WETH untukbalanceOfCara membaca baki token WETH di dompet semasa.

Kami menggunakan alat debugging untuk menguji di 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个数为单位
}

Perisian penyempurnaan dijalankan di:

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

Jika kaedah kontrak pintar mempunyai nilai pulangan, ia boleh digunakanexchange.IO("decode", ...)Pengkodean fungsi.calldataDi sini, kita akan melihat bagaimana cara dan cara untuk menghubungi kontrak pintar secara langsungbalanceOfDengan cara yang sama, saya mendapat baki WETH untuk dompet ujian saya iaitu 0.015 WETH.

Melakukan kaedah Write untuk calldata

Untuk melaksanakan calldata untuk kaedah Write, anda perlu menggunakan kaedah RPC:eth_sendRawTransactionPergilah.

Kami menggunakan alat debugging untuk menguji di 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

Lebih lanjut