This commit is contained in:
Mark Spanbroek 2022-01-26 10:31:54 +01:00
parent e4224a1241
commit fec6bdc581
1 changed files with 55 additions and 71 deletions

View File

@ -15,6 +15,16 @@ type
address: Address address: Address
ContractError* = object of EthersError ContractError* = object of EthersError
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)
func provider*(contract: Contract): Provider = func provider*(contract: Contract): Provider =
contract.provider contract.provider
@ -27,48 +37,39 @@ func address*(contract: Contract): Address =
template raiseContractError(message: string) = template raiseContractError(message: string) =
raise newException(ContractError, message) raise newException(ContractError, message)
proc createTxData(function: string, parameters: tuple): seq[byte] = proc createTransaction(contract: Contract,
let selector = selector(function, typeof parameters).toArray
return @selector & AbiEncoder.encode(parameters)
proc createTx(contract: Contract,
function: string, function: string,
parameters: tuple): Transaction = parameters: tuple): Transaction =
Transaction(to: contract.address, data: createTxData(function, parameters)) let selector = selector(function, typeof parameters).toArray
let data = @selector & AbiEncoder.encode(parameters)
Transaction(to: contract.address, data: data)
proc decodeResponse(T: type, bytes: seq[byte]): T = proc decodeResponse(T: type, bytes: seq[byte]): T =
without decoded =? AbiDecoder.decode(bytes, T): without decoded =? AbiDecoder.decode(bytes, T):
raiseContractError "unable to decode return value as " & $T raiseContractError "unable to decode return value as " & $T
return decoded return decoded
proc call[ContractType: Contract, ReturnType]( proc call(contract: Contract, function: string, parameters: tuple) {.async.} =
contract: ContractType, let transaction = createTransaction(contract, function, parameters)
discard await contract.provider.call(transaction)
proc call(contract: Contract,
function: string, function: string,
parameters: tuple): Future[ReturnType] {.async.} = parameters: tuple,
let transaction = createTx(contract, function, parameters) ReturnType: type): Future[ReturnType] {.async.} =
let transaction = createTransaction(contract, function, parameters)
let response = await contract.provider.call(transaction) let response = await contract.provider.call(transaction)
return decodeResponse(ReturnType, response) return decodeResponse(ReturnType, response)
proc callNoResult[ContractType: Contract]( proc send(contract: Contract, function: string, parameters: tuple) {.async.} =
contract: ContractType, if signer =? contract.signer:
function: string, let transaction = createTransaction(contract, function, parameters)
parameters: tuple) {.async.} =
let transaction = createTx(contract, function, parameters)
discard await contract.provider.call(transaction)
proc send[ContractType: Contract](
contract: ContractType,
function: string,
parameters: tuple) {.async.} =
without signer =? contract.signer:
raiseContractError "trying to send transaction without a signer"
let transaction = createTx(contract, function, parameters)
let populated = await signer.populateTransaction(transaction) let populated = await signer.populateTransaction(transaction)
await signer.sendTransaction(populated) await signer.sendTransaction(populated)
else:
await call(contract, function, parameters)
func getParameterTuple(procedure: var NimNode): NimNode = func getParameterTuple(procedure: NimNode): NimNode =
let parameters = procedure[3] let parameters = procedure[3]
var tupl = newNimNode(nnkTupleConstr, parameters) var tupl = newNimNode(nnkTupleConstr, parameters)
for parameter in parameters[2..^1]: for parameter in parameters[2..^1]:
@ -88,35 +89,26 @@ func isConstant(procedure: NimNode): bool =
false false
func addContractCall(procedure: var NimNode) = func addContractCall(procedure: var NimNode) =
let name = procedure[0] let contract = procedure[3][1][0]
let function = if name.kind == nnkPostfix: $name[1] else: $name let function = $basename(procedure[0])
let parameters = procedure[3] let parameters = getParameterTuple(procedure)
let contract = parameters[1][0] let returntype = procedure[3][0]
let contracttype = parameters[1][1] procedure[6] =
let returntype = parameters[0]
let tupl = getParameterTuple(procedure)
if procedure.isConstant: if procedure.isConstant:
if returntype.kind == nnkEmpty: if returntype.kind == nnkEmpty:
procedure[6] = quote do: quote:
await callNoResult[`contracttype`]( await call(`contract`, `function`, `parameters`)
`contract`, `function`, `tupl`
)
else: else:
procedure[6] = quote do: quote:
return await call[`contracttype`,`returntype`]( return await call(`contract`, `function`, `parameters`, `returntype`)
`contract`, `function`, `tupl`
)
else: else:
procedure[6] = quote do: quote:
if `contract`.signer.isSome: await send(`contract`, `function`, `parameters`)
await send[`contracttype`](`contract`, `function`, `tupl`)
else:
await callNoResult[`contracttype`](`contract`, `function`, `tupl`)
func addFuture(procedure: var NimNode) = func addFuture(procedure: var NimNode) =
let returntype = procedure[3][0] let returntype = procedure[3][0]
if returntype.kind != nnkEmpty: if returntype.kind != nnkEmpty:
procedure[3][0] = quote do: Future[`returntype`] procedure[3][0] = quote: Future[`returntype`]
func addAsyncPragma(procedure: var NimNode) = func addAsyncPragma(procedure: var NimNode) =
let pragmas = procedure[4] let pragmas = procedure[4]
@ -124,23 +116,8 @@ func addAsyncPragma(procedure: var NimNode) =
procedure[4] = newNimNode(nnkPragma) procedure[4] = newNimNode(nnkPragma)
procedure[4].add ident("async") procedure[4].add ident("async")
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)
template view* {.pragma.}
template pure* {.pragma.}
template constant* {.pragma.}
func checkReturnType(procedure: NimNode) = func checkReturnType(procedure: NimNode) =
let parameters = procedure[3] let returntype = procedure[3][0]
let returntype = parameters[0]
if returntype.kind != nnkEmpty and not procedure.isConstant: if returntype.kind != nnkEmpty and not procedure.isConstant:
const message = const message =
"only contract functions with {.constant.}, {.pure.} or {.view.} " & "only contract functions with {.constant.}, {.pure.} or {.view.} " &
@ -148,13 +125,20 @@ func checkReturnType(procedure: NimNode) =
error(message, returntype) error(message, returntype)
macro contract*(procedure: untyped{nkProcDef|nkMethodDef}): untyped = macro contract*(procedure: untyped{nkProcDef|nkMethodDef}): untyped =
let parameters = procedure[3] let parameters = procedure[3]
let body = procedure[6] let body = procedure[6]
parameters.expectMinLen(2)
parameters.expectMinLen(2) # at least return type and contract instance
body.expectKind(nnkEmpty) body.expectKind(nnkEmpty)
procedure.checkReturnType() procedure.checkReturnType()
var contractcall = copyNimTree(procedure) var contractcall = copyNimTree(procedure)
contractcall.addContractCall() contractcall.addContractCall()
contractcall.addFuture() contractcall.addFuture()
contractcall.addAsyncPragma() contractcall.addAsyncPragma()
contractcall contractcall
template view* {.pragma.}
template pure* {.pragma.}
template constant* {.pragma.}