Skip to content

Commit

Permalink
[BOOST-4122] feat(evm): Event Action (#12)
Browse files Browse the repository at this point in the history
  • Loading branch information
sammccord authored Aug 30, 2024
2 parents 0b922c4 + eaf5d37 commit c0e0640
Show file tree
Hide file tree
Showing 13 changed files with 715 additions and 5 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ The build step for `@boostxyz/sdk` requires the following deployed contract addr
VITE_BOOST_CORE_ADDRESS=
VITE_BOOST_REGISTRY_ADDRESS=
VITE_CONTRACT_ACTION_BASE=
VITE_EVENT_ACTION_BASE=
VITE_ERC721_MINT_ACTION_BASE=
VITE_SIMPLE_ALLOWLIST_BASE=
VITE_SIMPLE_DENYLIST_BASE=
Expand Down
16 changes: 16 additions & 0 deletions packages/cli/src/commands/deploy.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import ContractActionArtifact from '@boostxyz/evm/artifacts/contracts/actions/ContractAction.sol/ContractAction.json';
import ERC721MintActionArtifact from '@boostxyz/evm/artifacts/contracts/actions/ERC721MintAction.sol/ERC721MintAction.json';
import EventActionArtifact from '@boostxyz/evm/artifacts/contracts/actions/EventAction.sol/EventAction.json';
import SimpleAllowListArtifact from '@boostxyz/evm/artifacts/contracts/allowlists/SimpleAllowList.sol/SimpleAllowList.json';
import SimpleDenyListArtifact from '@boostxyz/evm/artifacts/contracts/allowlists/SimpleDenyList.sol/SimpleDenyList.json';
import SimpleBudgetArtifact from '@boostxyz/evm/artifacts/contracts/budgets/SimpleBudget.sol/SimpleBudget.json';
Expand All @@ -20,6 +21,7 @@ import {
ERC20Incentive,
ERC721MintAction,
ERC1155Incentive,
EventAction,
PointsIncentive,
SignerValidator,
SimpleAllowList,
Expand All @@ -41,6 +43,7 @@ export type DeployResult = {
BOOST_CORE_ADDRESS: string;
BOOST_REGISTRY_ADDRESS: string;
CONTRACT_ACTION_BASE: string;
EVENT_ACTION_BASE: string;
ERC721_MINT_ACTION_BASE: string;
SIMPLE_ALLOWLIST_BASE: string;
SIMPLE_DENYLIST_BASE: string;
Expand Down Expand Up @@ -175,6 +178,15 @@ export const deploy: Command<DeployResult> = async function deploy(opts) {
}),
);

const eventActionBase = await getDeployedContractAddress(
config,
deployContract(config, {
abi: EventActionArtifact.abi,
bytecode: EventActionArtifact.bytecode as Hex,
account,
}),
);

const pointsIncentiveBase = await getDeployedContractAddress(
config,
deployContract(config, {
Expand All @@ -200,6 +212,9 @@ export const deploy: Command<DeployResult> = async function deploy(opts) {
ERC721MintAction: class TERC721MintAction extends ERC721MintAction {
public static override base = erc721MintActionBase;
},
EventAction: class TEventAction extends EventAction {
public static override base = eventActionBase;
},
SimpleAllowList: class TSimpleAllowList extends SimpleAllowList {
public static override base = simpleAllowListBase;
},
Expand Down Expand Up @@ -240,6 +255,7 @@ export const deploy: Command<DeployResult> = async function deploy(opts) {
BOOST_CORE_ADDRESS: core.assertValidAddress(),
BOOST_REGISTRY_ADDRESS: registry.assertValidAddress(),
CONTRACT_ACTION_BASE: contractActionBase,
EVENT_ACTION_BASE: eventActionBase,
ERC721_MINT_ACTION_BASE: erc721MintActionBase,
SIMPLE_ALLOWLIST_BASE: simpleAllowListBase,
SIMPLE_DENYLIST_BASE: simpleDenyListBase,
Expand Down
92 changes: 92 additions & 0 deletions packages/evm/contracts/actions/AEventAction.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.24;

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

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

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

/// @title Event Action
/// @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
abstract contract AEventAction is Action {
ActionEvent[] internal actionEvents;

// Define Enums
enum FilterType {
EQUAL,
NOT_EQUAL,
GREATER_THAN,
LESS_THAN,
CONTAINS
}

enum PrimitiveType {
UINT,
ADDRESS,
BYTES,
STRING
}

// Define Structs
struct Criteria {
FilterType filterType;
PrimitiveType fieldType;
uint8 fieldIndex; // Where in the logs arg array the field is located
bytes filterData; // data fiels in case we need more complex filtering in the future - initially unused
}

struct ActionEvent {
bytes4 eventSignature;
uint8 actionType;
address targetContract;
Criteria actionParameter;
}

/// @inheritdoc Cloneable
function initialize(bytes calldata data_) public virtual override(Cloneable) {
revert NotInitializing();
}

/// @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 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_);
}

/// @inheritdoc Action
function getComponentInterface() public pure virtual override(Action) 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 Action
function supportsInterface(bytes4 interfaceId) public view virtual override(Action) returns (bool) {
return interfaceId == type(AEventAction).interfaceId || super.supportsInterface(interfaceId);
}
}
38 changes: 38 additions & 0 deletions packages/evm/contracts/actions/EventAction.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.24;

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

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

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

contract EventAction is AEventAction {
/// @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 {
ActionEvent actionEventOne;
ActionEvent actionEventTwo;
ActionEvent actionEventThree;
ActionEvent actionEventFour;
}

constructor() {
_disableInitializers();
}

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

function _initialize(InitPayload memory init_) internal virtual onlyInitializing {
actionEvents.push(init_.actionEventOne);
actionEvents.push(init_.actionEventTwo);
actionEvents.push(init_.actionEventThree);
actionEvents.push(init_.actionEventFour);
}
}
107 changes: 107 additions & 0 deletions packages/evm/test/actions/EventAction.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.24;

import {Test, console} from "lib/forge-std/src/Test.sol";
import {LibClone} from "@solady/utils/LibClone.sol";

import {MockERC721} from "contracts/shared/Mocks.sol";
import {EventAction} from "contracts/actions/EventAction.sol";
import {AEventAction} from "contracts/actions/AEventAction.sol";
import {Cloneable} from "contracts/shared/Cloneable.sol";

contract EventActionTest is Test {
MockERC721 public mockAsset = new MockERC721();
EventAction public baseAction = new EventAction();
EventAction public action;

function setUp() public {
action = _newActionClone();

// Define the InitPayload with an ActionEvent
AEventAction.Criteria memory criteria;
AEventAction.ActionEvent memory actionEventOne;

criteria = AEventAction.Criteria({
filterType: AEventAction.FilterType.EQUAL,
fieldType: AEventAction.PrimitiveType.ADDRESS,
fieldIndex: 0, // Assume the first field in the log is the 'from' address
filterData: abi.encode(address(this)) // The filter checks if 'from' address equals this contract's address
});

actionEventOne = AEventAction.ActionEvent({
eventSignature: bytes4(keccak256("Transfer(address,address,uint256)")),
actionType: 0,
targetContract: address(mockAsset),
actionParameter: criteria
});

EventAction.InitPayload memory payload = EventAction.InitPayload({
actionEventOne: actionEventOne,
actionEventTwo: actionEventOne,
actionEventThree: actionEventOne,
actionEventFour: actionEventOne
});

// Initialize the EventAction contract
action.initialize(abi.encode(payload));
}

///////////////////////////
// EventAction.initialize //
///////////////////////////

function testInitialize() public {
// Ensure the action was initialized correctly
assertEq(action.getActionEventsCount(), 4);
assertEq(action.getActionEvent(0).eventSignature, bytes4(keccak256("Transfer(address,address,uint256)")));
}

////////////////////////////
// EventAction.getActionEvents //
////////////////////////////

function testGetActionEvents() public {
// Ensure the action events are retrieved correctly
AEventAction.ActionEvent[] memory retrievedEvents = action.getActionEvents();

assertEq(retrievedEvents.length, 4);
assertEq(retrievedEvents[0].eventSignature, bytes4(keccak256("Transfer(address,address,uint256)")));
}

/////////////////////////////////
// EventAction.getActionEvent //
/////////////////////////////////

function testGetActionEvent() public {
// Ensure the action event is retrieved correctly
AEventAction.ActionEvent memory retrievedEvent = action.getActionEvent(0);

assertEq(retrievedEvent.eventSignature, bytes4(keccak256("Transfer(address,address,uint256)")));
}

////////////////////////////////////
// EventAction.getComponentInterface //
////////////////////////////////////

function testGetComponentInterface() public {
// Retrieve the component interface
console.logBytes4(action.getComponentInterface());
}
////////////////////////////////////
// EventAction.supportsInterface //
////////////////////////////////////

function testSupportsInterface() public {
// Check the interface support
assertTrue(action.supportsInterface(type(AEventAction).interfaceId));
assertTrue(action.supportsInterface(type(Cloneable).interfaceId));
}

///////////////////////////
// Test Helper Functions //
///////////////////////////

function _newActionClone() internal returns (EventAction) {
return EventAction(LibClone.clone(address(baseAction)));
}
}
1 change: 1 addition & 0 deletions packages/sdk/.env.sample
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
VITE_BOOST_CORE_ADDRESS=
VITE_BOOST_REGISTRY_ADDRESS=
VITE_CONTRACT_ACTION_BASE=
VITE_EVENT_ACTION_BASE=
VITE_ERC721_MINT_ACTION_BASE=
VITE_SIMPLE_ALLOWLIST_BASE=
VITE_SIMPLE_DENYLIST_BASE=
Expand Down
10 changes: 9 additions & 1 deletion packages/sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
"license": "GPL-3.0-or-later",
"private": true,
"type": "module",
"files": ["dist"],
"files": [
"dist"
],
"repository": "https://github.com/rabbitholegg/boost-protocol",
"author": "Boost Team<[email protected]>",
"access": "public",
Expand Down Expand Up @@ -93,6 +95,12 @@
"node": "./dist/Actions/ERC721MintAction.js",
"types": "./dist/Actions/ERC721MintAction.d.ts"
},
"./Actions/EventAction": {
"require": "./dist/Actions/EventAction.cjs",
"import": "./dist/Actions/EventAction.js",
"node": "./dist/Actions/EventAction.js",
"types": "./dist/Actions/EventAction.d.ts"
},
"./AllowLists/AllowList": {
"require": "./dist/AllowLists/AllowList.cjs",
"import": "./dist/AllowLists/AllowList.js",
Expand Down
13 changes: 9 additions & 4 deletions packages/sdk/src/Actions/Action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { DeployableOptions } from '../Deployable/Deployable';
import { InvalidComponentInterfaceError } from '../errors';
import { ContractAction } from './ContractAction';
import { ERC721MintAction } from './ERC721MintAction';
import { EventAction } from './EventAction';

export { ContractAction, ERC721MintAction };

Expand All @@ -14,16 +15,17 @@ export { ContractAction, ERC721MintAction };
* @export
* @typedef {Action}
*/
export type Action = ContractAction | ERC721MintAction;
export type Action = ContractAction | ERC721MintAction | EventAction;

/**
* A map of Action component interfaces to their constructors.
*
* @type {{ "0x2fae823b": ContractAction; "0xcba21e6c": ERC721MintAction; }}
* @type {{ "0x2fae823b": ContractAction; "0xcba21e6c": ERC721MintAction; "0x916b9f6d": EventAction; }}
*/
export const ActionByComponentInterface = {
['0x2fae823b']: ContractAction,
['0xcba21e6c']: ERC721MintAction,
['0x916b9f6d']: EventAction,
};

/**
Expand All @@ -33,7 +35,7 @@ export const ActionByComponentInterface = {
* @async
* @param {DeployableOptions} options
* @param {Address} address
* @returns {Promise<ContractAction | ERC721MintAction>}
* @returns {Promise<ContractAction | ERC721MintAction | EventAction>}
* @throws {@link InvalidComponentInterfaceError}
*/
export async function actionFromAddress(
Expand All @@ -52,5 +54,8 @@ export async function actionFromAddress(
interfaceId,
);
}
return new Ctor(options, address) as ContractAction | ERC721MintAction;
return new Ctor(options, address) as
| ContractAction
| ERC721MintAction
| EventAction;
}
Loading

0 comments on commit c0e0640

Please sign in to comment.