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
|
|
|
|
|
feat: Allow contract transactions to be waited on
Allow waiting for a specified number of confirmations for contract transactions.
This change only requires an optional TransactionResponse return type to be added to the contract function. This allows the transaction hash to be passed to `.wait`.
For example, previously the `mint` method looked like this without a return value:
```
method mint(token: TestToken, holder: Address, amount: UInt256) {.base, contract.}
```
it still works without a return value, but if we want to wait for a 3 confirmations, we can now define it like this:
```
method mint(token: TestToken, holder: Address, amount: UInt256): ?TransactionResponse {.base, contract.}
```
and use like this:
```
let receipt = await token.connect(signer0)
.mint(accounts[1], 100.u256)
.wait(3) # wait for 3 confirmations
```
2022-05-17 04:57:18 +00:00
|
|
|
func isTxResponse(returntype: NimNode): bool =
|
|
|
|
return returntype.kind == nnkPrefix and
|
|
|
|
returntype[0].kind == nnkIdent and
|
|
|
|
returntype[0].strVal == "?" and
|
|
|
|
returntype[1].kind == nnkIdent and
|
|
|
|
returntype[1].strVal == $TransactionResponse
|
|
|
|
|
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:
|
feat: Allow contract transactions to be waited on
Allow waiting for a specified number of confirmations for contract transactions.
This change only requires an optional TransactionResponse return type to be added to the contract function. This allows the transaction hash to be passed to `.wait`.
For example, previously the `mint` method looked like this without a return value:
```
method mint(token: TestToken, holder: Address, amount: UInt256) {.base, contract.}
```
it still works without a return value, but if we want to wait for a 3 confirmations, we can now define it like this:
```
method mint(token: TestToken, holder: Address, amount: UInt256): ?TransactionResponse {.base, contract.}
```
and use like this:
```
let receipt = await token.connect(signer0)
.mint(accounts[1], 100.u256)
.wait(3) # wait for 3 confirmations
```
2022-05-17 04:57:18 +00:00
|
|
|
# When ?TransactionResponse is specified as the return type of contract
|
|
|
|
# method, it allows for contract transactions to be awaited on
|
|
|
|
# confirmations
|
|
|
|
if returntype.isTxResponse:
|
|
|
|
quote:
|
|
|
|
return await send(`contract`, `function`, `parameters`)
|
|
|
|
else:
|
|
|
|
quote:
|
|
|
|
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]
|
feat: Allow contract transactions to be waited on
Allow waiting for a specified number of confirmations for contract transactions.
This change only requires an optional TransactionResponse return type to be added to the contract function. This allows the transaction hash to be passed to `.wait`.
For example, previously the `mint` method looked like this without a return value:
```
method mint(token: TestToken, holder: Address, amount: UInt256) {.base, contract.}
```
it still works without a return value, but if we want to wait for a 3 confirmations, we can now define it like this:
```
method mint(token: TestToken, holder: Address, amount: UInt256): ?TransactionResponse {.base, contract.}
```
and use like this:
```
let receipt = await token.connect(signer0)
.mint(accounts[1], 100.u256)
.wait(3) # wait for 3 confirmations
```
2022-05-17 04:57:18 +00:00
|
|
|
|
|
|
|
if returntype.kind != nnkEmpty:
|
|
|
|
# Do not throw exception for methods that have a TransactionResponse
|
|
|
|
# return type as that is needed for .wait
|
|
|
|
if returntype.isTxResponse:
|
|
|
|
return
|
|
|
|
|
|
|
|
if not procedure.isConstant:
|
|
|
|
const message =
|
|
|
|
"only contract functions with {.view.} or {.pure.} " &
|
|
|
|
"can have a return type"
|
|
|
|
error(message, returntype)
|
2022-01-25 16:17:43 +00:00
|
|
|
|
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)
|