From e37f45476124f138234bfe748c98c1665aca3086 Mon Sep 17 00:00:00 2001 From: Mark Spanbroek Date: Wed, 9 Apr 2025 15:03:38 +0200 Subject: [PATCH] Allow for gas estimation of contract calls --- ethers/contracts.nim | 7 +++++- ethers/contracts/gas.nim | 45 ++++++++++++++++++++++++++++++++++++ testmodule/testContracts.nim | 12 ++++++++-- 3 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 ethers/contracts/gas.nim diff --git a/ethers/contracts.nim b/ethers/contracts.nim index 2528d53..7e2d1a9 100644 --- a/ethers/contracts.nim +++ b/ethers/contracts.nim @@ -5,6 +5,7 @@ import ./contracts/confirmation import ./contracts/events import ./contracts/filters import ./contracts/syntax +import ./contracts/gas import ./contracts/function export contract @@ -16,6 +17,7 @@ export syntax.view export syntax.pure export syntax.getter export syntax.errors +export gas.estimateGas {.push raises: [].} @@ -23,4 +25,7 @@ macro contract*(procedure: untyped{nkProcDef | nkMethodDef}): untyped = procedure.params.expectMinLen(2) # at least return type and contract instance procedure.body.expectKind(nnkEmpty) - createContractFunction(procedure) + newStmtList( + createContractFunction(procedure), + createGasEstimationCall(procedure) + ) diff --git a/ethers/contracts/gas.nim b/ethers/contracts/gas.nim new file mode 100644 index 0000000..e90bb89 --- /dev/null +++ b/ethers/contracts/gas.nim @@ -0,0 +1,45 @@ +import std/macros +import ../basics +import ../provider +import ../signer +import ./contract +import ./contractcall +import ./transactions +import ./syntax + +type ContractGasEstimations[C] = distinct C + +func estimateGas*[C: Contract](contract: C): ContractGasEstimations[C] = + ContractGasEstimations[C](contract) + +proc estimateGas( + call: ContractCall +): Future[UInt256] {.async: (raises: [CancelledError, ProviderError, EthersError]).} = + var transaction = createTransaction(call) + if signer =? call.contract.signer: + await signer.estimateGas(transaction) + else: + await call.contract.provider.estimateGas(transaction) + +func wrapFirstParameter(procedure: var NimNode) = + let contractType = procedure.params[1][1] + let gasEstimationsType = quote do: ContractGasEstimations[`contractType`] + procedure.params[1][1] = gasEstimationsType + +func setReturnType(procedure: var NimNode) = + procedure.params[0] = quote do: Future[UInt256] + +func addEstimateCall(procedure: var NimNode) = + let contractCall = getContractCall(procedure) + procedure.body = quote do: + return await estimateGas(`contractCall`) + +func createGasEstimationCall*(procedure: NimNode): NimNode = + result = copyNimTree(procedure) + result.wrapFirstParameter() + result.addOverridesParameter() + result.setReturnType() + result.addAsyncPragma() + result.addUsedPragma() + result.addEstimateCall() + diff --git a/testmodule/testContracts.nim b/testmodule/testContracts.nim index b1fcdda..023eb69 100644 --- a/testmodule/testContracts.nim +++ b/testmodule/testContracts.nim @@ -64,7 +64,7 @@ for url in ["ws://" & providerUrl, "http://" & providerUrl]: test "can call constant functions without a return type": token = TestToken.new(token.address, provider.getSigner()) proc mint(token: TestToken, holder: Address, amount: UInt256) {.contract, view.} - await mint(token, accounts[1], 100.u256) + await token.mint(accounts[1], 100.u256) check (await balanceOf(token, accounts[1])) == 0.u256 test "can call non-constant functions without a return type": @@ -74,7 +74,6 @@ for url in ["ws://" & providerUrl, "http://" & providerUrl]: check (await balanceOf(token, accounts[1])) == 100.u256 test "can call non-constant functions with a Confirmable return type": - token = TestToken.new(token.address, provider.getSigner()) proc mint(token: TestToken, holder: Address, @@ -146,6 +145,15 @@ for url in ["ws://" & providerUrl, "http://" & providerUrl]: discard await token.transfer(accounts[1], 50.u256, beforeMint) discard await token.transfer(accounts[1], 50.u256, afterMint) + test "can estimate gas of a function call": + proc mint(token: TestToken, holder: Address, amount: UInt256) {.contract.} + let estimate = await token.estimateGas.mint(accounts[1], 100.u256) + let correctGas = TransactionOverrides(gasLimit: some estimate) + await token.mint(accounts[1], 100.u256, correctGas) + let invalidGas = TransactionOverrides(gasLimit: some (estimate - 1)) + expect ProviderError: + await token.mint(accounts[1], 100.u256, invalidGas) + test "receives events when subscribed": var transfers: seq[Transfer]