From fae4339ab6297902e73b5dbf6a177e6fcc1cd449 Mon Sep 17 00:00:00 2001 From: Eric <5089238+emizzle@users.noreply.github.com> Date: Thu, 8 Feb 2024 12:58:15 +1100 Subject: [PATCH] use nim-serde instead of json util Allows aliasing of de/serialized fields, so revert changes of sender to `from` and transactionType to `type` --- ethers.nimble | 1 + ethers/contract.nim | 8 +- ethers/erc20.nim | 2 +- ethers/provider.nim | 102 ++-- ethers/providers/jsonrpc.nim | 4 +- ethers/providers/jsonrpc/conversions.nim | 6 +- ethers/providers/jsonrpc/json.nim | 537 ------------------ ethers/providers/jsonrpc/subscriptions.nim | 2 +- ethers/signer.nim | 10 +- ethers/signers/wallet.nim | 2 +- ethers/transaction.nim | 30 +- .../providers/jsonrpc/testConversions.nim | 9 +- .../providers/jsonrpc/testJsonRpcSigner.nim | 6 +- testmodule/providers/jsonrpc/testjson.nim | 498 ---------------- 14 files changed, 92 insertions(+), 1125 deletions(-) delete mode 100644 ethers/providers/jsonrpc/json.nim delete mode 100644 testmodule/providers/jsonrpc/testjson.nim diff --git a/ethers.nimble b/ethers.nimble index 41edfa5..e841f74 100644 --- a/ethers.nimble +++ b/ethers.nimble @@ -9,6 +9,7 @@ requires "chronos >= 4.0.0 & < 4.1.0" requires "contractabi >= 0.6.0 & < 0.7.0" requires "questionable >= 0.10.2 & < 0.11.0" requires "json_rpc >= 0.4.0 & < 0.5.0" +requires "serde >= 0.1.1 & < 0.2.0" requires "stint" requires "stew" requires "eth" diff --git a/ethers/contract.nim b/ethers/contract.nim index 4e3fdf8..daeecb3 100644 --- a/ethers/contract.nim +++ b/ethers/contract.nim @@ -101,8 +101,8 @@ proc call(contract: Contract, overrides = TransactionOverrides()) {.async.} = var transaction = createTransaction(contract, function, parameters, overrides) - if signer =? contract.signer and transaction.`from`.isNone: - transaction.`from` = some(await signer.getAddress()) + if signer =? contract.signer and transaction.sender.isNone: + transaction.sender = some(await signer.getAddress()) discard await contract.provider.call(transaction, overrides) @@ -114,8 +114,8 @@ proc call(contract: Contract, overrides = TransactionOverrides()): Future[ReturnType] {.async.} = var transaction = createTransaction(contract, function, parameters, overrides) - if signer =? contract.signer and transaction.`from`.isNone: - transaction.`from` = some(await signer.getAddress()) + if signer =? contract.signer and transaction.sender.isNone: + transaction.sender = some(await signer.getAddress()) let response = await contract.provider.call(transaction, overrides) return decodeResponse(ReturnType, returnMultiple, response) diff --git a/ethers/erc20.nim b/ethers/erc20.nim index 57f0cbf..bcbc040 100644 --- a/ethers/erc20.nim +++ b/ethers/erc20.nim @@ -76,5 +76,5 @@ method transferFrom*(token: Erc20Token, spender: Address, recipient: Address, amount: UInt256): ?TransactionResponse {.base, contract.} - ## Moves `amount` tokens from `from` to `to` using the allowance + ## Moves `amount` tokens from sender to `to` using the allowance ## mechanism. `amount` is then deducted from the caller's allowance. diff --git a/ethers/provider.nim b/ethers/provider.nim index a8f7861..1bf2363 100644 --- a/ethers/provider.nim +++ b/ethers/provider.nim @@ -1,9 +1,9 @@ import pkg/chronicles +import pkg/serde import pkg/stew/byteutils import ./basics import ./transaction import ./blocktag -import ./providers/jsonrpc/json export basics export transaction @@ -15,20 +15,20 @@ type Provider* = ref object of RootObj ProviderError* = object of EthersError Subscription* = ref object of RootObj - EventFilter* = ref object of RootObj - address* {.serialize.}: Address - topics* {.serialize.}: seq[Topic] - Filter* = ref object of EventFilter - fromBlock* {.serialize.}: BlockTag - toBlock* {.serialize.}: BlockTag - FilterByBlockHash* = ref object of EventFilter - blockHash* {.serialize.}: BlockHash - Log* = object - blockNumber* {.serialize.}: UInt256 - data* {.serialize.}: seq[byte] - logIndex* {.serialize.}: UInt256 - removed* {.serialize.}: bool - topics* {.serialize.}: seq[Topic] + EventFilter* {.serialize.} = ref object of RootObj + address*: Address + topics*: seq[Topic] + Filter* {.serialize.} = ref object of EventFilter + fromBlock*: BlockTag + toBlock*: BlockTag + FilterByBlockHash* {.serialize.} = ref object of EventFilter + blockHash*: BlockHash + Log* {.serialize.} = object + blockNumber*: UInt256 + data*: seq[byte] + logIndex*: UInt256 + removed*: bool + topics*: seq[Topic] TransactionHash* = array[32, byte] BlockHash* = array[32, byte] TransactionStatus* = enum @@ -38,43 +38,43 @@ type TransactionResponse* = object provider*: Provider hash* {.serialize.}: TransactionHash - TransactionReceipt* = object - `from`* {.serialize.}: ?Address - to* {.serialize.}: ?Address - contractAddress* {.serialize.}: ?Address - transactionIndex* {.serialize.}: UInt256 - gasUsed* {.serialize.}: UInt256 - logsBloom* {.serialize.}: seq[byte] - blockHash* {.serialize.}: ?BlockHash - transactionHash* {.serialize.}: TransactionHash - logs* {.serialize.}: seq[Log] - blockNumber* {.serialize.}: ?UInt256 - cumulativeGasUsed* {.serialize.}: UInt256 - effectiveGasPrice* {.serialize.}: ?UInt256 - status* {.serialize.}: TransactionStatus - `type`* {.serialize.}: TransactionType + TransactionReceipt* {.serialize.} = object + sender* {.serialize("from"), deserialize("from").}: ?Address + to*: ?Address + contractAddress*: ?Address + transactionIndex*: UInt256 + gasUsed*: UInt256 + logsBloom*: seq[byte] + blockHash*: ?BlockHash + transactionHash*: TransactionHash + logs*: seq[Log] + blockNumber*: ?UInt256 + cumulativeGasUsed*: UInt256 + effectiveGasPrice*: ?UInt256 + status*: TransactionStatus + transactionType* {.serialize("type"), deserialize("type").}: TransactionType LogHandler* = proc(log: Log) {.gcsafe, raises:[].} BlockHandler* = proc(blck: Block) {.gcsafe, raises:[].} Topic* = array[32, byte] - Block* = object - number* {.serialize.}: ?UInt256 - timestamp* {.serialize.}: UInt256 - hash* {.serialize.}: ?BlockHash - PastTransaction* = object - blockHash* {.serialize.}: BlockHash - blockNumber* {.serialize.}: UInt256 - `from`* {.serialize.}: Address - gas* {.serialize.}: UInt256 - gasPrice* {.serialize.}: UInt256 - hash* {.serialize.}: TransactionHash - input* {.serialize.}: seq[byte] - nonce* {.serialize.}: UInt256 - to* {.serialize.}: Address - transactionIndex* {.serialize.}: UInt256 - `type`* {.serialize.}: ?TransactionType - chainId* {.serialize.}: ?UInt256 - value* {.serialize.}: UInt256 - v* {.serialize.}, r* {.serialize.}, s* {.serialize.}: UInt256 + Block* {.serialize.} = object + number*: ?UInt256 + timestamp*: UInt256 + hash*: ?BlockHash + PastTransaction* {.serialize.} = object + blockHash*: BlockHash + blockNumber*: UInt256 + sender* {.serialize("from"), deserialize("from").}: Address + gas*: UInt256 + gasPrice*: UInt256 + hash*: TransactionHash + input*: seq[byte] + nonce*: UInt256 + to*: Address + transactionIndex*: UInt256 + transactionType* {.serialize("type"), deserialize("type").}: ?TransactionType + chainId*: ?UInt256 + value*: UInt256 + v*, r*, s*: UInt256 const EthersDefaultConfirmations* {.intdefine.} = 12 const EthersReceiptTimeoutBlks* {.intdefine.} = 50 # in blocks @@ -87,7 +87,7 @@ template raiseProviderError(msg: string) = func toTransaction*(past: PastTransaction): Transaction = Transaction( - `from`: some past.`from`, + sender: some past.sender, to: past.to, data: past.input, value: past.value, @@ -95,7 +95,7 @@ func toTransaction*(past: PastTransaction): Transaction = chainId: past.chainId, gasPrice: some past.gasPrice, gasLimit: some past.gas, - `type`: past.`type` + transactionType: past.transactionType ) method getBlockNumber*( diff --git a/ethers/providers/jsonrpc.nim b/ethers/providers/jsonrpc.nim index 904ada3..bd2db32 100644 --- a/ethers/providers/jsonrpc.nim +++ b/ethers/providers/jsonrpc.nim @@ -4,15 +4,15 @@ import pkg/chronicles import pkg/eth/common/eth_types_json_serialization import pkg/json_rpc/rpcclient import pkg/json_rpc/errors +import pkg/serde import ../basics import ../provider import ../signer import ./jsonrpc/rpccalls import ./jsonrpc/conversions import ./jsonrpc/subscriptions -import ./jsonrpc/json -export json +export serde export basics export provider export chronicles diff --git a/ethers/providers/jsonrpc/conversions.nim b/ethers/providers/jsonrpc/conversions.nim index 98a3d66..4abd3da 100644 --- a/ethers/providers/jsonrpc/conversions.nim +++ b/ethers/providers/jsonrpc/conversions.nim @@ -3,15 +3,15 @@ import std/strutils import pkg/chronicles except fromJson, `%`, `%*`, toJson import pkg/json_rpc/jsonmarshal import pkg/questionable/results +import pkg/serde import pkg/stew/byteutils import ../../basics import ../../transaction import ../../blocktag import ../../provider -import ./json export jsonmarshal -export json +export serde export chronicles except fromJson, `%`, `%*`, toJson {.push raises: [].} @@ -62,7 +62,7 @@ func `%`*(transaction: Transaction): JsonNode = "data": %transaction.data, "value": %transaction.value } - if sender =? transaction.`from`: + if sender =? transaction.sender: result["from"] = %sender if nonce =? transaction.nonce: result["nonce"] = %nonce diff --git a/ethers/providers/jsonrpc/json.nim b/ethers/providers/jsonrpc/json.nim deleted file mode 100644 index e87c366..0000000 --- a/ethers/providers/jsonrpc/json.nim +++ /dev/null @@ -1,537 +0,0 @@ - -import std/json as stdjson except `%`, `%*` -import std/macros -import std/options -import std/sequtils -import std/sets -import std/strutils -import std/tables -import std/typetraits -import pkg/chronicles except toJson -import pkg/contractabi -import pkg/stew/byteutils -import pkg/stint -import pkg/questionable -import pkg/questionable/results - -import ../../basics - -export stdjson except `%`, `%*`, parseJson -export chronicles except toJson -export sets - -{.push raises: [].} - -logScope: - topics = "json de/serialization" - -type - SerdeError* = object of EthersError - UnexpectedKindError* = object of SerdeError - SerdeMode* = enum - OptOut, ## serialize: all object fields will be serialized, except fields marked with 'ignore' - ## deserialize: all json keys will be deserialized, no error if extra json field - OptIn, ## serialize: only object fields marked with serialize will be serialzied - ## deserialize: only fields marked with deserialize will be deserialized - Strict ## serialize: all object fields will be serialized, regardless if the field is marked with 'ignore' - ## deserialize: object fields and json fields must match exactly - SerdeFieldOptions = object - key: string - ignore: bool - -template serialize*(key = "", ignore = false, mode = SerdeMode.OptOut) {.pragma.} -template deserialize*(key = "", ignore = false, mode = SerdeMode.OptOut) {.pragma.} - -proc isDefault[T](paramValue: T): bool {.compileTime.} = - var result = paramValue == T.default - when T is SerdeMode: - return paramValue == SerdeMode.OptOut - return result - -template expectMissingPragmaParam(value, pragma, name, msg) = - static: - when value.hasCustomPragma(pragma): - const params = value.getCustomPragmaVal(pragma) - for paramName, paramValue in params.fieldPairs: - - if paramName == name and not paramValue.isDefault: - raiseAssert(msg) - -proc mapErrTo[E1: ref CatchableError, E2: SerdeError]( - e1: E1, - _: type E2, - msg: string = e1.msg): ref E2 = - - return newException(E2, msg, e1) - -proc newSerdeError(msg: string): ref SerdeError = - newException(SerdeError, msg) - -proc newUnexpectedKindError( - expectedType: type, - expectedKinds: string, - json: JsonNode -): ref UnexpectedKindError = - let kind = if json.isNil: "nil" - else: $json.kind - newException(UnexpectedKindError, - "deserialization to " & $expectedType & " failed: expected " & - expectedKinds & " but got " & $kind) - -proc newUnexpectedKindError( - expectedType: type, - expectedKinds: set[JsonNodeKind], - json: JsonNode -): ref UnexpectedKindError = - newUnexpectedKindError(expectedType, $expectedKinds, json) - -proc newUnexpectedKindError( - expectedType: type, - expectedKind: JsonNodeKind, - json: JsonNode -): ref UnexpectedKindError = - newUnexpectedKindError(expectedType, {expectedKind}, json) - -template expectJsonKind( - expectedType: type, - expectedKinds: set[JsonNodeKind], - json: JsonNode -) = - if json.isNil or json.kind notin expectedKinds: - return failure(newUnexpectedKindError(expectedType, expectedKinds, json)) - -template expectJsonKind*( - expectedType: type, - expectedKind: JsonNodeKind, - json: JsonNode -) = - expectJsonKind(expectedType, {expectedKind}, json) - -proc fieldKeys[T](obj: T): seq[string] = - for name, _ in fieldPairs(when type(T) is ref: obj[] else: obj): - result.add name - -func keysNotIn[T](json: JsonNode, obj: T): HashSet[string] = - let jsonKeys = json.keys.toSeq.toHashSet - let objKeys = obj.fieldKeys.toHashSet - difference(jsonKeys, objKeys) - -template getSerdeFieldOptions(pragma, fieldName, fieldValue): SerdeFieldOptions = - var opts = SerdeFieldOptions(key: fieldName, ignore: false) - when fieldValue.hasCustomPragma(pragma): - fieldValue.expectMissingPragmaParam(pragma, "mode", - "Cannot set " & astToStr(pragma) & " 'mode' on '" & fieldName & "' field defintion.") - let (key, ignore, _) = fieldValue.getCustomPragmaVal(pragma) - opts.ignore = ignore - if key != "": - opts.key = key - opts - -template getSerdeMode(T, pragma): SerdeMode = - when T.hasCustomPragma(pragma): - T.expectMissingPragmaParam(pragma, "key", - "Cannot set " & astToStr(pragma) & " 'key' on '" & $T & - "' type definition.") - T.expectMissingPragmaParam(pragma, "ignore", - "Cannot set " & astToStr(pragma) & " 'ignore' on '" & $T & - "' type definition.") - let (_, _, mode) = T.getCustomPragmaVal(pragma) - mode - else: - # Default mode -- when the type is NOT annotated with a - # serialize/deserialize pragma. - # - # NOTE This may be different in the logic branch above, when the type is - # annotated with serialize/deserialize but doesn't specify a mode. The - # default in that case will fallback to the default mode specified in the - # pragma signature (currently OptOut for both serialize and deserialize) - # - # Examples: - # 1. type MyObj = object - # Type is not annotated, mode defaults to OptOut (as specified on the - # pragma signatures) for both serialization and deserializtion - # - # 2. type MyObj {.serialize, deserialize.} = object - # Type is annotated, mode defaults to OptIn for serialization and OptOut - # for deserialization - when pragma == serialize: - SerdeMode.OptIn - elif pragma == deserialize: - SerdeMode.OptOut - -proc fromJson*( - T: type enum, - json: JsonNode -): ?!T = - expectJsonKind(string, JString, json) - without val =? parseEnum[T](json.str).catch, error: - return failure error.mapErrTo(SerdeError) - return success val - -proc fromJson*( - _: type string, - json: JsonNode -): ?!string = - if json.isNil: - return failure newSerdeError("'json' expected, but was nil") - elif json.kind == JNull: - return success("null") - elif json.isNil or json.kind != JString: - return failure newUnexpectedKindError(string, JString, json) - catch json.getStr - -proc fromJson*( - _: type bool, - json: JsonNode -): ?!bool = - expectJsonKind(bool, JBool, json) - catch json.getBool - -proc fromJson*( - _: type int, - json: JsonNode -): ?!int = - expectJsonKind(int, JInt, json) - catch json.getInt - -proc fromJson*[T: SomeInteger]( - _: type T, - json: JsonNode -): ?!T = - when T is uint|uint64 or (not defined(js) and int.sizeof == 4): - expectJsonKind(T, {JInt, JString}, json) - case json.kind - of JString: - without x =? parseBiggestUInt(json.str).catch, error: - return failure newSerdeError(error.msg) - return success cast[T](x) - else: - return success T(json.num) - else: - expectJsonKind(T, {JInt}, json) - return success cast[T](json.num) - -proc fromJson*[T: SomeFloat]( - _: type T, - json: JsonNode -): ?!T = - expectJsonKind(T, {JInt, JFloat, JString}, json) - if json.kind == JString: - case json.str - of "nan": - let b = NaN - return success T(b) - # dst = NaN # would fail some tests because range conversions would cause CT error - # in some cases; but this is not a hot-spot inside this branch and backend can optimize this. - of "inf": - let b = Inf - return success T(b) - of "-inf": - let b = -Inf - return success T(b) - else: - let err = newUnexpectedKindError(T, "'nan|inf|-inf'", json) - return failure(err) - else: - if json.kind == JFloat: - return success T(json.fnum) - else: - return success T(json.num) - -proc fromJson*( - _: type seq[byte], - json: JsonNode -): ?!seq[byte] = - expectJsonKind(seq[byte], JString, json) - hexToSeqByte(json.getStr).catch - -proc fromJson*[N: static[int], T: array[N, byte]]( - _: type T, - json: JsonNode -): ?!T = - expectJsonKind(T, JString, json) - T.fromHex(json.getStr).catch - -proc fromJson*[T: distinct]( - _: type T, - json: JsonNode -): ?!T = - success T(? T.distinctBase.fromJson(json)) - -proc fromJson*[N: static[int], T: StUint[N]]( - _: type T, - json: JsonNode -): ?!T = - expectJsonKind(T, JString, json) - let jsonStr = json.getStr - let prefix = jsonStr[0..1].toLowerAscii - case prefix: - of "0x": catch parse(jsonStr, T, 16) - of "0o": catch parse(jsonStr, T, 8) - of "0b": catch parse(jsonStr, T, 2) - else: catch parse(jsonStr, T) - -proc fromJson*[T]( - _: type Option[T], - json: JsonNode -): ?! Option[T] = - if json.isNil or json.kind == JNull: - return success(none T) - without val =? T.fromJson(json), error: - return failure(error) - success(val.some) - -proc fromJson*[T]( - _: type seq[T], - json: JsonNode -): ?! seq[T] = - expectJsonKind(seq[T], JArray, json) - var arr: seq[T] = @[] - for elem in json.elems: - arr.add(? T.fromJson(elem)) - success arr - -proc fromJson*[T: ref object or object]( - _: type T, - json: JsonNode -): ?!T = - - when T is JsonNode: - return success T(json) - - expectJsonKind(T, JObject, json) - var res = when type(T) is ref: T.new() else: T.default - let mode = T.getSerdeMode(deserialize) - - # ensure there's no extra fields in json - if mode == SerdeMode.Strict: - let extraFields = json.keysNotIn(res) - if extraFields.len > 0: - return failure newSerdeError("json field(s) missing in object: " & $extraFields) - - for name, value in fieldPairs(when type(T) is ref: res[] else: res): - - logScope: - field = $T & "." & name - mode - - let hasDeserializePragma = value.hasCustomPragma(deserialize) - let opts = getSerdeFieldOptions(deserialize, name, value) - let isOptionalValue = typeof(value) is Option - var skip = false # workaround for 'continue' not supported in a 'fields' loop - - case mode: - of Strict: - if opts.key notin json: - return failure newSerdeError("object field missing in json: " & opts.key) - elif opts.ignore: - # unable to figure out a way to make this a compile time check - warn "object field marked as 'ignore' while in Strict mode, field will be deserialized anyway" - - of OptIn: - if not hasDeserializePragma: - debug "object field not marked as 'deserialize', skipping" - skip = true - elif opts.ignore: - debug "object field marked as 'ignore', skipping" - skip = true - elif opts.key notin json and not isOptionalValue: - return failure newSerdeError("object field missing in json: " & opts.key) - - of OptOut: - if opts.ignore: - debug "object field is opted out of deserialization ('igore' is set), skipping" - skip = true - elif hasDeserializePragma and opts.key == name: - warn "object field marked as deserialize in OptOut mode, but 'ignore' not set, field will be deserialized" - - if not skip: - - if isOptionalValue: - - let jsonVal = json{opts.key} - without parsed =? typeof(value).fromJson(jsonVal), e: - debug "failed to deserialize field", - `type` = $typeof(value), - json = jsonVal, - error = e.msg - return failure(e) - value = parsed - - # not Option[T] - elif opts.key in json and - jsonVal =? json{opts.key}.catch and - not jsonVal.isNil: - - without parsed =? typeof(value).fromJson(jsonVal), e: - debug "failed to deserialize field", - `type` = $typeof(value), - json = jsonVal, - error = e.msg - return failure(e) - value = parsed - - success(res) - -proc parseJson*(json: string): ?!JsonNode = - ## fix for nim raising Exception - try: - return stdjson.parseJson(json).catch - except Exception as e: - return err newException(CatchableError, e.msg) - -proc fromJson*[T: ref object or object]( - _: type T, - bytes: seq[byte] -): ?!T = - let json = ? parse(string.fromBytes(bytes)) - T.fromJson(json) - -proc fromJson*( - _: type JsonNode, - jsn: string -): ?!JsonNode = - return json.parseJson(jsn) - -proc fromJson*[T: ref object or object]( - _: type T, - jsn: string -): ?!T = - let jsn = ? json.parseJson(jsn) # full qualification required in-module only - T.fromJson(jsn) - -func `%`*(s: string): JsonNode = newJString(s) - -func `%`*(n: uint): JsonNode = - if n > cast[uint](int.high): - newJString($n) - else: - newJInt(BiggestInt(n)) - -func `%`*(n: int): JsonNode = newJInt(n) - -func `%`*(n: BiggestUInt): JsonNode = - if n > cast[BiggestUInt](BiggestInt.high): - newJString($n) - else: - newJInt(BiggestInt(n)) - -func `%`*(n: BiggestInt): JsonNode = newJInt(n) - -func `%`*(n: float): JsonNode = - if n != n: newJString("nan") - elif n == Inf: newJString("inf") - elif n == -Inf: newJString("-inf") - else: newJFloat(n) - -func `%`*(b: bool): JsonNode = newJBool(b) - -func `%`*(keyVals: openArray[tuple[key: string, val: JsonNode]]): JsonNode = - if keyVals.len == 0: return newJArray() - let jObj = newJObject() - for key, val in items(keyVals): jObj.fields[key] = val - jObj - -template `%`*(j: JsonNode): JsonNode = j - -func `%`*[T](table: Table[string, T]|OrderedTable[string, T]): JsonNode = - let jObj = newJObject() - for k, v in table: jObj[k] = ? %v - jObj - -func `%`*[T](opt: Option[T]): JsonNode = - if opt.isSome: %(opt.get) else: newJNull() - - -proc `%`*[T: object or ref object](obj: T): JsonNode = - - let jsonObj = newJObject() - let o = when T is ref object: obj[] - else: obj - - let mode = T.getSerdeMode(serialize) - - for name, value in o.fieldPairs: - - logScope: - field = $T & "." & name - mode - - let opts = getSerdeFieldOptions(serialize, name, value) - const serializeField = value.hasCustomPragma(serialize) - var skip = false # workaround for 'continue' not supported in a 'fields' loop - - case mode: - of OptIn: - if not serializeField: - debug "object field not marked with serialize, skipping" - skip = true - elif opts.ignore: - skip = true - - of OptOut: - if opts.ignore: - debug "object field opted out of serialization ('ignore' is set), skipping" - skip = true - elif serializeField and opts.key == name: # all serialize params are default - warn "object field marked as serialize in OptOut mode, but 'ignore' not set, field will be serialized" - - of Strict: - if opts.ignore: - # unable to figure out a way to make this a compile time check - warn "object field marked as 'ignore' while in Strict mode, field will be serialized anyway" - - if not skip: - jsonObj[opts.key] = %value - - jsonObj - -proc `%`*(o: enum): JsonNode = % $o - -func `%`*(stint: StInt|StUint): JsonNode = %stint.toString - -func `%`*(cstr: cstring): JsonNode = % $cstr - -func `%`*(arr: openArray[byte]): JsonNode = % arr.to0xHex - -func `%`*[T](elements: openArray[T]): JsonNode = - let jObj = newJArray() - for elem in elements: jObj.add(%elem) - jObj - -func `%`*[T: distinct](id: T): JsonNode = - type baseType = T.distinctBase - % baseType(id) - -proc toJson*[T](item: T): string = $(%item) - -proc toJsnImpl(x: NimNode): NimNode = - case x.kind - of nnkBracket: # array - if x.len == 0: return newCall(bindSym"newJArray") - result = newNimNode(nnkBracket) - for i in 0 ..< x.len: - result.add(toJsnImpl(x[i])) - result = newCall(bindSym("%", brOpen), result) - of nnkTableConstr: # object - if x.len == 0: return newCall(bindSym"newJObject") - result = newNimNode(nnkTableConstr) - for i in 0 ..< x.len: - x[i].expectKind nnkExprColonExpr - result.add newTree(nnkExprColonExpr, x[i][0], toJsnImpl(x[i][1])) - result = newCall(bindSym("%", brOpen), result) - of nnkCurly: # empty object - x.expectLen(0) - result = newCall(bindSym"newJObject") - of nnkNilLit: - result = newCall(bindSym"newJNull") - of nnkPar: - if x.len == 1: result = toJsnImpl(x[0]) - else: result = newCall(bindSym("%", brOpen), x) - else: - result = newCall(bindSym("%", brOpen), x) - -macro `%*`*(x: untyped): JsonNode = - ## Convert an expression to a JsonNode directly, without having to specify - ## `%` for every element. - result = toJsnImpl(x) diff --git a/ethers/providers/jsonrpc/subscriptions.nim b/ethers/providers/jsonrpc/subscriptions.nim index 38e8ec5..489e684 100644 --- a/ethers/providers/jsonrpc/subscriptions.nim +++ b/ethers/providers/jsonrpc/subscriptions.nim @@ -3,12 +3,12 @@ import std/tables import std/sequtils import pkg/chronos import pkg/json_rpc/rpcclient +import pkg/serde import ../../basics import ../../provider import ./rpccalls import ./conversions import ./looping -import ./json type JsonRpcSubscriptions* = ref object of RootObj diff --git a/ethers/signer.nim b/ethers/signer.nim index 3a70dc7..8240069 100644 --- a/ethers/signer.nim +++ b/ethers/signer.nim @@ -84,7 +84,7 @@ method estimateGas*( convertError: address = await signer.getAddress - transaction.`from` = some(address) + transaction.sender = some(address) try: return await signer.provider.estimateGas(transaction) except ProviderError as e: @@ -132,7 +132,7 @@ method populateTransaction*( convertError: address = await signer.getAddress() - if sender =? transaction.`from` and sender != address: + if sender =? transaction.sender and sender != address: raiseSignerError("from address mismatch") if chainId =? transaction.chainId and chainId != await signer.getChainId(): raiseSignerError("chain id mismatch") @@ -145,8 +145,8 @@ method populateTransaction*( var populated = transaction try: - if transaction.`from`.isNone: - populated.`from` = some(address) + if transaction.sender.isNone: + populated.sender = some(address) if transaction.chainId.isNone: populated.chainId = some(await signer.getChainId()) if transaction.gasPrice.isNone and (transaction.maxFee.isNone or transaction.maxPriorityFee.isNone): @@ -185,7 +185,7 @@ method cancelTransaction*( # cancels a transaction by sending with a 0-valued transaction to ourselves # with the failed tx's nonce - without sender =? tx.`from`: + without sender =? tx.sender: raiseSignerError "transaction must have sender" without nonce =? tx.nonce: raiseSignerError "transaction must have nonce" diff --git a/ethers/signers/wallet.nim b/ethers/signers/wallet.nim index d757b62..5c70e5a 100644 --- a/ethers/signers/wallet.nim +++ b/ethers/signers/wallet.nim @@ -74,7 +74,7 @@ method getAddress*( proc signTransaction*(wallet: Wallet, transaction: Transaction): Future[seq[byte]] {.async: (raises:[WalletError]).} = - if sender =? transaction.`from` and sender != wallet.address: + if sender =? transaction.sender and sender != wallet.address: raiseWalletError "from address mismatch" return wallet.privateKey.sign(transaction) diff --git a/ethers/transaction.nim b/ethers/transaction.nim index 4d3128b..1ff2e1b 100644 --- a/ethers/transaction.nim +++ b/ethers/transaction.nim @@ -1,28 +1,28 @@ +import pkg/serde import pkg/stew/byteutils import ./basics -import ./providers/jsonrpc/json type TransactionType* = enum Legacy = 0, AccessList = 1, Dynamic = 2 - Transaction* = object - `from`* {.serialize.}: ?Address - to* {.serialize.}: Address - data* {.serialize.}: seq[byte] - value* {.serialize.}: UInt256 - nonce* {.serialize.}: ?UInt256 - chainId* {.serialize.}: ?UInt256 - gasPrice* {.serialize.}: ?UInt256 - maxFee* {.serialize.}: ?UInt256 - maxPriorityFee* {.serialize.}: ?UInt256 - gasLimit* {.serialize.}: ?UInt256 - `type`* {.serialize.}: ?TransactionType + Transaction* {.serialize.} = object + sender* {.serialize("from").}: ?Address + to*: Address + data*: seq[byte] + value*: UInt256 + nonce*: ?UInt256 + chainId*: ?UInt256 + gasPrice*: ?UInt256 + maxFee*: ?UInt256 + maxPriorityFee*: ?UInt256 + gasLimit*: ?UInt256 + transactionType* {.serialize("type").}: ?TransactionType func `$`*(transaction: Transaction): string = result = "(" - if sender =? transaction.`from`: + if sender =? transaction.sender: result &= "from: " & $sender & ", " result &= "to: " & $transaction.to & ", " result &= "value: " & $transaction.value & ", " @@ -35,6 +35,6 @@ func `$`*(transaction: Transaction): string = result &= ", gasPrice: " & $gasPrice if gasLimit =? transaction.gasLimit: result &= ", gasLimit: " & $gasLimit - if txType =? transaction.`type`: + if txType =? transaction.transactionType: result &= ", type: " & $txType result &= ")" diff --git a/testmodule/providers/jsonrpc/testConversions.nim b/testmodule/providers/jsonrpc/testConversions.nim index 5df1358..55d7bc6 100644 --- a/testmodule/providers/jsonrpc/testConversions.nim +++ b/testmodule/providers/jsonrpc/testConversions.nim @@ -4,6 +4,7 @@ import pkg/ethers/provider import pkg/ethers/providers/jsonrpc/conversions import pkg/questionable import pkg/questionable/results +import pkg/serde import pkg/stew/byteutils func flatten(s: string): string = @@ -137,7 +138,7 @@ suite "JSON Conversions": fail check tx.blockHash == BlockHash(array[32, byte].fromHex("0x595bffbe897e025ea2df3213c4cc52c3f3d69bc04b49011d558f1b0e70038922")) check tx.blockNumber == 0x22e.u256 - check tx.`from` == Address.init("0xe00b677c29ff8d8fe6068530e2bc36158c54dd34").get + check tx.sender == Address.init("0xe00b677c29ff8d8fe6068530e2bc36158c54dd34").get check tx.gas == 0x4d4bb.u256 check tx.gasPrice == 0x3b9aca07.u256 check tx.hash == TransactionHash(array[32, byte].fromHex("0xa31608907c338d6497b0c6ec81049d845c7d409490ebf78171f35143897ca790")) @@ -146,7 +147,7 @@ suite "JSON Conversions": check tx.to == Address.init("0x92f09aa59dccb892a9f5406ddd9c0b98f02ea57e").get check tx.transactionIndex == 0x3.u256 check tx.value == 0.u256 - check tx.`type` == some TransactionType.Legacy + check tx.transactionType == some TransactionType.Legacy check tx.chainId == some 0xc0de4.u256 check tx.v == 0x181bec.u256 check tx.r == UInt256.fromBytesBE(hexToSeqByte("0x57ba18460934526333b80b0fea08737c363f3cd5fbec4a25a8a25e3e8acb362a")) @@ -156,7 +157,7 @@ suite "JSON Conversions": let tx = PastTransaction( blockHash: BlockHash(array[32, byte].fromHex("0x595bffbe897e025ea2df3213c4cc52c3f3d69bc04b49011d558f1b0e70038922")), blockNumber: 0x22e.u256, - `from`: Address.init("0xe00b677c29ff8d8fe6068530e2bc36158c54dd34").get, + sender: Address.init("0xe00b677c29ff8d8fe6068530e2bc36158c54dd34").get, gas: 0x4d4bb.u256, gasPrice: 0x3b9aca07.u256, hash: TransactionHash(array[32, byte].fromHex("0xa31608907c338d6497b0c6ec81049d845c7d409490ebf78171f35143897ca790")), @@ -168,7 +169,7 @@ suite "JSON Conversions": v: 0x181bec.u256, r: UInt256.fromBytesBE(hexToSeqByte("0x57ba18460934526333b80b0fea08737c363f3cd5fbec4a25a8a25e3e8acb362a")), s: UInt256.fromBytesBE(hexToSeqByte("0x33aa50bc8bd719b6b17ad0bf52006bf8943999198f2bf731eb33c118091000f2")), - `type`: some TransactionType.Legacy, + transactionType: some TransactionType.Legacy, chainId: some 0xc0de4.u256 ) let expected = """ diff --git a/testmodule/providers/jsonrpc/testJsonRpcSigner.nim b/testmodule/providers/jsonrpc/testJsonRpcSigner.nim index c25ef0a..34d78ea 100644 --- a/testmodule/providers/jsonrpc/testJsonRpcSigner.nim +++ b/testmodule/providers/jsonrpc/testJsonRpcSigner.nim @@ -52,7 +52,7 @@ suite "JsonRpcSigner": let signer = provider.getSigner() let transaction = Transaction.example let populated = await signer.populateTransaction(transaction) - check !populated.`from` == await signer.getAddress() + check !populated.sender == await signer.getAddress() check !populated.gasPrice == await signer.getGasPrice() check !populated.nonce == await signer.getTransactionCount(BlockTag.pending) check !populated.gasLimit == await signer.estimateGas(transaction) @@ -61,7 +61,7 @@ suite "JsonRpcSigner": test "populate does not overwrite existing fields": let signer = provider.getSigner() var transaction = Transaction.example - transaction.`from` = some await signer.getAddress() + transaction.sender = some await signer.getAddress() transaction.nonce = some UInt256.example transaction.chainId = some await signer.getChainId() transaction.gasPrice = some UInt256.example @@ -72,7 +72,7 @@ suite "JsonRpcSigner": test "populate fails when sender does not match signer address": let signer = provider.getSigner() var transaction = Transaction.example - transaction.`from` = accounts[1].some + transaction.sender = accounts[1].some expect SignerError: discard await signer.populateTransaction(transaction) diff --git a/testmodule/providers/jsonrpc/testjson.nim b/testmodule/providers/jsonrpc/testjson.nim deleted file mode 100644 index 93718f4..0000000 --- a/testmodule/providers/jsonrpc/testjson.nim +++ /dev/null @@ -1,498 +0,0 @@ -import std/math -import std/options -import std/strformat -import std/strutils -import std/unittest -import pkg/stew/byteutils -import pkg/stint -import pkg/ethers/providers/jsonrpc/json as utilsjson -import pkg/questionable -import pkg/questionable/results - - -func flatten(s: string): string = - s.replace(" ") - .replace("\n") - -suite "json serialization - serialize": - - test "serializes UInt256 to non-hex string representation": - check (% 100000.u256) == newJString("100000") - - test "serializes sequence to an array": - let json = % @[1, 2, 3] - let expected = "[1,2,3]" - check $json == expected - - test "serializes Option[T] when has a value": - let obj = %(some 1) - let expected = "1" - check $obj == expected - - test "serializes Option[T] when doesn't have a value": - let obj = %(none int) - let expected = "null" - check $obj == expected - - test "serializes uints int.high or smaller": - let largeUInt: uint = uint(int.high) - check %largeUInt == newJInt(BiggestInt(largeUInt)) - - test "serializes large uints": - let largeUInt: uint = uint(int.high) + 1'u - check %largeUInt == newJString($largeUInt) - - test "serializes Inf float": - check %Inf == newJString("inf") - - test "serializes -Inf float": - check %(-Inf) == newJString("-inf") - - test "can construct json objects with %*": - type MyObj = object - mystring {.serialize.}: string - myint {.serialize.}: int - myoption {.serialize.}: ?bool - - let myobj = MyObj(mystring: "abc", myint: 123, myoption: some true) - let mystuint = 100000.u256 - - let json = %*{ - "myobj": myobj, - "mystuint": mystuint - } - - let expected = """{ - "myobj": { - "mystring": "abc", - "myint": 123, - "myoption": true - }, - "mystuint": "100000" - }""".flatten - - check $json == expected - - test "only serializes marked fields": - type MyObj = object - mystring {.serialize.}: string - myint {.serialize.}: int - mybool: bool - - let obj = % MyObj(mystring: "abc", myint: 1, mybool: true) - - let expected = """{ - "mystring": "abc", - "myint": 1 - }""".flatten - - check $obj == expected - - test "serializes ref objects": - type MyRef = ref object - mystring {.serialize.}: string - myint {.serialize.}: int - - let obj = % MyRef(mystring: "abc", myint: 1) - - let expected = """{ - "mystring": "abc", - "myint": 1 - }""".flatten - - check $obj == expected - -suite "json serialization - deserialize": - - test "deserializes NaN float": - check %NaN == newJString("nan") - - test "deserialize enum": - type MyEnum = enum - First, - Second - let json = newJString("Second") - check !MyEnum.fromJson(json) == Second - - test "deserializes UInt256 from non-hex string representation": - let json = newJString("100000") - check !UInt256.fromJson(json) == 100000.u256 - - test "deserializes Option[T] when has a value": - let json = newJInt(1) - check (!fromJson(?int, json) == some 1) - - test "deserializes Option[T] when doesn't have a value": - let json = newJNull() - check !fromJson(?int, json) == none int - - test "deserializes float": - let json = newJFloat(1.234) - check !float.fromJson(json) == 1.234 - - test "deserializes Inf float": - let json = newJString("inf") - check !float.fromJson(json) == Inf - - test "deserializes -Inf float": - let json = newJString("-inf") - check !float.fromJson(json) == -Inf - - test "deserializes NaN float": - let json = newJString("nan") - check (!float.fromJson(json)).isNaN - - test "deserializes array to sequence": - let expected = @[1, 2, 3] - let json = !"[1,2,3]".parseJson - check !seq[int].fromJson(json) == expected - - test "deserializes uints int.high or smaller": - let largeUInt: uint = uint(int.high) - let json = newJInt(BiggestInt(largeUInt)) - check !uint.fromJson(json) == largeUInt - - test "deserializes large uints": - let largeUInt: uint = uint(int.high) + 1'u - let json = newJString($BiggestUInt(largeUInt)) - check !uint.fromJson(json) == largeUInt - - test "can deserialize json objects": - type MyObj = object - mystring: string - myint: int - myoption: ?bool - - let expected = MyObj(mystring: "abc", myint: 123, myoption: some true) - - let json = !parseJson("""{ - "mystring": "abc", - "myint": 123, - "myoption": true - }""") - check !MyObj.fromJson(json) == expected - - test "ignores serialize pragma when deserializing": - type MyObj = object - mystring {.serialize.}: string - mybool: bool - - let expected = MyObj(mystring: "abc", mybool: true) - - let json = !parseJson("""{ - "mystring": "abc", - "mybool": true - }""") - - check !MyObj.fromJson(json) == expected - - test "deserializes objects with extra fields": - type MyObj = object - mystring: string - mybool: bool - - let expected = MyObj(mystring: "abc", mybool: true) - - let json = !"""{ - "mystring": "abc", - "mybool": true, - "extra": "extra" - }""".parseJson - check !MyObj.fromJson(json) == expected - - test "deserializes objects with less fields": - type MyObj = object - mystring: string - mybool: bool - - let expected = MyObj(mystring: "abc", mybool: false) - - let json = !"""{ - "mystring": "abc" - }""".parseJson - check !MyObj.fromJson(json) == expected - - test "deserializes ref objects": - type MyRef = ref object - mystring: string - myint: int - - let expected = MyRef(mystring: "abc", myint: 1) - - let json = !"""{ - "mystring": "abc", - "myint": 1 - }""".parseJson - - let deserialized = !MyRef.fromJson(json) - check deserialized.mystring == expected.mystring - check deserialized.myint == expected.myint - - -suite "json serialization pragmas": - - test "fails to compile when object marked with 'serialize' specifies options": - type - MyObj {.serialize(key="test", ignore=true).} = object - - check not compiles(%MyObj()) - - test "compiles when object marked with 'serialize' only": - type - MyObj {.serialize.} = object - - check compiles(%MyObj()) - - test "fails to compile when field marked with 'deserialize' specifies mode": - type - MyObj = object - field {.deserialize(mode=OptIn).}: bool - - check not compiles(MyObj.fromJson("""{"field":true}""")) - - test "compiles when object marked with 'deserialize' specifies mode": - type - MyObj {.deserialize(mode=OptIn).} = object - field: bool - - check compiles(MyObj.fromJson("""{"field":true}""")) - - test "fails to compile when object marked with 'deserialize' specifies key": - type - MyObj {.deserialize("test").} = object - field: bool - - check not compiles(MyObj.fromJson("""{"field":true}""")) - - test "compiles when field marked with 'deserialize' specifies key": - type - MyObj = object - field {.deserialize("test").}: bool - - check compiles(MyObj.fromJson("""{"field":true}""")) - - test "compiles when field marked with empty 'deserialize'": - type - MyObj = object - field {.deserialize.}: bool - - check compiles(MyObj.fromJson("""{"field":true}""")) - - test "compiles when field marked with 'serialize'": - type - MyObj = object - field {.serialize.}: bool - - check compiles(%MyObj()) - - -suite "json serialization, mode = OptIn": - - test "serializes with default mode OptIn when object not marked with serialize": - type MyObj = object - field1 {.serialize.}: bool - field2: bool - - let obj = MyObj(field1: true, field2: true) - check obj.toJson == """{"field1":true}""" - - test "not marking object with serialize is equivalent to marking it with serialize in OptIn mode": - type MyObj = object - field1 {.serialize.}: bool - field2: bool - - type MyObjMarked {.serialize(mode=OptIn).} = object - field1 {.serialize.}: bool - field2: bool - - let obj = MyObj(field1: true, field2: true) - let objMarked = MyObjMarked(field1: true, field2: true) - check obj.toJson == objMarked.toJson - - test "serializes field with key when specified": - type MyObj = object - field1 {.serialize("test").}: bool - field2 {.serialize.}: bool - - let obj = MyObj(field1: true, field2: true) - check obj.toJson == """{"test":true,"field2":true}""" - - test "does not serialize ignored field": - type MyObj = object - field1 {.serialize.}: bool - field2 {.serialize(ignore=true).}: bool - - let obj = MyObj(field1: true, field2: true) - check obj.toJson == """{"field1":true}""" - - -suite "json deserialization, mode = OptIn": - - test "deserializes only fields marked as deserialize when mode is OptIn": - type MyObj {.deserialize(mode=OptIn).} = object - field1: int - field2 {.deserialize.}: bool - - let val = !MyObj.fromJson("""{"field1":true,"field2":true}""") - check val == MyObj(field1: 0, field2: true) - - test "deserializes Optional fields when mode is OptIn": - type MyObj {.deserialize(mode=OptIn).} = object - field1 {.deserialize.}: bool - field2 {.deserialize.}: Option[bool] - - let val = !MyObj.fromJson("""{"field1":true}""") - check val == MyObj(field1: true, field2: none bool) - - -suite "json serialization, mode = OptOut": - - test "serialize on object definition defaults to OptOut mode, serializes all fields": - type MyObj {.serialize.} = object - field1: bool - field2: bool - - let obj = MyObj(field1: true, field2: true) - check obj.toJson == """{"field1":true,"field2":true}""" - - test "not specifying serialize mode is equivalent to specifying OptOut mode": - type MyObj {.serialize.} = object - field1: bool - field2: bool - - type MyObjMarked {.serialize(mode=OptOut).} = object - field1: bool - field2: bool - - let obj = MyObj(field1: true, field2: true) - let objMarked = MyObjMarked(field1: true, field2: true) - check obj.toJson == objMarked.toJson - - test "ignores field when marked with ignore": - type MyObj {.serialize.} = object - field1 {.serialize(ignore=true).}: bool - field2: bool - - let obj = MyObj(field1: true, field2: true) - check obj.toJson == """{"field2":true}""" - - test "serializes field with key instead of field name": - type MyObj {.serialize.} = object - field1 {.serialize("test").}: bool - field2: bool - - let obj = MyObj(field1: true, field2: true) - check obj.toJson == """{"test":true,"field2":true}""" - - -suite "json deserialization, mode = OptOut": - - test "deserializes object in OptOut mode when not marked with deserialize": - type MyObj = object - field1: bool - field2: bool - - let val = !MyObj.fromJson("""{"field1":true,"field3":true}""") - check val == MyObj(field1: true, field2: false) - - test "deserializes object field with marked json key": - type MyObj = object - field1 {.deserialize("test").}: bool - field2: bool - - let val = !MyObj.fromJson("""{"test":true,"field2":true}""") - check val == MyObj(field1: true, field2: true) - - test "fails to deserialize object field with wrong type": - type MyObj = object - field1: int - field2: bool - - let r = MyObj.fromJson("""{"field1":true,"field2":true}""") - check r.isFailure - check r.error of UnexpectedKindError - check r.error.msg == "deserialization to int failed: expected {JInt} but got JBool" - - test "does not deserialize ignored fields in OptOut mode": - type MyObj = object - field1 {.deserialize(ignore=true).}: bool - field2: bool - - let val = !MyObj.fromJson("""{"field1":true,"field2":true}""") - check val == MyObj(field1: false, field2: true) - - test "deserializes fields when marked with deserialize but not ignored": - type MyObj = object - field1 {.deserialize.}: bool - field2: bool - - let val = !MyObj.fromJson("""{"field1":true,"field2":true}""") - check val == MyObj(field1: true, field2: true) - - test "deserializes Optional field": - type MyObj = object - field1: Option[bool] - field2: bool - - let val = !MyObj.fromJson("""{"field2":true}""") - check val == MyObj(field1: none bool, field2: true) - - -suite "json serialization - mode = Strict": - - test "serializes all fields in Strict mode": - type MyObj {.serialize(mode=Strict).} = object - field1: bool - field2: bool - - let obj = MyObj(field1: true, field2: true) - check obj.toJson == """{"field1":true,"field2":true}""" - - test "ignores ignored fields in Strict mode": - type MyObj {.serialize(mode=Strict).} = object - field1 {.serialize(ignore=true).}: bool - field2: bool - - let obj = MyObj(field1: true, field2: true) - check obj.toJson == """{"field1":true,"field2":true}""" - - -suite "json deserialization, mode = Strict": - - test "deserializes matching object and json fields when mode is Strict": - type MyObj {.deserialize(mode=Strict).} = object - field1: bool - field2: bool - - let val = !MyObj.fromJson("""{"field1":true,"field2":true}""") - check val == MyObj(field1: true, field2: true) - - test "fails to deserialize with missing json field when mode is Strict": - type MyObj {.deserialize(mode=Strict).} = object - field1: bool - field2: bool - - let r = MyObj.fromJson("""{"field2":true}""") - check r.isFailure - check r.error of SerdeError - check r.error.msg == "object field missing in json: field1" - - test "fails to deserialize with missing object field when mode is Strict": - type MyObj {.deserialize(mode=Strict).} = object - field2: bool - - let r = MyObj.fromJson("""{"field1":true,"field2":true}""") - check r.isFailure - check r.error of SerdeError - check r.error.msg == "json field(s) missing in object: {\"field1\"}" - - test "deserializes ignored fields in Strict mode": - type MyObj {.deserialize(mode=Strict).} = object - field1 {.deserialize(ignore=true).}: bool - field2: bool - - let val = !MyObj.fromJson("""{"field1":true,"field2":true}""") - check val == MyObj(field1: true, field2: true) - - -