From cf6dc7699f3f44ce245a999617eec1fab2ac1ea1 Mon Sep 17 00:00:00 2001 From: Yuriy Glukhov Date: Tue, 2 Jul 2019 16:55:39 +0300 Subject: [PATCH] More fixes, fetch historical logs on subsciption, more tests --- tests/all_tests.nim | 2 +- tests/test.nim | 15 +---- tests/test_deposit_contract.nim | 97 ++++++++++++++------------------- tests/test_logs.nim | 82 ++++++++++++++++++++++++++++ tests/test_utils.nim | 19 +++++++ web3.nim | 73 +++++++++++++++++++------ web3/ethcallsigs.nim | 16 +++--- web3/ethhexstrings.nim | 2 + web3/ethtypes.nim | 10 ++-- web3/stintjson.nim | 24 ++++---- 10 files changed, 232 insertions(+), 108 deletions(-) create mode 100644 tests/test_logs.nim create mode 100644 tests/test_utils.nim diff --git a/tests/all_tests.nim b/tests/all_tests.nim index 42fdb58..86c470e 100644 --- a/tests/all_tests.nim +++ b/tests/all_tests.nim @@ -5,4 +5,4 @@ # * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) # at your option. This file may not be copied, modified, or distributed except according to those terms. -import test, test_deposit_contract +import test, test_deposit_contract, test_logs diff --git a/tests/test.nim b/tests/test.nim index e62014e..9d85be4 100644 --- a/tests/test.nim +++ b/tests/test.nim @@ -1,6 +1,7 @@ import ../web3 import chronos, nimcrypto, json_rpc/rpcclient, options, json, stint import ../web3/[ethtypes, ethprocs, stintjson, ethhexstrings] +import test_utils #[ Contract NumberStorage @@ -66,18 +67,8 @@ proc test() {.async.} = echo "accounts: ", accounts let defaultAccount = accounts[0] - proc deployContract(code: string): Future[Address] {.async.} = - ## Deploy contract and return its address - var tr: EthSend - tr.source = defaultAccount - tr.data = code - tr.gas = 1500000.some - let r = await provider.eth_sendTransaction(tr) - let receipt = await provider.eth_getTransactionReceipt(r) - result = receipt.contractAddress.get - block: # NumberStorage - let cc = await deployContract(NumberStorageCode) + let cc = await web3.deployContract(NumberStorageCode) echo "Deployed NumberStorage contract: ", cc let ns = web3.contractSender(NumberStorage, cc, defaultAccount) @@ -88,7 +79,7 @@ proc test() {.async.} = assert(n == 5.u256) block: # MetaCoin - let cc = await deployContract(MetaCoinCode) + let cc = await web3.deployContract(MetaCoinCode) echo "Deployed MetaCoin contract: ", cc let ns = web3.contractSender(MetaCoin, cc, defaultAccount) diff --git a/tests/test_deposit_contract.nim b/tests/test_deposit_contract.nim index ac5020e..26b68e2 100644 --- a/tests/test_deposit_contract.nim +++ b/tests/test_deposit_contract.nim @@ -1,6 +1,7 @@ import ../web3 import chronos, nimcrypto, json_rpc/rpcclient, options, json, stint import ../web3/[ethtypes, ethprocs, stintjson, ethhexstrings] +import test_utils contract(DepositContract): proc deposit(pubkey: Bytes48, withdrawalCredentials: Bytes32, signature: Bytes96) @@ -8,66 +9,48 @@ contract(DepositContract): const contractCode = "0x600035601c52740100000000000000000000000000000000000000006020526f7fffffffffffffffffffffffffffffff6040527fffffffffffffffffffffffffffffffff8000000000000000000000000000000060605274012a05f1fffffffffffffffffffffffffdabf41c006080527ffffffffffffffffffffffffed5fa0e000000000000000000000000000000000060a052341561009e57600080fd5b6101406000601f818352015b600061014051602081106100bd57600080fd5b600260c052602060c020015460208261016001015260208101905061014051602081106100e957600080fd5b600260c052602060c020015460208261016001015260208101905080610160526101609050602060c0825160208401600060025af161012757600080fd5b60c0519050606051600161014051018060405190131561014657600080fd5b809190121561015457600080fd5b6020811061016157600080fd5b600260c052602060c02001555b81516001018083528114156100aa575b50506111c656600035601c52740100000000000000000000000000000000000000006020526f7fffffffffffffffffffffffffffffff6040527fffffffffffffffffffffffffffffffff8000000000000000000000000000000060605274012a05f1fffffffffffffffffffffffffdabf41c006080527ffffffffffffffffffffffffed5fa0e000000000000000000000000000000000060a052600015610277575b6101605261014052600061018052610140516101a0526101c060006008818352015b61018051600860008112156100da578060000360020a82046100e1565b8060020a82025b905090506101805260ff6101a051166101e052610180516101e0516101805101101561010c57600080fd5b6101e0516101805101610180526101a0517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff86000811215610155578060000360020a820461015c565b8060020a82025b905090506101a0525b81516001018083528114156100bd575b50506018600860208206610200016020828401111561019357600080fd5b60208061022082610180600060046015f15050818152809050905090508051602001806102c0828460006004600a8704601201f16101d057600080fd5b50506102c05160206001820306601f82010390506103206102c0516008818352015b826103205111156102025761021e565b6000610320516102e001535b81516001018083528114156101f2575b50505060206102a05260406102c0510160206001820306601f8201039050610280525b6000610280511115156102535761026f565b602061028051036102a001516020610280510361028052610241565b610160515650005b63c5f2892f60005114156103cf57341561029057600080fd5b6000610140526001546101605261018060006020818352015b600160016101605116141561032a57600061018051602081106102cb57600080fd5b600060c052602060c02001546020826102200101526020810190506101405160208261022001015260208101905080610220526102209050602060c0825160208401600060025af161031c57600080fd5b60c051905061014052610398565b6000610140516020826101a0010152602081019050610180516020811061035057600080fd5b600260c052602060c02001546020826101a0010152602081019050806101a0526101a09050602060c0825160208401600060025af161038e57600080fd5b60c0519050610140525b61016060026103a657600080fd5b60028151048152505b81516001018083528114156102a9575b50506101405160005260206000f3005b63621fd13060005114156104e15734156103e857600080fd5b63806732896101405260015461016052610160516006580161009b565b506101c0526000610220525b6101c05160206001820306601f8201039050610220511015156104335761044c565b610220516101e001526102205160200161022052610411565b6101c0805160200180610280828460006004600a8704601201f161046f57600080fd5b50506102805160206001820306601f82010390506102e0610280516008818352015b826102e05111156104a1576104bd565b60006102e0516102a001535b8151600101808352811415610491575b5050506020610260526040610280510160206001820306601f8201039050610260f3005b63c47e300d600051141561103b57606060046101403760506004356004016101a037603060043560040135111561051757600080fd5b604060243560040161022037602060243560040135111561053757600080fd5b608060443560040161028037606060443560040135111561055757600080fd5b63ffffffff6001541061056957600080fd5b633b9aca00610340526103405161057f57600080fd5b61034051340461032052633b9aca0061032051101561059d57600080fd5b60306101a051146105ad57600080fd5b602061022051146105bd57600080fd5b606061028051146105cd57600080fd5b6101a0516101c0516101e05161020051610220516102405161026051610280516102a0516102c0516102e05161030051610320516103405161036051610380516103a05163806732896103c052610320516103e0526103e0516006580161009b565b506104405260006104a0525b6104405160206001820306601f82010390506104a05110151561065d57610676565b6104a05161046001526104a0516020016104a05261063b565b6103a05261038052610360526103405261032052610300526102e0526102c0526102a05261028052610260526102405261022052610200526101e0526101c0526101a052610440805160200180610360828460006004600a8704601201f16106dd57600080fd5b50506101a0516101c0516101e05161020051610220516102405161026051610280516102a0516102c0516102e05161030051610320516103405161036051610380516103a0516103c0516103e05161040051610420516104405161046051610480516104a05163806732896104c0526001546104e0526104e0516006580161009b565b506105405260006105a0525b6105405160206001820306601f82010390506105a05110151561078e576107a7565b6105a05161056001526105a0516020016105a05261076c565b6104a05261048052610460526104405261042052610400526103e0526103c0526103a05261038052610360526103405261032052610300526102e0526102c0526102a05261028052610260526102405261022052610200526101e0526101c0526101a0526105408051602001806105c0828460006004600a8704601201f161082e57600080fd5b505060a06106405261064051610680526101a08051602001806106405161068001828460006004600a8704601201f161086657600080fd5b505061064051610680015160206001820306601f8201039050610640516106800161062081516040818352015b83610620511015156108a4576108c1565b6000610620516020850101535b8151600101808352811415610893575b50505050602061064051610680015160206001820306601f820103905061064051010161064052610640516106a0526102208051602001806106405161068001828460006004600a8704601201f161091857600080fd5b505061064051610680015160206001820306601f8201039050610640516106800161062081516020818352015b836106205110151561095657610973565b6000610620516020850101535b8151600101808352811415610945575b50505050602061064051610680015160206001820306601f820103905061064051010161064052610640516106c0526103608051602001806106405161068001828460006004600a8704601201f16109ca57600080fd5b505061064051610680015160206001820306601f8201039050610640516106800161062081516020818352015b8361062051101515610a0857610a25565b6000610620516020850101535b81516001018083528114156109f7575b50505050602061064051610680015160206001820306601f820103905061064051010161064052610640516106e0526102808051602001806106405161068001828460006004600a8704601201f1610a7c57600080fd5b505061064051610680015160206001820306601f8201039050610640516106800161062081516060818352015b8361062051101515610aba57610ad7565b6000610620516020850101535b8151600101808352811415610aa9575b50505050602061064051610680015160206001820306601f82010390506106405101016106405261064051610700526105c08051602001806106405161068001828460006004600a8704601201f1610b2e57600080fd5b505061064051610680015160206001820306601f8201039050610640516106800161062081516020818352015b8361062051101515610b6c57610b89565b6000610620516020850101535b8151600101808352811415610b5b575b50505050602061064051610680015160206001820306601f8201039050610640510101610640527fdc5fc95703516abd38fa03c3737ff3b52dc52347055c8028460fdf5bbe2f12ce61064051610680a160006107205260006101a06030806020846107e001018260208501600060046016f150508051820191505060006010602082066107600160208284011115610c2057600080fd5b60208061078082610720600060046015f15050818152809050905090506010806020846107e001018260208501600060046013f1505080518201915050806107e0526107e09050602060c0825160208401600060025af1610c8057600080fd5b60c0519050610740526000600060406020820661088001610280518284011115610ca957600080fd5b6060806108a0826020602088068803016102800160006004601bf1505081815280905090509050602060c0825160208401600060025af1610ce957600080fd5b60c0519050602082610a800101526020810190506000604060206020820661094001610280518284011115610d1d57600080fd5b606080610960826020602088068803016102800160006004601bf1505081815280905090509050602080602084610a0001018260208501600060046015f150508051820191505061072051602082610a0001015260208101905080610a0052610a009050602060c0825160208401600060025af1610d9a57600080fd5b60c0519050602082610a8001015260208101905080610a8052610a809050602060c0825160208401600060025af1610dd157600080fd5b60c0519050610860526000600061074051602082610b20010152602081019050610220602080602084610b2001018260208501600060046015f150508051820191505080610b2052610b209050602060c0825160208401600060025af1610e3757600080fd5b60c0519050602082610ca00101526020810190506000610360600880602084610c2001018260208501600060046012f15050805182019150506000601860208206610ba00160208284011115610e8c57600080fd5b602080610bc082610720600060046015f1505081815280905090509050601880602084610c2001018260208501600060046014f150508051820191505061086051602082610c2001015260208101905080610c2052610c209050602060c0825160208401600060025af1610eff57600080fd5b60c0519050602082610ca001015260208101905080610ca052610ca09050602060c0825160208401600060025af1610f3657600080fd5b60c0519050610b00526001805460018254011015610f5357600080fd5b6001815401815550600154610d2052610d4060006020818352015b60016001610d2051161415610fa357610b0051610d405160208110610f9257600080fd5b600060c052602060c0200155611037565b6000610d405160208110610fb657600080fd5b600060c052602060c0200154602082610d60010152602081019050610b0051602082610d6001015260208101905080610d6052610d609050602060c0825160208401600060025af161100757600080fd5b60c0519050610b0052610d20600261101e57600080fd5b60028151048152505b8151600101808352811415610f6e575b5050005b60006000fd5b6101856111c6036101856000396101856111c6036000f3" -var contractAddress = Address.fromHex("0xf199d05a7a1223e95b07f40ffd84826b779afaf0") - -proc deploy() {.async.} = - let provider = newRpcWebSocketClient() - await provider.connect("ws://localhost:8545") - let web3 = newWeb3(provider) - let accounts = await provider.eth_accounts() - echo "accounts: ", accounts - let defaultAccount = accounts[0] - - proc deployContract(code: string): Future[Address] {.async.} = - ## Deploy contract and return its address - var tr: EthSend - tr.source = defaultAccount - tr.data = code - tr.gas = 3000000.some - let r = await provider.eth_sendTransaction(tr) - let receipt = await provider.eth_getTransactionReceipt(r) - result = receipt.contractAddress.get - - contractAddress = await deployContract(contractCode) - echo "Deployed Deposit contract: ", contractAddress +var contractAddress = Address.fromHex("e9d8d67ec115e8345606b3ab59fc71cec46761e4") proc ethToWei(eth: UInt256): UInt256 = eth * 1000000000000000000.u256 -proc watch() {.async.} = - var cc = contractAddress - let provider = newRpcWebSocketClient() - await provider.connect("ws://localhost:8545") - let web3 = newWeb3(provider) - let accounts = await provider.eth_accounts() - let defaultAccount = accounts[0] - - var ns = web3.contractSender(DepositContract, cc, defaultAccount) - - let notifFut = newFuture[void]() - var notificationsReceived = 0 - - var pk = Bytes48.fromHex("0x97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb") - var cr = Bytes32.fromHex("0xaa000000000000000000000000000000000000000000000000000000000000bb") - var sig = Bytes96.fromHex("0xad606d747d9d8590583107e23b41d6191215495df34a34d767e81c3c7f1f2d7041f421f3486186044a02c3dd65a05b44061455c2ca7d6525db68d5fa146e34de8234d3acd8de7e00b971acd4458b740fa6368d437db2c8dae6b2011db9be2f07") - - var fut = newFuture[void]() - - let s = await ns.subscribe(Deposit) do(pubkey: Bytes48, withdrawalCredentials: Bytes32, amount: Bytes8, signature: Bytes96, merkleTreeIndex: Bytes8): - echo "onDeposit" - echo "pubkey: ", pubkey - echo "withdrawalCredentials: ", withdrawalCredentials - echo "amount: ", amount - echo "signature: ", signature - echo "merkleTreeIndex: ", merkleTreeIndex - assert(pubkey == pk) - fut.complete() - - ns.value = ethToWei(32.u256) - discard await ns.deposit(pk, cr, sig) - - await fut +proc test() {.async.} = + let provider = newRpcWebSocketClient() + await provider.connect("ws://localhost:8545") + let web3 = newWeb3(provider) + let accounts = await provider.eth_accounts() + let defaultAccount = accounts[0] -waitFor deploy() -waitFor watch() + contractAddress = await web3.deployContract(contractCode) + echo "Deployed Deposit contract: ", contractAddress + + + var ns = web3.contractSender(DepositContract, contractAddress, defaultAccount) + + let notifFut = newFuture[void]() + var notificationsReceived = 0 + + var pk = Bytes48.fromHex("0x97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb") + var cr = Bytes32.fromHex("0xaa000000000000000000000000000000000000000000000000000000000000bb") + var sig = Bytes96.fromHex("0xad606d747d9d8590583107e23b41d6191215495df34a34d767e81c3c7f1f2d7041f421f3486186044a02c3dd65a05b44061455c2ca7d6525db68d5fa146e34de8234d3acd8de7e00b971acd4458b740fa6368d437db2c8dae6b2011db9be2f07") + + var fut = newFuture[void]() + + let s = await ns.subscribe(Deposit, %*{"fromBlock": "0x0"}) do(pubkey: Bytes48, withdrawalCredentials: Bytes32, amount: Bytes8, signature: Bytes96, merkleTreeIndex: Bytes8): + echo "onDeposit" + echo "pubkey: ", pubkey + echo "withdrawalCredentials: ", withdrawalCredentials + echo "amount: ", amount + echo "signature: ", signature + echo "merkleTreeIndex: ", merkleTreeIndex + assert(pubkey == pk) + fut.complete() + + ns.value = ethToWei(32.u256) + discard await ns.deposit(pk, cr, sig) + + await fut + + +waitFor test() diff --git a/tests/test_logs.nim b/tests/test_logs.nim new file mode 100644 index 0000000..feaaaaf --- /dev/null +++ b/tests/test_logs.nim @@ -0,0 +1,82 @@ +import ../web3 +import chronos, nimcrypto, json_rpc/rpcclient, options, json, stint +import ../web3/[ethtypes, ethprocs, stintjson, ethhexstrings] +import test_utils + +import random + +#[ Contract LoggerContract +pragma solidity >=0.4.25 <0.6.0; + +contract LoggerContract { + + uint fNum; + + event MyEvent(address sender, uint value); + + + function invoke(uint value) public { + emit MyEvent(msg.sender, value); + } +} +]# +contract(LoggerContract): + proc MyEvent(sender: Address, number: Uint256) {.event.} + proc invoke(value: Uint256) + +const LoggerContractCode = "6080604052348015600f57600080fd5b5060bc8061001e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c80632b30d2b814602d575b600080fd5b604760048036036020811015604157600080fd5b50356049565b005b604080513381526020810183905281517fdf50c7bb3b25f812aedef81bc334454040e7b27e27de95a79451d663013b7e17929181900390910190a15056fea265627a7a723058202ed7f5086297d2a49fbe359f4e489a007b69eb5077f5c76328bffdb63f164b4b64736f6c63430005090032" + +var contractAddress = Address.fromHex("0xEA255DeA28c84F698Fa195f87fC83D1d4125ef9C") + +proc test() {.async.} = + let provider = newRpcWebSocketClient() + await provider.connect("ws://localhost:8545") + let web3 = newWeb3(provider) + let accounts = await provider.eth_accounts() + echo "accounts: ", accounts + let defaultAccount = accounts[0] + # let q = await provider.eth_blockNumber() + echo "block: ", uint64(await provider.eth_blockNumber()) + + + block: # LoggerContract + + contractAddress = await web3.deployContract(LoggerContractCode) + echo "Deployed LoggerContract contract: ", contractAddress + + let ns = web3.contractSender(LoggerContract, contractAddress, defaultAccount) + + proc testInvoke() {.async.} = + let r = rand(1 .. 1000000) + echo "invoke(", r, "): ", await ns.invoke(r.u256) + + const invocationsBefore = 5 + const invocationsAfter = 5 + + for i in 1 .. invocationsBefore: + await testInvoke() + + # Now that we have invoked the function `invocationsBefore` let's wait for the transactions to + # settle and see if we receive the logs after subscription. Note in ganache transactions are + # processed immediately. With a real eth client we would need to wait for transactions to settle + + await sleepAsync(3.seconds) + + let notifFut = newFuture[void]() + var notificationsReceived = 0 + + let s = await ns.subscribe(MyEvent, %*{"fromBlock": "0x0"}) do(sender: Address, value: Uint256): + echo "onEvent: ", sender, " value ", value + inc notificationsReceived + + if notificationsReceived == invocationsBefore + invocationsAfter: + notifFut.complete() + + for i in 1 .. invocationsAfter: + await testInvoke() + + await notifFut + + await s.unsubscribe() + +waitFor test() diff --git a/tests/test_utils.nim b/tests/test_utils.nim new file mode 100644 index 0000000..6eecfc5 --- /dev/null +++ b/tests/test_utils.nim @@ -0,0 +1,19 @@ +import ../web3, chronos, options, json_rpc/rpcclient +import ../web3/[ethtypes] + + +proc deployContract*(web3: Web3, code: string): Future[Address] {.async.} = + let provider = web3.provider + let accounts = await provider.eth_accounts() + + var code = code + if code[1] notin {'x', 'X'}: + code = "0x" & code + var tr: EthSend + tr.source = accounts[0] + tr.data = code + tr.gas = Quantity(3000000).some + let r = await provider.eth_sendTransaction(tr) + let receipt = await provider.eth_getTransactionReceipt(r) + result = receipt.contractAddress.get + \ No newline at end of file diff --git a/web3.nim b/web3.nim index fabfbb8..cd115a9 100644 --- a/web3.nim +++ b/web3.nim @@ -30,15 +30,21 @@ type id*: string web3*: Web3 callback*: proc(j: JsonNode) + pendingEvents: seq[JsonNode] + historicalEventsProcessed: bool + removed: bool proc handleSubscriptionNotification(w: Web3, j: JsonNode) = let s = w.subscriptions.getOrDefault(j{"subscription"}.getStr()) - if not s.isNil: - try: - s.callback(j{"result"}) - except Exception as e: - echo "Caught exception: ", e.msg - echo e.getStackTrace() + if not s.isNil and not s.removed: + if s.historicalEventsProcessed: + try: + s.callback(j{"result"}) + except Exception as e: + echo "Caught exception in handleSubscriptionNotification: ", e.msg + echo e.getStackTrace() + else: + s.pendingEvents.add(j) proc newWeb3*(provider: RpcClient): Web3 = result = Web3(provider: provider) @@ -47,6 +53,23 @@ proc newWeb3*(provider: RpcClient): Web3 = provider.setMethodHandler("eth_subscription") do(j: JsonNode): r.handleSubscriptionNotification(j) +proc getHistoricalEvents(s: Subscription, options: JsonNode) {.async.} = + try: + let logs = await s.web3.provider.eth_getLogs(options) + for l in logs: + if s.removed: break + s.callback(l) + s.historicalEventsProcessed = true + var i = 0 + while i < s.pendingEvents.len: # Mind reentrancy + if s.removed: break + s.callback(s.pendingEvents[i]) + inc i + s.pendingEvents = @[] + except Exception as e: + echo "Caught exception in getHistoricalEvents: ", e.msg + echo e.getStackTrace() + proc subscribe*(w: Web3, name: string, options: JsonNode, callback: proc(j: JsonNode)): Future[Subscription] {.async.} = var options = options if options.isNil: options = newJNull() @@ -54,7 +77,13 @@ proc subscribe*(w: Web3, name: string, options: JsonNode, callback: proc(j: Json result = Subscription(id: id, web3: w, callback: callback) w.subscriptions[id] = result +proc subscribeToLogs*(w: Web3, options: JsonNode, callback: proc(j: JsonNode)): Future[Subscription] {.async.} = + result = await subscribe(w, "logs", options, callback) + discard getHistoricalEvents(result, options) + proc unsubscribe*(s: Subscription): Future[void] {.async.} = + s.web3.subscriptions.del(s.id) + s.removed = true discard await s.web3.provider.eth_unsubscribe(s.id) func encode*[bits: static[int]](x: Stuint[bits]): EncodeResult = @@ -358,6 +387,18 @@ proc getSignature(function: FunctionObject | EventObject): NimNode = result.add(newLit(")")) result = newCall(ident"static", result) +proc addAddressAndSignatureToOptions(options: JsonNode, address: Address, signature: string): JsonNode = + result = options + if result.isNil: + result = newJObject() + if "address" notin result: + result["address"] = %address + var topics = result{"topics"} + if topics.isNil: + topics = newJArray() + result["topics"] = topics + topics.elems.insert(%signature, 0) + proc parseContract(body: NimNode): seq[InterfaceObject] = proc parseOutputs(outputNode: NimNode): seq[FunctionInputOutput] = #if outputNode.kind == nnkIdent: @@ -593,7 +634,7 @@ macro contract*(cname: untyped, body: untyped): untyped = var `cc`: EthCall `cc`.source = some(`senderName`.fromAddress) `cc`.to = `senderName`.contractAddress - `cc`.gas = some(3000000) + `cc`.gas = some(Quantity(3000000)) `encoder` `cc`.data = some("0x" & ($keccak_256.digest(`signature`))[0..<8].toLower & `encodedParams`) echo "Call data: ", `cc`.data @@ -613,7 +654,7 @@ macro contract*(cname: untyped, body: untyped): untyped = cc.source = `senderName`.fromAddress cc.to = some(`senderName`.contractAddress) cc.value = some(`senderName`.value) - cc.gas = some(3000000) + cc.gas = some(Quantity(3000000)) `encoder` cc.data = "0x" & ($keccak_256.digest(`signature`))[0..<8].toLower & `encodedParams` @@ -667,16 +708,13 @@ macro contract*(cname: untyped, body: untyped): untyped = result.add quote do: type `cbident` = object - proc subscribe(s: Sender[`cname`], t: typedesc[`cbident`], `callbackIdent`: `procTy`): Future[Subscription] = - let options = %*{ - "fromBlock": "latest", - "toBlock": "latest", - "address": s.contractAddress, - "topics": ["0x" & $keccak256.digest(`signature`)] - } - s.web3.subscribe("logs", options) do(`jsonIdent`: JsonNode): + proc subscribe(s: Sender[`cname`], t: typedesc[`cbident`], options: JsonNode, `callbackIdent`: `procTy`): Future[Subscription] = + let options = addAddressAndSignatureToOptions(options, s.contractAddress, "0x" & toLowerAscii($keccak256.digest(`signature`))) + + s.web3.subscribeToLogs(options) do(`jsonIdent`: JsonNode): `argParseBody` `call` + else: discard @@ -711,5 +749,8 @@ macro contract*(cname: untyped, body: untyped): untyped = proc contractSender*(web3: Web3, T: typedesc, toAddress, fromAddress: Address): Sender[T] = Sender[T](web3: web3, contractAddress: toAddress, fromAddress: fromAddress) +proc subscribe*(s: Sender, t: typedesc, cb: proc): Future[Subscription] {.inline.} = + subscribe(s, t, nil, cb) + proc `$`*(b: Bool): string = $(Stint[256](b)) diff --git a/web3/ethcallsigs.nim b/web3/ethcallsigs.nim index 7eaf334..e9f33f2 100644 --- a/web3/ethcallsigs.nim +++ b/web3/ethcallsigs.nim @@ -15,7 +15,7 @@ proc eth_mining(): bool proc eth_hashrate(): int proc eth_gasPrice(): int64 proc eth_accounts(): seq[Address] -proc eth_blockNumber(): string +proc eth_blockNumber(): Quantity proc eth_getBalance(data: array[20, byte], quantityTag: string): int proc eth_getStorageAt(data: array[20, byte], quantity: int, quantityTag: string): seq[byte] proc eth_getTransactionCount(data: array[20, byte], quantityTag: string) @@ -41,13 +41,15 @@ proc eth_getCompilers(): seq[string] proc eth_compileLLL(): seq[byte] proc eth_compileSolidity(): seq[byte] proc eth_compileSerpent(): seq[byte] -proc eth_newFilter(filterOptions: FilterOptions): int -proc eth_newBlockFilter(): int -proc eth_newPendingTransactionFilter(): int -proc eth_uninstallFilter(filterId: int): bool -proc eth_getFilterChanges(filterId: int): seq[LogObject] -proc eth_getFilterLogs(filterId: int): seq[LogObject] +proc eth_newFilter(filterOptions: FilterOptions): string +proc eth_newBlockFilter(): string +proc eth_newPendingTransactionFilter(): string +proc eth_uninstallFilter(filterId: string): bool +proc eth_getFilterChanges(filterId: string): JsonNode +proc eth_getFilterLogs(filterId: string): JsonNode proc eth_getLogs(filterOptions: FilterOptions): seq[LogObject] +proc eth_getLogs(filterOptions: JsonNode): JsonNode + proc eth_getWork(): seq[UInt256] proc eth_submitWork(nonce: int64, powHash: Uint256, mixDigest: Uint256): bool proc eth_submitHashrate(hashRate: UInt256, id: Uint256): bool diff --git a/web3/ethhexstrings.nim b/web3/ethhexstrings.nim index 7d8a687..2f9ecb9 100644 --- a/web3/ethhexstrings.nim +++ b/web3/ethhexstrings.nim @@ -1,3 +1,5 @@ +import strutils + type HexQuantityStr* = distinct string HexDataStr* = distinct string diff --git a/web3/ethtypes.nim b/web3/ethtypes.nim index 4958a00..71ea3e8 100644 --- a/web3/ethtypes.nim +++ b/web3/ethtypes.nim @@ -13,10 +13,12 @@ type Address* = distinct array[20, byte] TxHash* = FixedBytes[32] + Quantity* = distinct uint64 + EthSend* = object source*: Address # the address the transaction is send from. to*: Option[Address] # (optional when creating new contract) the address the transaction is directed to. - gas*: Option[int] # (optional, default: 90000) integer of the gas provided for the transaction execution. It will return unused gas. + gas*: Option[Quantity] # (optional, default: 90000) integer of the gas provided for the transaction execution. It will return unused gas. gasPrice*: Option[int] # (optional, default: To-Be-Determined) integer of the gasPrice used for each paid gas. value*: Option[Uint256] # (optional) integer of the value sent with this transaction. data*: string # the compiled code of a contract OR the hash of the invoked method signature and encoded parameters. For details see Ethereum Contract ABI. @@ -33,7 +35,7 @@ type EthCall* = object source*: Option[Address] # (optional) The address the transaction is send from. to*: Address # The address the transaction is directed to. - gas*: Option[int] # (optional) Integer of the gas provided for the transaction execution. eth_call consumes zero gas, but this parameter may be needed by some executions. + gas*: Option[Quantity] # (optional) Integer of the gas provided for the transaction execution. eth_call consumes zero gas, but this parameter may be needed by some executions. gasPrice*: Option[int] # (optional) Integer of the gasPrice used for each paid gas. value*: Option[int] # (optional) Integer of the value sent with this transaction. data*: Option[string] # (optional) Hash of the method signature and encoded parameters. For details see Ethereum Contract ABI. @@ -78,7 +80,7 @@ type to*: Address # address of the receiver. null when its a contract creation transaction. value*: int64 # value transferred in Wei. gasPrice*: int64 # gas price provided by the sender in Wei. - gas*: int64 # gas provided by the sender. + gas*: Quantity # gas provided by the sender. input*: seq[byte] # the data send along with the transaction. ReceiptKind* = enum rkRoot, rkStatus @@ -110,7 +112,7 @@ type FilterOptions* = object fromBlock*: Option[string] # (optional, default: "latest") integer block number, or "latest" for the last mined block or "pending", "earliest" for not yet mined transactions. toBlock*: Option[string] # (optional, default: "latest") integer block number, or "latest" for the last mined block or "pending", "earliest" for not yet mined transactions. - address*: Option[string] # (optional) contract address or a list of addresses from which logs should originate. + address*: Option[Address] # (optional) contract address or a list of addresses from which logs should originate. topics*: Option[seq[string]]#Option[seq[FilterData]] # (optional) list of DATA topics. Topics are order-dependent. Each topic can also be a list of DATA with "or" options. LogObject* = object diff --git a/web3/stintjson.nim b/web3/stintjson.nim index 71523a6..86c4852 100644 --- a/web3/stintjson.nim +++ b/web3/stintjson.nim @@ -1,16 +1,8 @@ -import json, options, stint, byteutils +import json, options, stint, byteutils, strutils from json_rpc/rpcserver import expect -import ethtypes +import ethtypes, ethhexstrings -template stintStr(n: UInt256|Int256): JsonNode = - var s = n.toHex - if s.len mod 2 != 0: s = "0" & s - s = "0x" & s - %s - -proc `%`*(n: UInt256): JsonNode = n.stintStr - -proc `%`*(n: Int256): JsonNode = n.stintStr +proc `%`*(n: Int256|UInt256): JsonNode = %("0x" & n.toHex) # allows UInt256 to be passed as a json string proc fromJson*(n: JsonNode, argName: string, result: var UInt256) = @@ -47,6 +39,16 @@ proc fromJson*(n: JsonNode, argName: string, result: var Address) {.inline.} = # expects base 16 string, starting with "0x" bytesFromJson(n, argName, array[20, byte](result)) +proc fromJson*(n: JsonNode, argName: string, result: var Quantity) {.inline.} = + if n.kind == JInt: + result = Quantity(n.getBiggestInt) + else: + n.kind.expect(JString, argName) + result = Quantity(parseHexInt(n.getStr)) + +proc `%`*(v: Quantity): JsonNode = + result = %encodeQuantity(v.uint64) + proc `%`*[N](v: FixedBytes[N]): JsonNode = result = %("0x" & array[N, byte](v).toHex)