feat: Update implementation for new contract abi (#3390)

* update RLN contract abi functions and procs

* Clean up debugging lines

* Use more descriptive object field names for MembershipInfo

* Fix formatting

* fix group_manager after rebase to use new contract method sig

* Fix linting for group_manager.nim

* Test idcommitment to BE and debug logs

* Improve IdCommitment logging

* Update all keystore credentials to use BE format

* Add workaround for groupmanager web3 eth_call

* Add await to sendEthCallWithChainID

* Add error handling for failed eth_call

* Improve error handling for eth_call workaround

* Revert keystore credentials back to using LE

* Update toRateCommitment proc to use LE instead of BE

* Add IdCommitment to calldata as BE

* feat: Update rln contract deployment and tests (#3408)

* update RLN contract abi functions and procs

* update waku-rlnv2-contract submodule commit to latest

* Add RlnV2 contract deployment using forge scripts

* Clean up output of forge script command, debug logs to trace, warn to error

* Move TestToken deployment to own proc

* first implementation of token minting and approval

* Update rln tests with usermessagelimit new minimum

* Clean up code and error handling

* Rework RLN tests WIP

* Fix RLN test for new contract

* RLN Tests updated

* Fix formatting

* Improve error logs

* Fix error message formatting

* Fix linting

* Add pnpm dependency installation for rln tests

* Update test dependencies in makefile

* Minor updates, error messages etc

* Code cleanup and change some debug logging to trace

* Improve handling of Result return value

* Use absolute path for waku-rlnv2-contract

* Simplify token approval and balance check

* Remove unused Anvil options

* Add additional checks for stopAnvil process

* Fix anvil process call to null

* Add lock to tests for rln_group_manager_onchain

* Debug for forge command

* Verify paths

* Install pnpm as global

* Cleanup anvil running procs

* Add check before installing anvil

* CLean up onchain group_manager

* Add proc to setup environment for contract deployer

* Refactoring and improved error handling

* Fix anvil install directory string

* Fix linting in test_range_split

* Add const for the contract address length

* Add separate checks for why Approval transaction fails

* Update RLN contract address and chainID for TWN
This commit is contained in:
Tanya S 2025-06-20 11:46:08 +02:00 committed by GitHub
parent 4277a53490
commit ee4058b2d7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 687 additions and 234 deletions

View File

@ -112,11 +112,8 @@ ifeq (, $(shell which cargo))
curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain stable curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain stable
endif endif
anvil: rustup rln-deps: rustup
ifeq (, $(shell which anvil 2> /dev/null)) ./scripts/install_rln_tests_dependencies.sh
# Install Anvil if it's not installed
./scripts/install_anvil.sh
endif
deps: | deps-common nat-libs waku.nims deps: | deps-common nat-libs waku.nims
@ -205,8 +202,8 @@ testcommon: | build deps
########## ##########
.PHONY: testwaku wakunode2 testwakunode2 example2 chat2 chat2bridge liteprotocoltester .PHONY: testwaku wakunode2 testwakunode2 example2 chat2 chat2bridge liteprotocoltester
# install anvil only for the testwaku target # install rln-deps only for the testwaku target
testwaku: | build deps anvil librln testwaku: | build deps rln-deps librln
echo -e $(BUILD_MSG) "build/$@" && \ echo -e $(BUILD_MSG) "build/$@" && \
$(ENV_SCRIPT) nim test -d:os=$(shell uname) $(NIM_PARAMS) waku.nims $(ENV_SCRIPT) nim test -d:os=$(shell uname) $(NIM_PARAMS) waku.nims

View File

@ -9,7 +9,7 @@ x-logging: &logging
x-rln-relay-eth-client-address: &rln_relay_eth_client_address ${RLN_RELAY_ETH_CLIENT_ADDRESS:-} # Add your RLN_RELAY_ETH_CLIENT_ADDRESS after the "-" x-rln-relay-eth-client-address: &rln_relay_eth_client_address ${RLN_RELAY_ETH_CLIENT_ADDRESS:-} # Add your RLN_RELAY_ETH_CLIENT_ADDRESS after the "-"
x-rln-environment: &rln_env x-rln-environment: &rln_env
RLN_RELAY_CONTRACT_ADDRESS: ${RLN_RELAY_CONTRACT_ADDRESS:-0xfe7a9eabcE779a090FD702346Fd0bFAc02ce6Ac8} RLN_RELAY_CONTRACT_ADDRESS: ${RLN_RELAY_CONTRACT_ADDRESS:-0xB9cd878C90E49F797B4431fBF4fb333108CB90e6}
RLN_RELAY_CRED_PATH: ${RLN_RELAY_CRED_PATH:-} # Optional: Add your RLN_RELAY_CRED_PATH after the "-" RLN_RELAY_CRED_PATH: ${RLN_RELAY_CRED_PATH:-} # Optional: Add your RLN_RELAY_CRED_PATH after the "-"
RLN_RELAY_CRED_PASSWORD: ${RLN_RELAY_CRED_PASSWORD:-} # Optional: Add your RLN_RELAY_CRED_PASSWORD after the "-" RLN_RELAY_CRED_PASSWORD: ${RLN_RELAY_CRED_PASSWORD:-} # Optional: Add your RLN_RELAY_CRED_PASSWORD after the "-"

View File

@ -24,7 +24,7 @@ fi
docker run -v $(pwd)/keystore:/keystore/:Z harbor.status.im/wakuorg/nwaku:v0.30.1 generateRlnKeystore \ docker run -v $(pwd)/keystore:/keystore/:Z harbor.status.im/wakuorg/nwaku:v0.30.1 generateRlnKeystore \
--rln-relay-eth-client-address=${RLN_RELAY_ETH_CLIENT_ADDRESS} \ --rln-relay-eth-client-address=${RLN_RELAY_ETH_CLIENT_ADDRESS} \
--rln-relay-eth-private-key=${ETH_TESTNET_KEY} \ --rln-relay-eth-private-key=${ETH_TESTNET_KEY} \
--rln-relay-eth-contract-address=0xfe7a9eabcE779a090FD702346Fd0bFAc02ce6Ac8 \ --rln-relay-eth-contract-address=0xB9cd878C90E49F797B4431fBF4fb333108CB90e6 \
--rln-relay-cred-path=/keystore/keystore.json \ --rln-relay-cred-path=/keystore/keystore.json \
--rln-relay-cred-password="${RLN_RELAY_CRED_PASSWORD}" \ --rln-relay-cred-password="${RLN_RELAY_CRED_PASSWORD}" \
--rln-relay-user-message-limit=20 \ --rln-relay-user-message-limit=20 \

View File

@ -2,13 +2,14 @@
# Install Anvil # Install Anvil
if ! command -v anvil &> /dev/null; then
BASE_DIR="${XDG_CONFIG_HOME:-$HOME}"
FOUNDRY_DIR="${FOUNDRY_DIR:-"$BASE_DIR/.foundry"}"
FOUNDRY_BIN_DIR="$FOUNDRY_DIR/bin"
BASE_DIR="${XDG_CONFIG_HOME:-$HOME}" curl -L https://foundry.paradigm.xyz | bash
FOUNDRY_DIR="${FOUNDRY_DIR-"$BASE_DIR/.foundry"}" # Extract the source path from the download result
FOUNDRY_BIN_DIR="$FOUNDRY_DIR/bin" echo "foundryup_path: $FOUNDRY_BIN_DIR"
# run foundryup
curl -L https://foundry.paradigm.xyz | bash $FOUNDRY_BIN_DIR/foundryup
# Extract the source path from the download result fi
echo "foundryup_path: $FOUNDRY_BIN_DIR"
# run foundryup
$FOUNDRY_BIN_DIR/foundryup

8
scripts/install_pnpm.sh Executable file
View File

@ -0,0 +1,8 @@
#!/usr/bin/env bash
# Install pnpm
if ! command -v pnpm &> /dev/null; then
echo "pnpm is not installed, installing it now..."
npm i pnpm --global
fi

View File

@ -0,0 +1,7 @@
#!/usr/bin/env bash
# Install Anvil
./scripts/install_anvil.sh
#Install pnpm
./scripts/install_pnpm.sh

View File

@ -3,7 +3,7 @@
{.push raises: [].} {.push raises: [].}
import import
std/[options, sequtils, deques, random], std/[options, sequtils, deques, random, locks],
results, results,
stew/byteutils, stew/byteutils,
testutils/unittests, testutils/unittests,
@ -28,58 +28,71 @@ import
../testlib/wakucore, ../testlib/wakucore,
./utils_onchain ./utils_onchain
var testLock: Lock
initLock(testLock)
suite "Onchain group manager": suite "Onchain group manager":
# We run Anvil setup:
let runAnvil {.used.} = runAnvil() # Acquire lock to ensure tests run sequentially
acquire(testLock)
var manager {.threadvar.}: OnchainGroupManager let runAnvil {.used.} = runAnvil()
asyncSetup: var manager {.threadvar.}: OnchainGroupManager
manager = await setupOnchainGroupManager() manager = waitFor setupOnchainGroupManager()
asyncTeardown: teardown:
await manager.stop() waitFor manager.stop()
stopAnvil(runAnvil)
# Release lock after test completes
release(testLock)
asyncTest "should initialize successfully": test "should initialize successfully":
(await manager.init()).isOkOr: (waitFor manager.init()).isOkOr:
raiseAssert $error raiseAssert $error
check: check:
manager.ethRpc.isSome() manager.ethRpc.isSome()
manager.wakuRlnContract.isSome() manager.wakuRlnContract.isSome()
manager.initialized manager.initialized
manager.rlnRelayMaxMessageLimit == 100 manager.rlnRelayMaxMessageLimit == 600
asyncTest "should error on initialization when chainId does not match": test "should error on initialization when chainId does not match":
manager.chainId = utils_onchain.CHAIN_ID + 1 manager.chainId = utils_onchain.CHAIN_ID + 1
(await manager.init()).isErrOr: (waitFor manager.init()).isErrOr:
raiseAssert "Expected error when chainId does not match" raiseAssert "Expected error when chainId does not match"
asyncTest "should initialize when chainId is set to 0": test "should initialize when chainId is set to 0":
manager.chainId = 0x0'u256 manager.chainId = 0x0'u256
(waitFor manager.init()).isOkOr:
(await manager.init()).isOkOr:
raiseAssert $error raiseAssert $error
asyncTest "should error on initialization when loaded metadata does not match": test "should error on initialization when loaded metadata does not match":
(await manager.init()).isOkOr: (waitFor manager.init()).isOkOr:
assert false, $error assert false, $error
let metadataSetRes = manager.setMetadata() let metadataSetRes = manager.setMetadata()
assert metadataSetRes.isOk(), metadataSetRes.error assert metadataSetRes.isOk(), metadataSetRes.error
let metadataOpt = manager.rlnInstance.getMetadata().valueOr: let metadataOpt = manager.rlnInstance.getMetadata().valueOr:
assert false, $error assert false, $error
return return
assert metadataOpt.isSome(), "metadata is not set" assert metadataOpt.isSome(), "metadata is not set"
let metadata = metadataOpt.get() let metadata = metadataOpt.get()
assert metadata.chainId == 1234, "chainId is not equal to 1234"
assert metadata.chainId == 1337, "chainId is not equal to 1337"
assert metadata.contractAddress == manager.ethContractAddress, assert metadata.contractAddress == manager.ethContractAddress,
"contractAddress is not equal to " & manager.ethContractAddress "contractAddress is not equal to " & manager.ethContractAddress
let web3 = manager.ethRpc.get()
let differentContractAddress = await uploadRLNContract(manager.ethClientUrls[0]) let accounts = waitFor web3.provider.eth_accounts()
web3.defaultAccount = accounts[2]
let (privateKey, acc) = createEthAccount(web3)
let tokenAddress = (waitFor deployTestToken(privateKey, acc, web3)).valueOr:
assert false, "Failed to deploy test token contract: " & $error
return
let differentContractAddress = (
waitFor executeForgeContractDeployScripts(privateKey, acc, web3)
).valueOr:
assert false, "Failed to deploy RLN contract: " & $error
return
# simulating a change in the contractAddress # simulating a change in the contractAddress
let manager2 = OnchainGroupManager( let manager2 = OnchainGroupManager(
ethClientUrls: @[EthClient], ethClientUrls: @[EthClient],
@ -89,52 +102,47 @@ suite "Onchain group manager":
assert false, errStr assert false, errStr
, ,
) )
let e = await manager2.init() let e = waitFor manager2.init()
(e).isErrOr: (e).isErrOr:
assert false, "Expected error when contract address doesn't match" assert false, "Expected error when contract address doesn't match"
asyncTest "should error if contract does not exist": test "should error if contract does not exist":
manager.ethContractAddress = "0x0000000000000000000000000000000000000000" manager.ethContractAddress = "0x0000000000000000000000000000000000000000"
var triggeredError = false (waitFor manager.init()).isErrOr:
try: raiseAssert "Expected error when contract address doesn't exist"
discard await manager.init()
except CatchableError:
triggeredError = true
check triggeredError test "should error when keystore path and password are provided but file doesn't exist":
asyncTest "should error when keystore path and password are provided but file doesn't exist":
manager.keystorePath = some("/inexistent/file") manager.keystorePath = some("/inexistent/file")
manager.keystorePassword = some("password") manager.keystorePassword = some("password")
(await manager.init()).isErrOr: (waitFor manager.init()).isErrOr:
raiseAssert "Expected error when keystore file doesn't exist" raiseAssert "Expected error when keystore file doesn't exist"
asyncTest "trackRootChanges: start tracking roots": test "trackRootChanges: start tracking roots":
(await manager.init()).isOkOr: (waitFor manager.init()).isOkOr:
raiseAssert $error raiseAssert $error
discard manager.trackRootChanges() discard manager.trackRootChanges()
asyncTest "trackRootChanges: should guard against uninitialized state": test "trackRootChanges: should guard against uninitialized state":
try: try:
discard manager.trackRootChanges() discard manager.trackRootChanges()
except CatchableError: except CatchableError:
check getCurrentExceptionMsg().len == 38 check getCurrentExceptionMsg().len == 38
asyncTest "trackRootChanges: should sync to the state of the group": test "trackRootChanges: should sync to the state of the group":
let credentials = generateCredentials(manager.rlnInstance) let credentials = generateCredentials(manager.rlnInstance)
(await manager.init()).isOkOr: (waitFor manager.init()).isOkOr:
raiseAssert $error raiseAssert $error
let merkleRootBefore = manager.fetchMerkleRoot() let merkleRootBefore = manager.fetchMerkleRoot()
try: try:
await manager.register(credentials, UserMessageLimit(1)) waitFor manager.register(credentials, UserMessageLimit(20))
except Exception, CatchableError: except Exception, CatchableError:
assert false, "exception raised: " & getCurrentExceptionMsg() assert false, "exception raised: " & getCurrentExceptionMsg()
discard await withTimeout(trackRootChanges(manager), 15.seconds) discard waitFor withTimeout(trackRootChanges(manager), 15.seconds)
let merkleRootAfter = manager.fetchMerkleRoot() let merkleRootAfter = manager.fetchMerkleRoot()
@ -151,7 +159,7 @@ suite "Onchain group manager":
metadata.validRoots == manager.validRoots.toSeq() metadata.validRoots == manager.validRoots.toSeq()
merkleRootBefore != merkleRootAfter merkleRootBefore != merkleRootAfter
asyncTest "trackRootChanges: should fetch history correctly": test "trackRootChanges: should fetch history correctly":
# TODO: We can't use `trackRootChanges()` directly in this test because its current implementation # TODO: We can't use `trackRootChanges()` directly in this test because its current implementation
# relies on a busy loop rather than event-based monitoring. As a result, some root changes # relies on a busy loop rather than event-based monitoring. As a result, some root changes
# may be missed, leading to inconsistent test results (i.e., it may randomly return true or false). # may be missed, leading to inconsistent test results (i.e., it may randomly return true or false).
@ -159,15 +167,16 @@ suite "Onchain group manager":
# after each registration. # after each registration.
const credentialCount = 6 const credentialCount = 6
let credentials = generateCredentials(manager.rlnInstance, credentialCount) let credentials = generateCredentials(manager.rlnInstance, credentialCount)
(await manager.init()).isOkOr: (waitFor manager.init()).isOkOr:
raiseAssert $error raiseAssert $error
let merkleRootBefore = manager.fetchMerkleRoot() let merkleRootBefore = manager.fetchMerkleRoot()
try: try:
for i in 0 ..< credentials.len(): for i in 0 ..< credentials.len():
await manager.register(credentials[i], UserMessageLimit(1)) debug "Registering credential", index = i, credential = credentials[i]
discard await manager.updateRoots() waitFor manager.register(credentials[i], UserMessageLimit(20))
discard waitFor manager.updateRoots()
except Exception, CatchableError: except Exception, CatchableError:
assert false, "exception raised: " & getCurrentExceptionMsg() assert false, "exception raised: " & getCurrentExceptionMsg()
@ -177,13 +186,13 @@ suite "Onchain group manager":
merkleRootBefore != merkleRootAfter merkleRootBefore != merkleRootAfter
manager.validRoots.len() == credentialCount manager.validRoots.len() == credentialCount
asyncTest "register: should guard against uninitialized state": test "register: should guard against uninitialized state":
let dummyCommitment = default(IDCommitment) let dummyCommitment = default(IDCommitment)
try: try:
await manager.register( waitFor manager.register(
RateCommitment( RateCommitment(
idCommitment: dummyCommitment, userMessageLimit: UserMessageLimit(1) idCommitment: dummyCommitment, userMessageLimit: UserMessageLimit(20)
) )
) )
except CatchableError: except CatchableError:
@ -191,18 +200,18 @@ suite "Onchain group manager":
except Exception: except Exception:
assert false, "exception raised: " & getCurrentExceptionMsg() assert false, "exception raised: " & getCurrentExceptionMsg()
asyncTest "register: should register successfully": test "register: should register successfully":
# TODO :- similar to ```trackRootChanges: should fetch history correctly``` # TODO :- similar to ```trackRootChanges: should fetch history correctly```
(await manager.init()).isOkOr: (waitFor manager.init()).isOkOr:
raiseAssert $error raiseAssert $error
let idCommitment = generateCredentials(manager.rlnInstance).idCommitment let idCommitment = generateCredentials(manager.rlnInstance).idCommitment
let merkleRootBefore = manager.fetchMerkleRoot() let merkleRootBefore = manager.fetchMerkleRoot()
try: try:
await manager.register( waitFor manager.register(
RateCommitment( RateCommitment(
idCommitment: idCommitment, userMessageLimit: UserMessageLimit(1) idCommitment: idCommitment, userMessageLimit: UserMessageLimit(20)
) )
) )
except Exception, CatchableError: except Exception, CatchableError:
@ -215,47 +224,47 @@ suite "Onchain group manager":
merkleRootAfter != merkleRootBefore merkleRootAfter != merkleRootBefore
manager.latestIndex == 1 manager.latestIndex == 1
asyncTest "register: callback is called": test "register: callback is called":
let idCredentials = generateCredentials(manager.rlnInstance) let idCredentials = generateCredentials(manager.rlnInstance)
let idCommitment = idCredentials.idCommitment let idCommitment = idCredentials.idCommitment
let fut = newFuture[void]() let fut = newFuture[void]()
proc callback(registrations: seq[Membership]): Future[void] {.async.} = proc callback(registrations: seq[Membership]): Future[void] {.async.} =
let rateCommitment = getRateCommitment(idCredentials, UserMessageLimit(1)).get() let rateCommitment = getRateCommitment(idCredentials, UserMessageLimit(20)).get()
check: check:
registrations.len == 1 registrations.len == 1
registrations[0].rateCommitment == rateCommitment registrations[0].rateCommitment == rateCommitment
registrations[0].index == 0 registrations[0].index == 0
fut.complete() fut.complete()
(await manager.init()).isOkOr: (waitFor manager.init()).isOkOr:
raiseAssert $error raiseAssert $error
manager.onRegister(callback) manager.onRegister(callback)
try: try:
await manager.register( waitFor manager.register(
RateCommitment( RateCommitment(
idCommitment: idCommitment, userMessageLimit: UserMessageLimit(1) idCommitment: idCommitment, userMessageLimit: UserMessageLimit(20)
) )
) )
except Exception, CatchableError: except Exception, CatchableError:
assert false, "exception raised: " & getCurrentExceptionMsg() assert false, "exception raised: " & getCurrentExceptionMsg()
await fut waitFor fut
asyncTest "withdraw: should guard against uninitialized state": test "withdraw: should guard against uninitialized state":
let idSecretHash = generateCredentials(manager.rlnInstance).idSecretHash let idSecretHash = generateCredentials(manager.rlnInstance).idSecretHash
try: try:
await manager.withdraw(idSecretHash) waitFor manager.withdraw(idSecretHash)
except CatchableError: except CatchableError:
assert true assert true
except Exception: except Exception:
assert false, "exception raised: " & getCurrentExceptionMsg() assert false, "exception raised: " & getCurrentExceptionMsg()
asyncTest "validateRoot: should validate good root": test "validateRoot: should validate good root":
let idCredentials = generateCredentials(manager.rlnInstance) let idCredentials = generateCredentials(manager.rlnInstance)
let idCommitment = idCredentials.idCommitment let idCommitment = idCredentials.idCommitment
@ -264,27 +273,27 @@ suite "Onchain group manager":
proc callback(registrations: seq[Membership]): Future[void] {.async.} = proc callback(registrations: seq[Membership]): Future[void] {.async.} =
if registrations.len == 1 and if registrations.len == 1 and
registrations[0].rateCommitment == registrations[0].rateCommitment ==
getRateCommitment(idCredentials, UserMessageLimit(1)).get() and getRateCommitment(idCredentials, UserMessageLimit(20)).get() and
registrations[0].index == 0: registrations[0].index == 0:
manager.idCredentials = some(idCredentials) manager.idCredentials = some(idCredentials)
fut.complete() fut.complete()
manager.onRegister(callback) manager.onRegister(callback)
(await manager.init()).isOkOr: (waitFor manager.init()).isOkOr:
raiseAssert $error raiseAssert $error
try: try:
await manager.register(idCredentials, UserMessageLimit(1)) waitFor manager.register(idCredentials, UserMessageLimit(20))
except Exception, CatchableError: except Exception, CatchableError:
assert false, "exception raised: " & getCurrentExceptionMsg() assert false, "exception raised: " & getCurrentExceptionMsg()
await fut waitFor fut
let rootUpdated = await manager.updateRoots() let rootUpdated = waitFor manager.updateRoots()
if rootUpdated: if rootUpdated:
let proofResult = await manager.fetchMerkleProofElements() let proofResult = waitFor manager.fetchMerkleProofElements()
if proofResult.isErr(): if proofResult.isErr():
error "Failed to fetch Merkle proof", error = proofResult.error error "Failed to fetch Merkle proof", error = proofResult.error
manager.merkleProofCache = proofResult.get() manager.merkleProofCache = proofResult.get()
@ -306,14 +315,14 @@ suite "Onchain group manager":
check: check:
validated validated
asyncTest "validateRoot: should reject bad root": test "validateRoot: should reject bad root":
let idCredentials = generateCredentials(manager.rlnInstance) let idCredentials = generateCredentials(manager.rlnInstance)
let idCommitment = idCredentials.idCommitment let idCommitment = idCredentials.idCommitment
(await manager.init()).isOkOr: (waitFor manager.init()).isOkOr:
raiseAssert $error raiseAssert $error
manager.userMessageLimit = some(UserMessageLimit(1)) manager.userMessageLimit = some(UserMessageLimit(20))
manager.membershipIndex = some(MembershipIndex(0)) manager.membershipIndex = some(MembershipIndex(0))
manager.idCredentials = some(idCredentials) manager.idCredentials = some(idCredentials)
@ -339,9 +348,9 @@ suite "Onchain group manager":
check: check:
validated == false validated == false
asyncTest "verifyProof: should verify valid proof": test "verifyProof: should verify valid proof":
let credentials = generateCredentials(manager.rlnInstance) let credentials = generateCredentials(manager.rlnInstance)
(await manager.init()).isOkOr: (waitFor manager.init()).isOkOr:
raiseAssert $error raiseAssert $error
let fut = newFuture[void]() let fut = newFuture[void]()
@ -349,7 +358,7 @@ suite "Onchain group manager":
proc callback(registrations: seq[Membership]): Future[void] {.async.} = proc callback(registrations: seq[Membership]): Future[void] {.async.} =
if registrations.len == 1 and if registrations.len == 1 and
registrations[0].rateCommitment == registrations[0].rateCommitment ==
getRateCommitment(credentials, UserMessageLimit(1)).get() and getRateCommitment(credentials, UserMessageLimit(20)).get() and
registrations[0].index == 0: registrations[0].index == 0:
manager.idCredentials = some(credentials) manager.idCredentials = some(credentials)
fut.complete() fut.complete()
@ -357,15 +366,15 @@ suite "Onchain group manager":
manager.onRegister(callback) manager.onRegister(callback)
try: try:
await manager.register(credentials, UserMessageLimit(1)) waitFor manager.register(credentials, UserMessageLimit(20))
except Exception, CatchableError: except Exception, CatchableError:
assert false, "exception raised: " & getCurrentExceptionMsg() assert false, "exception raised: " & getCurrentExceptionMsg()
await fut waitFor fut
let rootUpdated = await manager.updateRoots() let rootUpdated = waitFor manager.updateRoots()
if rootUpdated: if rootUpdated:
let proofResult = await manager.fetchMerkleProofElements() let proofResult = waitFor manager.fetchMerkleProofElements()
if proofResult.isErr(): if proofResult.isErr():
error "Failed to fetch Merkle proof", error = proofResult.error error "Failed to fetch Merkle proof", error = proofResult.error
manager.merkleProofCache = proofResult.get() manager.merkleProofCache = proofResult.get()
@ -388,21 +397,21 @@ suite "Onchain group manager":
check: check:
verified verified
asyncTest "verifyProof: should reject invalid proof": test "verifyProof: should reject invalid proof":
(await manager.init()).isOkOr: (waitFor manager.init()).isOkOr:
raiseAssert $error raiseAssert $error
let idCredential = generateCredentials(manager.rlnInstance) let idCredential = generateCredentials(manager.rlnInstance)
try: try:
await manager.register(idCredential, UserMessageLimit(1)) waitFor manager.register(idCredential, UserMessageLimit(20))
except Exception, CatchableError: except Exception, CatchableError:
assert false, assert false,
"exception raised when calling startGroupSync: " & getCurrentExceptionMsg() "exception raised when calling startGroupSync: " & getCurrentExceptionMsg()
let messageBytes = "Hello".toBytes() let messageBytes = "Hello".toBytes()
let rootUpdated = await manager.updateRoots() let rootUpdated = waitFor manager.updateRoots()
manager.merkleProofCache = newSeq[byte](640) manager.merkleProofCache = newSeq[byte](640)
for i in 0 ..< 640: for i in 0 ..< 640:
@ -427,10 +436,10 @@ suite "Onchain group manager":
check: check:
verified == false verified == false
asyncTest "root queue should be updated correctly": test "root queue should be updated correctly":
const credentialCount = 12 const credentialCount = 12
let credentials = generateCredentials(manager.rlnInstance, credentialCount) let credentials = generateCredentials(manager.rlnInstance, credentialCount)
(await manager.init()).isOkOr: (waitFor manager.init()).isOkOr:
raiseAssert $error raiseAssert $error
type TestBackfillFuts = array[0 .. credentialCount - 1, Future[void]] type TestBackfillFuts = array[0 .. credentialCount - 1, Future[void]]
@ -445,7 +454,7 @@ suite "Onchain group manager":
proc callback(registrations: seq[Membership]): Future[void] {.async.} = proc callback(registrations: seq[Membership]): Future[void] {.async.} =
if registrations.len == 1 and if registrations.len == 1 and
registrations[0].rateCommitment == registrations[0].rateCommitment ==
getRateCommitment(credentials[futureIndex], UserMessageLimit(1)).get() and getRateCommitment(credentials[futureIndex], UserMessageLimit(20)).get() and
registrations[0].index == MembershipIndex(futureIndex): registrations[0].index == MembershipIndex(futureIndex):
futs[futureIndex].complete() futs[futureIndex].complete()
futureIndex += 1 futureIndex += 1
@ -456,47 +465,40 @@ suite "Onchain group manager":
manager.onRegister(generateCallback(futures, credentials)) manager.onRegister(generateCallback(futures, credentials))
for i in 0 ..< credentials.len(): for i in 0 ..< credentials.len():
await manager.register(credentials[i], UserMessageLimit(1)) waitFor manager.register(credentials[i], UserMessageLimit(20))
discard await manager.updateRoots() discard waitFor manager.updateRoots()
except Exception, CatchableError: except Exception, CatchableError:
assert false, "exception raised: " & getCurrentExceptionMsg() assert false, "exception raised: " & getCurrentExceptionMsg()
await allFutures(futures) waitFor allFutures(futures)
check: check:
manager.validRoots.len() == credentialCount manager.validRoots.len() == credentialCount
asyncTest "isReady should return false if ethRpc is none": test "isReady should return false if ethRpc is none":
(await manager.init()).isOkOr: (waitFor manager.init()).isOkOr:
raiseAssert $error raiseAssert $error
manager.ethRpc = none(Web3) manager.ethRpc = none(Web3)
var isReady = true var isReady = true
try: try:
isReady = await manager.isReady() isReady = waitFor manager.isReady()
except Exception, CatchableError: except Exception, CatchableError:
assert false, "exception raised: " & getCurrentExceptionMsg() assert false, "exception raised: " & getCurrentExceptionMsg()
check: check:
isReady == false isReady == false
asyncTest "isReady should return true if ethRpc is ready": test "isReady should return true if ethRpc is ready":
(await manager.init()).isOkOr: (waitFor manager.init()).isOkOr:
raiseAssert $error raiseAssert $error
var isReady = false var isReady = false
try: try:
isReady = await manager.isReady() isReady = waitFor manager.isReady()
except Exception, CatchableError: except Exception, CatchableError:
assert false, "exception raised: " & getCurrentExceptionMsg() assert false, "exception raised: " & getCurrentExceptionMsg()
check: check:
isReady == true isReady == true
################################
## Terminating/removing Anvil
################################
# We stop Anvil daemon
stopAnvil(runAnvil)

View File

@ -30,7 +30,7 @@ import
../testlib/common, ../testlib/common,
./utils ./utils
const CHAIN_ID* = 1337'u256 const CHAIN_ID* = 1234'u256
template skip0xPrefix(hexStr: string): int = template skip0xPrefix(hexStr: string): int =
## Returns the index of the first meaningful char in `hexStr` by skipping ## Returns the index of the first meaningful char in `hexStr` by skipping
@ -61,64 +61,347 @@ proc generateCredentials*(rlnInstance: ptr RLN, n: int): seq[IdentityCredential]
credentials.add(generateCredentials(rlnInstance)) credentials.add(generateCredentials(rlnInstance))
return credentials return credentials
# a util function used for testing purposes proc getContractAddressFromDeployScriptOutput(output: string): Result[string, string] =
# it deploys membership contract on Anvil (or any Eth client available on EthClient address) const searchStr = "Return ==\n0: address "
# must be edited if used for a different contract than membership contract const addressLength = 42 # Length of an Ethereum address in hex format
# <the difference between this and rln-v1 is that there is no need to deploy the poseidon hasher contract> let idx = output.find(searchStr)
proc uploadRLNContract*(ethClientAddress: string): Future[Address] {.async.} = if idx >= 0:
let web3 = await newWeb3(ethClientAddress) let startPos = idx + searchStr.len
debug "web3 connected to", ethClientAddress let endPos = output.find('\n', startPos)
if (endPos - startPos) >= addressLength:
let address = output[startPos ..< endPos]
return ok(address)
return err("Unable to find contract address in deploy script output")
# fetch the list of registered accounts proc getForgePath(): string =
let accounts = await web3.provider.eth_accounts() var forgePath = ""
web3.defaultAccount = accounts[1] if existsEnv("XDG_CONFIG_HOME"):
let add = web3.defaultAccount forgePath = joinPath(forgePath, os.getEnv("XDG_CONFIG_HOME", ""))
debug "contract deployer account address ", add else:
forgePath = joinPath(forgePath, os.getEnv("HOME", ""))
forgePath = joinPath(forgePath, ".foundry/bin/forge")
return $forgePath
let balance = contract(ERC20Token):
await web3.provider.eth_getBalance(web3.defaultAccount, blockId("latest")) proc allowance(owner: Address, spender: Address): UInt256 {.view.}
debug "Initial account balance: ", balance proc balanceOf(account: Address): UInt256 {.view.}
# deploy poseidon hasher bytecode proc getTokenBalance(
let poseidonT3Receipt = await web3.deployContract(PoseidonT3) web3: Web3, tokenAddress: Address, account: Address
let poseidonT3Address = poseidonT3Receipt.contractAddress.get() ): Future[UInt256] {.async.} =
let poseidonAddressStripped = strip0xPrefix($poseidonT3Address) let token = web3.contractSender(ERC20Token, tokenAddress)
return await token.balanceOf(account).call()
# deploy lazy imt bytecode proc ethToWei(eth: UInt256): UInt256 =
let lazyImtReceipt = await web3.deployContract( eth * 1000000000000000000.u256
LazyIMT.replace("__$PoseidonT3$__", poseidonAddressStripped)
)
let lazyImtAddress = lazyImtReceipt.contractAddress.get()
let lazyImtAddressStripped = strip0xPrefix($lazyImtAddress)
# deploy waku rlnv2 contract proc sendMintCall(
let wakuRlnContractReceipt = await web3.deployContract( web3: Web3,
WakuRlnV2Contract.replace("__$PoseidonT3$__", poseidonAddressStripped).replace( accountFrom: Address,
"__$LazyIMT$__", lazyImtAddressStripped tokenAddress: Address,
recipientAddress: Address,
amountTokens: UInt256,
recipientBalanceBeforeExpectedTokens: Option[UInt256] = none(UInt256),
): Future[TxHash] {.async.} =
let doBalanceAssert = recipientBalanceBeforeExpectedTokens.isSome()
if doBalanceAssert:
let balanceBeforeMint = await getTokenBalance(web3, tokenAddress, recipientAddress)
let balanceBeforeExpectedTokens = recipientBalanceBeforeExpectedTokens.get()
assert balanceBeforeMint == balanceBeforeExpectedTokens,
fmt"Balance is {balanceBeforeMint} before minting but expected {balanceBeforeExpectedTokens}"
# Create mint transaction
# Method ID for mint(address,uint256) is 0x40c10f19 which is part of the openzeppelin ERC20 standard
# The method ID for a deployed test token can be viewed here https://sepolia.lineascan.build/address/0x185A0015aC462a0aECb81beCc0497b649a64B9ea#writeContract
let mintSelector = "0x40c10f19"
let addressHex = recipientAddress.toHex()
# Pad the address and amount to 32 bytes each
let paddedAddress = addressHex.align(64, '0')
let amountHex = amountTokens.toHex()
let amountWithout0x =
if amountHex.toLower().startsWith("0x"):
amountHex[2 .. ^1]
else:
amountHex
let paddedAmount = amountWithout0x.align(64, '0')
let mintCallData = mintSelector & paddedAddress & paddedAmount
let gasPrice = int(await web3.provider.eth_gasPrice())
# Create the transaction
var tx: TransactionArgs
tx.`from` = Opt.some(accountFrom)
tx.to = Opt.some(tokenAddress)
tx.value = Opt.some(0.u256) # No ETH is sent for token operations
tx.gasPrice = Opt.some(Quantity(gasPrice))
tx.data = Opt.some(byteutils.hexToSeqByte(mintCallData))
trace "Sending mint call"
let txHash = await web3.send(tx)
let balanceOfSelector = "0x70a08231"
let balanceCallData = balanceOfSelector & paddedAddress
# Wait a bit for transaction to be mined
await sleepAsync(500.milliseconds)
if doBalanceAssert:
let balanceAfterMint = await getTokenBalance(web3, tokenAddress, recipientAddress)
let balanceAfterExpectedTokens =
recipientBalanceBeforeExpectedTokens.get() + amountTokens
assert balanceAfterMint == balanceAfterExpectedTokens,
fmt"Balance is {balanceAfterMint} after transfer but expected {balanceAfterExpectedTokens}"
return txHash
# Check how many tokens a spender (the RLN contract) is allowed to spend on behalf of the owner (account which wishes to register a membership)
proc checkTokenAllowance(
web3: Web3, tokenAddress: Address, owner: Address, spender: Address
): Future[UInt256] {.async.} =
let token = web3.contractSender(ERC20Token, tokenAddress)
let allowance = await token.allowance(owner, spender).call()
trace "Current allowance", owner = owner, spender = spender, allowance = allowance
return allowance
proc setupContractDeployment(
forgePath: string, submodulePath: string
): Result[void, string] =
trace "Contract deployer paths", forgePath = forgePath, submodulePath = submodulePath
# Build the Foundry project
try:
let (forgeCleanOutput, forgeCleanExitCode) =
execCmdEx(fmt"""cd {submodulePath} && {forgePath} clean""")
trace "Executed forge clean command", output = forgeCleanOutput
if forgeCleanExitCode != 0:
return err("forge clean command failed")
let (forgeInstallOutput, forgeInstallExitCode) =
execCmdEx(fmt"""cd {submodulePath} && {forgePath} install""")
trace "Executed forge install command", output = forgeInstallOutput
if forgeInstallExitCode != 0:
return err("forge install command failed")
let (pnpmInstallOutput, pnpmInstallExitCode) =
execCmdEx(fmt"""cd {submodulePath} && pnpm install""")
trace "Executed pnpm install command", output = pnpmInstallOutput
if pnpmInstallExitCode != 0:
return err("pnpm install command failed" & pnpmInstallOutput)
let (forgeBuildOutput, forgeBuildExitCode) =
execCmdEx(fmt"""cd {submodulePath} && {forgePath} build""")
trace "Executed forge build command", output = forgeBuildOutput
if forgeBuildExitCode != 0:
return err("forge build command failed")
# Set the environment variable API keys to anything for local testnet deployment
putEnv("API_KEY_CARDONA", "123")
putEnv("API_KEY_LINEASCAN", "123")
putEnv("API_KEY_ETHERSCAN", "123")
except OSError, IOError:
return err("Command execution failed: " & getCurrentExceptionMsg())
return ok()
proc deployTestToken*(
pk: keys.PrivateKey, acc: Address, web3: Web3
): Future[Result[Address, string]] {.async.} =
## Executes a Foundry forge script that deploys the a token contract (ERC-20) used for testing. This is a prerequisite to enable the contract deployment and this token contract address needs to be minted and approved for the accounts that need to register memberships with the contract
## submodulePath: path to the submodule containing contract deploy scripts
# All RLN related tests should be run from the root directory of the project
let submodulePath = absolutePath("./vendor/waku-rlnv2-contract")
# Verify submodule path exists
if not dirExists(submodulePath):
error "Submodule path does not exist", submodulePath = submodulePath
return err("Submodule path does not exist: " & submodulePath)
let forgePath = getForgePath()
setupContractDeployment(forgePath, submodulePath).isOkOr:
error "Failed to setup contract deployment", error = $error
return err("Failed to setup contract deployment: " & $error)
# Deploy TestToken contract
let forgeCmdTestToken =
fmt"""cd {submodulePath} && {forgePath} script test/TestToken.sol --broadcast -vvv --rpc-url http://localhost:8540 --tc TestTokenFactory --private-key {pk} && rm -rf broadcast/*/*/run-1*.json && rm -rf cache/*/*/run-1*.json"""
let (outputDeployTestToken, exitCodeDeployTestToken) = execCmdEx(forgeCmdTestToken)
trace "Executed forge command to deploy TestToken contract",
output = outputDeployTestToken
if exitCodeDeployTestToken != 0:
return error("Forge command to deploy TestToken contract failed")
# Parse the command output to find contract address
let testTokenAddress = getContractAddressFromDeployScriptOutput(outputDeployTestToken).valueOr:
error "Failed to get TestToken contract address from deploy script output",
error = $error
return err(
"Failed to get TestToken contract address from deploy script output: " & $error
) )
) debug "Address of the TestToken contract", testTokenAddress
let wakuRlnContractAddress = wakuRlnContractReceipt.contractAddress.get()
let wakuRlnAddressStripped = strip0xPrefix($wakuRlnContractAddress)
debug "Address of the deployed rlnv2 contract: ", wakuRlnContractAddress let testTokenAddressBytes = hexToByteArray[20](testTokenAddress)
let testTokenAddressAddress = Address(testTokenAddressBytes)
putEnv("TOKEN_ADDRESS", testTokenAddressAddress.toHex())
# need to send concat: impl & init_bytes return ok(testTokenAddressAddress)
let contractInput =
byteutils.toHex(encode(wakuRlnContractAddress)) & Erc1967ProxyContractInput
debug "contractInput", contractInput
let proxyReceipt =
await web3.deployContract(Erc1967Proxy, contractInput = contractInput)
debug "proxy receipt", contractAddress = proxyReceipt.contractAddress.get() # Sends an ERC20 token approval call to allow a spender to spend a certain amount of tokens on behalf of the owner
let proxyAddress = proxyReceipt.contractAddress.get() proc approveTokenAllowanceAndVerify*(
web3: Web3,
accountFrom: Address,
privateKey: keys.PrivateKey,
tokenAddress: Address,
spender: Address,
amountWei: UInt256,
expectedAllowanceBefore: Option[UInt256] = none(UInt256),
): Future[Result[TxHash, string]] {.async.} =
var allowanceBefore: UInt256
if expectedAllowanceBefore.isSome():
allowanceBefore =
await checkTokenAllowance(web3, tokenAddress, accountFrom, spender)
let expected = expectedAllowanceBefore.get()
if allowanceBefore != expected:
return
err(fmt"Allowance is {allowanceBefore} before approval but expected {expected}")
let newBalance = await web3.provider.eth_getBalance(web3.defaultAccount, "latest") # Temporarily set the private key
debug "Account balance after the contract deployment: ", newBalance let oldPrivateKey = web3.privateKey
web3.privateKey = Opt.some(privateKey)
web3.lastKnownNonce = Opt.none(Quantity)
try:
# ERC20 approve function signature: approve(address spender, uint256 amount)
# Method ID for approve(address,uint256) is 0x095ea7b3
const APPROVE_SELECTOR = "0x095ea7b3"
let addressHex = spender.toHex().align(64, '0')
let amountHex = amountWei.toHex().align(64, '0')
let approveCallData = APPROVE_SELECTOR & addressHex & amountHex
let gasPrice = await web3.provider.eth_gasPrice()
var tx: TransactionArgs
tx.`from` = Opt.some(accountFrom)
tx.to = Opt.some(tokenAddress)
tx.value = Opt.some(0.u256)
tx.gasPrice = Opt.some(gasPrice)
tx.gas = Opt.some(Quantity(100000))
tx.data = Opt.some(byteutils.hexToSeqByte(approveCallData))
tx.chainId = Opt.some(CHAIN_ID)
trace "Sending approve call", tx = tx
let txHash = await web3.send(tx)
let receipt = await web3.getMinedTransactionReceipt(txHash)
if receipt.status.isNone():
return err("Approval transaction failed receipt is none")
if receipt.status.get() != 1.Quantity:
return err("Approval transaction failed status quantity not 1")
# Single verification check after mining (no extra sleep needed)
let allowanceAfter =
await checkTokenAllowance(web3, tokenAddress, accountFrom, spender)
let expectedAfter =
if expectedAllowanceBefore.isSome():
expectedAllowanceBefore.get() + amountWei
else:
amountWei
if allowanceAfter < expectedAfter:
return err(
fmt"Allowance is {allowanceAfter} after approval but expected at least {expectedAfter}"
)
return ok(txHash)
except CatchableError as e:
return err(fmt"Failed to send approve transaction: {e.msg}")
finally:
# Restore the old private key
web3.privateKey = oldPrivateKey
proc executeForgeContractDeployScripts*(
privateKey: keys.PrivateKey, acc: Address, web3: Web3
): Future[Result[Address, string]] {.async, gcsafe.} =
## Executes a set of foundry forge scripts required to deploy the RLN contract and returns the deployed proxy contract address
## submodulePath: path to the submodule containing contract deploy scripts
# All RLN related tests should be run from the root directory of the project
let submodulePath = "./vendor/waku-rlnv2-contract"
# Verify submodule path exists
if not dirExists(submodulePath):
error "Submodule path does not exist", submodulePath = submodulePath
return err("Submodule path does not exist: " & submodulePath)
let forgePath = getForgePath()
debug "Forge path", forgePath
# Verify forge executable exists
if not fileExists(forgePath):
error "Forge executable not found", forgePath = forgePath
return err("Forge executable not found: " & forgePath)
trace "contract deployer account details", account = acc, privateKey = privateKey
let setupContractEnv = setupContractDeployment(forgePath, submodulePath)
if setupContractEnv.isErr():
error "Failed to setup contract deployment"
return err("Failed to setup contract deployment")
# Deploy LinearPriceCalculator contract
let forgeCmdPriceCalculator =
fmt"""cd {submodulePath} && {forgePath} script script/Deploy.s.sol --broadcast -vvvv --rpc-url http://localhost:8540 --tc DeployPriceCalculator --private-key {privateKey} && rm -rf broadcast/*/*/run-1*.json && rm -rf cache/*/*/run-1*.json"""
let (outputDeployPriceCalculator, exitCodeDeployPriceCalculator) =
execCmdEx(forgeCmdPriceCalculator)
trace "Executed forge command to deploy LinearPriceCalculator contract",
output = outputDeployPriceCalculator
if exitCodeDeployPriceCalculator != 0:
return error("Forge command to deploy LinearPriceCalculator contract failed")
# Parse the output to find contract address
let priceCalculatorAddressRes =
getContractAddressFromDeployScriptOutput(outputDeployPriceCalculator)
if priceCalculatorAddressRes.isErr():
error "Failed to get LinearPriceCalculator contract address from deploy script output"
let priceCalculatorAddress = priceCalculatorAddressRes.get()
debug "Address of the LinearPriceCalculator contract", priceCalculatorAddress
putEnv("PRICE_CALCULATOR_ADDRESS", priceCalculatorAddress)
let forgeCmdWakuRln =
fmt"""cd {submodulePath} && {forgePath} script script/Deploy.s.sol --broadcast -vvvv --rpc-url http://localhost:8540 --tc DeployWakuRlnV2 --private-key {privateKey} && rm -rf broadcast/*/*/run-1*.json && rm -rf cache/*/*/run-1*.json"""
let (outputDeployWakuRln, exitCodeDeployWakuRln) = execCmdEx(forgeCmdWakuRln)
trace "Executed forge command to deploy WakuRlnV2 contract",
output = outputDeployWakuRln
if exitCodeDeployWakuRln != 0:
error "Forge command to deploy WakuRlnV2 contract failed",
output = outputDeployWakuRln
# Parse the output to find contract address
let wakuRlnV2AddressRes =
getContractAddressFromDeployScriptOutput(outputDeployWakuRln)
if wakuRlnV2AddressRes.isErr():
error "Failed to get WakuRlnV2 contract address from deploy script output"
##TODO: raise exception here?
let wakuRlnV2Address = wakuRlnV2AddressRes.get()
debug "Address of the WakuRlnV2 contract", wakuRlnV2Address
putEnv("WAKURLNV2_ADDRESS", wakuRlnV2Address)
# Deploy Proxy contract
let forgeCmdProxy =
fmt"""cd {submodulePath} && {forgePath} script script/Deploy.s.sol --broadcast -vvvv --rpc-url http://localhost:8540 --tc DeployProxy --private-key {privateKey} && rm -rf broadcast/*/*/run-1*.json && rm -rf cache/*/*/run-1*.json"""
let (outputDeployProxy, exitCodeDeployProxy) = execCmdEx(forgeCmdProxy)
trace "Executed forge command to deploy proxy contract", output = outputDeployProxy
if exitCodeDeployProxy != 0:
error "Forge command to deploy Proxy failed", error = outputDeployProxy
return err("Forge command to deploy Proxy failed")
let proxyAddress = getContractAddressFromDeployScriptOutput(outputDeployProxy)
let proxyAddressBytes = hexToByteArray[20](proxyAddress.get())
let proxyAddressAddress = Address(proxyAddressBytes)
info "Address of the Proxy contract", proxyAddressAddress
await web3.close() await web3.close()
debug "disconnected from ", ethClientAddress return ok(proxyAddressAddress)
return proxyAddress
proc sendEthTransfer*( proc sendEthTransfer*(
web3: Web3, web3: Web3,
@ -133,7 +416,7 @@ proc sendEthTransfer*(
let balanceBeforeWei = await web3.provider.eth_getBalance(accountTo, "latest") let balanceBeforeWei = await web3.provider.eth_getBalance(accountTo, "latest")
let balanceBeforeExpectedWei = accountToBalanceBeforeExpectedWei.get() let balanceBeforeExpectedWei = accountToBalanceBeforeExpectedWei.get()
assert balanceBeforeWei == balanceBeforeExpectedWei, assert balanceBeforeWei == balanceBeforeExpectedWei,
fmt"Balance is {balanceBeforeWei} but expected {balanceBeforeExpectedWei}" fmt"Balance is {balanceBeforeWei} before transfer but expected {balanceBeforeExpectedWei}"
let gasPrice = int(await web3.provider.eth_gasPrice()) let gasPrice = int(await web3.provider.eth_gasPrice())
@ -146,17 +429,17 @@ proc sendEthTransfer*(
# TODO: handle the error if sending fails # TODO: handle the error if sending fails
let txHash = await web3.send(tx) let txHash = await web3.send(tx)
# Wait a bit for transaction to be mined
await sleepAsync(200.milliseconds)
if doBalanceAssert: if doBalanceAssert:
let balanceAfterWei = await web3.provider.eth_getBalance(accountTo, "latest") let balanceAfterWei = await web3.provider.eth_getBalance(accountTo, "latest")
let balanceAfterExpectedWei = accountToBalanceBeforeExpectedWei.get() + amountWei let balanceAfterExpectedWei = accountToBalanceBeforeExpectedWei.get() + amountWei
assert balanceAfterWei == balanceAfterExpectedWei, assert balanceAfterWei == balanceAfterExpectedWei,
fmt"Balance is {balanceAfterWei} but expected {balanceAfterExpectedWei}" fmt"Balance is {balanceAfterWei} after transfer but expected {balanceAfterExpectedWei}"
return txHash return txHash
proc ethToWei(eth: UInt256): UInt256 =
eth * 1000000000000000000.u256
proc createEthAccount*( proc createEthAccount*(
ethAmount: UInt256 = 1000.u256 ethAmount: UInt256 = 1000.u256
): Future[(keys.PrivateKey, Address)] {.async.} = ): Future[(keys.PrivateKey, Address)] {.async.} =
@ -198,7 +481,7 @@ proc getAnvilPath*(): string =
return $anvilPath return $anvilPath
# Runs Anvil daemon # Runs Anvil daemon
proc runAnvil*(port: int = 8540, chainId: string = "1337"): Process = proc runAnvil*(port: int = 8540, chainId: string = "1234"): Process =
# Passed options are # Passed options are
# --port Port to listen on. # --port Port to listen on.
# --gas-limit Sets the block gas limit in WEI. # --gas-limit Sets the block gas limit in WEI.
@ -212,13 +495,13 @@ proc runAnvil*(port: int = 8540, chainId: string = "1337"): Process =
anvilPath, anvilPath,
args = [ args = [
"--port", "--port",
"8540", $port,
"--gas-limit", "--gas-limit",
"300000000000000", "300000000000000",
"--balance", "--balance",
"1000000000", "1000000000",
"--chain-id", "--chain-id",
$CHAIN_ID, $chainId,
], ],
options = {poUsePath}, options = {poUsePath},
) )
@ -242,14 +525,26 @@ proc runAnvil*(port: int = 8540, chainId: string = "1337"): Process =
# Stops Anvil daemon # Stops Anvil daemon
proc stopAnvil*(runAnvil: Process) {.used.} = proc stopAnvil*(runAnvil: Process) {.used.} =
if runAnvil.isNil:
debug "stopAnvil called with nil Process"
return
let anvilPID = runAnvil.processID let anvilPID = runAnvil.processID
# We wait the daemon to exit debug "Stopping Anvil daemon", anvilPID = anvilPID
try: try:
# We terminate Anvil daemon by sending a SIGTERM signal to the runAnvil PID to trigger RPC server termination and clean-up # Send termination signals
kill(runAnvil) when not defined(windows):
debug "Sent SIGTERM to Anvil", anvilPID = anvilPID discard execCmdEx(fmt"kill -TERM {anvilPID}")
except: discard execCmdEx(fmt"kill -9 {anvilPID}")
error "Anvil daemon termination failed: ", err = getCurrentExceptionMsg() else:
discard execCmdEx(fmt"taskkill /F /PID {anvilPID}")
# Close Process object to release resources
close(runAnvil)
debug "Anvil daemon stopped", anvilPID = anvilPID
except Exception as e:
debug "Error stopping Anvil daemon", anvilPID = anvilPID, error = e.msg
proc setupOnchainGroupManager*( proc setupOnchainGroupManager*(
ethClientUrl: string = EthClient, amountEth: UInt256 = 10.u256 ethClientUrl: string = EthClient, amountEth: UInt256 = 10.u256
@ -261,12 +556,10 @@ proc setupOnchainGroupManager*(
let rlnInstance = rlnInstanceRes.get() let rlnInstance = rlnInstanceRes.get()
let contractAddress = await uploadRLNContract(ethClientUrl)
# connect to the eth client # connect to the eth client
let web3 = await newWeb3(ethClientUrl) let web3 = await newWeb3(ethClientUrl)
let accounts = await web3.provider.eth_accounts() let accounts = await web3.provider.eth_accounts()
web3.defaultAccount = accounts[0] web3.defaultAccount = accounts[1]
let (privateKey, acc) = createEthAccount(web3) let (privateKey, acc) = createEthAccount(web3)
@ -276,6 +569,32 @@ proc setupOnchainGroupManager*(
web3, web3.defaultAccount, acc, ethToWei(1000.u256), some(0.u256) web3, web3.defaultAccount, acc, ethToWei(1000.u256), some(0.u256)
) )
let testTokenAddress = (await deployTestToken(privateKey, acc, web3)).valueOr:
assert false, "Failed to deploy test token contract: " & $error
return
# mint the token from the generated account
discard await sendMintCall(
web3, web3.defaultAccount, testTokenAddress, acc, ethToWei(1000.u256), some(0.u256)
)
let contractAddress = (await executeForgeContractDeployScripts(privateKey, acc, web3)).valueOr:
assert false, "Failed to deploy RLN contract: " & $error
return
# If the generated account wishes to register a membership, it needs to approve the contract to spend its tokens
let tokenApprovalResult = await approveTokenAllowanceAndVerify(
web3,
acc, # owner
privateKey,
testTokenAddress, # ERC20 token address
contractAddress, # spender - the proxy contract that will spend the tokens
ethToWei(200.u256),
some(0.u256), # expected allowance before approval
)
assert tokenApprovalResult.isOk, tokenApprovalResult.error()
let manager = OnchainGroupManager( let manager = OnchainGroupManager(
ethClientUrls: @[ethClientUrl], ethClientUrls: @[ethClientUrl],
ethContractAddress: $contractAddress, ethContractAddress: $contractAddress,

View File

@ -209,7 +209,7 @@ suite "Waku Sync reconciliation":
baseHash = hashLocal baseHash = hashLocal
alteredHash = toDigest("msg" & $i & "_x") alteredHash = toDigest("msg" & $i & "_x")
hashRemote = alteredHash hashRemote = alteredHash
remote.insert(SyncID(time: ts, hash: hashRemote)).isOkOr: remote.insert(SyncID(time: ts, hash: hashRemote)).isOkOr:
assert false, "failed to insert hash: " & $error assert false, "failed to insert hash: " & $error

@ -1 +1 @@
Subproject commit a576a8949ca20e310f2fbb4ec0bd05a57ac3045f Subproject commit b7e9a9b1bc69256a2a3076c1f099b50ce84e7eff

View File

@ -30,12 +30,12 @@ type ClusterConf* = object
# Cluster configuration corresponding to The Waku Network. Note that it # Cluster configuration corresponding to The Waku Network. Note that it
# overrides existing cli configuration # overrides existing cli configuration
proc TheWakuNetworkConf*(T: type ClusterConf): ClusterConf = proc TheWakuNetworkConf*(T: type ClusterConf): ClusterConf =
const RelayChainId = 11155111'u256 const RelayChainId = 59141'u256
return ClusterConf( return ClusterConf(
maxMessageSize: "150KiB", maxMessageSize: "150KiB",
clusterId: 1, clusterId: 1,
rlnRelay: true, rlnRelay: true,
rlnRelayEthContractAddress: "0xfe7a9eabcE779a090FD702346Fd0bFAc02ce6Ac8", rlnRelayEthContractAddress: "0xB9cd878C90E49F797B4431fBF4fb333108CB90e6",
rlnRelayDynamic: true, rlnRelayDynamic: true,
rlnRelayChainId: RelayChainId, rlnRelayChainId: RelayChainId,
rlnEpochSizeSec: 600, rlnEpochSizeSec: 600,

View File

@ -30,23 +30,29 @@ logScope:
# using the when predicate does not work within the contract macro, hence need to dupe # using the when predicate does not work within the contract macro, hence need to dupe
contract(WakuRlnContract): contract(WakuRlnContract):
# this serves as an entrypoint into the rln membership set # this serves as an entrypoint into the rln membership set
proc register(idCommitment: UInt256, userMessageLimit: UInt32) proc register(
idCommitment: UInt256, userMessageLimit: UInt32, idCommitmentsToErase: seq[UInt256]
)
# Initializes the implementation contract (only used in unit tests) # Initializes the implementation contract (only used in unit tests)
proc initialize(maxMessageLimit: UInt256) proc initialize(maxMessageLimit: UInt256)
# this event is raised when a new member is registered # this event is emitted when a new member is registered
proc MemberRegistered(rateCommitment: UInt256, index: UInt32) {.event.} proc MembershipRegistered(
idCommitment: UInt256, membershipRateLimit: UInt256, index: UInt32
) {.event.}
# this function denotes existence of a given user # this function denotes existence of a given user
proc memberExists(idCommitment: UInt256): UInt256 {.view.} proc isInMembershipSet(idCommitment: Uint256): bool {.view.}
# this constant describes the next index of a new member # this constant describes the next index of a new member
proc commitmentIndex(): UInt256 {.view.} proc nextFreeIndex(): UInt256 {.view.}
# this constant describes the block number this contract was deployed on # this constant describes the block number this contract was deployed on
proc deployedBlockNumber(): UInt256 {.view.} proc deployedBlockNumber(): UInt256 {.view.}
# this constant describes max message limit of rln contract # this constant describes max message limit of rln contract
proc MAX_MESSAGE_LIMIT(): UInt256 {.view.} proc maxMembershipRateLimit(): UInt256 {.view.}
# this function returns the merkleProof for a given index # this function returns the merkleProof for a given index
# proc merkleProofElements(index: UInt40): seq[byte] {.view.} # proc getMerkleProof(index: EthereumUInt40): seq[array[32, byte]] {.view.}
# this function returns the merkle root # this function returns the Merkle root
proc root(): UInt256 {.view.} proc root(): Uint256 {.view.}
type type
WakuRlnContractWithSender = Sender[WakuRlnContract] WakuRlnContractWithSender = Sender[WakuRlnContract]
@ -67,11 +73,7 @@ type
proc setMetadata*( proc setMetadata*(
g: OnchainGroupManager, lastProcessedBlock = none(BlockNumber) g: OnchainGroupManager, lastProcessedBlock = none(BlockNumber)
): GroupManagerResult[void] = ): GroupManagerResult[void] =
let normalizedBlock = let normalizedBlock = lastProcessedBlock.get(g.latestProcessedBlock)
if lastProcessedBlock.isSome():
lastProcessedBlock.get()
else:
g.latestProcessedBlock
try: try:
let metadataSetRes = g.rlnInstance.setMetadata( let metadataSetRes = g.rlnInstance.setMetadata(
RlnMetadata( RlnMetadata(
@ -87,14 +89,68 @@ proc setMetadata*(
return err("failed to persist rln metadata: " & getCurrentExceptionMsg()) return err("failed to persist rln metadata: " & getCurrentExceptionMsg())
return ok() return ok()
proc sendEthCallWithChainId(
ethRpc: Web3,
functionSignature: string,
fromAddress: Address,
toAddress: Address,
chainId: UInt256,
): Future[Result[UInt256, string]] {.async.} =
## Workaround for web3 chainId=null issue on some networks (e.g., linea-sepolia)
## Makes contract calls with explicit chainId for view functions with no parameters
let functionHash =
keccak256.digest(functionSignature.toOpenArrayByte(0, functionSignature.len - 1))
let functionSelector = functionHash.data[0 .. 3]
let dataSignature = "0x" & functionSelector.mapIt(it.toHex(2)).join("")
var tx: TransactionArgs
tx.`from` = Opt.some(fromAddress)
tx.to = Opt.some(toAddress)
tx.value = Opt.some(0.u256)
tx.data = Opt.some(byteutils.hexToSeqByte(dataSignature))
tx.chainId = Opt.some(chainId)
let resultBytes = await ethRpc.provider.eth_call(tx, "latest")
if resultBytes.len == 0:
return err("No result returned for function call: " & functionSignature)
return ok(UInt256.fromBytesBE(resultBytes))
proc sendEthCallWithParams(
ethRpc: Web3,
functionSignature: string,
params: seq[byte],
fromAddress: Address,
toAddress: Address,
chainId: UInt256,
): Future[Result[seq[byte], string]] {.async.} =
## Workaround for web3 chainId=null issue with parameterized contract calls
let functionHash =
keccak256.digest(functionSignature.toOpenArrayByte(0, functionSignature.len - 1))
let functionSelector = functionHash.data[0 .. 3]
let callData = functionSelector & params
var tx: TransactionArgs
tx.`from` = Opt.some(fromAddress)
tx.to = Opt.some(toAddress)
tx.value = Opt.some(0.u256)
tx.data = Opt.some(callData)
tx.chainId = Opt.some(chainId)
let resultBytes = await ethRpc.provider.eth_call(tx, "latest")
return ok(resultBytes)
proc fetchMerkleProofElements*( proc fetchMerkleProofElements*(
g: OnchainGroupManager g: OnchainGroupManager
): Future[Result[seq[byte], string]] {.async.} = ): Future[Result[seq[byte], string]] {.async.} =
try: try:
# let merkleRootInvocation = g.wakuRlnContract.get().root()
# let merkleRoot = await merkleRootInvocation.call()
# The above code is not working with the latest web3 version due to chainId being null (specifically on linea-sepolia)
# TODO: find better solution than this custom sendEthCallWithChainId call
let membershipIndex = g.membershipIndex.get() let membershipIndex = g.membershipIndex.get()
let index40 = stuint(membershipIndex, 40) let index40 = stuint(membershipIndex, 40)
let methodSig = "merkleProofElements(uint40)" let methodSig = "getMerkleProof(uint40)"
let methodIdDigest = keccak.keccak256.digest(methodSig) let methodIdDigest = keccak.keccak256.digest(methodSig)
let methodId = methodIdDigest.data[0 .. 3] let methodId = methodIdDigest.data[0 .. 3]
@ -111,6 +167,7 @@ proc fetchMerkleProofElements*(
var tx: TransactionArgs var tx: TransactionArgs
tx.to = Opt.some(fromHex(Address, g.ethContractAddress)) tx.to = Opt.some(fromHex(Address, g.ethContractAddress))
tx.data = Opt.some(callData) tx.data = Opt.some(callData)
tx.chainId = Opt.some(g.chainId) # Explicitly set the chain ID
let responseBytes = await g.ethRpc.get().provider.eth_call(tx, "latest") let responseBytes = await g.ethRpc.get().provider.eth_call(tx, "latest")
@ -123,8 +180,17 @@ proc fetchMerkleRoot*(
g: OnchainGroupManager g: OnchainGroupManager
): Future[Result[UInt256, string]] {.async.} = ): Future[Result[UInt256, string]] {.async.} =
try: try:
let merkleRootInvocation = g.wakuRlnContract.get().root() let merkleRoot = (
let merkleRoot = await merkleRootInvocation.call() await sendEthCallWithChainId(
ethRpc = g.ethRpc.get(),
functionSignature = "root()",
fromAddress = g.ethRpc.get().defaultAccount,
toAddress = fromHex(Address, g.ethContractAddress),
chainId = g.chainId,
)
).valueOr:
error "Failed to fetch Merkle root", error = $error
return err("Failed to fetch merkle root: " & $error)
return ok(merkleRoot) return ok(merkleRoot)
except CatchableError: except CatchableError:
error "Failed to fetch Merkle root", error = getCurrentExceptionMsg() error "Failed to fetch Merkle root", error = getCurrentExceptionMsg()
@ -151,6 +217,7 @@ proc updateRoots*(g: OnchainGroupManager): Future[bool] {.async.} =
return false return false
let merkleRoot = UInt256ToField(rootRes.get()) let merkleRoot = UInt256ToField(rootRes.get())
if g.validRoots.len == 0: if g.validRoots.len == 0:
g.validRoots.addLast(merkleRoot) g.validRoots.addLast(merkleRoot)
return true return true
@ -183,8 +250,26 @@ proc trackRootChanges*(g: OnchainGroupManager) {.async: (raises: [CatchableError
error "Failed to fetch Merkle proof", error = proofResult.error error "Failed to fetch Merkle proof", error = proofResult.error
g.merkleProofCache = proofResult.get() g.merkleProofCache = proofResult.get()
# also need update registerd membership # also need to update registered membership
let memberCount = cast[int64](await wakuRlnContract.commitmentIndex().call()) # g.rlnRelayMaxMessageLimit =
# cast[uint64](await wakuRlnContract.nextFreeIndex().call())
# The above code is not working with the latest web3 version due to chainId being null (specifically on linea-sepolia)
# TODO: find better solution than this custom sendEthCallWithChainId call
let nextFreeIndex = await sendEthCallWithChainId(
ethRpc = ethRpc,
functionSignature = "nextFreeIndex()",
fromAddress = ethRpc.defaultAccount,
toAddress = fromHex(Address, g.ethContractAddress),
chainId = g.chainId,
)
if nextFreeIndex.isErr():
error "Failed to fetch next free index", error = nextFreeIndex.error
raise newException(
CatchableError, "Failed to fetch next free index: " & nextFreeIndex.error
)
let memberCount = cast[int64](nextFreeIndex.get())
waku_rln_number_registered_memberships.set(float64(memberCount)) waku_rln_number_registered_memberships.set(float64(memberCount))
await sleepAsync(rpcDelay) await sleepAsync(rpcDelay)
@ -219,15 +304,19 @@ method register*(
var gasPrice: int var gasPrice: int
g.retryWrapper(gasPrice, "Failed to get gas price"): g.retryWrapper(gasPrice, "Failed to get gas price"):
int(await ethRpc.provider.eth_gasPrice()) * 2 int(await ethRpc.provider.eth_gasPrice()) * 2
let idCommitmentHex = identityCredential.idCommitment.inHex()
debug "identityCredential idCommitmentHex", idCommitment = idCommitmentHex
let idCommitment = identityCredential.idCommitment.toUInt256() let idCommitment = identityCredential.idCommitment.toUInt256()
let idCommitmentsToErase: seq[UInt256] = @[]
debug "registering the member", debug "registering the member",
idCommitment = idCommitment, userMessageLimit = userMessageLimit idCommitment = idCommitment,
userMessageLimit = userMessageLimit,
idCommitmentsToErase = idCommitmentsToErase
var txHash: TxHash var txHash: TxHash
g.retryWrapper(txHash, "Failed to register the member"): g.retryWrapper(txHash, "Failed to register the member"):
await wakuRlnContract.register(idCommitment, userMessageLimit.stuint(32)).send( await wakuRlnContract
gasPrice = gasPrice .register(idCommitment, userMessageLimit.stuint(32), idCommitmentsToErase)
) .send(gasPrice = gasPrice)
# wait for the transaction to be mined # wait for the transaction to be mined
var tsReceipt: ReceiptObject var tsReceipt: ReceiptObject
@ -240,27 +329,29 @@ method register*(
debug "ts receipt", receipt = tsReceipt[] debug "ts receipt", receipt = tsReceipt[]
if tsReceipt.status.isNone(): if tsReceipt.status.isNone():
raise newException(ValueError, "register: transaction failed status is None") raise newException(ValueError, "Transaction failed: status is None")
if tsReceipt.status.get() != 1.Quantity: if tsReceipt.status.get() != 1.Quantity:
raise newException( raise newException(
ValueError, "register: transaction failed status is: " & $tsReceipt.status.get() ValueError, "Transaction failed with status: " & $tsReceipt.status.get()
) )
let firstTopic = tsReceipt.logs[0].topics[0] ## Extract MembershipRegistered event from transaction logs (third event)
# the hash of the signature of MemberRegistered(uint256,uint32) event is equal to the following hex value let thirdTopic = tsReceipt.logs[2].topics[0]
if firstTopic != debug "third topic", thirdTopic = thirdTopic
cast[FixedBytes[32]](keccak.keccak256.digest("MemberRegistered(uint256,uint32)").data): if thirdTopic !=
cast[FixedBytes[32]](keccak.keccak256.digest(
"MembershipRegistered(uint256,uint256,uint32)"
).data):
raise newException(ValueError, "register: unexpected event signature") raise newException(ValueError, "register: unexpected event signature")
# the arguments of the raised event i.e., MemberRegistered are encoded inside the data field ## Parse MembershipRegistered event data: rateCommitment(256) || membershipRateLimit(256) || index(32)
# data = rateCommitment encoded as 256 bits || index encoded as 32 bits let arguments = tsReceipt.logs[2].data
let arguments = tsReceipt.logs[0].data
debug "tx log data", arguments = arguments debug "tx log data", arguments = arguments
let let
# In TX log data, uints are encoded in big endian ## Extract membership index from transaction log data (big endian)
membershipIndex = UInt256.fromBytesBE(arguments[32 ..^ 1]) membershipIndex = UInt256.fromBytesBE(arguments[64 .. 95])
debug "parsed membershipIndex", membershipIndex trace "parsed membershipIndex", membershipIndex
g.userMessageLimit = some(userMessageLimit) g.userMessageLimit = some(userMessageLimit)
g.membershipIndex = some(membershipIndex.toMembershipIndex()) g.membershipIndex = some(membershipIndex.toMembershipIndex())
g.idCredentials = some(identityCredential) g.idCredentials = some(identityCredential)
@ -376,7 +467,7 @@ method generateProof*(
var proofValue = cast[ptr array[320, byte]](output_witness_buffer.`ptr`) var proofValue = cast[ptr array[320, byte]](output_witness_buffer.`ptr`)
let proofBytes: array[320, byte] = proofValue[] let proofBytes: array[320, byte] = proofValue[]
## parse the proof as [ proof<128> | root<32> | external_nullifier<32> | share_x<32> | share_y<32> | nullifier<32> ] ## Parse the proof as [ proof<128> | root<32> | external_nullifier<32> | share_x<32> | share_y<32> | nullifier<32> ]
let let
proofOffset = 128 proofOffset = 128
rootOffset = proofOffset + 32 rootOffset = proofOffset + 32
@ -418,9 +509,7 @@ method generateProof*(
return ok(output) return ok(output)
method verifyProof*( method verifyProof*(
g: OnchainGroupManager, # verifier context g: OnchainGroupManager, input: seq[byte], proof: RateLimitProof
input: seq[byte], # raw message data (signal)
proof: RateLimitProof, # proof received from the peer
): GroupManagerResult[bool] {.gcsafe, raises: [].} = ): GroupManagerResult[bool] {.gcsafe, raises: [].} =
## -- Verifies an RLN rate-limit proof against the set of valid Merkle roots -- ## -- Verifies an RLN rate-limit proof against the set of valid Merkle roots --
@ -543,11 +632,31 @@ method init*(g: OnchainGroupManager): Future[GroupManagerResult[void]] {.async.}
g.membershipIndex = some(keystoreCred.treeIndex) g.membershipIndex = some(keystoreCred.treeIndex)
g.userMessageLimit = some(keystoreCred.userMessageLimit) g.userMessageLimit = some(keystoreCred.userMessageLimit)
# now we check on the contract if the commitment actually has a membership # now we check on the contract if the commitment actually has a membership
let idCommitmentBytes = keystoreCred.identityCredential.idCommitment
let idCommitmentUInt256 = keystoreCred.identityCredential.idCommitment.toUInt256()
let idCommitmentHex = idCommitmentBytes.inHex()
debug "Keystore idCommitment in bytes", idCommitmentBytes = idCommitmentBytes
debug "Keystore idCommitment in UInt256 ", idCommitmentUInt256 = idCommitmentUInt256
debug "Keystore idCommitment in hex ", idCommitmentHex = idCommitmentHex
let idCommitment = idCommitmentUInt256
try: try:
let membershipExists = await wakuRlnContract let commitmentBytes = keystoreCred.identityCredential.idCommitment
.memberExists(keystoreCred.identityCredential.idCommitment.toUInt256()) let params = commitmentBytes.reversed()
.call() let resultBytes = await sendEthCallWithParams(
if membershipExists == 0: ethRpc = g.ethRpc.get(),
functionSignature = "isInMembershipSet(uint256)",
params = params,
fromAddress = ethRpc.defaultAccount,
toAddress = contractAddress,
chainId = g.chainId,
)
if resultBytes.isErr():
return err("Failed to check membership: " & resultBytes.error)
let responseBytes = resultBytes.get()
let membershipExists = responseBytes.len == 32 and responseBytes[^1] == 1'u8
debug "membershipExists", membershipExists = membershipExists
if membershipExists == false:
return err("the commitment does not have a membership") return err("the commitment does not have a membership")
except CatchableError: except CatchableError:
return err("failed to check if the commitment has a membership") return err("failed to check if the commitment has a membership")
@ -564,8 +673,18 @@ method init*(g: OnchainGroupManager): Future[GroupManagerResult[void]] {.async.}
if metadata.contractAddress != g.ethContractAddress.toLower(): if metadata.contractAddress != g.ethContractAddress.toLower():
return err("persisted data: contract address mismatch") return err("persisted data: contract address mismatch")
g.rlnRelayMaxMessageLimit = let maxMembershipRateLimit = (
cast[uint64](await wakuRlnContract.MAX_MESSAGE_LIMIT().call()) await sendEthCallWithChainId(
ethRpc = ethRpc,
functionSignature = "maxMembershipRateLimit()",
fromAddress = ethRpc.defaultAccount,
toAddress = contractAddress,
chainId = g.chainId,
)
).valueOr:
return err("Failed to fetch max membership rate limit: " & $error)
g.rlnRelayMaxMessageLimit = cast[uint64](maxMembershipRateLimit)
proc onDisconnect() {.async.} = proc onDisconnect() {.async.} =
error "Ethereum client disconnected" error "Ethereum client disconnected"