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 слушатель = нуль
функция main() {
var event =
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: тест var arrLog = [] функция onexit (()) { Log (включает конец запуска, проверяет запись) var firstBlockNumber = слушатель.firstBlockNumber var endBlockNumber = слушатель.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)
}
}
Код, указанный выше, работает на диске:
В этом разделе мы рассказываем о том, как создать фильтр событий и использовать его для прослушивания событий, связанных с горячими кошельками на бирже.USDT
Вы можете изменить или расширить этот пример, чтобы прослушивать любое событие, которое вас интересует.smart money
Новые сделки, которые были сделаны.NFT
Новые проекты, которые были сделаны в Дамаске и т.д.
Многие вычисления, связанные с Эфирием, выходят за рамки этого числа.JavaScript
Максимально безопасные целые числа языка. Поэтому на квантовых торговых платформах изобретателей необходимы некоторые методы обработки больших чисел, которые мы также использовали в предыдущих уроках, и которые не описаны в деталях.
ПечатьJavaScript
Максимальные безопасные целые числа, определенные в языке:
function main() {
Log("Number.MAX_SAFE_INTEGER:", Number.MAX_SAFE_INTEGER)
}
Результаты:
Номер.MAX_SAFE_INTEGER: 9007199254740991
Минимальная единица, которая определяется в Эфириуме, это1wei
Определение1Gwei
Это равно1000000000 wei
。1Gwei
В расчетах, связанных с Ethereum, это не очень большое число, некоторые данные намного больше его.Number.MAX_SAFE_INTEGER: 9007199254740991
。
Мы используем платформы, которые мы используем для того, чтобы создавать и контролировать свои собственные ресурсы.BigInt
Объекты представляют эти сверхбольшие целые данные.BigInt()
СоздатьBigInt
Объекты. Можно использовать числовые значения, шестнадцатизначные числовые строки в качестве параметров.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
Это два следующих параметра:toAmount()
、toInnerAmount()
Эти две функции, которые мы использовали много раз в предыдущих уроках, используются в основном для преобразования точности данных.
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 часто требуется обработка шестнадцатизначных данных на некоторых цепях.
Мы часто встречались в наших предыдущих курсах, например, с умными контрактами.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
}
Один токен ETH, как мы знаем,1e18 wei
Если мы получимwei
Данные по единицам126564027559051260
Как обменять на ETH?
ИспользованиеtoAmount(, 18)
Функция может быть легко преобразована.toInnerAmount()
Функция должна бытьtoAmount()
С помощью обратной операции функции (в зависимости от точности, увеличения) можно легко преобразовывать данные с помощью этих двух функций.
Следует обратить внимание на цельный диапазон безопасности в языке 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
И, естественно, это происходит потому, что 10000000000000000000000 больше.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.01
В обмен на 1 ETHwei
Количественные значения единиц10000000000000000
И это будет больше.Number.MAX_SAFE_INTEGER
Поэтому для таких ситуаций более безопасным вариантом является:BigInt(10000000000000000).toString(16)
。
Например, вы можете использовать Ethereum для выполнения транзакций, вызова смарт-контрактов.Write
Методы требуют определенного расхода газа и иногда подвергаются риску неудачи. Важно знать, какие сделки могут потерпеть неудачу до отправки сделки или вызова.
Методы RPC на Эфириумеeth_call
: можно имитировать транзакцию и возвращать возможные результаты транзакции, но не выполнять эту транзакцию на блокчейне;
eth_call
У метода есть два параметра: первый параметр - это словарная структура, а второй - это словарная структура.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
}
Второй параметр: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
Например, для аналогичных вызовов используется следующая тестовая среда для сети Эфириум.
Для контрактов ERC20approve
Метод нам всем хорошо знаком, мы практиковали его в предыдущих курсах. Поскольку контракт 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
Раутинговый контракт.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, чтобы оплатить газ, параметры возвращают ошибку:
недостаток средств
Если настроить слишком низкую цену на газ, то возникает ошибка:
Внутренний газ слишком низкий: есть 21000, хочу 21944 (поставленный газ 21000)
Для ERC20transfer
Мы также не знаем, как перевести ERC20 на адрес кошелька, и мы попробуем имитировать перевод 1000 DAI на 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
}
Поскольку мой тестовый кошелек не имеет токенов DAI, результаты, которые я ожидал получить при работе с инструментами для дебютирования, были неверными:
исполнение обращено вспять: Dai/insufficient-balance
Посмотрите на адрес этого кошелька Бога V:0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
В данном случае, если вы используете кошелек с кошельком DAI, то это означает, что вы используете кошелек с кошельком DAI. Если вы используете кошелек с кошельком DAI, то это означает, что вы используете кошелек с кошельком 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
Используя квантовую торговую платформу изобретателя, можно легко имитировать результаты сделок и избежать ненужных потерь в расходах на газ при отправке сделок, которые могут потерпеть неудачу. Мы использовали примерный код в этой главе, который имитирует перевод в V-кошелек, вызов, который V-кошелек переводит нам.eth_call
Но есть и другие способы. Используйте свое воображение, и вы будетеeth_call
Где это применяется?
Мы знаем, что такие токены, как ETH и BTC, относятся к однородным токенам, токены в ваших руках и токены в моих руках не отличаются друг от друга. Но есть много вещей в мире, которые не являются однородными, например: недвижимость, антиквариат, виртуальные произведения искусства и т. д., которые не могут быть представлены абстрактно однородными токенами. Итак, как мы можем определить, какие из множества смарт-контрактов, развернутых на Ethereum, являются смарт-контрактами по стандарту ERC721?
Для того, чтобы распознать ERC721, необходимо сначала понять стандарт ERC165.
С помощью стандарта ERC165 умный контракт может заявлять интерфейсы, которые он поддерживает для проверки других контрактов.supportsInterface(bytes4 interfaceId)
, параметрыinterfaceId
Идентификатор интерфейса, который будет запрошен. Если контракт реализует идентификатор интерфейса, он возвращает истинное значение бура, а если нет - ложное значение.
В следующей статье мы поговорим об этом.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;
}
}
Для подписи функций интерфейсов (состоит из списка имен функций и типов параметров) выполняется дифференциация или расчет, для 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);
}
Идентификатор интерфейса для этого интерфейса 0x01ffc9a7. Вы можете рассчитать это, запустив байт4 ((keccak256 ((
supportsInterface ((bytes4) )); или используя контракт Selector выше.
Если мы просто подсчитаем подпись функции, первые четыре байта, то мы получим: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:
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);
}
Если мы хотим судить о том, является ли какой-либо умный контракт ERC721, мы должны сначала узнать, что это за контракт ERC721.interfaceId
И тогда мы сможем попробовать его использовать.supportsInterface(bytes4 interfaceId)
В предыдущих уроках мы уже ознакомились с некоторыми концепциями и вычислениями стандарта ERC165.interfaceId
Если вы хотите, чтобы ваш алгоритм работал, вы должны написать код прямо здесь:
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)
}
Используется в кодеEncode()
Функция для вычисления подписи функцииkeccak256
Алгоритм), для вычисления в примере выше указанного кодаEncode()
Выходные параметры функций:"raw"
Функция возвращаетJavaScript
ЯзыкArrayBuffer
Тип.
Если вы хотите измеритьArrayBuffer
Объекты^
(Дифференцированные) операции, которые должны быть основаны наArrayBuffer
Создание объектовTypedArray
Посмотрите, а затем пройдите по данным, которые находятся в нем, и выполните отдельные вычисления.
Дебютные инструменты:
2023-06-13 15:04:09 信息 0x80ac58cd
Вы можете увидеть результаты вычислений иeip-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);
...
Имея ID интерфейса ERC721, мы можем определить, является ли какой-либо контракт стандартом ERC721.BAYC
Для тестирования, это контракт, следующий ERC721, сначала нам нужно зарегистрировать ABI, и поскольку мы вызвали только три способа, мы можем зарегистрировать только три способа:
Конкретные коды:
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)
}
В инструменте для декомпиляции можно запустить тесты:
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
Определение адреса0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d
Согласно стандарту ERC721.
В этой статье мы рассказали о том, как определить, что договор ERC721 не соответствует стандарту ERC165, если он не соответствует стандарту ERC20.
Что это?calldata
По мнению автора, простое и распространенное описание:
"Calldata" - это кодирование вызовов функций и параметров в Эфириуме, "calldata" кодируется в соответствии с спецификацией ABI (Application Binary Interface) контракта.
Например, мы могли бы использовать контракты ERC20, которые мы изучали в предыдущих курсах.balanceOf
、transfer
Призыв метода вместе с параметрами при вызове кодируется какcalldata
В некоторых сценариях применения, например:Взаимодействие между контрактамиВ этом случае это может быть полезно.calldata
И, конечно же, есть много других сценариев применения, которые я перечислил здесь.
Как запрограммировать вызов к функции умного контракта, чтобы получитьcalldata
?
Изобретатели могут использовать квантовые платформы для торговлиexchange.IO("encode", ...)
Кодирование вызовов к функциям умных контрактов очень простое в использовании.exchange.IO("encode", ...)
Первый параметр функции - фиксированная строка."encode"
; второй параметр - адрес смарт-контракта; третий параметр - название метода смарт-контракта, который будет закодирован; остальные параметры передаются в конкретные параметры этого метода смарт-контракта, который будет закодирован.
Когда мы кодируем призыв к методу умного контракта и генерируем соответствующиеcalldata
В данном случае, если этот умный контрактный метод является методом Write (т.е. написание операций), мы должны использоватьcalldata
Данные в качестве данных-полей для транзакций, а затем с использованием метода RPC на Ethereumeth_sendRawTransaction
Отправление запроса на исходные данные, содержащие данную транзакцию, в сеть Ethereum.
eth_sendRawTransaction
У метода есть только один параметр.data
:
данные: подписанная транзакция (обычно подписанная с помощью библиотеки, используя ваш частный ключ)
Этоdata
Параметры - это данные о транзакциях после вычисления подписи. Структура транзакционных данных Ethereum состоит из следующих полей:
{
"nonce": "0x1", // 交易发送方的账户交易次数
"gasPrice": "0x12a05f200", // 交易的Gas价格
"gasLimit": "0x5208", // 交易的Gas限制
"to": "0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2", // 目标合约地址或接收方地址
"value": "0x4563918244F40000", // 转账的以太币数量
"data": "0x0123456789ABCDEF", // 要发送给合约的数据
}
Как подписаться на Ethereum?
Изобретатели количественных торговых платформ, которые мы используемEncode()
Функции для вычисления подписи, конкретные примеры мы написали в последующем уроке "Выполнение метода Write calldata".
Для метода Readcalldata
В результате мы смогли выполнить эту задачу, используя метод RPC, который мы изучали ранее:eth_call
Мы уже говорили об этом ранее.eth_call
Этот метод RPC на Эфириуме работает только с умными контрактами.Write
Демонстрация методов, используемых в этой главеcalldata
Приведем примеры, как можно выполнять вызовы к методу чтения умных контрактов.balanceOf
Способы считывания баланса WETH в текущем кошельке.
Мы использовали инструменты для тестирования на сети Ethereum:
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个数为单位
}
Дебютные инструменты:
2023-06-15 11:51:31 信息 wethBalance: 0.015
2023-06-15 11:51:31 信息 calldataForDeposit: 0x70a082310000000000000000000000006b3f11d807809b0b1e5e3243df04a280d9f94bf4
Если метод умного контракта имеет возвращаемое значение, он может быть использован.exchange.IO("decode", ...)
Декодирование функций.calldata
В этом случае, мы можем использовать только один способ, чтобы получить доступ к этому.balanceOf
В результате, если вы получите 0,015, то вы получите 0,015, а если вы получите 0,015, то вы получите 0,015.
Для выполнения calldata метода Write необходимо использовать метод RPC:eth_sendRawTransaction
Я не знаю.
Мы использовали инструменты для тестирования на сети Ethereum:
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