diff --git a/.gitignore b/.gitignore index 64a3fc67..33234723 100644 --- a/.gitignore +++ b/.gitignore @@ -51,3 +51,5 @@ broadcast forge_cache/ .gas-snapshot +yarn-error.log +yarn.lock \ No newline at end of file diff --git a/src/modular-etherspot-wallet/erc7579-ref-impl/libs/SentinelList.sol b/src/modular-etherspot-wallet/erc7579-ref-impl/libs/SentinelList.sol index ba926941..fdf36183 100644 --- a/src/modular-etherspot-wallet/erc7579-ref-impl/libs/SentinelList.sol +++ b/src/modular-etherspot-wallet/erc7579-ref-impl/libs/SentinelList.sol @@ -1,4 +1,3 @@ -// https://github.com/zeroknots/sentinellist/ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; @@ -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); } @@ -40,30 +34,31 @@ 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; } @@ -71,9 +66,12 @@ library SentinelListLib { 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); @@ -81,23 +79,26 @@ library SentinelListLib { // 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 diff --git a/test/foundry/wallet/ModularEtherspotWallet.t.sol b/test/foundry/wallet/ModularEtherspotWallet.t.sol index 0693ab2c..ebeb23e6 100644 --- a/test/foundry/wallet/ModularEtherspotWallet.t.sol +++ b/test/foundry/wallet/ModularEtherspotWallet.t.sol @@ -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(); @@ -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));