Access Controller

Access Controller manages all permission-related states and permission checks in Story Protocol. In particular, it maintains the Permission Table and Permission Engine to process and store permissions. IPAccount permissions are set by the IPAccount owner.

Permission Table

Permission Record

IPAccountSigner (caller)To (only module)Function SigPermission
0x123..1110x789..2220x790..3330xAaAaAaAaAllow
0x123..1110x789..2220x790..3330xBBBBBBBBDeny
0x123..1110x789..2220x790..3330xCCCCCCAbstain

Each record defines a permission in the form of the Signer (caller) calling the Func of the To (module) on behalf of the IPAccount.

The permission field can be set as "Allow," "Deny," or "Abstain." Abstain indicates that the permission decision is determined by the upper-level permission.

Wildcard

Wildcard is also supported when defining permissions; it defines a permission that applies to multiple modules and/or functions.

With wildcards, users can easily define a whitelist or blacklist of permissions.

IPAccountSigner (caller)To (module)FuncPermission
0x123..1110x789..222**Allow
0x123..1110x789..2220x790..333*Deny

The above example shows that the signer (0x789...) is unable to invoke any functions of the module (0x790...) on behalf of the IPAccount (0x123...).

In other words, the IPAccount has blacklisted the signer from calling any functions on the module 0x790...333

  • Supported wildcards:
ParameterWildcard
Funcbytes4(0)
Addresses (IPAccount / To)address(0)

Permission Prioritization

Specific permissions override general permissions.

IPAccountSigner (caller)To (module)FuncPermission
0x123..1110x789..222**Allow
0x123..1110x789..2220x790..333*Deny
0x123..1110x789..2220x790..3330xCCCCDDDDAllow

The above shows that the signer (0x789...) is not allowed to call any functions of the module (0x790...) on behalf of IPAccount (0x123...), except for the function 0xCCCCDDDD

Furthermore, the signer (0x789...) is permitted to call all other modules on behalf of IPAccount (0x123...).

Global Permission

Unlike IPAccount permissions, which only apply to specific IPAccounts, global permissions apply to all IPAccounts. Only Story Protocol's governance can set global permissions.

The main use cases for global permissions are to allow trusted/core modules (developed by Story Protocol) to call all IPAccounts without the need for individual approval from each IPAccount.

IPAccountSigner (caller)To (module)FuncPermission
*0x999..9990x888..888*Allow
*0x777..777**Allow

The signer(0x999…) can call any functions of the module (0x888…) on behalf of any IPAccount.
The signer(0x777…) can call any module on behalf of any IPAccount.

Call Flows with Access Control

There exist three types of call flows expected by the Access Controller.

  1. An IPAccount calls a module directly.
  2. A module calls another module directly.
  3. A module calls a registry directly.

IPAccount calling a Module directly

  • IPAccount performs a permission check with the Access Controller.
  • The module only needs to check if the msg.sender is a valid IPAccount.

When calling a module from an IPAccount, the IPAccount performs an access control check with AccessController to determine if the current caller has permission to make the call. In the module, it only needs to check whether the transaction msg.sender is a valid IPAccount.

AccessControlled provide a modifier onlyIpAccount() helps to perform the access control check.

contract MockModule is IModule, AccessControlled {
    function action(string memory param) external view onlyIpAccount() returns (string memory) {
            // do something
    }
}

Module calling another Module

  • The callee module needs to perform the authorization check itself.

When a module is called directly from another module, it is responsible for performing the access control check using AccessController. This check determines whether the current caller has permission to make the call to the module.

AccessControlled provide a modifier verifyPermission(address ipAccount) helps to perform the access control check.

contract MockModule is IModule, AccessControlled {
    function callFromAnotherModule(address ipAccount) external verifyPermission(ipAccount) returns (string memory) {
        if (!IAccessController(accessController).checkPermission(ipAccount, msg.sender, address(this), this.callFromAnotherModule.selector)) {
		        revert Unauthorized();
        }
			  // do something
    }
}

Module calling Registry

  • The registry performs the authorization check by calling AccessController.
  • The registry authorizes modules through set global permission

When a registry is called by a module, it can perform the access control check using AccessController. This check determines whether the callee module has permission to call the registry.

// called by StoryProtocl Admin
IAccessController(accessController).setGlobalPermission(address(0), address(module), address(registry), bytes4(0))) {

contract MockRegistry {
    function registerAction() external returns (string memory) {
        if (!IAccessController(accessController).checkPermission(address(0), msg.sender, address(this), this.registerAction.selector)) {
		        revert Unauthorized();
        }
			  // do something
    }
}

📘

The IPAccount's permissions will be revoked upon transfer of ownership.

The permissions associated with the IPAccount are exclusively linked to its current owner. When the ownership of the IPAccount is transferred to a new individual, the existing permissions granted to the previous owner are automatically revoked. This ensures that only the current, legitimate owner has access to these permissions. If, in the future, the IPAccount ownership is transferred back to the original owner, the permissions that were initially revoked will be reinstated, restoring the original owner's access and control.