import std/[macros, strutils, options], nimcrypto/keccak, json_serialization, ./[encoding, eth_api_types], stint, stew/byteutils type ContractInvocation*[TResult, TSender] = object data*: seq[byte] sender*: TSender ContractInstance*[TContract, TSender] = object sender*: TSender ContractDeployment*[TContract, TSender] = object data*: seq[byte] sender*: TSender InterfaceObjectKind = enum function, constructor, event MutabilityKind = enum pure, view, nonpayable, payable FunctionInputOutput = object name: string typ: NimNode EventInput = object name: string typ: NimNode 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 proc keccak256Bytes(s: string): array[32, byte] {.inline.} = keccak256.digest(s).data proc initContractInvocation[TSender](TResult: typedesc, sender: TSender, data: seq[byte]): ContractInvocation[TResult, TSender] {.inline.} = ContractInvocation[TResult, TSender](data: data, sender: sender) proc joinStrings(s: varargs[string]): string = join(s) proc unknownType() = discard # Used for informative errors template seqType[T](s: typedesc[seq[T]]): typedesc = T proc typeSignature(T: typedesc): string = when T is string: "string" elif (T is DynamicBytes) or (T is seq[byte]): "bytes" elif T is FixedBytes: "bytes" & $T.N elif T is StUint: "uint" & $T.bits elif T is Address: "address" elif T is bool: "bool" elif T is seq: typeSignature(seqType(T)) & "[]" else: unknownType(T) proc getSignature(function: FunctionObject | EventObject): NimNode = result = newCall(bindSym"joinStrings") result.add(newLit(function.name & "(")) for i, input in function.inputs: result.add(newCall(bindSym"typeSignature", input.typ)) if i != function.inputs.high: result.add(newLit(",")) result.add(newLit(")")) proc parseContract(body: NimNode): seq[InterfaceObject] = proc parseOutputs(outputNode: NimNode): seq[FunctionInputOutput] = result.add FunctionInputOutput(typ: (if outputNode.kind == nnkEmpty: ident"void" else: outputNode)) proc parseInputs(inputNodes: NimNode): seq[FunctionInputOutput] = for i in 1.. 0: copyMem(addr result[0], unsafeAddr a[0], sza) if szb > 0: copyMem(addr result[sza], unsafeAddr b[0], szb) proc genConstructor(cname: NimNode, constructorObject: ConstructorObject): NimNode = let sender = genSym(nskParam, "sender") contractCode = genSym(nskParam, "contractCode") funcParamsTuple = newNimNode(nnkTupleConstr) for input in constructorObject.inputs: funcParamsTuple.add(ident input.name) result = quote do: proc deployContract*[TSender](`sender`: TSender, contractType: typedesc[`cname`], `contractCode`: openarray[byte]): ContractDeployment[`cname`, TSender] = discard for input in constructorObject.inputs: result[3].add nnkIdentDefs.newTree( ident input.name, input.typ, newEmptyNode() ) result[6] = quote do: return ContractDeployment[`cname`, TSender](data: `contractCode` & encode(`funcParamsTuple`), sender: `sender`) proc genEvent(cname: NimNode, eventObject: EventObject): NimNode = if not eventObject.anonymous: let callbackIdent = ident "callback" let jsonIdent = ident "j" let jsonData = ident "jsonData" var params = nnkFormalParams.newTree(newEmptyNode()) paramsWithRawData = nnkFormalParams.newTree(newEmptyNode()) argParseBody = newStmtList() i = 1 call = nnkCall.newTree(callbackIdent) callWithRawData = nnkCall.newTree(callbackIdent) offset = ident "offset" inputData = ident "inputData" argParseBody.add quote do: let `jsonData` = JrpcConv.decode(`jsonIdent`.string, JsonNode) var offsetInited = false for input in eventObject.inputs: let param = nnkIdentDefs.newTree( ident input.name, input.typ, newEmptyNode() ) params.add param paramsWithRawData.add param let argument = genSym(nskVar) kind = input.typ if input.indexed: argParseBody.add quote do: var `argument`: `kind` discard decode(hexToSeqByte(`jsonData`["topics"][`i`].getStr), 0, 0, `argument`) i += 1 else: if not offsetInited: argParseBody.add quote do: var `inputData` = hexToSeqByte(`jsonData`["data"].getStr) var `offset` = 0 offsetInited = true argParseBody.add quote do: var `argument`: `kind` `offset` += decode(`inputData`, 0, `offset`, `argument`) call.add argument callWithRawData.add argument let eventName = eventObject.name cbident = ident eventName procTy = nnkProcTy.newTree(params, newEmptyNode()) signature = getSignature(eventObject) # generated with dumpAstGen - produces "{.raises: [], gcsafe.}" let pragmas = nnkPragma.newTree( nnkExprColonExpr.newTree( newIdentNode("raises"), nnkBracket.newTree() ), newIdentNode("gcsafe") ) procTy[1] = pragmas callWithRawData.add jsonIdent paramsWithRawData.add nnkIdentDefs.newTree( jsonIdent, bindSym "JsonString", newEmptyNode() ) let procTyWithRawData = nnkProcTy.newTree(paramsWithRawData, newEmptyNode()) procTyWithRawData[1] = pragmas result = quote do: type `cbident`* = object template eventTopic*(T: type `cbident`): eth_api_types.Topic = const r = keccak256Bytes(`signature`) eth_api_types.Topic(r) proc subscribe[TSender](s: ContractInstance[`cname`, TSender], t: type `cbident`, options: FilterOptions, `callbackIdent`: `procTy`, errorHandler: SubscriptionErrorHandler, withHistoricEvents = true): Future[Subscription] {.used.} = proc eventHandler(`jsonIdent`: JsonString) {.gcsafe, raises: [].} = try: `argParseBody` `call` except CatchableError as err: errorHandler err[] s.sender.subscribeForLogs(options, eventTopic(`cbident`), eventHandler, errorHandler, withHistoricEvents) proc subscribe[TSender](s: ContractInstance[`cname`, TSender], t: type `cbident`, options: FilterOptions, `callbackIdent`: `procTyWithRawData`, errorHandler: SubscriptionErrorHandler, withHistoricEvents = true): Future[Subscription] {.used.} = proc eventHandler(`jsonIdent`: JsonString) {.gcsafe, raises: [].} = try: `argParseBody` `callWithRawData` except CatchableError as err: errorHandler err[] s.sender.subscribeForLogs(options, eventTopic(`cbident`), eventHandler, errorHandler, withHistoricEvents) macro contract*(cname: untyped, body: untyped): untyped = var objects = parseContract(body) result = newStmtList() result.add quote do: type `cname`* = object var constructorGenerated = false for obj in objects: case obj.kind: of function: result.add genFunction(cname, obj.functionObject) of constructor: result.add genConstructor(cname, obj.constructorObject) constructorGenerated = true of event: result.add genEvent(cname, obj.eventObject) if not constructorGenerated: result.add genConstructor(cname, ConstructorObject()) when defined(debugMacros) or defined(debugWeb3Macros): echo result.repr