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

Commit

Permalink
Introducing Governance support into Protocol (#35)
Browse files Browse the repository at this point in the history
  • Loading branch information
kingster-will authored and jdubpark committed Jan 29, 2024
1 parent 8836092 commit a46562d
Show file tree
Hide file tree
Showing 16 changed files with 566 additions and 29 deletions.
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

0 comments on commit a46562d

Please sign in to comment.