Commit 4424552c authored by Maurelian's avatar Maurelian Committed by GitHub

ctb: Add DeputyGuardianModule (#9982)

* ctb: Allow the Liveness Module's threshold to be customized

* ctb: Hardcode threshold percentage as immutable in livenessmodule

* ctb: test threshold math differentially

* ctb: Threshold tests with hardcoded values and boundary fuzz tests
parent 02c1193e
...@@ -92,8 +92,8 @@ ...@@ -92,8 +92,8 @@
"sourceCodeHash": "0x9633cea9b66077e222f470439fe3e9a31f3e33b4f7a5618374c44310fd234b24" "sourceCodeHash": "0x9633cea9b66077e222f470439fe3e9a31f3e33b4f7a5618374c44310fd234b24"
}, },
"src/Safe/LivenessModule.sol": { "src/Safe/LivenessModule.sol": {
"initCodeHash": "0xcd8b76f70634330e242d4ff497bba1f8e0aaa11d7aecf9845090029c43027a7f", "initCodeHash": "0xa8b233f0f26f8a73b997b12ba06d64cefa8ee98d523f68cd63320e9787468fae",
"sourceCodeHash": "0x3b358b51fce4f1516191104d2e1f782c8ec3edecee63c502cc301671aa632b93" "sourceCodeHash": "0x73aa5934e56ba2a45f368806c5db1d442bf5713d51b2184749f4638eaceb832e"
}, },
"src/cannon/MIPS.sol": { "src/cannon/MIPS.sol": {
"initCodeHash": "0xaf2ac814f64ccf12e9c6738db7cef865f51f9e39f39105adef9fba11465f6ee1", "initCodeHash": "0xaf2ac814f64ccf12e9c6738db7cef865f51f9e39f39105adef9fba11465f6ee1",
......
...@@ -21,6 +21,11 @@ ...@@ -21,6 +21,11 @@
"name": "_minOwners", "name": "_minOwners",
"type": "uint256" "type": "uint256"
}, },
{
"internalType": "uint256",
"name": "_thresholdPercentage",
"type": "uint256"
},
{ {
"internalType": "address", "internalType": "address",
"name": "_fallbackOwner", "name": "_fallbackOwner",
...@@ -70,7 +75,7 @@ ...@@ -70,7 +75,7 @@
"type": "uint256" "type": "uint256"
} }
], ],
"name": "get75PercentThreshold", "name": "getRequiredThreshold",
"outputs": [ "outputs": [
{ {
"internalType": "uint256", "internalType": "uint256",
...@@ -78,7 +83,7 @@ ...@@ -78,7 +83,7 @@
"type": "uint256" "type": "uint256"
} }
], ],
"stateMutability": "pure", "stateMutability": "view",
"type": "function" "type": "function"
}, },
{ {
...@@ -151,6 +156,19 @@ ...@@ -151,6 +156,19 @@
"stateMutability": "view", "stateMutability": "view",
"type": "function" "type": "function"
}, },
{
"inputs": [],
"name": "thresholdPercentage",
"outputs": [
{
"internalType": "uint256",
"name": "thresholdPercentage_",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{ {
"inputs": [], "inputs": [],
"name": "version", "name": "version",
......
...@@ -35,6 +35,9 @@ contract LivenessModule is ISemver { ...@@ -35,6 +35,9 @@ contract LivenessModule is ISemver {
/// This can be updated by replacing with a new module. /// This can be updated by replacing with a new module.
uint256 internal immutable MIN_OWNERS; uint256 internal immutable MIN_OWNERS;
/// @notice The percentage used to calculate the threshold for the Safe.
uint256 internal immutable THRESHOLD_PERCENTAGE;
/// @notice The fallback owner of the Safe /// @notice The fallback owner of the Safe
/// This can be updated by replacing with a new module. /// This can be updated by replacing with a new module.
address internal immutable FALLBACK_OWNER; address internal immutable FALLBACK_OWNER;
...@@ -44,8 +47,8 @@ contract LivenessModule is ISemver { ...@@ -44,8 +47,8 @@ contract LivenessModule is ISemver {
uint256 internal constant GUARD_STORAGE_SLOT = 0x4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c8; uint256 internal constant GUARD_STORAGE_SLOT = 0x4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c8;
/// @notice Semantic version. /// @notice Semantic version.
/// @custom:semver 1.0.0 /// @custom:semver 1.1.0
string public constant version = "1.0.0"; string public constant version = "1.1.0";
// Constructor to initialize the Safe and baseModule instances // Constructor to initialize the Safe and baseModule instances
constructor( constructor(
...@@ -53,25 +56,29 @@ contract LivenessModule is ISemver { ...@@ -53,25 +56,29 @@ contract LivenessModule is ISemver {
LivenessGuard _livenessGuard, LivenessGuard _livenessGuard,
uint256 _livenessInterval, uint256 _livenessInterval,
uint256 _minOwners, uint256 _minOwners,
uint256 _thresholdPercentage,
address _fallbackOwner address _fallbackOwner
) { ) {
SAFE = _safe; SAFE = _safe;
LIVENESS_GUARD = _livenessGuard; LIVENESS_GUARD = _livenessGuard;
LIVENESS_INTERVAL = _livenessInterval; LIVENESS_INTERVAL = _livenessInterval;
THRESHOLD_PERCENTAGE = _thresholdPercentage;
FALLBACK_OWNER = _fallbackOwner; FALLBACK_OWNER = _fallbackOwner;
MIN_OWNERS = _minOwners; MIN_OWNERS = _minOwners;
address[] memory owners = _safe.getOwners(); address[] memory owners = _safe.getOwners();
require(_minOwners <= owners.length, "LivenessModule: minOwners must be less than the number of owners"); require(_minOwners <= owners.length, "LivenessModule: minOwners must be less than the number of owners");
require( require(
_safe.getThreshold() >= get75PercentThreshold(owners.length), _safe.getThreshold() >= getRequiredThreshold(owners.length),
"LivenessModule: Safe must have a threshold of at least 75% of the number of owners" "LivenessModule: Insufficient threshold for the number of owners"
); );
require(_thresholdPercentage > 0, "LivenessModule: thresholdPercentage must be greater than 0");
require(_thresholdPercentage <= 100, "LivenessModule: thresholdPercentage must be less than or equal to 100");
} }
/// @notice For a given number of owners, return the lowest threshold which is greater than 75. /// @notice For a given number of owners, return the lowest threshold which is greater than the required percentage.
/// Note: this function returns 1 for numOwners == 1. /// Note: this function returns 1 for numOwners == 1.
function get75PercentThreshold(uint256 _numOwners) public pure returns (uint256 threshold_) { function getRequiredThreshold(uint256 _numOwners) public view returns (uint256 threshold_) {
threshold_ = (_numOwners * 75 + 99) / 100; threshold_ = (_numOwners * THRESHOLD_PERCENTAGE + 99) / 100;
} }
/// @notice Getter function for the Safe contract instance /// @notice Getter function for the Safe contract instance
...@@ -98,7 +105,13 @@ contract LivenessModule is ISemver { ...@@ -98,7 +105,13 @@ contract LivenessModule is ISemver {
minOwners_ = MIN_OWNERS; minOwners_ = MIN_OWNERS;
} }
/// @notice Getter function for the fallback owner /// @notice Getter function for the required threshold percentage
/// @return thresholdPercentage_ The minimum number of owners
function thresholdPercentage() public view returns (uint256 thresholdPercentage_) {
thresholdPercentage_ = THRESHOLD_PERCENTAGE;
}
/// @notice Getter function for the fallback
/// @return fallbackOwner_ The fallback owner of the Safe /// @return fallbackOwner_ The fallback owner of the Safe
function fallbackOwner() public view returns (address fallbackOwner_) { function fallbackOwner() public view returns (address fallbackOwner_) {
fallbackOwner_ = FALLBACK_OWNER; fallbackOwner_ = FALLBACK_OWNER;
...@@ -161,7 +174,7 @@ contract LivenessModule is ISemver { ...@@ -161,7 +174,7 @@ contract LivenessModule is ISemver {
/// @param _newOwnersCount New number of owners after removal. /// @param _newOwnersCount New number of owners after removal.
function _removeOwner(address _prevOwner, address _ownerToRemove, uint256 _newOwnersCount) internal { function _removeOwner(address _prevOwner, address _ownerToRemove, uint256 _newOwnersCount) internal {
if (_newOwnersCount > 0) { if (_newOwnersCount > 0) {
uint256 newThreshold = get75PercentThreshold(_newOwnersCount); uint256 newThreshold = getRequiredThreshold(_newOwnersCount);
// Remove the owner and update the threshold // Remove the owner and update the threshold
_removeOwnerSafeCall({ _prevOwner: _prevOwner, _owner: _ownerToRemove, _threshold: newThreshold }); _removeOwnerSafeCall({ _prevOwner: _prevOwner, _owner: _ownerToRemove, _threshold: newThreshold });
} else { } else {
...@@ -223,11 +236,11 @@ contract LivenessModule is ISemver { ...@@ -223,11 +236,11 @@ contract LivenessModule is ISemver {
// Check that"LivenessModule: must remove all owners and transfer to fallback owner if numOwners < minOwners" // Check that"LivenessModule: must remove all owners and transfer to fallback owner if numOwners < minOwners"
// the threshold is correct. This check is also correct when there is a single // the threshold is correct. This check is also correct when there is a single
// owner, because get75PercentThreshold(1) returns 1. // owner, because getRequiredThreshold(1) returns 1.
uint256 threshold = SAFE.getThreshold(); uint256 threshold = SAFE.getThreshold();
require( require(
threshold == get75PercentThreshold(numOwners), threshold == getRequiredThreshold(numOwners),
"LivenessModule: Safe must have a threshold of 75% of the number of owners" "LivenessModule: Insufficient threshold for the number of owners"
); );
// Check that the guard has not been changed // Check that the guard has not been changed
......
...@@ -20,9 +20,10 @@ contract LivenessModule_TestInit is Test, SafeTestTools { ...@@ -20,9 +20,10 @@ contract LivenessModule_TestInit is Test, SafeTestTools {
event RemovedOwner(address indexed owner); event RemovedOwner(address indexed owner);
event OwnershipTransferredToFallback(); event OwnershipTransferredToFallback();
uint256 initTime = 10; uint256 constant INIT_TIME = 10;
uint256 livenessInterval = 30 days; uint256 constant LIVENESS_INTERVAL = 30 days;
uint256 minOwners = 6; uint256 constant MIN_OWNERS = 6;
uint256 constant THRESHOLD_PERCENTAGE = 75;
LivenessModule livenessModule; LivenessModule livenessModule;
LivenessGuard livenessGuard; LivenessGuard livenessGuard;
SafeInstance safeInstance; SafeInstance safeInstance;
...@@ -40,14 +41,35 @@ contract LivenessModule_TestInit is Test, SafeTestTools { ...@@ -40,14 +41,35 @@ contract LivenessModule_TestInit is Test, SafeTestTools {
/// @dev Set the current time to after the liveness interval /// @dev Set the current time to after the liveness interval
function _warpPastLivenessInterval() internal { function _warpPastLivenessInterval() internal {
vm.warp(initTime + livenessInterval + 1); vm.warp(INIT_TIME + LIVENESS_INTERVAL + 1);
}
/// @dev Helper function to calculate the required threshold for a given number of owners and threshold percentage.
/// This does a lot of extra work to set up a dummy LivenessModule and Safe, but helps to keep the tests clean.
function _getRequiredThreshold(uint256 _numOwners, uint256 _thresholdPercentage) internal returns (uint256) {
// Mock calls made to the Safe by the LivenessModule's constructor
address[] memory dummyOwners = new address[](_numOwners);
dummyOwners[0] = address(0);
vm.mockCall(address(0), abi.encodeCall(OwnerManager.getOwners, ()), abi.encode(dummyOwners));
vm.mockCall(address(0), abi.encodeCall(OwnerManager.getThreshold, ()), abi.encode(dummyOwners.length));
// This module is only used to calculate the required threshold for the Safe. It's state will not be modified
// after it is created so the constructor arguments are not important.
LivenessModule dummyLivenessModule = new LivenessModule({
_safe: Safe(payable(address(0))),
_livenessGuard: LivenessGuard(address(0)),
_livenessInterval: 0,
_thresholdPercentage: _thresholdPercentage,
_minOwners: 0,
_fallbackOwner: address(0)
});
return dummyLivenessModule.getRequiredThreshold(_numOwners);
} }
/// @dev Sets up the test environment /// @dev Sets up the test environment
function setUp() public virtual { function setUp() public virtual {
// Set the block timestamp to the initTime, so that signatures recorded in the first block // Set the block timestamp to the initTime, so that signatures recorded in the first block
// are non-zero. // are non-zero.
vm.warp(initTime); vm.warp(INIT_TIME);
// Create a Safe with 10 owners // Create a Safe with 10 owners
(, uint256[] memory keys) = SafeTestLib.makeAddrsAndKeys("moduleTest", 10); (, uint256[] memory keys) = SafeTestLib.makeAddrsAndKeys("moduleTest", 10);
...@@ -58,8 +80,9 @@ contract LivenessModule_TestInit is Test, SafeTestTools { ...@@ -58,8 +80,9 @@ contract LivenessModule_TestInit is Test, SafeTestTools {
livenessModule = new LivenessModule({ livenessModule = new LivenessModule({
_safe: safeInstance.safe, _safe: safeInstance.safe,
_livenessGuard: livenessGuard, _livenessGuard: livenessGuard,
_livenessInterval: livenessInterval, _livenessInterval: LIVENESS_INTERVAL,
_minOwners: minOwners, _thresholdPercentage: THRESHOLD_PERCENTAGE,
_minOwners: MIN_OWNERS,
_fallbackOwner: fallbackOwner _fallbackOwner: fallbackOwner
}); });
safeInstance.setGuard(address(livenessGuard)); safeInstance.setGuard(address(livenessGuard));
...@@ -74,24 +97,26 @@ contract LivenessModule_Constructor_TestFail is LivenessModule_TestInit { ...@@ -74,24 +97,26 @@ contract LivenessModule_Constructor_TestFail is LivenessModule_TestInit {
new LivenessModule({ new LivenessModule({
_safe: safeInstance.safe, _safe: safeInstance.safe,
_livenessGuard: livenessGuard, _livenessGuard: livenessGuard,
_livenessInterval: livenessInterval, _livenessInterval: LIVENESS_INTERVAL,
_minOwners: 11, _thresholdPercentage: THRESHOLD_PERCENTAGE,
_minOwners: safeInstance.owners.length + 1,
_fallbackOwner: address(0) _fallbackOwner: address(0)
}); });
} }
/// @dev Tests that the constructor fails if the minOwners is greater than the number of owners /// @dev Tests that the constructor fails if the minOwners is greater than the number of owners
function test_constructor_wrongThreshold_reverts() external { function test_constructor_wrongThreshold_reverts() external {
uint256 wrongThreshold = livenessModule.get75PercentThreshold(safeInstance.owners.length) - 1; uint256 wrongThreshold = livenessModule.getRequiredThreshold(safeInstance.owners.length) - 1;
vm.mockCall( vm.mockCall(
address(safeInstance.safe), abi.encodeCall(OwnerManager.getThreshold, ()), abi.encode(wrongThreshold) address(safeInstance.safe), abi.encodeCall(OwnerManager.getThreshold, ()), abi.encode(wrongThreshold)
); );
vm.expectRevert("LivenessModule: Safe must have a threshold of at least 75% of the number of owners"); vm.expectRevert("LivenessModule: Insufficient threshold for the number of owners");
new LivenessModule({ new LivenessModule({
_safe: safeInstance.safe, _safe: safeInstance.safe,
_livenessGuard: livenessGuard, _livenessGuard: livenessGuard,
_livenessInterval: livenessInterval, _livenessInterval: LIVENESS_INTERVAL,
_minOwners: minOwners, _thresholdPercentage: THRESHOLD_PERCENTAGE,
_minOwners: MIN_OWNERS,
_fallbackOwner: address(0) _fallbackOwner: address(0)
}); });
} }
...@@ -104,6 +129,8 @@ contract LivenessModule_Getters_Test is LivenessModule_TestInit { ...@@ -104,6 +129,8 @@ contract LivenessModule_Getters_Test is LivenessModule_TestInit {
assertEq(address(livenessModule.livenessGuard()), address(livenessGuard)); assertEq(address(livenessModule.livenessGuard()), address(livenessGuard));
assertEq(livenessModule.livenessInterval(), 30 days); assertEq(livenessModule.livenessInterval(), 30 days);
assertEq(livenessModule.minOwners(), 6); assertEq(livenessModule.minOwners(), 6);
assertEq(livenessModule.thresholdPercentage(), THRESHOLD_PERCENTAGE);
assertEq(safeInstance.safe.getThreshold(), livenessModule.getRequiredThreshold(safeInstance.owners.length));
assertEq(livenessModule.fallbackOwner(), fallbackOwner); assertEq(livenessModule.fallbackOwner(), fallbackOwner);
} }
} }
...@@ -126,30 +153,93 @@ contract LivenessModule_CanRemove_Test is LivenessModule_TestInit { ...@@ -126,30 +153,93 @@ contract LivenessModule_CanRemove_Test is LivenessModule_TestInit {
} }
} }
contract LivenessModule_Get75PercentThreshold_Test is LivenessModule_TestInit { contract LivenessModule_GetRequiredThreshold_Test is LivenessModule_TestInit {
/// @dev check the return values of the get75PercentThreshold function against manually /// @dev Tests if getRequiredThreshold work correctly by implementing the same logic in a different manner
function _getLeastIntegerValueAbovePercentage(
uint256 _total,
uint256 _percentage
)
internal
pure
returns (uint256)
{
require(_percentage > 0 && _percentage <= 100);
uint256 toAdd;
// If the total multiplied by the percentage is not divisible by 100, we need to add 1 to the result to
// compensate for the rounding down by integer division.
if ((_total * _percentage) % 100 > 0) {
toAdd = 1;
}
return (_total * _percentage) / 100 + toAdd;
}
/// @dev Differentially tests the getRequiredThreshold function against _getLeastIntegerValueAbovePercentage
function testFuzz_getRequiredThreshold_works(uint256 _numOwners, uint256 _percentage) external {
// Enforce valid percentages
uint256 percentage = bound(_percentage, 1, 100);
// Enforce a sane number of owners to keep runtime in check
uint256 numOwners = bound(_numOwners, 1, 100);
assertEq(
_getRequiredThreshold(numOwners, percentage), _getLeastIntegerValueAbovePercentage(numOwners, percentage)
);
}
/// @dev check the return values of the getRequiredThreshold function against the boundary conditions of 1 and 100
/// percent.
function testFuzz_getRequiredThreshold_atBoundaries_works(uint256 _numOwners) external {
// Enforce a sane number of owners to keep runtime in check
uint256 numOwners = bound(_numOwners, 1, 100);
assertEq(_getRequiredThreshold(numOwners, 100), numOwners);
assertEq(_getRequiredThreshold(numOwners, 1), 1);
}
/// @dev check the return values of the getRequiredThreshold function against manually
/// calculated values. /// calculated values.
function test_get75PercentThreshold_Works() external view { function test_getRequiredThreshold_hardcoded_works() external {
assertEq(livenessModule.get75PercentThreshold(20), 15); // 75% threshold
assertEq(livenessModule.get75PercentThreshold(19), 15); assertEq(_getRequiredThreshold(20, 75), 15);
assertEq(livenessModule.get75PercentThreshold(18), 14); assertEq(_getRequiredThreshold(19, 75), 15);
assertEq(livenessModule.get75PercentThreshold(17), 13); assertEq(_getRequiredThreshold(18, 75), 14);
assertEq(livenessModule.get75PercentThreshold(16), 12); assertEq(_getRequiredThreshold(17, 75), 13);
assertEq(livenessModule.get75PercentThreshold(15), 12); assertEq(_getRequiredThreshold(16, 75), 12);
assertEq(livenessModule.get75PercentThreshold(14), 11); assertEq(_getRequiredThreshold(15, 75), 12);
assertEq(livenessModule.get75PercentThreshold(13), 10); assertEq(_getRequiredThreshold(14, 75), 11);
assertEq(livenessModule.get75PercentThreshold(12), 9); assertEq(_getRequiredThreshold(13, 75), 10);
assertEq(livenessModule.get75PercentThreshold(11), 9); assertEq(_getRequiredThreshold(12, 75), 9);
assertEq(livenessModule.get75PercentThreshold(10), 8); assertEq(_getRequiredThreshold(11, 75), 9);
assertEq(livenessModule.get75PercentThreshold(9), 7); assertEq(_getRequiredThreshold(10, 75), 8);
assertEq(livenessModule.get75PercentThreshold(8), 6); assertEq(_getRequiredThreshold(9, 75), 7);
assertEq(livenessModule.get75PercentThreshold(7), 6); assertEq(_getRequiredThreshold(8, 75), 6);
assertEq(livenessModule.get75PercentThreshold(6), 5); assertEq(_getRequiredThreshold(7, 75), 6);
assertEq(livenessModule.get75PercentThreshold(5), 4); assertEq(_getRequiredThreshold(6, 75), 5);
assertEq(livenessModule.get75PercentThreshold(4), 3); assertEq(_getRequiredThreshold(5, 75), 4);
assertEq(livenessModule.get75PercentThreshold(3), 3); assertEq(_getRequiredThreshold(4, 75), 3);
assertEq(livenessModule.get75PercentThreshold(2), 2); assertEq(_getRequiredThreshold(3, 75), 3);
assertEq(livenessModule.get75PercentThreshold(1), 1); assertEq(_getRequiredThreshold(2, 75), 2);
assertEq(_getRequiredThreshold(1, 75), 1);
// 33% threshold
assertEq(_getRequiredThreshold(20, 33), 7);
assertEq(_getRequiredThreshold(19, 33), 7);
assertEq(_getRequiredThreshold(18, 33), 6);
assertEq(_getRequiredThreshold(17, 33), 6);
assertEq(_getRequiredThreshold(16, 33), 6);
assertEq(_getRequiredThreshold(15, 33), 5);
assertEq(_getRequiredThreshold(14, 33), 5);
assertEq(_getRequiredThreshold(13, 33), 5);
assertEq(_getRequiredThreshold(12, 33), 4);
assertEq(_getRequiredThreshold(11, 33), 4);
assertEq(_getRequiredThreshold(10, 33), 4);
assertEq(_getRequiredThreshold(9, 33), 3);
assertEq(_getRequiredThreshold(8, 33), 3);
assertEq(_getRequiredThreshold(7, 33), 3);
assertEq(_getRequiredThreshold(6, 33), 2);
assertEq(_getRequiredThreshold(5, 33), 2);
assertEq(_getRequiredThreshold(4, 33), 2);
assertEq(_getRequiredThreshold(3, 33), 1);
assertEq(_getRequiredThreshold(2, 33), 1);
assertEq(_getRequiredThreshold(1, 33), 1);
} }
} }
...@@ -275,7 +365,7 @@ contract LivenessModule_RemoveOwners_TestFail is LivenessModule_TestInit { ...@@ -275,7 +365,7 @@ contract LivenessModule_RemoveOwners_TestFail is LivenessModule_TestInit {
); );
_warpPastLivenessInterval(); _warpPastLivenessInterval();
vm.expectRevert("LivenessModule: Safe must have a threshold of 75% of the number of owners"); vm.expectRevert("LivenessModule: Insufficient threshold for the number of owners");
livenessModule.removeOwners(prevOwners, ownersToRemove); livenessModule.removeOwners(prevOwners, ownersToRemove);
} }
} }
...@@ -325,11 +415,6 @@ contract LivenessModule_RemoveOwners_Test is LivenessModule_TestInit { ...@@ -325,11 +415,6 @@ contract LivenessModule_RemoveOwners_Test is LivenessModule_TestInit {
} }
} }
/// @dev A copy of LivenessModule.get75PercentThreshold as a free function to use below.
function get75PercentThreshold(uint256 _numOwners) pure returns (uint256 threshold_) {
threshold_ = (_numOwners * 75 + 99) / 100;
}
contract LivenessModule_RemoveOwnersFuzz_Test is LivenessModule_TestInit { contract LivenessModule_RemoveOwnersFuzz_Test is LivenessModule_TestInit {
using SafeTestLib for SafeInstance; using SafeTestLib for SafeInstance;
...@@ -349,7 +434,7 @@ contract LivenessModule_RemoveOwnersFuzz_Test is LivenessModule_TestInit { ...@@ -349,7 +434,7 @@ contract LivenessModule_RemoveOwnersFuzz_Test is LivenessModule_TestInit {
/// @dev This contract inherits the storage layout from the LivenessModule_TestInit contract, but we /// @dev This contract inherits the storage layout from the LivenessModule_TestInit contract, but we
/// override the base setUp function, to avoid instantiating an unnecessary Safe and liveness checking system. /// override the base setUp function, to avoid instantiating an unnecessary Safe and liveness checking system.
function setUp() public override { function setUp() public override {
vm.warp(initTime); vm.warp(INIT_TIME);
fallbackOwner = makeAddr("fallbackOwner"); fallbackOwner = makeAddr("fallbackOwner");
} }
...@@ -357,10 +442,11 @@ contract LivenessModule_RemoveOwnersFuzz_Test is LivenessModule_TestInit { ...@@ -357,10 +442,11 @@ contract LivenessModule_RemoveOwnersFuzz_Test is LivenessModule_TestInit {
function _prepare( function _prepare(
uint256 _numOwners, uint256 _numOwners,
uint256 _minOwners, uint256 _minOwners,
uint256 _numLiveOwners uint256 _numLiveOwners,
uint256 _thresholdPercentage
) )
internal internal
returns (uint256 numOwners_, uint256 minOwners_, uint256 numLiveOwners_) returns (uint256 numOwners_, uint256 minOwners_, uint256 numLiveOwners_, uint256 thresholdPercentage_)
{ {
// First we modify the test parameters to ensure that they describe a plausible starting point. // First we modify the test parameters to ensure that they describe a plausible starting point.
// //
...@@ -374,6 +460,9 @@ contract LivenessModule_RemoveOwnersFuzz_Test is LivenessModule_TestInit { ...@@ -374,6 +460,9 @@ contract LivenessModule_RemoveOwnersFuzz_Test is LivenessModule_TestInit {
// Ensure that _numLiveOwners is less than _numOwners so that we can remove at least one owner. // Ensure that _numLiveOwners is less than _numOwners so that we can remove at least one owner.
numLiveOwners_ = bound(_numLiveOwners, 0, numOwners_ - 1); numLiveOwners_ = bound(_numLiveOwners, 0, numOwners_ - 1);
// thresholdPercentage must be between a percentage greater than 0.
thresholdPercentage_ = bound(_thresholdPercentage, 1, 100);
// The above bounds are a bit tricky, so we assert that the resulting parameters enable us to test all possible // The above bounds are a bit tricky, so we assert that the resulting parameters enable us to test all possible
// success and revert cases in the removeOwners function. // success and revert cases in the removeOwners function.
// This is also necessary to avoid underflows or out of bounds accesses in the test. // This is also necessary to avoid underflows or out of bounds accesses in the test.
...@@ -385,13 +474,14 @@ contract LivenessModule_RemoveOwnersFuzz_Test is LivenessModule_TestInit { ...@@ -385,13 +474,14 @@ contract LivenessModule_RemoveOwnersFuzz_Test is LivenessModule_TestInit {
// Create a Safe with _numOwners owners // Create a Safe with _numOwners owners
(, uint256[] memory keys) = SafeTestLib.makeAddrsAndKeys("rmOwnersTest", numOwners_); (, uint256[] memory keys) = SafeTestLib.makeAddrsAndKeys("rmOwnersTest", numOwners_);
uint256 threshold = get75PercentThreshold(numOwners_); uint256 threshold = _getRequiredThreshold(numOwners_, thresholdPercentage_);
safeInstance = _setupSafe(keys, threshold); safeInstance = _setupSafe(keys, threshold);
livenessGuard = new LivenessGuard(safeInstance.safe); livenessGuard = new LivenessGuard(safeInstance.safe);
livenessModule = new LivenessModule({ livenessModule = new LivenessModule({
_safe: safeInstance.safe, _safe: safeInstance.safe,
_livenessGuard: livenessGuard, _livenessGuard: livenessGuard,
_livenessInterval: livenessInterval, _livenessInterval: LIVENESS_INTERVAL,
_thresholdPercentage: thresholdPercentage_,
_minOwners: minOwners_, _minOwners: minOwners_,
_fallbackOwner: fallbackOwner _fallbackOwner: fallbackOwner
}); });
...@@ -407,13 +497,15 @@ contract LivenessModule_RemoveOwnersFuzz_Test is LivenessModule_TestInit { ...@@ -407,13 +497,15 @@ contract LivenessModule_RemoveOwnersFuzz_Test is LivenessModule_TestInit {
uint256 _numOwners, uint256 _numOwners,
uint256 _minOwners, uint256 _minOwners,
uint256 _numLiveOwners, uint256 _numLiveOwners,
uint256 _thresholdPercentage,
uint256 _shutDownBehavior, uint256 _shutDownBehavior,
uint256 _numOwnersToRemoveinShutDown uint256 _numOwnersToRemoveinShutDown
) )
external external
{ {
// Prepare the test env and test params // Prepare the test env and test params
(uint256 numOwners, uint256 minOwners, uint256 numLiveOwners) = _prepare(_numOwners, _minOwners, _numLiveOwners); (uint256 numOwners, uint256 minOwners, uint256 numLiveOwners, uint256 thresholdPercentage) =
_prepare(_numOwners, _minOwners, _numLiveOwners, _thresholdPercentage);
// Create an array of live owners, and call showLiveness for each of them // Create an array of live owners, and call showLiveness for each of them
address[] memory liveOwners = new address[](numLiveOwners); address[] memory liveOwners = new address[](numLiveOwners);
...@@ -437,7 +529,7 @@ contract LivenessModule_RemoveOwnersFuzz_Test is LivenessModule_TestInit { ...@@ -437,7 +529,7 @@ contract LivenessModule_RemoveOwnersFuzz_Test is LivenessModule_TestInit {
// Validate the resulting state of the Safe // Validate the resulting state of the Safe
assertEq(safeInstance.safe.getOwners().length, numLiveOwners); assertEq(safeInstance.safe.getOwners().length, numLiveOwners);
assertEq(safeInstance.safe.getThreshold(), get75PercentThreshold(numLiveOwners)); assertEq(safeInstance.safe.getThreshold(), _getRequiredThreshold(numLiveOwners, thresholdPercentage));
for (uint256 i; i < numLiveOwners; i++) { for (uint256 i; i < numLiveOwners; i++) {
assertTrue(safeInstance.safe.isOwner(liveOwners[i])); assertTrue(safeInstance.safe.isOwner(liveOwners[i]));
} }
...@@ -507,7 +599,7 @@ contract LivenessModule_RemoveOwnersFuzz_Test is LivenessModule_TestInit { ...@@ -507,7 +599,7 @@ contract LivenessModule_RemoveOwnersFuzz_Test is LivenessModule_TestInit {
} }
// For both of the incorrect behaviors, verify no change to the Safe state // For both of the incorrect behaviors, verify no change to the Safe state
assertEq(safeInstance.safe.getOwners().length, numOwners); assertEq(safeInstance.safe.getOwners().length, numOwners);
assertEq(safeInstance.safe.getThreshold(), get75PercentThreshold(numOwners)); assertEq(safeInstance.safe.getThreshold(), _getRequiredThreshold(numOwners, thresholdPercentage));
for (uint256 i; i < numOwners; i++) { for (uint256 i; i < numOwners; i++) {
assertTrue(safeInstance.safe.isOwner(safeInstance.owners[i])); assertTrue(safeInstance.safe.isOwner(safeInstance.owners[i]));
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment