Allow calls to non-constant functions
This commit is contained in:
parent
82116d3b14
commit
e4224a1241
|
@ -3,6 +3,7 @@ import pkg/chronos
|
||||||
import pkg/contractabi
|
import pkg/contractabi
|
||||||
import ./basics
|
import ./basics
|
||||||
import ./provider
|
import ./provider
|
||||||
|
import ./signer
|
||||||
|
|
||||||
export basics
|
export basics
|
||||||
export provider
|
export provider
|
||||||
|
@ -10,9 +11,19 @@ export provider
|
||||||
type
|
type
|
||||||
Contract* = ref object of RootObj
|
Contract* = ref object of RootObj
|
||||||
provider: Provider
|
provider: Provider
|
||||||
|
signer: ?Signer
|
||||||
address: Address
|
address: Address
|
||||||
ContractError* = object of EthersError
|
ContractError* = object of EthersError
|
||||||
|
|
||||||
|
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) =
|
template raiseContractError(message: string) =
|
||||||
raise newException(ContractError, message)
|
raise newException(ContractError, message)
|
||||||
|
|
||||||
|
@ -30,13 +41,32 @@ proc decodeResponse(T: type, bytes: seq[byte]): 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, ResultType](
|
proc call[ContractType: Contract, ReturnType](
|
||||||
contract: ContractType,
|
contract: ContractType,
|
||||||
function: string,
|
function: string,
|
||||||
parameters: tuple):Future[ResultType] {.async.} =
|
parameters: tuple): Future[ReturnType] {.async.} =
|
||||||
let transaction = createTx(contract, function, parameters)
|
let transaction = createTx(contract, function, parameters)
|
||||||
let response = await contract.provider.call(transaction)
|
let response = await contract.provider.call(transaction)
|
||||||
return decodeResponse(ResultType, response)
|
return decodeResponse(ReturnType, response)
|
||||||
|
|
||||||
|
proc callNoResult[ContractType: Contract](
|
||||||
|
contract: ContractType,
|
||||||
|
function: string,
|
||||||
|
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)
|
||||||
|
await signer.sendTransaction(populated)
|
||||||
|
|
||||||
func getParameterTuple(procedure: var NimNode): NimNode =
|
func getParameterTuple(procedure: var NimNode): NimNode =
|
||||||
let parameters = procedure[3]
|
let parameters = procedure[3]
|
||||||
|
@ -46,22 +76,46 @@ func getParameterTuple(procedure: var NimNode): NimNode =
|
||||||
tupl.add name
|
tupl.add name
|
||||||
return tupl
|
return tupl
|
||||||
|
|
||||||
|
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 "constant":
|
||||||
|
return true
|
||||||
|
false
|
||||||
|
|
||||||
func addContractCall(procedure: var NimNode) =
|
func addContractCall(procedure: var NimNode) =
|
||||||
let name = procedure[0]
|
let name = procedure[0]
|
||||||
let function = if name.kind == nnkPostfix: $name[1] else: $name
|
let function = if name.kind == nnkPostfix: $name[1] else: $name
|
||||||
let parameters = procedure[3]
|
let parameters = procedure[3]
|
||||||
let contract = parameters[1][0]
|
let contract = parameters[1][0]
|
||||||
let contracttype = parameters[1][1]
|
let contracttype = parameters[1][1]
|
||||||
let resulttype = parameters[0]
|
let returntype = parameters[0]
|
||||||
let tupl = getParameterTuple(procedure)
|
let tupl = getParameterTuple(procedure)
|
||||||
|
if procedure.isConstant:
|
||||||
|
if returntype.kind == nnkEmpty:
|
||||||
procedure[6] = quote do:
|
procedure[6] = quote do:
|
||||||
return await call[`contracttype`,`resulttype`](`contract`, `function`, `tupl`)
|
await callNoResult[`contracttype`](
|
||||||
|
`contract`, `function`, `tupl`
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
procedure[6] = quote do:
|
||||||
|
return await call[`contracttype`,`returntype`](
|
||||||
|
`contract`, `function`, `tupl`
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
procedure[6] = quote do:
|
||||||
|
if `contract`.signer.isSome:
|
||||||
|
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[void]
|
|
||||||
else:
|
|
||||||
procedure[3][0] = quote do: Future[`returntype`]
|
procedure[3][0] = quote do: Future[`returntype`]
|
||||||
|
|
||||||
func addAsyncPragma(procedure: var NimNode) =
|
func addAsyncPragma(procedure: var NimNode) =
|
||||||
|
@ -75,11 +129,30 @@ func new*(ContractType: type Contract,
|
||||||
provider: Provider): ContractType =
|
provider: Provider): ContractType =
|
||||||
ContractType(provider: provider, address: address)
|
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) =
|
||||||
|
let parameters = procedure[3]
|
||||||
|
let returntype = parameters[0]
|
||||||
|
if returntype.kind != nnkEmpty and not procedure.isConstant:
|
||||||
|
const message =
|
||||||
|
"only contract functions with {.constant.}, {.pure.} or {.view.} " &
|
||||||
|
"can have a return type"
|
||||||
|
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)
|
||||||
body.expectKind(nnkEmpty)
|
body.expectKind(nnkEmpty)
|
||||||
|
procedure.checkReturnType()
|
||||||
var contractcall = copyNimTree(procedure)
|
var contractcall = copyNimTree(procedure)
|
||||||
contractcall.addContractCall()
|
contractcall.addContractCall()
|
||||||
contractcall.addFuture()
|
contractcall.addFuture()
|
||||||
|
|
|
@ -101,3 +101,8 @@ method getAddress*(signer: JsonRpcSigner): Future[Address] {.async.} =
|
||||||
return accounts[0]
|
return accounts[0]
|
||||||
|
|
||||||
raiseProviderError "no address found"
|
raiseProviderError "no address found"
|
||||||
|
|
||||||
|
method sendTransaction*(signer: JsonRpcSigner,
|
||||||
|
transaction: Transaction) {.async.} =
|
||||||
|
let client = await signer.provider.client
|
||||||
|
discard await client.eth_sendTransaction(transaction)
|
||||||
|
|
|
@ -15,6 +15,10 @@ method provider*(signer: Signer): Provider {.base.} =
|
||||||
method getAddress*(signer: Signer): Future[Address] {.base.} =
|
method getAddress*(signer: Signer): Future[Address] {.base.} =
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
|
method sendTransaction*(signer: Signer,
|
||||||
|
transaction: Transaction) {.base, async.} =
|
||||||
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method getGasPrice*(signer: Signer): Future[UInt256] {.base.} =
|
method getGasPrice*(signer: Signer): Future[UInt256] {.base.} =
|
||||||
signer.provider.getGasPrice()
|
signer.provider.getGasPrice()
|
||||||
|
|
||||||
|
|
|
@ -8,27 +8,51 @@ type
|
||||||
Erc20* = ref object of Contract
|
Erc20* = ref object of Contract
|
||||||
TestToken = ref object of Erc20
|
TestToken = ref object of Erc20
|
||||||
|
|
||||||
method totalSupply*(erc20: Erc20): UInt256 {.base, contract.}
|
method totalSupply*(erc20: Erc20): UInt256 {.base, contract, view.}
|
||||||
method balanceOf*(erc20: Erc20, account: Address): UInt256 {.base, contract.}
|
method balanceOf*(erc20: Erc20, account: Address): UInt256 {.base, contract, view.}
|
||||||
method allowance*(erc20: Erc20, owner, spender: Address): UInt256 {.base, contract.}
|
method allowance*(erc20: Erc20, owner, spender: Address): UInt256 {.base, contract, view.}
|
||||||
|
|
||||||
|
method mint(token: TestToken, holder: Address, amount: UInt256) {.base, contract.}
|
||||||
|
|
||||||
suite "Contracts":
|
suite "Contracts":
|
||||||
|
|
||||||
var token: TestToken
|
var token: TestToken
|
||||||
var provider: JsonRpcProvider
|
var provider: JsonRpcProvider
|
||||||
var snapshot: JsonNode
|
var snapshot: JsonNode
|
||||||
|
var accounts: seq[Address]
|
||||||
|
|
||||||
setup:
|
setup:
|
||||||
provider = JsonRpcProvider.new()
|
provider = JsonRpcProvider.new()
|
||||||
snapshot = await provider.send("evm_snapshot")
|
snapshot = await provider.send("evm_snapshot")
|
||||||
|
accounts = await provider.listAccounts()
|
||||||
let deployment = readDeployment()
|
let deployment = readDeployment()
|
||||||
token = TestToken.new(!deployment.address(TestToken), provider)
|
token = TestToken.new(!deployment.address(TestToken), provider)
|
||||||
|
|
||||||
teardown:
|
teardown:
|
||||||
discard await provider.send("evm_revert", @[snapshot])
|
discard await provider.send("evm_revert", @[snapshot])
|
||||||
|
|
||||||
test "can call view methods":
|
test "can call constant functions":
|
||||||
let accounts = await provider.listAccounts()
|
|
||||||
check (await token.totalSupply()) == 0.u256
|
check (await token.totalSupply()) == 0.u256
|
||||||
check (await token.balanceOf(accounts[0])) == 0.u256
|
check (await token.balanceOf(accounts[0])) == 0.u256
|
||||||
check (await token.allowance(accounts[0], accounts[1])) == 0.u256
|
check (await token.allowance(accounts[0], accounts[1])) == 0.u256
|
||||||
|
|
||||||
|
test "can call non-constant functions":
|
||||||
|
token = TestToken.new(token.address, provider.getSigner())
|
||||||
|
await token.mint(accounts[1], 100.u256)
|
||||||
|
check (await token.totalSupply()) == 100.u256
|
||||||
|
check (await token.balanceOf(accounts[1])) == 100.u256
|
||||||
|
|
||||||
|
test "can call non-constant functions without a signer":
|
||||||
|
await token.mint(accounts[1], 100.u256)
|
||||||
|
check (await token.balanceOf(accounts[1])) == 0.u256
|
||||||
|
|
||||||
|
test "can call constant functions without a return type":
|
||||||
|
token = TestToken.new(token.address, provider.getSigner())
|
||||||
|
proc mint(token: TestToken, holder: Address, amount: UInt256) {.contract, view.}
|
||||||
|
await mint(token, accounts[1], 100.u256)
|
||||||
|
check (await balanceOf(token, accounts[1])) == 0.u256
|
||||||
|
|
||||||
|
test "fails to compile when non-constant function has a return type":
|
||||||
|
let works = compiles:
|
||||||
|
proc foo(token: TestToken, bar: Address): UInt256 {.contract.}
|
||||||
|
check not works
|
||||||
|
|
Loading…
Reference in New Issue