mirror of
https://github.com/logos-messaging/logos-messaging-nim.git
synced 2026-01-05 23:43:07 +00:00
fix: streamline contract api (#3528)
This commit is contained in:
parent
1b73bdb056
commit
c826f51213
@ -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):
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
101
waku/waku_rln_relay/group_manager/on_chain/rpc_wrapper.nim
Normal file
101
waku/waku_rln_relay/group_manager/on_chain/rpc_wrapper.nim
Normal 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)
|
||||
Loading…
x
Reference in New Issue
Block a user