Skip to content

Commit 8eef4d5

Browse files
committed
add option to runAnvil for state dump or load with optional contract deployment on setup
1 parent 4916050 commit 8eef4d5

File tree

3 files changed

+146
-50
lines changed

3 files changed

+146
-50
lines changed

tests/waku_rln_relay/anvil_state/state-deployed-contracts-mint-and-approved.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

tests/waku_rln_relay/test_rln_group_manager_onchain.nim

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ suite "Onchain group manager":
3333
var manager {.threadVar.}: OnchainGroupManager
3434

3535
setup:
36-
anvilProc = runAnvil()
37-
manager = waitFor setupOnchainGroupManager()
36+
anvilProc = runAnvil(stateFile = some(DEFAULT_ANVIL_STATE_PATH))
37+
manager = waitFor setupOnchainGroupManager(deployContracts = false)
3838

3939
teardown:
4040
stopAnvil(anvilProc)

tests/waku_rln_relay/utils_onchain.nim

Lines changed: 143 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,22 @@ import
3131
./utils
3232

3333
const CHAIN_ID* = 1234'u256
34-
35-
template skip0xPrefix(hexStr: string): int =
36-
## Returns the index of the first meaningful char in `hexStr` by skipping
37-
## "0x" prefix
38-
if hexStr.len > 1 and hexStr[0] == '0' and hexStr[1] in {'x', 'X'}: 2 else: 0
39-
40-
func strip0xPrefix(s: string): string =
41-
let prefixLen = skip0xPrefix(s)
42-
if prefixLen != 0:
43-
s[prefixLen .. ^1]
44-
else:
45-
s
34+
const DEFAULT_ANVIL_STATE_PATH* =
35+
"tests/waku_rln_relay/anvil_state/state-deployed-contracts-mint-and-approved.json"
36+
const TOKEN_ADDRESS* = "0x5FbDB2315678afecb367f032d93F642f64180aa3"
37+
const WAKU_RLNV2_PROXY_ADDRESS* = "0x5fc8d32690cc91d4c39d9d3abcbd16989f875707"
38+
39+
# template skip0xPrefix(hexStr: string): int =
40+
# ## Returns the index of the first meaningful char in `hexStr` by skipping
41+
# ## "0x" prefix
42+
# if hexStr.len > 1 and hexStr[0] == '0' and hexStr[1] in {'x', 'X'}: 2 else: 0
43+
44+
# func strip0xPrefix(s: string): string =
45+
# let prefixLen = skip0xPrefix(s)
46+
# if prefixLen != 0:
47+
# s[prefixLen .. ^1]
48+
# else:
49+
# s
4650

4751
proc generateCredentials*(): IdentityCredential =
4852
let credRes = membershipKeyGen()
@@ -488,19 +492,26 @@ proc getAnvilPath*(): string =
488492
return $anvilPath
489493

490494
# Runs Anvil daemon
491-
proc runAnvil*(port: int = 8540, chainId: string = "1234"): Process =
495+
proc runAnvil*(
496+
port: int = 8540,
497+
chainId: string = "1234",
498+
stateFile: Option[string] = none(string),
499+
dumpStateOnExit: bool = false,
500+
): Process =
492501
# Passed options are
493502
# --port Port to listen on.
494503
# --gas-limit Sets the block gas limit in WEI.
495504
# --balance The default account balance, specified in ether.
496505
# --chain-id Chain ID of the network.
506+
# --load-state Initialize the chain from a previously saved state snapshot (read-only)
507+
# --dump-state Dump the state on exit to the given file (write-only)
497508
# See anvil documentation https://book.getfoundry.sh/reference/anvil/ for more details
498509
try:
499510
let anvilPath = getAnvilPath()
500511
info "Anvil path", anvilPath
501-
let runAnvil = startProcess(
502-
anvilPath,
503-
args = [
512+
513+
var args =
514+
@[
504515
"--port",
505516
$port,
506517
"--gas-limit",
@@ -509,9 +520,45 @@ proc runAnvil*(port: int = 8540, chainId: string = "1234"): Process =
509520
"1000000000",
510521
"--chain-id",
511522
$chainId,
512-
],
513-
options = {poUsePath, poStdErrToStdOut},
514-
)
523+
]
524+
525+
# Add state file argument if provided
526+
if stateFile.isSome():
527+
let statePath = stateFile.get()
528+
info "State file parameter provided",
529+
statePath = statePath,
530+
dumpStateOnExit = dumpStateOnExit,
531+
absolutePath = absolutePath(statePath)
532+
533+
# Ensure the directory exists
534+
let stateDir = parentDir(statePath)
535+
if not dirExists(stateDir):
536+
info "Creating state directory", dir = stateDir
537+
createDir(stateDir)
538+
539+
# Use --load-state (read-only) when we want to use cached state without modifying it
540+
# Use --dump-state (write-only) when we want to create a new cache from fresh deployment
541+
if dumpStateOnExit:
542+
# Fresh deployment: start clean and dump state on exit
543+
args.add("--dump-state")
544+
args.add(statePath)
545+
debug "Anvil configured to dump state on exit", path = statePath
546+
else:
547+
# Using cache: only load state, don't overwrite it (preserves clean cached state)
548+
if fileExists(statePath):
549+
args.add("--load-state")
550+
args.add(statePath)
551+
debug "Anvil configured to load state file (read-only)", path = statePath
552+
else:
553+
warn "State file does not exist, anvil will start fresh",
554+
path = statePath, absolutePath = absolutePath(statePath)
555+
else:
556+
info "No state file provided, anvil will start fresh without state persistence"
557+
558+
info "Starting anvil with arguments", args = args.join(" ")
559+
560+
let runAnvil =
561+
startProcess(anvilPath, args = args, options = {poUsePath, poStdErrToStdOut})
515562
let anvilPID = runAnvil.processID
516563

517564
# We read stdout from Anvil to see when daemon is ready
@@ -560,52 +607,100 @@ proc stopAnvil*(runAnvil: Process) {.used.} =
560607
info "Error stopping Anvil daemon", anvilPID = anvilPID, error = e.msg
561608

562609
proc setupOnchainGroupManager*(
563-
ethClientUrl: string = EthClient, amountEth: UInt256 = 10.u256
610+
ethClientUrl: string = EthClient,
611+
amountEth: UInt256 = 10.u256,
612+
deployContracts: bool = true,
564613
): Future[OnchainGroupManager] {.async.} =
614+
## Setup an onchain group manager for testing
615+
## If deployContracts is false, it will assume that the Anvil testnet already has the required contracts deployed, this significantly speeds up test runs.
616+
## To run Anvil with a cached state file containing pre-deployed contracts, see runAnvil documentation.
617+
##
618+
## To generate/update the cached state file:
619+
## 1. Call runAnvil with stateFile and dumpStateOnExit=true
620+
## 2. Run setupOnchainGroupManager with deployContracts=true to deploy contracts
621+
## 3. The state will be saved to the specified file when anvil exits
622+
## 4. Commit this file to git
623+
##
624+
## To use cached state:
625+
## 1. Call runAnvil with stateFile and dumpStateOnExit=false
626+
## 2. Anvil loads state in read-only mode (won't overwrite the cached file)
627+
## 3. Call setupOnchainGroupManager with deployContracts=false
628+
## 4. Tests run fast using pre-deployed contracts
565629
let rlnInstanceRes = createRlnInstance()
566630
check:
567631
rlnInstanceRes.isOk()
568632

569633
let rlnInstance = rlnInstanceRes.get()
570634

571-
# connect to the eth client
572635
let web3 = await newWeb3(ethClientUrl)
573636
let accounts = await web3.provider.eth_accounts()
574637
web3.defaultAccount = accounts[1]
575638

576-
let (privateKey, acc) = createEthAccount(web3)
639+
var privateKey: keys.PrivateKey
640+
var acc: Address
641+
var testTokenAddress: Address
642+
var contractAddress: Address
577643

578-
# we just need to fund the default account
579-
# the send procedure returns a tx hash that we don't use, hence discard
580-
discard await sendEthTransfer(
581-
web3, web3.defaultAccount, acc, ethToWei(1000.u256), some(0.u256)
582-
)
644+
if not deployContracts:
645+
info "Using contract addresses from constants"
583646

584-
let testTokenAddress = (await deployTestToken(privateKey, acc, web3)).valueOr:
585-
assert false, "Failed to deploy test token contract: " & $error
586-
return
647+
testTokenAddress = Address(hexToByteArray[20](TOKEN_ADDRESS))
648+
contractAddress = Address(hexToByteArray[20](WAKU_RLNV2_PROXY_ADDRESS))
587649

588-
# mint the token from the generated account
589-
discard await sendMintCall(
590-
web3, web3.defaultAccount, testTokenAddress, acc, ethToWei(1000.u256), some(0.u256)
591-
)
650+
(privateKey, acc) = createEthAccount(web3)
592651

593-
let contractAddress = (await executeForgeContractDeployScripts(privateKey, acc, web3)).valueOr:
594-
assert false, "Failed to deploy RLN contract: " & $error
595-
return
652+
# Fund the test account
653+
discard await sendEthTransfer(web3, web3.defaultAccount, acc, ethToWei(1000.u256))
596654

597-
# If the generated account wishes to register a membership, it needs to approve the contract to spend its tokens
598-
let tokenApprovalResult = await approveTokenAllowanceAndVerify(
599-
web3,
600-
acc,
601-
privateKey,
602-
testTokenAddress,
603-
contractAddress,
604-
ethToWei(200.u256),
605-
some(0.u256),
606-
)
655+
# Mint tokens to the test account
656+
discard await sendMintCall(
657+
web3, web3.defaultAccount, testTokenAddress, acc, ethToWei(1000.u256)
658+
)
659+
660+
# Approve the contract to spend tokens
661+
let tokenApprovalResult = await approveTokenAllowanceAndVerify(
662+
web3, acc, privateKey, testTokenAddress, contractAddress, ethToWei(200.u256)
663+
)
664+
assert tokenApprovalResult.isOk, tokenApprovalResult.error()
665+
else:
666+
info "Performing Token and RLN contracts deployment"
667+
(privateKey, acc) = createEthAccount(web3)
668+
669+
# fund the default account
670+
discard await sendEthTransfer(
671+
web3, web3.defaultAccount, acc, ethToWei(1000.u256), some(0.u256)
672+
)
673+
674+
testTokenAddress = (await deployTestToken(privateKey, acc, web3)).valueOr:
675+
assert false, "Failed to deploy test token contract: " & $error
676+
return
677+
678+
# mint the token from the generated account
679+
discard await sendMintCall(
680+
web3,
681+
web3.defaultAccount,
682+
testTokenAddress,
683+
acc,
684+
ethToWei(1000.u256),
685+
some(0.u256),
686+
)
687+
688+
contractAddress = (await executeForgeContractDeployScripts(privateKey, acc, web3)).valueOr:
689+
assert false, "Failed to deploy RLN contract: " & $error
690+
return
691+
692+
# If the generated account wishes to register a membership, it needs to approve the contract to spend its tokens
693+
let tokenApprovalResult = await approveTokenAllowanceAndVerify(
694+
web3,
695+
acc,
696+
privateKey,
697+
testTokenAddress,
698+
contractAddress,
699+
ethToWei(200.u256),
700+
some(0.u256),
701+
)
607702

608-
assert tokenApprovalResult.isOk, tokenApprovalResult.error()
703+
assert tokenApprovalResult.isOk, tokenApprovalResult.error()
609704

610705
let manager = OnchainGroupManager(
611706
ethClientUrls: @[ethClientUrl],

0 commit comments

Comments
 (0)