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"],
Nilaitopics
medan adalah struktur array yang digunakan untuk menerangkan peristiwa. Ia ditentukan bahawa panjangnya (array) tidak boleh melebihi 4 dan elemen pertama adalah tanda tangan hash peristiwa.
Dalam Platform Perdagangan Kuantum FMZ, kita boleh mengira hash tandatangan ini menggunakanEncode
fungsi, menggunakan kod berikut:
function main() {
var eventFunction = "Transfer(address,address,uint256)"
var eventHash = Encode("keccak256", "string", "hex", eventFunction)
Log("eventHash:", "0x" + eventHash)
// eventHash: 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
}
Mengirakeccak256
nilai hash (koding hex)Transfer(address,address,uint256)
adalah0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
.
Nilaitopics
medan adalah struktur array, dengan unsur kedua, dan unsur ketiga, masing-masing:
Alamat penghantaranfrom
Alamat penerimato
data
Data dalamdata
medan adalah:
"data": "0x0000000000000000000000000000000000000000000000000164f2434262e1cc",
Parameter tertentu dalam kes (parameter tanpa pengisytiharan indeks dalam kod Soliditi 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
ialah jumlah pemindahan yang sepadan.
Yang 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 kod ujian yang dijalankan dalam alat pembaikan FMZ:
Data dalamfrom
, to
medan juga boleh dianalisis bergantung kepada keperluan pada masa pengambilan, contohnya:
function main() {
var from = "0x00000000000000000000000012b791bb27b3a4ee958b5a435fea7d49ec076e9c"
var address = "0x" + exchange.IO("encodePacked", "address", from)
Log("address:", address)
}
Hasil berjalan:
Alamat: 0x12b791bb27b3a4ee958b5a435fea7d49ec076e9c
Sejakalat debugginghanya boleh menguji kod untuk masa yang singkat dan mengeluarkan kandungan hanya selepas pelaksanaan kod selesai, ia tidak boleh memaparkan dan mengeluarkan log dalam masa nyata.
Di sini kita menggunakan Ethereum mainnet, dan kita mendengarTransfer(address,address,uint256)
peristiwaUSDT
Berdasarkan apa yang kita pelajari dalam pelajaran terakhir, kita merancang dan menulis contoh mendengar secara berterusan 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, bahagian pengesahan (TODO: ujian) juga ditulis dalam kod.Transfer
peristiwa kontrak USDT sentiasa dipantau dan data direkodkan dan perbandingan antara data ini dan data peristiwa yang diperoleh sekaligus dapat diperhatikan bahawa data itu konsisten dengan:
Berdasarkan pelajaran sebelumnyaListening to contract events
, kita mengembangkannya dengan menambah penapis kepada proses mendengar untuk mendengar pemindahan ke dan dari alamat tertentu.topics
mengandungi sehingga 4 keping maklumat. jadi kita merancang peraturan penapis dengan[[A1, A2, ...An], null, [C1], D]
Sebagai contoh.
[A1, A2, ...An]
sepadan dengan data pada kedudukantopics[0]
.Null
sepadan dengan data pada kedudukantopics[1]
.[C1]
sepadan dengan data pada kedudukantopics[2]
.D
sepadan dengan data pada kedudukantopics[3]
.null
bermakna ia tidak ditapis, contohnyanull
sama dengantopics[1]
dan sebarang kecocokan nilai.[C1]
sama dengantopics[2]
atauD
sama dengantopics[3]
, dan log yang tidak sepadan disaring.[A1, A2, ...An]
sama dengantopics[0]
, [A1, A2, ...An]
dengan mana-mana daripada mereka yang sepadantopics[0]
, maka log tidak akan ditapis.Mendengar pemindahan USDT dari bursa
PemantauanUSDT
transaksi yang dipindahkan dari dan ke Bursa Binance:
function toAmount(s, decimals) {
return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString())
}
function toInnerAmount(n, decimals) {
return (BigDecimal(n) * BigDecimal(Math.pow(10, decimals))).toFixed(0)
}
function addEventListener(contractAddress, event, callBack) {
var self = {}
self.eventHash = "0x" + Encode("keccak256", "string", "hex", event)
self.contractAddress = contractAddress
self.latestBlockNumber = 0
self.fromBlockNumber = 0
self.firstBlockNumber = 0
self.filters = []
self.setFilter = function(filterCondition) {
if (filterCondition.length > 4) {
throw "filterCondition error"
}
self.filters.push(filterCondition)
Log("Set filter conditions:", filterCondition)
}
self.getTokenBalanceOfWallet = function(walletAddress, tokenAddress, tokenDecimals) {
var balance = exchange.IO("api", tokenAddress, "balanceOf", walletAddress)
if (balance) {
return toAmount(balance, tokenDecimals)
}
return null
}
self.getBlockNumber = function() {
var maxTry = 10
for (var i = 0; i < maxTry; i++) {
var ret = exchange.IO("api", "eth", "eth_blockNumber")
if (ret) {
return toAmount(ret, 0)
}
Sleep(5000)
}
throw "getBlockNumber failed"
}
self.run = function() {
var currBlockNumber = self.getBlockNumber()
var fromBlock = "0x" + self.fromBlockNumber.toString(16)
var toBlock = "0x" + currBlockNumber.toString(16)
var params = {
"fromBlock" : fromBlock,
"toBlock" : toBlock,
"address" : self.contractAddress,
"topics" : [self.eventHash]
}
var logs = exchange.IO("api", "eth", "eth_getLogs", params)
if (!logs) {
return
}
for (var i = 0; i < logs.length; i++) {
if (toAmount(logs[i].blockNumber, 0) > self.latestBlockNumber) {
// Check the filter condition, and execute the judgment if the filter condition is set
if (self.filters.length != 0) {
// Initial filter marker
var isFilter = true
// Traverse filter condition setting
for (var j = 0; j < self.filters.length; j++) {
// Take a filter setting, e.g: [[A1, A2, ...An], null, [C1], D]
var cond = self.filters[j]
// Traverse the filter setting
var final = true
for (var topicsIndex = 0; topicsIndex < cond.length; topicsIndex++) {
// Take one of the conditions in the filter setting, if it is the first condition: i.e. the data to be compared with topics[0]
var condValue = cond[topicsIndex]
// Data in the logs
if (topicsIndex > logs[i].topics.length - 1) {
continue
}
var topicsEleValue = logs[i].topics[topicsIndex]
// If it's a Transfer event, you need to handle the from and to
if (logs[i].topics[0] == "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef") {
if (topicsIndex == 1 || topicsIndex == 2) {
topicsEleValue = "0x" + exchange.IO("encodePacked", "address", topicsEleValue)
}
}
// If the condValue type is an array, it means that there are multiple comparison conditions in this position, and the multiple condition comparison is a logical or relationship
if (Array.isArray(condValue) && condValue.length > 1) {
// Determine condValue[0] == topicsEleValue || condValue[1] == topicsEleValue
final = final && condValue.some(element => element === topicsEleValue)
}else if (condValue === null) {
final = final && true
} else {
final = final && (condValue === topicsEleValue)
}
}
if (final) {
isFilter = false
}
}
if (isFilter) {
continue
}
}
callBack(logs[i])
}
}
self.latestBlockNumber = currBlockNumber
self.fromBlockNumber = self.latestBlockNumber - 1
}
self.latestBlockNumber = self.getBlockNumber()
self.fromBlockNumber = self.latestBlockNumber - 1
return self
}
var listener = null
function main() {
// Initial clean-up log
LogReset(1)
LogProfitReset()
var event = "Transfer(address,address,uint256)" // Listening to events
var contractAddress = "0xdac17f958d2ee523a2206206994597c13d831ec7" // USDT contract address
var decimals = exchange.IO("api", contractAddress, "decimals") // Get the precision information of USDT token
var accountBinanceAddress = "0x28C6c06298d514Db089934071355E5743bf21d60" // Binance hot wallet address
accountBinanceAddress = accountBinanceAddress.toLowerCase() // Addresses are handled in lowercase
Log(exchange.IO("api", contractAddress, "name"), " decimals:", decimals)
// Creating a listener object
listener = addEventListener(contractAddress, event, function(log) {
var fromAddress = "0x" + exchange.IO("encodePacked", "address", log.topics[1])
var toAddress = "0x" + exchange.IO("encodePacked", "address", log.topics[2])
if (fromAddress == accountBinanceAddress) {
Log("Binance transfer out - ", " Transfer:", fromAddress, "->", toAddress, ", value:", toAmount(log.data, decimals), ", blockNumber:", toAmount(log.blockNumber, 0), "#CD32CD")
} else if (toAddress == accountBinanceAddress) {
Log("Binance transfer in - ", " Transfer:", fromAddress, "->", toAddress, ", value:", toAmount(log.data, decimals), ", blockNumber:", toAmount(log.blockNumber, 0), "#FF0000")
}
})
// Set up event filtering
listener.setFilter([null, accountBinanceAddress, null]) // Binance -> USDT
listener.setFilter([null, null, accountBinanceAddress]) // USDT -> Binance
var preBalance = 0
while (true) {
listener.run()
var balance = listener.getTokenBalanceOfWallet(accountBinanceAddress, contractAddress, decimals)
if (balance) {
var direction = ""
if (preBalance != 0 && preBalance > balance) {
direction = " ↓ " + (preBalance - balance) + "#CD32CD"
} else if (preBalance != 0 && preBalance < balance) {
direction = " ↑ " + (balance - preBalance) + "#FF0000"
}
Log("Binance wallet address:", accountBinanceAddress, " balance:", balance, direction)
LogProfit(balance, "&") // Drawing only, no log printing
preBalance = balance
}
LogStatus(_D(), "Binance wallet address:", accountBinanceAddress, ", balance:", balance)
Sleep(5000 * 3)
}
}
Kod di atas berjalan dalam perdagangan langsung:
Dalam pelajaran ini, kami memperkenalkan bagaimana untuk mereka bentuk penapis peristiwa. dan menggunakannya untuk mendengarUSDT
Anda boleh mengubah suai dan memperluaskan program contoh ini untuk mendengar apa-apa acara yang anda minat, untuk melihat apa transaksi barusmart money
telah dibuat, apa barangan baru yangNFT
Orang kaya telah bergegas, dan sebagainya.
Banyak pengiraan yang berkaitan dengan Ethereum mempunyai nilai yang melebihi bilangan bulat selamat maksimumJavaScript
Oleh itu, beberapa kaedah diperlukan pada Platform Dagangan Kuantum FMZ untuk mengendalikan nilai yang besar, yang telah kita gunakan secara khusus dalam kursus sebelumnya dan tidak dibahaskan secara terperinci.
Mencetak bilangan bulat maksimum selamat yang ditakrifkan dalamJavaScript
Bahasa:
function main() {
Log("Number.MAX_SAFE_INTEGER:", Number.MAX_SAFE_INTEGER)
}
Hasil berjalan:
Nombor.MAX_SAFE_INTEGER: 9007199254740991
Unit terkecil yang ditakrifkan dalam Ethereum adalah1wei
, dan definisi1Gwei
adalah sama dengan1000000000 wei
. 1Gwei
adalah nombor yang tidak terlalu besar dalam pengiraan berkaitan Ethereum, dan beberapa data lebih besar daripada itu.Number.MAX_SAFE_INTEGER: 9007199254740991
.
Di FMZ Quant Trading Platform, kami menggunakan platformBigInt
objek untuk mewakili data bilangan bulat yang sangat besar ini.BigInt()
untuk membinaBigInt
objek. anda boleh membinaBigInt
objek menggunakan nombor, hexadecimal nombor rentetan sebagai parameter.toString()
kaedahBigInt
objek untuk mengeluarkan data yang diwakili oleh objek sebagai rentetan.
Operasi yang disokong olehBigInt
objek adalah:
+
-
*
/
%
*
Rujuk kepada contoh kod 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))
}
Ujian alat debug:
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 sama sepertiBigInt
objek untuk mewakili nombor koma terapung dengan nilai yang lebih besar, dan ia juga menyokong penambahan, pengurangan, perkalian dan pembahagian.
PeraturanBigFloat
objek menyokongtoFixed()
method.
Rujuk kepada 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))
}
Ujian alat debug:
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
objek adalah serasi dengan nilai bilangan bulat dan nilai koma terapung, dan menyokong inisialisasi denganBigInt
objek danBigFloat
objek, dan ia juga menyokong penambahan, pengurangan, perkalian dan pembahagian.
Rujuk kepada 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())
}
Memandu dalam 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-dua fungsi ini digunakan terutamanya untuk penukaran data ketepatan.
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 menukar (mengurangkan) pembolehubahs
mengikut parameter ketepatandecimals
Dalam pembangunan praktikal web3, ia sering perlu untuk berurusan dengan beberapa data hexadecimal berantai.
Kami sering menemui ini dalam kursus kami sebelumnya, contohnya,data
data lapangan dalamTransfer(address,address,uint256)
kejadian kontrak pintar:
{
"data": "0x00000000000000000000000000000000000000000000000001c1a55000000000",
"topics": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x0000000000000000000000006b75d8af000000e20b7a7ddf000ba900b4009a80", "0x000000000000000000000000bcb095c1f9c3dc02e834976706c87dee5d0f1fb6"],
"transactionHash": "0x27f9bf5abe3148169b4b85a83e1de32bd50eb81ecc52e5af006157d93353e4c4",
"transactionIndex": "0x0",
"removed": false,
"address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
"blockHash": "0x847be24a7b159c292bda030a011dfec89487b70e71eed486969b032d6ef04bad",
"blockNumber": "0x109b1cc",
"logIndex": "0x0"
}
Apabila memproses data"data": "0x00000000000000000000000000000000000000000000000001c1a55000000000"
, kita gunakantoAmount()
Pemprosesan ini direka untuk melakukan kerja yang baik menukar data medan data 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)) // Print out 0.12656402755905127
}
1 token ETH, seperti yang kita tahu, adalah1e18 wei
, jika kita mendapat data126564027559051260
dalamwei
, bagaimana untuk menukarnya kepada token ETH?
MenggunakantoAmount(, 18)
fungsi adalah kaedah penukaran yang sangat mudah.toInnerAmount()
fungsi adalah operasi terbaliktoAmount()
fungsi (bergantung pada ketepatan, memperbesar), dan mudah untuk menukar data menggunakan kedua-dua fungsi ini.
Adalah penting untuk diperhatikan julat keselamatan nilai bilangan bulat dalam bahasa JavaScript,Number.MAX_SAFE_INTEGER
, dan contoh berikut menggambarkan masalah tersembunyi semasa menukar 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))
}
Ia adalah mungkin untuk menjalankan dalam 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 pemerhatian kami mendapati bahawa:
Log("Convert", innerAmount, "to hexadecimal:", innerAmount.toString(16))
Baris kod ini sepadan dengan output log:Converting 10000000000000000 to hex: 10000000000000000
, yang tidak ditukar dengan betul. Sebabnya adalah semula jadi bahawa 1000000000000000000 adalah di luarNumber.MAX_SAFE_INTEGER
.
Tetapi apabila nilai perpuluhan adalah dalam julat yang selamat, iaitu, kurang daripadaNumber.MAX_SAFE_INTEGER
, yangtoString(16)
fungsi menukarnya dengan betul lagi, contohnya:
function main() {
var value = 1000
Log("Convert value to hexadecimal:", "0x" + value.toString(16)) // 0x3e8
Log("Convert 0x3e8 to decimal:", Number("0x3e8")) // 1000
}
Dalam blockchain, walaupun0.01
ETH ditukar kepada nilai10000000000000000
dalamwei
akan melebihiNumber.MAX_SAFE_INTEGER``, so a safer conversion for such cases is:
BigInt ((10000000000000000).toString ((16) ``.
Melakukan transaksi dan memanggilWrite
kaedah kontrak pintar pada Ethereum kos sejumlah gas dan kadang-kadang ia gagal. Adalah penting untuk mengetahui transaksi yang mungkin gagal sebelum menghantar dan memanggil mereka.
Kaedah RPC Ethereumeth_call
: ia boleh mensimulasikan transaksi dan mengembalikan hasil transaksi yang mungkin, tetapi ia tidak benar-benar melaksanakan transaksi di blockchain.
Peraturaneth_call
kaedah mempunyai 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 ialahblockNumber
: anda boleh lulus labellatest/pending/earliest
, dan sebagainya:
/* 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
*/
Seterusnya, kita mengambil kaedah kontrak pintarapprove
dantransfer
panggilan tokenDAI
sebagai contoh untuk panggilan simulasi, dan persekitaran ujian berikut adalah rangkaian utama Ethereum.
Kita semua sudah biasa denganapprove
Oleh kerana kontrak ERC20 sudah dibina ke dalam platform FMZ ABI, tidak perlu mendaftar ABI kontrak pintar untuk 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)
}
Kod dalam contoh pertama mengkodkanapprove(address,uint256)
kaedah dan parameter, dan nilai parameter0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
daripadaapprove
Perintah diberikan kepada kontrak pintar di alamat0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45
iaitu kontrak penghala untukUniswap V3
. Akhirnya kaedah Ethereum RPCeth_call
anda boleh melihat bahawagasPrice
dangas
ladang ditransactionObject
parameter boleh dihilangkan.
Alat debugging dijalankan dan simulasi memanggil kaedah meluluskan untuk membenarkan dengan berjaya (ia tidak benar-benar membenarkan):
2023-06-09 11:58:39 Info ret: 0x0000000000000000000000000000000000000000000000000000000000000001
2023-06-09 11:58:39 Info ERC20 token DAI approve encode, data: 095ea7b300000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc45ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
Ia juga mungkin untuk mensimulasikan beberapa senario kegagalan, apabila kita menyesuaikangasPrice
dangas
parameter, jika ETH dalam dompet tidak mencukupi untuk membayar yuran gas, satu ralat akan dilaporkan::
dana yang tidak mencukupi
Apabila kos gas ditetapkan terlalu rendah, satu ralat akan dilaporkan:
gas intrinsik terlalu rendah: mempunyai 21000, mahu 21944 (disediakan gas 21000)
Kami biasa dengan ERC20transfer
kaedah, yang membolehkan anda memindahkan token ERC20 ke alamat dompet tertentu, jadi mari kita cuba mensimulasikan pemindahan 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
}
Oleh kerana saya tidak mempunyai token DAI dalam dompet ujian ini, menjalankan ia dalam alat debug melaporkan ralat berikut secara tidak dijangka:
pelaksanaan terbalik: Dai/tidak mencukupi-saldo
Periksa alamat dompet Vitalik Buterin:0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
, jelas bahawa dompet ini mempunyai token DAI. jadi mari kita menyesuaikan arah pemindahan panggilan simulasi dan mensimulasikan pemindahan 1000 DAI dari Vitalik Buterin kepada kita.
Mengubah kod, di mana perubahan yang saya buat komen:
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)
}
Ujian alat penyempurnaan:
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 Platform Perdagangan Kuantum FMZ, mudah untuk mensimulasikan hasil transaksi dan mengelakkan kehilangan yuran gas yang tidak perlu daripada menghantar transaksi yang berpotensi gagal. Kami menggunakan kod contoh dari bab ini kursus untuk mensimulasikan panggilan untuk memindahkan wang ke dompet Vitalik Buterineth_call
Gunakan imaginasi anda, apa yang anda akan menggunakaneth_call
Kaedah untuk?
Kami tahu bahawa token seperti ETH dan BTC adalah token homogen, dan token di dompet anda tidak berbeza dengan token di dompet saya. Tetapi terdapat banyak perkara di dunia yang tidak homogen, seperti hartanah, barang antik, karya seni maya, dll. Ini tidak dapat diwakili oleh token homogen dalam abstraksi. Oleh itu, ada standard ERC721 untuk abstrak objek yang tidak homogen, dan ada NFT dan konsep yang berkaitan. Jadi di antara banyak kontrak pintar yang digunakan di Ethereum, bagaimana kita mengenal pasti kontrak pintar mana yang merupakan kontrak pintar standard ERC721?
Untuk mengenal pasti ERC721, penting untuk mengetahui standard ERC165 terlebih dahulu.
Dengan standard ERC165, kontrak pintar boleh mengisytiharkan antara muka yang disokongnya untuk kontrak lain untuk diperiksa.supportsInterface(bytes4 interfaceId)
, parameterinterfaceId
adalah ID antara muka yang akan ditanyakan. Jika kontrak melaksanakan ID antara muka mengembalikan nilai benar boolean, jika tidak, ia mengembalikan nilai palsu.
Di sini kita akan bercakap tentang bagaimana iniinterfaceId
dikira dan dikodkan secara khusus.
Standard 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 tandatangan fungsi antara muka (yang terdiri daripada nama fungsi dan senarai jenis parameter) untuk melakukan operasi tidak serupa, untuk kontrak antara muka ERC165 di mana kontrak 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.
Mengira tandatangan fungsi secara langsung dan mengambil 4 bait pertama untuk sampai keinterfaceId
.
function main() {
var ret = Encode("keccak256", "string", "hex", "supportsInterface(bytes4)")
Log("supportsInterface(bytes4) interfaceId:", "0x" + ret.slice(0, 8))
}
Ujian boleh dijalankan dalam alat debug di:
2023-06-13 14:53:35 Info supportsInterface(bytes4) interfaceId: 0x01ffc9a7
Ia dapat dilihat bahawa hasil yang dikira adalah konsisten dengan penerangan dalamStandard ERC165 document.
Seterusnya mari kita lihat definisi antara muka standard 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 menentukan sama ada kontrak pintar adalah kontrak ERC721, pertama kita perlu tahuinterfaceId
daripada kontrak ERC721 sebelum kita boleh cuba untuk menggunakansupportsInterface(bytes4 interfaceId)
Dalam kursus sebelumnya kita telah membiasakan diri dengan beberapa konsep standard ERC165 dan algoritma untuk mengirainterfaceId
, dan kita menulis kod untuk mengira secara 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)
}
Kod ini menggunakanEncode()
fungsi untuk pengiraan tandatangan fungsi (yangkeccak256
algoritma), dan untuk pengiraan dalam contoh kod di atas, menentukan parameter outputEncode()
fungsi sebagai"raw"
, fungsi mengembalikanArrayBuffer
jenisJavaScript
Bahasa.
Untuk melaksanakan^
(iso-atau) operasi pada duaArrayBuffer
objek, anda perlu membuatTypedArray
pandangan berdasarkanArrayBuffer
objek, kemudian mengulangi melalui data di dalamnya dan menjalankan operasi iso-atau satu demi satu.
Jalankan dalam alat debugging:
2023-06-13 15:04:09 Info 0x80ac58cd
Ia dapat dilihat bahawa hasil yang dikira adalah konsisten dengan yang diterangkan 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 antara muka ERC721, kita boleh menentukan sama ada kontrak adalah kontrak standard ERC721 atau tidak.BAYC
Untuk melakukan ujian, yang merupakan kontrak yang mengikuti ERC721. pertama kita perlu mendaftarkan ABI, dan kerana kita hanya memanggil tiga kaedah berikut, kita boleh mendaftarkan tiga kaedah ini:
Kod khusus adalah seperti 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)
}
Ujian boleh dijalankan dalam 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