diff --git a/ethers/basics.nim b/ethers/basics.nim index 2896a96..76662e1 100644 --- a/ethers/basics.nim +++ b/ethers/basics.nim @@ -11,3 +11,6 @@ export results export stint export upraises export address + +type + EthersError* = object of IOError diff --git a/ethers/contract.nim b/ethers/contract.nim index ee2c5f4..7e79f5c 100644 --- a/ethers/contract.nim +++ b/ethers/contract.nim @@ -11,7 +11,7 @@ type Contract* = ref object of RootObj provider: Provider address: Address - ContractError* = object of IOError + ContractError* = object of EthersError template raiseContractError(message: string) = raise newException(ContractError, message) diff --git a/ethers/providers/jsonrpc.nim b/ethers/providers/jsonrpc.nim index 0562cc7..19412d8 100644 --- a/ethers/providers/jsonrpc.nim +++ b/ethers/providers/jsonrpc.nim @@ -16,7 +16,7 @@ type JsonRpcSigner* = ref object of Signer provider: JsonRpcProvider address: ?Address - JsonRpcProviderError* = object of IOError + JsonRpcProviderError* = object of EthersError template raiseProviderError(message: string) = raise newException(JsonRpcProviderError, message) diff --git a/ethers/providers/rpccalls/conversions.nim b/ethers/providers/rpccalls/conversions.nim index 1066560..1a21ed7 100644 --- a/ethers/providers/rpccalls/conversions.nim +++ b/ethers/providers/rpccalls/conversions.nim @@ -26,17 +26,25 @@ func fromJson*(json: JsonNode, name: string, result: var Address) = # UInt256 func `%`*(integer: UInt256): JsonNode = - %toHex(integer) + %("0x" & toHex(integer)) func fromJson*(json: JsonNode, name: string, result: var UInt256) = result = UInt256.fromHex(json.getStr()) # Transaction -func `%`*(tx: Transaction): JsonNode = - result = %{ "to": %tx.to, "data": %tx.data } - if sender =? tx.sender: +func `%`*(transaction: Transaction): JsonNode = + result = %{ "to": %transaction.to, "data": %transaction.data } + if sender =? transaction.sender: result["from"] = %sender + if nonce =? transaction.nonce: + result["nonce"] = %nonce + if chainId =? transaction.chainId: + result["chainId"] = %chainId + if gasPrice =? transaction.gasPrice: + result["gasPrice"] = %gasPrice + if gasLimit =? transaction.gasLimit: + result["gas"] = %gasLimit # BlockTag diff --git a/ethers/signer.nim b/ethers/signer.nim index b1c4bf7..200db89 100644 --- a/ethers/signer.nim +++ b/ethers/signer.nim @@ -4,6 +4,10 @@ import ./provider export basics type Signer* = ref object of RootObj +type SignerError* = object of EthersError + +template raiseSignerError(message: string) = + raise newException(SignerError, message) method provider*(signer: Signer): Provider {.base.} = doAssert false, "not implemented" @@ -28,3 +32,27 @@ method estimateGas*(signer: Signer, method getChainId*(signer: Signer): Future[UInt256] {.base.} = signer.provider.getChainId() + +method populateTransaction*(signer: Signer, + transaction: Transaction): + Future[Transaction] {.base, async.} = + + if sender =? transaction.sender and sender != await signer.getAddress(): + raiseSignerError("from address mismatch") + if chainId =? transaction.chainId and chainId != await signer.getChainId(): + raiseSignerError("chain id mismatch") + + var populated = transaction + + if transaction.sender.isNone: + populated.sender = some(await signer.getAddress()) + if transaction.nonce.isNone: + populated.nonce = some(await signer.getTransactionCount(BlockTag.pending)) + if transaction.chainId.isNone: + populated.chainId = some(await signer.getChainId()) + if transaction.gasPrice.isNone: + populated.gasPrice = some(await signer.getGasPrice()) + if transaction.gasLimit.isNone: + populated.gasLimit = some(await signer.estimateGas(populated)) + + return populated diff --git a/ethers/transaction.nim b/ethers/transaction.nim index 7ec828b..c80a54b 100644 --- a/ethers/transaction.nim +++ b/ethers/transaction.nim @@ -5,6 +5,10 @@ type Transaction* = object sender*: ?Address to*: Address data*: seq[byte] + nonce*: ?UInt256 + chainId*: ?UInt256 + gasPrice*: ?UInt256 + gasLimit*: ?UInt256 func `$`*(transaction: Transaction): string = result = "(" @@ -12,4 +16,12 @@ func `$`*(transaction: Transaction): string = result &= "from: " & $sender & ", " result &= "to: " & $transaction.to & ", " result &= "data: 0x" & $transaction.data.toHex + if nonce =? transaction.nonce: + result &= ", nonce: 0x" & $nonce.toHex + if chainId =? transaction.chainId: + result &= ", chainId: " & $chainId + if gasPrice =? transaction.gasPrice: + result &= ", gasPrice: 0x" & $gasPrice.toHex + if gasLimit =? transaction.gasLimit: + result &= ", gasLimit: 0x" & $gasLimit.toHex result &= ")" diff --git a/testmodule/examples.nim b/testmodule/examples.nim index b4eba00..dfd7345 100644 --- a/testmodule/examples.nim +++ b/testmodule/examples.nim @@ -4,15 +4,21 @@ import pkg/ethers randomize() -proc example*(_: type Address): Address = - var address: array[20, byte] - for b in address.mitems: +proc example*[N](_: type array[N, byte]): array[N, byte] = + var a: array[N, byte] + for b in a.mitems: b = rand(byte) - Address.init(address) + a proc example*(_: type seq[byte]): seq[byte] = let length = rand(0..<20) newSeqWith(length, rand(byte)) +proc example*(_: type Address): Address = + Address.init(array[20, byte].example) + +proc example*(_: type UInt256): UInt256 = + UInt256.fromBytesBE(array[32, byte].example) + proc example*(_: type Transaction): Transaction = Transaction(to: Address.example, data: seq[byte].example) diff --git a/testmodule/testJsonRpcSigner.nim b/testmodule/testJsonRpcSigner.nim index 1b8a1dd..d424b71 100644 --- a/testmodule/testJsonRpcSigner.nim +++ b/testmodule/testJsonRpcSigner.nim @@ -38,3 +38,38 @@ suite "JsonRpcSigner": let signer = provider.getSigner() let chainId = await signer.getChainId() check chainId == 31337.u256 # hardhat chain id + + test "can populate missing fields in a transaction": + let signer = provider.getSigner() + let transaction = Transaction.example + let populated = await signer.populateTransaction(transaction) + 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) + check !populated.chainId == await signer.getChainId() + + test "populate does not overwrite existing fields": + let signer = provider.getSigner() + var transaction = Transaction.example + transaction.sender = some await signer.getAddress() + transaction.nonce = some UInt256.example + transaction.chainId = some await signer.getChainId() + transaction.gasPrice = some UInt256.example + transaction.gasLimit = some UInt256.example + let populated = await signer.populateTransaction(transaction) + check populated == transaction + + test "populate fails when sender does not match signer address": + let signer = provider.getSigner() + var transaction = Transaction.example + transaction.sender = accounts[1].some + expect SignerError: + discard await signer.populateTransaction(transaction) + + test "populate fails when chain id does not match": + let signer = provider.getSigner() + var transaction = Transaction.example + transaction.chainId = 0xdeadbeef.u256.some + expect SignerError: + discard await signer.populateTransaction(transaction)