2018-12-21 17:27:09 +00:00
|
|
|
import
|
2020-06-26 15:30:55 +00:00
|
|
|
macros, strutils, options, math, json, tables, uri, strformat
|
|
|
|
|
|
|
|
from os import DirSep
|
2019-06-12 16:46:29 +00:00
|
|
|
|
2020-06-26 15:30:55 +00:00
|
|
|
import
|
|
|
|
nimcrypto, stint, httputils, chronicles, chronos,
|
|
|
|
json_rpc/[rpcclient, jsonmarshal], stew/byteutils, eth/keys,
|
2020-06-30 12:54:56 +00:00
|
|
|
web3/[ethtypes, conversions, ethhexstrings, transaction_signing]
|
2018-12-21 17:27:09 +00:00
|
|
|
|
|
|
|
template sourceDir: string = currentSourcePath.rsplit(DirSep, 1)[0]
|
|
|
|
|
|
|
|
## Generate client convenience marshalling wrappers from forward declarations
|
2019-06-12 16:46:29 +00:00
|
|
|
createRpcSigs(RpcClient, sourceDir & DirSep & "web3" & DirSep & "ethcallsigs.nim")
|
|
|
|
|
|
|
|
export UInt256, Int256, Uint128, Int128
|
2019-07-31 09:16:35 +00:00
|
|
|
export ethtypes, conversions
|
2018-12-21 17:27:09 +00:00
|
|
|
|
|
|
|
type
|
2019-06-12 16:46:29 +00:00
|
|
|
Web3* = ref object
|
|
|
|
provider*: RpcClient
|
|
|
|
subscriptions*: Table[string, Subscription]
|
2019-08-05 20:08:18 +00:00
|
|
|
defaultAccount*: Address
|
2020-06-21 19:57:43 +00:00
|
|
|
privateKey*: Option[PrivateKey]
|
2020-07-28 19:54:37 +00:00
|
|
|
lastKnownNonce*: Option[Nonce]
|
2019-11-22 12:41:12 +00:00
|
|
|
onDisconnect*: proc() {.gcsafe.}
|
2019-06-12 16:46:29 +00:00
|
|
|
|
2019-01-22 16:38:34 +00:00
|
|
|
Sender*[T] = ref object
|
2019-06-12 16:46:29 +00:00
|
|
|
web3*: Web3
|
2019-08-05 20:08:18 +00:00
|
|
|
contractAddress*: Address
|
2019-01-10 11:50:51 +00:00
|
|
|
|
2019-01-22 16:38:34 +00:00
|
|
|
EncodeResult* = tuple[dynamic: bool, data: string]
|
2019-01-22 15:08:24 +00:00
|
|
|
|
2020-06-26 15:30:55 +00:00
|
|
|
SubscriptionEventHandler* = proc (j: JsonNode) {.gcsafe, raises: [Defect].}
|
|
|
|
SubscriptionErrorHandler* = proc (err: CatchableError) {.gcsafe, raises: [Defect].}
|
|
|
|
|
|
|
|
BlockHeaderHandler* = proc (b: BlockHeader) {.gcsafe, raises: [Defect].}
|
|
|
|
|
2019-06-12 16:46:29 +00:00
|
|
|
Subscription* = ref object
|
|
|
|
id*: string
|
|
|
|
web3*: Web3
|
2020-06-26 15:30:55 +00:00
|
|
|
eventHandler*: SubscriptionEventHandler
|
|
|
|
errorHandler*: SubscriptionErrorHandler
|
2019-07-02 13:55:39 +00:00
|
|
|
pendingEvents: seq[JsonNode]
|
|
|
|
historicalEventsProcessed: bool
|
|
|
|
removed: bool
|
2019-06-12 16:46:29 +00:00
|
|
|
|
2020-07-28 19:54:37 +00:00
|
|
|
ContractCallBase = ref object of RootObj
|
2019-08-05 20:08:18 +00:00
|
|
|
web3: Web3
|
|
|
|
data: string
|
|
|
|
to: Address
|
|
|
|
value: Uint256
|
|
|
|
|
2020-07-28 19:54:37 +00:00
|
|
|
ContractCall*[T] = ref object of ContractCallBase
|
2019-08-05 20:08:18 +00:00
|
|
|
|
2019-06-12 16:46:29 +00:00
|
|
|
proc handleSubscriptionNotification(w: Web3, j: JsonNode) =
|
|
|
|
let s = w.subscriptions.getOrDefault(j{"subscription"}.getStr())
|
2019-07-02 13:55:39 +00:00
|
|
|
if not s.isNil and not s.removed:
|
|
|
|
if s.historicalEventsProcessed:
|
2020-06-26 15:30:55 +00:00
|
|
|
s.eventHandler(j{"result"})
|
2019-07-02 13:55:39 +00:00
|
|
|
else:
|
|
|
|
s.pendingEvents.add(j)
|
2019-06-12 16:46:29 +00:00
|
|
|
|
|
|
|
proc newWeb3*(provider: RpcClient): Web3 =
|
|
|
|
result = Web3(provider: provider)
|
|
|
|
result.subscriptions = initTable[string, Subscription]()
|
|
|
|
let r = result
|
|
|
|
provider.setMethodHandler("eth_subscription") do(j: JsonNode):
|
|
|
|
r.handleSubscriptionNotification(j)
|
|
|
|
|
2019-07-31 09:16:35 +00:00
|
|
|
proc newWeb3*(uri: string): Future[Web3] {.async.} =
|
|
|
|
let u = parseUri(uri)
|
|
|
|
var provider: RpcClient
|
|
|
|
case u.scheme
|
|
|
|
of "http", "https":
|
|
|
|
let p = newRpcHttpClient()
|
|
|
|
await p.connect(uri)
|
|
|
|
provider = p
|
|
|
|
of "ws", "wss":
|
|
|
|
let p = newRpcWebSocketClient()
|
|
|
|
await p.connect(uri)
|
|
|
|
provider = p
|
|
|
|
else:
|
|
|
|
raise newException(CatchableError, "Unknown web3 url scheme")
|
|
|
|
result = newWeb3(provider)
|
2019-11-22 12:41:12 +00:00
|
|
|
let r = result
|
|
|
|
provider.onDisconnect = proc() =
|
|
|
|
r.subscriptions.clear()
|
|
|
|
if not r.onDisconnect.isNil:
|
|
|
|
r.onDisconnect()
|
2019-07-31 09:16:35 +00:00
|
|
|
|
2019-10-22 15:57:59 +00:00
|
|
|
proc close*(web3: Web3): Future[void] = web3.provider.close()
|
|
|
|
|
2019-07-02 13:55:39 +00:00
|
|
|
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
|
2020-06-26 15:30:55 +00:00
|
|
|
s.eventHandler(l)
|
2019-07-02 13:55:39 +00:00
|
|
|
s.historicalEventsProcessed = true
|
|
|
|
var i = 0
|
|
|
|
while i < s.pendingEvents.len: # Mind reentrancy
|
|
|
|
if s.removed: break
|
2020-06-26 15:30:55 +00:00
|
|
|
s.eventHandler(s.pendingEvents[i])
|
2019-07-02 13:55:39 +00:00
|
|
|
inc i
|
|
|
|
s.pendingEvents = @[]
|
2020-03-11 09:03:09 +00:00
|
|
|
except CatchableError as e:
|
2019-07-02 13:55:39 +00:00
|
|
|
echo "Caught exception in getHistoricalEvents: ", e.msg
|
|
|
|
echo e.getStackTrace()
|
|
|
|
|
2020-06-26 15:30:55 +00:00
|
|
|
proc subscribe*(w: Web3, name: string, options: JsonNode,
|
|
|
|
eventHandler: SubscriptionEventHandler,
|
|
|
|
errorHandler: SubscriptionErrorHandler): Future[Subscription]
|
|
|
|
{.async.} =
|
|
|
|
## Sets up a new subsciption using the `eth_subscribe` RPC call.
|
|
|
|
##
|
|
|
|
## May raise a `CatchableError` if the subscription is not established.
|
|
|
|
##
|
|
|
|
## Once the subscription is established, the `eventHandler` callback
|
|
|
|
## will be executed for each event of interest.
|
|
|
|
##
|
|
|
|
## In case of any errors or illegal behavior of the remote RPC node,
|
|
|
|
## the `errorHandler` will be executed with relevant information about
|
|
|
|
## the error.
|
|
|
|
let id = await w.provider.eth_subscribe(name, if options.isNil: newJNull()
|
|
|
|
else: options)
|
|
|
|
result = Subscription(id: id,
|
|
|
|
web3: w,
|
|
|
|
eventHandler: eventHandler,
|
|
|
|
errorHandler: errorHandler)
|
|
|
|
|
2019-06-12 16:46:29 +00:00
|
|
|
w.subscriptions[id] = result
|
|
|
|
|
2020-06-26 15:30:55 +00:00
|
|
|
proc subscribeForLogs*(w: Web3, options: JsonNode,
|
|
|
|
logsHandler: SubscriptionEventHandler,
|
2020-06-27 11:50:53 +00:00
|
|
|
errorHandler: SubscriptionErrorHandler,
|
|
|
|
withHistoricEvents = true): Future[Subscription]
|
2020-06-26 15:30:55 +00:00
|
|
|
{.async.} =
|
|
|
|
result = await subscribe(w, "logs", options, logsHandler, errorHandler)
|
2020-06-27 11:50:53 +00:00
|
|
|
if withHistoricEvents:
|
|
|
|
discard getHistoricalEvents(result, options)
|
|
|
|
else:
|
|
|
|
result.historicalEventsProcessed = true
|
2019-07-02 13:55:39 +00:00
|
|
|
|
2020-06-26 15:30:55 +00:00
|
|
|
proc subscribeForBlockHeaders*(w: Web3, options: JsonNode,
|
|
|
|
blockHeadersCallback: proc(b: BlockHeader) {.gcsafe, raises: [Defect].},
|
|
|
|
errorHandler: SubscriptionErrorHandler): Future[Subscription]
|
|
|
|
{.async.} =
|
|
|
|
proc eventHandler(json: JsonNode) {.gcsafe, raises: [Defect].} =
|
|
|
|
var blk: BlockHeader
|
|
|
|
try: fromJson(json, "result", blk)
|
|
|
|
except CatchableError as err: errorHandler(err[])
|
|
|
|
blockHeadersCallback(blk)
|
|
|
|
|
|
|
|
result = await subscribe(w, "newHeads", options, eventHandler, errorHandler)
|
|
|
|
result.historicalEventsProcessed = true
|
|
|
|
|
2019-06-12 16:46:29 +00:00
|
|
|
proc unsubscribe*(s: Subscription): Future[void] {.async.} =
|
2019-07-02 13:55:39 +00:00
|
|
|
s.web3.subscriptions.del(s.id)
|
|
|
|
s.removed = true
|
2019-06-12 16:46:29 +00:00
|
|
|
discard await s.web3.provider.eth_unsubscribe(s.id)
|
2018-12-21 17:27:09 +00:00
|
|
|
|
2019-01-29 14:17:20 +00:00
|
|
|
func encode*[bits: static[int]](x: Stuint[bits]): EncodeResult =
|
2018-12-21 17:27:09 +00:00
|
|
|
## Encodes a `Stuint` to a textual representation for use in the JsonRPC
|
|
|
|
## `sendTransaction` call.
|
2019-01-22 15:08:24 +00:00
|
|
|
(dynamic: false, data: '0'.repeat((256 - bits) div 4) & x.dumpHex)
|
2018-12-21 17:27:09 +00:00
|
|
|
|
2019-01-29 14:17:20 +00:00
|
|
|
func encode*[bits: static[int]](x: Stint[bits]): EncodeResult =
|
2018-12-21 17:27:09 +00:00
|
|
|
## Encodes a `Stint` to a textual representation for use in the JsonRPC
|
|
|
|
## `sendTransaction` call.
|
2019-01-22 15:08:24 +00:00
|
|
|
(dynamic: false,
|
|
|
|
data:
|
|
|
|
if x.isNegative:
|
|
|
|
'f'.repeat((256 - bits) div 4) & x.dumpHex
|
|
|
|
else:
|
|
|
|
'0'.repeat((256 - bits) div 4) & x.dumpHex
|
|
|
|
)
|
2018-12-21 17:27:09 +00:00
|
|
|
|
2019-06-25 17:51:35 +00:00
|
|
|
func decode*(input: string, offset: int, to: var Stuint): int =
|
|
|
|
let meaningfulLen = to.bits div 8 * 2
|
|
|
|
to = type(to).fromHex(input[offset .. offset + meaningfulLen - 1])
|
|
|
|
meaningfulLen
|
2019-01-29 14:17:20 +00:00
|
|
|
|
2019-06-25 17:51:35 +00:00
|
|
|
func decode*[N](input: string, offset: int, to: var Stint[N]): int =
|
|
|
|
let meaningfulLen = N div 8 * 2
|
|
|
|
fromHex(input[offset .. offset + meaningfulLen], to)
|
|
|
|
meaningfulLen
|
2019-01-29 14:17:20 +00:00
|
|
|
|
2019-06-25 17:51:35 +00:00
|
|
|
func fixedEncode(a: openarray[byte]): EncodeResult =
|
2019-09-05 17:35:21 +00:00
|
|
|
var padding = a.len mod 32
|
|
|
|
if padding != 0: padding = 32 - padding
|
|
|
|
result = (dynamic: false, data: "00".repeat(padding) & byteutils.toHex(a))
|
2019-06-12 16:46:29 +00:00
|
|
|
|
2019-06-25 17:51:35 +00:00
|
|
|
func encode*[N](b: FixedBytes[N]): EncodeResult = fixedEncode(array[N, byte](b))
|
|
|
|
func encode*(b: Address): EncodeResult = fixedEncode(array[20, byte](b))
|
|
|
|
|
|
|
|
func decodeFixed(input: string, offset: int, to: var openarray[byte]): int =
|
|
|
|
let meaningfulLen = to.len * 2
|
2019-09-05 17:35:21 +00:00
|
|
|
var padding = to.len mod 32
|
|
|
|
if padding != 0: padding = (32 - padding) * 2
|
2019-06-25 17:51:35 +00:00
|
|
|
let offset = offset + padding
|
2020-07-09 22:07:58 +00:00
|
|
|
hexToByteArray(input[offset .. offset + meaningfulLen - 1], to)
|
2019-06-25 17:51:35 +00:00
|
|
|
meaningfulLen + padding
|
|
|
|
|
|
|
|
func decode*[N](input: string, offset: int, to: var FixedBytes[N]): int {.inline.} =
|
|
|
|
decodeFixed(input, offset, array[N, byte](to))
|
|
|
|
|
|
|
|
func decode*(input: string, offset: int, to: var Address): int {.inline.} =
|
|
|
|
decodeFixed(input, offset, array[20, byte](to))
|
|
|
|
|
|
|
|
func encodeDynamic(v: openarray[byte]): EncodeResult =
|
|
|
|
result.dynamic = true
|
|
|
|
result.data = v.len.toHex(64).toLower
|
|
|
|
for y in v:
|
|
|
|
result.data &= y.toHex.toLower
|
|
|
|
result.data &= "00".repeat(v.len mod 32)
|
|
|
|
|
|
|
|
func encode*[N](x: DynamicBytes[N]): EncodeResult {.inline.} =
|
|
|
|
encodeDynamic(array[N, byte](x))
|
|
|
|
|
|
|
|
func decodeDynamic(input: string, offset: int, to: var openarray[byte]): int =
|
|
|
|
var dataOffset, dataLen: UInt256
|
|
|
|
result = decode(input, offset, dataOffset)
|
2020-07-15 03:03:36 +00:00
|
|
|
discard decode(input, dataOffset.truncate(int) * 2, dataLen)
|
2019-06-25 17:51:35 +00:00
|
|
|
# TODO: Check data len, and raise?
|
|
|
|
let meaningfulLen = to.len * 2
|
2020-07-15 03:03:36 +00:00
|
|
|
let actualDataOffset = (dataOffset.truncate(int) + 32) * 2
|
2020-07-09 22:07:58 +00:00
|
|
|
hexToByteArray(input[actualDataOffset .. actualDataOffset + meaningfulLen - 1], to)
|
2019-06-25 17:51:35 +00:00
|
|
|
|
|
|
|
func decode*[N](input: string, offset: int, to: var DynamicBytes[N]): int {.inline.} =
|
|
|
|
decodeDynamic(input, offset, array[N, byte](to))
|
|
|
|
|
|
|
|
proc unknownType() = discard # Used for informative errors
|
|
|
|
|
|
|
|
template typeSignature(T: typedesc): string =
|
|
|
|
when T is string:
|
|
|
|
"string"
|
|
|
|
elif T is DynamicBytes:
|
|
|
|
"bytes"
|
|
|
|
elif T is FixedBytes:
|
2019-09-05 17:35:21 +00:00
|
|
|
"bytes" & $T.N
|
2019-06-25 17:51:35 +00:00
|
|
|
elif T is StUint:
|
|
|
|
"uint" & $T.bits
|
|
|
|
elif T is Address:
|
|
|
|
"address"
|
|
|
|
else:
|
|
|
|
unknownType(T)
|
2019-01-29 14:17:20 +00:00
|
|
|
|
2019-08-05 20:08:18 +00:00
|
|
|
proc initContractCall[T](web3: Web3, data: string, to: Address): ContractCall[T] {.inline.} =
|
2020-07-28 19:54:37 +00:00
|
|
|
ContractCall[T](web3: web3, data: data, to: to)
|
2019-08-05 20:08:18 +00:00
|
|
|
|
2018-12-21 17:27:09 +00:00
|
|
|
macro makeTypeEnum(): untyped =
|
|
|
|
## This macro creates all the various types of Solidity contracts and maps
|
|
|
|
## them to the type used for their encoding. It also creates an enum to
|
|
|
|
## identify these types in the contract signatures, along with encoder
|
|
|
|
## functions used in the generated procedures.
|
|
|
|
result = newStmtList()
|
|
|
|
var lastpow2: int
|
|
|
|
for i in countdown(256, 8, 8):
|
|
|
|
let
|
|
|
|
identUint = newIdentNode("Uint" & $i)
|
|
|
|
identInt = newIdentNode("Int" & $i)
|
|
|
|
if ceil(log2(i.float)) == floor(log2(i.float)):
|
|
|
|
lastpow2 = i
|
2019-06-12 16:46:29 +00:00
|
|
|
if i notin {256, 125}: # Int/Uint256/128 are already defined in stint. No need to repeat.
|
|
|
|
result.add quote do:
|
|
|
|
type
|
|
|
|
`identUint`* = Stuint[`lastpow2`]
|
|
|
|
`identInt`* = Stint[`lastpow2`]
|
2018-12-21 17:27:09 +00:00
|
|
|
let
|
|
|
|
identUint = ident("Uint")
|
|
|
|
identInt = ident("Int")
|
|
|
|
identBool = ident("Bool")
|
|
|
|
result.add quote do:
|
|
|
|
type
|
|
|
|
`identUint`* = Uint256
|
|
|
|
`identInt`* = Int256
|
|
|
|
`identBool`* = distinct Int256
|
2019-06-25 17:51:35 +00:00
|
|
|
|
2018-12-21 17:27:09 +00:00
|
|
|
for m in countup(8, 256, 8):
|
|
|
|
let
|
|
|
|
identInt = ident("Int" & $m)
|
|
|
|
identUint = ident("Uint" & $m)
|
|
|
|
identFixed = ident "Fixed" & $m
|
|
|
|
identUfixed = ident "Ufixed" & $m
|
|
|
|
identT = ident "T"
|
|
|
|
result.add quote do:
|
|
|
|
# Fixed stuff is not actually implemented yet, these procedures don't
|
|
|
|
# do what they are supposed to.
|
|
|
|
type
|
2019-01-22 16:38:34 +00:00
|
|
|
`identFixed`*[N: static[int]] = distinct `identInt`
|
|
|
|
`identUfixed`*[N: static[int]] = distinct `identUint`
|
2018-12-21 17:27:09 +00:00
|
|
|
|
2019-06-25 17:51:35 +00:00
|
|
|
# func to*(x: `identInt`, `identT`: typedesc[`identFixed`]): `identT` =
|
|
|
|
# T(x)
|
2018-12-21 17:27:09 +00:00
|
|
|
|
2019-06-25 17:51:35 +00:00
|
|
|
# func to*(x: `identUint`, `identT`: typedesc[`identUfixed`]): `identT` =
|
|
|
|
# T(x)
|
2018-12-21 17:27:09 +00:00
|
|
|
|
2019-06-25 17:51:35 +00:00
|
|
|
# func encode*[N: static[int]](x: `identFixed`[N]): EncodeResult =
|
|
|
|
# encode(`identInt`(x) * (10 ^ N).to(`identInt`))
|
2018-12-21 17:27:09 +00:00
|
|
|
|
2019-06-25 17:51:35 +00:00
|
|
|
# func encode*[N: static[int]](x: `identUfixed`[N]): EncodeResult =
|
|
|
|
# encode(`identUint`(x) * (10 ^ N).to(`identUint`))
|
2018-12-21 17:27:09 +00:00
|
|
|
|
2019-06-25 17:51:35 +00:00
|
|
|
# func decode*[N: static[int]](input: string, to: `identFixed`[N]): `identFixed`[N] =
|
|
|
|
# decode(input, `identInt`) div / (10 ^ N).to(`identInt`)
|
2019-01-29 14:17:20 +00:00
|
|
|
|
2019-06-25 17:51:35 +00:00
|
|
|
# func decode*[N: static[int]](input: string, to: `identUfixed`[N]): `identFixed`[N] =
|
|
|
|
# decode(input, `identUint`) div / (10 ^ N).to(`identUint`)
|
2019-01-29 14:17:20 +00:00
|
|
|
|
2018-12-21 17:27:09 +00:00
|
|
|
let
|
|
|
|
identFixed = ident("Fixed")
|
|
|
|
identUfixed = ident("Ufixed")
|
|
|
|
result.add quote do:
|
|
|
|
type
|
2019-01-22 16:38:34 +00:00
|
|
|
`identFixed`* = distinct Int128
|
|
|
|
`identUfixed`* = distinct Uint128
|
2019-06-25 17:51:35 +00:00
|
|
|
for i in 1..256:
|
2018-12-21 17:27:09 +00:00
|
|
|
let
|
|
|
|
identBytes = ident("Bytes" & $i)
|
|
|
|
identResult = ident "result"
|
|
|
|
result.add quote do:
|
|
|
|
type
|
2019-06-25 17:51:35 +00:00
|
|
|
`identBytes`* = DynamicBytes[`i`]
|
2019-06-12 16:46:29 +00:00
|
|
|
|
|
|
|
#result.add newEnum(ident "FieldKind", fields, public = true, pure = true)
|
2018-12-21 17:27:09 +00:00
|
|
|
|
|
|
|
makeTypeEnum()
|
|
|
|
|
2019-06-25 17:51:35 +00:00
|
|
|
func encode*(x: Bool): EncodeResult = encode(Int256(x))
|
|
|
|
func decode*[N](input: string, offset: int, to: var Bool): int {.inline.} =
|
|
|
|
decode(input, offset, Stint(to))
|
|
|
|
|
2020-03-11 09:03:09 +00:00
|
|
|
func decode*(input: string, offset: int, obj: var object): int =
|
|
|
|
var offset = offset
|
|
|
|
for field in fields(obj):
|
|
|
|
offset += decode(input, offset, field)
|
|
|
|
|
2019-01-17 15:22:45 +00:00
|
|
|
type
|
|
|
|
Encodable = concept x
|
2019-01-22 15:08:24 +00:00
|
|
|
encode(x) is EncodeResult
|
2019-01-17 15:22:45 +00:00
|
|
|
|
2019-01-29 14:17:20 +00:00
|
|
|
func encode*(x: seq[Encodable]): EncodeResult =
|
2019-01-22 15:08:24 +00:00
|
|
|
result.dynamic = true
|
|
|
|
result.data = x.len.toHex(64).toLower
|
|
|
|
var
|
|
|
|
offset = 32*x.len
|
|
|
|
data = ""
|
2019-01-17 15:22:45 +00:00
|
|
|
for i in x:
|
2019-01-22 15:08:24 +00:00
|
|
|
let encoded = encode(i)
|
|
|
|
if encoded.dynamic:
|
|
|
|
result.data &= offset.toHex(64).toLower
|
|
|
|
data &= encoded.data
|
|
|
|
else:
|
|
|
|
result.data &= encoded.data
|
|
|
|
offset += encoded.data.len
|
|
|
|
result.data &= data
|
2019-01-17 15:22:45 +00:00
|
|
|
|
2019-01-29 14:17:20 +00:00
|
|
|
func decode*[T](input: string, to: seq[T]): seq[T] =
|
|
|
|
var count = input[0..64].decode(Stuint)
|
|
|
|
result = newSeq[T](count)
|
|
|
|
for i in 0..count:
|
|
|
|
result[i] = input[i*64 .. (i+1)*64].decode(T)
|
|
|
|
|
|
|
|
func encode*(x: openArray[Encodable]): EncodeResult =
|
2019-01-22 15:08:24 +00:00
|
|
|
result.dynamic = false
|
|
|
|
result.data = ""
|
|
|
|
var
|
|
|
|
offset = 32*x.len
|
|
|
|
data = ""
|
2019-01-17 15:22:45 +00:00
|
|
|
for i in x:
|
2019-01-22 15:08:24 +00:00
|
|
|
let encoded = encode(i)
|
|
|
|
if encoded.dynamic:
|
|
|
|
result.data &= offset.toHex(64).toLower
|
|
|
|
data &= encoded.data
|
|
|
|
else:
|
|
|
|
result.data &= encoded.data
|
|
|
|
offset += encoded.data.len
|
2019-01-17 15:22:45 +00:00
|
|
|
|
2019-01-29 14:17:20 +00:00
|
|
|
func decode*[T; I: static int](input: string, to: array[0..I, T]): array[0..I, T] =
|
|
|
|
for i in 0..I:
|
|
|
|
result[i] = input[i*64 .. (i+1)*64].decode(T)
|
|
|
|
|
2018-12-21 17:27:09 +00:00
|
|
|
type
|
|
|
|
InterfaceObjectKind = enum
|
|
|
|
function, constructor, event
|
|
|
|
MutabilityKind = enum
|
|
|
|
pure, view, nonpayable, payable
|
|
|
|
FunctionInputOutput = object
|
|
|
|
name: string
|
2019-09-05 17:35:21 +00:00
|
|
|
typ: NimNode
|
2018-12-21 17:27:09 +00:00
|
|
|
EventInput = object
|
|
|
|
name: string
|
2019-09-05 17:35:21 +00:00
|
|
|
typ: NimNode
|
2018-12-21 17:27:09 +00:00
|
|
|
indexed: bool
|
|
|
|
FunctionObject = object
|
|
|
|
name: string
|
|
|
|
stateMutability: MutabilityKind
|
|
|
|
inputs: seq[FunctionInputOutput]
|
|
|
|
outputs: seq[FunctionInputOutput]
|
|
|
|
ConstructorObject = object
|
|
|
|
stateMutability: MutabilityKind
|
|
|
|
inputs: seq[FunctionInputOutput]
|
|
|
|
outputs: seq[FunctionInputOutput]
|
|
|
|
EventObject = object
|
|
|
|
name: string
|
|
|
|
inputs: seq[EventInput]
|
|
|
|
anonymous: bool
|
|
|
|
|
|
|
|
InterfaceObject = object
|
|
|
|
case kind: InterfaceObjectKind
|
|
|
|
of function: functionObject: FunctionObject
|
|
|
|
of constructor: constructorObject: ConstructorObject
|
|
|
|
of event: eventObject: EventObject
|
|
|
|
|
2019-06-25 17:51:35 +00:00
|
|
|
proc joinStrings(s: varargs[string]): string = join(s)
|
|
|
|
|
|
|
|
proc getSignature(function: FunctionObject | EventObject): NimNode =
|
|
|
|
result = newCall(bindSym"joinStrings")
|
|
|
|
result.add(newLit(function.name & "("))
|
2018-12-21 17:27:09 +00:00
|
|
|
for i, input in function.inputs:
|
2019-09-05 17:35:21 +00:00
|
|
|
result.add(newCall(bindSym"typeSignature", input.typ))
|
2018-12-21 17:27:09 +00:00
|
|
|
if i != function.inputs.high:
|
2019-06-25 17:51:35 +00:00
|
|
|
result.add(newLit(","))
|
|
|
|
result.add(newLit(")"))
|
|
|
|
result = newCall(ident"static", result)
|
2019-01-09 21:55:03 +00:00
|
|
|
|
2019-07-02 13:55:39 +00:00
|
|
|
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)
|
|
|
|
|
2018-12-21 17:27:09 +00:00
|
|
|
proc parseContract(body: NimNode): seq[InterfaceObject] =
|
|
|
|
proc parseOutputs(outputNode: NimNode): seq[FunctionInputOutput] =
|
2019-09-05 17:35:21 +00:00
|
|
|
result.add FunctionInputOutput(typ: (if outputNode.kind == nnkEmpty: ident"void" else: outputNode))
|
|
|
|
|
2018-12-21 17:27:09 +00:00
|
|
|
proc parseInputs(inputNodes: NimNode): seq[FunctionInputOutput] =
|
|
|
|
for i in 1..<inputNodes.len:
|
|
|
|
let input = inputNodes[i]
|
2019-09-05 17:35:21 +00:00
|
|
|
input.expectKind(nnkIdentDefs)
|
|
|
|
let typ = input[^2]
|
|
|
|
for j in 0 .. input.len - 3:
|
|
|
|
let arg = input[j]
|
|
|
|
result.add(FunctionInputOutput(
|
|
|
|
name: $arg,
|
|
|
|
typ: typ,
|
|
|
|
))
|
|
|
|
|
2018-12-21 17:27:09 +00:00
|
|
|
proc parseEventInputs(inputNodes: NimNode): seq[EventInput] =
|
|
|
|
for i in 1..<inputNodes.len:
|
|
|
|
let input = inputNodes[i]
|
2019-09-05 17:35:21 +00:00
|
|
|
input.expectKind(nnkIdentDefs)
|
|
|
|
let typ = input[^2]
|
|
|
|
for j in 0 .. input.len - 3:
|
|
|
|
let arg = input[j]
|
|
|
|
case typ.kind:
|
2018-12-21 17:27:09 +00:00
|
|
|
of nnkBracketExpr:
|
2019-09-05 17:35:21 +00:00
|
|
|
if $typ[0] == "indexed":
|
2019-01-17 15:22:45 +00:00
|
|
|
result.add EventInput(
|
2019-09-05 17:35:21 +00:00
|
|
|
name: $arg,
|
|
|
|
typ: typ[1],
|
2019-01-17 15:22:45 +00:00
|
|
|
indexed: true
|
|
|
|
)
|
|
|
|
else:
|
2019-09-05 17:35:21 +00:00
|
|
|
result.add EventInput(name: $arg, typ: typ)
|
2018-12-21 17:27:09 +00:00
|
|
|
else:
|
2019-09-05 17:35:21 +00:00
|
|
|
result.add EventInput(name: $arg, typ: typ)
|
|
|
|
|
2018-12-21 17:27:09 +00:00
|
|
|
var
|
|
|
|
constructor: Option[ConstructorObject]
|
|
|
|
functions: seq[FunctionObject]
|
|
|
|
events: seq[EventObject]
|
|
|
|
for procdef in body:
|
|
|
|
doAssert(procdef.kind == nnkProcDef,
|
|
|
|
"Contracts can only be built with procedures")
|
|
|
|
let
|
2020-07-15 03:03:36 +00:00
|
|
|
isconstructor = procdef[4].findChild(it.strVal == "constructor") != nil
|
|
|
|
isevent = procdef[4].findChild(it.strVal == "event") != nil
|
2018-12-21 17:27:09 +00:00
|
|
|
doAssert(not (isconstructor and constructor.isSome),
|
|
|
|
"Contract can only have a single constructor")
|
|
|
|
doAssert(not (isconstructor and isevent),
|
|
|
|
"Can't be both event and constructor")
|
|
|
|
if not isevent:
|
|
|
|
let
|
2020-07-15 03:03:36 +00:00
|
|
|
ispure = procdef[4].findChild(it.strVal == "pure") != nil
|
|
|
|
isview = procdef[4].findChild(it.strVal == "view") != nil
|
|
|
|
ispayable = procdef[4].findChild(it.strVal == "payable") != nil
|
2018-12-21 17:27:09 +00:00
|
|
|
doAssert(not (ispure and isview),
|
|
|
|
"can't be both `pure` and `view`")
|
|
|
|
doAssert(not ((ispure or isview) and ispayable),
|
|
|
|
"can't be both `pure` or `view` while being `payable`")
|
|
|
|
if isconstructor:
|
|
|
|
constructor = some(ConstructorObject(
|
|
|
|
stateMutability: if ispure: pure elif isview: view elif ispayable: payable else: nonpayable,
|
|
|
|
inputs: parseInputs(procdef[3]),
|
|
|
|
outputs: parseOutputs(procdef[3][0])
|
|
|
|
))
|
|
|
|
else:
|
|
|
|
functions.add FunctionObject(
|
2020-07-15 03:03:36 +00:00
|
|
|
name: procdef[0].strVal,
|
2018-12-21 17:27:09 +00:00
|
|
|
stateMutability: if ispure: pure elif isview: view elif ispayable: payable else: nonpayable,
|
|
|
|
inputs: parseInputs(procdef[3]),
|
|
|
|
outputs: parseOutputs(procdef[3][0])
|
|
|
|
)
|
|
|
|
else:
|
2020-07-15 03:03:36 +00:00
|
|
|
let isanonymous = procdef[4].findChild(it.strVal == "anonymous") != nil
|
2018-12-21 17:27:09 +00:00
|
|
|
doAssert(procdef[3][0].kind == nnkEmpty,
|
|
|
|
"Events can't have return values")
|
|
|
|
events.add EventObject(
|
2020-07-15 03:03:36 +00:00
|
|
|
name: procdef[0].strVal,
|
2018-12-21 17:27:09 +00:00
|
|
|
inputs: parseEventInputs(procdef[3]),
|
|
|
|
anonymous: isanonymous
|
|
|
|
)
|
2019-11-04 17:05:31 +00:00
|
|
|
|
2018-12-21 17:27:09 +00:00
|
|
|
if constructor.isSome:
|
|
|
|
result.add InterfaceObject(kind: InterfaceObjectKind.constructor, constructorObject: constructor.unsafeGet)
|
|
|
|
for function in functions:
|
|
|
|
result.add InterfaceObject(kind: InterfaceObjectKind.function, functionObject: function)
|
|
|
|
for event in events:
|
|
|
|
result.add InterfaceObject(kind: InterfaceObjectKind.event, eventObject: event)
|
|
|
|
|
2019-01-22 16:38:34 +00:00
|
|
|
macro contract*(cname: untyped, body: untyped): untyped =
|
2018-12-21 17:27:09 +00:00
|
|
|
var objects = parseContract(body)
|
|
|
|
result = newStmtList()
|
2019-01-09 21:55:03 +00:00
|
|
|
let
|
|
|
|
address = ident "address"
|
2019-01-10 11:50:51 +00:00
|
|
|
client = ident "client"
|
2019-01-09 21:55:03 +00:00
|
|
|
receipt = genSym(nskForVar)
|
2019-01-10 11:50:51 +00:00
|
|
|
receiver = ident "receiver"
|
|
|
|
eventListener = ident "eventListener"
|
2019-01-04 17:28:43 +00:00
|
|
|
result.add quote do:
|
|
|
|
type
|
2019-06-12 16:46:29 +00:00
|
|
|
`cname` = object
|
2019-01-07 16:59:57 +00:00
|
|
|
|
2018-12-21 17:27:09 +00:00
|
|
|
for obj in objects:
|
2019-01-09 21:55:03 +00:00
|
|
|
case obj.kind:
|
|
|
|
of function:
|
2018-12-21 17:27:09 +00:00
|
|
|
let
|
2019-06-25 17:51:35 +00:00
|
|
|
signature = getSignature(obj.functionObject)
|
2018-12-21 17:27:09 +00:00
|
|
|
procName = ident obj.functionObject.name
|
2019-01-04 17:28:43 +00:00
|
|
|
senderName = ident "sender"
|
2019-08-05 20:08:18 +00:00
|
|
|
output = if obj.functionObject.outputs.len != 1:
|
|
|
|
ident "void"
|
2019-01-04 17:28:43 +00:00
|
|
|
else:
|
2019-09-05 17:35:21 +00:00
|
|
|
obj.functionObject.outputs[0].typ
|
2019-01-22 15:08:24 +00:00
|
|
|
var
|
|
|
|
encodedParams = genSym(nskVar)#newLit("")
|
|
|
|
offset = genSym(nskVar)
|
|
|
|
dataBuf = genSym(nskVar)
|
|
|
|
encodings = genSym(nskVar)
|
|
|
|
encoder = newStmtList()
|
|
|
|
encoder.add quote do:
|
|
|
|
var
|
|
|
|
`offset` = 0
|
|
|
|
`encodedParams` = ""
|
|
|
|
`dataBuf` = ""
|
|
|
|
`encodings`: seq[EncodeResult]
|
2018-12-21 17:27:09 +00:00
|
|
|
for input in obj.functionObject.inputs:
|
2019-01-22 15:08:24 +00:00
|
|
|
let inputName = ident input.name
|
|
|
|
encoder.add quote do:
|
|
|
|
let encoding = encode(`inputName`)
|
|
|
|
`offset` += (if encoding.dynamic:
|
|
|
|
32
|
|
|
|
else:
|
2019-09-05 17:35:21 +00:00
|
|
|
encoding.data.len div 2)
|
2019-01-22 15:08:24 +00:00
|
|
|
`encodings`.add encoding
|
|
|
|
encoder.add quote do:
|
|
|
|
for encoding in `encodings`:
|
|
|
|
if encoding.dynamic:
|
|
|
|
`encodedParams` &= `offset`.toHex(64).toLower
|
|
|
|
`dataBuf` &= encoding.data
|
|
|
|
else:
|
|
|
|
`encodedParams` &= encoding.data
|
2019-06-25 17:51:35 +00:00
|
|
|
`offset` += encoding.data.len div 2
|
|
|
|
|
2019-01-22 15:08:24 +00:00
|
|
|
`encodedParams` &= `dataBuf`
|
2018-12-21 17:27:09 +00:00
|
|
|
var procDef = quote do:
|
2019-08-05 20:08:18 +00:00
|
|
|
proc `procName`(`senderName`: Sender[`cname`]): ContractCall[`output`] =
|
2019-06-12 16:46:29 +00:00
|
|
|
discard
|
2018-12-21 17:27:09 +00:00
|
|
|
for input in obj.functionObject.inputs:
|
|
|
|
procDef[3].add nnkIdentDefs.newTree(
|
|
|
|
ident input.name,
|
2019-09-05 17:35:21 +00:00
|
|
|
input.typ,
|
2018-12-21 17:27:09 +00:00
|
|
|
newEmptyNode()
|
|
|
|
)
|
2019-08-05 20:08:18 +00:00
|
|
|
procDef[6].add quote do:
|
|
|
|
`encoder`
|
|
|
|
return initContractCall[`output`](
|
|
|
|
`senderName`.web3,
|
|
|
|
($keccak_256.digest(`signature`))[0..<8].toLower & `encodedParams`,
|
|
|
|
`senderName`.contractAddress)
|
|
|
|
|
2018-12-21 17:27:09 +00:00
|
|
|
result.add procDef
|
2019-01-09 21:55:03 +00:00
|
|
|
of event:
|
|
|
|
if not obj.eventObject.anonymous:
|
2019-06-12 16:46:29 +00:00
|
|
|
let callbackIdent = ident "callback"
|
|
|
|
let jsonIdent = ident "j"
|
2019-01-09 21:55:03 +00:00
|
|
|
var
|
|
|
|
params = nnkFormalParams.newTree(newEmptyNode())
|
2019-07-16 17:13:25 +00:00
|
|
|
paramsWithRawData = nnkFormalParams.newTree(newEmptyNode())
|
|
|
|
|
2019-01-09 21:55:03 +00:00
|
|
|
argParseBody = newStmtList()
|
|
|
|
i = 1
|
2019-06-12 16:46:29 +00:00
|
|
|
call = nnkCall.newTree(callbackIdent)
|
2019-07-16 17:13:25 +00:00
|
|
|
callWithRawData = nnkCall.newTree(callbackIdent)
|
2019-06-25 17:51:35 +00:00
|
|
|
offset = ident "offset"
|
|
|
|
inputData = ident "inputData"
|
2019-08-01 14:55:40 +00:00
|
|
|
|
2019-06-25 17:51:35 +00:00
|
|
|
var offsetInited = false
|
|
|
|
|
|
|
|
for input in obj.eventObject.inputs:
|
2019-07-16 17:13:25 +00:00
|
|
|
let param = nnkIdentDefs.newTree(
|
2019-01-09 21:55:03 +00:00
|
|
|
ident input.name,
|
2019-09-05 17:35:21 +00:00
|
|
|
input.typ,
|
2019-01-09 21:55:03 +00:00
|
|
|
newEmptyNode()
|
|
|
|
)
|
2019-07-16 17:13:25 +00:00
|
|
|
params.add param
|
|
|
|
paramsWithRawData.add param
|
2019-01-09 21:55:03 +00:00
|
|
|
let
|
2019-06-25 17:51:35 +00:00
|
|
|
argument = genSym(nskVar)
|
2019-09-05 17:35:21 +00:00
|
|
|
kind = input.typ
|
2019-01-09 21:55:03 +00:00
|
|
|
if input.indexed:
|
2019-06-25 17:51:35 +00:00
|
|
|
argParseBody.add quote do:
|
|
|
|
var `argument`: `kind`
|
|
|
|
discard decode(strip0xPrefix(`jsonIdent`["topics"][`i`].getStr), 0, `argument`)
|
2019-01-09 21:55:03 +00:00
|
|
|
i += 1
|
2019-06-25 17:51:35 +00:00
|
|
|
else:
|
|
|
|
if not offsetInited:
|
|
|
|
argParseBody.add quote do:
|
|
|
|
var `inputData` = strip0xPrefix(`jsonIdent`["data"].getStr)
|
|
|
|
var `offset` = 0
|
|
|
|
|
|
|
|
offsetInited = true
|
|
|
|
|
|
|
|
argParseBody.add quote do:
|
|
|
|
var `argument`: `kind`
|
|
|
|
`offset` += decode(`inputData`, `offset`, `argument`)
|
2019-01-09 21:55:03 +00:00
|
|
|
call.add argument
|
2019-07-16 17:13:25 +00:00
|
|
|
callWithRawData.add argument
|
|
|
|
let
|
2020-03-07 21:37:23 +00:00
|
|
|
eventName = obj.eventObject.name
|
|
|
|
cbident = ident eventName
|
2019-07-16 17:13:25 +00:00
|
|
|
procTy = nnkProcTy.newTree(params, newEmptyNode())
|
|
|
|
signature = getSignature(obj.eventObject)
|
|
|
|
|
2020-05-08 14:20:59 +00:00
|
|
|
# generated with dumpAstGen - produces "{.raises: [Defect], gcsafe.}"
|
|
|
|
let pragmas = nnkPragma.newTree(
|
|
|
|
nnkExprColonExpr.newTree(
|
|
|
|
newIdentNode("raises"),
|
|
|
|
nnkBracket.newTree(
|
|
|
|
newIdentNode("Defect")
|
|
|
|
)
|
|
|
|
),
|
|
|
|
newIdentNode("gcsafe")
|
|
|
|
)
|
|
|
|
|
|
|
|
procTy[1] = pragmas
|
2019-09-09 20:51:24 +00:00
|
|
|
|
2019-07-16 17:13:25 +00:00
|
|
|
callWithRawData.add jsonIdent
|
|
|
|
paramsWithRawData.add nnkIdentDefs.newTree(
|
|
|
|
jsonIdent,
|
|
|
|
bindSym "JsonNode",
|
|
|
|
newEmptyNode()
|
|
|
|
)
|
|
|
|
|
|
|
|
let procTyWithRawData = nnkProcTy.newTree(paramsWithRawData, newEmptyNode())
|
2020-05-08 14:20:59 +00:00
|
|
|
procTyWithRawData[1] = pragmas
|
2019-06-12 16:46:29 +00:00
|
|
|
|
|
|
|
result.add quote do:
|
|
|
|
type `cbident` = object
|
2020-03-07 21:37:23 +00:00
|
|
|
|
2020-03-07 21:46:10 +00:00
|
|
|
template eventTopic*(T: type `cbident`): string =
|
2020-03-07 21:37:23 +00:00
|
|
|
"0x" & toLowerAscii($keccak256.digest(`signature`))
|
|
|
|
|
|
|
|
proc subscribe(s: Sender[`cname`],
|
|
|
|
t: type `cbident`,
|
|
|
|
options: JsonNode,
|
2020-06-26 15:30:55 +00:00
|
|
|
`callbackIdent`: `procTy`,
|
2020-06-27 11:50:53 +00:00
|
|
|
errorHandler: SubscriptionErrorHandler,
|
|
|
|
withHistoricEvents = true): Future[Subscription] =
|
2020-03-07 21:37:23 +00:00
|
|
|
let options = addAddressAndSignatureToOptions(options, s.contractAddress, eventTopic(`cbident`))
|
2019-07-02 13:55:39 +00:00
|
|
|
|
2020-06-26 15:30:55 +00:00
|
|
|
proc eventHandler(`jsonIdent`: JsonNode) {.gcsafe, raises: [Defect].} =
|
|
|
|
try:
|
|
|
|
`argParseBody`
|
|
|
|
`call`
|
|
|
|
except CatchableError as err:
|
|
|
|
errorHandler err[]
|
|
|
|
|
2020-06-27 11:50:53 +00:00
|
|
|
s.web3.subscribeForLogs(options, eventHandler, errorHandler, withHistoricEvents)
|
2019-07-02 13:55:39 +00:00
|
|
|
|
2020-03-07 21:37:23 +00:00
|
|
|
proc subscribe(s: Sender[`cname`],
|
|
|
|
t: type `cbident`,
|
|
|
|
options: JsonNode,
|
2020-06-26 15:30:55 +00:00
|
|
|
`callbackIdent`: `procTyWithRawData`,
|
2020-06-27 11:50:53 +00:00
|
|
|
errorHandler: SubscriptionErrorHandler,
|
|
|
|
withHistoricEvents = true): Future[Subscription] =
|
2020-03-07 21:37:23 +00:00
|
|
|
let options = addAddressAndSignatureToOptions(options, s.contractAddress, eventTopic(`cbident`))
|
2019-07-16 17:13:25 +00:00
|
|
|
|
2020-06-26 15:30:55 +00:00
|
|
|
proc eventHandler(`jsonIdent`: JsonNode) {.gcsafe, raises: [Defect].} =
|
|
|
|
try:
|
|
|
|
`argParseBody`
|
|
|
|
`callWithRawData`
|
|
|
|
except CatchableError as err:
|
|
|
|
errorHandler err[]
|
|
|
|
|
2020-06-27 11:50:53 +00:00
|
|
|
s.web3.subscribeForLogs(options, eventHandler, errorHandler, withHistoricEvents)
|
2020-06-26 15:30:55 +00:00
|
|
|
|
2019-01-09 21:55:03 +00:00
|
|
|
else:
|
|
|
|
discard
|
2019-06-12 16:46:29 +00:00
|
|
|
|
2020-03-11 09:03:09 +00:00
|
|
|
when defined(debugMacros) or defined(debugWeb3Macros):
|
|
|
|
echo result.repr
|
|
|
|
|
2020-03-07 21:46:10 +00:00
|
|
|
proc getJsonLogs*(s: Sender,
|
|
|
|
EventName: type,
|
|
|
|
fromBlock, toBlock = none(RtBlockIdentifier),
|
|
|
|
blockHash = none(BlockHash)): Future[JsonNode] =
|
|
|
|
mixin eventTopic
|
|
|
|
|
|
|
|
var options = newJObject()
|
|
|
|
options["address"] = %s.contractAddress
|
|
|
|
var topics = newJArray()
|
|
|
|
topics.elems.insert(%eventTopic(EventName), 0)
|
|
|
|
options["topics"] = topics
|
|
|
|
if blockHash.isSome:
|
|
|
|
doAssert fromBlock.isNone and toBlock.isNone
|
|
|
|
options["blockhash"] = %blockHash.unsafeGet
|
|
|
|
else:
|
|
|
|
if fromBlock.isSome:
|
|
|
|
options["fromBlock"] = %fromBlock.unsafeGet
|
|
|
|
if toBlock.isSome:
|
|
|
|
options["toBlock"] = %toBlock.unsafeGet
|
|
|
|
|
|
|
|
s.web3.provider.eth_getLogs(options)
|
|
|
|
|
2020-07-28 19:54:37 +00:00
|
|
|
proc nextNonce*(web3: Web3): Future[Nonce] {.async.} =
|
|
|
|
if web3.lastKnownNonce.isSome:
|
|
|
|
inc web3.lastKnownNonce.get
|
|
|
|
return web3.lastKnownNonce.get
|
|
|
|
else:
|
|
|
|
let fromAddress = web3.privateKey.get().toPublicKey().toCanonicalAddress.Address
|
|
|
|
result = int(await web3.provider.eth_getTransactionCount(fromAddress, "latest"))
|
|
|
|
web3.lastKnownNonce = some result
|
|
|
|
|
2019-08-05 20:08:18 +00:00
|
|
|
proc send*(web3: Web3, c: EthSend): Future[TxHash] {.async.} =
|
2020-06-21 19:57:43 +00:00
|
|
|
if web3.privateKey.isSome():
|
2019-08-05 20:08:18 +00:00
|
|
|
var cc = c
|
2020-07-28 19:54:37 +00:00
|
|
|
if cc.nonce.isNone:
|
|
|
|
cc.nonce = some(await web3.nextNonce())
|
2020-06-21 19:57:43 +00:00
|
|
|
let t = "0x" & encodeTransaction(cc, web3.privateKey.get())
|
2019-08-05 20:08:18 +00:00
|
|
|
return await web3.provider.eth_sendRawTransaction(t)
|
|
|
|
else:
|
|
|
|
return await web3.provider.eth_sendTransaction(c)
|
|
|
|
|
2020-07-28 19:54:37 +00:00
|
|
|
proc send*(c: ContractCallBase,
|
|
|
|
value = 0.u256,
|
|
|
|
gas = 3000000'u64,
|
|
|
|
gasPrice = 0): Future[TxHash] {.async.} =
|
|
|
|
let
|
|
|
|
web3 = c.web3
|
|
|
|
gasPrice = if web3.privateKey.isSome() or gasPrice != 0: some(gasPrice)
|
|
|
|
else: none(int)
|
|
|
|
nonce = if web3.privateKey.isSome(): some(await web3.nextNonce())
|
|
|
|
else: none(Nonce)
|
|
|
|
|
|
|
|
cc = EthSend(
|
|
|
|
data: "0x" & c.data,
|
|
|
|
source: web3.defaultAccount,
|
|
|
|
to: some(c.to),
|
|
|
|
gas: some(Quantity(gas)),
|
|
|
|
value: some(value),
|
|
|
|
nonce: nonce,
|
|
|
|
gasPrice: gasPrice)
|
|
|
|
|
|
|
|
return await web3.send(cc)
|
2019-08-05 20:08:18 +00:00
|
|
|
|
2020-03-05 23:56:23 +00:00
|
|
|
proc call*[T](c: ContractCall[T],
|
|
|
|
value = 0.u256,
|
|
|
|
gas = 3000000'u64,
|
|
|
|
blockNumber = high(uint64)): Future[T] {.async.} =
|
2019-08-05 20:08:18 +00:00
|
|
|
var cc: EthCall
|
|
|
|
cc.data = some("0x" & c.data)
|
|
|
|
cc.source = some(c.web3.defaultAccount)
|
|
|
|
cc.to = c.to
|
|
|
|
cc.gas = some(Quantity(gas))
|
|
|
|
cc.value = some(value)
|
2020-03-05 23:56:23 +00:00
|
|
|
let response = strip0xPrefix:
|
|
|
|
if blockNumber != high(uint64):
|
2020-06-24 12:13:36 +00:00
|
|
|
await c.web3.provider.eth_call(cc, &"0x{blockNumber:X}")
|
2020-03-05 23:56:23 +00:00
|
|
|
else:
|
|
|
|
await c.web3.provider.eth_call(cc, "latest")
|
|
|
|
|
|
|
|
if response.len > 0:
|
|
|
|
var res: T
|
|
|
|
discard decode(response, 0, res)
|
|
|
|
return res
|
|
|
|
else:
|
|
|
|
raise newException(CatchableError, "No response from the Web3 provider")
|
2019-08-05 20:08:18 +00:00
|
|
|
|
2019-10-30 13:05:44 +00:00
|
|
|
proc getMinedTransactionReceipt*(web3: Web3, tx: TxHash): Future[ReceiptObject] {.async.} =
|
|
|
|
## Returns the receipt for the transaction. Waits for it to be mined if necessary.
|
|
|
|
# TODO: Potentially more optimal solution is to subscribe and wait for appropriate
|
|
|
|
# notification. Now we're just polling every 500ms which should be ok for most cases.
|
|
|
|
var r: Option[ReceiptObject]
|
|
|
|
while r.isNone:
|
|
|
|
r = await web3.provider.eth_getTransactionReceipt(tx)
|
|
|
|
if r.isNone:
|
|
|
|
await sleepAsync(500.milliseconds)
|
|
|
|
result = r.get
|
|
|
|
|
2019-08-05 20:08:18 +00:00
|
|
|
proc exec*[T](c: ContractCall[T], value = 0.u256, gas = 3000000'u64): Future[T] {.async.} =
|
|
|
|
let h = await c.send(value, gas)
|
2019-10-30 13:05:44 +00:00
|
|
|
let receipt = await c.web3.getMinedTransactionReceipt(h)
|
2019-08-05 20:08:18 +00:00
|
|
|
|
|
|
|
# TODO: decode result from receipt
|
|
|
|
|
|
|
|
|
2018-12-21 17:27:09 +00:00
|
|
|
# This call will generate the `cc.data` part to call that contract method in the code below
|
2019-01-04 17:28:43 +00:00
|
|
|
#sendCoin(fromHex(Stuint[256], "e375b6fb6d0bf0d86707884f3952fee3977251fe"), 600.to(Stuint[256]))
|
2018-12-21 17:27:09 +00:00
|
|
|
|
|
|
|
# Set up a JsonRPC call to send a transaction
|
|
|
|
# The idea here is to let the Web3 object contain the RPC calls, then allow the
|
|
|
|
# above DSL to create helpers to create the EthSend object and perform the
|
|
|
|
# transaction. The current idea is to make all this reduce to something like:
|
|
|
|
# var
|
|
|
|
# w3 = initWeb3("127.0.0.1", 8545)
|
|
|
|
# myContract = contract:
|
|
|
|
# <DSL>
|
|
|
|
# myContract.sender("0x780bc7b4055941c2cb0ee10510e3fc837eb093c1").sendCoin(
|
|
|
|
# fromHex(Stuint[256], "e375b6fb6d0bf0d86707884f3952fee3977251fe"),
|
|
|
|
# 600.to(Stuint[256])
|
|
|
|
# )
|
|
|
|
# If the address of the contract on the chain should be part of the DSL or
|
|
|
|
# dynamically registered is still not decided.
|
2019-01-04 17:28:43 +00:00
|
|
|
#var cc: EthSend
|
|
|
|
#cc.source = [0x78.byte, 0x0b, 0xc7, 0xb4, 0x05, 0x59, 0x41, 0xc2, 0xcb, 0x0e, 0xe1, 0x05, 0x10, 0xe3, 0xfc, 0x83, 0x7e, 0xb0, 0x93, 0xc1]
|
|
|
|
#cc.to = some([0x0a.byte, 0x78, 0xc0, 0x8F, 0x31, 0x4E, 0xB2, 0x5A, 0x35, 0x1B, 0xfB, 0xA9, 0x03,0x21, 0xa6, 0x96, 0x04, 0x74, 0xbD, 0x79])
|
|
|
|
#cc.data = "0x90b98a11000000000000000000000000e375b6fb6d0bf0d86707884f3952fee3977251FE0000000000000000000000000000000000000000000000000000000000000258"
|
|
|
|
|
|
|
|
#var w3 = initWeb3("127.0.0.1", 8545)
|
|
|
|
#let response = waitFor w3.eth.eth_sendTransaction(cc)
|
|
|
|
#echo response
|
|
|
|
|
2019-08-05 20:08:18 +00:00
|
|
|
proc contractSender*(web3: Web3, T: typedesc, toAddress: Address): Sender[T] =
|
|
|
|
Sender[T](web3: web3, contractAddress: toAddress)
|
2019-01-10 11:50:51 +00:00
|
|
|
|
2020-06-19 16:42:55 +00:00
|
|
|
proc isDeployed*(s: Sender, atBlock: RtBlockIdentifier): Future[bool] {.async.} =
|
|
|
|
let
|
|
|
|
codeFut = case atBlock.kind
|
2020-06-22 12:27:47 +00:00
|
|
|
of bidNumber:
|
2020-06-19 16:42:55 +00:00
|
|
|
s.web3.provider.eth_getCode(s.contractAddress, atBlock.number)
|
2020-06-22 12:27:47 +00:00
|
|
|
of bidAlias:
|
2020-06-19 16:42:55 +00:00
|
|
|
s.web3.provider.eth_getCode(s.contractAddress, atBlock.alias)
|
|
|
|
code = await codeFut
|
|
|
|
|
|
|
|
# TODO: Check that all methods of the contract are present by
|
|
|
|
# looking for their ABI signatures within the code:
|
|
|
|
# https://ethereum.stackexchange.com/questions/11856/how-to-detect-from-web3-if-method-exists-on-a-deployed-contract
|
|
|
|
return code.len > 0
|
|
|
|
|
2019-07-02 13:55:39 +00:00
|
|
|
proc subscribe*(s: Sender, t: typedesc, cb: proc): Future[Subscription] {.inline.} =
|
2020-06-30 12:54:22 +00:00
|
|
|
subscribe(s, t, newJObject(), cb, SubscriptionErrorHandler nil)
|
2019-07-02 13:55:39 +00:00
|
|
|
|
2019-01-22 16:38:34 +00:00
|
|
|
proc `$`*(b: Bool): string =
|
2019-01-04 17:28:43 +00:00
|
|
|
$(Stint[256](b))
|