2022-01-20 11:56:18 +00:00
|
|
|
import std/macros
|
|
|
|
import pkg/chronos
|
|
|
|
import pkg/contractabi
|
|
|
|
import ./basics
|
|
|
|
import ./provider
|
2022-01-25 16:17:43 +00:00
|
|
|
import ./signer
|
2022-02-01 14:49:36 +00:00
|
|
|
import ./events
|
2022-02-02 15:56:37 +00:00
|
|
|
import ./fields
|
2022-01-20 11:56:18 +00:00
|
|
|
|
|
|
|
export basics
|
|
|
|
export provider
|
2022-02-01 14:49:36 +00:00
|
|
|
export events
|
2022-01-20 11:56:18 +00:00
|
|
|
|
|
|
|
type
|
|
|
|
Contract* = ref object of RootObj
|
|
|
|
provider: Provider
|
2022-01-25 16:17:43 +00:00
|
|
|
signer: ?Signer
|
2022-01-20 11:56:18 +00:00
|
|
|
address: Address
|
2022-01-25 09:25:09 +00:00
|
|
|
ContractError* = object of EthersError
|
2022-02-02 15:56:37 +00:00
|
|
|
EventHandler*[E: Event] = proc(event: E) {.gcsafe, upraises:[].}
|
2022-01-20 11:56:18 +00:00
|
|
|
|
2022-01-26 09:31:54 +00:00
|
|
|
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 =
|
|
|
|
ContractType(signer: some signer, provider: signer.provider, address: address)
|
|
|
|
|
2022-01-26 11:23:30 +00:00
|
|
|
func connect*[T: Contract](contract: T, provider: Provider | Signer): T =
|
|
|
|
T.new(contract.address, provider)
|
|
|
|
|
2022-01-25 16:17:43 +00:00
|
|
|
func provider*(contract: Contract): Provider =
|
|
|
|
contract.provider
|
|
|
|
|
|
|
|
func signer*(contract: Contract): ?Signer =
|
|
|
|
contract.signer
|
|
|
|
|
|
|
|
func address*(contract: Contract): Address =
|
|
|
|
contract.address
|
|
|
|
|
2022-01-20 11:56:18 +00:00
|
|
|
template raiseContractError(message: string) =
|
|
|
|
raise newException(ContractError, message)
|
|
|
|
|
2022-01-26 09:31:54 +00:00
|
|
|
proc createTransaction(contract: Contract,
|
|
|
|
function: string,
|
|
|
|
parameters: tuple): Transaction =
|
2022-01-20 11:56:18 +00:00
|
|
|
let selector = selector(function, typeof parameters).toArray
|
2022-01-26 09:31:54 +00:00
|
|
|
let data = @selector & AbiEncoder.encode(parameters)
|
|
|
|
Transaction(to: contract.address, data: data)
|
2022-01-20 11:56:18 +00:00
|
|
|
|
|
|
|
proc decodeResponse(T: type, bytes: seq[byte]): T =
|
|
|
|
without decoded =? AbiDecoder.decode(bytes, T):
|
|
|
|
raiseContractError "unable to decode return value as " & $T
|
|
|
|
return decoded
|
|
|
|
|
2022-04-10 20:21:59 +00:00
|
|
|
proc call(contract: Contract,
|
|
|
|
function: string,
|
|
|
|
parameters: tuple,
|
|
|
|
blockTag = BlockTag.latest) {.async.} =
|
2022-01-26 09:31:54 +00:00
|
|
|
let transaction = createTransaction(contract, function, parameters)
|
2022-04-10 20:21:59 +00:00
|
|
|
discard await contract.provider.call(transaction, blockTag)
|
2022-01-25 16:17:43 +00:00
|
|
|
|
2022-01-26 09:31:54 +00:00
|
|
|
proc call(contract: Contract,
|
2022-01-25 16:17:43 +00:00
|
|
|
function: string,
|
2022-01-26 09:31:54 +00:00
|
|
|
parameters: tuple,
|
2022-04-10 20:21:59 +00:00
|
|
|
ReturnType: type,
|
|
|
|
blockTag = BlockTag.latest): Future[ReturnType] {.async.} =
|
2022-01-26 09:31:54 +00:00
|
|
|
let transaction = createTransaction(contract, function, parameters)
|
2022-04-10 20:21:59 +00:00
|
|
|
let response = await contract.provider.call(transaction, blockTag)
|
2022-01-26 09:31:54 +00:00
|
|
|
return decodeResponse(ReturnType, response)
|
2022-01-25 16:17:43 +00:00
|
|
|
|
2022-05-17 02:34:22 +00:00
|
|
|
proc send(contract: Contract, function: string, parameters: tuple):
|
|
|
|
Future[?TransactionResponse] {.async.} =
|
|
|
|
|
2022-01-26 09:31:54 +00:00
|
|
|
if signer =? contract.signer:
|
|
|
|
let transaction = createTransaction(contract, function, parameters)
|
|
|
|
let populated = await signer.populateTransaction(transaction)
|
2022-05-17 02:34:22 +00:00
|
|
|
let txResp = await signer.sendTransaction(populated)
|
|
|
|
return txResp.some
|
2022-01-26 09:31:54 +00:00
|
|
|
else:
|
|
|
|
await call(contract, function, parameters)
|
2022-05-17 02:34:22 +00:00
|
|
|
return TransactionResponse.none
|
2022-01-20 11:56:18 +00:00
|
|
|
|
2022-01-26 09:31:54 +00:00
|
|
|
func getParameterTuple(procedure: NimNode): NimNode =
|
2022-01-20 11:56:18 +00:00
|
|
|
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
|
|
|
|
|
2022-01-25 16:17:43 +00:00
|
|
|
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
|
|
|
|
false
|
|
|
|
|
2022-01-20 11:56:18 +00:00
|
|
|
func addContractCall(procedure: var NimNode) =
|
2022-01-26 09:31:54 +00:00
|
|
|
let contract = procedure[3][1][0]
|
|
|
|
let function = $basename(procedure[0])
|
|
|
|
let parameters = getParameterTuple(procedure)
|
|
|
|
let returntype = procedure[3][0]
|
|
|
|
procedure[6] =
|
|
|
|
if procedure.isConstant:
|
|
|
|
if returntype.kind == nnkEmpty:
|
|
|
|
quote:
|
|
|
|
await call(`contract`, `function`, `parameters`)
|
2022-01-25 16:17:43 +00:00
|
|
|
else:
|
2022-01-26 09:31:54 +00:00
|
|
|
quote:
|
|
|
|
return await call(`contract`, `function`, `parameters`, `returntype`)
|
|
|
|
else:
|
|
|
|
quote:
|
2022-05-17 02:34:22 +00:00
|
|
|
# TODO: need to be able to use wait here
|
|
|
|
discard await send(`contract`, `function`, `parameters`)
|
2022-01-20 11:56:18 +00:00
|
|
|
|
|
|
|
func addFuture(procedure: var NimNode) =
|
|
|
|
let returntype = procedure[3][0]
|
2022-01-25 16:17:43 +00:00
|
|
|
if returntype.kind != nnkEmpty:
|
2022-01-26 09:31:54 +00:00
|
|
|
procedure[3][0] = quote: Future[`returntype`]
|
2022-01-20 11:56:18 +00:00
|
|
|
|
|
|
|
func addAsyncPragma(procedure: var NimNode) =
|
|
|
|
let pragmas = procedure[4]
|
|
|
|
if pragmas.kind == nnkEmpty:
|
|
|
|
procedure[4] = newNimNode(nnkPragma)
|
|
|
|
procedure[4].add ident("async")
|
|
|
|
|
2022-01-25 16:17:43 +00:00
|
|
|
func checkReturnType(procedure: NimNode) =
|
2022-01-26 09:31:54 +00:00
|
|
|
let returntype = procedure[3][0]
|
2022-01-25 16:17:43 +00:00
|
|
|
if returntype.kind != nnkEmpty and not procedure.isConstant:
|
|
|
|
const message =
|
2022-01-26 16:03:43 +00:00
|
|
|
"only contract functions with {.view.} or {.pure.} " &
|
2022-01-25 16:17:43 +00:00
|
|
|
"can have a return type"
|
|
|
|
error(message, returntype)
|
|
|
|
|
2022-01-20 11:56:18 +00:00
|
|
|
macro contract*(procedure: untyped{nkProcDef|nkMethodDef}): untyped =
|
2022-01-26 09:31:54 +00:00
|
|
|
|
2022-01-20 11:56:18 +00:00
|
|
|
let parameters = procedure[3]
|
|
|
|
let body = procedure[6]
|
2022-01-26 09:31:54 +00:00
|
|
|
|
|
|
|
parameters.expectMinLen(2) # at least return type and contract instance
|
2022-01-20 11:56:18 +00:00
|
|
|
body.expectKind(nnkEmpty)
|
2022-01-25 16:17:43 +00:00
|
|
|
procedure.checkReturnType()
|
2022-01-26 09:31:54 +00:00
|
|
|
|
2022-01-20 11:56:18 +00:00
|
|
|
var contractcall = copyNimTree(procedure)
|
|
|
|
contractcall.addContractCall()
|
|
|
|
contractcall.addFuture()
|
|
|
|
contractcall.addAsyncPragma()
|
|
|
|
contractcall
|
2022-01-26 09:31:54 +00:00
|
|
|
|
|
|
|
template view* {.pragma.}
|
|
|
|
template pure* {.pragma.}
|
2022-02-02 15:56:37 +00:00
|
|
|
|
|
|
|
proc subscribe*[E: Event](contract: Contract,
|
|
|
|
_: type E,
|
|
|
|
handler: EventHandler[E]):
|
|
|
|
Future[Subscription] =
|
|
|
|
|
|
|
|
let topic = topic($E, E.fieldTypes).toArray
|
|
|
|
let filter = Filter(address: contract.address, topics: @[topic])
|
|
|
|
|
|
|
|
proc logHandler(log: Log) {.upraises: [].} =
|
|
|
|
if event =? E.decode(log.data, log.topics):
|
|
|
|
handler(event)
|
|
|
|
|
|
|
|
contract.provider.subscribe(filter, logHandler)
|