Commit 71bb67b5 authored by Maurelian's avatar Maurelian

feat: Address feedback

parent 41c9a3d4
...@@ -23,7 +23,7 @@ contract LivenessGuard is ISemver, BaseGuard { ...@@ -23,7 +23,7 @@ contract LivenessGuard is ISemver, BaseGuard {
/// @notice Emitted when an owner is recorded. /// @notice Emitted when an owner is recorded.
/// @param owner The owner's address. /// @param owner The owner's address.
event OwnerRecorded(bytes32 indexed txHash, address owner); event OwnerRecorded(address owner);
/// @notice Semantic version. /// @notice Semantic version.
/// @custom:semver 1.0.0 /// @custom:semver 1.0.0
...@@ -49,12 +49,18 @@ contract LivenessGuard is ISemver, BaseGuard { ...@@ -49,12 +49,18 @@ contract LivenessGuard is ISemver, BaseGuard {
for (uint256 i = 0; i < owners.length; i++) { for (uint256 i = 0; i < owners.length; i++) {
address owner = owners[i]; address owner = owners[i];
lastLive[owner] = block.timestamp; lastLive[owner] = block.timestamp;
emit OwnerRecorded(0x0, owner); emit OwnerRecorded(owner);
} }
} }
/// @notice Getter function for the Safe contract instance
/// @return safe_ The Safe contract instance
function safe() public view returns (Safe safe_) {
safe_ = SAFE;
}
/// @notice Internal function to ensure that only the Safe can call certain functions. /// @notice Internal function to ensure that only the Safe can call certain functions.
function _onlySafe() internal view { function _requireOnlySafe() internal view {
require(msg.sender == address(SAFE), "LivenessGuard: only Safe can call this function"); require(msg.sender == address(SAFE), "LivenessGuard: only Safe can call this function");
} }
...@@ -76,7 +82,7 @@ contract LivenessGuard is ISemver, BaseGuard { ...@@ -76,7 +82,7 @@ contract LivenessGuard is ISemver, BaseGuard {
external external
{ {
msgSender; // silence unused variable warning msgSender; // silence unused variable warning
_onlySafe(); _requireOnlySafe();
// Cache the set of owners prior to execution. // Cache the set of owners prior to execution.
// This will be used in the checkAfterExecution method. // This will be used in the checkAfterExecution method.
...@@ -88,7 +94,7 @@ contract LivenessGuard is ISemver, BaseGuard { ...@@ -88,7 +94,7 @@ contract LivenessGuard is ISemver, BaseGuard {
// This call will reenter to the Safe which is calling it. This is OK because it is only reading the // This call will reenter to the Safe which is calling it. This is OK because it is only reading the
// nonce, and using the getTransactionHash() method. // nonce, and using the getTransactionHash() method.
bytes32 txHash = Safe(payable(msg.sender)).getTransactionHash({ bytes32 txHash = SAFE.getTransactionHash({
to: to, to: to,
value: value, value: value,
data: data, data: data,
...@@ -98,7 +104,7 @@ contract LivenessGuard is ISemver, BaseGuard { ...@@ -98,7 +104,7 @@ contract LivenessGuard is ISemver, BaseGuard {
gasPrice: gasPrice, gasPrice: gasPrice,
gasToken: gasToken, gasToken: gasToken,
refundReceiver: refundReceiver, refundReceiver: refundReceiver,
_nonce: Safe(payable(msg.sender)).nonce() - 1 _nonce: SAFE.nonce() - 1
}); });
uint256 threshold = SAFE.getThreshold(); uint256 threshold = SAFE.getThreshold();
...@@ -107,7 +113,7 @@ contract LivenessGuard is ISemver, BaseGuard { ...@@ -107,7 +113,7 @@ contract LivenessGuard is ISemver, BaseGuard {
for (uint256 i = 0; i < signers.length; i++) { for (uint256 i = 0; i < signers.length; i++) {
lastLive[signers[i]] = block.timestamp; lastLive[signers[i]] = block.timestamp;
emit OwnerRecorded(txHash, signers[i]); emit OwnerRecorded(signers[i]);
} }
} }
...@@ -118,7 +124,7 @@ contract LivenessGuard is ISemver, BaseGuard { ...@@ -118,7 +124,7 @@ contract LivenessGuard is ISemver, BaseGuard {
/// 1. Add new owners to the lastLive mapping /// 1. Add new owners to the lastLive mapping
/// 2. Delete removed owners from the lastLive mapping /// 2. Delete removed owners from the lastLive mapping
function checkAfterExecution(bytes32, bool) external { function checkAfterExecution(bytes32, bool) external {
_onlySafe(); _requireOnlySafe();
// Get the current set of owners // Get the current set of owners
address[] memory ownersAfter = SAFE.getOwners(); address[] memory ownersAfter = SAFE.getOwners();
...@@ -132,6 +138,7 @@ contract LivenessGuard is ISemver, BaseGuard { ...@@ -132,6 +138,7 @@ contract LivenessGuard is ISemver, BaseGuard {
lastLive[ownerAfter] = block.timestamp; lastLive[ownerAfter] = block.timestamp;
} }
} }
// Now iterate over the remaining ownersBefore entries. Any remaining addresses are no longer an owner, so we // Now iterate over the remaining ownersBefore entries. Any remaining addresses are no longer an owner, so we
// delete them from the lastLive mapping. // delete them from the lastLive mapping.
uint256 ownersBeforeLength = ownersBefore.length(); uint256 ownersBeforeLength = ownersBefore.length();
...@@ -147,12 +154,6 @@ contract LivenessGuard is ISemver, BaseGuard { ...@@ -147,12 +154,6 @@ contract LivenessGuard is ISemver, BaseGuard {
require(SAFE.isOwner(msg.sender), "LivenessGuard: only Safe owners may demonstrate liveness"); require(SAFE.isOwner(msg.sender), "LivenessGuard: only Safe owners may demonstrate liveness");
lastLive[msg.sender] = block.timestamp; lastLive[msg.sender] = block.timestamp;
emit OwnerRecorded(0x0, msg.sender); emit OwnerRecorded(msg.sender);
}
/// @notice Getter function for the Safe contract instance
/// @return safe_ The Safe contract instance
function safe() public view returns (Safe safe_) {
safe_ = SAFE;
} }
} }
...@@ -55,7 +55,7 @@ contract LivenessModule is ISemver { ...@@ -55,7 +55,7 @@ contract LivenessModule is ISemver {
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() == get75PercentThreshold(owners.length),
"LivenessModule: Safe must have a threshold of 75% of the number of owners" "LivenessModule: Safe must have a threshold of 75% of the number of owners"
...@@ -70,7 +70,7 @@ contract LivenessModule is ISemver { ...@@ -70,7 +70,7 @@ contract LivenessModule is ISemver {
function removeOwners(address[] memory _previousOwners, address[] memory _ownersToRemove) external { function removeOwners(address[] memory _previousOwners, address[] memory _ownersToRemove) external {
require(_previousOwners.length == _ownersToRemove.length, "LivenessModule: arrays must be the same length"); require(_previousOwners.length == _ownersToRemove.length, "LivenessModule: arrays must be the same length");
// We will remove at least one owner, so we'll initialize the newOwners count to the current number of owners // We will remove at least one owner, so we'll initialize the ownersCount count to the current number of owners
uint256 ownersCount = SAFE.getOwners().length; uint256 ownersCount = SAFE.getOwners().length;
for (uint256 i = 0; i < _previousOwners.length; i++) { for (uint256 i = 0; i < _previousOwners.length; i++) {
ownersCount--; ownersCount--;
...@@ -81,8 +81,7 @@ contract LivenessModule is ISemver { ...@@ -81,8 +81,7 @@ contract LivenessModule is ISemver {
_removeOwner({ _removeOwner({
_prevOwner: _previousOwners[i], _prevOwner: _previousOwners[i],
_ownerToRemove: _ownersToRemove[i], _ownerToRemove: _ownersToRemove[i],
_newOwnersCount: ownersCount, _newOwnersCount: ownersCount
_newThreshold: get75PercentThreshold(ownersCount)
}); });
} }
_verifyFinalState(); _verifyFinalState();
...@@ -92,18 +91,11 @@ contract LivenessModule is ISemver { ...@@ -92,18 +91,11 @@ contract LivenessModule is ISemver {
/// @param _prevOwner Owner that pointed to the owner to be removed in the linked list /// @param _prevOwner Owner that pointed to the owner to be removed in the linked list
/// @param _ownerToRemove Owner address to be removed. /// @param _ownerToRemove Owner address to be removed.
/// @param _newOwnersCount New number of owners after removal. /// @param _newOwnersCount New number of owners after removal.
/// @param _newThreshold New threshold. function _removeOwner(address _prevOwner, address _ownerToRemove, uint256 _newOwnersCount) internal {
function _removeOwner(
address _prevOwner,
address _ownerToRemove,
uint256 _newOwnersCount,
uint256 _newThreshold
)
internal
{
if (_newOwnersCount > 0) { if (_newOwnersCount > 0) {
uint256 newThreshold = get75PercentThreshold(_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 {
// There is only one owner left. The Safe will not allow a safe with no owners, so we will // There is only one owner left. The Safe will not allow a safe with no owners, so we will
// need to swap owners instead. // need to swap owners instead.
......
...@@ -13,7 +13,7 @@ import { LivenessGuard } from "src/Safe/LivenessGuard.sol"; ...@@ -13,7 +13,7 @@ import { LivenessGuard } from "src/Safe/LivenessGuard.sol";
contract LivenessGuard_TestInit is Test, SafeTestTools { contract LivenessGuard_TestInit is Test, SafeTestTools {
using SafeTestLib for SafeInstance; using SafeTestLib for SafeInstance;
event OwnerRecorded(bytes32 indexed txHash, address signer); event OwnerRecorded(address owner);
uint256 initTime = 10; uint256 initTime = 10;
LivenessGuard livenessGuard; LivenessGuard livenessGuard;
...@@ -81,7 +81,7 @@ contract LivenessGuard_CheckTx_Test is LivenessGuard_TestInit { ...@@ -81,7 +81,7 @@ contract LivenessGuard_CheckTx_Test is LivenessGuard_TestInit {
for (uint256 i; i < signers.length; i++) { for (uint256 i; i < signers.length; i++) {
// Don't check topic1 so that we can avoid the ugly txHash calculation. // Don't check topic1 so that we can avoid the ugly txHash calculation.
vm.expectEmit(false, true, true, true, address(livenessGuard)); vm.expectEmit(false, true, true, true, address(livenessGuard));
emit OwnerRecorded(0x0, signers[i]); emit OwnerRecorded(signers[i]);
} }
vm.expectCall(address(safeInstance.safe), abi.encodeWithSignature("nonce()")); vm.expectCall(address(safeInstance.safe), abi.encodeWithSignature("nonce()"));
vm.expectCall(address(safeInstance.safe), abi.encodeCall(OwnerManager.getThreshold, ())); vm.expectCall(address(safeInstance.safe), abi.encodeCall(OwnerManager.getThreshold, ()));
...@@ -116,7 +116,7 @@ contract LivenessGuard_ShowLiveness_Test is LivenessGuard_TestInit { ...@@ -116,7 +116,7 @@ contract LivenessGuard_ShowLiveness_Test is LivenessGuard_TestInit {
address caller = safeInstance.owners[0]; address caller = safeInstance.owners[0];
vm.expectEmit(address(livenessGuard)); vm.expectEmit(address(livenessGuard));
emit OwnerRecorded(0x0, caller); emit OwnerRecorded(caller);
vm.prank(caller); vm.prank(caller);
livenessGuard.showLiveness(); livenessGuard.showLiveness();
......
...@@ -92,13 +92,14 @@ The following security properties must be upheld: ...@@ -92,13 +92,14 @@ The following security properties must be upheld:
1. Signatures are assigned to the correct signer. 1. Signatures are assigned to the correct signer.
1. Non-signers are unable to create a record of having signed. 1. Non-signers are unable to create a record of having signed.
1. A signer cannot be censored or grieffed such that their signing is not recorded. 1. A signer cannot be censored or griefed such that their signing is not recorded.
1. Signers may demonstrate liveness either by signing a transaction or by calling directly to the 1. Signers may demonstrate liveness either by signing a transaction or by calling directly to the
guard. guard.
1. The module only removes a signer if they have demonstrated liveness during the interval, or 1. The module only removes a signer if they have demonstrated liveness during the interval, or
if necessary to convert the safe to a 1 of 1. if necessary to convert the safe to a 1 of 1.
1. The module sets the correct 75% threshold upon removing a signer. 1. The module sets the correct 75% threshold upon removing a signer.
1. During a shutdown the module correctly removes all signers, and converts the safe to a 1 of 1. 1. During a shutdown the module correctly removes all signers, and converts the safe to a 1 of 1.
1. It must be impossible for the guard's checkTransaction or checkAfterExecution to permanently revert given any calldata and the current state.
### Interdependency between the Guard and Module ### Interdependency between the Guard and Module
...@@ -108,7 +109,7 @@ be set on the Safe, and the Module will be unable to function if the Guard is re ...@@ -108,7 +109,7 @@ be set on the Safe, and the Module will be unable to function if the Guard is re
### Deployment ### Deployment
The module are guard are intended to be deployed and installed on the safe in the following sequence: The module and guard are intended to be deployed and installed on the safe in the following sequence:
1. Deploy the guard contract, this will set a timestamp for each existing owner on the Safe. 1. Deploy the guard contract, this will set a timestamp for each existing owner on the Safe.
1. Deploy the module. 1. Deploy the module.
......
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