mirror of
https://github.com/logos-storage/nim-ethers.git
synced 2026-01-02 13:43:06 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
965b8cd752 | ||
|
|
30871c7b1d |
@ -1,18 +1,18 @@
|
||||
version = "2.0.0"
|
||||
version = "2.1.0"
|
||||
author = "Nim Ethers Authors"
|
||||
description = "library for interacting with Ethereum"
|
||||
license = "MIT"
|
||||
|
||||
requires "nim >= 2.0.14"
|
||||
requires "chronicles >= 0.10.3 & < 0.11.0"
|
||||
requires "chronicles >= 0.10.3 & < 0.13.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 "eth >= 0.5.0 & < 0.6.0"
|
||||
requires "stew >= 0.2.0"
|
||||
requires "eth >= 0.6.0 & < 0.10.0"
|
||||
|
||||
task test, "Run the test suite":
|
||||
# exec "nimble install -d -y"
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
)
|
||||
|
||||
|
||||
@ -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]).} =
|
||||
|
||||
@ -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]).} =
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import pkg/eth/keys
|
||||
import pkg/eth/rlp
|
||||
import pkg/eth/common/transaction as eth
|
||||
import pkg/eth/common/transaction_utils
|
||||
import pkg/eth/common/eth_hash
|
||||
import ../../basics
|
||||
import ../../transaction as ethers
|
||||
@ -25,18 +26,18 @@ func toSignableTransaction(transaction: Transaction): SignableTransaction =
|
||||
raiseWalletError "missing gas limit"
|
||||
|
||||
signable.nonce = nonce.truncate(uint64)
|
||||
signable.chainId = ChainId(chainId.truncate(uint64))
|
||||
signable.chainId = chainId
|
||||
signable.gasLimit = GasInt(gasLimit.truncate(uint64))
|
||||
|
||||
signable.to = Opt.some(EthAddress(transaction.to))
|
||||
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))
|
||||
@ -47,21 +48,7 @@ func toSignableTransaction(transaction: Transaction): SignableTransaction =
|
||||
|
||||
func sign(key: PrivateKey, transaction: SignableTransaction): seq[byte] =
|
||||
var transaction = transaction
|
||||
|
||||
# Temporary V value, used to signal to the hashing function
|
||||
# that we'd like to use an EIP-155 signature
|
||||
transaction.V = uint64(transaction.chainId) * 2 + 35
|
||||
|
||||
let hash = transaction.txHashNoSignature().data
|
||||
let signature = key.sign(SkMessage(hash)).toRaw()
|
||||
|
||||
transaction.R = UInt256.fromBytesBE(signature[0..<32])
|
||||
transaction.S = UInt256.fromBytesBE(signature[32..<64])
|
||||
transaction.V = uint64(signature[64])
|
||||
|
||||
if transaction.txType == TxLegacy:
|
||||
transaction.V += uint64(transaction.chainId) * 2 + 35
|
||||
|
||||
transaction.signature = transaction.sign(key, true)
|
||||
rlp.encode(transaction)
|
||||
|
||||
func sign*(key: PrivateKey, transaction: Transaction): seq[byte] =
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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":
|
||||
|
||||
@ -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":
|
||||
|
||||
@ -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)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user