Add Signer.populateTransaction()

This commit is contained in:
Mark Spanbroek 2022-01-25 10:25:09 +01:00
parent 4acc6ef45c
commit 6bd3e27e38
8 changed files with 102 additions and 10 deletions

View File

@ -11,3 +11,6 @@ export results
export stint export stint
export upraises export upraises
export address export address
type
EthersError* = object of IOError

View File

@ -11,7 +11,7 @@ type
Contract* = ref object of RootObj Contract* = ref object of RootObj
provider: Provider provider: Provider
address: Address address: Address
ContractError* = object of IOError ContractError* = object of EthersError
template raiseContractError(message: string) = template raiseContractError(message: string) =
raise newException(ContractError, message) raise newException(ContractError, message)

View File

@ -16,7 +16,7 @@ type
JsonRpcSigner* = ref object of Signer JsonRpcSigner* = ref object of Signer
provider: JsonRpcProvider provider: JsonRpcProvider
address: ?Address address: ?Address
JsonRpcProviderError* = object of IOError JsonRpcProviderError* = object of EthersError
template raiseProviderError(message: string) = template raiseProviderError(message: string) =
raise newException(JsonRpcProviderError, message) raise newException(JsonRpcProviderError, message)

View File

@ -26,17 +26,25 @@ func fromJson*(json: JsonNode, name: string, result: var Address) =
# UInt256 # UInt256
func `%`*(integer: UInt256): JsonNode = func `%`*(integer: UInt256): JsonNode =
%toHex(integer) %("0x" & toHex(integer))
func fromJson*(json: JsonNode, name: string, result: var UInt256) = func fromJson*(json: JsonNode, name: string, result: var UInt256) =
result = UInt256.fromHex(json.getStr()) result = UInt256.fromHex(json.getStr())
# Transaction # Transaction
func `%`*(tx: Transaction): JsonNode = func `%`*(transaction: Transaction): JsonNode =
result = %{ "to": %tx.to, "data": %tx.data } result = %{ "to": %transaction.to, "data": %transaction.data }
if sender =? tx.sender: if sender =? transaction.sender:
result["from"] = %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 # BlockTag

View File

@ -4,6 +4,10 @@ import ./provider
export basics export basics
type Signer* = ref object of RootObj 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.} = method provider*(signer: Signer): Provider {.base.} =
doAssert false, "not implemented" doAssert false, "not implemented"
@ -28,3 +32,27 @@ method estimateGas*(signer: Signer,
method getChainId*(signer: Signer): Future[UInt256] {.base.} = method getChainId*(signer: Signer): Future[UInt256] {.base.} =
signer.provider.getChainId() 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

View File

@ -5,6 +5,10 @@ type Transaction* = object
sender*: ?Address sender*: ?Address
to*: Address to*: Address
data*: seq[byte] data*: seq[byte]
nonce*: ?UInt256
chainId*: ?UInt256
gasPrice*: ?UInt256
gasLimit*: ?UInt256
func `$`*(transaction: Transaction): string = func `$`*(transaction: Transaction): string =
result = "(" result = "("
@ -12,4 +16,12 @@ func `$`*(transaction: Transaction): string =
result &= "from: " & $sender & ", " result &= "from: " & $sender & ", "
result &= "to: " & $transaction.to & ", " result &= "to: " & $transaction.to & ", "
result &= "data: 0x" & $transaction.data.toHex 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 &= ")" result &= ")"

View File

@ -4,15 +4,21 @@ import pkg/ethers
randomize() randomize()
proc example*(_: type Address): Address = proc example*[N](_: type array[N, byte]): array[N, byte] =
var address: array[20, byte] var a: array[N, byte]
for b in address.mitems: for b in a.mitems:
b = rand(byte) b = rand(byte)
Address.init(address) a
proc example*(_: type seq[byte]): seq[byte] = proc example*(_: type seq[byte]): seq[byte] =
let length = rand(0..<20) let length = rand(0..<20)
newSeqWith(length, rand(byte)) 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 = proc example*(_: type Transaction): Transaction =
Transaction(to: Address.example, data: seq[byte].example) Transaction(to: Address.example, data: seq[byte].example)

View File

@ -38,3 +38,38 @@ suite "JsonRpcSigner":
let signer = provider.getSigner() let signer = provider.getSigner()
let chainId = await signer.getChainId() let chainId = await signer.getChainId()
check chainId == 31337.u256 # hardhat chain id 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)