diff --git a/ethers.nimble b/ethers.nimble index 5df4668..d1489f8 100644 --- a/ethers.nimble +++ b/ethers.nimble @@ -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": diff --git a/ethers/contracts/overrides.nim b/ethers/contracts/overrides.nim index 6939706..241464d 100644 --- a/ethers/contracts/overrides.nim +++ b/ethers/contracts/overrides.nim @@ -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 diff --git a/ethers/contracts/transactions.nim b/ethers/contracts/transactions.nim index c538973..814c533 100644 --- a/ethers/contracts/transactions.nim +++ b/ethers/contracts/transactions.nim @@ -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, ) diff --git a/ethers/provider.nim b/ethers/provider.nim index 5a2153e..55cc07c 100644 --- a/ethers/provider.nim +++ b/ethers/provider.nim @@ -63,6 +63,7 @@ type number*: ?UInt256 timestamp*: UInt256 hash*: ?BlockHash + baseFeePerGas* : ?UInt256 PastTransaction* {.serialize.} = object blockHash*: BlockHash blockNumber*: UInt256 @@ -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]).} = diff --git a/ethers/providers/jsonrpc.nim b/ethers/providers/jsonrpc.nim index 636193c..65420a1 100644 --- a/ethers/providers/jsonrpc.nim +++ b/ethers/providers/jsonrpc.nim @@ -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 @@ -43,6 +44,7 @@ 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")] @@ -50,7 +52,8 @@ proc jsonHeaders: seq[(string, string)] = 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 @@ -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 @@ -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]).} = diff --git a/ethers/providers/jsonrpc/signatures.nim b/ethers/providers/jsonrpc/signatures.nim index 60aded4..9e78bc1 100644 --- a/ethers/providers/jsonrpc/signatures.nim +++ b/ethers/providers/jsonrpc/signatures.nim @@ -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 diff --git a/ethers/signer.nim b/ethers/signer.nim index a6aa82f..e38509e 100644 --- a/ethers/signer.nim +++ b/ethers/signer.nim @@ -1,4 +1,5 @@ import pkg/questionable +import pkg/chronicles import ./basics import ./errors import ./provider @@ -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] {. @@ -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 if transaction.nonce.isNone and transaction.gasLimit.isNone: # when both nonce and gasLimit are not populated, we must ensure getNonce is diff --git a/ethers/signers/wallet/signing.nim b/ethers/signers/wallet/signing.nim index 18b73f3..7a05146 100644 --- a/ethers/signers/wallet/signing.nim +++ b/ethers/signers/wallet/signing.nim @@ -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)) diff --git a/ethers/transaction.nim b/ethers/transaction.nim index 1ff2e1b..3bf454f 100644 --- a/ethers/transaction.nim +++ b/ethers/transaction.nim @@ -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 diff --git a/testmodule/providers/jsonrpc/testJsonRpcSigner.nim b/testmodule/providers/jsonrpc/testJsonRpcSigner.nim index 64d0527..31d6df5 100644 --- a/testmodule/providers/jsonrpc/testJsonRpcSigner.nim +++ b/testmodule/providers/jsonrpc/testJsonRpcSigner.nim @@ -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": diff --git a/testmodule/testContracts.nim b/testmodule/testContracts.nim index 023eb69..f1e70d1 100644 --- a/testmodule/testContracts.nim +++ b/testmodule/testContracts.nim @@ -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": diff --git a/testmodule/testWallet.nim b/testmodule/testWallet.nim index 6f1e105..21b5018 100644 --- a/testmodule/testWallet.nim +++ b/testmodule/testWallet.nim @@ -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) @@ -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)