En la carga de los recursos... Cargando...

Comience con el desarrollo web3 Basado en Ethereum Usando FMZ

El autor:FMZ~Lydia, Creado: 2023-06-25 09:17:53, Actualizado: 2024-11-11 22:34:49

Se añade el texto siguiente: transactionHash: 0x6aa8d80daf42f442591e7530e31323d05e1d6dd9f9f9b9c102e157d89810c048, dirección : 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2, blockHash: 0xcd3d567c9bd02a4549b1de0dc638ab5523e847c3c156b096424f56c633000fd9 ¿Por qué no? dirección : 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2, blockHash: 0xcd3d567c9bd02a4549b1de0dc638ab5523e847c3c156b096424f56c633000fd9, Bloque Número : 0x109b1cd, logIndex: 0xde, eliminado: falso, topics: [0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65, 0x00000000000000000000ef1c6e67703c7bd7107eed8303fbe6ec2554bf6b], datos: 0x000000000000000000000000000000000000000000000000000164f2434262e1cc, transactionHash: 0x6aa8d80daf42f442591e7530e31323d05e1d6dd9f9f9b9c102e157d89810c048, índice de transacciones : 0x91 ¿ Qué pasa?


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"],  

El valor de latopicscampo es una estructura de matriz utilizada para describir el evento. Se especifica que su longitud (matriz) no puede exceder 4 y el primer elemento es el hash de la firma del evento. En la Plataforma de Comercio Cuántico FMZ, podemos calcular este hash de firma utilizando elEncodefunción, utilizando el siguiente código:

function main() {
    var eventFunction = "Transfer(address,address,uint256)"
    var eventHash = Encode("keccak256", "string", "hex", eventFunction)
    Log("eventHash:", "0x" + eventHash)
    // eventHash: 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
}

Calcular elkeccak256valor hash (codificación hexadecimal) deTransfer(address,address,uint256)es0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef.

El valor de latopicscampo es una estructura de matriz, con el segundo elemento, y el tercer elemento, respectivamente:

  • Dirección de envíofrom

  • Dirección de recepciónto

  • data

    Los datos en eldatalos campos son:

    "data": "0x0000000000000000000000000000000000000000000000000164f2434262e1cc",
    

    En el caso de que ciertos parámetros (parámetros sin declaraciones indexadas en el código de solidez del contrato inteligente) se almacenen en eldata section.

    Analizar los datos0x0000000000000000000000000000000000000000000000000164f2434262e1cc

    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
    }
    

    Estos datos se obtienen como 0,10047146239950075 y eldataes el importe de transferencia correspondiente.


Lo anterior fue explicado, practicado y listo para ir. Podemos empezar a recuperar los registros en:

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

¿ Qué pasa?https://etherscan.io/:

img

Resultados del código de prueba ejecutado en la herramienta de depuración FMZ:

img

Datos en elfrom, toLos campos también se pueden analizar en función de las necesidades en el momento de la recuperación, por ejemplo:

function main() {
    var from = "0x00000000000000000000000012b791bb27b3a4ee958b5a435fea7d49ec076e9c"
    var address = "0x" + exchange.IO("encodePacked", "address", from)
    Log("address:", address)
}

Resultados de ejecución:

Dirección: 0x12b791bb27b3a4ee958b5a435fea7d49ec076e9c

Escuchar los acontecimientos del contrato

Desde elherramienta de depuraciónEn esta sección, utilizamos la plataforma de negociación de FMZ Quant para crear operaciones en vivo para probar.

Aquí usamos la red principal de Ethereum, y escuchamos elTransfer(address,address,uint256)el hecho de queUSDTBasándonos en lo que aprendimos en la última lección, diseñamos y escribimos un ejemplo de escuchar continuamente los eventos de un determinado contrato inteligente:

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 el comercio en vivo:

img

Para los resultados de ejecución, también se escribe una sección de validación (TODO: test) en el código.Transferel evento del contrato USDT se supervise continuamente y se registren los datos, y una comparación entre estos datos y los datos del evento obtenidos de una sola vez puede observar que los datos son consistentes con:

img

Filtración de eventos

Basado en la lección anteriorListening to contract eventsCuando un contrato inteligente crea un registro (es decir, libera un evento), los datos del registro se almacenan en el registro de los clientes.topicsAsí que diseñamos una regla de filtro con[[A1, A2, ...An], null, [C1], D]como ejemplo.

  1. [A1, A2, ...An]corresponde a los datos en posicióntopics[0].
  2. Nullcorresponde a los datos en posicióntopics[1].
  3. [C1]corresponde a los datos en posicióntopics[2].
  4. Dcorresponde a los datos en posicióntopics[3].
  • Si se establece un elemento de la estructura de condicionesnullsignifica que no se filtra, por ejemplonullcorresponde atopics[1]y cualquier coincidencia de valor.
  • Si el elemento de la estructura de condiciones establece un único valor que indica que la posición debe coincidir, por ejemplo[C1]corresponde atopics[2]o bienDcorresponde atopics[3], y se filtran los registros no coincidentes.
  • Si el elemento de la estructura de condiciones es una matriz, significa que al menos uno de los elementos de la matriz debe coincidir, por ejemplo[A1, A2, ...An]corresponde atopics[0], [A1, A2, ...An]con cualquiera de ellos coincidiendotopics[0], entonces los registros no se filtrarán.

Escuchar las transferencias en USDT de las bolsas

Control de lasUSDTlas transacciones transferidas desde y hacia la Bolsa 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)
    }
}

El código anterior se ejecuta en el comercio en vivo:

img

En esta lección, presentamos cómo diseñar un filtro de eventos.USDTPuede modificar y ampliar este programa de muestra para escuchar cualquier evento que le interese, para ver qué nuevas transaccionessmart money¿Cuáles son los nuevos artículos que ha hecho elNFTLos magnates se han apresurado, etc.

Conversión de unidades

Muchos de los cálculos relacionados con Ethereum tienen valores que exceden el número entero seguro máximo delJavaScriptPor lo tanto, se necesitan algunos métodos en la Plataforma de Comercio Cuántico FMZ para manejar valores grandes, que hemos utilizado específicamente en cursos anteriores y no hemos cubierto en detalle.

Imprimir el número entero seguro máximo definido en elJavaScriptLenguaje:

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

Resultados de ejecución:

No.MAX_SAFE_INTEGER: 9007199254740991 El número de la unidad es el siguiente:

¿ Qué pasa?

La unidad más pequeña definida en Ethereum es1wei, y la definición1Gweies igual a1000000000 wei. 1GweiNo es realmente un número muy grande en los cálculos relacionados con Ethereum, y algunos datos son mucho más grandes que él.Number.MAX_SAFE_INTEGER: 9007199254740991.

En FMZ Quant Trading Platform, usamos las plataformasBigIntObjeto para representar estos datos enteros muy grandes.BigInt()para construir elBigIntObjeto. Puedes construirBigIntobjetos que utilizan cadenas numéricas hexadecimales como parámetros.toString()método deBigIntObjeto para la salida de los datos representados por el objeto como una cadena.

Las operaciones apoyadas por elBigIntsu objeto son:

  • Añadir:+
  • Sustracción:-
  • Multiplicación:*
  • División:/
  • Operaciones en módulo:%
  • Operaciones de potencia:*

Consulte los siguientes ejemplos de códigos:

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

Prueba de la herramienta de depuración:

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

El BigFloat

ElBigFloatobjeto se utiliza de manera similar a laBigIntObjeto para representar números de coma flotante con valores más grandes, y también admite suma, resta, multiplicación y división. ElBigFloatEl objeto soporta eltoFixed() method.

Consulte el siguiente ejemplo de código:

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

Prueba de la herramienta de depuración:

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

- ¿ Qué pasa?

ElBigDecimalObjeto es compatible con valores enteros y valores de coma flotante y admite la inicialización con elBigIntObjeto y elBigFloatObjeto, y también admite suma, resta, multiplicación y división.

Consulte el siguiente ejemplo de código:

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

Se ejecuta en la herramienta de depuración:

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

Conversión de unidades

Las siguientes dos funciones:toAmount(), toInnerAmount()Hemos utilizado muchas veces en cursos anteriores, estas dos funciones se utilizan principalmente para la conversión de datos de precisión.

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

EltoAmount()La función convierte (reduce) una variablessegún el parámetro de precisióndecimalsEn el desarrollo práctico de web3, a menudo es necesario tratar con algunos datos hexadecimales encadenados. Esto se ha visto a menudo en nuestros cursos anteriores, por ejemplo, eldatadatos de campo en elTransfer(address,address,uint256)el caso de un contrato inteligente:

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

Cuando se procesan datos"data": "0x00000000000000000000000000000000000000000000000001c1a55000000000", usamos eltoAmount()Este procesamiento está diseñado para hacer un buen trabajo de conversión de datos de campo a valores legibles.

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, como sabemos, es1e18 wei, si obtenemos datos126564027559051260En elwei, ¿cómo convertirlo en tokens ETH? Utilizando eltoAmount(, 18)El método de conversión es muy simple.toInnerAmount()la función es la operación inversa de latoAmount()En el caso de los datos de la red, la función de conversión de los datos de la red de datos de la red de datos de la red de datos de la red de datos de la red de datos de la red de datos de la red de datos de la red de datos de la red de datos de la red de datos de la red de datos de la red de datos de la red de datos de la red.

Es importante tener en cuenta el rango de seguridad de valor entero en el lenguaje JavaScript,Number.MAX_SAFE_INTEGER, y el siguiente ejemplo ilustra un problema oculto al convertir datos:

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 posible ejecutar en la herramienta de depuración:

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

A través de la observación encontramos que:

Log("Convert", innerAmount, "to hexadecimal:", innerAmount.toString(16))

Esta línea de código corresponde a la salida de registro:Converting 10000000000000000 to hex: 10000000000000000La razón es naturalmente que 10000000000000000 está más allá deNumber.MAX_SAFE_INTEGER.

Pero cuando el valor decimal está dentro del rango seguro, es decir, menor queNumber.MAX_SAFE_INTEGER, eltoString(16)función lo convierte correctamente de nuevo, por ejemplo:

function main() {
    var value = 1000
    Log("Convert value to hexadecimal:", "0x" + value.toString(16))   // 0x3e8
    Log("Convert 0x3e8 to decimal:", Number("0x3e8"))               // 1000
}

En blockchain, incluso0.01ETH convertidos a un valor de10000000000000000En elweiexcederáNumber.MAX_SAFE_INTEGER``, so a safer conversion for such cases is:BigInt ((10000000000000000).toString ((16) ``.

Llamadas de simulación

Ejecución de las operaciones y convocatoria de lasWriteEl método de contratos inteligentes en Ethereum cuesta una cierta cantidad de gas y a veces falla. Es importante saber qué transacciones son propensas a fallar antes de enviarlas y llamarlas.

- ¿ Qué pasa?

Método RPC de Ethereumeth_call: puede simular una transacción y devolver el resultado de una posible transacción, pero en realidad no ejecuta la transacción en la cadena de bloques.

Eleth_callEl método tiene 2 parámetros, el primero es una estructura de diccionario,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
}

El segundo parámetro esblockNumber: puede pasar la etiquetalatest/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
*/

A continuación, tomamos el método de contrato inteligenteapproveytransferllamadas del tokenDAIcomo ejemplo para las llamadas de simulación, y el siguiente entorno de prueba es la red principal de Ethereum.

Se aprueba la llamada de simulación

Todos estamos familiarizados con elapproveEn el caso de los contratos ERC20 que se utilizan en la simulación, no hay necesidad de registrar el ABI del contrato inteligente para que sea llamado por la simulación.

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

El código en el ejemplo codifica primero elapprove(address,uint256)método y parámetros, y el valor del parámetro0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffde lasapproveEl método indica el número máximo de autorizaciones. la autorización se da al contrato inteligente en la dirección0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45Es decir, el contrato del router paraUniswap V3Finalmente el método Ethereum RPCeth_callSe puede ver que elgasPriceygaslos campos en eltransactionObjectlos parámetros pueden omitirse.

Se ejecuta la herramienta de depuración y la simulación llama al método de aprobación para autorizar con éxito (no autoriza realmente):

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

También es posible simular algunos escenarios de fallas, cuando ajustamos elgasPriceygasParámetros, si el ETH en la billetera no es suficiente para pagar la tarifa de gas, se informará de un error:

fondos insuficientes

Cuando el coste del gas esté demasiado bajo, se notificará un error:

Gas intrínseco demasiado bajo: tiene 21000, quiere 21944 (gas suministrado 21000)

Transferencia de llamada de simulación

Estamos familiarizados con ERC20transfermétodo, que le permite transferir tokens ERC20 a una dirección de cartera determinada, así que vamos a tratar de simular una transferencia de 1000 DAI a 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 
}

Como no tengo fichas DAI en esta billetera de prueba, ejecutándola en la herramienta de depuración informó inesperadamente el siguiente error:

ejecución revertida: Dai/saldo insuficiente

Compruebe la dirección de la cartera de Vitalik Buterin:0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045Así que ajustemos la dirección de transferencia de la llamada de simulación y simulemos la transferencia de 1000 DAI de Vitalik Buterin a nosotros.

Modificar el código, donde los cambios que hice comentarios:

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

Prueba de la herramienta de depuración:

2023-06-09 13:34:31		Info	0x0000000000000000000000000000000000000000000000000000000000000001
2023-06-09 13:34:31		Info	Transfer amount: 1000 DAI, use toInnerAmount convert to: 1000000000000000000000

Utilizando la plataforma de negociación FMZ Quant, es fácil simular los resultados de las transacciones y evitar la pérdida innecesaria de tarifas de gas por enviar transacciones potencialmente fallidas. Utilizamos el código de ejemplo de este capítulo del curso para simular la llamada para transferir dinero a la billetera de Vitalik Buterin y la billetera de Vitalik Buterin para transferir dinero a nosotros. Por supuesto, hay muchos más usos para esto.eth_callUtilice su imaginación, ¿qué usaría eleth_call¿Cuál es el método?

Identificar los contratos ERC721

Sabemos que los tokens como ETH y BTC son tokens homogeneizados, y el token en su billetera no es diferente del token en mi billetera. Pero hay muchas cosas en el mundo que no son homogéneas, como bienes raíces, antigüedades, obras de arte virtuales, etc. Estos no pueden ser representados por tokens homogéneos en abstracción. Por lo tanto, existe el estándar ERC721 para abstraer objetos no homogéneos, y hay NFT y conceptos relacionados. Entonces, entre los muchos contratos inteligentes desplegados en Ethereum, ¿cómo identificamos qué contratos inteligentes son contratos inteligentes estándar ERC721?

Para identificar ERC721, es importante conocer primero la norma ERC165.

El valor de las pérdidas

Con el estándar ERC165, un contrato inteligente puede declarar las interfaces que admite para que otros contratos las comprueben.supportsInterface(bytes4 interfaceId), el parámetrointerfaceIdes el ID de interfaz a consultar. Si el contrato implementa el ID de interfaz devuelve un valor verdadero booleano, de lo contrario devuelve un valor falso.

Aquí vamos a hablar de cómo estointerfaceIdse calcula y codifica específicamente.

Se aplicará la norma ERC165.muestra un ejemplo:

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

Para la firma de función de la interfaz (que consta de un nombre de función y una lista de tipos de parámetros) para realizar una operación de disimilaridad, para un contrato de interfaz ERC165 en el que el contrato tenga una sola función:

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

El identificador de interfaz para esta interfaz es 0x01ffc9a7. Puede calcularlo ejecutando bytes4 ((keccak256 ((supportsInterface ((bytes4) )); o utilizando el contrato Selector anterior.

Calcular la firma de la función directamente y tomar sus primeros 4 bytes para llegar ainterfaceId.

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

Las pruebas se pueden ejecutar en la herramienta de depuración en:

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

Se puede observar que los resultados calculados son consistentes con la descripción en elSe aplicará la norma ERC165. document.

Se trata de la siguiente:

A continuación, examinemos la definición de interfaz de la norma de contratos 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 queremos determinar si un contrato inteligente es un contrato ERC721, primero necesitamos saber elinterfaceIdde la ERC721 contrato antes de que podamos tratar de utilizar elsupportsInterface(bytes4 interfaceId)En los cursos anteriores nos hemos familiarizado con algunos conceptos del estándar ERC165 y el algoritmo para calcular lainterfaceId, y escribimos código para calcular directamente:

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

El código utiliza elEncode()Función para el cálculo de la firma de la función (lakeccak256El valor de los valores de los valores de los valores de los valores de los valores de los valores de los valores de los valores de los valores de los valores de los valores de los valores de los valores de los valores de los valores de los valores de los valoresEncode()función como"raw", la función devuelve elArrayBuffertipo deJavaScriptel lenguaje. Para llevar a cabo una^(iso-o) operación en dosArrayBufferobjetos, usted necesita para crear unTypedArrayLa opinión basada en elArrayBufferobjeto, luego iterar a través de los datos en él y realizar la operación iso-o uno por uno.

Ejecute en la herramienta de depuración:

2023-06-13 15:04:09		Info	0x80ac58cd

Se puede observar que los resultados calculados son consistentes con los descritos enEIP-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);
...

Con el ID de interfaz ERC721, podemos determinar si un contrato es un contrato estándar ERC721 o no.BAYCPara hacer la prueba, que es un contrato que sigue ERC721. primero tenemos que registrar el ABI, y ya que sólo llamamos a los siguientes tres métodos, podemos registrar estos tres métodos:

  • soporta Interfaz ((ID de interfaz)
  • el símbolo (())
  • nombre ())

Los códigos específicos son los siguientes:

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

Las pruebas se pueden ejecutar en la herramienta de depuración:

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

Relacionados

Más.