Skip to content

Commit

Permalink
fix: pagination tests and Sentinel List Library updated to latest ver…
Browse files Browse the repository at this point in the history
…sion (#74)
  • Loading branch information
kanthgithub authored Jul 2, 2024
1 parent 4cae433 commit 8e91dcf
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 33 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,5 @@ broadcast
forge_cache/
.gas-snapshot

yarn-error.log
yarn.lock
67 changes: 34 additions & 33 deletions src/modular-etherspot-wallet/erc7579-ref-impl/libs/SentinelList.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// https://github.com/zeroknots/sentinellist/
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

Expand All @@ -20,16 +19,11 @@ library SentinelListLib {
self.entries[SENTINEL] = SENTINEL;
}

function alreadyInitialized(
SentinelList storage self
) internal view returns (bool) {
function alreadyInitialized(SentinelList storage self) internal view returns (bool) {
return self.entries[SENTINEL] != ZERO_ADDRESS;
}

function getNext(
SentinelList storage self,
address entry
) internal view returns (address) {
function getNext(SentinelList storage self, address entry) internal view returns (address) {
if (entry == ZERO_ADDRESS) {
revert LinkedList_InvalidEntry(entry);
}
Expand All @@ -40,64 +34,71 @@ library SentinelListLib {
if (newEntry == ZERO_ADDRESS || newEntry == SENTINEL) {
revert LinkedList_InvalidEntry(newEntry);
}
if (self.entries[newEntry] != ZERO_ADDRESS)
revert LinkedList_EntryAlreadyInList(newEntry);
if (self.entries[newEntry] != ZERO_ADDRESS) revert LinkedList_EntryAlreadyInList(newEntry);
self.entries[newEntry] = self.entries[SENTINEL];
self.entries[SENTINEL] = newEntry;
}

function pop(
SentinelList storage self,
address prevEntry,
address popEntry
) internal {
function pop(SentinelList storage self, address prevEntry, address popEntry) internal {
if (popEntry == ZERO_ADDRESS || popEntry == SENTINEL) {
revert LinkedList_InvalidEntry(prevEntry);
}
if (self.entries[prevEntry] != popEntry)
revert LinkedList_InvalidEntry(popEntry);
if (self.entries[prevEntry] != popEntry) revert LinkedList_InvalidEntry(popEntry);
self.entries[prevEntry] = self.entries[popEntry];
self.entries[popEntry] = ZERO_ADDRESS;
}

function contains(
SentinelList storage self,
address entry
) internal view returns (bool) {
function popAll(SentinelList storage self) internal {
address next = self.entries[SENTINEL];
while (next != ZERO_ADDRESS) {
address current = next;
next = self.entries[next];
self.entries[current] = ZERO_ADDRESS;
}
self.entries[SENTINEL] = ZERO_ADDRESS;
}

function contains(SentinelList storage self, address entry) internal view returns (bool) {
return SENTINEL != entry && self.entries[entry] != ZERO_ADDRESS;
}

function getEntriesPaginated(
SentinelList storage self,
address start,
uint256 pageSize
) internal view returns (address[] memory array, address next) {
if (start != SENTINEL && contains(self, start))
revert LinkedList_InvalidEntry(start);
)
internal
view
returns (address[] memory array, address next)
{
if (start != SENTINEL && !contains(self, start)) revert LinkedList_InvalidEntry(start);
if (pageSize == 0) revert LinkedList_InvalidPage();
// Init array with max page size
array = new address[](pageSize);

// Populate return array
uint256 entryCount = 0;
next = self.entries[start];
while (
next != ZERO_ADDRESS && next != SENTINEL && entryCount < pageSize
) {
while (next != ZERO_ADDRESS && next != SENTINEL && entryCount < pageSize) {
array[entryCount] = next;
next = self.entries[next];
entryCount++;
}

/**
* Because of the argument validation, we can assume that the loop will always iterate over the valid entry list values
* and the `next` variable will either be an enabled entry or a sentinel address (signalling the end).
* Because of the argument validation, we can assume that the loop will always iterate over
* the valid entry list values
* and the `next` variable will either be an enabled entry or a sentinel address
* (signalling the end).
*
* If we haven't reached the end inside the loop, we need to set the next pointer to the last element of the entry array
* because the `next` variable (which is a entry by itself) acting as a pointer to the start of the next page is neither
* incSENTINELrent page, nor will it be included in the next one if you pass it as a start.
* If we haven't reached the end inside the loop, we need to set the next pointer to
* the last element of the entry array
* because the `next` variable (which is a entry by itself) acting as a pointer to the
* start of the next page is neither
* incSENTINELrent page, nor will it be included in the next one if you pass it as a
* start.
*/
if (next != SENTINEL) {
if (next != SENTINEL && entryCount > 0) {
next = array[entryCount - 1];
}
// Set correct size of returned array
Expand Down
28 changes: 28 additions & 0 deletions test/foundry/wallet/ModularEtherspotWallet.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ contract ModularEtherspotWalletTest is TestAdvancedUtils {
error OnlyOwnerOrGuardianOrSelf();
error OnlyProxy();
error RequiredModule();
error LinkedList_InvalidEntry(address entry);

function setUp() public override {
super.setUp();
Expand Down Expand Up @@ -885,6 +886,33 @@ contract ModularEtherspotWalletTest is TestAdvancedUtils {
mewAccount.discardCurrentProposal();
}

function test_PaginateExecutors() public {
mew = setupMEW();

// paginate from sentinel (start node) and expect the 1 default executor
(address[] memory results, address next) = mew.getExecutorsPaginated(address(0x1), 1);
assertTrue(results.length == 1);
assertEq(results[0], address(defaultExecutor));
assertEq(next, address(0x1));

// paginate from the default executor and expect no results
(address[] memory results2, address next2) = mew.getExecutorsPaginated(address(defaultExecutor), 1);
assertTrue(results2.length == 0);
assertEq(next2, address(0x1));


// Correctly encode the selector for the error signature and the argument
bytes memory encodedRevertReason = abi.encodeWithSelector(
bytes4(keccak256("LinkedList_InvalidEntry(address)")),
address(this)
);

// should assert on revert with error: revert LinkedList_InvalidEntry(start)
// Expect the revert with the encoded reason
vm.expectRevert(encodedRevertReason);
mew.getExecutorsPaginated(address(this), 1);
}

function test_fail_uninstallModule_RequiredModule() public {
mew = setupMEW();
vm.startPrank(address(mewAccount));
Expand Down

0 comments on commit 8e91dcf

Please sign in to comment.