Skip to content

Commit

Permalink
ctb: Add DeputyGuardianModule (ethereum-optimism#9982)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
maurelian authored Apr 18, 2024
1 parent 02c1193 commit 4424552
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 66 deletions.
4 changes: 2 additions & 2 deletions packages/contracts-bedrock/semver-lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@
"sourceCodeHash": "0x9633cea9b66077e222f470439fe3e9a31f3e33b4f7a5618374c44310fd234b24"
},
"src/Safe/LivenessModule.sol": {
"initCodeHash": "0xcd8b76f70634330e242d4ff497bba1f8e0aaa11d7aecf9845090029c43027a7f",
"sourceCodeHash": "0x3b358b51fce4f1516191104d2e1f782c8ec3edecee63c502cc301671aa632b93"
"initCodeHash": "0xa8b233f0f26f8a73b997b12ba06d64cefa8ee98d523f68cd63320e9787468fae",
"sourceCodeHash": "0x73aa5934e56ba2a45f368806c5db1d442bf5713d51b2184749f4638eaceb832e"
},
"src/cannon/MIPS.sol": {
"initCodeHash": "0xaf2ac814f64ccf12e9c6738db7cef865f51f9e39f39105adef9fba11465f6ee1",
Expand Down
22 changes: 20 additions & 2 deletions packages/contracts-bedrock/snapshots/abi/LivenessModule.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
"name": "_minOwners",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_thresholdPercentage",
"type": "uint256"
},
{
"internalType": "address",
"name": "_fallbackOwner",
Expand Down Expand Up @@ -70,15 +75,15 @@
"type": "uint256"
}
],
"name": "get75PercentThreshold",
"name": "getRequiredThreshold",
"outputs": [
{
"internalType": "uint256",
"name": "threshold_",
"type": "uint256"
}
],
"stateMutability": "pure",
"stateMutability": "view",
"type": "function"
},
{
Expand Down Expand Up @@ -151,6 +156,19 @@
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "thresholdPercentage",
"outputs": [
{
"internalType": "uint256",
"name": "thresholdPercentage_",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "version",
Expand Down
37 changes: 25 additions & 12 deletions packages/contracts-bedrock/src/Safe/LivenessModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ contract LivenessModule is ISemver {
/// This can be updated by replacing with a new module.
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
/// This can be updated by replacing with a new module.
address internal immutable FALLBACK_OWNER;
Expand All @@ -44,34 +47,38 @@ contract LivenessModule is ISemver {
uint256 internal constant GUARD_STORAGE_SLOT = 0x4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c8;

/// @notice Semantic version.
/// @custom:semver 1.0.0
string public constant version = "1.0.0";
/// @custom:semver 1.1.0
string public constant version = "1.1.0";

// Constructor to initialize the Safe and baseModule instances
constructor(
Safe _safe,
LivenessGuard _livenessGuard,
uint256 _livenessInterval,
uint256 _minOwners,
uint256 _thresholdPercentage,
address _fallbackOwner
) {
SAFE = _safe;
LIVENESS_GUARD = _livenessGuard;
LIVENESS_INTERVAL = _livenessInterval;
THRESHOLD_PERCENTAGE = _thresholdPercentage;
FALLBACK_OWNER = _fallbackOwner;
MIN_OWNERS = _minOwners;
address[] memory owners = _safe.getOwners();
require(_minOwners <= owners.length, "LivenessModule: minOwners must be less than the number of owners");
require(
_safe.getThreshold() >= get75PercentThreshold(owners.length),
"LivenessModule: Safe must have a threshold of at least 75% of the number of owners"
_safe.getThreshold() >= getRequiredThreshold(owners.length),
"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.
function get75PercentThreshold(uint256 _numOwners) public pure returns (uint256 threshold_) {
threshold_ = (_numOwners * 75 + 99) / 100;
function getRequiredThreshold(uint256 _numOwners) public view returns (uint256 threshold_) {
threshold_ = (_numOwners * THRESHOLD_PERCENTAGE + 99) / 100;
}

/// @notice Getter function for the Safe contract instance
Expand All @@ -98,7 +105,13 @@ contract LivenessModule is ISemver {
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
function fallbackOwner() public view returns (address fallbackOwner_) {
fallbackOwner_ = FALLBACK_OWNER;
Expand Down Expand Up @@ -161,7 +174,7 @@ contract LivenessModule is ISemver {
/// @param _newOwnersCount New number of owners after removal.
function _removeOwner(address _prevOwner, address _ownerToRemove, uint256 _newOwnersCount) internal {
if (_newOwnersCount > 0) {
uint256 newThreshold = get75PercentThreshold(_newOwnersCount);
uint256 newThreshold = getRequiredThreshold(_newOwnersCount);
// Remove the owner and update the threshold
_removeOwnerSafeCall({ _prevOwner: _prevOwner, _owner: _ownerToRemove, _threshold: newThreshold });
} else {
Expand Down Expand Up @@ -223,11 +236,11 @@ contract LivenessModule is ISemver {

// 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
// owner, because get75PercentThreshold(1) returns 1.
// owner, because getRequiredThreshold(1) returns 1.
uint256 threshold = SAFE.getThreshold();
require(
threshold == get75PercentThreshold(numOwners),
"LivenessModule: Safe must have a threshold of 75% of the number of owners"
threshold == getRequiredThreshold(numOwners),
"LivenessModule: Insufficient threshold for the number of owners"
);

// Check that the guard has not been changed
Expand Down
Loading

0 comments on commit 4424552

Please sign in to comment.