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 = nulo
função principal (() {
Var evento =
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 = [] função onexit ((() { Log (ponto final de execução, ponto de verificação de registro) 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("长度不等!")
return
}
for (var i = 0; i < arrLog.length; i++) {
Log("判断blockNumber:", logs[i].blockNumber == arrLog[i].blockNumber, ",判断from:", logs[i].topics[1] == arrLog[i].topics[1],
"判断to:", logs[i].topics[2] == arrLog[i].topics[2])
}
} */
实盘运行:
![img](/upload/asset/16ffd65adc050d33056d.png)
对于执行结果,代码中也编写了验证部分(TODO: test)。经过简单验证可以看到持续监控USDT合约的```Transfer```事件并且记录数据,用这个数据和一次性获取的事件数据对比可以观察出数据一致:
![img](/upload/asset/16e07390a11a606276b1.png)
### 事件过滤
在上一节课程「监听合约事件」的基础上,我们拓展一下,在监听的过程中增加过滤器,监听指定地址的转入转出。当智能合约创建日志时(即释放事件),日志数据中```topics```最多包含4条信息。所以我们设计一个过滤规则,以```[[A1, A2, ...An], null, [C1], D]```为例子。
1、```[A1, A2, ...An]```对应```topics[0]```位置的数据。
2、```null```对应```topics[1]```位置的数据。
3、```[C1]```对应```topics[2]```位置的数据。
4、```D```对应```topics[3]```位置的数据。
- 如果条件结构中的元素设置```null```表示不被过滤,例如```null```对应```topics[1]```,任何值都匹配。
- 如果条件结构中的元素设置单个值表示该位置必须匹配,例如```[C1]```对应```topics[2]```或者```D```对应```topics[3]```,不匹配的日志被过滤。
- 如果条件结构中的元素是一个数组,表示数组中的元素至少有一个要匹配,例如```[A1, A2, ...An]```对应```topics[0]```,```[A1, A2, ...An]```中有任意一个和```topics[0]```匹配,则日志不会被过滤。
**监听交易所的USDT转账**
监控从币安交易所转出、转入币安交易所```USDT```的交易:
```javascript
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("设置过滤条件:", 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) {
// 检查过滤条件,设置了过滤条件则执行判断
if (self.filters.length != 0) {
// 初始过滤标记
var isFilter = true
// 遍历过滤条件设置
for (var j = 0; j < self.filters.length; j++) {
// 取一个过滤设置,例如:[[A1, A2, ...An], null, [C1], D]
var cond = self.filters[j]
// 遍历这个过滤设置
var final = true
for (var topicsIndex = 0; topicsIndex < cond.length; topicsIndex++) {
// 拿到这个过滤设置中的某一个条件,如果是第一个条件:即要和topics[0]对比的数据
var condValue = cond[topicsIndex]
// 日志中的数据
if (topicsIndex > logs[i].topics.length - 1) {
continue
}
var topicsEleValue = logs[i].topics[topicsIndex]
// 如果是Transfer事件,需要处理from和to
if (logs[i].topics[0] == "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef") {
if (topicsIndex == 1 || topicsIndex == 2) {
topicsEleValue = "0x" + exchange.IO("encodePacked", "address", topicsEleValue)
}
}
// 如果condValue类型是数组,表示该位置的对比条件有多个,多个条件对比是逻辑或关系
if (Array.isArray(condValue) && condValue.length > 1) {
// 判断 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() {
// 初始清理日志
LogReset(1)
LogProfitReset()
var event = "Transfer(address,address,uint256)" // 监听事件
var contractAddress = "0xdac17f958d2ee523a2206206994597c13d831ec7" // USDT合约地址
var decimals = exchange.IO("api", contractAddress, "decimals") // 获取USDT token的精度信息
var accountBinanceAddress = "0x28C6c06298d514Db089934071355E5743bf21d60" // Binance 热钱包地址
accountBinanceAddress = accountBinanceAddress.toLowerCase() // 地址处理为小写
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])
if (fromAddress == accountBinanceAddress) {
Log("币安转出 - ", " Transfer:", fromAddress, "->", toAddress, ", value:", toAmount(log.data, decimals), ", blockNumber:", toAmount(log.blockNumber, 0), "#CD32CD")
} else if (toAddress == accountBinanceAddress) {
Log("转入币安 - ", " Transfer:", fromAddress, "->", toAddress, ", value:", toAmount(log.data, decimals), ", blockNumber:", toAmount(log.blockNumber, 0), "#FF0000")
}
})
// 设置事件过滤
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("币安钱包地址:", accountBinanceAddress, " 余额:", balance, direction)
LogProfit(balance, "&") // 只画图,不打印日志
preBalance = balance
}
LogStatus(_D(), "币安钱包地址:", accountBinanceAddress, ", 余额:", balance)
Sleep(5000 * 3)
}
}
O código acima é executado no disco real:
Nesta seção, nós mostramos como projetar filtros de eventos e como usá-los para escutar as notícias relacionadas com as carteiras quentes do Bitcoin Exchange.USDT
Transações. Você pode modificar e expandir este exemplo para ouvir qualquer evento que você esteja interessado em ver.smart money
O que foi feito de novo?NFT
O que é que a construção da barragem tem a ver com os novos projetos e assim por diante.
Muitas das calculações relacionadas com o ethereum foram realizadas com valores que ultrapassam o valor de uma moeda.JavaScript
Os números inteiros de segurança máxima da linguagem. Portanto, é necessário que os inventores quantifiquem as plataformas de negociação com métodos para lidar com grandes números, que também foram usados em cursos anteriores, sem detalhes.
ImpressãoJavaScript
O maior número inteiro de segurança definido na linguagem é:
function main() {
Log("Number.MAX_SAFE_INTEGER:", Number.MAX_SAFE_INTEGER)
}
Os resultados:
Número.MAX_SAFE_INTEGER: 9007199254740991
A unidade mínima definida no Ethereum é1wei
Definição1Gwei
É igual a1000000000 wei
。1Gwei
Na verdade, não é um número muito grande na computação relacionada ao Ethereum, e alguns dados são muito maiores do que ele.Number.MAX_SAFE_INTEGER: 9007199254740991
。
A plataforma de negociação quantitativa inventada pelos inventores é a plataforma que usamos.BigInt
Os objetos representam esses dados inteiros enormes; usam funções de construção.BigInt()
Para construirBigInt
Objetos. Pode ser construído como um parâmetro usando um valor numérico, uma string numérica de 16 dígitos.BigInt
Objetos ─ UtilizaçãoBigInt
ObjetotoString()
Método para exportar dados representados por objetos em forma de strings.
BigInt
Operações suportadas por objetos:
+
-
*
/
%
**
Veja os seguintes exemplos de código:
function main() {
// 1Gwei的十进制表示
var oneGwei = 1000000000
// 1Gwei的十进制转换为十六进制表示
var oneGweiForHex = "0x" + oneGwei.toString(16)
Log("oneGwei : ", oneGwei)
Log("oneGweiForHex : ", oneGweiForHex)
// 构造BigInt对象
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("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))
}
Testes de ferramentas de depuração:
2023-06-08 11:39:50 信息 Number.MAX_SAFE_INTEGER * 2 : 18014398509481982
2023-06-08 11:39:50 信息 Number.MAX_SAFE_INTEGER : 9007199254740991
2023-06-08 11:39:50 信息 100 的平方根 : 10
2023-06-08 11:39:50 信息 1Gwei ** 2 : 1000000000000000000
2023-06-08 11:39:50 信息 (1Gwei + 1) % 1Gwei : 1
2023-06-08 11:39:50 信息 1Gwei + 1Gwei : 2000000000
2023-06-08 11:39:50 信息 1Gwei - 1Gwei : 0
2023-06-08 11:39:50 信息 1Gwei * 1Gwei : 1000000000000000000
2023-06-08 11:39:50 信息 1Gwei / 1Gwei : 1
2023-06-08 11:39:50 信息 oneGweiForHex : 0x3b9aca00
2023-06-08 11:39:50 信息 oneGwei : 1000000000
BigFloat
Objeto eBigInt
O uso de objetos é semelhante, usado para representar números flutuantes com valores maiores, e também suporta operações de adição, subtração e multiplicação.BigFloat
Suporte de objetostoFixed()
Como fazer isso?
Veja os seguintes exemplos 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))
}
Testes de ferramentas de depuração:
2023-06-08 13:56:44 信息 pi / 2.0 : 1.57
2023-06-08 13:56:44 信息 pi * 2.0 : 6.28
2023-06-08 13:56:44 信息 pi - oneGweiForHex : -999999996.86
2023-06-08 13:56:44 信息 pi + oneGwei : 1000000003.14
BigDecimal
Objetos compatíveis com inteiros, números de ponto flutuante, suporte ao usoBigInt
Objetos.BigFloat
Inicialização de objetos e suporte a operações de adição, subtração e multiplicação.
Veja os seguintes exemplos 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())
}
A ferramenta de depuração é executada:
2023-06-08 14:52:53 信息 pi / pi : 1
2023-06-08 14:52:53 信息 2.0 * pi : 6.283
2023-06-08 14:52:53 信息 oneGwei - pi : 999999996.8585
2023-06-08 14:52:53 信息 oneGwei + pi : 1000000003.1415
2023-06-08 14:52:53 信息 BigFloat(pi) : 3.1415
2023-06-08 14:52:53 信息 BigInt(oneGwei) : 1e+9
2023-06-08 14:52:53 信息 oneGweiForHex : 1e+9
2023-06-08 14:52:53 信息 oneGwei : 1e+9
2023-06-08 14:52:53 信息 pi : 3.14
As duas funções são:toAmount()
、toInnerAmount()
Como já fomos usando várias vezes em cursos anteriores, estas duas funções são usadas principalmente para a conversão de precisão de dados.
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)
}
toAmount()
A função pode mudar uma variável.s
De acordo com os parâmetros de precisãodecimals
Realizar uma conversão de <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
E isso é uma coisa que muitas vezes nos encontramos em cursos anteriores, como contratos inteligentes.Transfer(address,address,uint256)
O que aconteceudata
Dados de campo:
{
"data": "0x00000000000000000000000000000000000000000000000001c1a55000000000",
"topics": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x0000000000000000000000006b75d8af000000e20b7a7ddf000ba900b4009a80", "0x000000000000000000000000bcb095c1f9c3dc02e834976706c87dee5d0f1fb6"],
"transactionHash": "0x27f9bf5abe3148169b4b85a83e1de32bd50eb81ecc52e5af006157d93353e4c4",
"transactionIndex": "0x0",
"removed": false,
"address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
"blockHash": "0x847be24a7b159c292bda030a011dfec89487b70e71eed486969b032d6ef04bad",
"blockNumber": "0x109b1cc",
"logIndex": "0x0"
}
Processamento de dados"data": "0x00000000000000000000000000000000000000000000000001c1a55000000000"
A partir de agora,toAmount()
Funções. Este tipo de design de processamento pode ser muito bom paradata
Os dados do campo são convertidos em valores legíveis.
function toAmount(s, decimals) {
return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString())
}
function main() {
var data = "0x00000000000000000000000000000000000000000000000001c1a55000000000"
Log(toAmount(data, 18)) // 打印出 0.12656402755905127
}
Um token de ETH que nós sabemos é1e18 wei
E se nós temos umwei
Dados por unidade126564027559051260
Como é que é possível converter em ETH?
UtilizaçãotoAmount(, 18)
A função pode ser transformada muito simplesmente.toInnerAmount()
A função é:toAmount()
A função pode ser reversível (de acordo com a precisão, amplificação) e pode ser usada para transformar dados.
É importante notar que o intervalo de segurança inteiro da linguagem JavaScript é:Number.MAX_SAFE_INTEGER
O exemplo a seguir mostra um problema mais oculto na conversão de dados:
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)
// 十进制数值 10000000000000000 -> 十六进制数值 0x2386f26fc10000
Log("转换", innerAmount, "为十六进制:", innerAmount.toString(16))
Log("转换", BigInt(10000000000000000).toString(10), "为十六进制:", BigInt(10000000000000000).toString(16))
Log("0x" + BigInt(10000000000000000).toString(16), "转换为10进制:", toAmount("0x" + BigInt(10000000000000000).toString(16), 0))
}
A ferramenta de depuração pode ser executada:
2023-06-15 16:21:40 信息 0x2386f26fc10000 转换为10进制: 10000000000000000
2023-06-15 16:21:40 信息 转换 10000000000000000 为十六进制: 2386f26fc10000
2023-06-15 16:21:40 信息 转换 10000000000000000 为十六进制: 10000000000000000
2023-06-15 16:21:40 信息 typeof(innerAmount): number , innerAmount: 10000000000000000
2023-06-15 16:21:40 信息 innerAmount: 10000000000000000
2023-06-15 16:21:40 信息 Number.MAX_SAFE_INTEGER: 9007199254740991
A partir da observação, descobrimos:
Log("转换", innerAmount, "为十六进制:", innerAmount.toString(16))
O log output correspondente a esta linha de código é:转换 10000000000000000 为十六进制: 10000000000000000
A razão natural é que o número de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhões de milhõesNumber.MAX_SAFE_INTEGER
。
Mas quando um valor decimal está dentro do limite de segurança, ou seja, é menor do queNumber.MAX_SAFE_INTEGER
O que é isso?toString(16)
A conversão de funções é normal, por exemplo:
function main() {
var value = 1000
Log("把value转换为十六进制:", "0x" + value.toString(16)) // 0x3e8
Log("把0x3e8转换为十进制:", Number("0x3e8")) // 1000
}
No blockchain, mesmo que0.01
A troca de ETH é:wei
Valor numérico da unidade10000000000000000
E também vai além.Number.MAX_SAFE_INTEGER
A maioria das pessoas não tem acesso a internet, e a maioria das pessoas não tem acesso a internet.BigInt(10000000000000000).toString(16)
。
O Bitcoin é um instrumento de negociação que permite executar transações no Ethereum, chamando contratos inteligentes.Write
Os métodos consomem um certo custo de gás e, às vezes, existem riscos de falha. É muito importante saber quais transações podem falhar antes de enviar transações e chamadas.
Método de RPC do Ethereumeth_call
O algoritmo pode simular uma transação e retornar resultados possíveis, mas não executá-la no blockchain.
eth_call
O método tem dois parâmetros, o primeiro parâmetro é uma estrutura de dicionário, e o segundo parâmetro é uma estrutura de dicionário.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
}
O segundo parâmetro é:blockNumber
É possível passar etiquetas:latest
/pending
/earliest
Por exemplo:
/* 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 próxima coisa que fazemos é usar tokens.DAI
O método do contrato inteligenteapprove
、transfer
O exemplo de uma chamada analógica é o seguinte ambiente de teste para a rede Ethereum.
O contrato ERC20approve
O método é familiar para todos nós e foi praticado em cursos anteriores. Como o contrato ERC20 já está embutido no ABI da plataforma FMZ, não é necessário registrar o ABI do contrato inteligente que você quer simular.
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)
}
O código do exemplo vai começar porapprove(address,uint256)
O método, os parâmetros são codificados.approve
Valores dos parâmetros do método0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
Indica o número máximo de autorizações.0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45
Ou seja,Uniswap V3
O contrato de roteamento. Última chamada do método Ethereum RPCeth_call
Simulação.transactionObject
ParâmetrosgasPrice
、gas
Os campos podem ser omitidos.
A ferramenta de depuração é executada, e o método de aprovação de chamadas simuladas é aprovado com sucesso (mas não é realmente autorizado):
2023-06-09 11:58:39 信息 ret: 0x0000000000000000000000000000000000000000000000000000000000000001
2023-06-09 11:58:39 信息 ERC20 token DAI approve encode, data: 095ea7b300000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc45ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
E também podemos simular alguns cenários de falhas, quando ajustamos o sistema de controle de falhas.gasPrice
egas
O parâmetro é errado se o ETH na carteira não for suficiente para pagar o preço do gás:
fundos insuficientes
Quando o preço do gás é muito baixo, há um erro:
Gás intrínseco muito baixo: 21000, quero 21944 (gás fornecido 21000)
Para o ERC20transfer
O método que não conhecemos é o de transferir tokens ERC20 para um endereço de carteira, e tentamos simular a transferência de 1000 DAI para V.
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")
// 转账给V神
var decimals_DAI = exchange.IO("api", contractAddress_DAI, "decimals")
var transferAmount = toInnerAmount(1000, decimals_DAI)
Log("转账金额:", 1000, "DAI, 使用 toInnerAmount 转换为:", 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 a minha carteira de teste não tem um DAI token, o resultado esperado no debug foi errado:
Execução revertida: Dai/saldo insuficiente
Veja o endereço da carteira do deus V:0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
Então, vamos alinhar a direção de transferência para a chamada analógica, simulando que V nos transfere 1000 DAI.
O que eu faço é alterar o código, e eu escrevo um comentário no local:
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("转账金额:", 1000, "DAI, 使用 toInnerAmount 转换为:", transferAmount)
// encode transfer
var data = exchange.IO("encode", contractAddress_DAI, "transfer(address,uint256)",
wallet, transferAmount) // 使用wallet变量作为参数,转账接收方地址改为我自己
var transactionObject = {
"from" : walletVitalik, // 使用walletVitalik变量作为from字段的值,模拟这个调用是由V神钱包地址发出
"to" : contractAddress_DAI,
"data" : "0x" + data,
}
var blockNumber = "latest"
var ret = exchange.IO("api", "eth", "eth_call", transactionObject, blockNumber)
Log(ret)
}
Testes de ferramentas de depuração:
2023-06-09 13:34:31 信息 0x0000000000000000000000000000000000000000000000000000000000000001
2023-06-09 13:34:31 信息 转账金额: 1000 DAI, 使用 toInnerAmount 转换为: 1000000000000000000000
Usando a plataforma de negociação quantitativa do inventor, é possível simular com facilidade o resultado da transação e evitar a perda de custos de gás desnecessários ao enviar transações que podem falhar. Usamos o código de exemplo do curso deste capítulo para simular uma transferência para a carteira de V, uma chamada da carteira de V para transferir para nós.eth_call
O que você pode fazer é usar a sua imaginação, e você vai ter uma vida melhor.eth_call
Afinal, onde é que o método é usado?
Sabemos que todos os tokens como o ETH e o BTC são tokens homogêneos, o que você tem na sua mão não é diferente do que eu tenho. Mas há muitas coisas no mundo que são de qualidade diferente, por exemplo: imóveis, antiguidades, obras de arte virtuais, etc., que não podem ser representadas em termos abstratos com tokens homogêneos. Então, como podemos identificar quais dos muitos contratos inteligentes que estão sendo implantados no Ethereum são os de acordo com o padrão ERC721?
Para identificar o ERC721, você deve entender o padrão ERC165.
Com o padrão ERC165, um contrato inteligente pode declarar as interfaces que ele suporta para outras contratações de verificação.supportsInterface(bytes4 interfaceId)
ParâmetrosinterfaceId
O ID da interface a ser consultada. Se o contrato for implementado, o ID da interface retornará o valor verdadeiro de Boole, caso contrário, retornará o falso.
A seguir, vamos falar sobre isso.interfaceId
O que é que é isso, o que é isso?
Padrão ERC165A partir de agora, o mundo está em um novo estágio.
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 a assinatura de funções de uma interface (composta por uma lista de nomes de funções e tipos de parâmetros), para um contrato de interface ERC165 com apenas uma função:
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);
}
O identificador de interface para esta interface é 0x01ffc9a7. Você pode calcular isso executando bytes4 ((keccak256(
supportsInterface(bytes4) )); ou usando o contrato Selector acima.
Então, se você calcular a assinatura de uma função e tirar os primeiros quatro bytes, você teráinterfaceId
。
function main() {
var ret = Encode("keccak256", "string", "hex", "supportsInterface(bytes4)")
Log("supportsInterface(bytes4) interfaceId:", "0x" + ret.slice(0, 8))
}
A ferramenta de depuração pode ser usada para executar testes:
2023-06-13 14:53:35 信息 supportsInterface(bytes4) interfaceId: 0x01ffc9a7
O resultado do cálculo ePadrão ERC165A descrição está de acordo com a documentação.
A seguir, vamos ver a definição de interface do padrão 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);
}
Se quisermos saber se um contrato inteligente é um contrato ERC721, primeiro precisamos saber o que é um contrato ERC721.interfaceId
E então você pode experimentar.supportsInterface(bytes4 interfaceId)
O método de julgamento, no curso anterior já estamos familiarizados com alguns conceitos e cálculos do padrão ERC165.interfaceId
O algoritmo, que nós escrevemos diretamente no código, calcula:
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 "错误:数组中元素个数为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 "错误:TypeArray view长度不同"
}
for (var index = 0; index < ret.length; index++) {
ret[index] ^= viewData[index]
}
}
}
ret = Encode("raw", "raw", "hex", ret.buffer)
}
} else {
throw "错误:参数需要数组类型。"
}
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)
}
Usado no códigoEncode()
Função para cálculo de assinatura de funçãokeccak256
Algoritmo), para o cálculo do exemplo de código acima, especifiqueEncode()
O parâmetro de saída da função é"raw"
A função retornaJavaScript
LínguasArrayBuffer
O que você está fazendo?
Se for para dois.ArrayBuffer
Objetos realizados^
(diferente ou) operação, que precisa ser baseado emArrayBuffer
Criar objetosTypedArray
A visualização, depois percorre os dados, fazendo uma divisão ou uma operação individualmente.
A ferramenta de depuração é executada:
2023-06-13 15:04:09 信息 0x80ac58cd
Podemos ver os resultados eeip-721O que você está fazendo é errado.
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);
...
Com o ID de interface do ERC721, podemos determinar se um contrato é ou não um contrato do padrão ERC721.BAYC
Para fazer o teste, este é um contrato que segue o ERC721, primeiro precisamos registrar o ABI, e como só chamamos os três métodos seguintes, só podemos registrar esses três métodos:
O código específico é o seguinte:
function main() {
// ERC721的合约地址,这里用的BAYC
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接口Id,在之前的课程中计算得出
var interfaceId = "0x80ac58cd"
// 注册ABI
exchange.IO("abi", testContractAddress, testABI)
// 调用supportsInterface方法
var isErc721 = exchange.IO("api", testContractAddress, "supportsInterface", interfaceId)
// 输出信息
Log("合约地址:", testContractAddress)
Log("合约名称:", exchange.IO("api", testContractAddress, "name"))
Log("合约代号:", exchange.IO("api", testContractAddress, "symbol"))
Log("合约是否为ERC721标准:", isErc721)
}
A ferramenta de depuração pode ser usada para executar testes:
2023-06-13 16:32:57 信息 合约是否为ERC721标准: true
2023-06-13 16:32:57 信息 合约代号: BAYC
2023-06-13 16:32:57 信息 合约名称: BoredApeYachtClub
2023-06-13 16:32:57 信息 合约地址: 0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d
Determine o endereço0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d
O contrato é para o ERC721.
Nesse artigo, descrevemos como julgar um contrato ERC721, então um contrato ERC20 que não suporta o padrão ERC165 deve ser identificado de outra forma. Você sabe como verificar se um contrato é ERC20?
O que é?calldata
O que você acha da descrição simples e popular aqui?
"Calldata" é a codificação de uma função chamada ou parâmetro no Ethereum, "calldata" é codificada de acordo com a especificação ABI (Application Binary Interface) do contrato.
Por exemplo, podemos usar o contrato ERC20 que aprendemos no curso anterior.balanceOf
、transfer
A chamada de um método, juntamente com os parâmetros de chamada, é codificada como umcalldata
❖ Em alguns cenários de aplicação, por exemplo:Interação entre contratosA partir de agora, a maioria dos blogueiros não tem acesso ao site.calldata
A partir daí, o Google lançou um novo aplicativo para o Google Maps, que é o primeiro aplicativo do Google para o Google.
Como codificar uma chamada de função de contrato inteligente para obtercalldata
?
A plataforma de negociação quantitativa do inventor pode ser usadaexchange.IO("encode", ...)
O código para chamadas de funções de contratos inteligentes é muito simples de usar.exchange.IO("encode", ...)
O primeiro parâmetro da função é uma string fixa."encode"
; o segundo parâmetro é o endereço do contrato inteligente; o terceiro parâmetro é o nome do método de contrato inteligente a ser codificado; os restantes parâmetros são transmitidos para o valor específico do método de contrato inteligente a ser codificado.
Quando nós codificamos uma chamada para um método de contrato inteligente e geramos o correspondentecalldata
Quando os dados, se este método de contrato inteligente é um método de escrever (ou seja: escrever operações), nós precisamos de gerarcalldata
Dados como campos de dados para transações, e depois usando o método RPC do Ethereumeth_sendRawTransaction
Enviar uma solicitação de dados brutos que contenham a transação para a rede Ethereum.
eth_sendRawTransaction
O método tem apenas um parâmetro.data
:
dados: A transação assinada (normalmente assinada com uma biblioteca, usando a sua chave privada)
Esta édata
Parâmetros são dados de transações calculados após a assinatura. A estrutura de dados de transações do Ethereum tem principalmente os seguintes campos:
{
"nonce": "0x1", // 交易发送方的账户交易次数
"gasPrice": "0x12a05f200", // 交易的Gas价格
"gasLimit": "0x5208", // 交易的Gas限制
"to": "0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2", // 目标合约地址或接收方地址
"value": "0x4563918244F40000", // 转账的以太币数量
"data": "0x0123456789ABCDEF", // 要发送给合约的数据
}
Como é que se assina uma transação Ethereum?
A plataforma de troca de quantidade que usamosEncode()
Uma função para calcular assinaturas, um exemplo específico que escrevemos no curso seguinte "Executar o método de escrita calldata".
Para o método Readcalldata
A partir de agora, o RPC será executado usando o método RPC que aprendemos anteriormente:eth_call
A partir daí, a maioria das pessoas que estão no mercado de trabalho não tem acesso a ele.eth_call
Este método de RPC do Ethereum é apenas um contrato inteligente.Write
Demonstração do método, usado neste capítulocalldata
A forma de demonstrar a execução de um contrato inteligente chamada de método Read.balanceOf
Método para ler o saldo de tokens WETH da carteira atual.
A partir daí, a empresa começou a produzir produtos de alta qualidade para o mercado, com o objetivo de oferecer produtos de alta qualidade para o mercado.
function toAmount(s, decimals) {
return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString())
}
function main() {
// WETH合约的ABI
var abiWETH = `[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"guy","type":"address"},{"name":"wad","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"src","type":"address"},{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"wad","type":"uint256"}],"name":"withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"deposit","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":true,"name":"guy","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":true,"name":"dst","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"dst","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Withdrawal","type":"event"}]`
// WETH合约地址
var wethAddress = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"
// 注册WETH合约的ABI
exchange.IO("abi", wethAddress, abiWETH)
// 当前配置的交易所对象的钱包地址
var walletAddress = exchange.IO("address")
// 编码WETH合约的deposit方法调用
var calldataForDeposit = exchange.IO("encode", wethAddress, "balanceOf(address)", walletAddress)
Log("calldataForDeposit:", "0x" + calldataForDeposit)
// 构造transaction,作为eth_call的第一个参数
var transaction = {
"from" : walletAddress,
"to" : wethAddress,
"data" : "0x" + calldataForDeposit,
}
// eth_call的第二个参数
var blockNumber = "latest"
// 使用eth_call调用
var ret = exchange.IO("api", "eth", "eth_call", transaction, blockNumber)
var wethBalance = exchange.IO("decode", "uint256", ret) // 可以使用exchange.IO("decode", ...) 函数解码
Log("wethBalance:", toAmount(wethBalance, 18)) // 从以wei为单位,换算成WETH个数为单位
}
A ferramenta de depuração é executada:
2023-06-15 11:51:31 信息 wethBalance: 0.015
2023-06-15 11:51:31 信息 calldataForDeposit: 0x70a082310000000000000000000000006b3f11d807809b0b1e5e3243df04a280d9f94bf4
Se o método de um contrato inteligente tiver um valor de retorno, pode ser usado.exchange.IO("decode", ...)
Descifrar funções.calldata
O que é um contrato inteligente?balanceOf
O método é o mesmo, eu tenho um saldo de WETH de 0.015 WETH no meu portfólio de teste.
Para executar calldata do método Write, é necessário usar o método RPC:eth_sendRawTransaction
Não, não.
A partir daí, a empresa começou a produzir produtos de alta qualidade para o mercado, com o objetivo de oferecer produtos de alta qualidade para o mercado.
function toAmount(s, decimals) {
return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString())
}
function toInnerAmount(s, decimals) {
return (BigDecimal(s)*BigDecimal(Math.pow(10, decimals))).toFixed(0)
}
function main() {
// WETH合约的ABI
var abiWETH = `[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"guy","type":"address"},{"name":"wad","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"src","type":"address"},{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"wad","type":"uint256"}],"name":"withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"deposit","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":true,"name":"guy","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed