-
Notifications
You must be signed in to change notification settings - Fork 969
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
Changes from 3 commits
743af7f
78dadea
32c86d9
069992c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -107,23 +107,37 @@ contract ModuleManager is SelfAuthorized, Executor { | |
} | ||
|
||
/// @dev Returns array of modules. | ||
/// @param start Start of the page. | ||
/// @param pageSize Maximum number of modules that should be returned. | ||
/// If all entries fit into a single page, the next pointer will be 0x1. | ||
/// If another page is present, next will be the last element of the returned array. | ||
/// @param start Start of the page. Has to be a module or start pointer (0x1 address) | ||
/// @param pageSize Maximum number of modules that should be returned. Has to be > 0 | ||
/// @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 commentThe reason will be displayed to describe this comment to others. Learn more. Theoretically we should be able to use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. good catch |
||
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 commentThe reason will be displayed to describe this comment to others. Learn more. I changed |
||
array[moduleCount] = currentModule; | ||
currentModule = modules[currentModule]; | ||
moduleCount++; | ||
} | ||
next = currentModule; | ||
// Because of the argument validation we can assume that | ||
// the `currentModule` will always be either a module address | ||
// or sentinel address (aka the end). If we haven't reached the end | ||
// inside the loop, we need to set the next pointer to the last element | ||
// because it skipped over to the next module which is neither included | ||
// in the current page nor won't be included in the next one | ||
// if you pass it as a start. | ||
if (next != SENTINEL_MODULES) { | ||
next = array[moduleCount - 1]; | ||
} | ||
// Set correct size of returned array | ||
// solhint-disable-next-line no-inline-assembly | ||
assembly { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,7 +9,7 @@ import { AddressOne } from "../../src/utils/constants"; | |
|
||
describe("ModuleManager", async () => { | ||
|
||
const [user1, user2] = waffle.provider.getWallets(); | ||
const [user1, user2, user3] = waffle.provider.getWallets(); | ||
|
||
const setupTests = deployments.createFixture(async ({ deployments }) => { | ||
await deployments.fixture(); | ||
|
@@ -50,7 +50,7 @@ describe("ModuleManager", async () => { | |
).to.revertedWith("GS013") | ||
}) | ||
|
||
it('emits event for new module', async () => { | ||
it('emits event for a new module', async () => { | ||
const { safe } = await setupTests() | ||
await expect( | ||
executeContractCallWithSigners(safe, safe, "enableModule", [user2.address], [user1]) | ||
|
@@ -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 commentThe 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? |
||
it('requires page size to be greater than 0', async () => { | ||
const { safe } = await setupTests() | ||
await expect(safe.getModulesPaginated(AddressOne, 0)).to.be.revertedWith("GS106") | ||
}) | ||
|
||
it('requires start to be a module or start pointer', async () => { | ||
const { safe } = await setupTests() | ||
|
||
await expect(safe.getModulesPaginated(AddressZero, 1)).to.be.reverted | ||
await executeContractCallWithSigners(safe, safe, "enableModule", [user1.address], [user1]) | ||
expect(await safe.getModulesPaginated(user1.address, 1)).to.be.deep.equal([[], AddressOne]) | ||
await expect(safe.getModulesPaginated(user2.address, 1)).to.be.revertedWith("GS105") | ||
}) | ||
|
||
it('Returns all modules over multiple pages', async () => { | ||
const { safe } = await setupTests() | ||
await expect( | ||
executeContractCallWithSigners(safe, safe, "enableModule", [user1.address], [user1]) | ||
).to.emit(safe, "EnabledModule").withArgs(user1.address) | ||
|
||
await expect( | ||
executeContractCallWithSigners(safe, safe, "enableModule", [user2.address], [user1]) | ||
).to.emit(safe, "EnabledModule").withArgs(user2.address) | ||
|
||
await expect( | ||
executeContractCallWithSigners(safe, safe, "enableModule", [user3.address], [user1]) | ||
).to.emit(safe, "EnabledModule").withArgs(user3.address) | ||
|
||
await expect(await safe.isModuleEnabled(user1.address)).to.be.true | ||
await expect(await safe.isModuleEnabled(user2.address)).to.be.true | ||
await expect(await safe.isModuleEnabled(user3.address)).to.be.true | ||
/* | ||
This will pass the test which is not correct | ||
await expect(await safe.getModulesPaginated(AddressOne, 1)).to.be.deep.equal([[user3.address], user2.address]) | ||
await expect(await safe.getModulesPaginated(user2.address, 1)).to.be.deep.equal([[user1.address], AddressOne]) | ||
*/ | ||
await expect(await safe.getModulesPaginated(AddressOne, 1)).to.be.deep.equal([[user3.address], user3.address]) | ||
await expect(await safe.getModulesPaginated(user3.address, 1)).to.be.deep.equal([[user2.address], user2.address]) | ||
await expect(await safe.getModulesPaginated(user2.address, 1)).to.be.deep.equal([[user1.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.
maybe it's worth noting that removing the
pageSize
check passes all the test except it doesn't revert when passedpageSize === 0
and instead returns an empty array