hive: proper rpc test
This commit is contained in:
parent
b03c2dca4f
commit
0ad7ffcdbe
|
@ -40,3 +40,32 @@ proc balanceAt*(client: RpcClient, address: EthAddress, blockNumber: uint64): Fu
|
|||
proc balanceAt*(client: RpcClient, address: EthAddress): Future[UInt256] {.async.} =
|
||||
let hex = await client.eth_getBalance(ethAddressStr(address), "latest")
|
||||
result = UInt256.fromHex(hex.string)
|
||||
|
||||
proc nonceAt*(client: RpcClient, address: EthAddress): Future[AccountNonce] {.async.} =
|
||||
let hex = await client.eth_getTransactionCount(ethAddressStr(address), "latest")
|
||||
result = parseHexInt(hex.string).AccountNonce
|
||||
|
||||
proc txReceipt*(client: RpcClient, txHash: Hash256): Future[Option[Receipt]] {.async.} =
|
||||
let rr = await client.eth_getTransactionReceipt(txHash)
|
||||
if rr.isNone:
|
||||
return none(Receipt)
|
||||
|
||||
let rc = rr.get()
|
||||
let rec = Receipt(
|
||||
receiptType: LegacyReceipt,
|
||||
isHash : rc.root.isSome,
|
||||
status : rc.status.isSome,
|
||||
hash : rc.root.get(Hash256()),
|
||||
cumulativeGasUsed: parseHexInt(rc.cumulativeGasUsed.string).GasInt,
|
||||
bloom : BloomFilter(rc.logsBloom),
|
||||
logs : rc.logs
|
||||
)
|
||||
result = some(rec)
|
||||
|
||||
proc gasUsed*(client: RpcClient, txHash: Hash256): Future[Option[GasInt]] {.async.} =
|
||||
let rr = await client.eth_getTransactionReceipt(txHash)
|
||||
if rr.isNone:
|
||||
return none(GasInt)
|
||||
|
||||
let rc = rr.get()
|
||||
result = some(parseHexInt(rc.gasUsed.string).GasInt)
|
||||
|
|
|
@ -1,90 +0,0 @@
|
|||
# Nimbus
|
||||
# Copyright (c) 2021 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
import
|
||||
std/strutils,
|
||||
eth/[common],
|
||||
stew/byteutils,
|
||||
stint,
|
||||
chronos,
|
||||
unittest2,
|
||||
json_rpc/[rpcclient],
|
||||
"."/[vault, client]
|
||||
|
||||
export client
|
||||
|
||||
const
|
||||
gasPrice* = 30000000000 # 30 Gwei or 30 * pow(10, 9)
|
||||
chainID* = ChainID(7)
|
||||
|
||||
type
|
||||
TestEnv* = ref object
|
||||
vault*: Vault
|
||||
client*: RpcClient
|
||||
|
||||
TestSpec* = object
|
||||
name*: string
|
||||
run*: proc(t: TestEnv): Future[TestStatus]
|
||||
|
||||
func eth(n: int): UInt256 {.compileTime.} =
|
||||
n.u256 * pow(10.u256, 18)
|
||||
|
||||
func u256(x: string): UInt256 =
|
||||
UInt256.fromHex(x)
|
||||
|
||||
func ethAddr(x: string): EthAddress =
|
||||
hexToByteArray[20](x)
|
||||
|
||||
# envTest make sure the env is set up properly for subsequent tests
|
||||
proc envTest(t: TestEnv): Future[TestStatus] {.async.} =
|
||||
let client = t.client
|
||||
let res = await client.web3_clientVersion()
|
||||
|
||||
const kv = {
|
||||
"cf49fda3be353c69b41ed96333cd24302da4556f": "0x123450000000000000000",
|
||||
"0161e041aad467a890839d5b08b138c1e6373072": "0x123450000000000000000",
|
||||
"87da6a8c6e9eff15d703fc2773e32f6af8dbe301": "0x123450000000000000000",
|
||||
"b97de4b8c857e4f6bc354f226dc3249aaee49209": "0x123450000000000000000",
|
||||
"c5065c9eeebe6df2c2284d046bfc906501846c51": "0x123450000000000000000"
|
||||
}
|
||||
|
||||
for x in kv:
|
||||
let res = await client.balanceAt(ethAddr(x[0]))
|
||||
let expected = u256(x[1])
|
||||
if res != expected:
|
||||
debugEcho "expected: $1, got $2" % [x[0], $res]
|
||||
return TestStatus.Failed
|
||||
|
||||
result = TestStatus.OK
|
||||
|
||||
# balanceAndNonceAtTest creates a new account and transfers funds to it.
|
||||
# It then tests if the balance and nonce of the sender and receiver
|
||||
# address are updated correct.
|
||||
proc balanceAndNonceAtTest(t: TestEnv): Future[TestStatus] {.async.} =
|
||||
let
|
||||
sourceAddr = await t.vault.createAccount(1.eth)
|
||||
sourceNonce = 0.AccountNonce
|
||||
targetAddr = await t.vault.createAccount(0.u256)
|
||||
|
||||
# Get current balance
|
||||
let sourceAddressBalanceBefore = t.client.balanceAt(sourceAddr)
|
||||
|
||||
# TODO: complete this test
|
||||
result = TestStatus.OK
|
||||
|
||||
const testList* = [
|
||||
TestSpec(
|
||||
name: "env is set up properly for subsequent tests",
|
||||
run: envTest
|
||||
),
|
||||
TestSpec(
|
||||
name: "balance and nonce update correctly",
|
||||
run: balanceAndNonceAtTest
|
||||
)
|
||||
]
|
|
@ -8,64 +8,19 @@
|
|||
# those terms.
|
||||
|
||||
import
|
||||
std/[os, times],
|
||||
eth/[trie/db],
|
||||
eth/p2p as ethp2p,
|
||||
stew/shims/net as stewNet,
|
||||
stew/results,
|
||||
chronos, json_rpc/[rpcserver, rpcclient],
|
||||
../../../nimbus/db/db_chain,
|
||||
../../../nimbus/sync/protocol,
|
||||
../../../nimbus/[config, context, genesis, utils/tx_pool],
|
||||
../../../nimbus/rpc/[common, p2p, debug],
|
||||
../../../tests/test_helpers,
|
||||
"."/[ethclient, vault, client],
|
||||
std/[times],
|
||||
chronos,
|
||||
"."/[rpc_tests, test_env],
|
||||
../sim_utils
|
||||
|
||||
const
|
||||
initPath = "hive_integration" / "nodocker" / "rpc" / "init"
|
||||
|
||||
proc manageAccounts(ctx: EthContext, conf: NimbusConf) =
|
||||
if string(conf.importKey).len > 0:
|
||||
let res = ctx.am.importPrivateKey(string conf.importKey)
|
||||
if res.isErr:
|
||||
echo res.error()
|
||||
quit(QuitFailure)
|
||||
|
||||
proc setupRpcServer(ctx: EthContext, chainDB: BaseChainDB,
|
||||
ethNode: EthereumNode, txPool: TxPoolRef,
|
||||
conf: NimbusConf): RpcServer =
|
||||
let rpcServer = newRpcHttpServer([initTAddress(conf.rpcAddress, conf.rpcPort)])
|
||||
setupCommonRpc(ethNode, conf, rpcServer)
|
||||
setupEthRpc(ethNode, ctx, chainDB, txPool, rpcServer)
|
||||
|
||||
rpcServer.start()
|
||||
rpcServer
|
||||
|
||||
proc setupWsRpcServer(ctx: EthContext, chainDB: BaseChainDB,
|
||||
ethNode: EthereumNode, txPool: TxPoolRef,
|
||||
conf: NimbusConf): RpcServer =
|
||||
let rpcServer = newRpcWebSocketServer(initTAddress(conf.wsAddress, conf.wsPort))
|
||||
setupCommonRpc(ethNode, conf, rpcServer)
|
||||
setupEthRpc(ethNode, ctx, chainDB, txPool, rpcServer)
|
||||
|
||||
rpcServer.start()
|
||||
rpcServer
|
||||
|
||||
proc runRpcTest() =
|
||||
let client = newRpcHttpClient()
|
||||
waitFor client.connect("127.0.0.1", Port(8545), false)
|
||||
|
||||
let testEnv = TestEnv(
|
||||
client: client,
|
||||
vault : newVault(chainID, gasPrice, client)
|
||||
)
|
||||
|
||||
var stat: SimStat
|
||||
let start = getTime()
|
||||
for x in testList:
|
||||
try:
|
||||
let status = waitFor x.run(testEnv)
|
||||
let env = setupEnv()
|
||||
let status = waitFor x.run(env)
|
||||
env.stopEnv()
|
||||
stat.inc(x.name, status)
|
||||
except ValueError as ex:
|
||||
stat.inc(x.name, TestStatus.Failed)
|
||||
|
@ -74,38 +29,5 @@ proc runRpcTest() =
|
|||
let elpd = getTime() - start
|
||||
print(stat, elpd, "rpc")
|
||||
|
||||
proc main() =
|
||||
let conf = makeConfig(@[
|
||||
"--prune-mode:archive",
|
||||
# "--nat:extip:0.0.0.0",
|
||||
"--network:7",
|
||||
"--import-key:" & initPath / "private-key",
|
||||
"--engine-signer:658bdf435d810c91414ec09147daa6db62406379",
|
||||
"--custom-network:" & initPath / "genesis.json",
|
||||
"--rpc",
|
||||
"--rpc-api:eth,debug",
|
||||
# "--rpc-address:0.0.0.0",
|
||||
"--rpc-port:8545",
|
||||
"--ws",
|
||||
"--ws-api:eth,debug",
|
||||
# "--ws-address:0.0.0.0",
|
||||
"--ws-port:8546"
|
||||
])
|
||||
|
||||
let
|
||||
ethCtx = newEthContext()
|
||||
ethNode = setupEthNode(conf, ethCtx, eth)
|
||||
chainDB = newBaseChainDB(newMemoryDb(),
|
||||
conf.pruneMode == PruneMode.Full,
|
||||
conf.networkId,
|
||||
conf.networkParams
|
||||
)
|
||||
|
||||
chainDB.populateProgress()
|
||||
chainDB.initializeEmptyDb()
|
||||
|
||||
let txPool = TxPoolRef.new(chainDB, conf.engineSigner)
|
||||
let rpcServer = setupRpcServer(ethCtx, chainDB, ethNode, txPool, conf)
|
||||
runRpcTest()
|
||||
|
||||
main()
|
||||
runRpcTest()
|
||||
|
|
|
@ -0,0 +1,150 @@
|
|||
# Nimbus
|
||||
# Copyright (c) 2021 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
import
|
||||
std/strutils,
|
||||
eth/[common],
|
||||
stew/byteutils,
|
||||
stint,
|
||||
chronos,
|
||||
unittest2,
|
||||
json_rpc/[rpcclient],
|
||||
"."/[vault, client, test_env]
|
||||
|
||||
export client
|
||||
|
||||
type
|
||||
TestSpec* = object
|
||||
name*: string
|
||||
run*: proc(t: TestEnv): Future[TestStatus]
|
||||
|
||||
func eth(n: int): UInt256 {.compileTime.} =
|
||||
n.u256 * pow(10.u256, 18)
|
||||
|
||||
func u256(x: string): UInt256 =
|
||||
UInt256.fromHex(x)
|
||||
|
||||
func ethAddr(x: string): EthAddress =
|
||||
hexToByteArray[20](x)
|
||||
|
||||
# envTest make sure the env is set up properly for subsequent tests
|
||||
proc envTest(t: TestEnv): Future[TestStatus] {.async.} =
|
||||
let client = t.rpcClient
|
||||
let res = await client.web3_clientVersion()
|
||||
|
||||
const kv = {
|
||||
"cf49fda3be353c69b41ed96333cd24302da4556f": "0x123450000000000000000",
|
||||
"0161e041aad467a890839d5b08b138c1e6373072": "0x123450000000000000000",
|
||||
"87da6a8c6e9eff15d703fc2773e32f6af8dbe301": "0x123450000000000000000",
|
||||
"b97de4b8c857e4f6bc354f226dc3249aaee49209": "0x123450000000000000000",
|
||||
"c5065c9eeebe6df2c2284d046bfc906501846c51": "0x123450000000000000000"
|
||||
}
|
||||
|
||||
for x in kv:
|
||||
let res = await client.balanceAt(ethAddr(x[0]))
|
||||
let expected = u256(x[1])
|
||||
if res != expected:
|
||||
echo "expected: $1, got $2" % [x[0], $res]
|
||||
return TestStatus.Failed
|
||||
|
||||
result = TestStatus.OK
|
||||
|
||||
# balanceAndNonceAtTest creates a new account and transfers funds to it.
|
||||
# It then tests if the balance and nonce of the sender and receiver
|
||||
# address are updated correct.
|
||||
proc balanceAndNonceAtTest(t: TestEnv): Future[TestStatus] {.async.} =
|
||||
let
|
||||
client = t.rpcClient
|
||||
vault = t.vault
|
||||
sourceAddr = await vault.createAccount(1.eth)
|
||||
targetAddr = await vault.createAccount(0.u256)
|
||||
|
||||
var
|
||||
sourceNonce = 0.AccountNonce
|
||||
|
||||
# Get current balance
|
||||
let sourceAddressBalanceBefore = await client.balanceAt(sourceAddr)
|
||||
|
||||
let expected = 1.eth
|
||||
if sourceAddressBalanceBefore != expected:
|
||||
echo "Expected balance $1, got $1" % [$expected, $sourceAddressBalanceBefore]
|
||||
return TestStatus.Failed
|
||||
|
||||
let nonceBefore = await client.nonceAt(sourceAddr)
|
||||
if nonceBefore != sourceNonce:
|
||||
echo "Invalid nonce, want $1, got $1" % [$sourceNonce, $nonceBefore]
|
||||
return TestStatus.Failed
|
||||
|
||||
# send 1234 wei to target account and verify balances and nonces are updated
|
||||
let
|
||||
amount = 1234.u256
|
||||
gasLimit = 50000.GasInt
|
||||
|
||||
let tx = vault.signTx(sourceAddr, sourceNonce, targetAddr, amount, gasLimit, gasPrice)
|
||||
inc sourceNonce
|
||||
|
||||
let txHash = rlpHash(tx)
|
||||
echo "BalanceAt: send $1 wei from 0x$2 to 0x$3 in 0x$4" % [
|
||||
$tx.value, sourceAddr.toHex, targetAddr.toHex, txHash.data.toHex]
|
||||
|
||||
let ok = await client.sendTransaction(tx)
|
||||
if not ok:
|
||||
echo "failed to send transaction"
|
||||
return TestStatus.Failed
|
||||
|
||||
var gasUsed: GasInt
|
||||
var loop = 0
|
||||
while true:
|
||||
let res = await client.gasUsed(txHash)
|
||||
if res.isSome:
|
||||
gasUsed = res.get()
|
||||
break
|
||||
|
||||
let period = chronos.seconds(1)
|
||||
await sleepAsync(period)
|
||||
inc loop
|
||||
if loop == 5:
|
||||
echo "get gas used timeout"
|
||||
return TestStatus.Failed
|
||||
|
||||
# ensure balances have been updated
|
||||
let accountBalanceAfter = await client.balanceAt(sourceAddr)
|
||||
let balanceTargetAccountAfter = await client.balanceAt(targetAddr)
|
||||
|
||||
# expected balance is previous balance - tx amount - tx fee (gasUsed * gasPrice)
|
||||
let exp = sourceAddressBalanceBefore - amount - (gasUsed * tx.gasPrice).u256
|
||||
|
||||
if exp != accountBalanceAfter:
|
||||
echo "Expected sender account to have a balance of $1, got $2" % [$exp, $accountBalanceAfter]
|
||||
return TestStatus.Failed
|
||||
|
||||
if balanceTargetAccountAfter != amount:
|
||||
echo "Expected new account to have a balance of $1, got $2" % [$tx.value, $balanceTargetAccountAfter]
|
||||
return TestStatus.Failed
|
||||
|
||||
# ensure nonce is incremented by 1
|
||||
let nonceAfter = await client.nonceAt(sourceAddr)
|
||||
let expectedNonce = nonceBefore + 1
|
||||
|
||||
if expectedNonce != nonceAfter:
|
||||
echo "Invalid nonce, want $1, got $2" % [$expectedNonce, $nonceAfter]
|
||||
return TestStatus.Failed
|
||||
|
||||
result = TestStatus.OK
|
||||
|
||||
const testList* = [
|
||||
TestSpec(
|
||||
name: "env is set up properly for subsequent tests",
|
||||
run: envTest
|
||||
),
|
||||
TestSpec(
|
||||
name: "balance and nonce update correctly",
|
||||
run: balanceAndNonceAtTest
|
||||
)
|
||||
]
|
|
@ -0,0 +1,136 @@
|
|||
# Nimbus
|
||||
# Copyright (c) 2021 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
import
|
||||
std/[os],
|
||||
eth/[trie/db],
|
||||
eth/common as ethcommon,
|
||||
eth/p2p as ethp2p,
|
||||
stew/shims/net as stewNet,
|
||||
stew/results,
|
||||
chronos, json_rpc/[rpcserver, rpcclient],
|
||||
../../../nimbus/db/db_chain,
|
||||
../../../nimbus/sync/protocol,
|
||||
../../../nimbus/[config, context, genesis, utils/tx_pool, sealer],
|
||||
../../../nimbus/rpc/[common, p2p, debug],
|
||||
../../../nimbus/p2p/chain,
|
||||
../../../tests/test_helpers,
|
||||
./vault
|
||||
|
||||
type
|
||||
StopServerProc = proc(srv: RpcServer)
|
||||
|
||||
TestEnv* = ref object
|
||||
vault*: Vault
|
||||
rpcClient*: RpcClient
|
||||
rpcServer: RpcServer
|
||||
sealingEngine: SealingEngineRef
|
||||
stopServer: StopServerProc
|
||||
|
||||
const
|
||||
initPath = "hive_integration" / "nodocker" / "rpc" / "init"
|
||||
gasPrice* = 30000000000 # 30 Gwei or 30 * pow(10, 9)
|
||||
chainID* = ChainID(7)
|
||||
|
||||
proc manageAccounts(ctx: EthContext, conf: NimbusConf) =
|
||||
if string(conf.importKey).len > 0:
|
||||
let res = ctx.am.importPrivateKey(string conf.importKey)
|
||||
if res.isErr:
|
||||
echo res.error()
|
||||
quit(QuitFailure)
|
||||
|
||||
proc setupRpcServer(ctx: EthContext, chainDB: BaseChainDB,
|
||||
ethNode: EthereumNode, txPool: TxPoolRef,
|
||||
conf: NimbusConf): RpcServer =
|
||||
let rpcServer = newRpcHttpServer([initTAddress(conf.rpcAddress, conf.rpcPort)])
|
||||
setupCommonRpc(ethNode, conf, rpcServer)
|
||||
setupEthRpc(ethNode, ctx, chainDB, txPool, rpcServer)
|
||||
|
||||
rpcServer.start()
|
||||
rpcServer
|
||||
|
||||
proc setupWsRpcServer(ctx: EthContext, chainDB: BaseChainDB,
|
||||
ethNode: EthereumNode, txPool: TxPoolRef,
|
||||
conf: NimbusConf): RpcServer =
|
||||
let rpcServer = newRpcWebSocketServer(initTAddress(conf.wsAddress, conf.wsPort))
|
||||
setupCommonRpc(ethNode, conf, rpcServer)
|
||||
setupEthRpc(ethNode, ctx, chainDB, txPool, rpcServer)
|
||||
|
||||
rpcServer.start()
|
||||
rpcServer
|
||||
|
||||
proc stopRpcHttpServer(srv: RpcServer) =
|
||||
let rpcServer = RpcHttpServer(srv)
|
||||
waitFor rpcServer.stop()
|
||||
waitFor rpcServer.closeWait()
|
||||
|
||||
proc stopRpcWsServer(srv: RpcServer) =
|
||||
let rpcServer = RpcWebSocketServer(srv)
|
||||
rpcServer.stop()
|
||||
waitFor rpcServer.closeWait()
|
||||
|
||||
proc setupEnv*(): TestEnv =
|
||||
let conf = makeConfig(@[
|
||||
"--prune-mode:archive",
|
||||
# "--nat:extip:0.0.0.0",
|
||||
"--network:7",
|
||||
"--import-key:" & initPath / "private-key",
|
||||
"--engine-signer:658bdf435d810c91414ec09147daa6db62406379",
|
||||
"--custom-network:" & initPath / "genesis.json",
|
||||
"--rpc",
|
||||
"--rpc-api:eth,debug",
|
||||
# "--rpc-address:0.0.0.0",
|
||||
"--rpc-port:8545",
|
||||
"--ws",
|
||||
"--ws-api:eth,debug",
|
||||
# "--ws-address:0.0.0.0",
|
||||
"--ws-port:8546"
|
||||
])
|
||||
|
||||
let
|
||||
ethCtx = newEthContext()
|
||||
ethNode = setupEthNode(conf, ethCtx, eth)
|
||||
chainDB = newBaseChainDB(newMemoryDb(),
|
||||
conf.pruneMode == PruneMode.Full,
|
||||
conf.networkId,
|
||||
conf.networkParams
|
||||
)
|
||||
|
||||
manageAccounts(ethCtx, conf)
|
||||
chainDB.populateProgress()
|
||||
chainDB.initializeEmptyDb()
|
||||
|
||||
let chainRef = newChain(chainDB)
|
||||
let txPool = TxPoolRef.new(chainDB, conf.engineSigner)
|
||||
let sealingEngine = SealingEngineRef.new(
|
||||
chainRef, ethCtx, conf.engineSigner,
|
||||
txPool, EngineStopped
|
||||
)
|
||||
|
||||
let rpcServer = setupRpcServer(ethCtx, chainDB, ethNode, txPool, conf)
|
||||
let rpcClient = newRpcHttpClient()
|
||||
waitFor rpcClient.connect("127.0.0.1", Port(8545), false)
|
||||
let stopServer = stopRpcHttpServer
|
||||
|
||||
sealingEngine.start()
|
||||
|
||||
let t = TestEnv(
|
||||
rpcClient: rpcClient,
|
||||
sealingEngine: sealingEngine,
|
||||
rpcServer: rpcServer,
|
||||
vault : newVault(chainID, gasPrice, rpcClient),
|
||||
stopServer: stopServer
|
||||
)
|
||||
|
||||
result = t
|
||||
|
||||
proc stopEnv*(t: TestEnv) =
|
||||
waitFor t.rpcClient.close()
|
||||
waitFor t.sealingEngine.stop()
|
||||
t.stopServer(t.rpcServer)
|
|
@ -93,6 +93,29 @@ proc makeFundingTx*(v: Vault, recipient: EthAddress, amount: UInt256): Transacti
|
|||
|
||||
signTransaction(unsignedTx, v.vaultKey, v.chainId, eip155 = true)
|
||||
|
||||
proc signTx*(v: Vault,
|
||||
sender: EthAddress,
|
||||
nonce: AccountNonce,
|
||||
recipient: EthAddress,
|
||||
amount: UInt256,
|
||||
gasLimit, gasPrice: GasInt,
|
||||
payload: seq[byte] = @[]): Transaction =
|
||||
|
||||
let
|
||||
unsignedTx = Transaction(
|
||||
txType : TxLegacy,
|
||||
chainId : v.chainId,
|
||||
nonce : nonce,
|
||||
gasPrice: gasPrice,
|
||||
gasLimit: gasLimit,
|
||||
to : some(recipient),
|
||||
value : amount,
|
||||
payload : payload
|
||||
)
|
||||
|
||||
let key = v.accounts[sender]
|
||||
signTransaction(unsignedTx, key, v.chainId, eip155 = true)
|
||||
|
||||
# createAccount creates a new account that is funded from the vault contract.
|
||||
# It will panic when the account could not be created and funded.
|
||||
proc createAccount*(v: Vault, amount: UInt256): Future[EthAddress] {.async.} =
|
||||
|
|
Loading…
Reference in New Issue