mirror of
https://github.com/logos-messaging/logos-messaging-rlnv2-contract.git
synced 2026-01-02 14:03:07 +00:00
* 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 * 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 * chore: fuzz test expansion (#40) * 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 * 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 * 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: remove test_MultiUserEraseReuseRace - test_TimestampManipulationRaces * 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: cleanup after rebase * fix: remove redundant file * fix: formatting * fix: formatting * fix: adorno + archive EchidnaReplayRaces.t.sol * test: focus on erasures with timestamps * fix: remove isolated test * test: Echidna tests for races - add dynamic assertions before operation - untrack erased IDs * fix: remove unused replay test
146 lines
5.8 KiB
Solidity
146 lines
5.8 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity 0.8.24;
|
|
|
|
import "../src/LinearPriceCalculator.sol";
|
|
import "../src/WakuRlnV2.sol";
|
|
import "../src/Membership.sol"; // Added import for MembershipUpgradeable
|
|
import "./TestStableToken.sol";
|
|
import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
|
|
|
|
// Echidna invariants and assertions for WakuRlnV2 multi-user timestamp manipulation races
|
|
contract EchidnaTestRaces {
|
|
WakuRlnV2 internal w;
|
|
TestStableToken internal token;
|
|
address internal tokenOwner = address(this);
|
|
|
|
// Storage for multi-user registrations (to track registered IDs)
|
|
uint256[] internal registeredIds;
|
|
|
|
constructor() {
|
|
address tokenImpl = address(new TestStableToken());
|
|
bytes memory tokenInitData = abi.encodeCall(TestStableToken.initialize, (1_000_000 * 10 ** 18));
|
|
address tokenProxyAddr = address(new ERC1967Proxy(tokenImpl, tokenInitData));
|
|
token = TestStableToken(tokenProxyAddr);
|
|
|
|
LinearPriceCalculator priceCalculator = new LinearPriceCalculator(address(token), 1e18 / 20); // Example
|
|
|
|
address impl = address(new WakuRlnV2());
|
|
bytes memory initData =
|
|
abi.encodeCall(WakuRlnV2.initialize, (address(priceCalculator), 160_000, 20, 600, 15_552_000, 2_592_000));
|
|
address proxyAddr = address(new ERC1967Proxy(impl, initData));
|
|
w = WakuRlnV2(proxyAddr);
|
|
}
|
|
|
|
// Function to register a single membership; Echidna can call this multiple times with time advances between
|
|
function registerMembership(uint256 idCommitment, uint32 rateLimit) public {
|
|
(, uint256 price) = w.priceCalculator().calculate(rateLimit);
|
|
token.mint(address(this), price);
|
|
token.approve(address(w), price);
|
|
w.register(idCommitment, rateLimit, new uint256[](0));
|
|
|
|
// Add to list (only if successful; reverts otherwise)
|
|
registeredIds.push(idCommitment);
|
|
}
|
|
|
|
// Function to attempt extension on a random registered membership and assert based on current time
|
|
function attemptExtensionRace(uint256 index) public {
|
|
if (registeredIds.length == 0) return; // Skip if no registrations yet
|
|
|
|
uint256 focusId = registeredIds[index % registeredIds.length];
|
|
|
|
// Query current membership info from contract to handle updates (e.g., from extensions)
|
|
MembershipUpgradeable.MembershipInfo memory info;
|
|
(
|
|
info.depositAmount,
|
|
info.activeDuration,
|
|
info.gracePeriodStartTimestamp,
|
|
info.gracePeriodDuration,
|
|
info.rateLimit,
|
|
info.index,
|
|
info.holder,
|
|
info.token
|
|
) = w.memberships(focusId);
|
|
|
|
// If membership doesn't exist (e.g., erased), skip
|
|
if (info.rateLimit == 0) return;
|
|
|
|
uint256 graceStart = info.gracePeriodStartTimestamp;
|
|
uint256 graceEnd = graceStart + uint256(info.gracePeriodDuration);
|
|
bool isInGrace = (block.timestamp >= graceStart && block.timestamp < graceEnd);
|
|
bool isExpired = (block.timestamp >= graceEnd);
|
|
|
|
// Additional assertions: State consistency (pre-operation)
|
|
assert(w.isInGracePeriod(focusId) == isInGrace);
|
|
assert(w.isExpired(focusId) == isExpired);
|
|
|
|
uint256[] memory ids = new uint256[](1);
|
|
ids[0] = focusId;
|
|
|
|
bool success = false;
|
|
try w.extendMemberships(ids) {
|
|
success = true;
|
|
} catch { }
|
|
|
|
// Assertion: Extension should succeed only if in grace period (and sender is holder, but always true here)
|
|
assert(success == isInGrace);
|
|
}
|
|
|
|
// Function to attempt erasure on a random registered membership and assert based on current time
|
|
function attemptErasureRace(uint256 index, bool fullErase) public {
|
|
if (registeredIds.length == 0) return; // Skip if no registrations yet
|
|
|
|
uint256 focusId = registeredIds[index % registeredIds.length];
|
|
|
|
// Query current membership info from contract to handle updates
|
|
MembershipUpgradeable.MembershipInfo memory info;
|
|
(
|
|
info.depositAmount,
|
|
info.activeDuration,
|
|
info.gracePeriodStartTimestamp,
|
|
info.gracePeriodDuration,
|
|
info.rateLimit,
|
|
info.index,
|
|
info.holder,
|
|
info.token
|
|
) = w.memberships(focusId);
|
|
|
|
// If membership doesn't exist (e.g., already erased), skip
|
|
if (info.rateLimit == 0) return;
|
|
|
|
uint256 graceStart = info.gracePeriodStartTimestamp;
|
|
uint256 activeEnd = graceStart; // graceStart is end of active
|
|
uint256 graceEnd = graceStart + uint256(info.gracePeriodDuration);
|
|
|
|
bool isActive = (block.timestamp < activeEnd);
|
|
bool isExpired = (block.timestamp >= graceEnd);
|
|
|
|
// Additional assertions: State consistency (pre-operation)
|
|
assert(w.isExpired(focusId) == isExpired);
|
|
assert(w.isInGracePeriod(focusId) == !(isExpired || isActive));
|
|
|
|
uint256[] memory ids = new uint256[](1);
|
|
ids[0] = focusId;
|
|
|
|
bool success = false;
|
|
try w.eraseMemberships(ids, fullErase) {
|
|
success = true;
|
|
} catch { }
|
|
|
|
// Assertion: Erasure should succeed only if not active (i.e., in grace or expired)
|
|
// (and for grace, sender == holder, but always true here)
|
|
assert(success == !isActive);
|
|
|
|
// If successful erasure, remove from local registeredIds to avoid stale entries
|
|
if (success) {
|
|
// Find and remove focusId from array (swap with last and pop)
|
|
for (uint256 i = 0; i < registeredIds.length; i++) {
|
|
if (registeredIds[i] == focusId) {
|
|
registeredIds[i] = registeredIds[registeredIds.length - 1];
|
|
registeredIds.pop();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|