fix: streamline contract api (#3528)

This commit is contained in:
Darshan K 2025-08-01 15:23:47 +05:30 committed by GitHub
parent 1b73bdb056
commit c826f51213
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 302 additions and 216 deletions

View File

@ -262,56 +262,111 @@ suite "Waku external config - Shards":
suite "Waku external config - http url parsing":
test "Basic HTTP URLs without authentication":
check string(parseCmdArg(EthRpcUrl, "https://example.com/path")) == "https://example.com/path"
check string(parseCmdArg(EthRpcUrl, "https://example.com/")) == "https://example.com/"
check string(parseCmdArg(EthRpcUrl, "http://localhost:8545")) == "http://localhost:8545"
check string(parseCmdArg(EthRpcUrl, "https://mainnet.infura.io")) == "https://mainnet.infura.io"
check string(parseCmdArg(EthRpcUrl, "https://example.com/path")) ==
"https://example.com/path"
check string(parseCmdArg(EthRpcUrl, "https://example.com/")) ==
"https://example.com/"
check string(parseCmdArg(EthRpcUrl, "http://localhost:8545")) ==
"http://localhost:8545"
check string(parseCmdArg(EthRpcUrl, "https://mainnet.infura.io")) ==
"https://mainnet.infura.io"
test "Basic authentication with simple credentials":
check string(parseCmdArg(EthRpcUrl, "https://user:pass@example.com/path")) == "https://user:pass@example.com/path"
check string(parseCmdArg(EthRpcUrl, "https://john.doe:secret123@example.com/api/v1")) == "https://john.doe:secret123@example.com/api/v1"
check string(parseCmdArg(EthRpcUrl, "https://user_name:pass_word@example.com/")) == "https://user_name:pass_word@example.com/"
check string(parseCmdArg(EthRpcUrl, "https://user-name:pass-word@example.com/")) == "https://user-name:pass-word@example.com/"
check string(parseCmdArg(EthRpcUrl, "https://user123:pass456@example.com/")) == "https://user123:pass456@example.com/"
check string(parseCmdArg(EthRpcUrl, "https://user:pass@example.com/path")) ==
"https://user:pass@example.com/path"
check string(
parseCmdArg(EthRpcUrl, "https://john.doe:secret123@example.com/api/v1")
) == "https://john.doe:secret123@example.com/api/v1"
check string(parseCmdArg(EthRpcUrl, "https://user_name:pass_word@example.com/")) ==
"https://user_name:pass_word@example.com/"
check string(parseCmdArg(EthRpcUrl, "https://user-name:pass-word@example.com/")) ==
"https://user-name:pass-word@example.com/"
check string(parseCmdArg(EthRpcUrl, "https://user123:pass456@example.com/")) ==
"https://user123:pass456@example.com/"
test "Special characters (percent-encoded) in credentials":
check string(parseCmdArg(EthRpcUrl, "https://user%40email:pass%21%23%24@example.com/")) == "https://user%40email:pass%21%23%24@example.com/"
check string(parseCmdArg(EthRpcUrl, "https://user%2Bplus:pass%26and@example.com/")) == "https://user%2Bplus:pass%26and@example.com/"
check string(parseCmdArg(EthRpcUrl, "https://user%3Acolon:pass%3Bsemi@example.com/")) == "https://user%3Acolon:pass%3Bsemi@example.com/"
check string(parseCmdArg(EthRpcUrl, "https://user%2Fslash:pass%3Fquest@example.com/")) == "https://user%2Fslash:pass%3Fquest@example.com/"
check string(parseCmdArg(EthRpcUrl, "https://user%5Bbracket:pass%5Dbracket@example.com/")) == "https://user%5Bbracket:pass%5Dbracket@example.com/"
check string(parseCmdArg(EthRpcUrl, "https://user%20space:pass%20space@example.com/")) == "https://user%20space:pass%20space@example.com/"
check string(parseCmdArg(EthRpcUrl, "https://user%3Cless:pass%3Egreater@example.com/")) == "https://user%3Cless:pass%3Egreater@example.com/"
check string(parseCmdArg(EthRpcUrl, "https://user%7Bbrace:pass%7Dbrace@example.com/")) == "https://user%7Bbrace:pass%7Dbrace@example.com/"
check string(parseCmdArg(EthRpcUrl, "https://user%5Cback:pass%7Cpipe@example.com/")) == "https://user%5Cback:pass%7Cpipe@example.com/"
check string(
parseCmdArg(EthRpcUrl, "https://user%40email:pass%21%23%24@example.com/")
) == "https://user%40email:pass%21%23%24@example.com/"
check string(parseCmdArg(EthRpcUrl, "https://user%2Bplus:pass%26and@example.com/")) ==
"https://user%2Bplus:pass%26and@example.com/"
check string(
parseCmdArg(EthRpcUrl, "https://user%3Acolon:pass%3Bsemi@example.com/")
) == "https://user%3Acolon:pass%3Bsemi@example.com/"
check string(
parseCmdArg(EthRpcUrl, "https://user%2Fslash:pass%3Fquest@example.com/")
) == "https://user%2Fslash:pass%3Fquest@example.com/"
check string(
parseCmdArg(EthRpcUrl, "https://user%5Bbracket:pass%5Dbracket@example.com/")
) == "https://user%5Bbracket:pass%5Dbracket@example.com/"
check string(
parseCmdArg(EthRpcUrl, "https://user%20space:pass%20space@example.com/")
) == "https://user%20space:pass%20space@example.com/"
check string(
parseCmdArg(EthRpcUrl, "https://user%3Cless:pass%3Egreater@example.com/")
) == "https://user%3Cless:pass%3Egreater@example.com/"
check string(
parseCmdArg(EthRpcUrl, "https://user%7Bbrace:pass%7Dbrace@example.com/")
) == "https://user%7Bbrace:pass%7Dbrace@example.com/"
check string(parseCmdArg(EthRpcUrl, "https://user%5Cback:pass%7Cpipe@example.com/")) ==
"https://user%5Cback:pass%7Cpipe@example.com/"
test "Complex passwords with special characters":
check string(parseCmdArg(EthRpcUrl, "https://admin:P%40ssw0rd%21%23%24%25%5E%26*()@example.com/")) == "https://admin:P%40ssw0rd%21%23%24%25%5E%26*()@example.com/"
check string(parseCmdArg(EthRpcUrl, "https://user:abc123%21%40%23DEF456@example.com/")) == "https://user:abc123%21%40%23DEF456@example.com/"
check string(parseCmdArg(EthRpcUrl, "https://user:P%40%24%24w0rd%21%23%24%25%5E%26%2A%28%29_%2B-%3D%5B%5D%7B%7D%7C%3B%27%3A%22%2C.%2F%3C%3E%3F%60~%5C@example.com")) == "https://user:P%40%24%24w0rd%21%23%24%25%5E%26%2A%28%29_%2B-%3D%5B%5D%7B%7D%7C%3B%27%3A%22%2C.%2F%3C%3E%3F%60~%5C@example.com"
check string(
parseCmdArg(
EthRpcUrl, "https://admin:P%40ssw0rd%21%23%24%25%5E%26*()@example.com/"
)
) == "https://admin:P%40ssw0rd%21%23%24%25%5E%26*()@example.com/"
check string(
parseCmdArg(EthRpcUrl, "https://user:abc123%21%40%23DEF456@example.com/")
) == "https://user:abc123%21%40%23DEF456@example.com/"
check string(
parseCmdArg(
EthRpcUrl,
"https://user:P%40%24%24w0rd%21%23%24%25%5E%26%2A%28%29_%2B-%3D%5B%5D%7B%7D%7C%3B%27%3A%22%2C.%2F%3C%3E%3F%60~%5C@example.com",
)
) ==
"https://user:P%40%24%24w0rd%21%23%24%25%5E%26%2A%28%29_%2B-%3D%5B%5D%7B%7D%7C%3B%27%3A%22%2C.%2F%3C%3E%3F%60~%5C@example.com"
test "Different hostname types":
check string(parseCmdArg(EthRpcUrl, "https://user:pass@subdomain.example.com/path")) == "https://user:pass@subdomain.example.com/path"
check string(parseCmdArg(EthRpcUrl, "https://user:pass@192.168.1.1/admin")) == "https://user:pass@192.168.1.1/admin"
check string(parseCmdArg(EthRpcUrl, "https://user:pass@[2001:db8::1]/path")) == "https://user:pass@[2001:db8::1]/path"
check string(parseCmdArg(EthRpcUrl, "https://user:pass@example.co.uk/path")) == "https://user:pass@example.co.uk/path"
check string(parseCmdArg(EthRpcUrl, "https://user:pass@subdomain.example.com/path")) ==
"https://user:pass@subdomain.example.com/path"
check string(parseCmdArg(EthRpcUrl, "https://user:pass@192.168.1.1/admin")) ==
"https://user:pass@192.168.1.1/admin"
check string(parseCmdArg(EthRpcUrl, "https://user:pass@[2001:db8::1]/path")) ==
"https://user:pass@[2001:db8::1]/path"
check string(parseCmdArg(EthRpcUrl, "https://user:pass@example.co.uk/path")) ==
"https://user:pass@example.co.uk/path"
test "URLs with port numbers":
check string(parseCmdArg(EthRpcUrl, "https://user:pass@example.com:8080/path")) == "https://user:pass@example.com:8080/path"
check string(parseCmdArg(EthRpcUrl, "https://user:pass@example.com:443/")) == "https://user:pass@example.com:443/"
check string(parseCmdArg(EthRpcUrl, "http://user:pass@example.com:80/path")) == "http://user:pass@example.com:80/path"
check string(parseCmdArg(EthRpcUrl, "https://user:pass@example.com:8080/path")) ==
"https://user:pass@example.com:8080/path"
check string(parseCmdArg(EthRpcUrl, "https://user:pass@example.com:443/")) ==
"https://user:pass@example.com:443/"
check string(parseCmdArg(EthRpcUrl, "http://user:pass@example.com:80/path")) ==
"http://user:pass@example.com:80/path"
test "URLs with query parameters and fragments":
check string(parseCmdArg(EthRpcUrl, "https://user:pass@example.com/path?query=1#section")) == "https://user:pass@example.com/path?query=1#section"
check string(parseCmdArg(EthRpcUrl, "https://user:pass@example.com/?foo=bar&baz=qux")) == "https://user:pass@example.com/?foo=bar&baz=qux"
check string(parseCmdArg(EthRpcUrl, "https://api.example.com/rpc?key=value")) == "https://api.example.com/rpc?key=value"
check string(parseCmdArg(EthRpcUrl, "https://api.example.com/rpc#section")) == "https://api.example.com/rpc#section"
check string(
parseCmdArg(EthRpcUrl, "https://user:pass@example.com/path?query=1#section")
) == "https://user:pass@example.com/path?query=1#section"
check string(
parseCmdArg(EthRpcUrl, "https://user:pass@example.com/?foo=bar&baz=qux")
) == "https://user:pass@example.com/?foo=bar&baz=qux"
check string(parseCmdArg(EthRpcUrl, "https://api.example.com/rpc?key=value")) ==
"https://api.example.com/rpc?key=value"
check string(parseCmdArg(EthRpcUrl, "https://api.example.com/rpc#section")) ==
"https://api.example.com/rpc#section"
test "Edge cases with credentials":
check string(parseCmdArg(EthRpcUrl, "https://a:b@example.com/")) == "https://a:b@example.com/"
check string(parseCmdArg(EthRpcUrl, "https://user:@example.com/")) == "https://user:@example.com/"
check string(parseCmdArg(EthRpcUrl, "https://:pass@example.com/")) == "https://:pass@example.com/"
check string(parseCmdArg(EthRpcUrl, "http://user:pass@example.com/")) == "http://user:pass@example.com/"
check string(parseCmdArg(EthRpcUrl, "https://a:b@example.com/")) ==
"https://a:b@example.com/"
check string(parseCmdArg(EthRpcUrl, "https://user:@example.com/")) ==
"https://user:@example.com/"
check string(parseCmdArg(EthRpcUrl, "https://:pass@example.com/")) ==
"https://:pass@example.com/"
check string(parseCmdArg(EthRpcUrl, "http://user:pass@example.com/")) ==
"http://user:pass@example.com/"
test "Websocket URLs are rejected":
expect(ValueError):

View File

@ -20,40 +20,14 @@ import
../../rln/rln_interface,
../../conversion_utils,
../group_manager_base,
./retry_wrapper
./retry_wrapper,
./rpc_wrapper
export group_manager_base
logScope:
topics = "waku rln_relay onchain_group_manager"
# using the when predicate does not work within the contract macro, hence need to dupe
contract(WakuRlnContract):
# this serves as an entrypoint into the rln membership set
proc register(
idCommitment: UInt256, userMessageLimit: UInt32, idCommitmentsToErase: seq[UInt256]
)
# Initializes the implementation contract (only used in unit tests)
proc initialize(maxMessageLimit: UInt256)
# this event is emitted when a new member is registered
proc MembershipRegistered(
idCommitment: UInt256, membershipRateLimit: UInt256, index: UInt32
) {.event.}
# this function denotes existence of a given user
proc isInMembershipSet(idCommitment: Uint256): bool {.view.}
# this constant describes the next index of a new member
proc nextFreeIndex(): UInt256 {.view.}
# this constant describes the block number this contract was deployed on
proc deployedBlockNumber(): UInt256 {.view.}
# this constant describes max message limit of rln contract
proc maxMembershipRateLimit(): UInt256 {.view.}
# this function returns the merkleProof for a given index
# proc getMerkleProof(index: EthereumUInt40): seq[array[32, byte]] {.view.}
# this function returns the Merkle root
proc root(): Uint256 {.view.}
type
WakuRlnContractWithSender = Sender[WakuRlnContract]
OnchainGroupManager* = ref object of GroupManager
@ -70,6 +44,106 @@ type
latestProcessedBlock*: BlockNumber
merkleProofCache*: seq[byte]
# The below 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 sendEthCallWithoutParams call
proc fetchMerkleProofElements*(
g: OnchainGroupManager
): Future[Result[seq[byte], string]] {.async.} =
try:
let membershipIndex = g.membershipIndex.get()
let index40 = stuint(membershipIndex, 40)
let methodSig = "getMerkleProof(uint40)"
var paddedParam = newSeq[byte](32)
let indexBytes = index40.toBytesBE()
for i in 0 ..< min(indexBytes.len, paddedParam.len):
paddedParam[paddedParam.len - indexBytes.len + i] = indexBytes[i]
let response = await sendEthCallWithParams(
ethRpc = g.ethRpc.get(),
functionSignature = methodSig,
params = paddedParam,
fromAddress = g.ethRpc.get().defaultAccount,
toAddress = fromHex(Address, g.ethContractAddress),
chainId = g.chainId,
)
return response
except CatchableError:
error "Failed to fetch Merkle proof elements", error = getCurrentExceptionMsg()
return err("Failed to fetch merkle proof elements: " & getCurrentExceptionMsg())
proc fetchMerkleRoot*(
g: OnchainGroupManager
): Future[Result[UInt256, string]] {.async.} =
try:
let merkleRoot = await sendEthCallWithoutParams(
ethRpc = g.ethRpc.get(),
functionSignature = "root()",
fromAddress = g.ethRpc.get().defaultAccount,
toAddress = fromHex(Address, g.ethContractAddress),
chainId = g.chainId,
)
return merkleRoot
except CatchableError:
error "Failed to fetch Merkle root", error = getCurrentExceptionMsg()
return err("Failed to fetch merkle root: " & getCurrentExceptionMsg())
proc fetchNextFreeIndex*(
g: OnchainGroupManager
): Future[Result[UInt256, string]] {.async.} =
try:
let nextFreeIndex = await sendEthCallWithoutParams(
ethRpc = g.ethRpc.get(),
functionSignature = "nextFreeIndex()",
fromAddress = g.ethRpc.get().defaultAccount,
toAddress = fromHex(Address, g.ethContractAddress),
chainId = g.chainId,
)
return nextFreeIndex
except CatchableError:
error "Failed to fetch next free index", error = getCurrentExceptionMsg()
return err("Failed to fetch next free index: " & getCurrentExceptionMsg())
proc fetchMembershipStatus*(
g: OnchainGroupManager, idCommitment: IDCommitment
): Future[Result[bool, string]] {.async.} =
try:
let params = idCommitment.reversed()
let resultBytes = await sendEthCallWithParams(
ethRpc = g.ethRpc.get(),
functionSignature = "isInMembershipSet(uint256)",
params = params,
fromAddress = g.ethRpc.get().defaultAccount,
toAddress = fromHex(Address, g.ethContractAddress),
chainId = g.chainId,
)
if resultBytes.isErr():
return err("Failed to check membership: " & resultBytes.error)
let responseBytes = resultBytes.get()
return ok(responseBytes.len == 32 and responseBytes[^1] == 1'u8)
except CatchableError:
error "Failed to fetch membership set membership", error = getCurrentExceptionMsg()
return err("Failed to fetch membership set membership: " & getCurrentExceptionMsg())
proc fetchMaxMembershipRateLimit*(
g: OnchainGroupManager
): Future[Result[UInt256, string]] {.async.} =
try:
let maxMembershipRateLimit = await sendEthCallWithoutParams(
ethRpc = g.ethRpc.get(),
functionSignature = "maxMembershipRateLimit()",
fromAddress = g.ethRpc.get().defaultAccount,
toAddress = fromHex(Address, g.ethContractAddress),
chainId = g.chainId,
)
return maxMembershipRateLimit
except CatchableError:
error "Failed to fetch max membership rate limit", error = getCurrentExceptionMsg()
return err("Failed to fetch max membership rate limit: " & getCurrentExceptionMsg())
proc setMetadata*(
g: OnchainGroupManager, lastProcessedBlock = none(BlockNumber)
): GroupManagerResult[void] =
@ -89,113 +163,6 @@ proc setMetadata*(
return err("failed to persist rln metadata: " & getCurrentExceptionMsg())
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*(
g: OnchainGroupManager
): Future[Result[seq[byte], string]] {.async.} =
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 index40 = stuint(membershipIndex, 40)
let methodSig = "getMerkleProof(uint40)"
let methodIdDigest = keccak.keccak256.digest(methodSig)
let methodId = methodIdDigest.data[0 .. 3]
var paddedParam = newSeq[byte](32)
let indexBytes = index40.toBytesBE()
for i in 0 ..< min(indexBytes.len, paddedParam.len):
paddedParam[paddedParam.len - indexBytes.len + i] = indexBytes[i]
var callData = newSeq[byte]()
for b in methodId:
callData.add(b)
callData.add(paddedParam)
var tx: TransactionArgs
tx.to = Opt.some(fromHex(Address, g.ethContractAddress))
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")
return ok(responseBytes)
except CatchableError:
error "Failed to fetch Merkle proof elements", error = getCurrentExceptionMsg()
return err("Failed to fetch merkle proof elements: " & getCurrentExceptionMsg())
proc fetchMerkleRoot*(
g: OnchainGroupManager
): Future[Result[UInt256, string]] {.async.} =
try:
let merkleRoot = (
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)
except CatchableError:
error "Failed to fetch Merkle root", error = getCurrentExceptionMsg()
return err("Failed to fetch merkle root: " & getCurrentExceptionMsg())
template initializedGuard(g: OnchainGroupManager): untyped =
if not g.initialized:
raise newException(CatchableError, "OnchainGroupManager is not initialized")
@ -250,19 +217,7 @@ proc trackRootChanges*(g: OnchainGroupManager) {.async: (raises: [CatchableError
error "Failed to fetch Merkle proof", error = proofResult.error
g.merkleProofCache = proofResult.get()
# also need to update registered membership
# 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,
)
let nextFreeIndex = await g.fetchNextFreeIndex()
if nextFreeIndex.isErr():
error "Failed to fetch next free index", error = nextFreeIndex.error
raise newException(
@ -638,28 +593,10 @@ method init*(g: OnchainGroupManager): Future[GroupManagerResult[void]] {.async.}
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:
let commitmentBytes = keystoreCred.identityCredential.idCommitment
let params = commitmentBytes.reversed()
let resultBytes = await sendEthCallWithParams(
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")
except CatchableError:
return err("failed to check if the commitment has a membership")
let idCommitment = keystoreCred.identityCredential.idCommitment
let membershipExists = (await g.fetchMembershipStatus(idCommitment)).valueOr:
return err("the commitment does not have a membership: " & error)
debug "membershipExists", membershipExists = membershipExists
g.idCredentials = some(keystoreCred.identityCredential)
@ -673,16 +610,9 @@ method init*(g: OnchainGroupManager): Future[GroupManagerResult[void]] {.async.}
if metadata.contractAddress != g.ethContractAddress.toLower():
return err("persisted data: contract address mismatch")
let maxMembershipRateLimit = (
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)
let maxMembershipRateLimitRes = await g.fetchMaxMembershipRateLimit()
let maxMembershipRateLimit = maxMembershipRateLimitRes.valueOr:
return err("failed to fetch max membership rate limit: " & error)
g.rlnRelayMaxMessageLimit = cast[uint64](maxMembershipRateLimit)

View File

@ -0,0 +1,101 @@
import
os,
web3,
web3/eth_api_types,
web3/primitives,
eth/keys as keys,
chronicles,
nimcrypto/keccak as keccak,
stint,
json,
std/[strutils, tables, algorithm],
stew/[byteutils, arrayops],
sequtils
import
../../../waku_keystore,
../../rln,
../../rln/rln_interface,
../../conversion_utils,
../../protocol_types,
../group_manager_base
logScope:
topics = "waku rln_relay onchain rpc_wrapper"
# using the when predicate does not work within the contract macro, hence need to dupe
contract(WakuRlnContract):
# this serves as an entrypoint into the rln membership set
proc register(
idCommitment: UInt256, userMessageLimit: UInt32, idCommitmentsToErase: seq[UInt256]
)
# this event is emitted when a new member is registered
proc MembershipRegistered(
idCommitment: UInt256, membershipRateLimit: UInt256, index: UInt32
) {.event.}
# Initializes the implementation contract (only used in unit tests)
proc initialize(maxMessageLimit: UInt256)
# this function denotes existence of a given user
proc isInMembershipSet(idCommitment: Uint256): bool {.view.}
# this constant describes the next index of a new member
proc nextFreeIndex(): UInt256 {.view.}
# this constant describes the block number this contract was deployed on
proc deployedBlockNumber(): UInt256 {.view.}
# this constant describes max message limit of rln contract
proc maxMembershipRateLimit(): UInt256 {.view.}
# this function returns the merkleProof for a given index
proc getMerkleProof(index: UInt256): seq[byte] {.view.}
# this function returns the Merkle root
proc root(): Uint256 {.view.}
proc sendEthCallWithoutParams*(
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)