0000000000164f2434262e1cc",
We can see that there are various events in the logs data, if we only care about ```Transfer``` events, we need to filter out the ```Transfer``` events in these data.
### Retrieving Logs
The Ethereum log is divided into two parts: 1. ```topics```; 2. ```data```.
- ```topics```
Taking the results of the code run for the ```eth_getLogs``` section test as an example, the data in the ```topics``` field is:
```desc
"topics": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x00000000000000000000000012b791bb27b3a4ee958b5a435fea7d49ec076e9c", "0x000000000000000000000000ef1c6e67703c7bd7107eed8303fbe6ec2554bf6b"],
Giá trị củatopics
trường là một cấu trúc mảng được sử dụng để mô tả sự kiện. Nó được chỉ định rằng chiều dài (mảng) của nó không thể vượt quá 4 và phần tử đầu tiên là ký hiệu hash của sự kiện.
Trong nền tảng giao dịch lượng tử FMZ, chúng ta có thể tính hash chữ ký này bằng cách sử dụngEncode
chức năng, sử dụng mã sau:
function main() {
var eventFunction = "Transfer(address,address,uint256)"
var eventHash = Encode("keccak256", "string", "hex", eventFunction)
Log("eventHash:", "0x" + eventHash)
// eventHash: 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
}
Tính toánkeccak256
giá trị băm (đã mã hóa hex) củaTransfer(address,address,uint256)
là0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
.
Giá trị củatopics
trường là một cấu trúc mảng, với phần tử thứ hai, và phần tử thứ ba, tương ứng:
Địa chỉ gửifrom
Địa chỉ nhậnto
data
Dữ liệu trongdata
trường là:
"data": "0x0000000000000000000000000000000000000000000000000164f2434262e1cc",
Một số tham số trong trường hợp (các tham số không có tuyên bố lập chỉ mục trong mã Solidity của hợp đồng thông minh) được lưu trữ trongdata
section.
Phân tích dữ liệu0x0000000000000000000000000000000000000000000000000164f2434262e1cc
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
}
Dữ liệu này được lấy như 0,10047146239950075 vàdata
là số tiền chuyển nhượng tương ứng.
Những điều trên đã được giải thích, thực hành và sẵn sàng để đi.
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++
}
}
}
Kiểm tra đi.https://etherscan.io/
:
Kết quả của mã thử nghiệm chạy trong công cụ gỡ lỗi FMZ:
Dữ liệu trongfrom
, to
Các trường cũng có thể được phân tích tùy thuộc vào nhu cầu tại thời điểm truy xuất, ví dụ:
function main() {
var from = "0x00000000000000000000000012b791bb27b3a4ee958b5a435fea7d49ec076e9c"
var address = "0x" + exchange.IO("encodePacked", "address", from)
Log("address:", address)
}
Kết quả chạy:
Địa chỉ: 0x12b791bb27b3a4ee958b5a435fea7d49ec076e9c
Kể từ khicông cụ gỡ lỗitrong phần này, chúng tôi sử dụng nền tảng giao dịch FMZ Quant để tạo giao dịch trực tiếp để kiểm tra.
Ở đây chúng ta sử dụng Ethereum mainnet, và chúng ta lắng ngheTransfer(address,address,uint256)
sự kiện củaUSDT
Dựa trên những gì chúng tôi đã học được trong bài học trước, chúng tôi đã thiết kế và viết một ví dụ liên tục lắng nghe các sự kiện của một hợp đồng thông minh nhất định:
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])
}
}
*/
Chạy trên giao dịch trực tiếp:
Đối với kết quả thực hiện, một phần xác thực (TODO: test) cũng được viết trong mã.Transfer
sự kiện của hợp đồng USDT được theo dõi liên tục và dữ liệu được ghi lại và so sánh giữa dữ liệu này và dữ liệu sự kiện thu được một lần có thể quan sát thấy rằng dữ liệu phù hợp với:
Dựa trên bài học trướcListening to contract events
, chúng ta mở rộng nó bằng cách thêm các bộ lọc vào quá trình nghe để nghe chuyển giao đến và từ các địa chỉ được chỉ định.topics
Vì vậy, chúng tôi thiết kế một quy tắc lọc với[[A1, A2, ...An], null, [C1], D]
ví dụ.
[A1, A2, ...An]
tương ứng với dữ liệu tại vị trítopics[0]
.Null
tương ứng với dữ liệu tại vị trítopics[1]
.[C1]
tương ứng với dữ liệu tại vị trítopics[2]
.D
tương ứng với dữ liệu tại vị trítopics[3]
.null
có nghĩa là nó không được lọc, ví dụ:null
tương ứng vớitopics[1]
và bất kỳ sự khớp giá trị.[C1]
tương ứng vớitopics[2]
hoặcD
tương ứng vớitopics[3]
, và các nhật ký không phù hợp được lọc.[A1, A2, ...An]
tương ứng vớitopics[0]
, [A1, A2, ...An]
với bất kỳ một trong số họ phù hợptopics[0]
, thì các bản ghi sẽ không được lọc.Nghe chuyển tiền USDT từ sàn giao dịch
Giám sátUSDT
các giao dịch được chuyển từ và sang sàn giao dịch 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)
}
}
Mã trên được chạy trong giao dịch trực tiếp:
Trong bài học này, chúng tôi đã giới thiệu cách thiết kế bộ lọc sự kiện.USDT
Bạn có thể sửa đổi và mở rộng chương trình mẫu này để nghe bất kỳ sự kiện nào bạn quan tâm, để xem những giao dịch mớismart money
đã thực hiện, những gì các mặt hàng mớiNFT
Các ông trùm đã vội vàng, v.v.
Nhiều tính toán liên quan đến Ethereum có giá trị vượt quá số nguyên an toàn tối đa củaJavaScript
Do đó, một số phương pháp cần thiết trên nền tảng giao dịch lượng tử FMZ để xử lý các giá trị lớn, mà chúng tôi đã sử dụng cụ thể trong các khóa học trước đây và đã không đề cập chi tiết.
In số nguyên an toàn tối đa được xác định trongJavaScript
ngôn ngữ:
function main() {
Log("Number.MAX_SAFE_INTEGER:", Number.MAX_SAFE_INTEGER)
}
Kết quả chạy:
Số.MAX_SAFE_INTEGER: 9007199254740991
Đơn vị nhỏ nhất được định nghĩa trong Ethereum là1wei
, và định nghĩa1Gwei
bằng với1000000000 wei
. 1Gwei
không thực sự là một số rất lớn trong các tính toán liên quan đến Ethereum, và một số dữ liệu lớn hơn nhiều.Number.MAX_SAFE_INTEGER: 9007199254740991
.
Tại FMZ Quant Trading Platform, chúng tôi sử dụng các nền tảngBigInt
sử dụng các cấu trúcBigInt()
để xây dựngBigInt
Bạn có thể xây dựngBigInt
đối tượng sử dụng số, chuỗi số hexadecimal như các tham số.toString()
phương phápBigInt
đối tượng để xuất dữ liệu được đại diện bởi đối tượng dưới dạng chuỗi.
Các hoạt động được hỗ trợ bởiBigInt
Mục tiêu là:
+
-
*
/
%
*
Xem các ví dụ mã sau:
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))
}
Kiểm tra công cụ gỡ lỗi:
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
CácBigFloat
đối tượng được sử dụng tương tự nhưBigInt
đối tượng để đại diện cho các số dấu phẩy động với các giá trị lớn hơn, và nó cũng hỗ trợ cộng, trừ, nhân và chia.
CácBigFloat
đối tượng hỗ trợtoFixed()
method.
Xem ví dụ mã sau:
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))
}
Kiểm tra công cụ gỡ lỗi:
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
CácBigDecimal
object tương thích với giá trị số nguyên và giá trị dấu phẩy động, và hỗ trợ khởi tạo vớiBigInt
đối tượng vàBigFloat
và nó cũng hỗ trợ cộng, trừ, nhân và chia.
Xem ví dụ mã sau:
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())
}
Chạy trong công cụ gỡ lỗi:
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
Hai chức năng sau:toAmount()
, toInnerAmount()
chúng tôi đã sử dụng nhiều lần trong các khóa học trước đây, hai chức năng này chủ yếu được sử dụng cho chuyển đổi chính xác dữ liệu.
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)
}
CáctoAmount()
hàm chuyển đổi (giảm) một biếns
theo tham số độ chính xácdecimals
Trong phát triển thực tế web3, nó thường là cần thiết để đối phó với một số dữ liệu hexadecimal chuỗi.
Chúng tôi đã gặp phải điều này thường xuyên trong các khóa học trước đây của chúng tôi, ví dụ:data
dữ liệu thực địa trongTransfer(address,address,uint256)
sự kiện hợp đồng thông minh:
{
"data": "0x00000000000000000000000000000000000000000000000001c1a55000000000",
"topics": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x0000000000000000000000006b75d8af000000e20b7a7ddf000ba900b4009a80", "0x000000000000000000000000bcb095c1f9c3dc02e834976706c87dee5d0f1fb6"],
"transactionHash": "0x27f9bf5abe3148169b4b85a83e1de32bd50eb81ecc52e5af006157d93353e4c4",
"transactionIndex": "0x0",
"removed": false,
"address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
"blockHash": "0x847be24a7b159c292bda030a011dfec89487b70e71eed486969b032d6ef04bad",
"blockNumber": "0x109b1cc",
"logIndex": "0x0"
}
Khi xử lý dữ liệu"data": "0x00000000000000000000000000000000000000000000000001c1a55000000000"
, chúng ta sử dụngtoAmount()
Điều này xử lý được thiết kế để làm một công việc tốt của việc chuyển đổi dữ liệu trường dữ liệu vào giá trị có thể đọc.
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, như chúng ta biết, là1e18 wei
, nếu chúng ta có được một dữ liệu126564027559051260
trongwei
, làm thế nào để chuyển đổi nó thành token ETH?
Sử dụngtoAmount(, 18)
hàm là một phương pháp chuyển đổi rất đơn giản.toInnerAmount()
chức năng là hoạt động ngược lại củatoAmount()
chức năng (tùy thuộc vào độ chính xác, phóng to), và nó là dễ dàng để chuyển đổi dữ liệu bằng cách sử dụng hai chức năng này.
Điều quan trọng cần lưu ý là phạm vi an toàn giá trị số nguyên trong ngôn ngữ JavaScript,Number.MAX_SAFE_INTEGER
, và ví dụ sau đây minh họa một vấn đề ẩn khi chuyển đổi dữ liệu:
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))
}
Có thể chạy trong công cụ gỡ lỗi:
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
Thông qua quan sát, chúng tôi thấy rằng:
Log("Convert", innerAmount, "to hexadecimal:", innerAmount.toString(16))
Dòng mã này tương ứng với đầu ra log:Converting 10000000000000000 to hex: 10000000000000000
, mà không được chuyển đổi chính xác. Lý do là tự nhiên rằng 10000000000000000 là ngoàiNumber.MAX_SAFE_INTEGER
.
Nhưng khi giá trị thập phân nằm trong phạm vi an toàn, tức là, nhỏ hơnNumber.MAX_SAFE_INTEGER
, cáctoString(16)
hàm chuyển đổi nó một cách chính xác một lần nữa, ví dụ:
function main() {
var value = 1000
Log("Convert value to hexadecimal:", "0x" + value.toString(16)) // 0x3e8
Log("Convert 0x3e8 to decimal:", Number("0x3e8")) // 1000
}
Trong blockchain, thậm chí0.01
ETH chuyển đổi thành giá trị10000000000000000
trongwei
sẽ vượt quáNumber.MAX_SAFE_INTEGER``, so a safer conversion for such cases is:
BigInt ((100000000000000000000).toString ((16) ``.
Thực hiện các giao dịch và gọiWrite
Phương pháp hợp đồng thông minh trên Ethereum tốn một lượng khí đốt nhất định và đôi khi nó thất bại.
Phương pháp RPC của Ethereumeth_call
: nó có thể mô phỏng một giao dịch và trả về kết quả của một giao dịch có thể xảy ra, nhưng nó không thực sự thực hiện giao dịch trên blockchain.
Cáceth_call
phương pháp có 2 tham số, một là một cấu trúc từ điển,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
}
Các thông số thứ hai làblockNumber
: bạn có thể vượt qua nhãnlatest/pending/earliest
, vv:
/* 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
*/
Tiếp theo, chúng ta lấy phương pháp hợp đồng thông minhapprove
vàtransfer
gọi của tokenDAI
như một ví dụ cho các cuộc gọi mô phỏng, và môi trường thử nghiệm sau đây là mạng Ethereum chính.
Chúng ta đều quen thuộc vớiapprove
Các phương pháp này được sử dụng trong các khóa học trước đây, vì hợp đồng ERC20 đã được tích hợp vào nền tảng FMZ ABI, không cần phải đăng ký ABI của hợp đồng thông minh để được gọi bởi mô phỏng.
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)
}
Mã trong ví dụ đầu tiên mã hóaapprove(address,uint256)
phương pháp và tham số, và giá trị tham số0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
củaapprove
Các hợp đồng thông minh được cung cấp tại địa chỉ0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45
tức là hợp đồng router choUniswap V3
Cuối cùng là phương pháp Ethereum RPCeth_call
bạn có thể thấy rằng cácgasPrice
vàgas
các lĩnh vực trongtransactionObject
các tham số có thể bị bỏ qua.
Công cụ gỡ lỗi được chạy và mô phỏng gọi phương thức phê duyệt để ủy quyền thành công (nó không thực sự ủy quyền):
2023-06-09 11:58:39 Info ret: 0x0000000000000000000000000000000000000000000000000000000000000001
2023-06-09 11:58:39 Info ERC20 token DAI approve encode, data: 095ea7b300000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc45ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
Nó cũng có thể mô phỏng một số kịch bản thất bại, khi chúng ta điều chỉnhgasPrice
vàgas
các thông số, nếu ETH trong ví không đủ để thanh toán phí xăng, một lỗi sẽ được báo cáo::
không đủ tiền
Khi giá xăng được đặt quá thấp, một lỗi sẽ được báo cáo:
khí nội tại quá thấp: có 21000, muốn 21944 (được cung cấp khí 21000)
Chúng tôi quen thuộc với ERC20stransfer
phương pháp, cho phép bạn chuyển token ERC20 đến một địa chỉ ví nhất định, vì vậy hãy thử mô phỏng chuyển 1000 DAI đến 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
}
Vì tôi không có token DAI trong ví thử nghiệm này, chạy nó trong công cụ gỡ lỗi báo cáo lỗi sau một cách bất ngờ:
thực hiện đảo ngược: Dai/không đủ số dư
Kiểm tra địa chỉ ví của Vitalik Buterin:0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
Vì vậy, hãy điều chỉnh hướng chuyển giao của cuộc gọi mô phỏng và mô phỏng việc chuyển giao 1000 DAI từ Vitalik Buterin cho chúng tôi.
Thay đổi mã, nơi những thay đổi tôi đã thực hiện bình luận:
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)
}
Kiểm tra công cụ gỡ lỗi:
2023-06-09 13:34:31 Info 0x0000000000000000000000000000000000000000000000000000000000000001
2023-06-09 13:34:31 Info Transfer amount: 1000 DAI, use toInnerAmount convert to: 1000000000000000000000
Sử dụng nền tảng giao dịch FMZ Quant, rất dễ dàng mô phỏng kết quả giao dịch và tránh mất phí khí không cần thiết từ việc gửi các giao dịch có khả năng thất bại. Chúng tôi đã sử dụng mã ví dụ từ chương này của khóa học để mô phỏng cuộc gọi chuyển tiền vào ví của Vitalik Buterin và ví của Vitalik Buterin để chuyển tiền cho chúng tôi.eth_call
Sử dụng trí tưởng tượng của bạn, những gì bạn sẽ sử dụngeth_call
phương pháp cho?
Chúng ta biết rằng các token như ETH và BTC là các token đồng nhất, và token trong ví của bạn không khác với token trong ví của tôi. Nhưng có nhiều thứ trên thế giới không đồng nhất, chẳng hạn như bất động sản, đồ cổ, tác phẩm nghệ thuật ảo, v.v. Chúng không thể được đại diện bằng các token đồng nhất trong trừu tượng. Do đó, có tiêu chuẩn ERC721 để trừu tượng các đối tượng không đồng nhất, và có NFT và các khái niệm liên quan. Vì vậy, trong số nhiều hợp đồng thông minh được triển khai trên Ethereum, làm thế nào để chúng ta xác định hợp đồng thông minh nào là hợp đồng thông minh tiêu chuẩn ERC721?
Để xác định ERC721, điều quan trọng là phải biết tiêu chuẩn ERC165 trước.
Với tiêu chuẩn ERC165, một hợp đồng thông minh có thể tuyên bố các giao diện mà nó hỗ trợ cho các hợp đồng khác để kiểm tra.supportsInterface(bytes4 interfaceId)
, tham sốinterfaceId
là ID giao diện để được truy vấn. Nếu hợp đồng thực hiện interfaceId trả về một giá trị đúng boolean, nếu không nó trả về một giá trị sai.
Ở đây chúng ta sẽ nói về cách nàyinterfaceId
được tính toán và mã hóa cụ thể.
Tiêu chuẩn ERC165cho thấy một ví dụ:
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;
}
}
Đối với chữ ký chức năng của giao diện (bao gồm tên chức năng và danh sách các loại tham số) để thực hiện một hoạt động không giống nhau, đối với hợp đồng giao diện ERC165 khi hợp đồng chỉ có một chức năng:
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);
}
Mã nhận diện giao diện cho giao diện này là 0x01ffc9a7. Bạn có thể tính toán bằng cách chạy bytes4 ((keccak256 ((
supportsInterface ((bytes4) )); hoặc sử dụng hợp đồng Selector ở trên.
Tính toán chữ ký hàm trực tiếp và lấy 4 byte đầu tiên của nó để đếninterfaceId
.
function main() {
var ret = Encode("keccak256", "string", "hex", "supportsInterface(bytes4)")
Log("supportsInterface(bytes4) interfaceId:", "0x" + ret.slice(0, 8))
}
Các thử nghiệm có thể được chạy trong công cụ gỡ lỗi tại:
2023-06-13 14:53:35 Info supportsInterface(bytes4) interfaceId: 0x01ffc9a7
Có thể thấy rằng kết quả tính toán phù hợp với mô tả trongTiêu chuẩn ERC165 document.
Tiếp theo hãy xem định nghĩa giao diện của tiêu chuẩn hợp đồng 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);
}
Nếu chúng ta muốn xác định xem một hợp đồng thông minh là một hợp đồng ERC721, trước tiên chúng ta cần biếtinterfaceId
của hợp đồng ERC721 trước khi chúng ta có thể cố gắng sử dụngsupportsInterface(bytes4 interfaceId)
Trong các khóa học trước đây, chúng tôi đã làm quen với một số khái niệm của tiêu chuẩn ERC165 và thuật toán để tính toáninterfaceId
, và chúng ta viết mã để tính toán trực tiếp:
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)
}
Mã sử dụngEncode()
chức năng để tính toán chữ ký chức năng (thekeccak256
thuật toán), và để tính toán trong ví dụ mã ở trên, xác định các thông số đầu ra củaEncode()
chức năng như"raw"
, hàm trả vềArrayBuffer
loạiJavaScript
ngôn ngữ.
Để thực hiện một^
(iso-or) hoạt động trên haiArrayBuffer
đối tượng, bạn cần phải tạo ra mộtTypedArray
quan điểm dựa trênArrayBuffer
đối tượng, sau đó lặp lại thông qua các dữ liệu trong đó và thực hiện các hoạt động iso-or một một.
Chạy trong công cụ gỡ lỗi:
2023-06-13 15:04:09 Info 0x80ac58cd
Có thể thấy rằng các kết quả tính toán phù hợp với những kết quả được mô tả trongeip-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);
...
Với ID giao diện ERC721, chúng ta có thể xác định nếu một hợp đồng là một hợp đồng tiêu chuẩn ERC721 hoặc không.BAYC
Trước tiên chúng ta cần đăng ký ABI, và vì chúng ta chỉ gọi ba phương thức sau đây, chúng ta có thể đăng ký ba phương thức này:
Mã cụ thể là như sau:
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)
}
Các thử nghiệm có thể được chạy trong công cụ gỡ lỗi:
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