Skip to content
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

Add Owner Guardianship #1732

Merged
merged 2 commits into from
Aug 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions protocol/governance/contracts/modules/core/GuardianModule.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//SPDX-License-Identifier: MIT
pragma solidity >=0.8.11 <0.9.0;

import {AccessError} from "@synthetixio/core-contracts/contracts/errors/AccessError.sol";
import {AddressError} from "@synthetixio/core-contracts/contracts/errors/AddressError.sol";
import {ChangeError} from "@synthetixio/core-contracts/contracts/errors/ChangeError.sol";
import {Guardian} from "../../storage/Guardian.sol";

contract GuardianModule {
/**
* @notice Thrown when an address tries to accept guardian role but has not been nominated.
* @param addr The address that is trying to accept the guardian role.
*/
error NotNominated(address addr);

/**
* @notice Emitted when an address has been nominated.
* @param newOwner The address that has been nominated.
*/
event GuardianNominated(address newOwner);

/**
* @notice Emitted when the guardianship of the contract has changed.
* @param oldOwner The previous guardian of the contract.
* @param newOwner The new guardian of the contract.
*/
event GuardianChanged(address oldOwner, address newOwner);

function acceptGuardianship() public {
Guardian.Data storage store = Guardian.load();

address currentNominatedGuardian = store.nominatedGuardian;
if (msg.sender != currentNominatedGuardian) {
revert NotNominated(msg.sender);
}

emit GuardianChanged(store.guardian, currentNominatedGuardian);

store.guardian = currentNominatedGuardian;
store.nominatedGuardian = address(0);
store.ownershipRequestedAt = 0;
}

function nominateNewGuardian(address newNominatedGuardian) public {
Guardian.onlyGuardian();

Guardian.Data storage store = Guardian.load();

if (newNominatedGuardian == address(0)) {
revert AddressError.ZeroAddress();
}

if (newNominatedGuardian == store.nominatedGuardian) {
revert ChangeError.NoChange();
}

store.nominatedGuardian = newNominatedGuardian;
emit GuardianNominated(newNominatedGuardian);
}

function renounceGuardianNomination() external {
Guardian.Data storage store = Guardian.load();

if (store.nominatedGuardian != msg.sender) {
revert NotNominated(msg.sender);
}

store.nominatedGuardian = address(0);
store.ownershipRequestedAt = 0;
}
}
54 changes: 53 additions & 1 deletion protocol/governance/contracts/modules/core/OwnerModule.sol
Original file line number Diff line number Diff line change
@@ -1,9 +1,61 @@
//SPDX-License-Identifier: MIT
pragma solidity >=0.8.11 <0.9.0;

import {AccessError} from "@synthetixio/core-contracts/contracts/errors/AccessError.sol";
import {AddressError} from "@synthetixio/core-contracts/contracts/errors/AddressError.sol";
import {ChangeError} from "@synthetixio/core-contracts/contracts/errors/ChangeError.sol";
import {SafeCastU256} from "@synthetixio/core-contracts/contracts/utils/SafeCast.sol";
import {OwnerModule as BaseOwnerModule} from "@synthetixio/core-modules/contracts/modules/OwnerModule.sol";
import {OwnableStorage} from "@synthetixio/core-contracts/contracts/ownership/OwnableStorage.sol";
import {Guardian} from "../../storage/Guardian.sol";

// solhint-disable-next-line no-empty-blocks
contract OwnerModule is BaseOwnerModule {
using SafeCastU256 for uint256;

/**
* @notice Thrown when an the ownership is accepted before the nomination delay
*/
error OwnershipAcceptanceTooEarly();

function acceptOwnership() public override {
super.acceptOwnership();

Guardian.Data storage store = Guardian.load();

if (block.timestamp.to64() - store.ownershipRequestedAt < Guardian.RESCUE_DELAY) {
revert OwnershipAcceptanceTooEarly();
}

store.ownershipRequestedAt = 0;
}

function nominateNewOwner(address newNominatedOwner) public override {
Guardian.onlyGuardian();

OwnableStorage.Data storage ownableStore = OwnableStorage.load();
Guardian.Data storage guardianStore = Guardian.load();

if (newNominatedOwner == address(0)) {
revert AddressError.ZeroAddress();
}

if (
newNominatedOwner == ownableStore.nominatedOwner ||
newNominatedOwner == ownableStore.owner
) {
revert ChangeError.NoChange();
}

guardianStore.ownershipRequestedAt = block.timestamp.to64();
ownableStore.nominatedOwner = newNominatedOwner;

emit OwnerNominated(newNominatedOwner);
}

function renounceNomination() external override {
super.renounceNomination();

Guardian.Data storage store = Guardian.load();
store.ownershipRequestedAt = 0;
}
}
34 changes: 34 additions & 0 deletions protocol/governance/contracts/storage/Guardian.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//SPDX-License-Identifier: MIT
pragma solidity >=0.8.11 <0.9.0;

import {AccessError} from "@synthetixio/core-contracts/contracts/errors/AccessError.sol";

library Guardian {
bytes32 private constant _STORAGE_SLOT =
keccak256(abi.encode("io.synthetix.governance.Guardian"));

uint64 public constant RESCUE_DELAY = 7 days;

struct Data {
address guardian;
address nominatedGuardian;
uint64 ownershipRequestedAt;
}

function load() internal pure returns (Data storage store) {
bytes32 s = _STORAGE_SLOT;
assembly {
store.slot := s
}
}

function onlyGuardian() internal view {
if (msg.sender != getGuardian()) {
revert AccessError.Unauthorized(msg.sender);
}
}

function getGuardian() internal view returns (address) {
return Guardian.load().guardian;
}
}
Loading