Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions ethers.nimble
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ license = "MIT"
requires "nim >= 2.0.14"
requires "chronicles >= 0.10.3 & < 0.11.0"
requires "chronos >= 4.0.4 & < 4.1.0"
requires "contractabi >= 0.7.0 & < 0.8.0"
requires "contractabi >= 0.7.2 & < 0.8.0"
requires "questionable >= 0.10.2 & < 0.11.0"
requires "json_rpc >= 0.5.0 & < 0.6.0"
requires "serde >= 1.2.1 & < 1.3.0"
requires "stint >= 0.8.1 & < 0.9.0"
requires "stew >= 0.2.0 & < 0.3.0"
requires "stew >= 0.2.0"
requires "eth >= 0.5.0 & < 0.6.0"

task test, "Run the test suite":
Expand Down
4 changes: 2 additions & 2 deletions ethers/contracts/overrides.nim
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ type
nonce*: ?UInt256
chainId*: ?UInt256
gasPrice*: ?UInt256
maxFee*: ?UInt256
maxPriorityFee*: ?UInt256
maxFeePerGas*: ?UInt256
maxPriorityFeePerGas*: ?UInt256
gasLimit*: ?UInt256
CallOverrides* = ref object of TransactionOverrides
blockTag*: ?BlockTag
4 changes: 2 additions & 2 deletions ethers/contracts/transactions.nim
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ proc createTransaction*(call: ContractCall): Transaction =
nonce: call.overrides.nonce,
chainId: call.overrides.chainId,
gasPrice: call.overrides.gasPrice,
maxFee: call.overrides.maxFee,
maxPriorityFee: call.overrides.maxPriorityFee,
maxFeePerGas: call.overrides.maxFeePerGas,
maxPriorityFeePerGas: call.overrides.maxPriorityFeePerGas,
gasLimit: call.overrides.gasLimit,
)

Expand Down
6 changes: 6 additions & 0 deletions ethers/provider.nim
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ type
number*: ?UInt256
timestamp*: UInt256
hash*: ?BlockHash
baseFeePerGas* : ?UInt256
PastTransaction* {.serialize.} = object
blockHash*: BlockHash
blockNumber*: UInt256
Expand Down Expand Up @@ -121,6 +122,11 @@ method getGasPrice*(
): Future[UInt256] {.base, async: (raises: [ProviderError, CancelledError]).} =
doAssert false, "not implemented"

method getMaxPriorityFeePerGas*(
provider: Provider
): Future[UInt256] {.base, async: (raises: [CancelledError]).} =
doAssert false, "not implemented"

method getTransactionCount*(
provider: Provider, address: Address, blockTag = BlockTag.latest
): Future[UInt256] {.base, async: (raises: [ProviderError, CancelledError]).} =
Expand Down
19 changes: 17 additions & 2 deletions ethers/providers/jsonrpc.nim
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type
JsonRpcProvider* = ref object of Provider
client: Future[RpcClient]
subscriptions: Future[JsonRpcSubscriptions]
maxPriorityFeePerGas: UInt256

JsonRpcSubscription* = ref object of Subscription
subscriptions: JsonRpcSubscriptions
Expand All @@ -43,14 +44,16 @@ type

const defaultUrl = "http://localhost:8545"
const defaultPollingInterval = 4.seconds
const defaultMaxPriorityFeePerGas = 1_000_000_000.u256

proc jsonHeaders: seq[(string, string)] =
@[("Content-Type", "application/json")]

proc new*(
_: type JsonRpcProvider,
url=defaultUrl,
pollingInterval=defaultPollingInterval): JsonRpcProvider {.raises: [JsonRpcProviderError].} =
pollingInterval=defaultPollingInterval,
maxPriorityFeePerGas=defaultMaxPriorityFeePerGas): JsonRpcProvider {.raises: [JsonRpcProviderError].} =

var initialized: Future[void]
var client: RpcClient
Expand Down Expand Up @@ -87,7 +90,7 @@ proc new*(
return subscriptions

initialized = initialize()
return JsonRpcProvider(client: awaitClient(), subscriptions: awaitSubscriptions())
return JsonRpcProvider(client: awaitClient(), subscriptions: awaitSubscriptions(), maxPriorityFeePerGas: maxPriorityFeePerGas)

proc callImpl(
client: RpcClient, call: string, args: JsonNode
Expand Down Expand Up @@ -151,6 +154,18 @@ method getGasPrice*(
let client = await provider.client
return await client.eth_gasPrice()

method getMaxPriorityFeePerGas*(
provider: JsonRpcProvider
): Future[UInt256] {.async: (raises: [CancelledError]).} =
try:
convertError:
let client = await provider.client
return await client.eth_maxPriorityFeePerGas()
except JsonRpcProviderError:
# If the provider does not provide the implementation
# let's just remove the manual value
return provider.maxPriorityFeePerGas

method getTransactionCount*(
provider: JsonRpcProvider, address: Address, blockTag = BlockTag.latest
): Future[UInt256] {.async: (raises: [ProviderError, CancelledError]).} =
Expand Down
1 change: 1 addition & 0 deletions ethers/providers/jsonrpc/signatures.nim
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ proc eth_newBlockFilter(): JsonNode
proc eth_newFilter(filter: EventFilter): JsonNode
proc eth_getFilterChanges(id: JsonNode): JsonNode
proc eth_uninstallFilter(id: JsonNode): bool
proc eth_maxPriorityFeePerGas(): UInt256
28 changes: 26 additions & 2 deletions ethers/signer.nim
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import pkg/questionable
import pkg/chronicles
import ./basics
import ./errors
import ./provider
Expand Down Expand Up @@ -55,6 +56,11 @@ method getGasPrice*(
.} =
return await signer.provider.getGasPrice()

method getMaxPriorityFeePerGas*(
signer: Signer
): Future[UInt256] {.async: (raises: [SignerError, CancelledError]).} =
return await signer.provider.getMaxPriorityFeePerGas()

method getTransactionCount*(
signer: Signer, blockTag = BlockTag.latest
): Future[UInt256] {.
Expand Down Expand Up @@ -124,8 +130,26 @@ method populateTransaction*(
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):
populated.gasPrice = some(await signer.getGasPrice())

let blk = await signer.provider.getBlock(BlockTag.latest)

if baseFeePerGas =? blk.?baseFeePerGas:
let maxPriorityFeePerGas = transaction.maxPriorityFeePerGas |? (await signer.provider.getMaxPriorityFeePerGas())
populated.maxPriorityFeePerGas = some(maxPriorityFeePerGas)

# Multiply by 2 because during times of congestion, baseFeePerGas can increase by 12.5% per block.
# https://github.com/ethers-io/ethers.js/discussions/3601#discussioncomment-4461273
let maxFeePerGas = transaction.maxFeePerGas |? (baseFeePerGas * 2 + maxPriorityFeePerGas)
populated.maxFeePerGas = some(maxFeePerGas)

populated.gasPrice = none(UInt256)

trace "EIP-1559 is supported", maxPriorityFeePerGas = maxPriorityFeePerGas, maxFeePerGas = maxFeePerGas
else:
populated.gasPrice = some(transaction.gasPrice |? (await signer.getGasPrice()))
populated.maxFeePerGas = none(UInt256)
populated.maxPriorityFeePerGas = none(UInt256)
trace "EIP-1559 is not supported", gasPrice = populated.gasPrice
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we set populated.maxFeePerGas = none(UInt256) just in case here? Thinking of a case where the transaction.maxFeePerGas was already populated but the block didn't have a baseFeePerGas value.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added it just in case, but it should not have any impact. If the block doesn't have baseFeePerGas that means EIP-1559 is not supported so maxFeePerGas will not be used.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair point. When the transaction is signed in the wallet, the first condition checked is whether or not transaction.maxFeePerGas has a value (see line 35 in signers/wallet/signing.nim), which ends up setting signable.txType = TxEip1559 and could have side effects later on.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may also want to set maxPriorityFeePerGas to none just in case. Just a suggestion.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes you are right. I just updated the PR to set none to maxPriorityFeePerGas to avoid side effect. Thanks.


if transaction.nonce.isNone and transaction.gasLimit.isNone:
# when both nonce and gasLimit are not populated, we must ensure getNonce is
Expand Down
8 changes: 4 additions & 4 deletions ethers/signers/wallet/signing.nim
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ func toSignableTransaction(transaction: Transaction): SignableTransaction =
signable.value = transaction.value
signable.payload = transaction.data

if maxFee =? transaction.maxFee and
maxPriorityFee =? transaction.maxPriorityFee:
if maxFeePerGas =? transaction.maxFeePerGas and
maxPriorityFeePerGas =? transaction.maxPriorityFeePerGas:
signable.txType = TxEip1559
signable.maxFeePerGas = GasInt(maxFee.truncate(uint64))
signable.maxPriorityFeePerGas = GasInt(maxPriorityFee.truncate(uint64))
signable.maxFeePerGas = GasInt(maxFeePerGas.truncate(uint64))
signable.maxPriorityFeePerGas = GasInt(maxPriorityFeePerGas.truncate(uint64))
elif gasPrice =? transaction.gasPrice:
signable.txType = TxLegacy
signable.gasPrice = GasInt(gasPrice.truncate(uint64))
Expand Down
4 changes: 2 additions & 2 deletions ethers/transaction.nim
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ type
nonce*: ?UInt256
chainId*: ?UInt256
gasPrice*: ?UInt256
maxFee*: ?UInt256
maxPriorityFee*: ?UInt256
maxPriorityFeePerGas*: ?UInt256
maxFeePerGas*: ?UInt256
gasLimit*: ?UInt256
transactionType* {.serialize("type").}: ?TransactionType

Expand Down
11 changes: 9 additions & 2 deletions testmodule/providers/jsonrpc/testJsonRpcSigner.nim
Original file line number Diff line number Diff line change
Expand Up @@ -55,20 +55,27 @@ suite "JsonRpcSigner":
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()

let blk = !(await signer.provider.getBlock(BlockTag.latest))
check !populated.maxPriorityFeePerGas == await signer.getMaxPriorityFeePerGas()
check !populated.maxFeePerGas == !blk.baseFeePerGas * 2.u256 + !populated.maxPriorityFeePerGas

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.maxPriorityFeePerGas = some UInt256.example
transaction.gasLimit = some UInt256.example
let populated = await signer.populateTransaction(transaction)

let blk = !(await signer.provider.getBlock(BlockTag.latest))
transaction.maxFeePerGas = some(!blk.baseFeePerGas * 2.u256 + !populated.maxPriorityFeePerGas)

check populated == transaction

test "populate fails when sender does not match signer address":
Expand Down
6 changes: 3 additions & 3 deletions testmodule/testContracts.nim
Original file line number Diff line number Diff line change
Expand Up @@ -107,17 +107,17 @@ for url in ["ws://" & providerUrl, "http://" & providerUrl]:
check (await token.connect(provider).balanceOf(accounts[1])) == 25.u256
check (await token.connect(provider).balanceOf(accounts[2])) == 25.u256

test "takes custom values for nonce, gasprice and gaslimit":
test "takes custom values for nonce, gasprice and maxPriorityFeePerGas":
let overrides = TransactionOverrides(
nonce: some 100.u256,
gasPrice: some 200.u256,
maxPriorityFeePerGas: some 200.u256,
gasLimit: some 300.u256
)
let signer = MockSigner.new(provider)
discard await token.connect(signer).mint(accounts[0], 42.u256, overrides)
check signer.transactions.len == 1
check signer.transactions[0].nonce == overrides.nonce
check signer.transactions[0].gasPrice == overrides.gasPrice
check signer.transactions[0].maxPriorityFeePerGas == overrides.maxPriorityFeePerGas
check signer.transactions[0].gasLimit == overrides.gasLimit

test "can call functions for different block heights":
Expand Down
8 changes: 4 additions & 4 deletions testmodule/testWallet.nim
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ suite "Wallet":
to: wallet.address,
nonce: some 0.u256,
chainId: some 31337.u256,
maxFee: some 2_000_000_000.u256,
maxPriorityFee: some 1_000_000_000.u256,
maxFeePerGas: some 2_000_000_000.u256,
maxPriorityFeePerGas: some 1_000_000_000.u256,
gasLimit: some 21_000.u256
)
let signedTx = await wallet.signTransaction(tx)
Expand Down Expand Up @@ -115,8 +115,8 @@ suite "Wallet":
let wallet = !Wallet.new(pk_with_funds, provider)
let overrides = TransactionOverrides(
nonce: some 0.u256,
maxFee: some 1_000_000_000.u256,
maxPriorityFee: some 1_000_000_000.u256,
maxFeePerGas: some 1_000_000_000.u256,
maxPriorityFeePerGas: some 1_000_000_000.u256,
gasLimit: some 22_000.u256)
let testToken = Erc20.new(wallet.address, wallet)
await testToken.transfer(wallet.address, 24.u256, overrides)