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
......
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