Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: add custom errors for ACL and NatSpec updates #203

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
289 changes: 198 additions & 91 deletions contracts/contracts/ACL.sol
Original file line number Diff line number Diff line change
@@ -1,22 +1,38 @@
// SPDX-License-Identifier: BSD-3-Clause-Clear

pragma solidity ^0.8.24;

import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
import "../addresses/TFHEExecutorAddress.sol";
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
import {tfheExecutorAdd} from "../addresses/TFHEExecutorAddress.sol";

/**
* @title ACL
* @notice The ACL (Access Control List) is a permission management system designed to
* control who can access, compute on, or decrypt encrypted values in fhEVM.
* By defining and enforcing these permissions, the ACL ensures that encrypted data remains secure while still being usable
* within authorized contexts.
*/
contract ACL is UUPSUpgradeable, Ownable2StepUpgradeable {
/// @notice Name of the contract
string private constant CONTRACT_NAME = "ACL";
/// @notice Returned if the delegatee contract is already delegatee for sender & delegator addresses.
error AlreadyDelegated();

/// @notice Version of the contract
uint256 private constant MAJOR_VERSION = 0;
uint256 private constant MINOR_VERSION = 1;
uint256 private constant PATCH_VERSION = 0;
/// @notice Returned if the sender is the delegatee address.
error SenderCannotBeDelegateeAddress();

address private constant tfheExecutorAddress = tfheExecutorAdd;
/// @notice Returned if the sender address is not allowed for allow operations.
/// @param sender Sender address.
error SenderNotAllowed(address sender);

/// @notice Emitted when a list of handles is allowed for decryption.
/// @param handlesList List of handles allowed for decryption.
event AllowedForDecryption(uint256[] handlesList);

/// @notice Emitted when a new delegate address is added.
/// @param sender Sender address
/// @param delegatee Delegatee address.
/// @param contractAddress Contract address.
event NewDelegation(address indexed sender, address indexed delegatee, address indexed contractAddress);

/// @custom:storage-location erc7201:fhevm.storage.ACL
struct ACLStorage {
Expand All @@ -25,41 +41,81 @@ contract ACL is UUPSUpgradeable, Ownable2StepUpgradeable {
mapping(address account => mapping(address delegatee => mapping(address contractAddress => bool isDelegate))) delegates;
}

// keccak256(abi.encode(uint256(keccak256("fhevm.storage.ACL")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant ACLStorageLocation = 0xa688f31953c2015baaf8c0a488ee1ee22eb0e05273cc1fd31ea4cbee42febc00;
/// @notice Name of the contract.
string private constant CONTRACT_NAME = "ACL";

function _getACLStorage() internal pure returns (ACLStorage storage $) {
assembly {
$.slot := ACLStorageLocation
}
}
/// @notice Major version of the contract.
uint256 private constant MAJOR_VERSION = 0;

/// @notice Getter function for the TFHEExecutor contract address
function getTFHEExecutorAddress() public view virtual returns (address) {
return tfheExecutorAddress;
}
/// @notice Minor version of the contract.
uint256 private constant MINOR_VERSION = 1;

event NewDelegation(address indexed sender, address indexed delegatee, address indexed contractAddress);
event AllowedForDecryption(uint256[] handlesList);
/// @notice Patch version of the contract.
uint256 private constant PATCH_VERSION = 0;

function _authorizeUpgrade(address _newImplementation) internal virtual override onlyOwner {}
/// @notice TFHEExecutor address.
address private constant tfheExecutorAddress = tfheExecutorAdd;

/// @dev keccak256(abi.encode(uint256(keccak256("fhevm.storage.ACL")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant ACLStorageLocation = 0xa688f31953c2015baaf8c0a488ee1ee22eb0e05273cc1fd31ea4cbee42febc00;

/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}

/// @notice Initializes the contract setting `initialOwner` as the initial owner
function initialize(address initialOwner) external initializer {
/**
* @notice Initializes the contract.
* @param initialOwner Initial owner address.
*/
function initialize(address initialOwner) public initializer {
__Ownable_init(initialOwner);
}

// allowTransient use of `handle` for address `account`.
// The caller must be allowed to use `handle` for allowTransient() to succeed. If not, allowTransient() reverts.
// @note: The Coprocessor contract can always `allowTransient`, contrarily to `allow`
/**
* @notice Allows the use of `handle` for the address `account`.
* @dev The caller must be allowed to use `handle` for allow() to succeed. If not, allow() reverts.
* @param handle Handle.
* @param account Address of the account.
*/
function allow(uint256 handle, address account) public virtual {
ACLStorage storage $ = _getACLStorage();
if (!isAllowed(handle, msg.sender)) {
revert SenderNotAllowed(msg.sender);
}
$.persistedAllowedPairs[handle][account] = true;
}

/**
* @notice Allows a list of handles to be decrypted.
* @param handlesList List of handles.
*/
function allowForDecryption(uint256[] memory handlesList) public virtual {
uint256 len = handlesList.length;
ACLStorage storage $ = _getACLStorage();
for (uint256 k = 0; k < len; k++) {
uint256 handle = handlesList[k];
if (!isAllowed(handle, msg.sender)) {
revert SenderNotAllowed(msg.sender);
}
$.allowedForDecryption[handle] = true;
}
emit AllowedForDecryption(handlesList);
}

/**
* @notice Allows the use of `handle` by address `account` for this transaction.
* @dev The caller must be allowed to use `handle` for allowTransient() to succeed.
* If not, allowTransient() reverts.
* The Coprocessor contract can always `allowTransient`, contrarily to `allow`.
* @param handle Handle.
* @param account Address of the account.
*/
function allowTransient(uint256 handle, address account) public virtual {
if (msg.sender != tfheExecutorAddress) {
require(isAllowed(handle, msg.sender), "sender isn't allowed");
if (!isAllowed(handle, msg.sender)) {
revert SenderNotAllowed(msg.sender);
}
}
bytes32 key = keccak256(abi.encodePacked(handle, account));
assembly {
Expand All @@ -71,91 +127,128 @@ contract ACL is UUPSUpgradeable, Ownable2StepUpgradeable {
}
}

function allowedTransient(uint256 handle, address account) public view virtual returns (bool) {
bool isAllowedTransient;
bytes32 key = keccak256(abi.encodePacked(handle, account));
assembly {
isAllowedTransient := tload(key)
}
return isAllowedTransient;
}

function cleanTransientStorage() external virtual {
// this function removes the transient allowances, could be useful for integration with Account Abstraction when bundling several UserOps calling ACL
assembly {
let length := tload(0)
tstore(0, 0)
let lengthPlusOne := add(length, 1)
for {
let i := 1
} lt(i, lengthPlusOne) {
i := add(i, 1)
} {
let handle := tload(i)
tstore(i, 0)
tstore(handle, 0)
}
/**
* @notice Delegate the access of `handles` in the context of account abstraction for issuing
* reencryption requests from a smart contract account.
* @param delegatee Delegatee address.
* @param delegateeContract Delegatee contract.
*/
function delegateAccount(address delegatee, address delegateeContract) public virtual {
if (delegateeContract == msg.sender) {
revert SenderCannotBeDelegateeAddress();
}
}

// Allow use of `handle` for address `account`.
// The caller must be allowed to use `handle` for allow() to succeed. If not, allow() reverts.
function allow(uint256 handle, address account) external virtual {
ACLStorage storage $ = _getACLStorage();
require(isAllowed(handle, msg.sender), "sender isn't allowed");
$.persistedAllowedPairs[handle][account] = true;
}

// Returns true if address `a` is allowed to use `c` and false otherwise.
function persistAllowed(uint256 handle, address account) public view virtual returns (bool) {
ACLStorage storage $ = _getACLStorage();
return $.persistedAllowedPairs[handle][account];
}

// Useful in the context of account abstraction for issuing reencryption requests from a smart contract account
function isAllowed(uint256 handle, address account) public view virtual returns (bool) {
return allowedTransient(handle, account) || persistAllowed(handle, account);
}
if ($.delegates[msg.sender][delegatee][delegateeContract]) {
revert AlreadyDelegated();
}

function delegateAccountForContract(address delegatee, address contractAddress) external virtual {
require(contractAddress != msg.sender, "contractAddress should be different from msg.sender");
ACLStorage storage $ = _getACLStorage();
require(!$.delegates[msg.sender][delegatee][contractAddress], "already delegated");
$.delegates[msg.sender][delegatee][contractAddress] = true;
emit NewDelegation(msg.sender, delegatee, contractAddress);
$.delegates[msg.sender][delegatee][delegateeContract] = true;
emit NewDelegation(msg.sender, delegatee, delegateeContract);
}

/**
* @notice Returns whether the delegatee is allowed to access the handle.
* @param delegatee Delegatee address.
* @param handle Handle.
* @param contractAddress Contract address.
* @param account Address of the account.
* @return isAllowed Whether the handle can be accessed.
*/
function allowedOnBehalf(
address delegatee,
uint256 handle,
address contractAddress,
address account
) external view virtual returns (bool) {
) public view virtual returns (bool) {
ACLStorage storage $ = _getACLStorage();
return
$.persistedAllowedPairs[handle][account] &&
$.persistedAllowedPairs[handle][contractAddress] &&
$.delegates[account][delegatee][contractAddress];
}

function allowForDecryption(uint256[] memory handlesList) external virtual {
uint256 len = handlesList.length;
ACLStorage storage $ = _getACLStorage();
for (uint256 k = 0; k < len; k++) {
uint256 handle = handlesList[k];
require(isAllowed(handle, msg.sender), "sender isn't allowed");
$.allowedForDecryption[handle] = true;
/**
* @notice Checks whether the account is allowed to use the handle in the
* same transaction (transient).
* @param handle Handle.
* @param account Address of the account.
* @return isAllowedTransient Whether the account can access the handle.
*/
function allowedTransient(uint256 handle, address account) public view virtual returns (bool) {
bool isAllowedTransient;
bytes32 key = keccak256(abi.encodePacked(handle, account));
assembly {
isAllowedTransient := tload(key)
}
emit AllowedForDecryption(handlesList);
return isAllowedTransient;
}

/**
* @notice Getter function for the TFHEExecutor contract address.
* @return tfheExecutorAddress Address of the TFHEExecutor.
*/
function getTFHEExecutorAddress() public view virtual returns (address) {
return tfheExecutorAddress;
}

/**
* @notice Returns whether the account is allowed to use the `handle` in the context of
* account abstraction for issuing reencryption requests from a smart contract account.
* @param handle Handle.
* @param account Address of the account.
* @return isAllowed Whether the account can access the handle.
*/
function isAllowed(uint256 handle, address account) public view virtual returns (bool) {
return allowedTransient(handle, account) || persistAllowed(handle, account);
}

/**
* @notice Checks whether a handle is allowed for decryption.
* @param handle Handle.
* @return isAllowed Whether the handle is allowed for decryption.
*/
function isAllowedForDecryption(uint256 handle) public view virtual returns (bool) {
ACLStorage storage $ = _getACLStorage();
return $.allowedForDecryption[handle];
}

/// @notice Getter for the name and version of the contract
/// @return string representing the name and the version of the contract
/**
* @notice Returns `true` if address `a` is allowed to use `c` and `false` otherwise.
* @param handle Handle.
* @param account Address of the account.
* @return isAllowed Whether the account can access the handle.
*/
function persistAllowed(uint256 handle, address account) public view virtual returns (bool) {
ACLStorage storage $ = _getACLStorage();
return $.persistedAllowedPairs[handle][account];
}

/**
* @dev This function removes the transient allowances, which could be useful for integration with
* Account Abstraction when bundling several UserOps calling the TFHEExecutorCoprocessor.
*/
function cleanTransientStorage() external virtual {
assembly {
let length := tload(0)
tstore(0, 0)
let lengthPlusOne := add(length, 1)
for {
let i := 1
} lt(i, lengthPlusOne) {
i := add(i, 1)
} {
let handle := tload(i)
tstore(i, 0)
tstore(handle, 0)
}
}
}

/**
* @notice Getter for the name and version of the contract.
* @return string Name and the version of the contract.
*/
function getVersion() external pure virtual returns (string memory) {
return
string(
Expand All @@ -170,4 +263,18 @@ contract ACL is UUPSUpgradeable, Ownable2StepUpgradeable {
)
);
}

/**
* @dev Should revert when `msg.sender` is not authorized to upgrade the contract.
*/
function _authorizeUpgrade(address _newImplementation) internal virtual override onlyOwner {}

/**
* @dev Returns the ACL storage location.
*/
function _getACLStorage() internal pure returns (ACLStorage storage $) {
assembly {
$.slot := ACLStorageLocation
}
}
}
Loading
Loading