0000000000164f2434262e1cc",
We can see that there are various events in the logs data, if we only care about ```Transfer``` events, we need to filter out the ```Transfer``` events in these data.
### Retrieving Logs
The Ethereum log is divided into two parts: 1. ```topics```; 2. ```data```.
- ```topics```
Taking the results of the code run for the ```eth_getLogs``` section test as an example, the data in the ```topics``` field is:
```desc
"topics": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x00000000000000000000000012b791bb27b3a4ee958b5a435fea7d49ec076e9c", "0x000000000000000000000000ef1c6e67703c7bd7107eed8303fbe6ec2554bf6b"],
Nilai daritopics
field adalah struktur array yang digunakan untuk menggambarkan peristiwa. Hal ini ditentukan bahwa panjangnya (array) tidak dapat melebihi 4 dan elemen pertama adalah hash tanda tangan dari peristiwa.
Dalam FMZ Quant Trading Platform, kita dapat menghitung hash tanda tangan ini menggunakanEncode
fungsi, menggunakan kode berikut:
function main() {
var eventFunction = "Transfer(address,address,uint256)"
var eventHash = Encode("keccak256", "string", "hex", eventFunction)
Log("eventHash:", "0x" + eventHash)
// eventHash: 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
}
Menghitungkeccak256
nilai hash (kode hex) dariTransfer(address,address,uint256)
adalah0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
.
Nilai daritopics
bidang adalah struktur array, dengan elemen kedua, dan elemen ketiga, masing-masing:
Alamat pengirimanfrom
Alamat penerimato
data
Data dalamdata
bidang adalah:
"data": "0x0000000000000000000000000000000000000000000000000164f2434262e1cc",
Parameter tertentu dalam hal (parameter tanpa deklarasi terindeks dalam kode Soliditas kontrak pintar) disimpan dalamdata
section.
Menganalisis data0x0000000000000000000000000000000000000000000000000164f2434262e1cc
function toAmount(s, decimals) {
return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString())
}
function main() {
var value = "0x0000000000000000000000000000000000000000000000000164f2434262e1cc"
Log(toAmount(value, 0) / 1e18) // 0.10047146239950075
}
Data ini diperoleh sebagai 0,10047146239950075 dandata
adalah jumlah transfer yang sesuai.
Hal di atas telah dijelaskan, dilatih dan siap untuk pergi.
function toAmount(s, decimals) {
return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString())
}
function toInnerAmount(n, decimals) {
return (BigDecimal(n) * BigDecimal(Math.pow(10, decimals))).toFixed(0)
}
function main() {
// getBlockNumber
var blockNumber = exchange.IO("api", "eth", "eth_blockNumber")
Log("blockNumber:", blockNumber)
// get logs
var fromBlock = "0x" + (toAmount(blockNumber, 0) - 1).toString(16)
var toBlock = "0x" + toAmount(blockNumber, 0).toString(16)
var params = {
"fromBlock" : fromBlock,
"toBlock" : toBlock,
"address" : "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
}
var logs = exchange.IO("api", "eth", "eth_getLogs", params)
// Traverse logs
var eventFunction = "Transfer(address,address,uint256)"
var eventHash = "0x" + Encode("keccak256", "string", "hex", eventFunction)
Log("eventHash:", eventHash)
var counter = 0
for (var i = logs.length - 1; i >= 0 && counter < 10; i--) {
if (logs[i].topics[0] == eventHash) {
Log("Event Transfer, data:", toAmount(logs[i].data, 0) / 1e18, ", blockNumber:", toAmount(logs[i].blockNumber, 0), ", transactionHash:", logs[i].transactionHash,
", log:", logs[i])
counter++
}
}
}
Periksa.https://etherscan.io/
:
Hasil dari kode uji yang dijalankan dalam alat debugging FMZ:
Data difrom
, to
bidang juga dapat dianalisis tergantung pada kebutuhan pada saat pengambilan, misalnya:
function main() {
var from = "0x00000000000000000000000012b791bb27b3a4ee958b5a435fea7d49ec076e9c"
var address = "0x" + exchange.IO("encodePacked", "address", from)
Log("address:", address)
}
Hasil pelaksanaan:
Alamat: 0x12b791bb27b3a4ee958b5a435fea7d49ec076e9c
Sejakalat debuggingDalam bagian ini, kami menggunakan FMZ Quant Trading Platform untuk membuat perdagangan langsung untuk menguji.
Di sini kita menggunakan Ethereum mainnet, dan kita mendengarkanTransfer(address,address,uint256)
peristiwaUSDT
Berdasarkan apa yang kita pelajari dalam pelajaran terakhir, kami merancang dan menulis contoh dari terus mendengarkan peristiwa kontrak pintar tertentu:
function toAmount(s, decimals) {
return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString())
}
function toInnerAmount(n, decimals) {
return (BigDecimal(n) * BigDecimal(Math.pow(10, decimals))).toFixed(0)
}
function addEventListener(contractAddress, event, callBack) {
var self = {}
self.eventHash = "0x" + Encode("keccak256", "string", "hex", event)
self.contractAddress = contractAddress
self.latestBlockNumber = 0
self.fromBlockNumber = 0
self.firstBlockNumber = 0
/* TODO: test
self.isFirst = true
*/
self.getBlockNumber = function() {
var maxTry = 10
for (var i = 0; i < maxTry; i++) {
var ret = exchange.IO("api", "eth", "eth_blockNumber")
if (ret) {
return toAmount(ret, 0)
}
Sleep(5000)
}
throw "getBlockNumber failed"
}
self.run = function() {
var currBlockNumber = self.getBlockNumber()
var fromBlock = "0x" + self.fromBlockNumber.toString(16)
var toBlock = "0x" + currBlockNumber.toString(16)
var params = {
"fromBlock" : fromBlock,
"toBlock" : toBlock,
"address" : self.contractAddress,
"topics" : [self.eventHash]
}
// Log("fromBlockNumber:", self.fromBlockNumber, ", currBlockNumber:", currBlockNumber, "#FF0000")
var logs = exchange.IO("api", "eth", "eth_getLogs", params)
if (!logs) {
return
}
for (var i = 0; i < logs.length; i++) {
if (toAmount(logs[i].blockNumber, 0) > self.latestBlockNumber) {
/* TODO: test
if (self.isFirst) {
self.firstBlockNumber = toAmount(logs[i].blockNumber, 0)
Log("firstBlockNumber:", self.firstBlockNumber)
self.isFirst = false
}
*/
callBack(logs[i])
}
}
self.latestBlockNumber = currBlockNumber
self.fromBlockNumber = self.latestBlockNumber - 1
}
self.latestBlockNumber = self.getBlockNumber()
self.fromBlockNumber = self.latestBlockNumber - 1
return self
}
var listener = null
function main() {
var event = "Transfer(address,address,uint256)"
var contractAddress = "0xdac17f958d2ee523a2206206994597c13d831ec7"
var decimals = exchange.IO("api", contractAddress, "decimals")
Log(exchange.IO("api", contractAddress, "name"), " decimals:", decimals)
listener = addEventListener(contractAddress, event, function(log) {
var fromAddress = "0x" + exchange.IO("encodePacked", "address", log.topics[1])
var toAddress = "0x" + exchange.IO("encodePacked", "address", log.topics[2])
Log("Transfer:", fromAddress, "->", toAddress, ", value:", toAmount(log.data, decimals), ", blockNumber:", toAmount(log.blockNumber, 0))
/* TODO: test
arrLog.push(log)
*/
})
while (true) {
listener.run()
Sleep(5000)
}
}
/* TODO: test
var arrLog = []
function onexit() {
Log("End the run and verify the record")
var firstBlockNumber = listener.firstBlockNumber
var endBlockNumber = listener.latestBlockNumber
Log("getLogs, from:", firstBlockNumber, " -> to:", endBlockNumber)
var fromBlock = "0x" + (firstBlockNumber).toString(16)
var toBlock = "0x" + (endBlockNumber).toString(16)
var params = {
"fromBlock" : fromBlock,
"toBlock" : toBlock,
"topics" : ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"],
"address" : "0xdac17f958d2ee523a2206206994597c13d831ec7"
}
var logs = exchange.IO("api", "eth", "eth_getLogs", params)
Log("arrLog:", arrLog.length)
Log("logs:", logs.length)
if (arrLog.length != logs.length) {
Log("Length varies!")
return
}
for (var i = 0; i < arrLog.length; i++) {
Log("Determine the blockNumber:", logs[i].blockNumber == arrLog[i].blockNumber, ", Determine from:", logs[i].topics[1] == arrLog[i].topics[1],
"Determine to:", logs[i].topics[2] == arrLog[i].topics[2])
}
}
*/
Berjalan pada perdagangan langsung:
Untuk hasil pelaksanaan, bagian validasi (TODO: test) juga ditulis dalam kode.Transfer
peristiwa kontrak USDT terus dipantau dan data dicatat dan perbandingan antara data ini dan data peristiwa yang diperoleh sekaligus dapat diamati bahwa data tersebut konsisten dengan:
Berdasarkan pelajaran sebelumnyaListening to contract events
, kita memperluasnya dengan menambahkan filter untuk proses mendengarkan untuk mendengarkan transfer ke dan dari alamat tertentu.topics
Jadi kita merancang aturan filter dengan[[A1, A2, ...An], null, [C1], D]
sebagai contoh.
[A1, A2, ...An]
sesuai dengan data pada posisitopics[0]
.Null
sesuai dengan data pada posisitopics[1]
.[C1]
sesuai dengan data pada posisitopics[2]
.D
sesuai dengan data pada posisitopics[3]
.null
berarti tidak disaring, misalnyanull
sesuai dengantopics[1]
dan setiap nilai yang cocok.[C1]
sesuai dengantopics[2]
atauD
sesuai dengantopics[3]
, dan log yang tidak cocok disaring.[A1, A2, ...An]
sesuai dengantopics[0]
, [A1, A2, ...An]
dengan salah satu dari mereka cocoktopics[0]
, maka log tidak akan disaring.Mendengarkan transfer USDT dari bursa
PemantauanUSDT
transaksi yang ditransfer dari dan ke Binance Exchange:
function toAmount(s, decimals) {
return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString())
}
function toInnerAmount(n, decimals) {
return (BigDecimal(n) * BigDecimal(Math.pow(10, decimals))).toFixed(0)
}
function addEventListener(contractAddress, event, callBack) {
var self = {}
self.eventHash = "0x" + Encode("keccak256", "string", "hex", event)
self.contractAddress = contractAddress
self.latestBlockNumber = 0
self.fromBlockNumber = 0
self.firstBlockNumber = 0
self.filters = []
self.setFilter = function(filterCondition) {
if (filterCondition.length > 4) {
throw "filterCondition error"
}
self.filters.push(filterCondition)
Log("Set filter conditions:", filterCondition)
}
self.getTokenBalanceOfWallet = function(walletAddress, tokenAddress, tokenDecimals) {
var balance = exchange.IO("api", tokenAddress, "balanceOf", walletAddress)
if (balance) {
return toAmount(balance, tokenDecimals)
}
return null
}
self.getBlockNumber = function() {
var maxTry = 10
for (var i = 0; i < maxTry; i++) {
var ret = exchange.IO("api", "eth", "eth_blockNumber")
if (ret) {
return toAmount(ret, 0)
}
Sleep(5000)
}
throw "getBlockNumber failed"
}
self.run = function() {
var currBlockNumber = self.getBlockNumber()
var fromBlock = "0x" + self.fromBlockNumber.toString(16)
var toBlock = "0x" + currBlockNumber.toString(16)
var params = {
"fromBlock" : fromBlock,
"toBlock" : toBlock,
"address" : self.contractAddress,
"topics" : [self.eventHash]
}
var logs = exchange.IO("api", "eth", "eth_getLogs", params)
if (!logs) {
return
}
for (var i = 0; i < logs.length; i++) {
if (toAmount(logs[i].blockNumber, 0) > self.latestBlockNumber) {
// Check the filter condition, and execute the judgment if the filter condition is set
if (self.filters.length != 0) {
// Initial filter marker
var isFilter = true
// Traverse filter condition setting
for (var j = 0; j < self.filters.length; j++) {
// Take a filter setting, e.g: [[A1, A2, ...An], null, [C1], D]
var cond = self.filters[j]
// Traverse the filter setting
var final = true
for (var topicsIndex = 0; topicsIndex < cond.length; topicsIndex++) {
// Take one of the conditions in the filter setting, if it is the first condition: i.e. the data to be compared with topics[0]
var condValue = cond[topicsIndex]
// Data in the logs
if (topicsIndex > logs[i].topics.length - 1) {
continue
}
var topicsEleValue = logs[i].topics[topicsIndex]
// If it's a Transfer event, you need to handle the from and to
if (logs[i].topics[0] == "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef") {
if (topicsIndex == 1 || topicsIndex == 2) {
topicsEleValue = "0x" + exchange.IO("encodePacked", "address", topicsEleValue)
}
}
// If the condValue type is an array, it means that there are multiple comparison conditions in this position, and the multiple condition comparison is a logical or relationship
if (Array.isArray(condValue) && condValue.length > 1) {
// Determine condValue[0] == topicsEleValue || condValue[1] == topicsEleValue
final = final && condValue.some(element => element === topicsEleValue)
}else if (condValue === null) {
final = final && true
} else {
final = final && (condValue === topicsEleValue)
}
}
if (final) {
isFilter = false
}
}
if (isFilter) {
continue
}
}
callBack(logs[i])
}
}
self.latestBlockNumber = currBlockNumber
self.fromBlockNumber = self.latestBlockNumber - 1
}
self.latestBlockNumber = self.getBlockNumber()
self.fromBlockNumber = self.latestBlockNumber - 1
return self
}
var listener = null
function main() {
// Initial clean-up log
LogReset(1)
LogProfitReset()
var event = "Transfer(address,address,uint256)" // Listening to events
var contractAddress = "0xdac17f958d2ee523a2206206994597c13d831ec7" // USDT contract address
var decimals = exchange.IO("api", contractAddress, "decimals") // Get the precision information of USDT token
var accountBinanceAddress = "0x28C6c06298d514Db089934071355E5743bf21d60" // Binance hot wallet address
accountBinanceAddress = accountBinanceAddress.toLowerCase() // Addresses are handled in lowercase
Log(exchange.IO("api", contractAddress, "name"), " decimals:", decimals)
// Creating a listener object
listener = addEventListener(contractAddress, event, function(log) {
var fromAddress = "0x" + exchange.IO("encodePacked", "address", log.topics[1])
var toAddress = "0x" + exchange.IO("encodePacked", "address", log.topics[2])
if (fromAddress == accountBinanceAddress) {
Log("Binance transfer out - ", " Transfer:", fromAddress, "->", toAddress, ", value:", toAmount(log.data, decimals), ", blockNumber:", toAmount(log.blockNumber, 0), "#CD32CD")
} else if (toAddress == accountBinanceAddress) {
Log("Binance transfer in - ", " Transfer:", fromAddress, "->", toAddress, ", value:", toAmount(log.data, decimals), ", blockNumber:", toAmount(log.blockNumber, 0), "#FF0000")
}
})
// Set up event filtering
listener.setFilter([null, accountBinanceAddress, null]) // Binance -> USDT
listener.setFilter([null, null, accountBinanceAddress]) // USDT -> Binance
var preBalance = 0
while (true) {
listener.run()
var balance = listener.getTokenBalanceOfWallet(accountBinanceAddress, contractAddress, decimals)
if (balance) {
var direction = ""
if (preBalance != 0 && preBalance > balance) {
direction = " ↓ " + (preBalance - balance) + "#CD32CD"
} else if (preBalance != 0 && preBalance < balance) {
direction = " ↑ " + (balance - preBalance) + "#FF0000"
}
Log("Binance wallet address:", accountBinanceAddress, " balance:", balance, direction)
LogProfit(balance, "&") // Drawing only, no log printing
preBalance = balance
}
LogStatus(_D(), "Binance wallet address:", accountBinanceAddress, ", balance:", balance)
Sleep(5000 * 3)
}
}
Kode di atas yang berjalan dalam perdagangan langsung:
Dalam pelajaran ini, kami memperkenalkan cara merancang filter acara.USDT
Anda dapat memodifikasi dan memperluas contoh program ini untuk mendengarkan setiap acara yang Anda minati, untuk melihat apa transaksi barusmart money
telah dibuat, apa item baru yangNFT
Orang-orang besar telah bergegas, dll.
Banyak perhitungan yang terkait dengan Ethereum memiliki nilai yang melebihi bilangan bulat aman maksimum dariJavaScript
Oleh karena itu, beberapa metode diperlukan pada FMZ Quant Trading Platform untuk menangani nilai besar, yang telah kita gunakan secara khusus dalam kursus sebelumnya dan tidak dibahas secara rinci.
Mencetak bilangan bulat aman maksimum yang didefinisikan dalamJavaScript
Bahasa:
function main() {
Log("Number.MAX_SAFE_INTEGER:", Number.MAX_SAFE_INTEGER)
}
Hasil pelaksanaan:
Nomor.MAX_SAFE_INTEGER: 9007199254740991
Satuan terkecil yang didefinisikan dalam Ethereum adalah1wei
, dan definisi1Gwei
sama dengan1000000000 wei
. 1Gwei
Jadi data ini dengan nilai yang sangat besar dapat dengan mudah melebihiNumber.MAX_SAFE_INTEGER: 9007199254740991
.
Di FMZ Quant Trading Platform, kami menggunakan platformBigInt
objek untuk mewakili data bilangan bulat yang sangat besar ini.BigInt()
untuk membangunBigInt
Anda dapat membangunBigInt
objek menggunakan angka, hexadecimal string angka sebagai parameter.toString()
metodeBigInt
objek untuk output data yang diwakili oleh objek sebagai string.
Operasi yang didukung olehBigInt
objeknya adalah:
+
-
*
/
%
*
Lihat contoh kode berikut:
function main() {
// Decimal representation of 1Gwei
var oneGwei = 1000000000
// Decimal to hexadecimal conversion of 1Gwei
var oneGweiForHex = "0x" + oneGwei.toString(16)
Log("oneGwei : ", oneGwei)
Log("oneGweiForHex : ", oneGweiForHex)
// Constructing BigInt objects
Log("1Gwei / 1Gwei : ", (BigInt(oneGwei) / BigInt(oneGweiForHex)).toString(10))
Log("1Gwei * 1Gwei : ", (BigInt(oneGwei) * BigInt(oneGweiForHex)).toString(10))
Log("1Gwei - 1Gwei : ", (BigInt(oneGwei) - BigInt(oneGweiForHex)).toString(10))
Log("1Gwei + 1Gwei : ", (BigInt(oneGwei) + BigInt(oneGweiForHex)).toString(10))
Log("(1Gwei + 1) % 1Gwei : ", (BigInt(oneGwei + 1) % BigInt(oneGweiForHex)).toString(10))
Log("1Gwei ** 2 : ", (BigInt(oneGwei) ** BigInt(2)).toString(10))
Log("The square root of 100 : ", (BigInt(100) ** BigFloat(0.5)).toString(10))
Log("Number.MAX_SAFE_INTEGER : ", BigInt(Number.MAX_SAFE_INTEGER).toString(10))
Log("Number.MAX_SAFE_INTEGER * 2 : ", (BigInt(Number.MAX_SAFE_INTEGER) * BigInt("2")).toString(10))
}
Pengujian alat debugging:
2023-06-08 11:39:50 Info Number.MAX_SAFE_INTEGER * 2 : 18014398509481982
2023-06-08 11:39:50 Info Number.MAX_SAFE_INTEGER : 9007199254740991
2023-06-08 11:39:50 Info The square root of 100 : 10
2023-06-08 11:39:50 Info 1Gwei ** 2 : 1000000000000000000
2023-06-08 11:39:50 Info (1Gwei + 1) % 1Gwei : 1
2023-06-08 11:39:50 Info 1Gwei + 1Gwei : 2000000000
2023-06-08 11:39:50 Info 1Gwei - 1Gwei : 0
2023-06-08 11:39:50 Info 1Gwei * 1Gwei : 1000000000000000000
2023-06-08 11:39:50 Info 1Gwei / 1Gwei : 1
2023-06-08 11:39:50 Info oneGweiForHex : 0x3b9aca00
2023-06-08 11:39:50 Info oneGwei : 1000000000
PeraturanBigFloat
objek digunakan dengan cara yang sama denganBigInt
objek untuk mewakili bilangan koma mengambang dengan nilai yang lebih besar, dan juga mendukung penjumlahan, pengurangan, perkalian dan pembagian.
PeraturanBigFloat
objek mendukungtoFixed()
method.
Lihat contoh kode 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))
}
Pengujian alat debugging:
2023-06-08 13:56:44 Info pi / 2.0 : 1.57
2023-06-08 13:56:44 Info pi * 2.0 : 6.28
2023-06-08 13:56:44 Info pi - oneGweiForHex : -999999996.86
2023-06-08 13:56:44 Info pi + oneGwei : 1000000003.14
PeraturanBigDecimal
object kompatibel dengan nilai integer dan nilai floating point, dan mendukung inisialisasi denganBigInt
objek danBigFloat
objek, dan juga mendukung penjumlahan, pengurangan, perkalian dan pembagian.
Lihat contoh kode 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())
}
Menjalankan alat debugging:
2023-06-08 14:52:53 Info pi / pi : 1
2023-06-08 14:52:53 Info 2.0 * pi : 6.283
2023-06-08 14:52:53 Info oneGwei - pi : 999999996.8585
2023-06-08 14:52:53 Info oneGwei + pi : 1000000003.1415
2023-06-08 14:52:53 Info BigFloat(pi) : 3.1415
2023-06-08 14:52:53 Info BigInt(oneGwei) : 1e+9
2023-06-08 14:52:53 Info oneGweiForHex : 1e+9
2023-06-08 14:52:53 Info oneGwei : 1e+9
2023-06-08 14:52:53 Info pi : 3.14
Dua fungsi berikut:toAmount()
, toInnerAmount()
kita telah digunakan berkali-kali dalam kursus sebelumnya, kedua fungsi ini terutama digunakan untuk konversi presisi 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)
}
PeraturantoAmount()
fungsi mengkonversi (mengurangi) variabels
Menurut parameter presisidecimals
Dalam pengembangan praktis web3, seringkali perlu untuk berurusan dengan beberapa data hexadecimal berantai.
Kami sering menemukan ini dalam kursus kami sebelumnya, misalnya,data
data lapangan diTransfer(address,address,uint256)
peristiwa kontrak pintar:
{
"data": "0x00000000000000000000000000000000000000000000000001c1a55000000000",
"topics": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x0000000000000000000000006b75d8af000000e20b7a7ddf000ba900b4009a80", "0x000000000000000000000000bcb095c1f9c3dc02e834976706c87dee5d0f1fb6"],
"transactionHash": "0x27f9bf5abe3148169b4b85a83e1de32bd50eb81ecc52e5af006157d93353e4c4",
"transactionIndex": "0x0",
"removed": false,
"address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
"blockHash": "0x847be24a7b159c292bda030a011dfec89487b70e71eed486969b032d6ef04bad",
"blockNumber": "0x109b1cc",
"logIndex": "0x0"
}
Saat memproses data"data": "0x00000000000000000000000000000000000000000000000001c1a55000000000"
, kita menggunakantoAmount()
Pemrosesan ini dirancang untuk melakukan pekerjaan yang baik dari konversi data data lapangan ke nilai yang dapat 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)) // Print out 0.12656402755905127
}
1 token ETH, seperti yang kita tahu, adalah1e18 wei
, jika kita mendapatkan data126564027559051260
dalamwei
, bagaimana mengubahnya menjadi token ETH?
MenggunakantoAmount(, 18)
fungsi adalah metode konversi yang sangat sederhana.toInnerAmount()
fungsi adalah operasi terbalik daritoAmount()
fungsi (tergantung pada presisi, memperbesar), dan mudah untuk mengkonversi data menggunakan kedua fungsi ini.
Penting untuk dicatat rentang keamanan nilai bilangan bulat dalam bahasa JavaScript,Number.MAX_SAFE_INTEGER
, dan contoh berikut menggambarkan masalah tersembunyi ketika mengubah data:
function toAmount(s, decimals) {
return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString())
}
function toInnerAmount(n, decimals) {
return (BigDecimal(n) * BigDecimal(Math.pow(10, decimals))).toFixed(0)
}
function main() {
var amount = 0.01
var innerAmount = Number(toInnerAmount(amount, 18))
Log("Number.MAX_SAFE_INTEGER:", Number.MAX_SAFE_INTEGER) // 9007199254740991
Log("innerAmount:", innerAmount) // 10000000000000000
Log("typeof(innerAmount):", typeof(innerAmount), ", innerAmount:", innerAmount)
// Decimal value 10000000000000000 -> Hexadecimal value 0x2386f26fc10000
Log("Convert", innerAmount, "to hexadecimal:", innerAmount.toString(16))
Log("Convert", BigInt(10000000000000000).toString(10), "to hexadecimal:", BigInt(10000000000000000).toString(16))
Log("0x" + BigInt(10000000000000000).toString(16), "Convert to decimal:", toAmount("0x" + BigInt(10000000000000000).toString(16), 0))
}
Hal ini mungkin untuk menjalankan di alat debugging:
2023-06-15 16:21:40 Info Convert 0x2386f26fc10000 to decimal: 10000000000000000
2023-06-15 16:21:40 Info Convert 10000000000000000 to hexadecimal: 2386f26fc10000
2023-06-15 16:21:40 Info Convert 10000000000000000 to hexadecimal: 10000000000000000
2023-06-15 16:21:40 Info typeof(innerAmount): number , innerAmount: 10000000000000000
2023-06-15 16:21:40 Info innerAmount: 10000000000000000
2023-06-15 16:21:40 Info Number.MAX_SAFE_INTEGER: 9007199254740991
Melalui pengamatan kami menemukan bahwa:
Log("Convert", innerAmount, "to hexadecimal:", innerAmount.toString(16))
Baris kode ini sesuai dengan log output:Converting 10000000000000000 to hex: 10000000000000000
, yang tidak dikonversi dengan benar. alasannya tentu saja bahwa 10000000000000000 adalah di luarNumber.MAX_SAFE_INTEGER
.
Tapi ketika nilai desimal berada dalam kisaran aman, yaitu, kurang dariNumber.MAX_SAFE_INTEGER
, yangtoString(16)
fungsi mengubahnya dengan benar lagi, misalnya:
function main() {
var value = 1000
Log("Convert value to hexadecimal:", "0x" + value.toString(16)) // 0x3e8
Log("Convert 0x3e8 to decimal:", Number("0x3e8")) // 1000
}
Di blockchain, bahkan0.01
ETH dikonversi menjadi nilai10000000000000000
dalamwei
akan melebihiNumber.MAX_SAFE_INTEGER``, so a safer conversion for such cases is:
BigInt ((10000000000000000).toString ((16) ``.
Melakukan transaksi dan memanggilWrite
metode kontrak pintar pada Ethereum biaya sejumlah gas dan kadang-kadang gagal. Penting untuk mengetahui transaksi mana yang mungkin gagal sebelum mengirim dan memanggil mereka. Ada panggilan simulasi pada Ethereum untuk pengujian.
Metode RPC Ethereumeth_call
: dapat mensimulasikan transaksi dan mengembalikan hasil dari kemungkinan transaksi, tetapi tidak benar-benar mengeksekusi transaksi di blockchain.
Peraturaneth_call
metode memiliki 2 parameter, yang pertama adalah struktur kamus,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 adalahblockNumber
: Anda bisa melewati labellatest/pending/earliest
, dll:
/* 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
*/
Selanjutnya, kita mengambil metode kontrak pintarapprove
dantransfer
panggilan tokenDAI
sebagai contoh untuk panggilan simulasi, dan lingkungan pengujian berikut adalah jaringan Ethereum utama.
Kita semua akrab denganapprove
metode untuk kontrak ERC20, dan kami telah mempraktikkannya dalam kursus sebelumnya. karena kontrak ERC20 sudah dibangun ke dalam platform FMZ ABI, tidak perlu mendaftarkan ABI dari kontrak pintar yang akan dipanggil oleh simulasi.
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)
}
Kode dalam contoh pertama mengkodeapprove(address,uint256)
metode dan parameter, dan nilai parameter0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
dariapprove
metode menunjukkan jumlah maksimum otorisasi. otorisasi diberikan kepada kontrak pintar di alamat0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45
Artinya kontrak router untukUniswap V3
Akhirnya metode Ethereum RPCeth_call
Anda dapat melihat bahwagasPrice
dangas
bidang ditransactionObject
parameter dapat dihilangkan.
Alat debugging dijalankan dan simulasi memanggil metode approve untuk mengotorisasi dengan sukses (itu tidak benar-benar mengotorisasi):
2023-06-09 11:58:39 Info ret: 0x0000000000000000000000000000000000000000000000000000000000000001
2023-06-09 11:58:39 Info ERC20 token DAI approve encode, data: 095ea7b300000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc45ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
Hal ini juga mungkin untuk mensimulasikan beberapa skenario kegagalan, ketika kita menyesuaikangasPrice
dangas
parameter, jika ETH di dompet tidak cukup untuk membayar biaya gas, kesalahan akan dilaporkan::
dana yang tidak cukup
Ketika biaya gas diatur terlalu rendah, kesalahan akan dilaporkan:
gas intrinsik terlalu rendah: memiliki 21000, ingin 21944 (disuplai gas 21000)
Kita sudah terbiasa dengan ERC20transfer
metode, yang memungkinkan Anda untuk mentransfer token ERC20 ke alamat dompet tertentu, jadi mari kita coba mensimulasikan transfer 1000 DAI ke Vitalik Buterin.
function toInnerAmount(n, decimals) {
return (BigDecimal(n) * BigDecimal(Math.pow(10, decimals))).toFixed(0)
}
function main() {
var walletVitalik = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
var contractAddress_DAI = "0x6b175474e89094c44da98b954eedeac495271d0f"
var wallet = exchange.IO("address")
// transfer to Vitalik Buterin
var decimals_DAI = exchange.IO("api", contractAddress_DAI, "decimals")
var transferAmount = toInnerAmount(1000, decimals_DAI)
Log("Transfer amount:", 1000, "DAI, use toInnerAmount convert to:", transferAmount)
// encode transfer
var data = exchange.IO("encode", contractAddress_DAI, "transfer(address,uint256)",
walletVitalik, transferAmount)
var transactionObject = {
"from" : wallet,
"to" : contractAddress_DAI,
"data" : "0x" + data,
}
var blockNumber = "latest"
var ret = exchange.IO("api", "eth", "eth_call", transactionObject, blockNumber)
return ret
}
Karena saya tidak memiliki token DAI di dompet uji ini, menjalankan di alat debug melaporkan kesalahan berikut secara tak terduga:
Eksekusi terbalik: Dai/saldos tidak cukup
Periksa alamat dompet Vitalik Buterin:0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
, jelas bahwa dompet ini memiliki token DAI. jadi mari
Mengubah kode, di mana perubahan yang saya buat komentar:
function toInnerAmount(n, decimals) {
return (BigDecimal(n) * BigDecimal(Math.pow(10, decimals))).toFixed(0)
}
function main() {
var walletVitalik = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
var contractAddress_DAI = "0x6b175474e89094c44da98b954eedeac495271d0f"
var wallet = exchange.IO("address")
var decimals_DAI = exchange.IO("api", contractAddress_DAI, "decimals")
var transferAmount = toInnerAmount(1000, decimals_DAI)
Log("Transfer amount:", 1000, "DAI, use toInnerAmount convert to:", transferAmount)
// encode transfer
var data = exchange.IO("encode", contractAddress_DAI, "transfer(address,uint256)",
wallet, transferAmount) // Use the wallet variable as a parameter and change the transfer recipient's address to my own
var transactionObject = {
"from" : walletVitalik, // Use the walletVitalik variable as the value of the from field to simulate that the call was made from the Vitalik Buterin's wallet address
"to" : contractAddress_DAI,
"data" : "0x" + data,
}
var blockNumber = "latest"
var ret = exchange.IO("api", "eth", "eth_call", transactionObject, blockNumber)
Log(ret)
}
Tes alat debugging:
2023-06-09 13:34:31 Info 0x0000000000000000000000000000000000000000000000000000000000000001
2023-06-09 13:34:31 Info Transfer amount: 1000 DAI, use toInnerAmount convert to: 1000000000000000000000
Dengan menggunakan FMZ Quant Trading Platform, mudah untuk mensimulasikan hasil transaksi dan menghindari kehilangan biaya gas yang tidak perlu dari mengirim transaksi yang berpotensi gagal. Kami menggunakan contoh kode dari bab ini dari kursus untuk mensimulasikan panggilan untuk mentransfer uang ke dompet Vitalik Buterineth_call
Gunakan imajinasi Anda, apa yang akan Anda gunakaneth_call
Metode untuk?
Kami tahu bahwa token seperti ETH dan BTC adalah token homogenisasi, dan token di dompet Anda tidak berbeda dari token di dompet saya. Tapi ada banyak hal di dunia yang tidak homogen, seperti real estat, barang antik, karya seni virtual, dll. Ini tidak dapat diwakili oleh token homogen dalam abstraksi. Oleh karena itu, ada standar ERC721 untuk abstrak objek yang tidak homogen, dan ada NFT dan konsep terkait. Jadi di antara banyak kontrak pintar yang digunakan di Ethereum, bagaimana kita mengidentifikasi kontrak pintar mana yang merupakan kontrak pintar standar ERC721?
Untuk mengidentifikasi ERC721, penting untuk mengetahui standar ERC165 terlebih dahulu.
Dengan standar ERC165, kontrak pintar dapat menyatakan antarmuka yang didukungnya untuk kontrak lain untuk diperiksa.supportsInterface(bytes4 interfaceId)
, parameterinterfaceId
adalah ID antarmuka yang akan ditanyakan. Jika kontrak mengimplementasikan ID antarmuka mengembalikan nilai benar boolean, jika tidak mengembalikan nilai palsu.
Di sini kita akan berbicara tentang bagaimana iniinterfaceId
Dihitung dan dikodekan secara khusus.
Standar ERC165menunjukkan contoh:
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 antarmuka (terdiri dari nama fungsi dan daftar jenis parameter) untuk melakukan operasi ketidaksamaan, untuk kontrak antarmuka ERC165 di mana kontrak hanya memiliki 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);
}
ID antarmuka untuk antarmuka ini adalah 0x01ffc9a7. Anda dapat menghitung ini dengan menjalankan bytes4 ((keccak256 ((
supportsInterface ((bytes4) )); atau menggunakan kontrak Selector di atas.
Menghitung tanda tangan fungsi secara langsung dan mengambil pertama 4 byte untuk mencapaiinterfaceId
.
function main() {
var ret = Encode("keccak256", "string", "hex", "supportsInterface(bytes4)")
Log("supportsInterface(bytes4) interfaceId:", "0x" + ret.slice(0, 8))
}
Tes dapat dijalankan di alat debug di:
2023-06-13 14:53:35 Info supportsInterface(bytes4) interfaceId: 0x01ffc9a7
Hal ini dapat dilihat bahwa hasil yang dihitung konsisten dengan deskripsi dalamStandar ERC165 document.
Selanjutnya mari kita lihat definisi antarmuka dari standar 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 ingin menentukan apakah kontrak pintar adalah kontrak ERC721, pertama-tama kita perlu tahuinterfaceId
dari kontrak ERC721 sebelum kita bisa mencoba untuk menggunakansupportsInterface(bytes4 interfaceId)
Dalam kursus sebelumnya kita telah membiasakan diri dengan beberapa konsep dari standar ERC165 dan algoritma untuk menghitunginterfaceId
, dan kita menulis kode untuk menghitung langsung:
function calcSelector(arrSelector) {
var ret = null
if (Array.isArray(arrSelector)) {
if (arrSelector.length == 1) {
ret = Encode("keccak256", "string", "hex", arrSelector[0])
} else if (arrSelector.length == 0) {
throw "Error: the number of elements in the array is 0"
} else {
var viewEncodeData = null
for (var i = 0; i < arrSelector.length; i++) {
if (i == 0) {
ret = new Uint8Array(Encode("keccak256", "string", "raw", arrSelector[i]))
} else {
viewData = new Uint8Array(Encode("keccak256", "string", "raw", arrSelector[i]))
if (viewData.length != ret.length) {
throw "Error: TypeArray view length is different"
}
for (var index = 0; index < ret.length; index++) {
ret[index] ^= viewData[index]
}
}
}
ret = Encode("raw", "raw", "hex", ret.buffer)
}
} else {
throw "Error: The parameter requires an array type."
}
return "0x" + ret.slice(0, 8)
}
function main() {
// supportsInterface(bytes4): 0x01ffc9a7
// var ret = calcSelector(["supportsInterface(bytes4)"])
// ERC721Metadata: 0x5b5e139f
/*
var arrSelector = [
"name()",
"symbol()",
"tokenURI(uint256)"
]
var ret = calcSelector(arrSelector)
*/
// ERC721: 0x80ac58cd
// /*
var arrSelector = [
"balanceOf(address)",
"ownerOf(uint256)",
"safeTransferFrom(address,address,uint256,bytes)",
"safeTransferFrom(address,address,uint256)",
"transferFrom(address,address,uint256)",
"approve(address,uint256)",
"setApprovalForAll(address,bool)",
"getApproved(uint256)",
"isApprovedForAll(address,address)",
]
var ret = calcSelector(arrSelector)
// */
Log(ret)
}
Kode ini menggunakanEncode()
fungsi untuk perhitungan tanda tangan fungsi (thekeccak256
algoritma), dan untuk perhitungan dalam contoh kode di atas, menentukan output parameter dariEncode()
fungsi sebagai"raw"
, fungsi mengembalikanArrayBuffer
jenisJavaScript
bahasa.
Untuk melakukan^
(iso-atau) operasi pada duaArrayBuffer
objek, Anda perlu untuk membuatTypedArray
pandangan berdasarkanArrayBuffer
objek, kemudian mengulangi melalui data di dalamnya dan melakukan iso-atau operasi satu per satu.
Jalankan di alat debugging:
2023-06-13 15:04:09 Info 0x80ac58cd
Hal ini dapat dilihat bahwa hasil yang dihitung konsisten dengan yang dijelaskan dalameip-721.
pragma solidity ^0.4.20;
/// @title ERC-721 Non-Fungible Token Standard/// @dev See https://eips.ethereum.org/EIPS/eip-721/// Note: the ERC-165 identifier for this interface is 0x80ac58cd.interface ERC721 /* is ERC165 */ {
/// @dev This emits when ownership of any NFT changes by any mechanism.
/// This event emits when NFTs are created (`from` == 0) and destroyed
/// (`to` == 0). Exception: during contract creation, any number of NFTs
/// may be created and assigned without emitting Transfer. At the time of
/// any transfer, the approved address for that NFT (if any) is reset to none.
event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);
...
Dengan ID antarmuka ERC721, kita dapat menentukan apakah kontrak adalah kontrak standar ERC721 atau tidak.BAYC
Untuk melakukan tes, yang merupakan kontrak yang mengikuti ERC721. pertama kita perlu mendaftarkan ABI, dan karena kita hanya memanggil tiga metode berikut, kita dapat mendaftarkan tiga metode ini:
Kode spesifiknya adalah sebagai berikut:
function main() {
// Contract address for ERC721, BAYC is used here
var testContractAddress = "0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d"
var testABI = `[{
"inputs": [{
"internalType": "bytes4",
"name": "interfaceId",
"type": "bytes4"
}],
"name": "supportsInterface",
"outputs": [{
"internalType": "bool",
"name": "",
"type": "bool"
}],
"stateMutability": "view",
"type": "function"
}, {
"inputs": [],
"name": "symbol",
"outputs": [{
"internalType": "string",
"name": "",
"type": "string"
}],
"stateMutability": "view",
"type": "function"
}, {
"inputs": [],
"name": "name",
"outputs": [{
"internalType": "string",
"name": "",
"type": "string"
}],
"stateMutability": "view",
"type": "function"
}]`
// ERC721 Interface Id, calculated in the previous course
var interfaceId = "0x80ac58cd"
// Register ABI
exchange.IO("abi", testContractAddress, testABI)
// Call the supportsInterface method
var isErc721 = exchange.IO("api", testContractAddress, "supportsInterface", interfaceId)
// Output Information
Log("Contract address:", testContractAddress)
Log("Contract name:", exchange.IO("api", testContractAddress, "name"))
Log("Contract code:", exchange.IO("api", testContractAddress, "symbol"))
Log("Whether the contract is ERC721 standard:", isErc721)
}
Tes dapat dijalankan di alat debugging:
2023-06-13 16:32:57 Info Whether the contract is ERC721 standard: true
2023-06-13 16:32:57 Info Contract code: BAYC
2023-06-13 16:32:57 Info