Skip to content
This repository has been archived by the owner on Apr 30, 2024. It is now read-only.

Introducing Governance support into Protocol #35

Merged
merged 3 commits into from
Jan 27, 2024
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
30 changes: 21 additions & 9 deletions contracts/AccessController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { IPAccountChecker } from "contracts/lib/registries/IPAccountChecker.sol"
import { IIPAccount } from "contracts/interfaces/IIPAccount.sol";
import { AccessPermission } from "contracts/lib/AccessPermission.sol";
import { Errors } from "contracts/lib/Errors.sol";
import { Governable } from "contracts/governance/Governable.sol";

/// @title AccessController
/// @dev This contract is used to control access permissions for different function calls in the protocol.
Expand All @@ -26,23 +27,28 @@ import { Errors } from "contracts/lib/Errors.sol";
/// - setPermission: Sets the permission for a specific function call.
/// - getPermission: Returns the permission level for a specific function call.
/// - checkPermission: Checks if a specific function call is allowed.
contract AccessController is IAccessController {
contract AccessController is IAccessController, Governable {
using IPAccountChecker for IIPAccountRegistry;

address public IP_ACCOUNT_REGISTRY;
address public MODULE_REGISTRY;

mapping(address => mapping(address => mapping(address => mapping(bytes4 => uint8)))) public permissions;

// TODO: can only be called by protocol admin
function initialize(address ipAccountRegistry_, address moduleRegistry_) external {
IP_ACCOUNT_REGISTRY = ipAccountRegistry_;
MODULE_REGISTRY = moduleRegistry_;
constructor(address governance) Governable(governance) {}

function initialize(address ipAccountRegistry, address moduleRegistry) external onlyProtocolAdmin {
IP_ACCOUNT_REGISTRY = ipAccountRegistry;
MODULE_REGISTRY = moduleRegistry;
}

/// @notice Sets the permission for all IPAccounts
function setGlobalPermission(address signer_, address to_, bytes4 func_, uint8 permission_) external {
// TODO: access controller can only be called by protocol admin
function setGlobalPermission(
address signer_,
address to_,
bytes4 func_,
uint8 permission_
) external onlyProtocolAdmin {
if (signer_ == address(0)) {
revert Errors.AccessController__SignerIsZeroAddress();
}
Expand All @@ -65,7 +71,13 @@ contract AccessController is IAccessController {
/// @param to_ The recipient of the transaction (support wildcard permission)
/// @param func_ The function selector (support wildcard permission)
/// @param permission_ The permission level (0 => ABSTAIN, 1 => ALLOW, 3 => DENY)
function setPermission(address ipAccount_, address signer_, address to_, bytes4 func_, uint8 permission_) external {
function setPermission(
address ipAccount_,
address signer_,
address to_,
bytes4 func_,
uint8 permission_
) external whenNotPaused {
// IPAccount and signer does not support wildcard permission
if (ipAccount_ == address(0)) {
revert Errors.AccessController__IPAccountIsZeroAddress();
Expand Down Expand Up @@ -117,7 +129,7 @@ contract AccessController is IAccessController {
address signer_,
address to_,
bytes4 func_
) external view returns (bool) {
) external view whenNotPaused returns (bool) {
// ipAccount_ can only call registered modules or set Permissions
if (to_ != address(this) && !IModuleRegistry(MODULE_REGISTRY).isRegistered(to_)) {
return false;
Expand Down
56 changes: 56 additions & 0 deletions contracts/governance/Governable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// SPDX-License-Identifier: UNLICENSED
// See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf
pragma solidity ^0.8.23;

import { Errors } from "contracts/lib/Errors.sol";
import { IGovernance } from "contracts/interfaces/governance/IGovernance.sol";
import { IGovernable } from "../interfaces/governance/IGovernable.sol";
import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";
import { GovernanceLib } from "contracts/lib/GovernanceLib.sol";
/// @title Governable
/// @dev All contracts managed by governance should inherit from this contract.
abstract contract Governable is IGovernable {
/// @notice The address of the governance.
address public governance;

/// @dev Ensures that the function is called by the protocol admin.
modifier onlyProtocolAdmin() {
if(!IGovernance(governance).hasRole(GovernanceLib.PROTOCOL_ADMIN, msg.sender)) {
revert Errors.Governance__OnlyProtocolAdmin();
}
_;
}

modifier whenNotPaused() {
if (IGovernance(governance).getState() == GovernanceLib.ProtocolState.Paused) {
revert Errors.Governance__ProtocolPaused();
}
_;
}

/// @notice Constructs a new Governable contract.
/// @param governance_ The address of the governance.
constructor(address governance_) {
if (governance_ == address(0)) revert Errors.Governance__ZeroAddress();
governance = governance_;
emit GovernanceUpdated(governance);
}

/// @notice Sets a new governance address.
/// @param newGovernance The address of the new governance.
function setGovernance(address newGovernance) external onlyProtocolAdmin {
if (newGovernance == address(0)) revert Errors.Governance__ZeroAddress();
if (!ERC165Checker.supportsInterface(newGovernance, type(IGovernance).interfaceId))
revert Errors.Governance__UnsupportedInterface("IGovernance");
if (IGovernance(newGovernance).getState() != IGovernance(governance).getState())
revert Errors.Governance__InconsistentState();
governance = newGovernance;
emit GovernanceUpdated(newGovernance);
}

/// @notice Returns the current governance address.
/// @return The address of the current governance.
function getGovernance() external view returns (address) {
return governance;
}
}
43 changes: 43 additions & 0 deletions contracts/governance/Governance.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// SPDX-License-Identifier: UNLICENSED
// See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf
pragma solidity ^0.8.23;

import { Errors } from "contracts/lib/Errors.sol";
import { AccessControl } from "@openzeppelin/contracts/access/AccessControl.sol";
import { IGovernance } from "contracts/interfaces/governance/IGovernance.sol";
import { GovernanceLib } from "contracts/lib/GovernanceLib.sol";

/// @title Governance
/// @dev This contract is used for governance of the protocol.
contract Governance is AccessControl, IGovernance {
GovernanceLib.ProtocolState internal state;

/// @notice Creates a new Governance contract.
/// @param admin The address of the initial admin.
constructor(address admin) {
if (admin == address(0)) revert Errors.Governance__ZeroAddress();
_grantRole(GovernanceLib.PROTOCOL_ADMIN, admin);
}

/// @notice Sets the state of the protocol.
/// @param newState The new state of the protocol.
function setState(GovernanceLib.ProtocolState newState) external override {
if (!hasRole(GovernanceLib.PROTOCOL_ADMIN, msg.sender)) revert Errors.Governance__OnlyProtocolAdmin();
if (newState == state) revert Errors.Governance__NewStateIsTheSameWithOldState();
emit StateSet(msg.sender, state, newState, block.timestamp);
state = newState;
}

/// @notice Returns the current state of the protocol.
/// @return The current state of the protocol.
function getState() external view override returns (GovernanceLib.ProtocolState) {
return state;
}

/// @notice Checks if the contract supports a specific interface.
/// @param interfaceId The id of the interface.
/// @return True if the contract supports the interface, false otherwise.
function supportsInterface(bytes4 interfaceId) public view override returns (bool) {
return (interfaceId == type(IGovernance).interfaceId || super.supportsInterface(interfaceId));
}
}
18 changes: 18 additions & 0 deletions contracts/interfaces/governance/IGovernable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: UNLICENSED
// See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf
pragma solidity ^0.8.23;


/// @title IGovernable
/// @notice This is the interface for the Lens Protocol main governance functions.
interface IGovernable {
/// @notice Emitted when the governance is updated
/// @param newGovernance The address of the new governance
event GovernanceUpdated(address indexed newGovernance);
/// @notice Sets the governance address
/// @param newGovernance The address of the new governance
function setGovernance(address newGovernance) external;
/// @notice Returns the current governance address
/// @return The address of the current governance
function getGovernance() external view returns (address);
}
31 changes: 31 additions & 0 deletions contracts/interfaces/governance/IGovernance.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: UNLICENSED
// See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf
pragma solidity ^0.8.23;

import { IAccessControl } from "@openzeppelin/contracts/access/IAccessControl.sol";
import { GovernanceLib } from "contracts/lib/GovernanceLib.sol";

/// @title IGovernance
/// @dev This interface defines the governance functionality for the protocol.
interface IGovernance is IAccessControl {
/// @notice Emitted when the protocol state is set
/// @param account The address that triggered the state change
/// @param prevState The previous state of the protocol
/// @param newState The new state of the protocol
/// @param timestamp The time when the state change occurred
event StateSet(
address indexed account,
GovernanceLib.ProtocolState prevState,
GovernanceLib.ProtocolState newState,
uint256 timestamp
);

/// @notice Sets the state of the protocol
/// @dev This function can only be called by an account with the appropriate role
/// @param newState The new state to set for the protocol
function setState(GovernanceLib.ProtocolState newState) external;

/// @notice Returns the current state of the protocol
/// @return The current state of the protocol
function getState() external view returns (GovernanceLib.ProtocolState);
}
10 changes: 10 additions & 0 deletions contracts/lib/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ pragma solidity ^0.8.19;
/// @title Errors Library
/// @notice Library for all Story Protocol contract errors.
library Errors {
////////////////////////////////////////////////////////////////////////////
// Governance //
////////////////////////////////////////////////////////////////////////////
error Governance__OnlyProtocolAdmin();
error Governance__ZeroAddress();
error Governance__ProtocolPaused();
error Governance__InconsistentState();
error Governance__NewStateIsTheSameWithOldState();
error Governance__UnsupportedInterface(string interfaceName);

////////////////////////////////////////////////////////////////////////////
// IPAccount //
////////////////////////////////////////////////////////////////////////////
Expand Down
18 changes: 18 additions & 0 deletions contracts/lib/GovernanceLib.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: UNLICENSED
// See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf
pragma solidity ^0.8.23;

/// @title Governance
/// @dev This library provides types for Story Protocol Governance.
library GovernanceLib {

bytes32 public constant PROTOCOL_ADMIN = bytes32(0);

/// @notice An enum containing the different states the protocol can be in.
/// @param Unpaused The unpaused state.
/// @param Paused The paused state.
enum ProtocolState {
Unpaused,
Paused
}
}
9 changes: 6 additions & 3 deletions contracts/registries/ModuleRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,21 @@ import { IModuleRegistry } from "contracts/interfaces/registries/IModuleRegistry
import { Errors } from "contracts/lib/Errors.sol";
import { IModule } from "contracts/interfaces/modules/base/IModule.sol";
import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
import { Governable } from "contracts/governance/Governable.sol";

/// @title ModuleRegistry
contract ModuleRegistry is IModuleRegistry {
contract ModuleRegistry is IModuleRegistry, Governable {
using Strings for *;

mapping(string => address) public _modules;
mapping(address => bool) public _isModule;

constructor(address governance) Governable(governance) {}

/// @notice Registers a new module in the protocol.
/// @param name The name of the module.
/// @param moduleAddress The address of the module.
function registerModule(string memory name, address moduleAddress) external {
function registerModule(string memory name, address moduleAddress) external onlyProtocolAdmin {
// TODO: check can only called by protocol admin
if (moduleAddress == address(0)) {
revert Errors.ModuleRegistry__ModuleAddressZeroAddress();
Expand Down Expand Up @@ -45,7 +48,7 @@ contract ModuleRegistry is IModuleRegistry {

/// @notice Removes a module from the protocol.
/// @param name The name of the module to be removed.
function removeModule(string memory name) external {
function removeModule(string memory name) external onlyProtocolAdmin {
if (bytes(name).length == 0) {
revert Errors.ModuleRegistry__NameEmptyString();
}
Expand Down
12 changes: 10 additions & 2 deletions script/foundry/deployment/Main.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { RoyaltyModule } from "contracts/modules/royalty-module/RoyaltyModule.so
import { DisputeModule } from "contracts/modules/dispute-module/DisputeModule.sol";
import { MockERC721 } from "contracts/mocks/MockERC721.sol";
import { IPResolver } from "contracts/resolvers/IPResolver.sol";
import { Governance } from "contracts/governance/Governance.sol";

// script
import { StringUtil } from "script/foundry/utils/StringUtil.sol";
Expand All @@ -38,6 +39,8 @@ contract Main is Script, BroadcastManager, JsonDeploymentHandler {
using StringUtil for uint256;
using stdJson for string;

Governance public governance;

address public constant ERC6551_REGISTRY = address(0x000000006551c19487814612e58FE06813775758);
AccessController public accessController;

Expand Down Expand Up @@ -91,11 +94,16 @@ contract Main is Script, BroadcastManager, JsonDeploymentHandler {
function _deployProtocolContracts(address accessControldeployer) private {
string memory contractKey;

contractKey = "Governance";
_predeploy(contractKey);
governance = new Governance(accessControldeployer);
_postdeploy(contractKey, address(governance));

mockNft = new MockERC721();

contractKey = "AccessController";
_predeploy(contractKey);
accessController = new AccessController();
accessController = new AccessController(address(governance));
_postdeploy(contractKey, address(accessController));

contractKey = "IPAccountImpl";
Expand All @@ -105,7 +113,7 @@ contract Main is Script, BroadcastManager, JsonDeploymentHandler {

contractKey = "ModuleRegistry";
_predeploy(contractKey);
moduleRegistry = new ModuleRegistry();
moduleRegistry = new ModuleRegistry(address(governance));
_postdeploy(contractKey, address(moduleRegistry));

contractKey = "LicenseRegistry";
Expand Down
12 changes: 5 additions & 7 deletions test/foundry/AccessController.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { MockAccessController } from "test/foundry/mocks/MockAccessController.so
import { MockERC721 } from "test/foundry/mocks/MockERC721.sol";
import { MockModule } from "test/foundry/mocks/MockModule.sol";
import { MockOrchestratorModule } from "test/foundry/mocks/MockOrchestratorModule.sol";
import { Governance } from "contracts/governance/Governance.sol";

contract AccessControllerTest is Test {
AccessController public accessController;
Expand All @@ -31,27 +32,24 @@ contract AccessControllerTest is Test {
ERC6551Registry public erc6551Registry = new ERC6551Registry();
address owner = vm.addr(1);
uint256 tokenId = 100;
Governance public governance;

function setUp() public {
accessController = new AccessController();
governance = new Governance(address(this));
accessController = new AccessController(address(governance));
implementation = new IPAccountImpl();
ipAccountRegistry = new IPAccountRegistry(
address(erc6551Registry),
address(accessController),
address(implementation)
);
moduleRegistry = new ModuleRegistry();
moduleRegistry = new ModuleRegistry(address(governance));
accessController.initialize(address(ipAccountRegistry), address(moduleRegistry));
nft.mintId(owner, tokenId);
address deployedAccount = ipAccountRegistry.registerIpAccount(block.chainid, address(nft), tokenId);
ipAccount = IIPAccount(payable(deployedAccount));

mockModule = new MockModule(address(ipAccountRegistry), address(moduleRegistry), "MockModule");
// moduleWithoutPermission = new MockModule(
// address(ipAccountRegistry),
// address(moduleRegistry),
// "ModuleWithoutPermission"
// );
}

// test owner can set permission
Expand Down
6 changes: 5 additions & 1 deletion test/foundry/IPAccount.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,21 @@ import "contracts/registries/ModuleRegistry.sol";
import "test/foundry/mocks/MockAccessController.sol";
import "test/foundry/mocks/MockERC721.sol";
import "test/foundry/mocks/MockModule.sol";
import { Governance } from "contracts/governance/Governance.sol";

contract IPAccountTest is Test {
IPAccountRegistry public registry;
IPAccountImpl public implementation;
MockERC721 nft = new MockERC721();
ERC6551Registry public erc6551Registry = new ERC6551Registry();
MockAccessController public accessController = new MockAccessController();
ModuleRegistry public moduleRegistry = new ModuleRegistry();
ModuleRegistry public moduleRegistry;
MockModule public module;
Governance public governance;

function setUp() public {
governance = new Governance(address(this));
moduleRegistry = new ModuleRegistry(address(governance));
implementation = new IPAccountImpl();
registry = new IPAccountRegistry(address(erc6551Registry), address(accessController), address(implementation));
module = new MockModule(address(registry), address(moduleRegistry), "MockModule");
Expand Down
Loading
Loading