-
Notifications
You must be signed in to change notification settings - Fork 927
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
1.4.0: Fix getModulesPaginated
#470
Conversation
68ab14a
to
a5418d4
Compare
Pull Request Test Coverage Report for Build 3912478679
💛 - Coveralls |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at this more we need to define a little bit more what is expected here.
E.g.
- What should happen if an invalid start is passed? (We also don'T have tests here)
- What should be returned at the end of the page? (0 vs 1 vs ?)
- What should be returned for an empty page.
With the current solution we break even the flow for working use cases (e.g. we change what is returned of all entries fit into a single page), which I would avoid
@@ -62,7 +62,7 @@ describe("ModuleManager", async () => { | |||
|
|||
await expect( | |||
await safe.getModulesPaginated(AddressOne, 10) | |||
).to.be.deep.equal([[user2.address], AddressOne]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This tests should not have to be adjusted. I would stick to the defined behavior for this case, meaning at the end of the page AddressOne
is returned as next (signaling the end).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would stick to the defined behavior for this case, meaning at the end of the page AddressOne is returned as next (signaling the end).
It seems like this would require adjustments in the module manager contract, because unlike OwnerManager
we don't have the last module pointing to a sentinel address. Do you think it's worth it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we don't have the last module pointing to a sentinel address.
That should always be the case. This is required else a lot of logic will break
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want to do something like this in getModulesPaginated
method then:
if (next == address(0)) next = SENTINEL_ADDRESS;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why? I do not understand why this would be required. The linked list always starts with the sentinel and ends on the sentinel:
- Empty list: 0x1 -> 0x1
- List with one module: 0x1 -> 0xbaddad -> 0x1
- List with n modules: 0x1 -> 0xbaddad -> 0x.... -> 0x1
If next
is 0x0
then something is wrong
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I was confused how this was possible until I saw that setupModules
is called during the setup. Will investigate why this is not the case
9b5ecde
to
1c1ef96
Compare
contracts/base/ModuleManager.sol
Outdated
@@ -123,7 +123,7 @@ contract ModuleManager is SelfAuthorized, Executor { | |||
currentModule = modules[currentModule]; | |||
moduleCount++; | |||
} | |||
next = currentModule; | |||
next = array[moduleCount == 0 ? 0 : moduleCount - 1]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
array[0]
(for the case that moduleCound == 0
) would be undefined, right? In the best case it is 0 in the worst case it some random data (should normally not happen).
I still believe we should first clearly define the expected behavior for this method (and maybe even for the .
start
has to be a module else the method should revertpageSize
needs to be greater 0, else the method should revert- If all entries fit into a single page, then
next
should be Address One (as this is the current behavior) - If another page is present then
next
should be the last element of the returned array (<- this was bugged before)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
array[0] (for the case that moduleCound == 0) would be undefined, right?
It's zero address I think
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah it should be zero (at least with the current solidity management), but not sure if there is a guarantee for it.
if pageSize
is 0 this should revert as array access should do bounds checks.
@rmeissner a side question:
Should we ensure here that |
93b8f85
to
cb3e0aa
Compare
@rmeissner I gave it another go, please check it. I still had to adjust some test though. |
contracts/base/ModuleManager.sol
Outdated
// Init array with max page size | ||
array = new address[](pageSize); | ||
|
||
// Populate return array | ||
uint256 moduleCount = 0; | ||
address currentModule = modules[start]; | ||
while (currentModule != address(0x0) && currentModule != SENTINEL_MODULES && moduleCount < pageSize) { | ||
while (currentModule != address(0) && currentModule != SENTINEL_MODULES && moduleCount < pageSize) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed 0x0
to 0
because within the file this was the only occurence of 0x0
and 0
was used everywhere else
…obal/safe-contracts into bounty_module_pagination
cb3e0aa
to
32c86d9
Compare
/// @return array Array of modules. | ||
/// @return next Start of the next page. | ||
function getModulesPaginated(address start, uint256 pageSize) external view returns (address[] memory array, address next) { | ||
require(start == SENTINEL_MODULES || isModuleEnabled(start), "GS105"); | ||
require(pageSize > 0, "GS106"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe it's worth noting that removing the pageSize
check passes all the test except it doesn't revert when passed pageSize === 0
and instead returns an empty array
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Besides adding a test case for a Safe that has no modules it looks good
@@ -244,4 +244,47 @@ describe("ModuleManager", async () => { | |||
).to.be.deep.eq([false, "0x08c379a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000013536f6d652072616e646f6d206d65737361676500000000000000000000000000"]) | |||
}) | |||
}) | |||
|
|||
describe("getModulesPaginated", async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have a test for the case that this method is called when no modules are enabled?
contracts/base/ModuleManager.sol
Outdated
/// @return array Array of modules. | ||
/// @return next Start of the next page. | ||
function getModulesPaginated(address start, uint256 pageSize) external view returns (address[] memory array, address next) { | ||
require(start == SENTINEL_MODULES || isModuleEnabled(start), "GS105"); | ||
require(pageSize > 0, "GS106"); | ||
// Init array with max page size | ||
array = new address[](pageSize); | ||
|
||
// Populate return array | ||
uint256 moduleCount = 0; | ||
address currentModule = modules[start]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Theoretically we should be able to use next
instead of currentModule
(merge these too), but not sure if that would make it really better
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good catch
Didn't see this comment. I like the addition, but would do it in a separate PR. |
…nty_module_pagination
ed1faee
to
069992c
Compare
This PR fixes #461 by checking if we reached the end of the modules list and conditionally returning either sentinel address signaling the end or the last element of the module list
Also added some test cases and validations for passed arguments