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

NatSpec, Comments, and Standards #109

Merged
merged 5 commits into from
Feb 17, 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
168 changes: 84 additions & 84 deletions contracts/AccessController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,26 +33,36 @@ contract AccessController is IAccessController, Governable {
address public IP_ACCOUNT_REGISTRY;
address public MODULE_REGISTRY;

/// @dev encoded permission => permission
/// encoded permission = keccak256(abi.encodePacked(ipAccount, signer, to, func))
mapping(bytes32 => uint8) public permissions;
/// @dev Tracks the permission granted to an encoded permission path, where the
/// encoded permission path = keccak256(abi.encodePacked(ipAccount, signer, to, func))
mapping(bytes32 => uint8) internal encodedPermissions;

constructor(address governance) Governable(governance) {}

// TODO: Change the function name to not clash with potential proxy contract `initialize`.
// TODO: Only allow calling once.
/// @dev Initialize the Access Controller with the IP Account Registry and Module Registry addresses.
/// These are separated from the constructor, because we need to deploy the AccessController first for
/// to deploy many registry and module contracts, including the IP Account Registry and Module Registry.
/// @dev Enforced to be only callable by the protocol admin in governance.
/// @param ipAccountRegistry The address of the IP Account Registry.
/// @param moduleRegistry The address of the Module Registry.
function initialize(address ipAccountRegistry, address moduleRegistry) external onlyProtocolAdmin {
IP_ACCOUNT_REGISTRY = ipAccountRegistry;
MODULE_REGISTRY = moduleRegistry;
}

/// @inheritdoc IAccessController
jdubpark marked this conversation as resolved.
Show resolved Hide resolved
function setBatchPermissions(AccessPermission.Permission[] memory permissions_) external whenNotPaused {
for (uint256 i = 0; i < permissions_.length; ) {
/// @notice Sets a batch of permissions in a single transaction.
/// @dev This function allows setting multiple permissions at once. Pausable.
/// @param permissions An array of `Permission` structs, each representing the permission to be set.
function setBatchPermissions(AccessPermission.Permission[] memory permissions) external whenNotPaused {
for (uint256 i = 0; i < permissions.length; ) {
setPermission(
permissions_[i].ipAccount,
permissions_[i].signer,
permissions_[i].to,
permissions_[i].func,
permissions_[i].permission
permissions[i].ipAccount,
permissions[i].signer,
permissions[i].to,
permissions[i].func,
permissions[i].permission
);
unchecked {
i += 1;
Expand All @@ -61,145 +71,135 @@ contract AccessController is IAccessController, Governable {
}

/// @notice Sets the permission for all IPAccounts
function setGlobalPermission(
address signer_,
address to_,
bytes4 func_,
uint8 permission_
) external onlyProtocolAdmin {
if (signer_ == address(0)) {
/// @dev Enforced to be only callable by the protocol admin in governance.
/// @param signer The address that can call `to` on behalf of the IP account
/// @param to The address that can be called by the `signer` (currently only modules can be `to`)
/// @param func The function selector of `to` that can be called by the `signer` on behalf of the `ipAccount`
/// @param permission The new permission level
function setGlobalPermission(address signer, address to, bytes4 func, uint8 permission) external onlyProtocolAdmin {
if (signer == address(0)) {
revert Errors.AccessController__SignerIsZeroAddress();
}
// permission must be one of ABSTAIN, ALLOW, DENY
if (permission_ > 2) {
if (permission > 2) {
revert Errors.AccessController__PermissionIsNotValid();
}
_setPermission(address(0), signer_, to_, func_, permission_);
emit PermissionSet(address(0), address(0), signer_, to_, func_, permission_);
_setPermission(address(0), signer, to, func, permission);
emit PermissionSet(address(0), address(0), signer, to, func, permission);
}

/// @notice Sets the permission for a specific function call
/// @dev By default, all policies are set to ABSTAIN, which means that the permission is not set
/// Owner of ipAccount by default has permission sets the permission
/// permission 0 => ABSTAIN, 1 => ALLOW, 3 => DENY
/// @dev Each policy is represented as a mapping from an IP account address to a signer address to a recipient
/// address to a function selector to a permission level. The permission level can be 0 (ABSTAIN), 1 (ALLOW), or
/// 2 (DENY).
/// @dev By default, all policies are set to 0 (ABSTAIN), which means that the permission is not set.
/// The owner of ipAccount by default has all permission.
/// address(0) => wildcard
/// bytes4(0) => wildcard
/// specific permission overrides wildcard permission
/// @param ipAccount_ The account that owns the IP (not support wildcard permission)
/// @param signer_ The account that signs the transaction (not support wildcard permission)
/// @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)
/// Specific permission overrides wildcard permission.
/// @param ipAccount The address of the IP account that grants the permission for `signer`
/// @param signer The address that can call `to` on behalf of the `ipAccount`
/// @param to The address that can be called by the `signer` (currently only modules can be `to`)
/// @param func The function selector of `to` that can be called by the `signer` on behalf of the `ipAccount`
/// @param permission The new permission level
function setPermission(
address ipAccount_,
address signer_,
address to_,
bytes4 func_,
uint8 permission_
address ipAccount,
address signer,
address to,
bytes4 func,
uint8 permission
) public whenNotPaused {
// IPAccount and signer does not support wildcard permission
if (ipAccount_ == address(0)) {
if (ipAccount == address(0)) {
revert Errors.AccessController__IPAccountIsZeroAddress();
}
if (signer_ == address(0)) {
if (signer == address(0)) {
revert Errors.AccessController__SignerIsZeroAddress();
}
if (!IIPAccountRegistry(IP_ACCOUNT_REGISTRY).isIpAccount(ipAccount_)) {
revert Errors.AccessController__IPAccountIsNotValid(ipAccount_);
if (!IIPAccountRegistry(IP_ACCOUNT_REGISTRY).isIpAccount(ipAccount)) {
revert Errors.AccessController__IPAccountIsNotValid(ipAccount);
}
// permission must be one of ABSTAIN, ALLOW, DENY
if (permission_ > 2) {
if (permission > 2) {
revert Errors.AccessController__PermissionIsNotValid();
}
if (!IModuleRegistry(MODULE_REGISTRY).isRegistered(msg.sender) && ipAccount_ != msg.sender) {
if (!IModuleRegistry(MODULE_REGISTRY).isRegistered(msg.sender) && ipAccount != msg.sender) {
revert Errors.AccessController__CallerIsNotIPAccount();
}
_setPermission(ipAccount_, signer_, to_, func_, permission_);
_setPermission(ipAccount, signer, to, func, permission);

emit PermissionSet(IIPAccount(payable(ipAccount_)).owner(), ipAccount_, signer_, to_, func_, permission_);
emit PermissionSet(IIPAccount(payable(ipAccount)).owner(), ipAccount, signer, to, func, permission);
}

/// @notice Checks if a specific function call is allowed.
/// @notice Checks the permission level for a specific function call. Reverts if permission is not granted.
/// Otherwise, the function is a noop.
/// @dev This function checks the permission level for a specific function call.
/// If a specific permission is set, it overrides the general (wildcard) permission.
/// If the current level permission is ABSTAIN, the final permission is determined by the upper level.
/// @param ipAccount_ The account that owns the IP.
/// @param signer_ The account that signs the transaction.
/// @param to_ The recipient of the transaction.
/// @param func_ The function selector.
/// @param ipAccount The address of the IP account that grants the permission for `signer`
/// @param signer The address that can call `to` on behalf of the `ipAccount`
/// @param to The address that can be called by the `signer` (currently only modules can be `to`)
/// @param func The function selector of `to` that can be called by the `signer` on behalf of the `ipAccount`
// solhint-disable code-complexity
function checkPermission(
address ipAccount_,
address signer_,
address to_,
bytes4 func_
) external view whenNotPaused {
// ipAccount_ can only call registered modules or set Permissions
if (to_ != address(this) && !IModuleRegistry(MODULE_REGISTRY).isRegistered(to_)) {
revert Errors.AccessController__RecipientIsNotRegisteredModule(to_);
function checkPermission(address ipAccount, address signer, address to, bytes4 func) external view whenNotPaused {
// ipAccount can only call registered modules or set Permissions
if (to != address(this) && !IModuleRegistry(MODULE_REGISTRY).isRegistered(to)) {
revert Errors.AccessController__RecipientIsNotRegisteredModule(to);
}
// Must be a valid IPAccount
if (!IIPAccountRegistry(IP_ACCOUNT_REGISTRY).isIpAccount(ipAccount_)) {
revert Errors.AccessController__IPAccountIsNotValid(ipAccount_);
if (!IIPAccountRegistry(IP_ACCOUNT_REGISTRY).isIpAccount(ipAccount)) {
revert Errors.AccessController__IPAccountIsNotValid(ipAccount);
}
// Owner can call all functions of all modules
if (IIPAccount(payable(ipAccount_)).owner() == signer_) {
if (IIPAccount(payable(ipAccount)).owner() == signer) {
return;
}
uint functionPermission = getPermission(ipAccount_, signer_, to_, func_);
uint functionPermission = getPermission(ipAccount, signer, to, func);
// Specific function permission overrides wildcard/general permission
if (functionPermission == AccessPermission.ALLOW) {
return;
}

// If specific function permission is ABSTAIN, check module level permission
if (functionPermission == AccessPermission.ABSTAIN) {
uint8 modulePermission = getPermission(ipAccount_, signer_, to_, bytes4(0));
uint8 modulePermission = getPermission(ipAccount, signer, to, bytes4(0));
// Return true if allow to call all functions of the module
if (modulePermission == AccessPermission.ALLOW) {
return;
}
// If module level permission is ABSTAIN, check transaction signer level permission
if (modulePermission == AccessPermission.ABSTAIN) {
if (getPermission(address(0), signer_, to_, func_) == AccessPermission.ALLOW) {
if (getPermission(address(0), signer, to, func) == AccessPermission.ALLOW) {
return;
}
// Pass if the ipAccount allow the signer can call all functions of all modules
// Otherwise, revert
if (getPermission(ipAccount_, signer_, address(0), bytes4(0)) == AccessPermission.ALLOW) {
if (getPermission(ipAccount, signer, address(0), bytes4(0)) == AccessPermission.ALLOW) {
return;
}
revert Errors.AccessController__PermissionDenied(ipAccount_, signer_, to_, func_);
revert Errors.AccessController__PermissionDenied(ipAccount, signer, to, func);
}
revert Errors.AccessController__PermissionDenied(ipAccount_, signer_, to_, func_);
revert Errors.AccessController__PermissionDenied(ipAccount, signer, to, func);
}
revert Errors.AccessController__PermissionDenied(ipAccount_, signer_, to_, func_);
revert Errors.AccessController__PermissionDenied(ipAccount, signer, to, func);
}

/// @notice Returns the permission level for a specific function call.
/// @param ipAccount The account that owns the IP.
/// @param signer The account that signs the transaction.
/// @param to The recipient of the transaction.
/// @param func The function selector.
/// @return The permission level for the specific function call.
/// @param ipAccount The address of the IP account that grants the permission for `signer`
/// @param signer The address that can call `to` on behalf of the `ipAccount`
/// @param to The address that can be called by the `signer` (currently only modules can be `to`)
/// @param func The function selector of `to` that can be called by the `signer` on behalf of the `ipAccount`
/// @return permission The current permission level for the function call on `to` by the `signer` for `ipAccount`
function getPermission(address ipAccount, address signer, address to, bytes4 func) public view returns (uint8) {
return permissions[_encodePermission(ipAccount, signer, to, func)];
return encodedPermissions[_encodePermission(ipAccount, signer, to, func)];
}

/// @dev the permission parameters will be encoded into bytes32 as key in the permissions mapping to save storage
/// @dev The permission parameters will be encoded into bytes32 as key in the permissions mapping to save storage
function _setPermission(address ipAccount, address signer, address to, bytes4 func, uint8 permission) internal {
permissions[_encodePermission(ipAccount, signer, to, func)] = permission;
encodedPermissions[_encodePermission(ipAccount, signer, to, func)] = permission;
}

/// @dev Encodes permission parameters into a hash (bytes32) to serve as a unique permission record ID.
/// This function is utilized both when setting permissions and checking permissions to uniquely identify them.
/// In addition to the four permission fields passed by the parameters (ipAccount, signer, to, func),
/// an additional field, "ipAccountOwner", is retrieved on-the-fly when encoding the permission.
/// @param ipAccount The IP account involved in the permission.
/// @param signer The account that signs the transaction.
/// @param to The recipient of the transaction.
/// @param func The function selector involved in the permission.
/// @return A bytes32 hash representing the unique ID of the permission record.
/// @dev encode permission to hash (bytes32)
function _encodePermission(
address ipAccount,
address signer,
Expand Down
51 changes: 27 additions & 24 deletions contracts/IPAccountImpl.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { Errors } from "./lib/Errors.sol";
contract IPAccountImpl is IERC165, IIPAccount {
address public immutable accessController;

/// @notice Returns the IPAccount's internal nonce for transaction ordering.
uint256 public state;

receive() external payable override(IERC6551Account) {}
Expand All @@ -35,14 +36,14 @@ contract IPAccountImpl is IERC165, IIPAccount {
}

/// @notice Checks if the contract supports a specific interface
/// @param interfaceId_ The interface identifier, as specified in ERC-165
/// @return True if the contract supports the interface, false otherwise
function supportsInterface(bytes4 interfaceId_) external pure returns (bool) {
return (interfaceId_ == type(IIPAccount).interfaceId ||
interfaceId_ == type(IERC6551Account).interfaceId ||
interfaceId_ == type(IERC1155Receiver).interfaceId ||
interfaceId_ == type(IERC721Receiver).interfaceId ||
interfaceId_ == type(IERC165).interfaceId);
/// @param interfaceId The interface identifier, as specified in ERC-165
/// @return bool is true if the contract supports the interface, false otherwise
function supportsInterface(bytes4 interfaceId) external pure returns (bool) {
return (interfaceId == type(IIPAccount).interfaceId ||
interfaceId == type(IERC6551Account).interfaceId ||
interfaceId == type(IERC1155Receiver).interfaceId ||
interfaceId == type(IERC721Receiver).interfaceId ||
interfaceId == type(IERC165).interfaceId);
}

/// @notice Returns the identifier of the non-fungible token which owns the account
Expand All @@ -68,11 +69,11 @@ contract IPAccountImpl is IERC165, IIPAccount {
}

/// @notice Checks if the signer is valid for the given data
/// @param signer_ The signer to check
/// @param data_ The data to check against
/// @param signer The signer to check
/// @param data The data to check against
/// @return The function selector if the signer is valid, 0 otherwise
function isValidSigner(address signer_, bytes calldata data_) external view returns (bytes4) {
if (_isValidSigner(signer_, address(0), data_)) {
function isValidSigner(address signer, bytes calldata data) external view returns (bytes4) {
if (_isValidSigner(signer, address(0), data)) {
return IERC6551Account.isValidSigner.selector;
}

Expand All @@ -87,23 +88,21 @@ contract IPAccountImpl is IERC165, IIPAccount {
return IERC721(contractAddress).ownerOf(tokenId);
}

/// @notice Checks if the signer is valid for the given data and recipient
/// @dev It leverages the access controller to check the permission
/// @param signer_ The signer to check
/// @param to_ The recipient of the transaction
/// @param data_ The calldata to check against
/// @return True if the signer is valid, false otherwise
function _isValidSigner(address signer_, address to_, bytes calldata data_) internal view returns (bool) {
if (data_.length > 0 && data_.length < 4) {
/// @dev Checks if the signer is valid for the given data and recipient via the AccessController permission system.
/// @param signer The signer to check
/// @param to The recipient of the transaction
/// @param data The calldata to check against
/// @return bool is true if the signer is valid, false otherwise
function _isValidSigner(address signer, address to, bytes calldata data) internal view returns (bool) {
if (data.length > 0 && data.length < 4) {
revert Errors.IPAccount__InvalidCalldata();
}
require(data_.length == 0 || data_.length >= 4, "Invalid calldata");
bytes4 selector = bytes4(0);
if (data_.length >= 4) {
selector = bytes4(data_[:4]);
if (data.length >= 4) {
selector = bytes4(data[:4]);
}
// the check will revert if permission is denied
IAccessController(accessController).checkPermission(address(this), signer_, to_, selector);
IAccessController(accessController).checkPermission(address(this), signer, to, selector);
return true;
}

Expand Down Expand Up @@ -158,14 +157,17 @@ contract IPAccountImpl is IERC165, IIPAccount {
emit Executed(to, value, data, state);
}

/// @inheritdoc IERC721Receiver
function onERC721Received(address, address, uint256, bytes memory) public pure returns (bytes4) {
return this.onERC721Received.selector;
}

/// @inheritdoc IERC1155Receiver
function onERC1155Received(address, address, uint256, uint256, bytes memory) public pure returns (bytes4) {
return this.onERC1155Received.selector;
}

/// @inheritdoc IERC1155Receiver
function onERC1155BatchReceived(
address,
address,
Expand All @@ -176,6 +178,7 @@ contract IPAccountImpl is IERC165, IIPAccount {
return this.onERC1155BatchReceived.selector;
}

/// @dev Executes a transaction from the IP Account.
function _execute(
address signer,
address to,
Expand Down
Loading
Loading