mirror of
https://github.com/logos-messaging/logos-delivery-rlnv2-contract.git
synced 2026-06-07 13:49:30 +00:00
Merge branch 'main' into feat/expand-root-storage-window to include updated tests
This commit is contained in:
commit
1fd8247640
@ -1,30 +1,33 @@
|
||||
TestStableTokenTest:test__CannotAddAlreadyMinterRole() (gas: 46286)
|
||||
TestStableTokenTest:test__CannotMintExceedingMaxSupply() (gas: 26202)
|
||||
TestStableTokenTest:test__CannotMintWithETHExceedingMaxSupply() (gas: 31164)
|
||||
TestStableTokenTest:test__CannotMintWithZeroETH() (gas: 18282)
|
||||
TestStableTokenTest:test__CannotRemoveNonMinterRole() (gas: 22638)
|
||||
TestStableTokenTest:test__CannotSetMaxSupplyBelowTotalSupply() (gas: 71114)
|
||||
TestStableTokenTest:test__CheckMinterRoleMapping() (gas: 70651)
|
||||
TestStableTokenTest:test__ContractDoesNotHoldETHAfterMint() (gas: 110700)
|
||||
TestStableTokenTest:test__ERC20BasicFunctionality() (gas: 146240)
|
||||
TestStableTokenTest:test__ETHBurnedEventEmitted() (gas: 112584)
|
||||
TestStableTokenTest:test__ETHIsBurnedToZeroAddress() (gas: 110545)
|
||||
TestStableTokenTest:test__MaxSupplyIsSetCorrectly() (gas: 15409)
|
||||
TestStableTokenTest:test__MintRequiresETH() (gas: 18254)
|
||||
TestStableTokenTest:test__MintWithDifferentETHAmounts() (gas: 209672)
|
||||
TestStableTokenTest:test__MinterAddedEventEmitted() (gas: 44991)
|
||||
TestStableTokenTest:test__MinterRemovedEventEmitted() (gas: 34697)
|
||||
TestStableTokenTest:test__MinterRoleCanMint() (gas: 98026)
|
||||
TestStableTokenTest:test__MultipleMinterRolesCanMint() (gas: 128734)
|
||||
TestStableTokenTest:test__NonMinterNonOwnerAccountCannotMint() (gas: 22444)
|
||||
TestStableTokenTest:test__NonOwnerCannotAddMinterRole() (gas: 18239)
|
||||
TestStableTokenTest:test__NonOwnerCannotRemoveMinterRole() (gas: 45775)
|
||||
TestStableTokenTest:test__NonOwnerCannotSetMaxSupply() (gas: 18070)
|
||||
TestStableTokenTest:test__OwnerCanAddMinterRole() (gas: 47336)
|
||||
TestStableTokenTest:test__OwnerCanMintWithoutMinterRole() (gas: 74316)
|
||||
TestStableTokenTest:test__OwnerCanRemoveMinterRole() (gas: 36544)
|
||||
TestStableTokenTest:test__OwnerCanSetMaxSupply() (gas: 30683)
|
||||
TestStableTokenTest:test__RemovedMinterRoleCannotMint() (gas: 37086)
|
||||
TestStableTokenTest:test__CannotAddAlreadyMinterRole() (gas: 46248)
|
||||
TestStableTokenTest:test__CannotMintExceedingMaxSupply() (gas: 26253)
|
||||
TestStableTokenTest:test__CannotMintWithETHExceedingMaxSupply() (gas: 31196)
|
||||
TestStableTokenTest:test__CannotMintWithZeroETH() (gas: 18269)
|
||||
TestStableTokenTest:test__CannotRemoveNonMinterRole() (gas: 22686)
|
||||
TestStableTokenTest:test__CannotSetMaxSupplyBelowTotalSupply() (gas: 71121)
|
||||
TestStableTokenTest:test__CheckMinterRoleMapping() (gas: 70476)
|
||||
TestStableTokenTest:test__ContractDoesNotHoldETHAfterMint() (gas: 110659)
|
||||
TestStableTokenTest:test__ERC20BasicFunctionality() (gas: 146438)
|
||||
TestStableTokenTest:test__ETHBurnedEventEmitted() (gas: 112577)
|
||||
TestStableTokenTest:test__ETHIsBurnedToZeroAddress() (gas: 110526)
|
||||
TestStableTokenTest:test__InitializeZeroReverts() (gas: 2558591)
|
||||
TestStableTokenTest:test__MaxSupplyIsSetCorrectly() (gas: 15454)
|
||||
TestStableTokenTest:test__MintRequiresETH() (gas: 18285)
|
||||
TestStableTokenTest:test__MintWithDifferentETHAmounts() (gas: 209788)
|
||||
TestStableTokenTest:test__MintWithETH_RevertsBelowOneETH() (gas: 25455)
|
||||
TestStableTokenTest:test__MintWithETH_SucceedsAtOneETH() (gas: 110063)
|
||||
TestStableTokenTest:test__MinterAddedEventEmitted() (gas: 44947)
|
||||
TestStableTokenTest:test__MinterRemovedEventEmitted() (gas: 34662)
|
||||
TestStableTokenTest:test__MinterRoleCanMint() (gas: 98092)
|
||||
TestStableTokenTest:test__MultipleMinterRolesCanMint() (gas: 128755)
|
||||
TestStableTokenTest:test__NonMinterNonOwnerAccountCannotMint() (gas: 22493)
|
||||
TestStableTokenTest:test__NonOwnerCannotAddMinterRole() (gas: 18223)
|
||||
TestStableTokenTest:test__NonOwnerCannotRemoveMinterRole() (gas: 45737)
|
||||
TestStableTokenTest:test__NonOwnerCannotSetMaxSupply() (gas: 18054)
|
||||
TestStableTokenTest:test__OwnerCanAddMinterRole() (gas: 47248)
|
||||
TestStableTokenTest:test__OwnerCanMintWithoutMinterRole() (gas: 74382)
|
||||
TestStableTokenTest:test__OwnerCanRemoveMinterRole() (gas: 36473)
|
||||
TestStableTokenTest:test__OwnerCanSetMaxSupply() (gas: 30795)
|
||||
TestStableTokenTest:test__RemovedMinterRoleCannotMint() (gas: 37073)
|
||||
WakuRlnV2Test:test__EmptyRangePagination() (gas: 307693)
|
||||
WakuRlnV2Test:test__ErasingNonExistentMembership() (gas: 46131)
|
||||
WakuRlnV2Test:test__FullCleanUpErasure() (gas: 1016790)
|
||||
@ -44,27 +47,27 @@ WakuRlnV2Test:test__LinearPriceCalculation(uint32) (runs: 1000, μ: 26069, ~: 26
|
||||
WakuRlnV2Test:test__MassRegistrationAndErasure() (gas: 2714587)
|
||||
WakuRlnV2Test:test__MaxTotalRateLimitEdgeCases() (gas: 21832122)
|
||||
WakuRlnV2Test:test__MerkleTreeUpdateAfterErasureAndReuse() (gas: 2426716)
|
||||
WakuRlnV2Test:test__NonMinterCanMintWithETHAndRegister() (gas: 373178)
|
||||
WakuRlnV2Test:test__NonMinterCanMintWithETHAndRegister() (gas: 373332)
|
||||
WakuRlnV2Test:test__OwnerConfigurationUpdates() (gas: 53177)
|
||||
WakuRlnV2Test:test__PriceCalculatorReconfiguration() (gas: 669789)
|
||||
WakuRlnV2Test:test__PriceCalculatorReconfiguration() (gas: 669854)
|
||||
WakuRlnV2Test:test__RegistrationWhenMaxRateLimitIsReached() (gas: 595140)
|
||||
WakuRlnV2Test:test__ReinitializationProtection() (gas: 80197)
|
||||
WakuRlnV2Test:test__RemoveAllExpiredMemberships(uint32) (runs: 1000, μ: 5020828, ~: 2445573)
|
||||
WakuRlnV2Test:test__RemoveExpiredMemberships(uint32) (runs: 1000, μ: 1146896, ~: 1146896)
|
||||
WakuRlnV2Test:test__TokenTransferFailures() (gas: 4114224)
|
||||
WakuRlnV2Test:test__RemoveAllExpiredMemberships(uint32) (runs: 1000, μ: 4516570, ~: 2259520)
|
||||
WakuRlnV2Test:test__RemoveExpiredMemberships(uint32) (runs: 1000, μ: 1055797, ~: 1055798)
|
||||
WakuRlnV2Test:test__TokenTransferFailures() (gas: 4139092)
|
||||
WakuRlnV2Test:test__UnauthorizedMerkleTreeModifications() (gas: 1113852)
|
||||
WakuRlnV2Test:test__Upgrade() (gas: 6702671)
|
||||
WakuRlnV2Test:test__UpgradeWithInvalidImplementation() (gas: 51496)
|
||||
WakuRlnV2Test:test__ValidPaginationQuery(uint32) (runs: 1000, μ: 393970, ~: 134452)
|
||||
WakuRlnV2Test:test__ValidPaginationQuery(uint32) (runs: 1000, μ: 378536, ~: 134452)
|
||||
WakuRlnV2Test:test__ValidPaginationQuery__OneElement() (gas: 301276)
|
||||
WakuRlnV2Test:test__ValidRegistration(uint32) (runs: 1000, μ: 307585, ~: 307585)
|
||||
WakuRlnV2Test:test__ValidRegistration(uint32) (runs: 1000, μ: 307650, ~: 307650)
|
||||
WakuRlnV2Test:test__ValidRegistrationExpiry(uint32) (runs: 1000, μ: 288640, ~: 288640)
|
||||
WakuRlnV2Test:test__ValidRegistrationExtend(uint32) (runs: 1000, μ: 534996, ~: 534996)
|
||||
WakuRlnV2Test:test__ValidRegistrationExtendSingleMembership(uint32) (runs: 1000, μ: 296279, ~: 296279)
|
||||
WakuRlnV2Test:test__ValidRegistrationNoGracePeriod(uint32) (runs: 1000, μ: 292251, ~: 292251)
|
||||
WakuRlnV2Test:test__ValidRegistrationWithEraseList() (gas: 1303567)
|
||||
WakuRlnV2Test:test__ValidRegistrationWithEraseList() (gas: 1337020)
|
||||
WakuRlnV2Test:test__ValidRegistration__kats() (gas: 277614)
|
||||
WakuRlnV2Test:test__WithdrawToken(uint32) (runs: 1000, μ: 277708, ~: 277708)
|
||||
WakuRlnV2Test:test__WithdrawToken(uint32) (runs: 1000, μ: 283285, ~: 283286)
|
||||
WakuRlnV2Test:test__ZeroGracePeriodDuration() (gas: 8156320)
|
||||
WakuRlnV2Test:test__ZeroPriceEdgeCase() (gas: 791578)
|
||||
WakuRlnV2Test:test__indexReuse_eraseMemberships(uint32) (runs: 1000, μ: 4235383, ~: 1628509)
|
||||
WakuRlnV2Test:test__ZeroPriceEdgeCase() (gas: 791643)
|
||||
WakuRlnV2Test:test__indexReuse_eraseMemberships(uint32) (runs: 1000, μ: 4269053, ~: 1835792)
|
||||
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@ -117,7 +117,7 @@ jobs:
|
||||
)" >> $GITHUB_ENV
|
||||
|
||||
- name: "Run the tests"
|
||||
run: "forge test"
|
||||
run: 'forge test --no-match-path "test/Echidna*.t.sol"'
|
||||
|
||||
- name: "Add test summary"
|
||||
run: |
|
||||
@ -151,7 +151,7 @@ jobs:
|
||||
run: "pnpm install"
|
||||
|
||||
- name: "Generate the coverage report using the unit and the integration tests"
|
||||
run: 'forge coverage --match-path "test/**/*.sol" --report lcov'
|
||||
run: 'forge coverage --match-path "test/**/*.sol" --no-match-path "test/Echidna*.t.sol" --report lcov'
|
||||
|
||||
- name: "Upload coverage report to Codecov"
|
||||
uses: "codecov/codecov-action@v3"
|
||||
|
||||
19
echidna.config.yaml
Normal file
19
echidna.config.yaml
Normal file
@ -0,0 +1,19 @@
|
||||
solcArgs: "--via-ir --optimize --optimize-runs 1"
|
||||
testMode: property
|
||||
testLimit: 100000 # For ~1 hour on strong CPU; adjust
|
||||
seqLen: 100 # Sequence length for stateful fuzzing
|
||||
shrinkLimit: 5000
|
||||
corpusDir: corpus
|
||||
deployContracts:
|
||||
- ["0x0000000000000000000000000000000000001000", "PoseidonT3"]
|
||||
- ["0x0000000000000000000000000000000000001001", "LazyIMT"]
|
||||
cryticArgs:
|
||||
- "--compile-libraries=(PoseidonT3,0x0000000000000000000000000000000000001000),(LazyIMT,0x0000000000000000000000000000000000001001)"
|
||||
propMaxGas: 25000000
|
||||
testMaxGas: 25000000
|
||||
maxTimeDelay: 20000000 # ~231 days in seconds; set high to cover active (180 days / 15552000s) and grace (30 days / 2592000s) periods for expiration races
|
||||
sender: ["0x10000", "0x20000", "0x30000", "0x40000"] # Multiple senders to simulate different users; expand if needed
|
||||
balanceAddr: 100000000000000000000
|
||||
coverage: true
|
||||
quiet: false
|
||||
projectName: "WakuRlnV2"
|
||||
5
echidna_cleanup.sh
Executable file
5
echidna_cleanup.sh
Executable file
@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
rm -rf corpus
|
||||
rm -f covered*.txt
|
||||
rm -rf .crytic/
|
||||
echo "Echidna artifacts cleaned up."
|
||||
19
echidna_races.config.yaml
Normal file
19
echidna_races.config.yaml
Normal file
@ -0,0 +1,19 @@
|
||||
solcArgs: "--via-ir --optimize --optimize-runs 1"
|
||||
testMode: assertion
|
||||
testLimit: 100000 # For ~1 hour on strong CPU; adjust
|
||||
seqLen: 100 # Sequence length for stateful fuzzing
|
||||
shrinkLimit: 5000
|
||||
corpusDir: corpus
|
||||
deployContracts:
|
||||
- ["0x0000000000000000000000000000000000001000", "PoseidonT3"]
|
||||
- ["0x0000000000000000000000000000000000001001", "LazyIMT"]
|
||||
cryticArgs:
|
||||
- "--compile-libraries=(PoseidonT3,0x0000000000000000000000000000000000001000),(LazyIMT,0x0000000000000000000000000000000000001001)"
|
||||
propMaxGas: 25000000
|
||||
testMaxGas: 25000000
|
||||
maxTimeDelay: 20000000 # ~231 days in seconds; set high to cover active (180 days / 15552000s) and grace (30 days / 2592000s) periods for expiration races
|
||||
sender: ["0x10000", "0x20000", "0x30000", "0x40000"] # Multiple senders to simulate different users; expand if needed
|
||||
balanceAddr: 100000000000000000000
|
||||
coverage: true
|
||||
quiet: false
|
||||
projectName: "WakuRlnV2"
|
||||
3
run_echidna_tests.sh
Executable file
3
run_echidna_tests.sh
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
echidna test/EchidnaTest.t.sol --contract EchidnaTest --config echidna.config.yaml
|
||||
echidna test/EchidnaTestRaces.t.sol --contract EchidnaTestRaces --config echidna_races.config.yaml
|
||||
171
test/EchidnaTest.t.sol
Normal file
171
test/EchidnaTest.t.sol
Normal file
@ -0,0 +1,171 @@
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
145
test/EchidnaTestRaces.t.sol
Normal file
145
test/EchidnaTestRaces.t.sol
Normal file
@ -0,0 +1,145 @@
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3,8 +3,9 @@ pragma solidity >=0.8.19 <0.9.0;
|
||||
|
||||
import { BaseScript } from "../script/Base.s.sol";
|
||||
import { ERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
|
||||
import { ERC20PermitUpgradeable } from
|
||||
"@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol";
|
||||
import {
|
||||
ERC20PermitUpgradeable
|
||||
} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol";
|
||||
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
|
||||
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
|
||||
import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
|
||||
|
||||
@ -64,8 +64,9 @@ contract MockPriceCalculator is IPriceCalculator {
|
||||
}
|
||||
|
||||
contract NonUUPSContract {
|
||||
// A mock contract that does not support UUPS (no proxiable UUID or _authorizeUpgrade)
|
||||
}
|
||||
// A mock contract that does not support UUPS (no proxiable UUID or _authorizeUpgrade)
|
||||
|
||||
}
|
||||
|
||||
contract WakuRlnV2Test is Test {
|
||||
WakuRlnV2 internal w;
|
||||
@ -911,8 +912,7 @@ contract WakuRlnV2Test is Test {
|
||||
w.register(idCommitment, rateLimit, noIdCommitmentsToErase);
|
||||
|
||||
// Destructure the memberships mapping tuple, skipping unused fields
|
||||
(
|
||||
, // depositAmount
|
||||
(, // depositAmount
|
||||
uint32 activeDuration,
|
||||
uint256 gracePeriodStart,
|
||||
uint32 gracePeriodDuration,
|
||||
@ -989,11 +989,10 @@ contract WakuRlnV2Test is Test {
|
||||
, // depositAmount
|
||||
, // activeDuration
|
||||
uint256 graceStart,
|
||||
uint32 gracePeriodDuration,
|
||||
, // rateLimit
|
||||
uint32 gracePeriodDuration,, // rateLimit
|
||||
, // index
|
||||
, // holder
|
||||
// token
|
||||
// token
|
||||
) = w.memberships(100);
|
||||
vm.warp(graceStart + gracePeriodDuration + 1); // Expire one
|
||||
|
||||
@ -1024,12 +1023,11 @@ contract WakuRlnV2Test is Test {
|
||||
(
|
||||
, // depositAmount
|
||||
, // activeDuration
|
||||
uint256 graceStart,
|
||||
, // gracePeriodDuration
|
||||
uint256 graceStart,, // gracePeriodDuration
|
||||
, // rateLimit
|
||||
, // index
|
||||
, // holder
|
||||
// token
|
||||
// token
|
||||
) = w.memberships(idCommitment1);
|
||||
vm.warp(graceStart);
|
||||
uint256[] memory toErase = new uint256[](1);
|
||||
@ -1087,12 +1085,11 @@ contract WakuRlnV2Test is Test {
|
||||
(
|
||||
, // depositAmount
|
||||
, // activeDuration
|
||||
uint256 gracePeriodStart,
|
||||
, // gracePeriodDuration
|
||||
uint256 gracePeriodStart,, // gracePeriodDuration
|
||||
, // rateLimit
|
||||
, // index
|
||||
, // holder
|
||||
// token
|
||||
// token
|
||||
) = wZeroGrace.memberships(idCommitment);
|
||||
|
||||
// Warp just after active period
|
||||
@ -1128,11 +1125,10 @@ contract WakuRlnV2Test is Test {
|
||||
, // depositAmount
|
||||
, // activeDuration
|
||||
uint256 graceStart,
|
||||
uint32 gracePeriodDuration,
|
||||
, // rateLimit
|
||||
uint32 gracePeriodDuration,, // rateLimit
|
||||
, // index
|
||||
, // holder
|
||||
// token
|
||||
// token
|
||||
) = w.memberships(idCommitment);
|
||||
|
||||
vm.warp(graceStart + gracePeriodDuration + 1); // Expire
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user