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"],
Der Wert dertopics
Feld ist eine Arraystruktur, die zur Beschreibung des Ereignisses verwendet wird. Es ist festgelegt, dass seine (Array-) Länge 4 nicht überschreiten darf und das erste Element der Signatur-Hash des Ereignisses ist.
In der FMZ Quant Trading Plattform können wir diesen Signatur-Hash mit derEncode
Funktion mit dem folgenden Code:
function main() {
var eventFunction = "Transfer(address,address,uint256)"
var eventHash = Encode("keccak256", "string", "hex", eventFunction)
Log("eventHash:", "0x" + eventHash)
// eventHash: 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
}
Berechnen Sie diekeccak256
Hash-Wert (Hex-Codierung) vonTransfer(address,address,uint256)
ist0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
.
Der Wert dertopics
Feld ist eine Arraystruktur mit dem zweiten Element und dem dritten Element:
Versandadressefrom
Empfangsadresseto
data
Die Daten indata
Feld sind:
"data": "0x0000000000000000000000000000000000000000000000000164f2434262e1cc",
Bestimmte Parameter im Falle (Parameter ohne indexierte Erklärung im Soliditätscode des Smart Contracts) werden in derdata
section.
Analyse der Daten0x0000000000000000000000000000000000000000000000000164f2434262e1cc
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
}
Diese Daten werden als 0,10047146239950075 und diedata
ist der entsprechende Transferbetrag.
Wir haben alles erklärt, geübt und sind bereit, die Protokolle abzurufen.
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++
}
}
}
Überprüfen Sie.https://etherscan.io/
:
Ergebnisse des im FMZ-Debugging-Tool ausgeführten Testcodes:
Daten in derfrom
, to
Feld können auch je nach Bedarf zum Zeitpunkt des Abrufs analysiert werden, z. B.:
function main() {
var from = "0x00000000000000000000000012b791bb27b3a4ee958b5a435fea7d49ec076e9c"
var address = "0x" + exchange.IO("encodePacked", "address", from)
Log("address:", address)
}
Laufende Ergebnisse:
Adresse: 0x12b791bb27b3a4ee958b5a435fea7d49ec076e9c
Seit demDebugging-ToolIn diesem Abschnitt verwenden wir die FMZ Quant Trading Plattform, um Live-Trading zu erstellen, um zu testen.
Hier verwenden wir das Ethereum Mainnet, und wir hören dieTransfer(address,address,uint256)
Veranstaltung desUSDT
Basierend auf dem, was wir in der letzten Lektion gelernt haben, haben wir ein Beispiel entworfen und geschrieben, wie wir kontinuierlich auf die Ereignisse eines bestimmten intelligenten Vertrages hören:
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])
}
}
*/
Auf Live-Trading läuft:
Für die Ausführungsergebnisse wird auch ein Validierungsabschnitt (TODO: test) in den Code geschrieben.Transfer
Die Daten über den USDT-Kontrakt werden kontinuierlich überwacht und erfasst, und ein Vergleich zwischen diesen Daten und den sofort erfassten Daten über den USDT-Kontrakt kann feststellen, dass die Daten folgendermaßen übereinstimmen:
Basierend auf der vorherigen LektionListening to contract events
, erweitern wir es durch Hinzufügen von Filtern zum Abhörprozess, um auf Transfers von und zu bestimmten Adressen zu hören.topics
Wir entwerfen also eine Filterregel mit[[A1, A2, ...An], null, [C1], D]
Das ist ein Beispiel.
[A1, A2, ...An]
entspricht den Daten an der Positiontopics[0]
.Null
entspricht den Daten an der Positiontopics[1]
.[C1]
entspricht den Daten an der Positiontopics[2]
.D
entspricht den Daten an der Positiontopics[3]
.null
bedeutet, dass es nicht gefiltert wird, z. B.null
entsprichttopics[1]
und alle entsprechenden Werte.[C1]
entsprichttopics[2]
oderD
entsprichttopics[3]
, und unvereinbare Logs werden gefiltert.[A1, A2, ...An]
entsprichttopics[0]
, [A1, A2, ...An]
mit jedem von ihnen passttopics[0]
, dann werden die Protokolle nicht gefiltert.Abhören von USDT-Überweisungen von Börsen
Überwachung derUSDT
Transaktionen, die von und an die Binance-Börse übertragen werden:
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)
}
}
Der oben genannte Code, der im Live-Handel ausgeführt wird:
In dieser Lektion haben wir eingeführt, wie man einen Ereignisfilter entwirft.USDT
Sie können dieses Beispielprogramm ändern und erweitern, um jedes Ereignis zu hören, das Sie interessiert, um zu sehen, welche neuen Transaktionensmart money
Die Kommission hat die Kommission aufgefordertNFT
Die Tycoons haben sich beeilt.
Viele der Berechnungen im Zusammenhang mit Ethereum haben Werte, die die maximale sichere Ganzzahl derJavaScript
Daher sind einige Methoden auf der FMZ Quant Trading Plattform erforderlich, um große Werte zu handhaben, die wir in früheren Kursen speziell verwendet haben und nicht im Detail behandelt haben.
Drucken Sie die maximale sichere ganze Zahl, die in derJavaScript
Sprache:
function main() {
Log("Number.MAX_SAFE_INTEGER:", Number.MAX_SAFE_INTEGER)
}
Laufende Ergebnisse:
Nummer.MAX_SAFE_INTEGER: 9007199254740991
Die kleinste Einheit in Ethereum ist1wei
, und die Definition1Gwei
ist gleich1000000000 wei
. 1Gwei
ist nicht wirklich eine sehr große Zahl in Ethereum-bezogenen Berechnungen, und einige Daten sind viel größer als es.Number.MAX_SAFE_INTEGER: 9007199254740991
.
Bei FMZ Quant Trading Platform verwenden wir die PlattformenBigInt
Verwenden Sie den KonstruktorBigInt()
dieBigInt
Objekt. Sie können konstruierenBigInt
Sie können die Zahlen mit numerischen, hexadezimalen Zahlenfolgen als Parameter verwenden.toString()
Verfahren derBigInt
Objekt, um die vom Objekt als Zeichenfolge dargestellten Daten auszugeben.
Die von der Kommission unterstützten MaßnahmenBigInt
Gegenstand sind:
+
-
*
/
%
*
Siehe folgende Codebeispiele:
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))
}
Debug-Tool-Tests:
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
DieBigFloat
Objekt wird ähnlich wie dieBigInt
Objekt, um schwimmende Zifferzeichenzahlen mit größeren Werten darzustellen, und es unterstützt auch Addition, Subtraktion, Multiplikation und Division.
DieBigFloat
Objekt unterstützt dietoFixed()
method.
Siehe folgende Codebeispiel:
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))
}
Debug-Tool-Tests:
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
DieBigDecimal
Objekt ist kompatibel mit ganzen Zahlenwerten und Gleitkommawerten und unterstützt die Initialisierung mit demBigInt
Gegenstand und dieBigFloat
Objekt, und es unterstützt auch Addition, Subtraktion, Multiplikation und Division.
Siehe folgende Codebeispiel:
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())
}
Ausführen des Debugging-Tools:
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
Die folgenden zwei Funktionen:toAmount()
, toInnerAmount()
Diese beiden Funktionen werden hauptsächlich für die Präzisionskonvertierung von Daten verwendet.
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)
}
DietoAmount()
Funktion konvertiert (reduziert) eine Variables
nach dem Präzisionsparameterdecimals
In der praktischen Entwicklung von Web3 ist es häufig notwendig, mit einigen verketzten hexadezimalen Daten umzugehen.
Wir haben dies oft in unseren früheren Kursen erlebt.data
Felddaten in derTransfer(address,address,uint256)
Fall eines Smart Contracts:
{
"data": "0x00000000000000000000000000000000000000000000000001c1a55000000000",
"topics": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x0000000000000000000000006b75d8af000000e20b7a7ddf000ba900b4009a80", "0x000000000000000000000000bcb095c1f9c3dc02e834976706c87dee5d0f1fb6"],
"transactionHash": "0x27f9bf5abe3148169b4b85a83e1de32bd50eb81ecc52e5af006157d93353e4c4",
"transactionIndex": "0x0",
"removed": false,
"address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
"blockHash": "0x847be24a7b159c292bda030a011dfec89487b70e71eed486969b032d6ef04bad",
"blockNumber": "0x109b1cc",
"logIndex": "0x0"
}
Bei der Verarbeitung von Daten"data": "0x00000000000000000000000000000000000000000000000001c1a55000000000"
, verwenden wir dietoAmount()
Diese Verarbeitung ist so konzipiert, dass sie die Datenfelddaten gut in lesbare Werte umwandelt.
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 ETH Token, wie wir wissen, ist1e18 wei
, wenn wir Daten bekommen126564027559051260
inwei
, wie man es in ETH-Token umwandelt?
Mit dertoAmount(, 18)
Die Funktion ist eine sehr einfache Umwandlungsmethode.toInnerAmount()
Funktion ist die umgekehrtetoAmount()
Die Daten werden mit Hilfe dieser beiden Funktionen leicht umgewandelt.
Es ist wichtig zu beachten, dass der ganzzahlige Sicherheitsbereich in der JavaScript-Sprache,Number.MAX_SAFE_INTEGER
, und das folgende Beispiel veranschaulicht ein verstecktes Problem bei der Konvertierung von Daten:
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))
}
Es ist möglich, in dem Debugging-Tool auszuführen:
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
Durch Beobachtungen haben wir festgestellt:
Log("Convert", innerAmount, "to hexadecimal:", innerAmount.toString(16))
Diese Codezeile entspricht der Log-Output:Converting 10000000000000000 to hex: 10000000000000000
Das liegt natürlich daran, dass 10000000000000000 jenseits derNumber.MAX_SAFE_INTEGER
.
Aber wenn der Dezimalwert innerhalb des sicheren Bereichs liegt, d.h. kleiner alsNumber.MAX_SAFE_INTEGER
, dietoString(16)
Funktion konvertiert es wieder ordnungsgemäß, zum Beispiel:
function main() {
var value = 1000
Log("Convert value to hexadecimal:", "0x" + value.toString(16)) // 0x3e8
Log("Convert 0x3e8 to decimal:", Number("0x3e8")) // 1000
}
In Blockchain, sogar0.01
ETH umgerechnet auf einen Wert von10000000000000000
inwei
wird übersteigenNumber.MAX_SAFE_INTEGER``, so a safer conversion for such cases is:
BigInt ((10000000000000000).toString ((16) ``.
Ausführung von Transaktionen und Aufruf derWrite
Es ist wichtig zu wissen, welche Transaktionen wahrscheinlich scheitern werden, bevor Sie sie senden und anrufen. Es gibt simulierte Anrufe auf Ethereum zum Testen.
Methode der RPC von Ethereumeth_call
: es kann eine Transaktion simulieren und das Ergebnis einer möglichen Transaktion zurückgeben, aber es führt die Transaktion nicht tatsächlich auf der Blockchain aus.
Dieeth_call
Die Methode hat 2 Parameter, der erste ist eine Wörterbuchstruktur,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
}
Der zweite Parameter istblockNumber
: Sie können das Etikett übergebenlatest/pending/earliest
, usw.:
/* 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
*/
Als nächstes nehmen wir die Smart Contract Methodeapprove
undtransfer
Anrufe des TokensDAI
als Beispiel für Simulationsanrufe, und die folgende Testumgebung ist das Haupt-Ethereum-Netzwerk.
Wir alle kennen dieapprove
Da der ERC20-Vertrag bereits in die FMZ-Plattform ABI integriert ist, ist es nicht notwendig, die ABI des Smart Contracts zu registrieren, um von der Simulation aufgerufen zu werden.
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)
}
Der Code im Beispiel kodiert zuerst dieapprove(address,uint256)
Methode und Parameter sowie Parameterwert0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
derapprove
Die Autorisation wird dem Smart Contract unter der Adresse0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45
Das heißt, der Routervertrag fürUniswap V3
Schließlich die Ethereum RPC Methodeeth_call
Sie können sehen, dass diegasPrice
undgas
DietransactionObject
Parameter können weggelassen werden.
Das Debugging-Tool wird ausgeführt und die Simulation ruft die Approve-Methode auf, um erfolgreich zu autorisieren (sie autorisiert nicht tatsächlich):
2023-06-09 11:58:39 Info ret: 0x0000000000000000000000000000000000000000000000000000000000000001
2023-06-09 11:58:39 Info ERC20 token DAI approve encode, data: 095ea7b300000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc45ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
Es ist auch möglich, einige Ausfallszenarien zu simulieren, wenn wir diegasPrice
undgas
Parameter, wenn die ETH in der Brieftasche nicht ausreicht, um die Gasgebühr zu bezahlen, wird ein Fehler gemeldet:
nicht ausreichende Mittel
Wenn die Gaskosten zu niedrig eingestellt sind, wird ein Fehler gemeldet:
Eigengas zu niedrig: haben 21000, wollen 21944 (geliefertes Gas 21000)
Wir sind mit ERC20transfer
Wir haben eine Methode, mit der Sie ERC20-Token an eine bestimmte Wallet-Adresse überweisen können, also versuchen wir, eine Überweisung von 1000 DAI an Vitalik Buterin zu simulieren.
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
}
Da ich keine DAI-Token in dieser Testbörse habe, gab es beim Ausführen im Debug-Tool unerwartet folgenden Fehler:
Ausführung rückgängig gemacht: Dai/unzureichendes Guthaben
Überprüfen Sie die Geldbörsenadresse von Vitalik Buterin:0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
Also lassen Sie uns die Übertragungsrichtung des Simulationsanrufs anpassen und die Übertragung von 1000 DAI von Vitalik Buterin zu uns simulieren.
Ändern Sie den Code, wo die Änderungen, die ich Kommentare gemacht:
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)
}
Fehlerbehebungs-Test:
2023-06-09 13:34:31 Info 0x0000000000000000000000000000000000000000000000000000000000000001
2023-06-09 13:34:31 Info Transfer amount: 1000 DAI, use toInnerAmount convert to: 1000000000000000000000
Mit der FMZ Quant Trading Plattform ist es einfach, die Ergebnisse von Transaktionen zu simulieren und unnötige Verluste an Gasgebühren durch das Senden möglicherweise fehlgeschlagener Transaktionen zu vermeiden. Wir haben den Beispielcode aus diesem Kapitel des Kurses verwendet, um den Aufruf zur Überweisung von Geld in Vitalik Buterins Brieftasche und Vitalik Buterins Brieftasche zu simulieren, um Geld an uns zu überweisen. Natürlich gibt es viele weitere Anwendungen dafür.eth_call
Verwenden Sie Ihre Vorstellungskraft, was würden Sie dieeth_call
Methode für?
Wir wissen, dass Token wie ETH und BTC homogenisierte Token sind, und das Token in Ihrer Brieftasche unterscheidet sich nicht vom Token in meiner Brieftasche. Aber es gibt viele Dinge in der Welt, die nicht homogen sind, wie Immobilien, Antiquitäten, virtuelle Kunstwerke usw. Diese können nicht durch homogene Token in Abstraktion dargestellt werden. Daher gibt es den ERC721-Standard, um nicht-homogene Objekte zu abstrahieren, und es gibt NFT und verwandte Konzepte. Unter den vielen Smart Contracts, die auf Ethereum eingesetzt werden, wie identifizieren wir, welche Smart Contracts ERC721 Standard Smart Contracts sind?
Um ERC721 zu identifizieren, ist es wichtig, zuerst den ERC165-Standard zu kennen.
Mit dem ERC165-Standard kann ein intelligenter Vertrag die von ihm unterstützten Schnittstellen für andere Verträge deklarieren, die überprüft werden sollen.supportsInterface(bytes4 interfaceId)
, der ParameterinterfaceId
ist die zu abfragende Schnittstellen-ID. Wenn der Vertrag die Schnittstellen-ID implementiert, gibt er einen Boolean-truen Wert zurück, andernfalls gibt er einen falschen Wert zurück.
Hier werden wir darüber sprechen, wie diesinterfaceId
ist spezifisch berechnet und codiert.
ERC165-StandardHier ein Beispiel:
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;
}
}
Für die Funktionssignatur der Schnittstelle (bestehend aus einem Funktionsnamen und einer Liste von Parameterarten), um eine Unähnlichkeitsoperation durchzuführen, für einen ERC165-Schnittstellenvertrag, bei dem der Vertrag nur eine Funktion hat:
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);
}
Die Schnittstellenkennzeichnung für diese Schnittstelle ist 0x01ffc9a7. Sie können dies berechnen, indem Sie bytes4 ((keccak256 ((
supportsInterface(bytes4) )); oder den oben genannten Selector-Vertrag verwenden.
Berechnen Sie die Funktionssignatur direkt und nehmen Sie die ersten 4 Bytes zu erreicheninterfaceId
.
function main() {
var ret = Encode("keccak256", "string", "hex", "supportsInterface(bytes4)")
Log("supportsInterface(bytes4) interfaceId:", "0x" + ret.slice(0, 8))
}
Tests können im Debug-Tool unter:
2023-06-13 14:53:35 Info supportsInterface(bytes4) interfaceId: 0x01ffc9a7
Es kann festgestellt werden, daß die errechneten Ergebnisse mit der Beschreibung in derERC165-Standard document.
Im Folgenden betrachten wir die Schnittstellendefinition des Vertragsstandards 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);
}
Wenn wir feststellen wollen, ob ein intelligenter Vertrag ein ERC721-Vertrag ist, müssen wir zunächst dieinterfaceId
Die Kommission ist der Ansicht, daß diesupportsInterface(bytes4 interfaceId)
In den vorangegangenen Kursen haben wir uns mit einigen Konzepten des ERC165-Standards und dem Algorithmus zur Berechnung derinterfaceId
, und wir schreiben Code, um direkt zu berechnen:
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)
}
Der Code verwendet dieEncode()
Funktion für die Funktionssignaturberechnung (diekeccak256
Die Berechnungen werden in den folgenden Bereichen durchgeführt:Encode()
Funktion als"raw"
, die Funktion gibt dieArrayBuffer
Art derJavaScript
Sprache.
Um eine^
(Iso-oder) Operation auf zweiArrayBuffer
Objekte, müssen Sie eineTypedArray
Ansicht auf der Grundlage derArrayBuffer
Objekt, dann durchlaufen die Daten darin und führen die Iso-oder Operation einzeln aus.
Führen Sie das Debugging-Tool aus:
2023-06-13 15:04:09 Info 0x80ac58cd
Es ist festzustellen, daß die errechneten Ergebnisse mit denen ineip-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);
...
Mit der ERC721-Schnittstelle ID können wir feststellen, ob ein Vertrag ein ERC721 Standardvertrag ist oder nicht.BAYC
Erstens müssen wir den ABI registrieren, und da wir nur die folgenden drei Methoden aufrufen, können wir diese drei Methoden registrieren:
Die spezifischen Codes sind wie folgt:
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)
}
Tests können im Debugging-Tool ausgeführt werden:
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