Skip to content

Commit

Permalink
chore(evm): migrate unshared business logic to impl contracts
Browse files Browse the repository at this point in the history
Any unshared logic is moved to impls, while preserving public interfaces
to ensure interfaces are available to the sdk

This includes migrating ACL logic to impl contracts out of abstracts
because they have no EIP-165 interfaces
  • Loading branch information
topocount committed Sep 9, 2024
1 parent 1290281 commit 9a4d71b
Show file tree
Hide file tree
Showing 41 changed files with 1,413 additions and 1,355 deletions.
11 changes: 11 additions & 0 deletions packages/evm/contracts/actions/AContractAction.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,17 @@ import {ACloneable} from "contracts/shared/ACloneable.sol";
import {AAction} from "contracts/actions/AAction.sol";

abstract contract AContractAction is AAction {
/// @notice The payload for initializing a ContractAction
/// @param target The target contract address
/// @param selector The selector for the function to be called
/// @param value The native token value to send with the function call
struct InitPayload {
uint256 chainId;
address target;
bytes4 selector;
uint256 value;
}

/// @notice Thrown when execution on a given chain is not supported
error TargetChainUnsupported(uint256 targetChainId);

Expand Down
54 changes: 1 addition & 53 deletions packages/evm/contracts/actions/AERC721MintAction.sol
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.24;

import {Ownable as AOwnable} from "@solady/auth/Ownable.sol";
import {ERC721} from "@solady/tokens/ERC721.sol";

import {BoostError} from "contracts/shared/BoostError.sol";
import {ACloneable} from "contracts/shared/ACloneable.sol";

import {AAction} from "contracts/actions/AAction.sol";
import {ContractAction} from "contracts/actions/ContractAction.sol";
import {AContractAction} from "contracts/actions/AContractAction.sol";
import {AValidator} from "contracts/validators/AValidator.sol";

Expand All @@ -17,57 +11,11 @@ import {AValidator} from "contracts/validators/AValidator.sol";
/// @dev The action is expected to be prepared with the data payload for the minting of the token
/// @dev This a minimal generic implementation that should be extended if additional functionality or customizations are required
/// @dev It is expected that the target contract has an externally accessible mint function whose selector
abstract contract AERC721MintAction is ContractAction, AValidator, AOwnable {
abstract contract AERC721MintAction is AContractAction, AValidator {
/// @notice The set of validated tokens
/// @dev This is intended to prevent multiple validations against the same token ID
mapping(uint256 => bool) public validated;

/// @inheritdoc ContractAction
/// @notice Initialize the contract with the owner and the required data
function initialize(bytes calldata data_) public virtual override(ContractAction, ACloneable) initializer {
ContractAction.initialize(data_);
}

/// @notice Execute the action (not yet implemented)
/// @param data_ The data payload for the call (not used in this implementation)
/// @return success The success status of the call
/// @return returnData The return data from the call
function execute(bytes calldata data_) external payable override returns (bool success, bytes memory returnData) {
(data_, success, returnData);
revert BoostError.NotImplemented();
}

/// @notice Prepare the action for execution and return the expected payload
/// @param data_ The ABI-encoded payload for the target contract call
/// @return The encoded payload to be sent to the target contract
/// @dev Note that the mint value is NOT included in the prepared payload but must be sent with the call
function prepare(bytes calldata data_) public view override returns (bytes memory) {
return super.prepare(data_);
}

/// @inheritdoc AValidator
/// @notice Validate that the action has been completed successfully
/// @param data_ The data payload for the action `(address holder, (uint256 tokenId))`
/// @return success True if the action has been validated for the user
/// @dev The first 20 bytes of the payload must be the holder address and the remaining bytes must be an encoded token ID (uint256)
/// @dev Example: `abi.encode(address(holder), abi.encode(uint256(tokenId)))`
function validate(uint256, /*unused*/ uint256, /* unused */ address, /*unused*/ bytes calldata data_)
external
virtual
override
returns (bool success)
{
(address holder, bytes memory payload) = abi.decode(data_, (address, bytes));
uint256 tokenId = uint256(bytes32(payload));

if (ERC721(target).ownerOf(tokenId) == holder && !validated[tokenId]) {
validated[tokenId] = true;
return true;
} else {
return false;
}
}

/// @inheritdoc AContractAction
function getComponentInterface() public pure virtual override(AContractAction, AValidator) returns (bytes4) {
return type(AERC721MintAction).interfaceId;
Expand Down
30 changes: 4 additions & 26 deletions packages/evm/contracts/actions/AEventAction.sol
Original file line number Diff line number Diff line change
Expand Up @@ -47,39 +47,17 @@ abstract contract AEventAction is AAction {
Criteria actionParameter;
}

/// @notice Prepare the action for execution and return the expected payload
/// @param data_ The ABI-encoded payload for the target contract call
/// @return bytes_ The encoded payload to be sent to the target contract
/// @dev Note that the mint value is NOT included in the prepared payload but must be sent with the call
function prepare(bytes calldata data_) public view virtual override returns (bytes memory bytes_) {
// Since this action is marshalled off-chain we don't need to prepare the payload
revert BoostError.NotImplemented();
//return data_;
}
function getActionEventsCount() public view virtual returns (uint256);

function execute(bytes calldata data_) external payable virtual override returns (bool, bytes memory) {
// Since this action is marshalled off-chain we don't need to execute the payload
revert BoostError.NotImplemented();
//return (true, data_);
}
function getActionEvent(uint256 index) public view virtual returns (ActionEvent memory);

function getActionEvents() public view virtual returns (ActionEvent[] memory);

/// @inheritdoc ACloneable
function getComponentInterface() public pure virtual override returns (bytes4) {
return type(AEventAction).interfaceId;
}

function getActionEventsCount() public view virtual returns (uint256) {
return actionEvents.length;
}

function getActionEvent(uint256 index) public view virtual returns (ActionEvent memory) {
return actionEvents[index];
}

function getActionEvents() public view virtual returns (ActionEvent[] memory) {
return actionEvents;
}

/// @inheritdoc AAction
function supportsInterface(bytes4 interfaceId) public view virtual override(AAction) returns (bool) {
return interfaceId == type(AEventAction).interfaceId || super.supportsInterface(interfaceId);
Expand Down
11 changes: 0 additions & 11 deletions packages/evm/contracts/actions/ContractAction.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,6 @@ import {ACloneable} from "contracts/shared/ACloneable.sol";
import {AContractAction} from "contracts/actions/AContractAction.sol";

contract ContractAction is AContractAction {
/// @notice The payload for initializing a ContractAction
/// @param target The target contract address
/// @param selector The selector for the function to be called
/// @param value The native token value to send with the function call
struct InitPayload {
uint256 chainId;
address target;
bytes4 selector;
uint256 value;
}

constructor() {
_disableInitializers();
}
Expand Down
57 changes: 49 additions & 8 deletions packages/evm/contracts/actions/ERC721MintAction.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,77 @@
pragma solidity ^0.8.24;

import {ERC721} from "@solady/tokens/ERC721.sol";
import {Ownable as AOwnable} from "@solady/auth/Ownable.sol";

import {ACloneable} from "contracts/shared/ACloneable.sol";
import {AValidator} from "contracts/validators/AValidator.sol";

import {AERC721MintAction} from "contracts/actions/AERC721MintAction.sol";
import {ContractAction} from "contracts/actions/ContractAction.sol";
import {BoostError} from "contracts/shared/BoostError.sol";

/// @title ERC721 Mint AAction
/// @notice A primitive action to mint and/or validate that an ERC721 token has been minted
/// @dev The action is expected to be prepared with the data payload for the minting of the token
/// @dev This a minimal generic implementation that should be extended if additional functionality or customizations are required
/// @dev It is expected that the target contract has an externally accessible mint function whose selector
contract ERC721MintAction is AERC721MintAction {
contract ERC721MintAction is AOwnable, AERC721MintAction {
/// @notice Construct the ERC721 Mint AAction
/// @dev Because this contract is a base implementation, it should not be initialized through the constructor. Instead, it should be cloned and initialized using the {initialize} function.
constructor() {
_disableInitializers();
}
/// @inheritdoc ACloneable
/// @notice Initialize the contract with the owner and the required mint data
/// @param data_ The data payload for the mint action `(address target, bytes4 selector, uint256 value)`

function initialize(bytes calldata data_) public virtual override initializer {
/// @inheritdoc ACloneable
/// @notice Initialize the contract with the owner and the required data
function initialize(bytes calldata data_) public virtual override(ACloneable) initializer {
_initialize(abi.decode(data_, (InitPayload)));
}

function _initialize(InitPayload memory init_) internal override onlyInitializing {
/// @notice Execute the action (not yet implemented)
/// @param data_ The data payload for the call (not used in this implementation)
/// @return success The success status of the call
/// @return returnData The return data from the call
function execute(bytes calldata data_) external payable override returns (bool success, bytes memory returnData) {
(data_, success, returnData);
revert BoostError.NotImplemented();
}

/// @notice Prepare the action for execution and return the expected payload
/// @param data_ The ABI-encoded payload for the target contract call
/// @return The encoded payload to be sent to the target contract
/// @dev Note that the mint value is NOT included in the prepared payload but must be sent with the call
function prepare(bytes calldata data_) public view override returns (bytes memory) {
return super.prepare(data_);
}

/// @inheritdoc AValidator
/// @notice Validate that the action has been completed successfully
/// @param data_ The data payload for the action `(address holder, (uint256 tokenId))`
/// @return success True if the action has been validated for the user
/// @dev The first 20 bytes of the payload must be the holder address and the remaining bytes must be an encoded token ID (uint256)
/// @dev Example: `abi.encode(address(holder), abi.encode(uint256(tokenId)))`
function validate(uint256, /*unused*/ uint256, /* unused */ address, /*unused*/ bytes calldata data_)
external
virtual
override
returns (bool success)
{
(address holder, bytes memory payload) = abi.decode(data_, (address, bytes));
uint256 tokenId = uint256(bytes32(payload));

if (ERC721(target).ownerOf(tokenId) == holder && !validated[tokenId]) {
validated[tokenId] = true;
return true;
} else {
return false;
}
}

function _initialize(InitPayload memory init_) internal virtual onlyInitializing {
_initializeOwner(msg.sender);
chainId = init_.chainId;
target = init_.target;
selector = init_.selector;
value = init_.value;
_initializeOwner(msg.sender);
}
}
26 changes: 26 additions & 0 deletions packages/evm/contracts/actions/EventAction.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pragma solidity ^0.8.24;
import {ERC721} from "@solady/tokens/ERC721.sol";

import {ACloneable} from "contracts/shared/ACloneable.sol";
import {BoostError} from "contracts/shared/BoostError.sol";

import {AEventAction} from "contracts/actions/AEventAction.sol";

Expand Down Expand Up @@ -35,4 +36,29 @@ contract EventAction is AEventAction {
actionEvents.push(init_.actionEventThree);
actionEvents.push(init_.actionEventFour);
}

/// @notice Prepare the action for execution and return the expected payload
/// @return bytes_ The encoded payload to be sent to the target contract
/// @dev Note that the mint value is NOT included in the prepared payload but must be sent with the call
function prepare(bytes calldata) public view virtual override returns (bytes memory) {
// Since this action is marshalled off-chain we don't need to prepare the payload
revert BoostError.NotImplemented();
}

function execute(bytes calldata) external payable virtual override returns (bool, bytes memory) {
// Since this action is marshalled off-chain we don't need to execute the payload
revert BoostError.NotImplemented();
}

function getActionEventsCount() public view virtual override returns (uint256) {
return actionEvents.length;
}

function getActionEvent(uint256 index) public view virtual override returns (ActionEvent memory) {
return actionEvents[index];
}

function getActionEvents() public view virtual override returns (ActionEvent[] memory) {
return actionEvents;
}
}
9 changes: 1 addition & 8 deletions packages/evm/contracts/allowlists/AAllowList.sol
Original file line number Diff line number Diff line change
@@ -1,19 +1,12 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.24;

import {Ownable} from "@solady/auth/Ownable.sol";

import {ACloneable} from "contracts/shared/ACloneable.sol";

/// @title Boost AllowList
/// @notice Abstract contract for a generic Allow List within the Boost protocol
/// @dev Allow List classes are expected to implement the authorization of users based on implementation-specific criteria, which may involve validation of a data payload. If no data is required, calldata should be empty.
abstract contract AAllowList is Ownable, ACloneable {
/// @notice Constructor to initialize the owner
constructor() {
_initializeOwner(msg.sender);
}

abstract contract AAllowList is ACloneable {
/// @notice Check if a user is authorized
/// @param user_ The address of the user
/// @param data_ The data payload for the authorization check, if applicable
Expand Down
24 changes: 2 additions & 22 deletions packages/evm/contracts/allowlists/ASimpleAllowList.sol
Original file line number Diff line number Diff line change
@@ -1,42 +1,22 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.24;

import {OwnableRoles} from "@solady/auth/OwnableRoles.sol";

import {ACloneable} from "contracts/shared/ACloneable.sol";
import {BoostError} from "contracts/shared/BoostError.sol";

import {AAllowList} from "contracts/allowlists/AAllowList.sol";

/// @title Simple AllowList
/// @notice A simple implementation of an AllowList that checks if a user is authorized based on a list of allowed addresses
abstract contract ASimpleAllowList is AAllowList, OwnableRoles {
abstract contract ASimpleAllowList is AAllowList {
/// @notice The role for managing the allow list
uint256 public constant LIST_MANAGER_ROLE = 1 << 1;

/// @dev An internal mapping of allowed statuses
mapping(address => bool) internal _allowed;

/// @notice Check if a user is authorized
/// @param user_ The address of the user
/// @param - The data payload for the authorization check, not used in this implementation
/// @return True if the user is authorized
function isAllowed(address user_, bytes calldata /* data_ - unused */ ) external view override returns (bool) {
return _allowed[user_];
}

/// @notice Set the allowed status of a user
/// @param users_ The list of users to update
/// @param allowed_ The allowed status of each user
/// @dev The length of the `users_` and `allowed_` arrays must be the same
/// @dev This function can only be called by the owner
function setAllowed(address[] calldata users_, bool[] calldata allowed_) external onlyRoles(LIST_MANAGER_ROLE) {
if (users_.length != allowed_.length) revert BoostError.LengthMismatch();

for (uint256 i = 0; i < users_.length; i++) {
_allowed[users_[i]] = allowed_[i];
}
}
function setAllowed(address[] calldata users_, bool[] calldata allowed_) external virtual;

/// @inheritdoc ACloneable
function getComponentInterface() public pure virtual override(ACloneable) returns (bytes4) {
Expand Down
22 changes: 2 additions & 20 deletions packages/evm/contracts/allowlists/ASimpleDenyList.sol
Original file line number Diff line number Diff line change
@@ -1,37 +1,19 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.24;

import {BoostError} from "contracts/shared/BoostError.sol";
import {ACloneable} from "contracts/shared/ACloneable.sol";

import {AAllowList} from "contracts/allowlists/AAllowList.sol";

/// @title SimpleDenyList
/// @title ASimpleDenyList
/// @notice A simple implementation of an AllowList that implicitly allows all addresses except those explicitly added to the deny list
abstract contract ASimpleDenyList is AAllowList {
/// @dev An internal mapping of denied statuses
mapping(address => bool) internal _denied;

/// @notice Check if a user is authorized (i.e. not denied)
/// @param user_ The address of the user
/// @param - The data payload for the authorization check, not used in this implementation
/// @return True if the user is authorized
function isAllowed(address user_, bytes calldata /* data_ - unused */ ) external view override returns (bool) {
return !_denied[user_];
}

/// @notice Set the denied status of a user
/// @param users_ The list of users to update
/// @param denied_ The denied status of each user
/// @dev The length of the `users_` and `denied_` arrays must be the same
/// @dev This function can only be called by the owner
function setDenied(address[] calldata users_, bool[] calldata denied_) external onlyOwner {
if (users_.length != denied_.length) revert BoostError.LengthMismatch();

for (uint256 i = 0; i < users_.length; i++) {
_denied[users_[i]] = denied_[i];
}
}
function setDenied(address[] calldata users_, bool[] calldata denied_) external virtual;

/// @inheritdoc ACloneable
function getComponentInterface() public pure virtual override(ACloneable) returns (bytes4) {
Expand Down
Loading

0 comments on commit 9a4d71b

Please sign in to comment.