mirror of
https://github.com/logos-messaging/logos-messaging-rlnv2-contract.git
synced 2026-01-02 14:03:07 +00:00
* test: register invalid * test: multiple registers * fix: increase max rejects * test: erasure with fullErase idCommitments * fix: reduce cyclomatic complexity * fix: reduce complexity one step less * fix: run tests in parallel * fix: undo run tests in parallel - default already * test: invalid extension with extreme values * fix: line length * test: set MaxTotalRateLimit * test: set ActiveDuration * test: Merkle inserts * test: Merkle erasures * test: GetRateCommitmentsRange * test: GetMerkleProof * fix: optimized MerkleInsert MerkleErasures * fix: update gas snapshot * test: malicious upgrade drains funds * fix: formatting * test: show success when unauthorized upgrade after malicious * test: offchain proof post lazy erase - multi-user erase reuse race * fix: line length * fix: remove offchain lazy erase test - rate limit still applies * fix: remove fuzz tests from CI run * fix: formatting * fix: formatting coverage * test: timestamp manipulation * fix: rename tests * test: front running for registration * fix: unused variables * test: register during spam conditions * fix: delete failing tests - test_MaliciousUpgradeDrainsFunds - testFrontrunning_RegistrationRevertsForVictim - testFrontrunning_SetFillingSpam * fix: delete MaliciousImplementation * fix: formatting with a new Foundry version * test: testEraseAndReuse with Echidna * fix: remove limit check * fix: remove test_MultiUserEraseReuseRace - test_TimestampManipulationRaces * fix: skip Echidna contract during forge test * test: Echidna contract with invariants - registerMembership - attemptExtensionRace - attemptErasureRace * fix: tune config file * fix: run and cleanup scripts for echidna * test: Echidna test replay * fix: Solidity version * fix: test_attemptExtensionRace_WakuRLN * fix: invalid commitment in test_attemptExtensionRace_WakuRLN * fix: invalid commitments in test_attemptErasureRace_WakuRLN * fix: line length * fix: skip all Echidna tests in CI * test: register invalid * test: multiple registers * fix: increase max rejects * test: erasure with fullErase idCommitments * fix: reduce cyclomatic complexity * fix: reduce complexity one step less * test: invalid extension with extreme values * fix: line length * test: set MaxTotalRateLimit * test: set ActiveDuration * test: Merkle inserts * test: Merkle erasures * test: GetRateCommitmentsRange * test: GetMerkleProof * fix: optimized MerkleInsert MerkleErasures * fix: update gas snapshot * fix: formatting * fix: remove tests with high overlap * fix: remove all tests originally meant for fuzzing * fix: rename merged Echidna tests * fix: formatting * test: fuzzing for essential invariants * test: EchidnaTest contract * fix: remove unnecessary imports * fix: remove unnecessary helpers * fix: remove bounds from invariants * fix: change test mode to property * fix: update run script * fix: max_test_rejects back to the original value * fix: remove unused local variables
172 lines
6.2 KiB
Solidity
172 lines
6.2 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity 0.8.24;
|
|
|
|
import "../src/WakuRlnV2.sol";
|
|
import "../src/LinearPriceCalculator.sol";
|
|
import "../src/Membership.sol";
|
|
import "../test/TestStableToken.sol";
|
|
import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
|
|
|
|
contract EchidnaTest {
|
|
WakuRlnV2 public w;
|
|
TestStableToken public token;
|
|
LinearPriceCalculator public priceCalculator;
|
|
|
|
uint32 public constant MAX_TOTAL_RATELIMIT_PER_EPOCH = 160_000;
|
|
uint32 public constant MIN_RATELIMIT_PER_MEMBERSHIP = 20;
|
|
uint32 public constant MAX_RATELIMIT_PER_MEMBERSHIP = 600;
|
|
uint32 public constant ACTIVE_DURATION = 180 days;
|
|
uint32 public constant GRACE_PERIOD_DURATION = 30 days;
|
|
uint256 public constant MAX_SUPPLY = 1_000_000 * 10 ** 18;
|
|
|
|
uint256[] public activeIdCommitments;
|
|
mapping(uint32 => uint256) public indexToId;
|
|
mapping(uint32 => uint32) public indexToRate;
|
|
mapping(uint256 => uint32) public idToExpectedActiveDuration;
|
|
|
|
constructor() {
|
|
// Deploy TestStableToken via proxy
|
|
address tokenImpl = address(new TestStableToken());
|
|
bytes memory tokenInitData = abi.encodeCall(TestStableToken.initialize, (MAX_SUPPLY));
|
|
ERC1967Proxy tokenProxy = new ERC1967Proxy(tokenImpl, tokenInitData);
|
|
token = TestStableToken(address(tokenProxy));
|
|
|
|
// Deploy LinearPriceCalculator
|
|
priceCalculator = new LinearPriceCalculator(address(token), 0.05 ether);
|
|
|
|
// Deploy WakuRlnV2 via proxy
|
|
address wImpl = address(new WakuRlnV2());
|
|
bytes memory wInitData = abi.encodeCall(
|
|
WakuRlnV2.initialize,
|
|
(
|
|
address(priceCalculator),
|
|
MAX_TOTAL_RATELIMIT_PER_EPOCH,
|
|
MIN_RATELIMIT_PER_MEMBERSHIP,
|
|
MAX_RATELIMIT_PER_MEMBERSHIP,
|
|
ACTIVE_DURATION,
|
|
GRACE_PERIOD_DURATION
|
|
)
|
|
);
|
|
ERC1967Proxy wProxy = new ERC1967Proxy(wImpl, wInitData);
|
|
w = WakuRlnV2(address(wProxy));
|
|
|
|
// Mint and approve tokens
|
|
token.mint(address(this), 1_000_000 ether);
|
|
token.approve(address(w), type(uint256).max);
|
|
}
|
|
|
|
// Helper for proof verification
|
|
function _verifyMerkleProof(
|
|
uint256[20] memory proof,
|
|
uint256 root,
|
|
uint32 index,
|
|
uint256 leaf,
|
|
uint8 depth
|
|
)
|
|
internal
|
|
pure
|
|
returns (bool)
|
|
{
|
|
uint256 current = leaf;
|
|
uint32 idx = index;
|
|
for (uint8 level = 0; level < depth; level++) {
|
|
bool isLeft = (idx & 1) == 0;
|
|
uint256 sibling = proof[level];
|
|
uint256[2] memory inputs;
|
|
if (isLeft) {
|
|
inputs[0] = current;
|
|
inputs[1] = sibling;
|
|
} else {
|
|
inputs[0] = sibling;
|
|
inputs[1] = current;
|
|
}
|
|
current = PoseidonT3.hash(inputs);
|
|
idx >>= 1;
|
|
}
|
|
return current == root;
|
|
}
|
|
|
|
// Invariants
|
|
|
|
function echidna_rate_commitments_range_correct() public view returns (bool) {
|
|
uint32 nextFree = w.nextFreeIndex();
|
|
if (nextFree == 0) return true;
|
|
uint32 startIndex = 0;
|
|
uint32 endIndex = nextFree - 1;
|
|
uint256[] memory commitments = w.getRateCommitmentsInRangeBoundsInclusive(startIndex, endIndex);
|
|
if (commitments.length != uint256(endIndex - startIndex + 1)) {
|
|
return false;
|
|
}
|
|
for (uint32 j = startIndex; j <= endIndex; j++) {
|
|
if (indexToRate[j] != 0) {
|
|
uint256 exp = PoseidonT3.hash([indexToId[j], uint256(indexToRate[j])]);
|
|
if (commitments[j - startIndex] != exp) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function echidna_merkle_proof_valid() public view returns (bool) {
|
|
uint32 nextFree = w.nextFreeIndex();
|
|
if (nextFree == 0) return true;
|
|
for (uint32 index = 0; index < nextFree; index++) {
|
|
uint256[20] memory proof = w.getMerkleProof(index);
|
|
uint256 root = w.root();
|
|
uint256 expectedCommitment = w.getRateCommitmentsInRangeBoundsInclusive(index, index)[0];
|
|
if (!_verifyMerkleProof(proof, root, index, expectedCommitment, 20)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function echidna_total_rate_limit_correct() public view returns (bool) {
|
|
uint256 computedTotal = 0;
|
|
for (uint256 i = 0; i < activeIdCommitments.length; i++) {
|
|
(,,,, uint32 rateLimitMem,,,) = w.memberships(activeIdCommitments[i]);
|
|
computedTotal += rateLimitMem;
|
|
}
|
|
return w.currentTotalRateLimit() == computedTotal;
|
|
}
|
|
|
|
function echidna_max_total_rate_limit_valid() public view returns (bool) {
|
|
uint32 maxTotal = w.maxTotalRateLimit();
|
|
uint256 currentTotal = w.currentTotalRateLimit();
|
|
uint32 maxMembership = w.maxMembershipRateLimit();
|
|
return maxTotal >= currentTotal && maxTotal >= maxMembership;
|
|
}
|
|
|
|
function echidna_merkle_inserts_integrity() public view returns (bool) {
|
|
uint32 nextFree = w.nextFreeIndex();
|
|
if (nextFree == 0) return true;
|
|
for (uint32 index = 0; index < nextFree; index++) {
|
|
uint256 commitment = w.getRateCommitmentsInRangeBoundsInclusive(index, index)[0];
|
|
if (indexToRate[index] != 0) {
|
|
uint256 exp = PoseidonT3.hash([indexToId[index], uint256(indexToRate[index])]);
|
|
if (commitment != exp) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function echidna_merkle_erasures_integrity() public view returns (bool) {
|
|
uint32 nextFree = w.nextFreeIndex();
|
|
if (nextFree == 0) return true;
|
|
for (uint32 index = 0; index < nextFree; index++) {
|
|
uint256 commitment = w.getRateCommitmentsInRangeBoundsInclusive(index, index)[0];
|
|
if (indexToRate[index] == 0) {
|
|
continue; // Erased, skip
|
|
}
|
|
uint256[20] memory proof = w.getMerkleProof(index);
|
|
if (!_verifyMerkleProof(proof, w.root(), index, commitment, 20)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
}
|