mirror of
https://github.com/logos-storage/nim-ethers.git
synced 2026-01-07 16:13:06 +00:00
Introduce Contract abstraction
This commit is contained in:
parent
b965599a47
commit
04ff046553
7
ethers.nim
Normal file
7
ethers.nim
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import ./ethers/provider
|
||||||
|
import ./ethers/providers/jsonrpc
|
||||||
|
import ./ethers/contract
|
||||||
|
|
||||||
|
export provider
|
||||||
|
export jsonrpc
|
||||||
|
export contract
|
||||||
@ -1,11 +1,13 @@
|
|||||||
import pkg/chronos
|
import pkg/chronos
|
||||||
import pkg/questionable
|
import pkg/questionable
|
||||||
|
import pkg/questionable/results
|
||||||
import pkg/stint
|
import pkg/stint
|
||||||
import pkg/upraises
|
import pkg/upraises
|
||||||
import pkg/contractabi/address
|
import pkg/contractabi/address
|
||||||
|
|
||||||
export chronos
|
export chronos
|
||||||
export questionable
|
export questionable
|
||||||
|
export results
|
||||||
export stint
|
export stint
|
||||||
export upraises
|
export upraises
|
||||||
export address
|
export address
|
||||||
|
|||||||
86
ethers/contract.nim
Normal file
86
ethers/contract.nim
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import std/macros
|
||||||
|
import pkg/chronos
|
||||||
|
import pkg/contractabi
|
||||||
|
import ./basics
|
||||||
|
import ./provider
|
||||||
|
|
||||||
|
export basics
|
||||||
|
export provider
|
||||||
|
|
||||||
|
type
|
||||||
|
Contract* = ref object of RootObj
|
||||||
|
provider: Provider
|
||||||
|
address: Address
|
||||||
|
ContractError* = object of IOError
|
||||||
|
|
||||||
|
template raiseContractError(message: string) =
|
||||||
|
raise newException(ContractError, message)
|
||||||
|
|
||||||
|
proc createTxData(function: string, parameters: tuple): seq[byte] =
|
||||||
|
let selector = selector(function, typeof parameters).toArray
|
||||||
|
return @selector & AbiEncoder.encode(parameters)
|
||||||
|
|
||||||
|
proc createTx(contract: Contract,
|
||||||
|
function: string,
|
||||||
|
parameters: tuple): Transaction =
|
||||||
|
Transaction(to: contract.address, data: createTxData(function, parameters))
|
||||||
|
|
||||||
|
proc decodeResponse(T: type, bytes: seq[byte]): T =
|
||||||
|
without decoded =? AbiDecoder.decode(bytes, T):
|
||||||
|
raiseContractError "unable to decode return value as " & $T
|
||||||
|
return decoded
|
||||||
|
|
||||||
|
proc call[ContractType: Contract, ResultType](
|
||||||
|
contract: ContractType,
|
||||||
|
function: string,
|
||||||
|
parameters: tuple):Future[ResultType] {.async.} =
|
||||||
|
let transaction = createTx(contract, function, parameters)
|
||||||
|
let response = await contract.provider.call(transaction)
|
||||||
|
return decodeResponse(ResultType, response)
|
||||||
|
|
||||||
|
func getParameterTuple(procedure: var 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 addContractCall(procedure: var NimNode) =
|
||||||
|
let name = $procedure[0]
|
||||||
|
let parameters = procedure[3]
|
||||||
|
let contract = parameters[1][0]
|
||||||
|
let contracttype = parameters[1][1]
|
||||||
|
let resulttype = parameters[0]
|
||||||
|
let tupl = getParameterTuple(procedure)
|
||||||
|
procedure[6] = quote do:
|
||||||
|
result = await call[`contracttype`,`resulttype`](`contract`, `name`, `tupl`)
|
||||||
|
|
||||||
|
func addFuture(procedure: var NimNode) =
|
||||||
|
let returntype = procedure[3][0]
|
||||||
|
if returntype.kind == nnkEmpty:
|
||||||
|
procedure[3][0] = quote do: Future[void]
|
||||||
|
else:
|
||||||
|
procedure[3][0] = quote do: Future[`returntype`]
|
||||||
|
|
||||||
|
func addAsyncPragma(procedure: var NimNode) =
|
||||||
|
let pragmas = procedure[4]
|
||||||
|
if pragmas.kind == nnkEmpty:
|
||||||
|
procedure[4] = newNimNode(nnkPragma)
|
||||||
|
procedure[4].add ident("async")
|
||||||
|
|
||||||
|
func new*(ContractType: type Contract,
|
||||||
|
address: Address,
|
||||||
|
provider: Provider): ContractType =
|
||||||
|
ContractType(provider: provider, address: address)
|
||||||
|
|
||||||
|
macro contract*(procedure: untyped{nkProcDef|nkMethodDef}): untyped =
|
||||||
|
let parameters = procedure[3]
|
||||||
|
let body = procedure[6]
|
||||||
|
parameters.expectMinLen(2)
|
||||||
|
body.expectKind(nnkEmpty)
|
||||||
|
var contractcall = copyNimTree(procedure)
|
||||||
|
contractcall.addContractCall()
|
||||||
|
contractcall.addFuture()
|
||||||
|
contractcall.addAsyncPragma()
|
||||||
|
contractcall
|
||||||
@ -1,6 +1,8 @@
|
|||||||
import ./basics
|
import ./basics
|
||||||
|
import ./transaction
|
||||||
|
|
||||||
export basics
|
export basics
|
||||||
|
export transaction
|
||||||
|
|
||||||
push: {.upraises: [].}
|
push: {.upraises: [].}
|
||||||
|
|
||||||
@ -9,3 +11,6 @@ type
|
|||||||
|
|
||||||
method getBlockNumber*(provider: Provider): Future[UInt256] {.base.} =
|
method getBlockNumber*(provider: Provider): Future[UInt256] {.base.} =
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
|
method call*(provider: Provider, tx: Transaction): Future[seq[byte]] {.base.} =
|
||||||
|
doAssert false, "not implemented"
|
||||||
|
|||||||
@ -41,3 +41,8 @@ proc listAccounts*(provider: JsonRpcProvider): Future[seq[Address]] {.async.} =
|
|||||||
method getBlockNumber*(provider: JsonRpcProvider): Future[UInt256] {.async.} =
|
method getBlockNumber*(provider: JsonRpcProvider): Future[UInt256] {.async.} =
|
||||||
let client = await provider.client
|
let client = await provider.client
|
||||||
return await client.eth_blockNumber()
|
return await client.eth_blockNumber()
|
||||||
|
|
||||||
|
method call*(provider: JsonRpcProvider,
|
||||||
|
tx: Transaction): Future[seq[byte]] {.async.} =
|
||||||
|
let client = await provider.client
|
||||||
|
return await client.eth_call(tx)
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import std/os
|
import std/os
|
||||||
import pkg/json_rpc/rpcclient
|
import pkg/json_rpc/rpcclient
|
||||||
import ../basics
|
import ../basics
|
||||||
|
import ../transaction
|
||||||
import ./rpccalls/conversions
|
import ./rpccalls/conversions
|
||||||
|
|
||||||
const file = currentSourcePath.parentDir / "rpccalls" / "signatures.nim"
|
const file = currentSourcePath.parentDir / "rpccalls" / "signatures.nim"
|
||||||
|
|||||||
@ -1,5 +1,15 @@
|
|||||||
import std/json
|
import std/json
|
||||||
|
import pkg/stew/byteutils
|
||||||
import ../../basics
|
import ../../basics
|
||||||
|
import ../../transaction
|
||||||
|
|
||||||
|
# byte sequence
|
||||||
|
|
||||||
|
func `%`*(bytes: seq[byte]): JsonNode =
|
||||||
|
%("0x" & bytes.toHex)
|
||||||
|
|
||||||
|
func fromJson*(json: JsonNode, name: string, result: var seq[byte]) =
|
||||||
|
result = hexToSeqByte(json.getStr())
|
||||||
|
|
||||||
# Address
|
# Address
|
||||||
|
|
||||||
@ -19,3 +29,8 @@ func `%`*(integer: UInt256): JsonNode =
|
|||||||
|
|
||||||
func fromJson*(json: JsonNode, name: string, result: var UInt256) =
|
func fromJson*(json: JsonNode, name: string, result: var UInt256) =
|
||||||
result = UInt256.fromHex(json.getStr())
|
result = UInt256.fromHex(json.getStr())
|
||||||
|
|
||||||
|
# Transaction
|
||||||
|
|
||||||
|
func `%`*(tx: Transaction): JsonNode =
|
||||||
|
%{ "to": %tx.to, "data": %tx.data }
|
||||||
|
|||||||
@ -1,2 +1,3 @@
|
|||||||
proc eth_accounts: seq[Address]
|
proc eth_accounts: seq[Address]
|
||||||
proc eth_blockNumber: UInt256
|
proc eth_blockNumber: UInt256
|
||||||
|
proc eth_call(tx: Transaction): seq[byte]
|
||||||
|
|||||||
9
ethers/transaction.nim
Normal file
9
ethers/transaction.nim
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import pkg/stew/byteutils
|
||||||
|
import ./basics
|
||||||
|
|
||||||
|
type Transaction* = object
|
||||||
|
to*: Address
|
||||||
|
data*: seq[byte]
|
||||||
|
|
||||||
|
func `$`*(transaction: Transaction): string =
|
||||||
|
"(to: " & $transaction.to & ", data: 0x" & $transaction.data.toHex & ")"
|
||||||
17
testmodule/hardhat.nim
Normal file
17
testmodule/hardhat.nim
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import std/json
|
||||||
|
import pkg/ethers/basics
|
||||||
|
|
||||||
|
type Deployment* = object
|
||||||
|
json: JsonNode
|
||||||
|
|
||||||
|
const defaultFile = "../testnode/deployment.json"
|
||||||
|
|
||||||
|
## Reads deployment information from a json file. It expects a file that has
|
||||||
|
## been exported with Hardhat deploy. See also:
|
||||||
|
## https://github.com/wighawag/hardhat-deploy/tree/master#6-hardhat-export
|
||||||
|
proc readDeployment*(file = defaultFile): Deployment =
|
||||||
|
Deployment(json: parseFile(file))
|
||||||
|
|
||||||
|
proc address*(deployment: Deployment, contract: string|type): ?Address =
|
||||||
|
let address = deployment.json["contracts"][$contract]["address"].getStr()
|
||||||
|
Address.init(address)
|
||||||
@ -1,3 +1,4 @@
|
|||||||
import ./testJsonRpcProvider
|
import ./testJsonRpcProvider
|
||||||
|
import ./testContracts
|
||||||
|
|
||||||
{.warning[UnusedImport]:off.}
|
{.warning[UnusedImport]:off.}
|
||||||
|
|||||||
28
testmodule/testContracts.nim
Normal file
28
testmodule/testContracts.nim
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import pkg/asynctest
|
||||||
|
import pkg/stint
|
||||||
|
import pkg/ethers
|
||||||
|
import ./hardhat
|
||||||
|
|
||||||
|
type
|
||||||
|
Erc20 = ref object of Contract
|
||||||
|
TestToken = ref object of Erc20
|
||||||
|
|
||||||
|
method totalSupply(erc20: Erc20): UInt256 {.base, contract.}
|
||||||
|
method balanceOf(erc20: Erc20, account: Address): UInt256 {.base, contract.}
|
||||||
|
method allowance(erc20: Erc20, owner, spender: Address): UInt256 {.base, contract.}
|
||||||
|
|
||||||
|
suite "Contracts":
|
||||||
|
|
||||||
|
var token: TestToken
|
||||||
|
var provider: JsonRpcProvider
|
||||||
|
|
||||||
|
setup:
|
||||||
|
provider = JsonRpcProvider.new()
|
||||||
|
let deployment = readDeployment()
|
||||||
|
token = TestToken.new(!deployment.address(TestToken), provider)
|
||||||
|
|
||||||
|
test "can call view methods":
|
||||||
|
let accounts = await provider.listAccounts()
|
||||||
|
check (await token.totalSupply()) == 0.u256
|
||||||
|
check (await token.balanceOf(accounts[0])) == 0.u256
|
||||||
|
check (await token.allowance(accounts[0], accounts[1])) == 0.u256
|
||||||
Loading…
x
Reference in New Issue
Block a user