mirror of
https://github.com/logos-storage/nim-ethers.git
synced 2026-01-02 13:43:06 +00:00
Split contract module into several parts
This commit is contained in:
parent
51aa7bc1b3
commit
def12bfdc1
@ -1,11 +1,11 @@
|
||||
import ./ethers/provider
|
||||
import ./ethers/signer
|
||||
import ./ethers/providers/jsonrpc
|
||||
import ./ethers/contract
|
||||
import ./ethers/contracts
|
||||
import ./ethers/wallet
|
||||
|
||||
export provider
|
||||
export signer
|
||||
export jsonrpc
|
||||
export contract
|
||||
export contracts
|
||||
export wallet
|
||||
@ -1,408 +0,0 @@
|
||||
import pkg/serde
|
||||
import std/macros
|
||||
import std/sequtils
|
||||
import pkg/chronicles
|
||||
import pkg/chronos
|
||||
import pkg/questionable
|
||||
import pkg/contractabi
|
||||
import ./basics
|
||||
import ./provider
|
||||
import ./signer
|
||||
import ./events
|
||||
import ./errors
|
||||
import ./errors/conversion
|
||||
import ./fields
|
||||
|
||||
export basics
|
||||
export provider
|
||||
export events
|
||||
export errors.SolidityError
|
||||
export errors.errors
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
logScope:
|
||||
topics = "ethers contract"
|
||||
|
||||
type
|
||||
Contract* = ref object of RootObj
|
||||
provider: Provider
|
||||
signer: ?Signer
|
||||
address: Address
|
||||
TransactionOverrides* = ref object of RootObj
|
||||
nonce*: ?UInt256
|
||||
chainId*: ?UInt256
|
||||
gasPrice*: ?UInt256
|
||||
maxFee*: ?UInt256
|
||||
maxPriorityFee*: ?UInt256
|
||||
gasLimit*: ?UInt256
|
||||
CallOverrides* = ref object of TransactionOverrides
|
||||
blockTag*: ?BlockTag
|
||||
Confirmable* = object
|
||||
response*: ?TransactionResponse
|
||||
convert*: ConvertCustomErrors
|
||||
EventHandler*[E: Event] = proc(event: ?!E) {.gcsafe, raises:[].}
|
||||
|
||||
func new*(ContractType: type Contract,
|
||||
address: Address,
|
||||
provider: Provider): ContractType =
|
||||
ContractType(provider: provider, address: address)
|
||||
|
||||
func new*(ContractType: type Contract,
|
||||
address: Address,
|
||||
signer: Signer): ContractType {.raises: [SignerError].} =
|
||||
ContractType(signer: some signer, provider: signer.provider, address: address)
|
||||
|
||||
func connect*[T: Contract](contract: T, provider: Provider | Signer): T {.raises: [SignerError].} =
|
||||
T.new(contract.address, provider)
|
||||
|
||||
func provider*(contract: Contract): Provider =
|
||||
contract.provider
|
||||
|
||||
func signer*(contract: Contract): ?Signer =
|
||||
contract.signer
|
||||
|
||||
func address*(contract: Contract): Address =
|
||||
contract.address
|
||||
|
||||
template raiseContractError(message: string) =
|
||||
raise newException(ContractError, message)
|
||||
|
||||
proc createTransaction(contract: Contract,
|
||||
function: string,
|
||||
parameters: tuple,
|
||||
overrides = TransactionOverrides()): Transaction =
|
||||
let selector = selector(function, typeof parameters).toArray
|
||||
let data = @selector & AbiEncoder.encode(parameters)
|
||||
Transaction(
|
||||
to: contract.address,
|
||||
data: data,
|
||||
nonce: overrides.nonce,
|
||||
chainId: overrides.chainId,
|
||||
gasPrice: overrides.gasPrice,
|
||||
maxFee: overrides.maxFee,
|
||||
maxPriorityFee: overrides.maxPriorityFee,
|
||||
gasLimit: overrides.gasLimit,
|
||||
)
|
||||
|
||||
proc decodeResponse(T: type, bytes: seq[byte]): T {.raises: [ContractError].} =
|
||||
without decoded =? AbiDecoder.decode(bytes, T):
|
||||
raiseContractError "unable to decode return value as " & $T
|
||||
return decoded
|
||||
|
||||
proc call(
|
||||
provider: Provider, transaction: Transaction, overrides: TransactionOverrides
|
||||
): Future[seq[byte]] {.async: (raises: [ProviderError, CancelledError]).} =
|
||||
if overrides of CallOverrides and blockTag =? CallOverrides(overrides).blockTag:
|
||||
await provider.call(transaction, blockTag)
|
||||
else:
|
||||
await provider.call(transaction)
|
||||
|
||||
proc call(
|
||||
contract: Contract,
|
||||
function: string,
|
||||
parameters: tuple,
|
||||
overrides = TransactionOverrides(),
|
||||
) {.async: (raises: [ProviderError, SignerError, CancelledError]).} =
|
||||
var transaction = createTransaction(contract, function, parameters, overrides)
|
||||
|
||||
if signer =? contract.signer and transaction.sender.isNone:
|
||||
transaction.sender = some(await signer.getAddress())
|
||||
|
||||
discard await contract.provider.call(transaction, overrides)
|
||||
|
||||
proc call(
|
||||
contract: Contract,
|
||||
function: string,
|
||||
parameters: tuple,
|
||||
ReturnType: type,
|
||||
overrides = TransactionOverrides(),
|
||||
): Future[ReturnType] {.
|
||||
async: (raises: [ProviderError, SignerError, ContractError, CancelledError])
|
||||
.} =
|
||||
var transaction = createTransaction(contract, function, parameters, overrides)
|
||||
|
||||
if signer =? contract.signer and transaction.sender.isNone:
|
||||
transaction.sender = some(await signer.getAddress())
|
||||
|
||||
let response = await contract.provider.call(transaction, overrides)
|
||||
return decodeResponse(ReturnType, response)
|
||||
|
||||
proc send(
|
||||
contract: Contract,
|
||||
function: string,
|
||||
parameters: tuple,
|
||||
overrides = TransactionOverrides()
|
||||
): Future[?TransactionResponse] {.async: (raises: [SignerError, ProviderError, CancelledError]).} =
|
||||
|
||||
if signer =? contract.signer:
|
||||
withLock(signer):
|
||||
let transaction = createTransaction(contract, function, parameters, overrides)
|
||||
let populated = await signer.populateTransaction(transaction)
|
||||
trace "sending contract transaction", function, params = $parameters
|
||||
let txResp = await signer.sendTransaction(populated)
|
||||
return txResp.some
|
||||
else:
|
||||
await call(contract, function, parameters, overrides)
|
||||
return TransactionResponse.none
|
||||
|
||||
func getParameterTuple(procedure: NimNode): NimNode =
|
||||
let parameters = procedure[3]
|
||||
var tupl = newNimNode(nnkTupleConstr, parameters)
|
||||
for parameter in parameters[2..^1]:
|
||||
for name in parameter[0..^3]:
|
||||
tupl.add name
|
||||
return tupl
|
||||
|
||||
func getErrorTypes(procedure: NimNode): NimNode =
|
||||
let pragmas = procedure[4]
|
||||
var tupl = newNimNode(nnkTupleConstr)
|
||||
for pragma in pragmas:
|
||||
if pragma.kind == nnkExprColonExpr:
|
||||
if pragma[0].eqIdent "errors":
|
||||
pragma[1].expectKind(nnkBracket)
|
||||
for error in pragma[1]:
|
||||
tupl.add error
|
||||
if tupl.len == 0:
|
||||
quote do: tuple[]
|
||||
else:
|
||||
tupl
|
||||
|
||||
func isGetter(procedure: NimNode): bool =
|
||||
let pragmas = procedure[4]
|
||||
for pragma in pragmas:
|
||||
if pragma.eqIdent "getter":
|
||||
return true
|
||||
false
|
||||
|
||||
func isConstant(procedure: NimNode): bool =
|
||||
let pragmas = procedure[4]
|
||||
for pragma in pragmas:
|
||||
if pragma.eqIdent "view":
|
||||
return true
|
||||
elif pragma.eqIdent "pure":
|
||||
return true
|
||||
elif pragma.eqIdent "getter":
|
||||
return true
|
||||
false
|
||||
|
||||
func isMultipleReturn(returnType: NimNode): bool =
|
||||
(returnType.kind == nnkPar and returnType.len > 1) or
|
||||
(returnType.kind == nnkTupleConstr) or
|
||||
(returnType.kind == nnkTupleTy)
|
||||
|
||||
func addOverrides(procedure: var NimNode) =
|
||||
procedure[3].add(
|
||||
newIdentDefs(
|
||||
ident("overrides"),
|
||||
newEmptyNode(),
|
||||
quote do: TransactionOverrides()
|
||||
)
|
||||
)
|
||||
|
||||
func addContractCall(procedure: var NimNode) =
|
||||
let contract = procedure[3][1][0]
|
||||
let function = $basename(procedure[0])
|
||||
let parameters = getParameterTuple(procedure)
|
||||
let returnType = procedure[3][0]
|
||||
let isGetter = procedure.isGetter
|
||||
|
||||
procedure.addOverrides()
|
||||
let errors = getErrorTypes(procedure)
|
||||
|
||||
func call: NimNode =
|
||||
if returnType.kind == nnkEmpty:
|
||||
quote:
|
||||
await call(`contract`, `function`, `parameters`, overrides)
|
||||
elif returnType.isMultipleReturn or isGetter:
|
||||
quote:
|
||||
return await call(
|
||||
`contract`, `function`, `parameters`, `returnType`, overrides
|
||||
)
|
||||
else:
|
||||
quote:
|
||||
# solidity functions return a tuple, so wrap return type in a tuple
|
||||
let tupl = await call(
|
||||
`contract`, `function`, `parameters`, (`returnType`,), overrides
|
||||
)
|
||||
return tupl[0]
|
||||
|
||||
func send: NimNode =
|
||||
if returnType.kind == nnkEmpty:
|
||||
quote:
|
||||
discard await send(`contract`, `function`, `parameters`, overrides)
|
||||
else:
|
||||
quote:
|
||||
when typeof(result) isnot Confirmable:
|
||||
{.error:
|
||||
"unexpected return type, " &
|
||||
"missing {.view.}, {.pure.} or {.getter.} ?"
|
||||
.}
|
||||
let response = await send(`contract`, `function`, `parameters`, overrides)
|
||||
let convert = customErrorConversion(`errors`)
|
||||
Confirmable(response: response, convert: convert)
|
||||
|
||||
procedure[6] =
|
||||
if procedure.isConstant:
|
||||
call()
|
||||
else:
|
||||
send()
|
||||
|
||||
func addErrorHandling(procedure: var NimNode) =
|
||||
let body = procedure[6]
|
||||
let errors = getErrorTypes(procedure)
|
||||
procedure[6] = quote do:
|
||||
try:
|
||||
`body`
|
||||
except ProviderError as error:
|
||||
if data =? error.data:
|
||||
let convert = customErrorConversion(`errors`)
|
||||
raise convert(error)
|
||||
else:
|
||||
raise error
|
||||
|
||||
func addFuture(procedure: var NimNode) =
|
||||
let returntype = procedure[3][0]
|
||||
if returntype.kind != nnkEmpty:
|
||||
procedure[3][0] = quote: Future[`returntype`]
|
||||
|
||||
func addAsyncPragma(procedure: var NimNode) =
|
||||
let pragmas = procedure[4]
|
||||
if pragmas.kind == nnkEmpty:
|
||||
procedure[4] = newNimNode(nnkPragma)
|
||||
procedure[4].add nnkExprColonExpr.newTree(
|
||||
newIdentNode("async"),
|
||||
nnkTupleConstr.newTree(
|
||||
nnkExprColonExpr.newTree(
|
||||
newIdentNode("raises"),
|
||||
nnkBracket.newTree(
|
||||
newIdentNode("CancelledError"),
|
||||
newIdentNode("ProviderError"),
|
||||
newIdentNode("EthersError"),
|
||||
),
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
macro contract*(procedure: untyped{nkProcDef | nkMethodDef}): untyped =
|
||||
let parameters = procedure[3]
|
||||
let body = procedure[6]
|
||||
|
||||
parameters.expectMinLen(2) # at least return type and contract instance
|
||||
body.expectKind(nnkEmpty)
|
||||
|
||||
var contractcall = copyNimTree(procedure)
|
||||
contractcall.addContractCall()
|
||||
contractcall.addErrorHandling()
|
||||
contractcall.addFuture()
|
||||
contractcall.addAsyncPragma()
|
||||
contractcall
|
||||
|
||||
template view* {.pragma.}
|
||||
template pure* {.pragma.}
|
||||
template getter* {.pragma.}
|
||||
|
||||
proc subscribe*[E: Event](contract: Contract,
|
||||
_: type E,
|
||||
handler: EventHandler[E]):
|
||||
Future[Subscription] =
|
||||
|
||||
let topic = topic($E, E.fieldTypes).toArray
|
||||
let filter = EventFilter(address: contract.address, topics: @[topic])
|
||||
|
||||
proc logHandler(logResult: ?!Log) {.raises: [].} =
|
||||
without log =? logResult, error:
|
||||
handler(failure(E, error))
|
||||
return
|
||||
|
||||
if event =? E.decode(log.data, log.topics):
|
||||
handler(success(event))
|
||||
|
||||
contract.provider.subscribe(filter, logHandler)
|
||||
|
||||
proc confirm(tx: Confirmable, confirmations, timeout: int):
|
||||
Future[TransactionReceipt] {.async: (raises: [CancelledError, EthersError]).} =
|
||||
|
||||
without response =? tx.response:
|
||||
raise newException(
|
||||
EthersError,
|
||||
"Transaction hash required. Possibly was a call instead of a send?"
|
||||
)
|
||||
|
||||
try:
|
||||
return await response.confirm(confirmations, timeout)
|
||||
except ProviderError as error:
|
||||
let convert = tx.convert
|
||||
raise convert(error)
|
||||
|
||||
proc confirm*(tx: Future[Confirmable],
|
||||
confirmations: int = EthersDefaultConfirmations,
|
||||
timeout: int = EthersReceiptTimeoutBlks):
|
||||
Future[TransactionReceipt] {.async: (raises: [CancelledError, EthersError]).} =
|
||||
## Convenience method that allows confirm to be chained to a contract
|
||||
## transaction, eg:
|
||||
## `await token.connect(signer0)
|
||||
## .mint(accounts[1], 100.u256)
|
||||
## .confirm(3)`
|
||||
try:
|
||||
return await (await tx).confirm(confirmations, timeout)
|
||||
except CancelledError as e:
|
||||
raise e
|
||||
except EthersError as e:
|
||||
raise e
|
||||
except CatchableError as e:
|
||||
raise newException(
|
||||
EthersError,
|
||||
"Error when trying to confirm the contract transaction: " & e.msg
|
||||
)
|
||||
|
||||
proc queryFilter[E: Event](contract: Contract,
|
||||
_: type E,
|
||||
filter: EventFilter):
|
||||
Future[seq[E]] {.async.} =
|
||||
|
||||
var logs = await contract.provider.getLogs(filter)
|
||||
logs.keepItIf(not it.removed)
|
||||
|
||||
var events: seq[E] = @[]
|
||||
for log in logs:
|
||||
if event =? E.decode(log.data, log.topics):
|
||||
events.add event
|
||||
|
||||
return events
|
||||
|
||||
proc queryFilter*[E: Event](contract: Contract,
|
||||
_: type E):
|
||||
Future[seq[E]] =
|
||||
|
||||
let topic = topic($E, E.fieldTypes).toArray
|
||||
let filter = EventFilter(address: contract.address,
|
||||
topics: @[topic])
|
||||
|
||||
contract.queryFilter(E, filter)
|
||||
|
||||
proc queryFilter*[E: Event](contract: Contract,
|
||||
_: type E,
|
||||
blockHash: BlockHash):
|
||||
Future[seq[E]] =
|
||||
|
||||
let topic = topic($E, E.fieldTypes).toArray
|
||||
let filter = FilterByBlockHash(address: contract.address,
|
||||
topics: @[topic],
|
||||
blockHash: blockHash)
|
||||
|
||||
contract.queryFilter(E, filter)
|
||||
|
||||
proc queryFilter*[E: Event](contract: Contract,
|
||||
_: type E,
|
||||
fromBlock: BlockTag,
|
||||
toBlock: BlockTag):
|
||||
Future[seq[E]] =
|
||||
|
||||
let topic = topic($E, E.fieldTypes).toArray
|
||||
let filter = Filter(address: contract.address,
|
||||
topics: @[topic],
|
||||
fromBlock: fromBlock,
|
||||
toBlock: toBlock)
|
||||
|
||||
contract.queryFilter(E, filter)
|
||||
26
ethers/contracts.nim
Normal file
26
ethers/contracts.nim
Normal file
@ -0,0 +1,26 @@
|
||||
import std/macros
|
||||
import ./contracts/contract
|
||||
import ./contracts/overrides
|
||||
import ./contracts/confirmation
|
||||
import ./contracts/events
|
||||
import ./contracts/filters
|
||||
import ./contracts/syntax
|
||||
import ./contracts/function
|
||||
|
||||
export contract
|
||||
export overrides
|
||||
export confirmation
|
||||
export events
|
||||
export filters
|
||||
export syntax.view
|
||||
export syntax.pure
|
||||
export syntax.getter
|
||||
export syntax.errors
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
macro contract*(procedure: untyped{nkProcDef | nkMethodDef}): untyped =
|
||||
procedure.params.expectMinLen(2) # at least return type and contract instance
|
||||
procedure.body.expectKind(nnkEmpty)
|
||||
|
||||
createContractFunction(procedure)
|
||||
45
ethers/contracts/confirmation.nim
Normal file
45
ethers/contracts/confirmation.nim
Normal file
@ -0,0 +1,45 @@
|
||||
import ../provider
|
||||
import ../errors/conversion
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
type Confirmable* = object
|
||||
response*: ?TransactionResponse
|
||||
convert*: ConvertCustomErrors
|
||||
|
||||
proc confirm(tx: Confirmable, confirmations, timeout: int):
|
||||
Future[TransactionReceipt] {.async: (raises: [CancelledError, EthersError]).} =
|
||||
|
||||
without response =? tx.response:
|
||||
raise newException(
|
||||
EthersError,
|
||||
"Transaction hash required. Possibly was a call instead of a send?"
|
||||
)
|
||||
|
||||
try:
|
||||
return await response.confirm(confirmations, timeout)
|
||||
except ProviderError as error:
|
||||
let convert = tx.convert
|
||||
raise convert(error)
|
||||
|
||||
proc confirm*(tx: Future[Confirmable],
|
||||
confirmations: int = EthersDefaultConfirmations,
|
||||
timeout: int = EthersReceiptTimeoutBlks):
|
||||
Future[TransactionReceipt] {.async: (raises: [CancelledError, EthersError]).} =
|
||||
## Convenience method that allows confirm to be chained to a contract
|
||||
## transaction, eg:
|
||||
## `await token.connect(signer0)
|
||||
## .mint(accounts[1], 100.u256)
|
||||
## .confirm(3)`
|
||||
try:
|
||||
return await (await tx).confirm(confirmations, timeout)
|
||||
except CancelledError as e:
|
||||
raise e
|
||||
except EthersError as e:
|
||||
raise e
|
||||
except CatchableError as e:
|
||||
raise newException(
|
||||
EthersError,
|
||||
"Error when trying to confirm the contract transaction: " & e.msg
|
||||
)
|
||||
|
||||
36
ethers/contracts/contract.nim
Normal file
36
ethers/contracts/contract.nim
Normal file
@ -0,0 +1,36 @@
|
||||
import ../basics
|
||||
import ../provider
|
||||
import ../signer
|
||||
|
||||
{.push raises:[].}
|
||||
|
||||
type Contract* = ref object of RootObj
|
||||
provider: Provider
|
||||
signer: ?Signer
|
||||
address: Address
|
||||
|
||||
func new*(ContractType: type Contract,
|
||||
address: Address,
|
||||
provider: Provider): ContractType =
|
||||
ContractType(provider: provider, address: address)
|
||||
|
||||
func new*(ContractType: type Contract,
|
||||
address: Address,
|
||||
signer: Signer): ContractType {.raises: [SignerError].} =
|
||||
ContractType(signer: some signer, provider: signer.provider, address: address)
|
||||
|
||||
func connect*[C: Contract](contract: C, provider: Provider): C =
|
||||
C.new(contract.address, provider)
|
||||
|
||||
func connect*[C: Contract](contract: C, signer: Signer): C {.raises: [SignerError].} =
|
||||
C.new(contract.address, signer)
|
||||
|
||||
func provider*(contract: Contract): Provider =
|
||||
contract.provider
|
||||
|
||||
func signer*(contract: Contract): ?Signer =
|
||||
contract.signer
|
||||
|
||||
func address*(contract: Contract): Address =
|
||||
contract.address
|
||||
|
||||
35
ethers/contracts/contractcall.nim
Normal file
35
ethers/contracts/contractcall.nim
Normal file
@ -0,0 +1,35 @@
|
||||
import ../basics
|
||||
import ./contract
|
||||
import ./overrides
|
||||
|
||||
type ContractCall*[Arguments: tuple] = object
|
||||
contract: Contract
|
||||
function: string
|
||||
arguments: Arguments
|
||||
overrides: TransactionOverrides
|
||||
|
||||
func init*[Arguments: tuple](
|
||||
_: type ContractCall,
|
||||
contract: Contract,
|
||||
function: string,
|
||||
arguments: Arguments,
|
||||
overrides: TransactionOverrides
|
||||
): ContractCall[arguments] =
|
||||
ContractCall[Arguments](
|
||||
contract: contract,
|
||||
function: function,
|
||||
arguments: arguments,
|
||||
overrides: overrides
|
||||
)
|
||||
|
||||
func contract*(call: ContractCall): Contract =
|
||||
call.contract
|
||||
|
||||
func function*(call: ContractCall): string =
|
||||
call.function
|
||||
|
||||
func arguments*(call: ContractCall): auto =
|
||||
call.arguments
|
||||
|
||||
func overrides*(call: ContractCall): TransactionOverrides =
|
||||
call.overrides
|
||||
@ -1,7 +1,7 @@
|
||||
import std/macros
|
||||
import pkg/contractabi
|
||||
import ./basics
|
||||
import ./provider
|
||||
import ../basics
|
||||
import ../provider
|
||||
|
||||
type
|
||||
Event* = object of RootObj
|
||||
78
ethers/contracts/filters.nim
Normal file
78
ethers/contracts/filters.nim
Normal file
@ -0,0 +1,78 @@
|
||||
import std/sequtils
|
||||
import pkg/contractabi
|
||||
import ../basics
|
||||
import ../provider
|
||||
import ./contract
|
||||
import ./events
|
||||
import ./fields
|
||||
|
||||
type EventHandler*[E: Event] = proc(event: ?!E) {.gcsafe, raises:[].}
|
||||
|
||||
proc subscribe*[E: Event](contract: Contract,
|
||||
_: type E,
|
||||
handler: EventHandler[E]):
|
||||
Future[Subscription] =
|
||||
|
||||
let topic = topic($E, E.fieldTypes).toArray
|
||||
let filter = EventFilter(address: contract.address, topics: @[topic])
|
||||
|
||||
proc logHandler(logResult: ?!Log) {.raises: [].} =
|
||||
without log =? logResult, error:
|
||||
handler(failure(E, error))
|
||||
return
|
||||
|
||||
if event =? E.decode(log.data, log.topics):
|
||||
handler(success(event))
|
||||
|
||||
contract.provider.subscribe(filter, logHandler)
|
||||
|
||||
proc queryFilter[E: Event](contract: Contract,
|
||||
_: type E,
|
||||
filter: EventFilter):
|
||||
Future[seq[E]] {.async.} =
|
||||
|
||||
var logs = await contract.provider.getLogs(filter)
|
||||
logs.keepItIf(not it.removed)
|
||||
|
||||
var events: seq[E] = @[]
|
||||
for log in logs:
|
||||
if event =? E.decode(log.data, log.topics):
|
||||
events.add event
|
||||
|
||||
return events
|
||||
|
||||
proc queryFilter*[E: Event](contract: Contract,
|
||||
_: type E):
|
||||
Future[seq[E]] =
|
||||
|
||||
let topic = topic($E, E.fieldTypes).toArray
|
||||
let filter = EventFilter(address: contract.address,
|
||||
topics: @[topic])
|
||||
|
||||
contract.queryFilter(E, filter)
|
||||
|
||||
proc queryFilter*[E: Event](contract: Contract,
|
||||
_: type E,
|
||||
blockHash: BlockHash):
|
||||
Future[seq[E]] =
|
||||
|
||||
let topic = topic($E, E.fieldTypes).toArray
|
||||
let filter = FilterByBlockHash(address: contract.address,
|
||||
topics: @[topic],
|
||||
blockHash: blockHash)
|
||||
|
||||
contract.queryFilter(E, filter)
|
||||
|
||||
proc queryFilter*[E: Event](contract: Contract,
|
||||
_: type E,
|
||||
fromBlock: BlockTag,
|
||||
toBlock: BlockTag):
|
||||
Future[seq[E]] =
|
||||
|
||||
let topic = topic($E, E.fieldTypes).toArray
|
||||
let filter = Filter(address: contract.address,
|
||||
topics: @[topic],
|
||||
fromBlock: fromBlock,
|
||||
toBlock: toBlock)
|
||||
|
||||
contract.queryFilter(E, filter)
|
||||
86
ethers/contracts/function.nim
Normal file
86
ethers/contracts/function.nim
Normal file
@ -0,0 +1,86 @@
|
||||
import std/macros
|
||||
import ../errors/conversion
|
||||
import ./syntax
|
||||
import ./transactions
|
||||
|
||||
func getErrorTypes(procedure: NimNode): NimNode =
|
||||
let pragmas = procedure[4]
|
||||
var tupl = newNimNode(nnkTupleConstr)
|
||||
for pragma in pragmas:
|
||||
if pragma.kind == nnkExprColonExpr:
|
||||
if pragma[0].eqIdent "errors":
|
||||
pragma[1].expectKind(nnkBracket)
|
||||
for error in pragma[1]:
|
||||
tupl.add error
|
||||
if tupl.len == 0:
|
||||
quote do: tuple[]
|
||||
else:
|
||||
tupl
|
||||
|
||||
func addContractCall(procedure: var NimNode) =
|
||||
let contractCall = getContractCall(procedure)
|
||||
let returnType = procedure.params[0]
|
||||
let isGetter = procedure.isGetter
|
||||
|
||||
let errors = getErrorTypes(procedure)
|
||||
|
||||
func call: NimNode =
|
||||
if returnType.kind == nnkEmpty:
|
||||
quote:
|
||||
await callTransaction(`contractCall`)
|
||||
elif returnType.isMultipleReturn or isGetter:
|
||||
quote:
|
||||
return await callTransaction(`contractCall`, `returnType`)
|
||||
else:
|
||||
quote:
|
||||
# solidity functions return a tuple, so wrap return type in a tuple
|
||||
let tupl = await callTransaction(`contractCall`, (`returnType`,))
|
||||
return tupl[0]
|
||||
|
||||
func send: NimNode =
|
||||
if returnType.kind == nnkEmpty:
|
||||
quote:
|
||||
discard await sendTransaction(`contractCall`)
|
||||
else:
|
||||
quote:
|
||||
when typeof(result) isnot Confirmable:
|
||||
{.error:
|
||||
"unexpected return type, " &
|
||||
"missing {.view.}, {.pure.} or {.getter.} ?"
|
||||
.}
|
||||
let response = await sendTransaction(`contractCall`)
|
||||
let convert = customErrorConversion(`errors`)
|
||||
Confirmable(response: response, convert: convert)
|
||||
|
||||
procedure.body =
|
||||
if procedure.isConstant:
|
||||
call()
|
||||
else:
|
||||
send()
|
||||
|
||||
func addErrorHandling(procedure: var NimNode) =
|
||||
let body = procedure[6]
|
||||
let errors = getErrorTypes(procedure)
|
||||
procedure.body = quote do:
|
||||
try:
|
||||
`body`
|
||||
except ProviderError as error:
|
||||
if data =? error.data:
|
||||
let convert = customErrorConversion(`errors`)
|
||||
raise convert(error)
|
||||
else:
|
||||
raise error
|
||||
|
||||
func addFuture(procedure: var NimNode) =
|
||||
let returntype = procedure[3][0]
|
||||
if returntype.kind != nnkEmpty:
|
||||
procedure[3][0] = quote: Future[`returntype`]
|
||||
|
||||
func createContractFunction*(procedure: NimNode): NimNode =
|
||||
result = copyNimTree(procedure)
|
||||
result.addOverridesParameter()
|
||||
result.addContractCall()
|
||||
result.addErrorHandling()
|
||||
result.addFuture()
|
||||
result.addAsyncPragma()
|
||||
|
||||
13
ethers/contracts/overrides.nim
Normal file
13
ethers/contracts/overrides.nim
Normal file
@ -0,0 +1,13 @@
|
||||
import ../basics
|
||||
import ../blocktag
|
||||
|
||||
type
|
||||
TransactionOverrides* = ref object of RootObj
|
||||
nonce*: ?UInt256
|
||||
chainId*: ?UInt256
|
||||
gasPrice*: ?UInt256
|
||||
maxFee*: ?UInt256
|
||||
maxPriorityFee*: ?UInt256
|
||||
gasLimit*: ?UInt256
|
||||
CallOverrides* = ref object of TransactionOverrides
|
||||
blockTag*: ?BlockTag
|
||||
76
ethers/contracts/syntax.nim
Normal file
76
ethers/contracts/syntax.nim
Normal file
@ -0,0 +1,76 @@
|
||||
import std/macros
|
||||
import ./contractcall
|
||||
|
||||
template view* {.pragma.}
|
||||
template pure* {.pragma.}
|
||||
template getter* {.pragma.}
|
||||
template errors*(types) {.pragma.}
|
||||
|
||||
func isGetter*(procedure: NimNode): bool =
|
||||
let pragmas = procedure[4]
|
||||
for pragma in pragmas:
|
||||
if pragma.eqIdent "getter":
|
||||
return true
|
||||
false
|
||||
|
||||
func isConstant*(procedure: NimNode): bool =
|
||||
let pragmas = procedure[4]
|
||||
for pragma in pragmas:
|
||||
if pragma.eqIdent "view":
|
||||
return true
|
||||
elif pragma.eqIdent "pure":
|
||||
return true
|
||||
elif pragma.eqIdent "getter":
|
||||
return true
|
||||
false
|
||||
|
||||
func isMultipleReturn*(returnType: NimNode): bool =
|
||||
(returnType.kind == nnkPar and returnType.len > 1) or
|
||||
(returnType.kind == nnkTupleConstr) or
|
||||
(returnType.kind == nnkTupleTy)
|
||||
|
||||
func getContract(procedure: NimNode): NimNode =
|
||||
let firstArgument = procedure.params[1][0]
|
||||
quote do:
|
||||
Contract(`firstArgument`)
|
||||
|
||||
func getFunctionName(procedure: NimNode): string =
|
||||
$basename(procedure[0])
|
||||
|
||||
func getArgumentTuple(procedure: NimNode): NimNode =
|
||||
let parameters = procedure.params
|
||||
var arguments = newNimNode(nnkTupleConstr, parameters)
|
||||
for parameter in parameters[2..^2]:
|
||||
for name in parameter[0..^3]:
|
||||
arguments.add name
|
||||
return arguments
|
||||
|
||||
func getOverrides(procedure: NimNode): NimNode =
|
||||
procedure.params.last[^3]
|
||||
|
||||
func getContractCall*(procedure: NimNode): NimNode =
|
||||
let contract = getContract(procedure)
|
||||
let function = getFunctionName(procedure)
|
||||
let arguments = getArgumentTuple(procedure)
|
||||
let overrides = getOverrides(procedure)
|
||||
quote do:
|
||||
ContractCall.init(`contract`, `function`, `arguments`, `overrides`)
|
||||
|
||||
func addOverridesParameter*(procedure: var NimNode) =
|
||||
let overrides = genSym(nskParam, "overrides")
|
||||
procedure.params.add(
|
||||
newIdentDefs(
|
||||
overrides,
|
||||
newEmptyNode(),
|
||||
quote do: TransactionOverrides()
|
||||
)
|
||||
)
|
||||
|
||||
func addAsyncPragma*(procedure: var NimNode) =
|
||||
procedure.addPragma nnkExprColonExpr.newTree(
|
||||
quote do: async,
|
||||
quote do: (raises: [CancelledError, ProviderError, EthersError])
|
||||
)
|
||||
|
||||
func addUsedPragma*(procedure: var NimNode) =
|
||||
procedure.addPragma(quote do: used)
|
||||
71
ethers/contracts/transactions.nim
Normal file
71
ethers/contracts/transactions.nim
Normal file
@ -0,0 +1,71 @@
|
||||
import pkg/contractabi
|
||||
import pkg/chronicles
|
||||
import ../basics
|
||||
import ../provider
|
||||
import ../signer
|
||||
import ../transaction
|
||||
import ./contract
|
||||
import ./contractcall
|
||||
import ./overrides
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
logScope:
|
||||
topics = "ethers contract"
|
||||
|
||||
proc createTransaction*(call: ContractCall): Transaction =
|
||||
let selector = selector(call.function, typeof call.arguments).toArray
|
||||
let data = @selector & AbiEncoder.encode(call.arguments)
|
||||
Transaction(
|
||||
to: call.contract.address,
|
||||
data: data,
|
||||
nonce: call.overrides.nonce,
|
||||
chainId: call.overrides.chainId,
|
||||
gasPrice: call.overrides.gasPrice,
|
||||
maxFee: call.overrides.maxFee,
|
||||
maxPriorityFee: call.overrides.maxPriorityFee,
|
||||
gasLimit: call.overrides.gasLimit,
|
||||
)
|
||||
|
||||
proc decodeResponse(T: type, bytes: seq[byte]): T {.raises: [ContractError].} =
|
||||
without decoded =? AbiDecoder.decode(bytes, T):
|
||||
raise newException(ContractError, "unable to decode return value as " & $T)
|
||||
return decoded
|
||||
|
||||
proc call(
|
||||
provider: Provider, transaction: Transaction, overrides: TransactionOverrides
|
||||
): Future[seq[byte]] {.async: (raises: [ProviderError, CancelledError]).} =
|
||||
if overrides of CallOverrides and blockTag =? CallOverrides(overrides).blockTag:
|
||||
await provider.call(transaction, blockTag)
|
||||
else:
|
||||
await provider.call(transaction)
|
||||
|
||||
proc callTransaction*(call: ContractCall) {.async: (raises: [ProviderError, SignerError, CancelledError]).} =
|
||||
var transaction = createTransaction(call)
|
||||
|
||||
if signer =? call.contract.signer and transaction.sender.isNone:
|
||||
transaction.sender = some(await signer.getAddress())
|
||||
|
||||
discard await call.contract.provider.call(transaction, call.overrides)
|
||||
|
||||
proc callTransaction*(call: ContractCall, ReturnType: type): Future[ReturnType] {.async: (raises: [ProviderError, SignerError, ContractError, CancelledError]).} =
|
||||
var transaction = createTransaction(call)
|
||||
|
||||
if signer =? call.contract.signer and transaction.sender.isNone:
|
||||
transaction.sender = some(await signer.getAddress())
|
||||
|
||||
let response = await call.contract.provider.call(transaction, call.overrides)
|
||||
return decodeResponse(ReturnType, response)
|
||||
|
||||
proc sendTransaction*(call: ContractCall): Future[?TransactionResponse] {.async: (raises: [SignerError, ProviderError, CancelledError]).} =
|
||||
if signer =? call.contract.signer:
|
||||
withLock(signer):
|
||||
let transaction = createTransaction(call)
|
||||
let populated = await signer.populateTransaction(transaction)
|
||||
trace "sending contract transaction", function = call.function, params = $call.arguments
|
||||
let txResp = await signer.sendTransaction(populated)
|
||||
return txResp.some
|
||||
else:
|
||||
await callTransaction(call)
|
||||
return TransactionResponse.none
|
||||
|
||||
@ -16,5 +16,3 @@ proc toErr*[E1: ref CatchableError, E2: EthersError](
|
||||
msg: string = e1.msg): ref E2 =
|
||||
|
||||
return newException(E2, msg, e1)
|
||||
|
||||
template errors*(types) {.pragma.}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user