831ec7”, “1INCH”: “0x111111111117dC0aa78b770fA6A738034120C302”, “USDC”: “0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48”, “DAI”: “0x6b175474e89094c44da98b954eedeac495271d0f”, } for (let name in tokenAddressMap) { ex.addToken(name, tokenAddressMap[name]) }
特定の取引ペアの交換プール価格を取得して印刷したい場合は、次のようにします。`ex`オブジェクトのメンバー関数`getPrice()`次のように書くことができます。
```javascript
Log(ex.getPrice('ETH_USDT'))
Log(ex.getPrice('1INCH_USDT'))
償還操作を実行する場合は、exオブジェクトのメンバー関数swapToken()交換を実行します:
// swap 0.01 ETH to USDT
Log(ex.swapToken('ETH', 0.01, 'USDT'))
let usdtBalance = ex.balanceOf('USDT')
Log("balance of USDT", usdtBalance)
// swap USDT to DAI then DAI to ETH
Log(ex.swapToken('USDT', usdtBalance, 'DAI,ETH'))
この章では、Inventor Quantitative Trading Platform を使用してスマート コントラクトによってリリースされたイベントを読み取る方法を学習します。スマート コントラクトによってリリースされたイベントは、Ethereum 仮想マシンのログに保存されます。
スマートコントラクトによってリリースされたイベントを照会するには、EthereumのRPCメソッドを使用する必要があります。eth_getLogs、オンチェーン ログ データを取得します。Ethereum RPC ノードを呼び出す方法については、前回のコースですでに説明しました。
例えば、WETH契約イベントはFMZを使用してコード化できるデバッグツールテストでは、交換オブジェクトによって構成されたRPCノードは、Ethereumのメインネットワークノードであり、eth_getLogsメソッドを使用する際に3つのパラメータを指定した。fromBlock、toBlock、addressでは、fromBlock および toBlock パラメータを使用して、クエリをブロック内のデータに制限します。
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" // WETH合约的地址
}
var logs = exchange.IO("api", "eth", "eth_getLogs", params)
// 由于数据量比较大,如果使用Log函数打印,数据会被截断。使用return将完整数据返回在页面「函数结果」编辑框中
return logs
}
ログデータを取得します。データが大きいため、一部の内容を省略します。
[{
"data": "0x00000000000000000000000000000000000000000000000001c1a55000000000",
"topics": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x0000000000000000000000006b75d8af000000e20b7a7ddf000ba900b4009a80", "0x000000000000000000000000bcb095c1f9c3dc02e834976706c87dee5d0f1fb6"],
"transactionHash": "0x27f9bf5abe3148169b4b85a83e1de32bd50eb81ecc52e5af006157d93353e4c4",
"transactionIndex": "0x0",
"removed": false,
"address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
"blockHash": "0x847be24a7b159c292bda030a011dfec89487b70e71eed486969b032d6ef04bad",
"blockNumber": "0x109b1cc",
"logIndex": "0x0"
}, {
"data": "0x00000000000000000000000000000000000000000000000008ea20cdea027c00",
"logIndex": "0x5",
"topics": ["0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c", "0x0000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488d"],
"address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
"blockHash": "0x847be24a7b159c292bda030a011dfec89487b70e71eed486969b032d6ef04bad",
"blockNumber": "0x109b1cc",
"removed": false,
"transactionHash": "0xace3afa02e8af5d1ef6fc1635fbdf7bee37624547937ea5272c23968dd034c09",
"transactionIndex": "0x1"
},
...
{
"blockNumber": "0x109b1cd",
"address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
"data": "0x00000000000000000000000000000000000000000000000002c053531ab8a000",
"logIndex": "0xd3",
"removed": false,
"topics": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x0000000000000000000000001111111254eeb25477b68fb85ed929f73a960582", "0x000000000000000000000000252ba9b5916171dbdadd2cec7f91875a006955d0"],
"transactionHash": "0x3012b82891f85b077cfe1c12cb9722b93c696ef2c37d67981ccddcc9c3396aca",
"transactionIndex": "0x8d",
"blockHash": "0xcd3d567c9bd02a4549b1de0dc638ab5523e847c3c156b096424f56c633000fd9"
}, {
"topics": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x00000000000000000000000012b791bb27b3a4ee958b5a435fea7d49ec076e9c", "0x000000000000000000000000ef1c6e67703c7bd7107eed8303fbe6ec2554bf6b"],
"transactionIndex": "0x91",
"logIndex": "0xdb",
"removed": false,
"blockNumber": "0x109b1cd",
"data": "0x0000000000000000000000000000000000000000000000000164f2434262e1cc",
"transactionHash": "0x6aa8d80daf42f442591e7530e31323d05e1d6dd9f9f9b9c102e157d89810c048",
"address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
"blockHash": "0xcd3d567c9bd02a4549b1de0dc638ab5523e847c3c156b096424f56c633000fd9"
}, {
"address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
"blockHash": "0xcd3d567c9bd02a4549b1de0dc638ab5523e847c3c156b096424f56c633000fd9",
"blockNumber": "0x109b1cd",
"logIndex": "0xde",
"removed": false,
"topics": ["0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65", "0x000000000000000000000000ef1c6e67703c7bd7107eed8303fbe6ec2554bf6b"],
"data": "0x0000000000000000000000000000000000000000000000000164f2434262e1cc",
"transactionHash": "0x6aa8d80daf42f442591e7530e31323d05e1d6dd9f9f9b9c102e157d89810c048",
"transactionIndex": "0x91"
}]
ログデータにはさまざまなイベントがあることがわかります。TransferイベントではこれらのデータをTransferイベントは除外されます。
イーサリアムのログは2つの部分に分かれています: 1. トピックtopics2. データdata。
topics
によるeth_getLogs章のテストのコード実行結果を例に挙げます。topicsフィールドのデータは次のとおりです。 "topics": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x00000000000000000000000012b791bb27b3a4ee958b5a435fea7d49ec076e9c", "0x000000000000000000000000ef1c6e67703c7bd7107eed8303fbe6ec2554bf6b"],
これtopics(トピック) フィールドの値は、イベントを記述する配列構造です。配列の長さは 4 を超えないことが規定されており、最初の要素はイベントの署名ハッシュ値です。
Inventor定量取引プラットフォームでは、Encodeこの関数は、次のコードを使用して署名ハッシュ値を計算できます。
function main() {
var eventFunction = "Transfer(address,address,uint256)"
var eventHash = Encode("keccak256", "string", "hex", eventFunction)
Log("eventHash:", "0x" + eventHash)
// eventHash: 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
}
計算されたTransfer(address,address,uint256)のkeccak256ハッシュ値(16進数エンコード)は0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef。
topicsフィールドの値は配列構造であり、2 番目の要素と 3 番目の要素は次のとおりです。
送信先住所from
受取住所to
データdata
dataフィールドのデータは次のとおりです。
"data": "0x0000000000000000000000000000000000000000000000000164f2434262e1cc",
イベント内の一部のパラメータ(スマートコントラクトのSolidityコードでインデックス宣言されていないパラメータ)は、data一部。
このデータを解析する0x0000000000000000000000000000000000000000000000000164f2434262e1cc
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
}
データ: 0.10047146239950075、dataデータは振込金額に対応します。
上記の内容を説明して実践したら準備完了です。ログの取得を開始できます。
function toAmount(s, decimals) {
return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString())
}
function toInnerAmount(n, decimals) {
return (BigDecimal(n) * BigDecimal(Math.pow(10, decimals))).toFixed(0)
}
function main() {
// 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)
// 遍历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++
}
}
}
存在するhttps://etherscan.io/クエリについて:

FMZ デバッグ ツールでテスト コードを実行した結果:

検索のニーズに応じて解析することもできるfrom,toフィールドのデータ、例:
function main() {
var from = "0x00000000000000000000000012b791bb27b3a4ee958b5a435fea7d49ec076e9c"
var address = "0x" + exchange.IO("encodePacked", "address", from)
Log("address:", address)
}
操作結果:
address: 0x12b791bb27b3a4ee958b5a435fea7d49ec076e9c
なぜならデバッグツールコードのテストは短時間しかできず、内容はコード実行後にのみ出力されます。リアルタイムでログを表示・出力することは不可能です。このセクションでは、Inventor Quantitative Trading Platform を使用して、テスト用の実際のアカウントを作成します。
ここではイーサリアムメインネットを使って監視してみましょうUSDTこの通貨契約Transfer(address,address,uint256)イベントでは、前回の授業で学んだことを基に、スマート コントラクト イベントを継続的に監視する例を設計して記述しました。
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("结束运行,验证记录")
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])
}
}
*/
実際のディスク操作:

実行結果については、検証部分(TODO:テスト)もコード内に記述します。簡単な検証の後、USDT 契約が継続的に監視されていることがわかります。Transferイベントが記録され、データが記録されます。このデータを一度取得したイベント データと比較すると、データが一貫していることがわかります。

前回のレッスン「契約イベントの監視」に基づいて、監視プロセスにフィルターを追加して、指定されたアドレスへの転送と指定されたアドレスからの転送を監視することで、これを拡張します。スマートコントラクトがログを作成する(つまりイベントをリリースする)と、ログデータは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]いずれか1つとtopics[0]一致が見つかった場合、ログはフィルタリングされません。取引所でのUSDTの送金を監視する
Binanceとの間の送金を監視するUSDT取引:
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)
}
}
上記のコードは実際のディスクで実行されます。


このレッスンでは、イベント フィルターの設計方法を紹介しました。そして、Binance Exchangeのホットウォレットを監視するために使用しましたUSDT貿易。このサンプル プログラムを変更および拡張して、関心のあるイベントをリッスンすることができます。smart moneyどのような新しい取引が行われましたか?NFT大物たちはどんな新しいプロジェクトに取り組んでいるのか、など。
多くのイーサリアム関連の計算では、値がJavaScript言語の最大安全整数。したがって、Inventor Quantitative Trading Platform で大量の数値を処理するには、いくつかの方法が必要です。これらの方法は以前のコースで使用しましたが、詳細には説明していませんでした。この章では、この点について詳しく説明します。
印刷JavaScript言語で定義されている最大の安全な整数:
function main() {
Log("Number.MAX_SAFE_INTEGER:", Number.MAX_SAFE_INTEGER)
}
操作結果:
Number.MAX_SAFE_INTEGER: 9007199254740991
イーサリアムで定義されている最小単位は1wei、意味1Gwei等しい1000000000 wei。1Gwei実は、イーサリアム関連の計算ではそれほど大きな数字ではありません。一部のデータはこれよりはるかに大きいです。すると、非常に大きな値を持つこれらのデータは簡単に超過する可能性がありますNumber.MAX_SAFE_INTEGER: 9007199254740991。
Inventor Quantitative Trading Platformでは、プラットフォームのBigIntこれらの非常に大きな整数データを表すためにオブジェクトが使用されます。コンストラクタの使用BigInt()構築するBigInt物体。数値または16進数値文字列をパラメータとして使用して構築できますBigInt物体。使用BigInt物体toString()このメソッドは、オブジェクトによって表されるデータを文字列として出力します。
BigIntオブジェクトでサポートされる操作:
+-*/%**次のコード例を参照してください。
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))
}
デバッグツールテスト:
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オブジェクトとBigIntオブジェクトも同様に使用され、より大きな値の浮動小数点数を表すために使用されます。また、加算、減算、乗算、除算の演算もサポートしています。
BigFloatオブジェクトのサポートtoFixed()方法。
次のコード例を参照してください。
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))
}
デバッグツールテスト:
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このオブジェクトは整数値と浮動小数点値と互換性があり、BigIntオブジェクト、BigFloatオブジェクトの初期化では、加算、減算、乗算、除算の演算もサポートされます。
次のコード例を参照してください。
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())
}
デバッガーで実行します:
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
次の 2 つの機能:toAmount()、toInnerAmount()これら 2 つの関数は、以前のコースで何度も使用しました。これらは主に、データの精度変換に使用されます。
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()この関数は変数を変換するs精度パラメータに応じてdecimals(縮小)変換を実行します。実際、web3 の開発では、チェーン上で 16 進数データを処理する必要がある場合がよくあります。
スマート コントラクトなど、これまでのコースでこれによく遭遇します。Transfer(address,address,uint256)イベントではdataフィールドデータ:
{
"data": "0x00000000000000000000000000000000000000000000000001c1a55000000000",
"topics": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x0000000000000000000000006b75d8af000000e20b7a7ddf000ba900b4009a80", "0x000000000000000000000000bcb095c1f9c3dc02e834976706c87dee5d0f1fb6"],
"transactionHash": "0x27f9bf5abe3148169b4b85a83e1de32bd50eb81ecc52e5af006157d93353e4c4",
"transactionIndex": "0x0",
"removed": false,
"address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
"blockHash": "0x847be24a7b159c292bda030a011dfec89487b70e71eed486969b032d6ef04bad",
"blockNumber": "0x109b1cc",
"logIndex": "0x0"
}
データ処理"data": "0x00000000000000000000000000000000000000000000000001c1a55000000000"、今回はtoAmount()関数。この処理設計はdataフィールド データを読み取り可能な数値に変換します。
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
}
私たちが知っている1ETHトークンは1e18 weiもし私たちがwei単位のデータ126564027559051260それをETHトークンの数に変換するにはどうすればいいですか?
使用toAmount(, 18)関数は簡単に変換できます。toInnerAmount()機能はtoAmount()関数の逆操作(精度、増幅に応じて)この 2 つの関数を使用すると、データを変換するのに非常に便利です。
JavaScript言語における整数値の安全な範囲はNumber.MAX_SAFE_INTEGER次の例は、データ変換におけるより隠れた問題を示しています。
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))
}
デバッガーで実行できます:
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
観察を通じて次のことがわかりました。
Log("转换", innerAmount, "为十六进制:", innerAmount.toString(16))
このコード行に対応するログ出力は次のとおりです。转换 10000000000000000 为十六进制: 10000000000000000が正しく変換されませんでした。理由は当然100000000000000000を超えるからですNumber.MAX_SAFE_INTEGER。
ただし、小数値が安全範囲内、つまりNumber.MAX_SAFE_INTEGER時間、toString(16)関数変換は再び正常になります。例:
function main() {
var value = 1000
Log("把value转换为十六进制:", "0x" + value.toString(16)) // 0x3e8
Log("把0x3e8转换为十进制:", Number("0x3e8")) // 1000
}
ブロックチェーンの分野でも0.01ETHに変換weiユニットの値10000000000000000また、Number.MAX_SAFE_INTEGERしたがって、この状況でのより安全な変換は次のようになります。BigInt(10000000000000000).toString(16)。
Ethereumでトランザクションを実行し、スマートコントラクトを呼び出すWriteこの方法は一定量のガスを消費し、失敗するリスクもあります。トランザクションを送信または呼び出す前に、どのトランザクションが失敗する可能性があるかを把握することが重要です。 Ethereum にはテスト用のシミュレートされた呼び出しメソッドがあります。
イーサリアムのRPC方式eth_call:トランザクションをシミュレートし、可能なトランザクション結果を返すことはできますが、ブロックチェーン上で実際にトランザクションを実行することはありません。
eth_callこのメソッドには2つのパラメータがあり、最初のパラメータは辞書構造です。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
}
2番目のパラメータはblockNumber: タグを渡すことができるlatest/pending/earliest待って:
/* 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
*/
次にトークンを使用しますDAIスマートコントラクト方式approve、transfer通話は模擬通話の一例です。以下のテスト環境はEthereumメインネットワークです。
ERC20契約の場合approve私たちはすでにこの方法に精通しており、以前のコースで練習しました。 ERC20 コントラクトはすでに FMZ プラットフォーム ABI に組み込まれているため、シミュレートするスマート コントラクトの ABI を登録する必要はありません。
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)
}
この例のコードはまずapprove(address,uint256)メソッドとパラメータはエンコードされます。approveメソッドパラメータ値0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff承認の最大数を示します。スマートコントラクト、アドレスを承認する0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45今すぐUniswap V3ルーティング契約。最後にEthereum RPCメソッドを呼び出すeth_callシミュレーションを実行します。見ることができますtransactionObjectパラメータgasPrice、gasフィールドは省略できます。
デバッグ ツールが実行され、approve メソッドの呼び出しによる正常な承認をシミュレートします (ただし、実際には承認されません)。
2023-06-09 11:58:39 信息 ret: 0x0000000000000000000000000000000000000000000000000000000000000001
2023-06-09 11:58:39 信息 ERC20 token DAI approve encode, data: 095ea7b300000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc45ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
失敗のシナリオをシミュレーションすることもできます。gasPriceそしてgasパラメータを設定する際に、ウォレット内の ETH がガス料金を支払うのに十分でない場合はエラーが報告されます。
insufficient funds
ガス料金が低すぎると、エラーが報告されます。
intrinsic gas too low: have 21000, want 21944 (supplied gas 21000)
ERC20の場合transferこの方法も私たちもよく知っています。この方法では、ERC20トークンを特定のウォレットアドレスに転送できます。1,000 DAIを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")
// 转账给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
}
私のテストウォレットには DAI トークンがないので、デバッグツールで実行した結果は予想どおりで、エラーが報告されます。
execution reverted: Dai/insufficient-balance
Vitalikのウォレットアドレスを確認してください:0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045このウォレットにはDAIトークンが含まれていることがわかります。次に、シミュレートされた通話の転送方向を交換し、Vitalik が 1,000 DAI を私たちに転送することをシミュレートしてみましょう。
コードを変更し、変更した箇所にコメントを追加します。
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)
}
デバッグツールテスト:
2023-06-09 13:34:31 信息 0x0000000000000000000000000000000000000000000000000000000000000001
2023-06-09 13:34:31 信息 转账金额: 1000 DAI, 使用 toInnerAmount 转换为: 1000000000000000000000
Inventor Quantitative Trading Platform を使用すると、トランザクションの結果を簡単にシミュレートし、失敗する可能性のあるトランザクションの送信によって発生する不必要なガス料金の損失を回避できます。この章のサンプル コードを使用して、Vitalik のウォレットへの送金と Vitalik のウォレットから私たちに送金する呼び出しをシミュレートします。もちろん、これはeth_callメソッドにはさらに多くの用途があります。想像力を働かせればeth_callこの方法はどこで使用されていますか?
ETH や BTC などのトークンはすべて同質のトークンであることはわかっています。あなたの手にあるトークンは、私の手にあるトークンと何ら変わりません。しかし、世の中には不動産や骨董品、仮想アート作品など、均質なトークンで抽象的に表現することができない、性質の異なるものが数多く存在します。そこで、非均質なオブジェクトを抽象化するためにERC721規格が作成され、NFTや関連する概念が誕生しました。 では、Ethereum に展開されている多数のスマート コントラクトの中で、どのスマート コントラクトが ERC721 標準のスマート コントラクトであるかをどのように識別するのでしょうか?
ERC721 を識別するには、まず ERC165 標準を理解する必要があります。
ERC165 標準を通じて、スマート コントラクトは、他のコントラクトがチェックできるようにサポートするインターフェイスを宣言できます。 ERC165 インターフェース コントラクトには 1 つの機能しかありません。supportsInterface(bytes4 interfaceId)、パラメータinterfaceId照会するインターフェース ID。コントラクトがインターフェース Id を実装している場合はブール値の true 値を返し、そうでない場合は false 値を返します。
次はこれについてお話ししますinterfaceId具体的に計算してエンコードする方法。
ERC165 標準例を挙げます。
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;
}
}
インターフェースの関数シグネチャ(関数名とパラメータ型リストで構成)に対して XOR 演算を実行します。関数が 1 つだけの ERC165 インターフェース コントラクトの場合:
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);
}
The interface identifier for this interface is 0x01ffc9a7. You can calculate this by running bytes4(keccak256(‘supportsInterface(bytes4)’)); or using the Selector contract above.
関数シグネチャを直接計算し、最初の4バイトを取得してinterfaceId。
function main() {
var ret = Encode("keccak256", "string", "hex", "supportsInterface(bytes4)")
Log("supportsInterface(bytes4) interfaceId:", "0x" + ret.slice(0, 8))
}
デバッガーでテストを実行できます。
2023-06-13 14:53:35 信息 supportsInterface(bytes4) interfaceId: 0x01ffc9a7
計算結果を見ることができ、ERC165 標準文書内の説明は一貫しています。
次に、ERC721 コントラクト標準のインターフェース定義を見てみましょう。
”`solidity 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 re