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

Commit 135c74a

Browse files
Introducing Governance support into Protocol (#35)
1 parent c48a09b commit 135c74a

File tree

16 files changed

+566
-29
lines changed

16 files changed

+566
-29
lines changed

contracts/AccessController.sol

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { IPAccountChecker } from "contracts/lib/registries/IPAccountChecker.sol"
1010
import { IIPAccount } from "contracts/interfaces/IIPAccount.sol";
1111
import { AccessPermission } from "contracts/lib/AccessPermission.sol";
1212
import { Errors } from "contracts/lib/Errors.sol";
13+
import { Governable } from "contracts/governance/Governable.sol";
1314

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

3233
address public IP_ACCOUNT_REGISTRY;
3334
address public MODULE_REGISTRY;
3435

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

37-
// TODO: can only be called by protocol admin
38-
function initialize(address ipAccountRegistry_, address moduleRegistry_) external {
39-
IP_ACCOUNT_REGISTRY = ipAccountRegistry_;
40-
MODULE_REGISTRY = moduleRegistry_;
38+
constructor(address governance) Governable(governance) {}
39+
40+
function initialize(address ipAccountRegistry, address moduleRegistry) external onlyProtocolAdmin {
41+
IP_ACCOUNT_REGISTRY = ipAccountRegistry;
42+
MODULE_REGISTRY = moduleRegistry;
4143
}
4244

4345
/// @notice Sets the permission for all IPAccounts
44-
function setGlobalPermission(address signer_, address to_, bytes4 func_, uint8 permission_) external {
45-
// TODO: access controller can only be called by protocol admin
46+
function setGlobalPermission(
47+
address signer_,
48+
address to_,
49+
bytes4 func_,
50+
uint8 permission_
51+
) external onlyProtocolAdmin {
4652
if (signer_ == address(0)) {
4753
revert Errors.AccessController__SignerIsZeroAddress();
4854
}
@@ -65,7 +71,13 @@ contract AccessController is IAccessController {
6571
/// @param to_ The recipient of the transaction (support wildcard permission)
6672
/// @param func_ The function selector (support wildcard permission)
6773
/// @param permission_ The permission level (0 => ABSTAIN, 1 => ALLOW, 3 => DENY)
68-
function setPermission(address ipAccount_, address signer_, address to_, bytes4 func_, uint8 permission_) external {
74+
function setPermission(
75+
address ipAccount_,
76+
address signer_,
77+
address to_,
78+
bytes4 func_,
79+
uint8 permission_
80+
) external whenNotPaused {
6981
// IPAccount and signer does not support wildcard permission
7082
if (ipAccount_ == address(0)) {
7183
revert Errors.AccessController__IPAccountIsZeroAddress();
@@ -117,7 +129,7 @@ contract AccessController is IAccessController {
117129
address signer_,
118130
address to_,
119131
bytes4 func_
120-
) external view returns (bool) {
132+
) external view whenNotPaused returns (bool) {
121133
// ipAccount_ can only call registered modules or set Permissions
122134
if (to_ != address(this) && !IModuleRegistry(MODULE_REGISTRY).isRegistered(to_)) {
123135
return false;
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
// See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf
3+
pragma solidity ^0.8.23;
4+
5+
import { Errors } from "contracts/lib/Errors.sol";
6+
import { IGovernance } from "contracts/interfaces/governance/IGovernance.sol";
7+
import { IGovernable } from "../interfaces/governance/IGovernable.sol";
8+
import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";
9+
import { GovernanceLib } from "contracts/lib/GovernanceLib.sol";
10+
/// @title Governable
11+
/// @dev All contracts managed by governance should inherit from this contract.
12+
abstract contract Governable is IGovernable {
13+
/// @notice The address of the governance.
14+
address public governance;
15+
16+
/// @dev Ensures that the function is called by the protocol admin.
17+
modifier onlyProtocolAdmin() {
18+
if(!IGovernance(governance).hasRole(GovernanceLib.PROTOCOL_ADMIN, msg.sender)) {
19+
revert Errors.Governance__OnlyProtocolAdmin();
20+
}
21+
_;
22+
}
23+
24+
modifier whenNotPaused() {
25+
if (IGovernance(governance).getState() == GovernanceLib.ProtocolState.Paused) {
26+
revert Errors.Governance__ProtocolPaused();
27+
}
28+
_;
29+
}
30+
31+
/// @notice Constructs a new Governable contract.
32+
/// @param governance_ The address of the governance.
33+
constructor(address governance_) {
34+
if (governance_ == address(0)) revert Errors.Governance__ZeroAddress();
35+
governance = governance_;
36+
emit GovernanceUpdated(governance);
37+
}
38+
39+
/// @notice Sets a new governance address.
40+
/// @param newGovernance The address of the new governance.
41+
function setGovernance(address newGovernance) external onlyProtocolAdmin {
42+
if (newGovernance == address(0)) revert Errors.Governance__ZeroAddress();
43+
if (!ERC165Checker.supportsInterface(newGovernance, type(IGovernance).interfaceId))
44+
revert Errors.Governance__UnsupportedInterface("IGovernance");
45+
if (IGovernance(newGovernance).getState() != IGovernance(governance).getState())
46+
revert Errors.Governance__InconsistentState();
47+
governance = newGovernance;
48+
emit GovernanceUpdated(newGovernance);
49+
}
50+
51+
/// @notice Returns the current governance address.
52+
/// @return The address of the current governance.
53+
function getGovernance() external view returns (address) {
54+
return governance;
55+
}
56+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
// See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf
3+
pragma solidity ^0.8.23;
4+
5+
import { Errors } from "contracts/lib/Errors.sol";
6+
import { AccessControl } from "@openzeppelin/contracts/access/AccessControl.sol";
7+
import { IGovernance } from "contracts/interfaces/governance/IGovernance.sol";
8+
import { GovernanceLib } from "contracts/lib/GovernanceLib.sol";
9+
10+
/// @title Governance
11+
/// @dev This contract is used for governance of the protocol.
12+
contract Governance is AccessControl, IGovernance {
13+
GovernanceLib.ProtocolState internal state;
14+
15+
/// @notice Creates a new Governance contract.
16+
/// @param admin The address of the initial admin.
17+
constructor(address admin) {
18+
if (admin == address(0)) revert Errors.Governance__ZeroAddress();
19+
_grantRole(GovernanceLib.PROTOCOL_ADMIN, admin);
20+
}
21+
22+
/// @notice Sets the state of the protocol.
23+
/// @param newState The new state of the protocol.
24+
function setState(GovernanceLib.ProtocolState newState) external override {
25+
if (!hasRole(GovernanceLib.PROTOCOL_ADMIN, msg.sender)) revert Errors.Governance__OnlyProtocolAdmin();
26+
if (newState == state) revert Errors.Governance__NewStateIsTheSameWithOldState();
27+
emit StateSet(msg.sender, state, newState, block.timestamp);
28+
state = newState;
29+
}
30+
31+
/// @notice Returns the current state of the protocol.
32+
/// @return The current state of the protocol.
33+
function getState() external view override returns (GovernanceLib.ProtocolState) {
34+
return state;
35+
}
36+
37+
/// @notice Checks if the contract supports a specific interface.
38+
/// @param interfaceId The id of the interface.
39+
/// @return True if the contract supports the interface, false otherwise.
40+
function supportsInterface(bytes4 interfaceId) public view override returns (bool) {
41+
return (interfaceId == type(IGovernance).interfaceId || super.supportsInterface(interfaceId));
42+
}
43+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
// See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf
3+
pragma solidity ^0.8.23;
4+
5+
6+
/// @title IGovernable
7+
/// @notice This is the interface for the Lens Protocol main governance functions.
8+
interface IGovernable {
9+
/// @notice Emitted when the governance is updated
10+
/// @param newGovernance The address of the new governance
11+
event GovernanceUpdated(address indexed newGovernance);
12+
/// @notice Sets the governance address
13+
/// @param newGovernance The address of the new governance
14+
function setGovernance(address newGovernance) external;
15+
/// @notice Returns the current governance address
16+
/// @return The address of the current governance
17+
function getGovernance() external view returns (address);
18+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
// See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf
3+
pragma solidity ^0.8.23;
4+
5+
import { IAccessControl } from "@openzeppelin/contracts/access/IAccessControl.sol";
6+
import { GovernanceLib } from "contracts/lib/GovernanceLib.sol";
7+
8+
/// @title IGovernance
9+
/// @dev This interface defines the governance functionality for the protocol.
10+
interface IGovernance is IAccessControl {
11+
/// @notice Emitted when the protocol state is set
12+
/// @param account The address that triggered the state change
13+
/// @param prevState The previous state of the protocol
14+
/// @param newState The new state of the protocol
15+
/// @param timestamp The time when the state change occurred
16+
event StateSet(
17+
address indexed account,
18+
GovernanceLib.ProtocolState prevState,
19+
GovernanceLib.ProtocolState newState,
20+
uint256 timestamp
21+
);
22+
23+
/// @notice Sets the state of the protocol
24+
/// @dev This function can only be called by an account with the appropriate role
25+
/// @param newState The new state to set for the protocol
26+
function setState(GovernanceLib.ProtocolState newState) external;
27+
28+
/// @notice Returns the current state of the protocol
29+
/// @return The current state of the protocol
30+
function getState() external view returns (GovernanceLib.ProtocolState);
31+
}

contracts/lib/Errors.sol

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,16 @@ pragma solidity ^0.8.19;
55
/// @title Errors Library
66
/// @notice Library for all Story Protocol contract errors.
77
library Errors {
8+
////////////////////////////////////////////////////////////////////////////
9+
// Governance //
10+
////////////////////////////////////////////////////////////////////////////
11+
error Governance__OnlyProtocolAdmin();
12+
error Governance__ZeroAddress();
13+
error Governance__ProtocolPaused();
14+
error Governance__InconsistentState();
15+
error Governance__NewStateIsTheSameWithOldState();
16+
error Governance__UnsupportedInterface(string interfaceName);
17+
818
////////////////////////////////////////////////////////////////////////////
919
// IPAccount //
1020
////////////////////////////////////////////////////////////////////////////

contracts/lib/GovernanceLib.sol

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
// See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf
3+
pragma solidity ^0.8.23;
4+
5+
/// @title Governance
6+
/// @dev This library provides types for Story Protocol Governance.
7+
library GovernanceLib {
8+
9+
bytes32 public constant PROTOCOL_ADMIN = bytes32(0);
10+
11+
/// @notice An enum containing the different states the protocol can be in.
12+
/// @param Unpaused The unpaused state.
13+
/// @param Paused The paused state.
14+
enum ProtocolState {
15+
Unpaused,
16+
Paused
17+
}
18+
}

contracts/registries/ModuleRegistry.sol

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,21 @@ import { IModuleRegistry } from "contracts/interfaces/registries/IModuleRegistry
66
import { Errors } from "contracts/lib/Errors.sol";
77
import { IModule } from "contracts/interfaces/modules/base/IModule.sol";
88
import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
9+
import { Governable } from "contracts/governance/Governable.sol";
910

1011
/// @title ModuleRegistry
11-
contract ModuleRegistry is IModuleRegistry {
12+
contract ModuleRegistry is IModuleRegistry, Governable {
1213
using Strings for *;
1314

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

18+
constructor(address governance) Governable(governance) {}
19+
1720
/// @notice Registers a new module in the protocol.
1821
/// @param name The name of the module.
1922
/// @param moduleAddress The address of the module.
20-
function registerModule(string memory name, address moduleAddress) external {
23+
function registerModule(string memory name, address moduleAddress) external onlyProtocolAdmin {
2124
// TODO: check can only called by protocol admin
2225
if (moduleAddress == address(0)) {
2326
revert Errors.ModuleRegistry__ModuleAddressZeroAddress();
@@ -45,7 +48,7 @@ contract ModuleRegistry is IModuleRegistry {
4548

4649
/// @notice Removes a module from the protocol.
4750
/// @param name The name of the module to be removed.
48-
function removeModule(string memory name) external {
51+
function removeModule(string memory name) external onlyProtocolAdmin {
4952
if (bytes(name).length == 0) {
5053
revert Errors.ModuleRegistry__NameEmptyString();
5154
}

script/foundry/deployment/Main.s.sol

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { RoyaltyModule } from "contracts/modules/royalty-module/RoyaltyModule.so
2828
import { DisputeModule } from "contracts/modules/dispute-module/DisputeModule.sol";
2929
import { MockERC721 } from "contracts/mocks/MockERC721.sol";
3030
import { IPResolver } from "contracts/resolvers/IPResolver.sol";
31+
import { Governance } from "contracts/governance/Governance.sol";
3132

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

42+
Governance public governance;
43+
4144
address public constant ERC6551_REGISTRY = address(0x000000006551c19487814612e58FE06813775758);
4245
AccessController public accessController;
4346

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

97+
contractKey = "Governance";
98+
_predeploy(contractKey);
99+
governance = new Governance(accessControldeployer);
100+
_postdeploy(contractKey, address(governance));
101+
94102
mockNft = new MockERC721();
95103

96104
contractKey = "AccessController";
97105
_predeploy(contractKey);
98-
accessController = new AccessController();
106+
accessController = new AccessController(address(governance));
99107
_postdeploy(contractKey, address(accessController));
100108

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

106114
contractKey = "ModuleRegistry";
107115
_predeploy(contractKey);
108-
moduleRegistry = new ModuleRegistry();
116+
moduleRegistry = new ModuleRegistry(address(governance));
109117
_postdeploy(contractKey, address(moduleRegistry));
110118

111119
contractKey = "LicenseRegistry";

test/foundry/AccessController.t.sol

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { MockAccessController } from "test/foundry/mocks/MockAccessController.so
1818
import { MockERC721 } from "test/foundry/mocks/MockERC721.sol";
1919
import { MockModule } from "test/foundry/mocks/MockModule.sol";
2020
import { MockOrchestratorModule } from "test/foundry/mocks/MockOrchestratorModule.sol";
21+
import { Governance } from "contracts/governance/Governance.sol";
2122

2223
contract AccessControllerTest is Test {
2324
AccessController public accessController;
@@ -31,27 +32,24 @@ contract AccessControllerTest is Test {
3132
ERC6551Registry public erc6551Registry = new ERC6551Registry();
3233
address owner = vm.addr(1);
3334
uint256 tokenId = 100;
35+
Governance public governance;
3436

3537
function setUp() public {
36-
accessController = new AccessController();
38+
governance = new Governance(address(this));
39+
accessController = new AccessController(address(governance));
3740
implementation = new IPAccountImpl();
3841
ipAccountRegistry = new IPAccountRegistry(
3942
address(erc6551Registry),
4043
address(accessController),
4144
address(implementation)
4245
);
43-
moduleRegistry = new ModuleRegistry();
46+
moduleRegistry = new ModuleRegistry(address(governance));
4447
accessController.initialize(address(ipAccountRegistry), address(moduleRegistry));
4548
nft.mintId(owner, tokenId);
4649
address deployedAccount = ipAccountRegistry.registerIpAccount(block.chainid, address(nft), tokenId);
4750
ipAccount = IIPAccount(payable(deployedAccount));
4851

4952
mockModule = new MockModule(address(ipAccountRegistry), address(moduleRegistry), "MockModule");
50-
// moduleWithoutPermission = new MockModule(
51-
// address(ipAccountRegistry),
52-
// address(moduleRegistry),
53-
// "ModuleWithoutPermission"
54-
// );
5553
}
5654

5755
// test owner can set permission

0 commit comments

Comments
 (0)