Le numéro d'enregistrement est le numéro d'enregistrement suivant:
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"],
La valeur de latopics
Le champ est une structure de tableau utilisée pour décrire l'événement. Il est spécifié que sa longueur (d'un tableau) ne peut pas dépasser 4 et que le premier élément est le hachage de signature de l'événement.
Dans la plateforme de trading FMZ Quant, nous pouvons calculer ce hachage de signature en utilisantEncode
fonction, en utilisant le code suivant:
function main() {
var eventFunction = "Transfer(address,address,uint256)"
var eventHash = Encode("keccak256", "string", "hex", eventFunction)
Log("eventHash:", "0x" + eventHash)
// eventHash: 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
}
Calculer lekeccak256
valeur de hachage (codage hexagonale) deTransfer(address,address,uint256)
est0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
.
La valeur de latopics
le champ est une structure de tableau, avec le deuxième élément et le troisième élément, respectivement:
Adresse d'envoifrom
Adresse de réceptionto
data
Les donnéesdata
les champs sont:
"data": "0x0000000000000000000000000000000000000000000000000164f2434262e1cc",
Certains paramètres (paramètres sans déclarations indexées dans le code de solidité du contrat intelligent) sont stockés dans ledata
section.
Analyser les données0x0000000000000000000000000000000000000000000000000164f2434262e1cc
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
}
Ces données sont obtenues sous la forme de 0,10047146239950075 et ledata
est le montant de transfert correspondant.
Ce qui précède a été expliqué, pratiqué et prêt à l'emploi.
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++
}
}
}
Vérifiez.https://etherscan.io/
:
Résultats du code de test exécuté dans l'outil de débogage FMZ:
Les donnéesfrom
, to
Les champs peuvent également être analysés en fonction des besoins au moment de la récupération, par exemple:
function main() {
var from = "0x00000000000000000000000012b791bb27b3a4ee958b5a435fea7d49ec076e9c"
var address = "0x" + exchange.IO("encodePacked", "address", from)
Log("address:", address)
}
Résultats de l'exécution:
Adresse: 0x12b791bb27b3a4ee958b5a435fea7d49ec076e9c
Depuis leoutil de débogageDans cette section, nous utilisons la plateforme de trading FMZ Quant pour créer des transactions en direct pour tester.
Ici, nous utilisons le mainnet Ethereum, et nous écoutonsTransfer(address,address,uint256)
événement de laUSDT
Basé sur ce que nous avons appris dans la dernière leçon, nous avons conçu et écrit un exemple d'écoute continue des événements d'un certain contrat intelligent:
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])
}
}
*/
En cours de négociation:
Pour les résultats d'exécution, une section de validation (TODO: test) est également écrite dans le code.Transfer
l'événement du contrat USDT est constamment surveillé et les données enregistrées et une comparaison entre ces données et les données d'événement obtenues à la fois permet d'observer que les données sont compatibles avec:
Basé sur la leçon précédenteListening to contract events
, nous l'élargissons en ajoutant des filtres au processus d'écoute pour écouter les transferts vers et depuis des adresses spécifiées.topics
Nous avons donc conçu une règle de filtre avec[[A1, A2, ...An], null, [C1], D]
comme exemple.
[A1, A2, ...An]
correspond aux données à la positiontopics[0]
.Null
correspond aux données à la positiontopics[1]
.[C1]
correspond aux données à la positiontopics[2]
.D
correspond aux données à la positiontopics[3]
.null
signifie qu'il n'est pas filtré, par exemplenull
correspond àtopics[1]
et toute correspondance de valeur.[C1]
correspond àtopics[2]
ouD
correspond àtopics[3]
, et les journaux non correspondants sont filtrés.[A1, A2, ...An]
correspond àtopics[0]
, [A1, A2, ...An]
avec n'importe lequel d'entre eux correspondanttopics[0]
, alors les journaux ne seront pas filtrés.Écouter les virements en USDT des bourses
Surveillance de laUSDT
les transactions transférées depuis et vers la Bourse 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)
}
}
Le code ci-dessus en cours de négociation en direct:
Dans cette leçon, nous avons présenté comment concevoir un filtre d'événement.USDT
Vous pouvez modifier et étendre ce programme d'exemple pour écouter tout événement qui vous intéresse, pour voir quelles nouvelles transactionssmart money
Il y a eu des changements.NFT
Les magnats se sont précipités, etc.
Beaucoup de calculs liés à Ethereum ont des valeurs qui dépassent l'entier sûr maximum de laJavaScript
Par conséquent, certaines méthodes sont nécessaires sur la plateforme de trading FMZ Quant pour gérer les grandes valeurs, que nous avons utilisées spécifiquement dans les cours précédents et que nous n'avons pas couvertes en détail.
Imprimez l'entier le plus sûr défini dans leJavaScript
langue:
function main() {
Log("Number.MAX_SAFE_INTEGER:", Number.MAX_SAFE_INTEGER)
}
Résultats de l'exécution:
Numéro.MAX_SAFE_INTEGER: 9007199254740991
La plus petite unité définie dans Ethereum est1wei
, et la définition1Gwei
est égal à1000000000 wei
. 1Gwei
n'est pas vraiment un très grand nombre dans les calculs liés à Ethereum, et certaines données sont beaucoup plus grandes que cela.Number.MAX_SAFE_INTEGER: 9007199254740991
.
Chez FMZ Quant Trading Platform, nous utilisons les plateformesBigInt
l'objet pour représenter ces données entières très grandes. Utiliser le constructeurBigInt()
pour construire leBigInt
Vous pouvez construireBigInt
les objets utilisant des chaînes numériques hexadécimales comme paramètres.toString()
méthode deBigInt
Objet pour produire les données représentées par l'objet sous forme de chaîne.
Les opérations soutenues par leBigInt
l'objet sont:
+
-
*
/
%
*
Voir les exemples de codes suivants:
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))
}
Test de l' outil de débogage:
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
LeBigFloat
l'objet est utilisé de façon similaire à laBigInt
objet pour représenter des nombres à virgule flottante avec des valeurs plus grandes, et il prend également en charge l'addition, la soustraction, la multiplication et la division.
LeBigFloat
l'objet soutient letoFixed()
method.
Voir l'exemple de code suivant:
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))
}
Test de l' outil de débogage:
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
LeBigDecimal
Objet est compatible avec les valeurs entières et les valeurs en virgule flottante, et prend en charge l'initialisation avec leBigInt
l'objet et leBigFloat
objet, et il prend également en charge l'addition, la soustraction, la multiplication et la division.
Voir l'exemple de code suivant:
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())
}
Exécuter dans l'outil de débogage:
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
Les deux fonctions suivantes:toAmount()
, toInnerAmount()
Nous avons utilisé plusieurs fois dans les cours précédents, ces deux fonctions sont principalement utilisées pour la conversion de données de précision.
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)
}
LetoAmount()
fonction convertit (réduit) une variables
selon le paramètre de précisiondecimals
Dans le développement pratique de web3, il est souvent nécessaire de traiter avec des données hexadécimales enchaînées.
Nous avons souvent rencontré cela dans nos cours précédents, par exemple, ledata
données de terrain dans leTransfer(address,address,uint256)
événement de contrat intelligent:
{
"data": "0x00000000000000000000000000000000000000000000000001c1a55000000000",
"topics": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x0000000000000000000000006b75d8af000000e20b7a7ddf000ba900b4009a80", "0x000000000000000000000000bcb095c1f9c3dc02e834976706c87dee5d0f1fb6"],
"transactionHash": "0x27f9bf5abe3148169b4b85a83e1de32bd50eb81ecc52e5af006157d93353e4c4",
"transactionIndex": "0x0",
"removed": false,
"address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
"blockHash": "0x847be24a7b159c292bda030a011dfec89487b70e71eed486969b032d6ef04bad",
"blockNumber": "0x109b1cc",
"logIndex": "0x0"
}
Lors du traitement des données"data": "0x00000000000000000000000000000000000000000000000001c1a55000000000"
, nous utilisons letoAmount()
Ce traitement est conçu pour faire un bon travail de conversion des données de champ de données en valeurs lisibles.
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 jeton ETH, comme nous le savons, est1e18 wei
, si nous obtenons des données126564027559051260
danswei
, comment le convertir en jetons ETH?
En utilisant letoAmount(, 18)
La fonction est une méthode de conversion trèstoInnerAmount()
fonction est l'opération inverse de latoAmount()
Les données sont faciles à convertir à l'aide de ces deux fonctions.
Il est important de noter la plage de sécurité des valeurs entières dans le langage JavaScript,Number.MAX_SAFE_INTEGER
, et l'exemple suivant illustre un problème caché lors de la conversion des données:
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))
}
Il est possible d'exécuter dans l'outil de débogage:
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
Par l'observation, nous avons constaté que:
Log("Convert", innerAmount, "to hexadecimal:", innerAmount.toString(16))
Cette ligne de code correspond à la sortie log:Converting 10000000000000000 to hex: 10000000000000000
La raison est naturellement que 10000000000000000 est au-delàNumber.MAX_SAFE_INTEGER
.
Mais lorsque la valeur décimale est dans la plage de sécurité, c'est-à-dire inférieure àNumber.MAX_SAFE_INTEGER
, letoString(16)
fonction le convertit correctement à nouveau, par exemple:
function main() {
var value = 1000
Log("Convert value to hexadecimal:", "0x" + value.toString(16)) // 0x3e8
Log("Convert 0x3e8 to decimal:", Number("0x3e8")) // 1000
}
Dans la blockchain, même0.01
ETH converti en une valeur de10000000000000000
danswei
dépasseraNumber.MAX_SAFE_INTEGER``, so a safer conversion for such cases is:
Je suis désolée pour vous.
L'exécution des opérations et l'appel desWrite
Il est important de savoir quelles transactions sont susceptibles d'échouer avant de les envoyer et de les appeler.
Méthode RPC d'Ethereumeth_call
: il peut simuler une transaction et retourner le résultat d'une transaction possible, mais il n'exécute pas réellement la transaction sur la blockchain.
Leeth_call
La méthode comporte 2 paramètres, le premier étant une structure de dictionnaire,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
}
Le deuxième paramètre estblockNumber
: vous pouvez passer l'étiquettelatest/pending/earliest
, etc.:
/* 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
*/
Ensuite, nous prenons la méthode du contrat intelligentapprove
ettransfer
les appels du jetonDAI
comme exemple pour les appels de simulation, et l'environnement de test suivant est le réseau Ethereum principal.
Nous sommes tous familiers avec leapprove
La simulation de l'interface utilisateur est basée sur une méthode de détection des contrats ERC20, et nous l'avons pratiquée dans les cours précédents.
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)
}
Le code de l'exemple encode d'abord leapprove(address,uint256)
méthode et paramètres, ainsi que la valeur du paramètre0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
de l'annéeapprove
La méthode indique le nombre maximal d'autorisations.0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45
C'est à dire le contrat de routeur pourUniswap V3
Enfin la méthode Ethereum RPCeth_call
Vous pouvez voir que legasPrice
etgas
champs dans letransactionObject
les paramètres peuvent être omis.
L'outil de débogage est exécuté et la simulation appelle la méthode approuver pour autoriser avec succès (elle n'autorise pas réellement):
2023-06-09 11:58:39 Info ret: 0x0000000000000000000000000000000000000000000000000000000000000001
2023-06-09 11:58:39 Info ERC20 token DAI approve encode, data: 095ea7b300000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc45ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
Il est également possible de simuler certains scénarios d'échec, lorsque nous ajustons legasPrice
etgas
paramètres, si l'ETH dans le portefeuille n'est pas suffisant pour payer les frais d'essence, une erreur sera signalée:
insuffisance des fonds
Lorsque le coût de l'essence est trop bas, une erreur est signalée:
gaz intrinsèque trop faible: avoir 21000, vouloir 21944 (gaz fourni 21000)
Nous connaissons les ERC20transfer
Cette méthode vous permet de transférer des jetons ERC20 vers une certaine adresse de portefeuille, alors essayons de simuler un transfert de 1000 DAI vers 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
}
Comme je n'ai pas de jetons DAI dans ce portefeuille de test, en l'exécutant dans l'outil de débogage, l'erreur suivante a été signalée de façon inattendue:
l'exécution est inversée: Dai/solde insuffisant
Vérifiez l'adresse du portefeuille de Vitalik Buterin:0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
Il est clair que ce portefeuille a des jetons DAI.
Modifiez le code, où les modifications que j'ai fait commentaires:
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)
}
Testez l' outil de débogage:
2023-06-09 13:34:31 Info 0x0000000000000000000000000000000000000000000000000000000000000001
2023-06-09 13:34:31 Info Transfer amount: 1000 DAI, use toInnerAmount convert to: 1000000000000000000000
En utilisant la plateforme de trading FMZ Quant, il est facile de simuler les résultats des transactions et d'éviter une perte inutile de frais de gaz liés à l'envoi de transactions potentiellement échouées.eth_call
Utilisez votre imagination.eth_call
Quelle est la méthode?
Nous savons que les jetons comme ETH et BTC sont des jetons homogénéisés, et le jeton de votre portefeuille n'est pas différent du jeton de mon portefeuille. Mais il y a beaucoup de choses dans le monde qui ne sont pas homogènes, comme l'immobilier, les antiquités, les œuvres d'art virtuelles, etc. Ceux-ci ne peuvent pas être représentés par des jetons homogènes en abstraction. Par conséquent, il existe la norme ERC721 pour abstraire les objets non homogènes, et il existe NFT et des concepts connexes. Alors, parmi les nombreux contrats intelligents déployés sur Ethereum, comment identifier quels contrats intelligents sont des contrats intelligents standard ERC721?
Pour identifier ERC721, il est important de connaître d'abord la norme ERC165.
Avec la norme ERC165, un contrat intelligent peut déclarer les interfaces qu'il prend en charge pour que d'autres contrats puissent être vérifiés.supportsInterface(bytes4 interfaceId)
, le paramètreinterfaceId
est l'ID d'interface à interroger. Si le contrat implémente l'interfaceId renvoie une valeur vraie booléenne, sinon il renvoie une valeur fausse.
Ici, nous allons parler de la façon dont ceinterfaceId
est calculé et codé spécifiquement.
Règlement ERC165Voici un exemple:
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;
}
}
Pour la signature de fonction de l'interface (composée d'un nom de fonction et d'une liste de types de paramètres) pour effectuer une opération de dissemblance, pour un contrat d'interface ERC165 dont le contrat n'a qu'une seule fonction:
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);
}
L'identifiant d'interface pour cette interface est 0x01ffc9a7. Vous pouvez le calculer en exécutant bytes4 ((keccak256 ((
supportsInterface ((bytes4) )); ou en utilisant le contrat Sélecteur ci-dessus.
Calculer la signature de la fonction directement et prendre ses 4 premiers octets pour arriver àinterfaceId
.
function main() {
var ret = Encode("keccak256", "string", "hex", "supportsInterface(bytes4)")
Log("supportsInterface(bytes4) interfaceId:", "0x" + ret.slice(0, 8))
}
Les tests peuvent être exécutés dans l'outil de débogage à:
2023-06-13 14:53:35 Info supportsInterface(bytes4) interfaceId: 0x01ffc9a7
On constate que les résultats calculés sont conformes à la description dans leRèglement ERC165 document.
Considérons ensuite la définition de l'interface de la norme 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);
}
Si nous voulons déterminer si un contrat intelligent est un contrat ERC721, nous devons d'abord connaîtreinterfaceId
Nous ne pouvons pas nous contenter d'un accord avec ERC721 avant d'essayer desupportsInterface(bytes4 interfaceId)
Dans les cours précédents, nous nous sommes familiarisés avec certains concepts de la norme ERC165 et avec l'algorithme de calcul de la valeur de l'ERC165.interfaceId
, et nous écrivons un code pour calculer directement:
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)
}
Le code utilise leEncode()
fonction pour le calcul de la signature de la fonction (lekeccak256
Pour le calcul dans l'exemple de code ci-dessus, en spécifiant le paramètre de sortie duEncode()
fonction comme"raw"
, la fonction renvoie leArrayBuffer
type deJavaScript
Le langage.
Pour effectuer une^
(iso-ou) opération sur deuxArrayBuffer
Les objets, vous devez créer unTypedArray
Le rapport de l'AgenceArrayBuffer
Objet, puis itérer à travers les données et effectuer l'iso-ou l'opération un par un.
Exécutez dans l'outil de débogage:
2023-06-13 15:04:09 Info 0x80ac58cd
On constate que les résultats calculés sont cohérents avec ceux décritsEIP-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);
...
Avec l'ID d'interface ERC721, nous pouvons déterminer si un contrat est un contrat standard ERC721 ou non.BAYC
Pour faire le test, qui est un contrat qui suit ERC721. d'abord, nous devons enregistrer l'ABI, et puisque nous appelons seulement les trois méthodes suivantes, nous pouvons enregistrer ces trois méthodes:
Les codes spécifiques sont les suivants:
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)
}
Les tests peuvent être exécutés dans l'outil de débogage:
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