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

[BOOST-4638] Interface and Abstract Cleanup #68

Merged
merged 2 commits into from
Sep 9, 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
58 changes: 29 additions & 29 deletions packages/evm/contracts/BoostCore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ import {SafeTransferLib} from "@solady/utils/SafeTransferLib.sol";
import {BoostError} from "contracts/shared/BoostError.sol";
import {BoostLib} from "contracts/shared/BoostLib.sol";
import {BoostRegistry} from "contracts/BoostRegistry.sol";
import {Cloneable} from "contracts/shared/Cloneable.sol";
import {ACloneable} from "contracts/shared/ACloneable.sol";

import {Action} from "contracts/actions/Action.sol";
import {AllowList} from "contracts/allowlists/AllowList.sol";
import {Budget} from "contracts/budgets/Budget.sol";
import {Incentive} from "contracts/incentives/Incentive.sol";
import {AAction} from "contracts/actions/AAction.sol";
import {AAllowList} from "contracts/allowlists/AAllowList.sol";
import {ABudget} from "contracts/budgets/ABudget.sol";
import {AIncentive} from "contracts/incentives/AIncentive.sol";
import {IAuth} from "contracts/auth/IAuth.sol";
import {AValidator} from "contracts/validators/AValidator.sol";

Expand All @@ -28,7 +28,7 @@ contract BoostCore is Ownable, ReentrancyGuard {
using SafeTransferLib for address;

struct InitPayload {
Budget budget;
ABudget budget;
BoostLib.Target action;
BoostLib.Target validator;
BoostLib.Target allowList;
Expand Down Expand Up @@ -86,11 +86,11 @@ contract BoostCore is Ownable, ReentrancyGuard {
}

/// @notice Create a new Boost
/// @param data_ The compressed data for the Boost `(Budget, Target<Action>, Target<Validator>, Target<AllowList>, Target<Incentive>[], protocolFee, referralFee, maxParticipants, owner)`
/// @param data_ The compressed data for the Boost `(ABudget, Target<AAction>, Target<Validator>, Target<AAllowList>, Target<AIncentive>[], protocolFee, referralFee, maxParticipants, owner)`
/// @dev The data is expected to:
/// - be packed using `abi.encode()` and compressed using [Solady's LibZip calldata compression](https://github.com/Vectorized/solady/blob/main/src/utils/LibZip.sol)
/// - properly decode to the following types (in order):
/// - `Budget` to be used for the Boost
/// - `ABudget` to be used for the Boost
/// - `Target` for the action
/// - `Target` for the validator which is expected to be one of the following:
/// - The address of a base implementation to be cloned (e.g. the result of `BoostRegistry.getBaseImplementation("SignerValidator")`), along with the parameters for its initializer;
Expand Down Expand Up @@ -122,8 +122,8 @@ contract BoostCore is Ownable, ReentrancyGuard {
boost.maxParticipants = payload_.maxParticipants;

// Setup the Boost components
boost.action = Action(_makeTarget(type(Action).interfaceId, payload_.action, true));
boost.allowList = AllowList(_makeTarget(type(AllowList).interfaceId, payload_.allowList, true));
boost.action = AAction(_makeTarget(type(AAction).interfaceId, payload_.action, true));
boost.allowList = AAllowList(_makeTarget(type(AAllowList).interfaceId, payload_.allowList, true));
boost.incentives = _makeIncentives(payload_.incentives, payload_.budget);
boost.validator = AValidator(
payload_.validator.instance == address(0)
Expand All @@ -143,7 +143,7 @@ contract BoostCore is Ownable, ReentrancyGuard {

/// @notice Claim an incentive for a Boost
/// @param boostId_ The ID of the Boost
/// @param incentiveId_ The ID of the Incentive
/// @param incentiveId_ The ID of the AIncentive
/// @param referrer_ The address of the referrer (if any)
/// @param data_ The data for the claim
function claimIncentive(uint256 boostId_, uint256 incentiveId_, address referrer_, bytes calldata data_)
Expand All @@ -155,7 +155,7 @@ contract BoostCore is Ownable, ReentrancyGuard {

/// @notice Claim an incentive for a Boost on behalf of another user
/// @param boostId_ The ID of the Boost
/// @param incentiveId_ The ID of the Incentive
/// @param incentiveId_ The ID of the AIncentive
/// @param referrer_ The address of the referrer (if any)
/// @param data_ The data for the claim
/// @param claimant the address of the user eligible for the incentive payout
Expand Down Expand Up @@ -210,11 +210,11 @@ contract BoostCore is Ownable, ReentrancyGuard {
claimFee = claimFee_;
}

/// @notice Check that the provided Budget is valid and that the caller is authorized to use it
/// @param budget_ The Budget to check
/// @dev This function will revert if the Budget is invalid or the caller is unauthorized
function _checkBudget(Budget budget_) internal view {
_checkTarget(type(Budget).interfaceId, address(budget_));
/// @notice Check that the provided ABudget is valid and that the caller is authorized to use it
/// @param budget_ The ABudget to check
/// @dev This function will revert if the ABudget is invalid or the caller is unauthorized
function _checkBudget(ABudget budget_) internal view {
_checkTarget(type(ABudget).interfaceId, address(budget_));
if (!budget_.isAuthorized(msg.sender)) revert BoostError.Unauthorized();
}

Expand All @@ -224,7 +224,7 @@ contract BoostCore is Ownable, ReentrancyGuard {
/// @dev This function will revert if the Target does not implement the expected interface
/// @dev This check costs ~376 gas, which is worth it to validate the target
function _checkTarget(bytes4 interfaceId, address instance) internal view {
if (instance == address(0) || !Cloneable(instance).supportsInterface(interfaceId)) {
if (instance == address(0) || !ACloneable(instance).supportsInterface(interfaceId)) {
revert BoostError.InvalidInstance(interfaceId, instance);
}
}
Expand All @@ -243,25 +243,25 @@ contract BoostCore is Ownable, ReentrancyGuard {
instance = _maybeClone(target, shouldInitialize);
}

/// @notice Configure a set of incentives for a Boost using the given Budget
/// @param targets_ The set of incentives {Target<Incentive>[]}
/// @param budget_ The Budget from which to allocate the incentives
/// @return incentives The set of initialized incentives {Incentive[]}
function _makeIncentives(BoostLib.Target[] memory targets_, Budget budget_)
/// @notice Configure a set of incentives for a Boost using the given ABudget
/// @param targets_ The set of incentives {Target<AIncentive>[]}
/// @param budget_ The ABudget from which to allocate the incentives
/// @return incentives The set of initialized incentives {AIncentive[]}
function _makeIncentives(BoostLib.Target[] memory targets_, ABudget budget_)
internal
returns (Incentive[] memory incentives)
returns (AIncentive[] memory incentives)
{
incentives = new Incentive[](targets_.length);
incentives = new AIncentive[](targets_.length);
for (uint256 i = 0; i < targets_.length; i++) {
// Deploy the clone, but don't initialize until it we've preflighted
_checkTarget(type(Incentive).interfaceId, targets_[i].instance);
_checkTarget(type(AIncentive).interfaceId, targets_[i].instance);

// Ensure the target is a base implementation (incentive clones are not reusable)
if (!targets_[i].isBase) {
revert BoostError.InvalidInstance(type(Incentive).interfaceId, targets_[i].instance);
revert BoostError.InvalidInstance(type(AIncentive).interfaceId, targets_[i].instance);
}

incentives[i] = Incentive(_makeTarget(type(Incentive).interfaceId, targets_[i], false));
incentives[i] = AIncentive(_makeTarget(type(AIncentive).interfaceId, targets_[i], false));

bytes memory preflight = incentives[i].preflight(targets_[i].parameters);
if (preflight.length != 0) {
Expand All @@ -279,7 +279,7 @@ contract BoostCore is Ownable, ReentrancyGuard {
instance = target_.isBase ? target_.instance.clone() : target_.instance;
if (target_.isBase && shouldInitialize_) {
// wake-disable-next-line reentrancy (false positive, entrypoint is nonReentrant)
Cloneable(instance).initialize(target_.parameters);
ACloneable(instance).initialize(target_.parameters);
}
}

Expand Down
32 changes: 16 additions & 16 deletions packages/evm/contracts/BoostRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import {LibClone} from "@solady/utils/LibClone.sol";
import {ReentrancyGuard} from "@solady/utils/ReentrancyGuard.sol";

import {BoostLib} from "contracts/shared/BoostLib.sol";
import {Cloneable} from "contracts/shared/Cloneable.sol";
import {AllowList} from "contracts/allowlists/AllowList.sol";
import {ACloneable} from "contracts/shared/ACloneable.sol";
import {AAllowList} from "contracts/allowlists/AAllowList.sol";

/// @title Boost Registry
/// @notice A registry for base implementations and cloned instances
Expand All @@ -31,7 +31,7 @@ contract BoostRegistry is ERC165, ReentrancyGuard {
/// @param deployer The address of the deployer
struct Clone {
RegistryType baseType;
Cloneable instance;
ACloneable instance;
address deployer;
string name;
}
Expand All @@ -44,7 +44,7 @@ contract BoostRegistry is ERC165, ReentrancyGuard {
RegistryType indexed registryType,
bytes32 indexed identifier,
address baseImplementation,
Cloneable deployedInstance
ACloneable deployedInstance
);

/// @notice Thrown when a base implementation is already registered
Expand All @@ -53,23 +53,23 @@ contract BoostRegistry is ERC165, ReentrancyGuard {
/// @notice Thrown when no match is found for the given identifier
error NotRegistered(bytes32 identifier);

/// @notice Thrown when the implementation is not a valid {Cloneable} base
error NotCloneable(address implementation);
/// @notice Thrown when the implementation is not a valid {ACloneable} base
error NotACloneable(address implementation);

/// @notice The registry of base implementations
mapping(bytes32 => Cloneable) private _bases;
mapping(bytes32 => ACloneable) private _bases;

/// @notice The registry of deployed clones
mapping(bytes32 => Clone) private _clones;

/// @notice The registry of clones created by a given deployer
mapping(address => bytes32[]) private _deployedClones;

/// @notice A modifier to ensure the given address holds a valid {Cloneable} base
/// @notice A modifier to ensure the given address holds a valid {ACloneable} base
/// @param implementation_ The address of the implementation to check
modifier onlyCloneables(address implementation_) {
if (!Cloneable(implementation_).supportsInterface(type(Cloneable).interfaceId)) {
revert NotCloneable(implementation_);
modifier onlyACloneables(address implementation_) {
if (!ACloneable(implementation_).supportsInterface(type(ACloneable).interfaceId)) {
revert NotACloneable(implementation_);
}
_;
}
Expand All @@ -82,12 +82,12 @@ contract BoostRegistry is ERC165, ReentrancyGuard {
/// @dev The given address must implement the given type interface (See {ERC165-supportsInterface})
function register(RegistryType type_, string calldata name_, address implementation_)
external
onlyCloneables(implementation_)
onlyACloneables(implementation_)
{
bytes32 identifier = getIdentifier(type_, name_);

if (address(_bases[identifier]) != address(0)) revert AlreadyRegistered(type_, identifier);
_bases[identifier] = Cloneable(implementation_);
_bases[identifier] = ACloneable(implementation_);

emit Registered(type_, identifier, implementation_);
}
Expand All @@ -102,11 +102,11 @@ contract BoostRegistry is ERC165, ReentrancyGuard {
function deployClone(RegistryType type_, address base_, string calldata name_, bytes calldata data_)
external
nonReentrant
returns (Cloneable instance)
returns (ACloneable instance)
{
// Deploy and initialize the clone
instance =
Cloneable(base_.cloneAndInitialize(keccak256(abi.encodePacked(type_, base_, name_, msg.sender)), data_));
ACloneable(base_.cloneAndInitialize(keccak256(abi.encodePacked(type_, base_, name_, msg.sender)), data_));

// Ensure the clone's identifier is unique
bytes32 identifier = getCloneIdentifier(type_, base_, msg.sender, name_);
Expand All @@ -123,7 +123,7 @@ contract BoostRegistry is ERC165, ReentrancyGuard {
/// @param identifier_ The unique identifier for the implementation (see {getIdentifier})
/// @return implementation The address of the implementation
/// @dev This function will revert if the implementation is not registered
function getBaseImplementation(bytes32 identifier_) public view returns (Cloneable implementation) {
function getBaseImplementation(bytes32 identifier_) public view returns (ACloneable implementation) {
implementation = _bases[identifier_];
if (address(implementation) == address(0)) revert NotRegistered(identifier_);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.24;

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

/// @title Boost Action
/// @notice Abstract contract for a generic Action within the Boost protocol
/// @dev Action classes are expected to decode the calldata for implementation-specific handling. If no data is required, calldata should be empty.
abstract contract Action is Cloneable {
/// @title Boost AAction
/// @notice Abstract contract for a generic AAction within the Boost protocol
/// @dev AAction classes are expected to decode the calldata for implementation-specific handling. If no data is required, calldata should be empty.
abstract contract AAction is ACloneable {
/// @notice Emitted when the action is executed by a proxy.
/// @dev The `data` field should contain the return data from the action, if any.
event ActionExecuted(address indexed executor, address caller, bool success, bytes data);
Expand All @@ -29,8 +29,8 @@ abstract contract Action is Cloneable {
/// @return The prepared payload
function prepare(bytes calldata data_) external virtual returns (bytes memory);

/// @inheritdoc Cloneable
function supportsInterface(bytes4 interfaceId) public view virtual override(Cloneable) returns (bool) {
return interfaceId == type(Action).interfaceId || super.supportsInterface(interfaceId);
/// @inheritdoc ACloneable
function supportsInterface(bytes4 interfaceId) public view virtual override(ACloneable) returns (bool) {
return interfaceId == type(AAction).interfaceId || super.supportsInterface(interfaceId);
}
}
23 changes: 17 additions & 6 deletions packages/evm/contracts/actions/AContractAction.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,22 @@ pragma solidity ^0.8.24;

import {ERC721} from "@solady/tokens/ERC721.sol";

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

import {Action} from "contracts/actions/Action.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;
}

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

Expand Down Expand Up @@ -47,13 +58,13 @@ abstract contract AContractAction is Action {
}
}

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

/// @inheritdoc Action
function supportsInterface(bytes4 interfaceId) public view virtual override(Action) returns (bool) {
/// @inheritdoc AAction
function supportsInterface(bytes4 interfaceId) public view virtual override(AAction) returns (bool) {
return interfaceId == type(AContractAction).interfaceId || super.supportsInterface(interfaceId);
}
}
Loading
Loading