nim-web3/web3.nim

780 lines
26 KiB
Nim
Raw Normal View History

2019-06-12 16:46:29 +00:00
import macros, strutils, options, math, json, tables
from os import DirSep
2018-12-21 17:27:09 +00:00
import
2019-06-12 16:46:29 +00:00
nimcrypto, stint, httputils, chronicles, chronos, json_rpc/rpcclient,
byteutils
2019-06-10 10:57:51 +00:00
import web3/[ethtypes, ethprocs, stintjson, ethhexstrings]
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
export ethtypes
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-01-22 16:38:34 +00:00
Sender*[T] = ref object
2019-06-12 16:46:29 +00:00
web3*: Web3
contractAddress*, fromAddress: Address
value*: Uint256
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
2019-06-12 16:46:29 +00:00
Subscription* = ref object
id*: string
web3*: Web3
callback*: proc(j: JsonNode)
pendingEvents: seq[JsonNode]
historicalEventsProcessed: bool
removed: bool
2019-06-12 16:46:29 +00:00
proc handleSubscriptionNotification(w: Web3, j: JsonNode) =
let s = w.subscriptions.getOrDefault(j{"subscription"}.getStr())
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)
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)
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()
2019-06-12 16:46:29 +00:00
proc subscribe*(w: Web3, name: string, options: JsonNode, callback: proc(j: JsonNode)): Future[Subscription] {.async.} =
var options = options
if options.isNil: options = newJNull()
let id = await w.provider.eth_subscribe(name, options)
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)
2019-06-12 16:46:29 +00:00
proc unsubscribe*(s: Subscription): Future[void] {.async.} =
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
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
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
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
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
func fixedEncode(a: openarray[byte]): EncodeResult =
result = (dynamic: false, data: "00".repeat(32 - a.len mod 32) & byteutils.toHex(a))
2019-06-12 16:46:29 +00:00
func encode*[N](b: FixedBytes[N]): EncodeResult = fixedEncode(array[N, byte](b))
func encode*(b: Address): EncodeResult = fixedEncode(array[20, byte](b))
proc skip0xPrefix(s: string): int =
if s.len > 1 and s[0] == '0' and s[1] in {'x', 'X'}: 2
2019-06-12 16:46:29 +00:00
else: 0
proc strip0xPrefix(s: string): string =
let prefixLen = skip0xPrefix(s)
if prefixLen != 0:
s[prefixLen .. ^1]
else:
s
proc fromHexAux(s: string, result: var openarray[byte]) =
let prefixLen = skip0xPrefix(s)
2019-06-12 16:46:29 +00:00
let meaningfulLen = s.len - prefixLen
let requiredChars = result.len * 2
if meaningfulLen > requiredChars:
let start = s.len - requiredChars
hexToByteArray(s[start .. s.len - 1], result)
elif meaningfulLen == requiredChars:
hexToByteArray(s, result)
else:
raise newException(ValueError, "Short hex string (" & $meaningfulLen & ") for Bytes[" & $result.len & "]")
func fromHex*[N](x: type FixedBytes[N], s: string): FixedBytes[N] {.inline.} =
fromHexAux(s, array[N, byte](result))
func fromHex*(x: type Address, s: string): Address {.inline.} =
fromHexAux(s, array[20, byte](result))
func decodeFixed(input: string, offset: int, to: var openarray[byte]): int =
let meaningfulLen = to.len * 2
let padding = (32 - to.len mod 32) * 2
let offset = offset + padding
fromHexAux(input[offset .. offset + meaningfulLen - 1], to)
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 fromHex*[N](x: type DynamicBytes[N], s: string): DynamicBytes[N] {.inline.} =
fromHexAux(s, array[N, byte](result))
func decodeDynamic(input: string, offset: int, to: var openarray[byte]): int =
var dataOffset, dataLen: UInt256
result = decode(input, offset, dataOffset)
discard decode(input, dataOffset.toInt * 2, dataLen)
# TODO: Check data len, and raise?
let meaningfulLen = to.len * 2
let actualDataOffset = (dataOffset.toInt + 32) * 2
fromHexAux(input[actualDataOffset .. actualDataOffset + meaningfulLen - 1], to)
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:
"byte" & $T.N
elif T is StUint:
"uint" & $T.bits
elif T is Address:
"address"
else:
unknownType(T)
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
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
# func to*(x: `identInt`, `identT`: typedesc[`identFixed`]): `identT` =
# T(x)
2018-12-21 17:27:09 +00:00
# func to*(x: `identUint`, `identT`: typedesc[`identUfixed`]): `identT` =
# T(x)
2018-12-21 17:27:09 +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
# func encode*[N: static[int]](x: `identUfixed`[N]): EncodeResult =
# encode(`identUint`(x) * (10 ^ N).to(`identUint`))
2018-12-21 17:27:09 +00:00
# func decode*[N: static[int]](input: string, to: `identFixed`[N]): `identFixed`[N] =
# decode(input, `identInt`) div / (10 ^ N).to(`identInt`)
# func decode*[N: static[int]](input: string, to: `identUfixed`[N]): `identFixed`[N] =
# decode(input, `identUint`) div / (10 ^ N).to(`identUint`)
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
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
`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
echo result.repr
makeTypeEnum()
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))
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
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
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
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
2019-01-14 22:10:34 +00:00
SequenceKind = enum
single, fixed, dynamic
2018-12-21 17:27:09 +00:00
FunctionInputOutput = object
name: string
2019-06-12 16:46:29 +00:00
typ: string
2019-01-14 22:10:34 +00:00
case sequenceKind: SequenceKind
of single, dynamic: discard
of fixed:
count: int
2018-12-21 17:27:09 +00:00
EventInput = object
name: string
2019-06-12 16:46:29 +00:00
typ: string
2018-12-21 17:27:09 +00:00
indexed: bool
2019-01-14 22:10:34 +00:00
case sequenceKind: SequenceKind
of single, dynamic: discard
of fixed:
count: int
2018-12-21 17:27:09 +00:00
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
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:
result.add(newCall(bindSym"typeSignature", ident input.typ))
2018-12-21 17:27:09 +00:00
if i != function.inputs.high:
result.add(newLit(","))
result.add(newLit(")"))
result = newCall(ident"static", result)
2019-01-09 21:55:03 +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] =
#if outputNode.kind == nnkIdent:
# result.add FunctionInputOutput(
# name: "",
2019-06-12 16:46:29 +00:00
# typ: $outputNode.ident
# )
case outputNode.kind:
of nnkBracketExpr:
2018-12-21 17:27:09 +00:00
result.add FunctionInputOutput(
name: "",
2019-06-12 16:46:29 +00:00
typ: $outputNode[0].ident,
sequenceKind: if outputNode.len == 1:
dynamic
else:
fixed
)
if outputNode.len == 2:
result[^1].count = outputNode[1].intVal.int
of nnkIdent:
result.add FunctionInputOutput(
name: "",
2019-06-12 16:46:29 +00:00
typ: $outputNode.ident,
sequenceKind: single
2018-12-21 17:27:09 +00:00
)
else:
discard
2018-12-21 17:27:09 +00:00
proc parseInputs(inputNodes: NimNode): seq[FunctionInputOutput] =
for i in 1..<inputNodes.len:
let input = inputNodes[i]
if input.kind == nnkIdentDefs:
2019-01-14 22:10:34 +00:00
echo input.repr
2019-06-12 16:46:29 +00:00
# echo input.treerepr
2019-01-14 22:10:34 +00:00
if input[1].kind == nnkBracketExpr:
result.add FunctionInputOutput(
name: $input[0].ident,
2019-06-12 16:46:29 +00:00
typ: $input[1][0].ident,
2019-01-17 15:22:45 +00:00
sequenceKind: if input[1].len == 1:
2019-01-14 22:10:34 +00:00
dynamic
2019-01-17 15:22:45 +00:00
else:
fixed
2019-01-14 22:10:34 +00:00
)
if input[1].len == 2:
result[^1].count = input[1][1].intVal.int
else:
result.add FunctionInputOutput(
name: $input[0].ident,
2019-06-12 16:46:29 +00:00
typ: $input[1].ident,
2019-01-14 22:10:34 +00:00
sequenceKind: single
)
2018-12-21 17:27:09 +00:00
proc parseEventInputs(inputNodes: NimNode): seq[EventInput] =
for i in 1..<inputNodes.len:
let input = inputNodes[i]
if input.kind == nnkIdentDefs:
case input[1].kind:
of nnkIdent:
result.add EventInput(
name: $input[0].ident,
2019-06-12 16:46:29 +00:00
typ: $input[1].ident,
2018-12-21 17:27:09 +00:00
indexed: false
)
of nnkBracketExpr:
2019-01-17 15:22:45 +00:00
#doAssert($input[1][0].ident == "indexed",
# "Only `indexed` is allowed as option for event inputs")
if $input[1][0].ident == "indexed":
result.add EventInput(
name: $input[0].ident,
2019-06-12 16:46:29 +00:00
typ: $input[1][1].ident,
2019-01-17 15:22:45 +00:00
indexed: true
)
else:
result.add EventInput(
name: $input[0].ident,
2019-06-12 16:46:29 +00:00
typ: $input[1][0].ident,
2019-01-17 15:22:45 +00:00
indexed: false,
sequenceKind: if input[1].len == 1:
dynamic
else:
fixed
)
if input[1].len != 1:
result[^1].count = input[1][1].intVal.int
2018-12-21 17:27:09 +00:00
else:
doAssert(false,
"Can't have anything but ident or bracket expression here")
echo body.treeRepr
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
isconstructor = procdef[4].findChild(it.ident == !"constructor") != nil
isevent = procdef[4].findChild(it.ident == !"event") != nil
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
ispure = procdef[4].findChild(it.ident == !"pure") != nil
isview = procdef[4].findChild(it.ident == !"view") != nil
ispayable = procdef[4].findChild(it.ident == !"payable") != nil
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(
name: $procdef[0].ident,
stateMutability: if ispure: pure elif isview: view elif ispayable: payable else: nonpayable,
inputs: parseInputs(procdef[3]),
outputs: parseOutputs(procdef[3][0])
)
else:
let isanonymous = procdef[4].findChild(it.ident == !"anonymous") != nil
doAssert(procdef[3][0].kind == nnkEmpty,
"Events can't have return values")
events.add EventObject(
name: $procdef[0].ident,
inputs: parseEventInputs(procdef[3]),
anonymous: isanonymous
)
echo constructor
echo functions
echo events
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"
result.add quote do:
type
2019-06-12 16:46:29 +00:00
`cname` = object
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:
2019-06-12 16:46:29 +00:00
echo "Outputs: ", repr obj.functionObject.outputs
2018-12-21 17:27:09 +00:00
let
signature = getSignature(obj.functionObject)
2018-12-21 17:27:09 +00:00
procName = ident obj.functionObject.name
senderName = ident "sender"
output =
if obj.functionObject.stateMutability in {payable, nonpayable}:
2019-06-12 16:46:29 +00:00
ident "TxHash"
else:
if obj.functionObject.outputs.len != 1:
2019-06-12 16:46:29 +00:00
ident "void"
else:
2019-06-12 16:46:29 +00:00
ident 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:
encoding.data.len)
`encodings`.add encoding
#encodedParams = nnkInfix.newTree(
# ident "&",
# encodedParams,
# nnkCall.newTree(ident "encode", ident input.name)
#)
encoder.add quote do:
for encoding in `encodings`:
if encoding.dynamic:
`encodedParams` &= `offset`.toHex(64).toLower
`dataBuf` &= encoding.data
else:
`encodedParams` &= encoding.data
`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-01-10 11:50:51 +00:00
proc `procName`(`senderName`: Sender[`cname`]): Future[`output`] {.async.} =
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-01-17 15:22:45 +00:00
(case input.sequenceKind:
2019-06-12 16:46:29 +00:00
of single: ident input.typ
of dynamic: nnkBracketExpr.newTree(ident "seq", ident input.typ)
2019-01-17 15:22:45 +00:00
of fixed:
nnkBracketExpr.newTree(
ident "array",
nnkInfix.newTree(
ident "..",
newLit(0),
newLit(input.count)
),
2019-06-12 16:46:29 +00:00
ident input.typ
2019-01-17 15:22:45 +00:00
)
),
2018-12-21 17:27:09 +00:00
newEmptyNode()
)
case obj.functionObject.stateMutability:
of view:
let cc = ident "cc"
procDef[6].add quote do:
var `cc`: EthCall
2019-06-12 16:46:29 +00:00
`cc`.source = some(`senderName`.fromAddress)
`cc`.to = `senderName`.contractAddress
`cc`.gas = some(Quantity(3000000))
2019-01-22 15:08:24 +00:00
`encoder`
`cc`.data = some("0x" & ($keccak_256.digest(`signature`))[0..<8].toLower & `encodedParams`)
2019-06-12 16:46:29 +00:00
echo "Call data: ", `cc`.data
if output != ident "void":
procDef[6].add quote do:
2019-06-12 16:46:29 +00:00
let response = await `senderName`.web3.provider.eth_call(`cc`, "latest")
echo "Call response: ", response
var res: `output`
discard decode(strip0xPrefix(response), 0, res)
return res
else:
procDef[6].add quote do:
2019-06-12 16:46:29 +00:00
await `senderName`.provider.eth_call(`cc`, "latest")
else:
procDef[6].add quote do:
var cc: EthSend
2019-06-12 16:46:29 +00:00
cc.source = `senderName`.fromAddress
cc.to = some(`senderName`.contractAddress)
cc.value = some(`senderName`.value)
cc.gas = some(Quantity(3000000))
2019-01-22 15:08:24 +00:00
`encoder`
cc.data = "0x" & ($keccak_256.digest(`signature`))[0..<8].toLower & `encodedParams`
2019-06-12 16:46:29 +00:00
echo "Call data: ", cc.data
let response = await `senderName`.web3.provider.eth_sendTransaction(cc)
return response
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())
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)
callWithRawData = nnkCall.newTree(callbackIdent)
offset = ident "offset"
inputData = ident "inputData"
var offsetInited = false
for input in obj.eventObject.inputs:
let param = nnkIdentDefs.newTree(
2019-01-09 21:55:03 +00:00
ident input.name,
2019-06-12 16:46:29 +00:00
ident input.typ,
2019-01-09 21:55:03 +00:00
newEmptyNode()
)
params.add param
paramsWithRawData.add param
2019-01-09 21:55:03 +00:00
let
argument = genSym(nskVar)
2019-06-12 16:46:29 +00:00
kind = ident input.typ
2019-01-09 21:55:03 +00:00
if input.indexed:
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
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
callWithRawData.add argument
let
cbident = ident obj.eventObject.name
procTy = nnkProcTy.newTree(params, newEmptyNode())
signature = getSignature(obj.eventObject)
callWithRawData.add jsonIdent
paramsWithRawData.add nnkIdentDefs.newTree(
jsonIdent,
bindSym "JsonNode",
newEmptyNode()
)
let procTyWithRawData = nnkProcTy.newTree(paramsWithRawData, newEmptyNode())
2019-06-12 16:46:29 +00:00
result.add quote do:
type `cbident` = object
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):
2019-06-12 16:46:29 +00:00
`argParseBody`
`call`
proc subscribe(s: Sender[`cname`], t: typedesc[`cbident`], options: JsonNode, `callbackIdent`: `procTyWithRawData`): Future[Subscription] =
let options = addAddressAndSignatureToOptions(options, s.contractAddress, "0x" & toLowerAscii($keccak256.digest(`signature`)))
s.web3.subscribeToLogs(options) do(`jsonIdent`: JsonNode):
`argParseBody`
`callWithRawData`
2019-01-09 21:55:03 +00:00
else:
discard
2019-06-12 16:46:29 +00:00
2018-12-21 17:27:09 +00:00
echo result.repr
# This call will generate the `cc.data` part to call that contract method in the code below
#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.
#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-06-12 16:46:29 +00:00
proc contractSender*(web3: Web3, T: typedesc, toAddress, fromAddress: Address): Sender[T] =
Sender[T](web3: web3, contractAddress: toAddress, fromAddress: fromAddress)
2019-01-10 11:50:51 +00:00
proc subscribe*(s: Sender, t: typedesc, cb: proc): Future[Subscription] {.inline.} =
subscribe(s, t, nil, cb)
2019-01-22 16:38:34 +00:00
proc `$`*(b: Bool): string =
$(Stint[256](b))