From 43dccf4c19f3f2aa721cdca84afb29845f81bf96 Mon Sep 17 00:00:00 2001 From: Quazia Date: Wed, 26 Jun 2024 11:33:08 -0400 Subject: [PATCH 01/17] feat(validators): add event based signer validator --- .../validators/EventSignerValidator.sol | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 packages/evm/contracts/validators/EventSignerValidator.sol diff --git a/packages/evm/contracts/validators/EventSignerValidator.sol b/packages/evm/contracts/validators/EventSignerValidator.sol new file mode 100644 index 00000000..765c922a --- /dev/null +++ b/packages/evm/contracts/validators/EventSignerValidator.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.24; + +import {SignatureCheckerLib} from "@solady/utils/SignatureCheckerLib.sol"; +import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; +import {BoostError} from "contracts/shared/BoostError.sol"; +import {Validator} from "contracts/validators/Validator.sol"; +import {SignerValidator} from "contracts/validators/Validator.sol"; + +// Define Enums +enum FilterType { EQUAL, NOT_EQUAL, GREATER_THAN, LESS_THAN } +enum PrimitiveType { UINT, ADDRESS, BYTES, STRING } + +// Define Structs +struct Criteria { + FilterType filterType; + PrimitiveType fieldType; + bytes filterData; +} + +struct ActionEvent { + bytes4 eventSignature; + uint8 actionType; + Criteria[4] actionParameters; +} + +contract ActionEventValidator is SignerValidator { + ActionEvent private actionEvent; + + event ActionEventInitialized( + bytes4 indexed eventSignature, + uint8 actionType, + Criteria[4] actionParameters + ); + + /// @notice Initialize the contract with the list of authorized signers and the ActionEvent + /// @param data_ The compressed data containing the list of authorized signers and the ActionEvent + /// @dev The first address in the list will be the initial owner of the contract + function initialize(bytes calldata data_) public virtual override initializer { + (address[] memory signers_, ActionEvent memory actionEvent_) = abi.decode(data_, (address[], ActionEvent)); + + _initializeOwner(signers_[0]); + for (uint256 i = 0; i < signers_.length; i++) { + signers[signers_[i]] = true; + } + + require(actionEvent_.eventSignature != bytes4(0), "Invalid event signature"); + require(bytes(actionTypeMapping[actionEvent_.actionType]).length != 0, "Invalid action type"); + + actionEvent = actionEvent_; + + emit ActionEventInitialized(actionEvent.eventSignature, actionEvent.actionType, actionEvent.actionParameters); + } + + function getActionEvent() public view returns (ActionEvent memory) { + return actionEvent; + } +} From 9dcc7c43ff5a3890686004885f02d91358d3203e Mon Sep 17 00:00:00 2001 From: Quazia Date: Wed, 26 Jun 2024 11:34:06 -0400 Subject: [PATCH 02/17] test(validators): event based signer validator --- .../validators/EventSignerValidator.t.sol | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 packages/evm/test/validators/EventSignerValidator.t.sol diff --git a/packages/evm/test/validators/EventSignerValidator.t.sol b/packages/evm/test/validators/EventSignerValidator.t.sol new file mode 100644 index 00000000..3b21094a --- /dev/null +++ b/packages/evm/test/validators/EventSignerValidator.t.sol @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.24; + +import {Test, console} from "lib/forge-std/src/Test.sol"; + +import {ECDSA} from "@solady/utils/ECDSA.sol"; +import {LibClone} from "@solady/utils/LibClone.sol"; +import {SignatureCheckerLib} from "@solady/utils/SignatureCheckerLib.sol"; + +import {MockERC1271Wallet} from "lib/solady/test/utils/mocks/MockERC1271Wallet.sol"; +import {MockERC1271Malicious} from "lib/solady/test/utils/mocks/MockERC1271Malicious.sol"; + +import {BoostError} from "contracts/shared/BoostError.sol"; +import {Cloneable} from "contracts/shared/Cloneable.sol"; +import {Validator} from "contracts/validators/Validator.sol"; +import {SignerValidator} from "contracts/validators/SignerValidator.sol"; +import {ActionEventValidator} from "contracts/validators/ActionEventValidator.sol"; + +contract ActionEventValidatorTest is Test { + ActionEventValidator baseValidator = new ActionEventValidator(); + ActionEventValidator validator; + + uint256 testSignerKey = uint256(vm.envUint("TEST_SIGNER_PRIVATE_KEY")); + address testSigner = vm.addr(testSignerKey); + + uint256 fakeSignerKey = uint256(0xdeadbeef); + address fakeSigner = vm.addr(fakeSignerKey); + + MockERC1271Wallet smartSignerMock = new MockERC1271Wallet(testSigner); + MockERC1271Malicious maliciousSignerMock = new MockERC1271Malicious(); + + bytes32 MESSAGE_HASH = keccak256(abi.encodePacked("test")); + bytes TRUSTED_EOA_SIGNATURE = _signHash(MESSAGE_HASH, testSignerKey); + bytes UNTRUSTED_EOA_SIGNATURE = _signHash(MESSAGE_HASH, fakeSignerKey); + + bytes PACKED_EOA_SIGNATURE = abi.encode(testSigner, MESSAGE_HASH, TRUSTED_EOA_SIGNATURE); + bytes PACKED_1271_SIGNATURE = abi.encode(smartSignerMock, MESSAGE_HASH, TRUSTED_EOA_SIGNATURE); + + bytes PACKED_MALICIOUS_SIGNATURE = abi.encode(maliciousSignerMock, MESSAGE_HASH, TRUSTED_EOA_SIGNATURE); + bytes PACKED_WRONG_SIGNATURE = abi.encode(testSigner, MESSAGE_HASH, UNTRUSTED_EOA_SIGNATURE); + bytes PACKED_UNTRUSTED_SIGNATURE = abi.encode(fakeSigner, MESSAGE_HASH, UNTRUSTED_EOA_SIGNATURE); + + function setUp() public { + address[] memory signers = new address[](3); + signers[0] = address(this); + signers[1] = testSigner; + signers[2] = address(smartSignerMock); + + Criteria[4] memory criteria; + for (uint i = 0; i < 4; i++) { + criteria[i] = Criteria({ + filterType: FilterType.EQUAL, + fieldType: PrimitiveType.BYTES, + filterData: bytes("") + }); + } + + ActionEvent memory actionEvent = ActionEvent({ + eventSignature: bytes4(keccak256("TestEvent")), + actionType: 1, + actionParameters: criteria + }); + + bytes memory data = abi.encode(signers, actionEvent); + validator = ActionEventValidator(LibClone.clone(address(baseValidator))); + validator.initialize(data); + } + + //////////////////////////////// + // ActionEventValidator.initialize // + //////////////////////////////// + + function testInitialize() public { + // The initializer should have set 3 signers: + assertTrue(validator.signers(address(this))); + assertTrue(validator.signers(testSigner)); + assertTrue(validator.signers(address(smartSignerMock))); + + // Other signers should not be authorized + assertFalse(validator.signers(fakeSigner)); + assertFalse(validator.signers(address(maliciousSignerMock))); + + // The owner of the contract should be the first signer + assertEq(validator.owner(), address(this)); + + // The action event should be initialized correctly + ActionEvent memory actionEvent = validator.getActionEvent(); + assertEq(actionEvent.eventSignature, bytes4(keccak256("TestEvent"))); + assertEq(actionEvent.actionType, 1); + } + + function test_InitializerDisabled() public { + // Because the slot is private, we use `vm.load` to access it then parse out the bits: + // - [0] is the `initializing` flag (which should be 0 == false) + // - [1..64] hold the `initializedVersion` (which should be 1) + bytes32 slot = vm.load(address(validator), 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffbf601132); + + uint64 version; + assembly { + version := shr(1, slot) + } + + assertNotEq(version, 0, "Version should not be 0"); + } + +} From 7ada596ca5f64663d6a4c7946cd8b68bb0efeff7 Mon Sep 17 00:00:00 2001 From: Quazia Date: Fri, 28 Jun 2024 11:33:58 -0400 Subject: [PATCH 03/17] fix(contracts): rename ActionEventValidator --- .../{EventSignerValidator.sol => ActionEventValidator.sol} | 0 .../{EventSignerValidator.t.sol => ActionEventValidator.sol} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename packages/evm/contracts/validators/{EventSignerValidator.sol => ActionEventValidator.sol} (100%) rename packages/evm/test/validators/{EventSignerValidator.t.sol => ActionEventValidator.sol} (100%) diff --git a/packages/evm/contracts/validators/EventSignerValidator.sol b/packages/evm/contracts/validators/ActionEventValidator.sol similarity index 100% rename from packages/evm/contracts/validators/EventSignerValidator.sol rename to packages/evm/contracts/validators/ActionEventValidator.sol diff --git a/packages/evm/test/validators/EventSignerValidator.t.sol b/packages/evm/test/validators/ActionEventValidator.sol similarity index 100% rename from packages/evm/test/validators/EventSignerValidator.t.sol rename to packages/evm/test/validators/ActionEventValidator.sol From ee69c753ae890e28bead057888a700ae153ee27d Mon Sep 17 00:00:00 2001 From: Quazia Date: Fri, 28 Jun 2024 11:46:22 -0400 Subject: [PATCH 04/17] chore: lint --- .../validators/ActionEventValidator.sol | 21 ++++++++++++------- .../test/validators/ActionEventValidator.sol | 17 +++++---------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/packages/evm/contracts/validators/ActionEventValidator.sol b/packages/evm/contracts/validators/ActionEventValidator.sol index 765c922a..f605e859 100644 --- a/packages/evm/contracts/validators/ActionEventValidator.sol +++ b/packages/evm/contracts/validators/ActionEventValidator.sol @@ -8,8 +8,19 @@ import {Validator} from "contracts/validators/Validator.sol"; import {SignerValidator} from "contracts/validators/Validator.sol"; // Define Enums -enum FilterType { EQUAL, NOT_EQUAL, GREATER_THAN, LESS_THAN } -enum PrimitiveType { UINT, ADDRESS, BYTES, STRING } +enum FilterType { + EQUAL, + NOT_EQUAL, + GREATER_THAN, + LESS_THAN +} + +enum PrimitiveType { + UINT, + ADDRESS, + BYTES, + STRING +} // Define Structs struct Criteria { @@ -27,11 +38,7 @@ struct ActionEvent { contract ActionEventValidator is SignerValidator { ActionEvent private actionEvent; - event ActionEventInitialized( - bytes4 indexed eventSignature, - uint8 actionType, - Criteria[4] actionParameters - ); + event ActionEventInitialized(bytes4 indexed eventSignature, uint8 actionType, Criteria[4] actionParameters); /// @notice Initialize the contract with the list of authorized signers and the ActionEvent /// @param data_ The compressed data containing the list of authorized signers and the ActionEvent diff --git a/packages/evm/test/validators/ActionEventValidator.sol b/packages/evm/test/validators/ActionEventValidator.sol index 3b21094a..f9b2a82b 100644 --- a/packages/evm/test/validators/ActionEventValidator.sol +++ b/packages/evm/test/validators/ActionEventValidator.sol @@ -47,19 +47,13 @@ contract ActionEventValidatorTest is Test { signers[2] = address(smartSignerMock); Criteria[4] memory criteria; - for (uint i = 0; i < 4; i++) { - criteria[i] = Criteria({ - filterType: FilterType.EQUAL, - fieldType: PrimitiveType.BYTES, - filterData: bytes("") - }); + for (uint256 i = 0; i < 4; i++) { + criteria[i] = + Criteria({filterType: FilterType.EQUAL, fieldType: PrimitiveType.BYTES, filterData: bytes("")}); } - ActionEvent memory actionEvent = ActionEvent({ - eventSignature: bytes4(keccak256("TestEvent")), - actionType: 1, - actionParameters: criteria - }); + ActionEvent memory actionEvent = + ActionEvent({eventSignature: bytes4(keccak256("TestEvent")), actionType: 1, actionParameters: criteria}); bytes memory data = abi.encode(signers, actionEvent); validator = ActionEventValidator(LibClone.clone(address(baseValidator))); @@ -102,5 +96,4 @@ contract ActionEventValidatorTest is Test { assertNotEq(version, 0, "Version should not be 0"); } - } From df84a635a197818ef9572feea1e7f25d20bebec8 Mon Sep 17 00:00:00 2001 From: Sam McCord Date: Wed, 24 Jul 2024 10:05:10 -0600 Subject: [PATCH 05/17] refactor(evm): update action event validator to include component interfaces --- .../validators/AActionEventValidator.sol | 60 +++++++++++++++++++ .../validators/ActionEventValidator.sol | 42 +------------ 2 files changed, 62 insertions(+), 40 deletions(-) create mode 100644 packages/evm/contracts/validators/AActionEventValidator.sol diff --git a/packages/evm/contracts/validators/AActionEventValidator.sol b/packages/evm/contracts/validators/AActionEventValidator.sol new file mode 100644 index 00000000..1e6f4d69 --- /dev/null +++ b/packages/evm/contracts/validators/AActionEventValidator.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.24; + +import {SignatureCheckerLib} from "@solady/utils/SignatureCheckerLib.sol"; +import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; + +import {Cloneable} from "contracts/shared/Cloneable.sol"; +import {BoostError} from "contracts/shared/BoostError.sol"; +import {BoostError} from "contracts/shared/BoostError.sol"; + +import {SignerValidator} from "contracts/validators/SignerValidator.sol"; +import {ASignerValidator} from "contracts/validators/ASignerValidator.sol"; +import {Validator} from "contracts/validators/Validator.sol"; + + +contract AActionEventValidator is SignerValidator { + ActionEvent internal actionEvent; + + // Define Enums + enum FilterType { + EQUAL, + NOT_EQUAL, + GREATER_THAN, + LESS_THAN + } + + enum PrimitiveType { + UINT, + ADDRESS, + BYTES, + STRING + } + + // Define Structs + struct Criteria { + FilterType filterType; + PrimitiveType fieldType; + bytes filterData; + } + + struct ActionEvent { + bytes4 eventSignature; + uint8 actionType; + Criteria[4] actionParameters; + } + + function getActionEvent() public view returns (ActionEvent memory) { + return actionEvent; + } + + /// @inheritdoc Cloneable + function getComponentInterface() public pure virtual override(Validator) returns (bytes4) { + return type(AActionEventValidator).interfaceId; + } + + /// @inheritdoc Cloneable + function supportsInterface(bytes4 interfaceId) public view virtual override(ASignerValidator, Validator) returns (bool) { + return interfaceId == type(AActionEventValidator).interfaceId || super.supportsInterface(interfaceId); + } +} diff --git a/packages/evm/contracts/validators/ActionEventValidator.sol b/packages/evm/contracts/validators/ActionEventValidator.sol index f605e859..541b63df 100644 --- a/packages/evm/contracts/validators/ActionEventValidator.sol +++ b/packages/evm/contracts/validators/ActionEventValidator.sol @@ -1,43 +1,9 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.24; -import {SignatureCheckerLib} from "@solady/utils/SignatureCheckerLib.sol"; -import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; -import {BoostError} from "contracts/shared/BoostError.sol"; -import {Validator} from "contracts/validators/Validator.sol"; -import {SignerValidator} from "contracts/validators/Validator.sol"; - -// Define Enums -enum FilterType { - EQUAL, - NOT_EQUAL, - GREATER_THAN, - LESS_THAN -} - -enum PrimitiveType { - UINT, - ADDRESS, - BYTES, - STRING -} - -// Define Structs -struct Criteria { - FilterType filterType; - PrimitiveType fieldType; - bytes filterData; -} - -struct ActionEvent { - bytes4 eventSignature; - uint8 actionType; - Criteria[4] actionParameters; -} - -contract ActionEventValidator is SignerValidator { - ActionEvent private actionEvent; +import {AActionEventValidator} from "contracts/validators/AActionEventValidator.sol"; +contract ActionEventValidator is AActionEventValidator { event ActionEventInitialized(bytes4 indexed eventSignature, uint8 actionType, Criteria[4] actionParameters); /// @notice Initialize the contract with the list of authorized signers and the ActionEvent @@ -58,8 +24,4 @@ contract ActionEventValidator is SignerValidator { emit ActionEventInitialized(actionEvent.eventSignature, actionEvent.actionType, actionEvent.actionParameters); } - - function getActionEvent() public view returns (ActionEvent memory) { - return actionEvent; - } } From 018093f361d5cdf6b03ee01dbdc8ada85580d564 Mon Sep 17 00:00:00 2001 From: Sam McCord Date: Wed, 24 Jul 2024 10:06:37 -0600 Subject: [PATCH 06/17] refactor(evm): update action event validator to include component interfaces --- .../evm/contracts/validators/AActionEventValidator.sol | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/evm/contracts/validators/AActionEventValidator.sol b/packages/evm/contracts/validators/AActionEventValidator.sol index 1e6f4d69..433e114c 100644 --- a/packages/evm/contracts/validators/AActionEventValidator.sol +++ b/packages/evm/contracts/validators/AActionEventValidator.sol @@ -12,7 +12,6 @@ import {SignerValidator} from "contracts/validators/SignerValidator.sol"; import {ASignerValidator} from "contracts/validators/ASignerValidator.sol"; import {Validator} from "contracts/validators/Validator.sol"; - contract AActionEventValidator is SignerValidator { ActionEvent internal actionEvent; @@ -54,7 +53,13 @@ contract AActionEventValidator is SignerValidator { } /// @inheritdoc Cloneable - function supportsInterface(bytes4 interfaceId) public view virtual override(ASignerValidator, Validator) returns (bool) { + function supportsInterface(bytes4 interfaceId) + public + view + virtual + override(ASignerValidator, Validator) + returns (bool) + { return interfaceId == type(AActionEventValidator).interfaceId || super.supportsInterface(interfaceId); } } From e9c90713d365c6eef8d50b7ed861c8a96357ce14 Mon Sep 17 00:00:00 2001 From: Quazia Date: Tue, 27 Aug 2024 11:02:30 -0400 Subject: [PATCH 07/17] feat(evm): delete event validator we'll be using an action to hold event information instead --- .../validators/AActionEventValidator.sol | 65 ------------------- .../validators/ActionEventValidator.sol | 27 -------- 2 files changed, 92 deletions(-) delete mode 100644 packages/evm/contracts/validators/AActionEventValidator.sol delete mode 100644 packages/evm/contracts/validators/ActionEventValidator.sol diff --git a/packages/evm/contracts/validators/AActionEventValidator.sol b/packages/evm/contracts/validators/AActionEventValidator.sol deleted file mode 100644 index 433e114c..00000000 --- a/packages/evm/contracts/validators/AActionEventValidator.sol +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.24; - -import {SignatureCheckerLib} from "@solady/utils/SignatureCheckerLib.sol"; -import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; - -import {Cloneable} from "contracts/shared/Cloneable.sol"; -import {BoostError} from "contracts/shared/BoostError.sol"; -import {BoostError} from "contracts/shared/BoostError.sol"; - -import {SignerValidator} from "contracts/validators/SignerValidator.sol"; -import {ASignerValidator} from "contracts/validators/ASignerValidator.sol"; -import {Validator} from "contracts/validators/Validator.sol"; - -contract AActionEventValidator is SignerValidator { - ActionEvent internal actionEvent; - - // Define Enums - enum FilterType { - EQUAL, - NOT_EQUAL, - GREATER_THAN, - LESS_THAN - } - - enum PrimitiveType { - UINT, - ADDRESS, - BYTES, - STRING - } - - // Define Structs - struct Criteria { - FilterType filterType; - PrimitiveType fieldType; - bytes filterData; - } - - struct ActionEvent { - bytes4 eventSignature; - uint8 actionType; - Criteria[4] actionParameters; - } - - function getActionEvent() public view returns (ActionEvent memory) { - return actionEvent; - } - - /// @inheritdoc Cloneable - function getComponentInterface() public pure virtual override(Validator) returns (bytes4) { - return type(AActionEventValidator).interfaceId; - } - - /// @inheritdoc Cloneable - function supportsInterface(bytes4 interfaceId) - public - view - virtual - override(ASignerValidator, Validator) - returns (bool) - { - return interfaceId == type(AActionEventValidator).interfaceId || super.supportsInterface(interfaceId); - } -} diff --git a/packages/evm/contracts/validators/ActionEventValidator.sol b/packages/evm/contracts/validators/ActionEventValidator.sol deleted file mode 100644 index 541b63df..00000000 --- a/packages/evm/contracts/validators/ActionEventValidator.sol +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.24; - -import {AActionEventValidator} from "contracts/validators/AActionEventValidator.sol"; - -contract ActionEventValidator is AActionEventValidator { - event ActionEventInitialized(bytes4 indexed eventSignature, uint8 actionType, Criteria[4] actionParameters); - - /// @notice Initialize the contract with the list of authorized signers and the ActionEvent - /// @param data_ The compressed data containing the list of authorized signers and the ActionEvent - /// @dev The first address in the list will be the initial owner of the contract - function initialize(bytes calldata data_) public virtual override initializer { - (address[] memory signers_, ActionEvent memory actionEvent_) = abi.decode(data_, (address[], ActionEvent)); - - _initializeOwner(signers_[0]); - for (uint256 i = 0; i < signers_.length; i++) { - signers[signers_[i]] = true; - } - - require(actionEvent_.eventSignature != bytes4(0), "Invalid event signature"); - require(bytes(actionTypeMapping[actionEvent_.actionType]).length != 0, "Invalid action type"); - - actionEvent = actionEvent_; - - emit ActionEventInitialized(actionEvent.eventSignature, actionEvent.actionType, actionEvent.actionParameters); - } -} From 32698138f650fd5b26107ea50ec96dfadb38dfa6 Mon Sep 17 00:00:00 2001 From: Quazia Date: Tue, 27 Aug 2024 11:04:38 -0400 Subject: [PATCH 08/17] feat(evm): add event action --- .../evm/contracts/actions/AEventAction.sol | 91 +++++++++++++++++++ .../evm/contracts/actions/EventAction.sol | 38 ++++++++ 2 files changed, 129 insertions(+) create mode 100644 packages/evm/contracts/actions/AEventAction.sol create mode 100644 packages/evm/contracts/actions/EventAction.sol diff --git a/packages/evm/contracts/actions/AEventAction.sol b/packages/evm/contracts/actions/AEventAction.sol new file mode 100644 index 00000000..09f05cf2 --- /dev/null +++ b/packages/evm/contracts/actions/AEventAction.sol @@ -0,0 +1,91 @@ +// 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; + Criteria actionParameters; + } + + /// @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(Action).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); + } +} diff --git a/packages/evm/contracts/actions/EventAction.sol b/packages/evm/contracts/actions/EventAction.sol new file mode 100644 index 00000000..1085bfef --- /dev/null +++ b/packages/evm/contracts/actions/EventAction.sol @@ -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); + } +} From a353914dd65aec72ab8f09ec49c049cf1fe50ea0 Mon Sep 17 00:00:00 2001 From: Quazia Date: Tue, 27 Aug 2024 11:04:53 -0400 Subject: [PATCH 09/17] test(evm): event action --- packages/evm/test/actions/EventAction.t.sol | 98 ++++++++++++++++++ .../test/validators/ActionEventValidator.sol | 99 ------------------- 2 files changed, 98 insertions(+), 99 deletions(-) create mode 100644 packages/evm/test/actions/EventAction.t.sol delete mode 100644 packages/evm/test/validators/ActionEventValidator.sol diff --git a/packages/evm/test/actions/EventAction.t.sol b/packages/evm/test/actions/EventAction.t.sol new file mode 100644 index 00000000..937119ee --- /dev/null +++ b/packages/evm/test/actions/EventAction.t.sol @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.24; + +import {Test} 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, + actionParameters: 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.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))); + } +} diff --git a/packages/evm/test/validators/ActionEventValidator.sol b/packages/evm/test/validators/ActionEventValidator.sol deleted file mode 100644 index f9b2a82b..00000000 --- a/packages/evm/test/validators/ActionEventValidator.sol +++ /dev/null @@ -1,99 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.24; - -import {Test, console} from "lib/forge-std/src/Test.sol"; - -import {ECDSA} from "@solady/utils/ECDSA.sol"; -import {LibClone} from "@solady/utils/LibClone.sol"; -import {SignatureCheckerLib} from "@solady/utils/SignatureCheckerLib.sol"; - -import {MockERC1271Wallet} from "lib/solady/test/utils/mocks/MockERC1271Wallet.sol"; -import {MockERC1271Malicious} from "lib/solady/test/utils/mocks/MockERC1271Malicious.sol"; - -import {BoostError} from "contracts/shared/BoostError.sol"; -import {Cloneable} from "contracts/shared/Cloneable.sol"; -import {Validator} from "contracts/validators/Validator.sol"; -import {SignerValidator} from "contracts/validators/SignerValidator.sol"; -import {ActionEventValidator} from "contracts/validators/ActionEventValidator.sol"; - -contract ActionEventValidatorTest is Test { - ActionEventValidator baseValidator = new ActionEventValidator(); - ActionEventValidator validator; - - uint256 testSignerKey = uint256(vm.envUint("TEST_SIGNER_PRIVATE_KEY")); - address testSigner = vm.addr(testSignerKey); - - uint256 fakeSignerKey = uint256(0xdeadbeef); - address fakeSigner = vm.addr(fakeSignerKey); - - MockERC1271Wallet smartSignerMock = new MockERC1271Wallet(testSigner); - MockERC1271Malicious maliciousSignerMock = new MockERC1271Malicious(); - - bytes32 MESSAGE_HASH = keccak256(abi.encodePacked("test")); - bytes TRUSTED_EOA_SIGNATURE = _signHash(MESSAGE_HASH, testSignerKey); - bytes UNTRUSTED_EOA_SIGNATURE = _signHash(MESSAGE_HASH, fakeSignerKey); - - bytes PACKED_EOA_SIGNATURE = abi.encode(testSigner, MESSAGE_HASH, TRUSTED_EOA_SIGNATURE); - bytes PACKED_1271_SIGNATURE = abi.encode(smartSignerMock, MESSAGE_HASH, TRUSTED_EOA_SIGNATURE); - - bytes PACKED_MALICIOUS_SIGNATURE = abi.encode(maliciousSignerMock, MESSAGE_HASH, TRUSTED_EOA_SIGNATURE); - bytes PACKED_WRONG_SIGNATURE = abi.encode(testSigner, MESSAGE_HASH, UNTRUSTED_EOA_SIGNATURE); - bytes PACKED_UNTRUSTED_SIGNATURE = abi.encode(fakeSigner, MESSAGE_HASH, UNTRUSTED_EOA_SIGNATURE); - - function setUp() public { - address[] memory signers = new address[](3); - signers[0] = address(this); - signers[1] = testSigner; - signers[2] = address(smartSignerMock); - - Criteria[4] memory criteria; - for (uint256 i = 0; i < 4; i++) { - criteria[i] = - Criteria({filterType: FilterType.EQUAL, fieldType: PrimitiveType.BYTES, filterData: bytes("")}); - } - - ActionEvent memory actionEvent = - ActionEvent({eventSignature: bytes4(keccak256("TestEvent")), actionType: 1, actionParameters: criteria}); - - bytes memory data = abi.encode(signers, actionEvent); - validator = ActionEventValidator(LibClone.clone(address(baseValidator))); - validator.initialize(data); - } - - //////////////////////////////// - // ActionEventValidator.initialize // - //////////////////////////////// - - function testInitialize() public { - // The initializer should have set 3 signers: - assertTrue(validator.signers(address(this))); - assertTrue(validator.signers(testSigner)); - assertTrue(validator.signers(address(smartSignerMock))); - - // Other signers should not be authorized - assertFalse(validator.signers(fakeSigner)); - assertFalse(validator.signers(address(maliciousSignerMock))); - - // The owner of the contract should be the first signer - assertEq(validator.owner(), address(this)); - - // The action event should be initialized correctly - ActionEvent memory actionEvent = validator.getActionEvent(); - assertEq(actionEvent.eventSignature, bytes4(keccak256("TestEvent"))); - assertEq(actionEvent.actionType, 1); - } - - function test_InitializerDisabled() public { - // Because the slot is private, we use `vm.load` to access it then parse out the bits: - // - [0] is the `initializing` flag (which should be 0 == false) - // - [1..64] hold the `initializedVersion` (which should be 1) - bytes32 slot = vm.load(address(validator), 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffbf601132); - - uint64 version; - assembly { - version := shr(1, slot) - } - - assertNotEq(version, 0, "Version should not be 0"); - } -} From 78496784176fe2601982b28c5861bfd47d491772 Mon Sep 17 00:00:00 2001 From: Quazia Date: Tue, 27 Aug 2024 11:11:43 -0400 Subject: [PATCH 10/17] feat(evm): add address target to event action --- packages/evm/contracts/actions/AEventAction.sol | 3 ++- packages/evm/test/actions/EventAction.t.sol | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/evm/contracts/actions/AEventAction.sol b/packages/evm/contracts/actions/AEventAction.sol index 09f05cf2..f59d3f4a 100644 --- a/packages/evm/contracts/actions/AEventAction.sol +++ b/packages/evm/contracts/actions/AEventAction.sol @@ -43,7 +43,8 @@ abstract contract AEventAction is Action { struct ActionEvent { bytes4 eventSignature; uint8 actionType; - Criteria actionParameters; + Address targetContract; + Criteria actionParameter; } /// @inheritdoc Cloneable diff --git a/packages/evm/test/actions/EventAction.t.sol b/packages/evm/test/actions/EventAction.t.sol index 937119ee..1c97a66a 100644 --- a/packages/evm/test/actions/EventAction.t.sol +++ b/packages/evm/test/actions/EventAction.t.sol @@ -31,6 +31,7 @@ contract EventActionTest is Test { actionEventOne = AEventAction.ActionEvent({ eventSignature: bytes4(keccak256("Transfer(address,address,uint256)")), actionType: 0, + targetContract: Address(mockAsset), actionParameters: criteria }); From 774e0fdffc41ef03ed1ed672fbf66eef051190dd Mon Sep 17 00:00:00 2001 From: Quazia Date: Tue, 27 Aug 2024 11:33:42 -0400 Subject: [PATCH 11/17] fix(evm): typo in actionParatmer vs actionParameters --- packages/evm/contracts/actions/AEventAction.sol | 2 +- packages/evm/test/actions/EventAction.t.sol | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/evm/contracts/actions/AEventAction.sol b/packages/evm/contracts/actions/AEventAction.sol index f59d3f4a..3b08c8fe 100644 --- a/packages/evm/contracts/actions/AEventAction.sol +++ b/packages/evm/contracts/actions/AEventAction.sol @@ -43,7 +43,7 @@ abstract contract AEventAction is Action { struct ActionEvent { bytes4 eventSignature; uint8 actionType; - Address targetContract; + address targetContract; Criteria actionParameter; } diff --git a/packages/evm/test/actions/EventAction.t.sol b/packages/evm/test/actions/EventAction.t.sol index 1c97a66a..a7f30889 100644 --- a/packages/evm/test/actions/EventAction.t.sol +++ b/packages/evm/test/actions/EventAction.t.sol @@ -31,8 +31,8 @@ contract EventActionTest is Test { actionEventOne = AEventAction.ActionEvent({ eventSignature: bytes4(keccak256("Transfer(address,address,uint256)")), actionType: 0, - targetContract: Address(mockAsset), - actionParameters: criteria + targetContract: address(mockAsset), + actionParameter: criteria }); EventAction.InitPayload memory payload = EventAction.InitPayload({ From 1a6ab6388f6bb2c3d9d597a821e83518361813ce Mon Sep 17 00:00:00 2001 From: Quazia Date: Tue, 27 Aug 2024 18:31:13 -0400 Subject: [PATCH 12/17] fix(evem): getComponentInterface in AEventAction --- packages/evm/contracts/actions/AEventAction.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/evm/contracts/actions/AEventAction.sol b/packages/evm/contracts/actions/AEventAction.sol index 3b08c8fe..18a778c0 100644 --- a/packages/evm/contracts/actions/AEventAction.sol +++ b/packages/evm/contracts/actions/AEventAction.sol @@ -70,7 +70,7 @@ abstract contract AEventAction is Action { /// @inheritdoc Action function getComponentInterface() public pure virtual override(Action) returns (bytes4) { - return type(Action).interfaceId; + return type(AEventAction).interfaceId; } function getActionEventsCount() public view virtual returns (uint256) { From 91f59b610c7e797638ca251de44358523862f464 Mon Sep 17 00:00:00 2001 From: Quazia Date: Tue, 27 Aug 2024 18:32:05 -0400 Subject: [PATCH 13/17] test(evem): getComponentInterface in AEventAction --- packages/evm/test/actions/EventAction.t.sol | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/evm/test/actions/EventAction.t.sol b/packages/evm/test/actions/EventAction.t.sol index a7f30889..33fb69fb 100644 --- a/packages/evm/test/actions/EventAction.t.sol +++ b/packages/evm/test/actions/EventAction.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.24; -import {Test} from "lib/forge-std/src/Test.sol"; +import {Test, console} from "lib/forge-std/src/Test.sol"; import {LibClone} from "@solady/utils/LibClone.sol"; import {MockERC721} from "contracts/shared/Mocks.sol"; @@ -79,6 +79,14 @@ contract EventActionTest is Test { assertEq(retrievedEvent.eventSignature, bytes4(keccak256("Transfer(address,address,uint256)"))); } + //////////////////////////////////// + // EventAction.getComponentInterface // + //////////////////////////////////// + + function testGetComponentInterface() public { + // Retrieve the component interface + console.logBytes4(action.getComponentInterface()); + } //////////////////////////////////// // EventAction.supportsInterface // //////////////////////////////////// From d3a204e02e2a7940a0fca23e4a9d55100f5a2864 Mon Sep 17 00:00:00 2001 From: Quazia Date: Tue, 27 Aug 2024 18:33:06 -0400 Subject: [PATCH 14/17] feat(sdk): add EventAction --- packages/cli/src/commands/deploy.ts | 16 ++ packages/sdk/package.json | 10 +- packages/sdk/src/Actions/Action.ts | 13 +- packages/sdk/src/Actions/EventAction.ts | 175 ++++++++++++++++++ packages/sdk/src/BoostCore.ts | 21 +++ packages/sdk/src/index.ts | 1 + packages/sdk/src/utils.ts | 232 ++++++++++++++++++++++++ packages/sdk/test/helpers.ts | 13 ++ 8 files changed, 476 insertions(+), 5 deletions(-) create mode 100644 packages/sdk/src/Actions/EventAction.ts diff --git a/packages/cli/src/commands/deploy.ts b/packages/cli/src/commands/deploy.ts index bf6fac1c..694b507e 100644 --- a/packages/cli/src/commands/deploy.ts +++ b/packages/cli/src/commands/deploy.ts @@ -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 EventActionArtifcat 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'; @@ -20,6 +21,7 @@ import { ERC20Incentive, ERC721MintAction, ERC1155Incentive, + EventAction, PointsIncentive, SignerValidator, SimpleAllowList, @@ -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; @@ -175,6 +178,15 @@ export const deploy: Command = async function deploy(opts) { }), ); + const eventActionBase = await getDeployedContractAddress( + config, + deployContract(config, { + abi: EventActionArtifcat.abi, + bytecode: EventActionArtifcat.bytecode as Hex, + account, + }), + ); + const pointsIncentiveBase = await getDeployedContractAddress( config, deployContract(config, { @@ -200,6 +212,9 @@ export const deploy: Command = 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; }, @@ -240,6 +255,7 @@ export const deploy: Command = 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, diff --git a/packages/sdk/package.json b/packages/sdk/package.json index ec178bc8..64d3bc5b 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -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", "access": "public", @@ -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", diff --git a/packages/sdk/src/Actions/Action.ts b/packages/sdk/src/Actions/Action.ts index ae69ff70..69258579 100644 --- a/packages/sdk/src/Actions/Action.ts +++ b/packages/sdk/src/Actions/Action.ts @@ -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 }; @@ -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, }; /** @@ -33,7 +35,7 @@ export const ActionByComponentInterface = { * @async * @param {DeployableOptions} options * @param {Address} address - * @returns {Promise} + * @returns {Promise} * @throws {@link InvalidComponentInterfaceError} */ export async function actionFromAddress( @@ -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; } diff --git a/packages/sdk/src/Actions/EventAction.ts b/packages/sdk/src/Actions/EventAction.ts new file mode 100644 index 00000000..a08141d2 --- /dev/null +++ b/packages/sdk/src/Actions/EventAction.ts @@ -0,0 +1,175 @@ +import { + eventActionAbi, + readEventActionGetActionEvent, + readEventActionGetActionEvents, + readEventActionGetActionEventsCount, + simulateEventActionExecute, + writeEventActionExecute, +} from '@boostxyz/evm'; +import { bytecode } from '@boostxyz/evm/artifacts/contracts/actions/EventAction.sol/EventAction.json'; +import type { Address, Hex } from 'viem'; +import type { + DeployableOptions, + GenericDeployableParams, +} from '../Deployable/Deployable'; +import { DeployableTarget } from '../Deployable/DeployableTarget'; +import { + type EventActionPayload, + type ReadParams, + RegistryType, + type WriteParams, + prepareEventActionPayload, +} from '../utils'; + +export type { EventActionPayload }; + +/** + * A generic event action + * + * @export + * @class EventAction + * @typedef {EventAction} + * @extends {DeployableTarget} + */ +export class EventAction extends DeployableTarget { + /** + * @inheritdoc + * + * @public + * @static + * @type {Address} + */ + public static override base: Address = import.meta.env.VITE_EVENT_ACTION_BASE; + /** + * @inheritdoc + * + * @public + * @static + * @type {RegistryType} + */ + public static override registryType: RegistryType = RegistryType.ACTION; + + /** + * Gets a specific action event by index + * + * @public + * @async + * @param {number} index The index of the action event to retrieve + * @param {?ReadParams} [params] + * @returns {Promise} + */ + public async getActionEvent( + index: number, + params?: ReadParams, + ) { + return readEventActionGetActionEvent(this._config, { + address: this.assertValidAddress(), + ...this.optionallyAttachAccount(), + // biome-ignore lint/suspicious/noExplicitAny: Accept any shape of valid wagmi/viem parameters, wagmi does the same thing internally + ...(params as any), + args: [index], + }); + } + + /** + * Gets all action events + * + * @public + * @async + * @param {?ReadParams} [params] + * @returns {Promise} + */ + public async getActionEvents( + params?: ReadParams, + ) { + return readEventActionGetActionEvents(this._config, { + address: this.assertValidAddress(), + ...this.optionallyAttachAccount(), + // biome-ignore lint/suspicious/noExplicitAny: Accept any shape of valid wagmi/viem parameters, wagmi does the same thing internally + ...(params as any), + }); + } + + /** + * Gets the count of action events + * + * @public + * @async + * @param {?ReadParams} [params] + * @returns {Promise} + */ + public async getActionEventsCount( + params?: ReadParams, + ) { + return readEventActionGetActionEventsCount(this._config, { + address: this.assertValidAddress(), + ...this.optionallyAttachAccount(), + // biome-ignore lint/suspicious/noExplicitAny: Accept any shape of valid wagmi/viem parameters, wagmi does the same thing internally + ...(params as any), + }); + } + + /** + * Executes a prepared event action + * + * @public + * @async + * @param {Hex} data + * @param {?WriteParams} [params] + * @returns {Promise} + */ + public async execute( + data: Hex, + params?: WriteParams, + ) { + return this.awaitResult(this.executeRaw(data, params)); + } + + /** + * Executes a prepared event action + * + * @public + * @async + * @param {Hex} data + * @param {?WriteParams} [params] + * @returns {unknown} + */ + public async executeRaw( + data: Hex, + params?: WriteParams, + ) { + const { request, result } = await simulateEventActionExecute(this._config, { + address: this.assertValidAddress(), + ...this.optionallyAttachAccount(), + // biome-ignore lint/suspicious/noExplicitAny: Accept any shape of valid wagmi/viem parameters, wagmi does the same thing internally + ...(params as any), + args: [data], + }); + const hash = await writeEventActionExecute(this._config, request); + return { hash, result }; + } + + /** + * @inheritdoc + * + * @public + * @param {?EventActionPayload} [_payload] + * @param {?DeployableOptions} [_options] + * @returns {GenericDeployableParams} + */ + public override buildParameters( + _payload?: EventActionPayload, + _options?: DeployableOptions, + ): GenericDeployableParams { + const [payload, options] = this.validateDeploymentConfig( + _payload, + _options, + ); + return { + abi: eventActionAbi, + bytecode: bytecode as Hex, + args: [prepareEventActionPayload(payload)], + ...this.optionallyAttachAccount(options.account), + }; + } +} diff --git a/packages/sdk/src/BoostCore.ts b/packages/sdk/src/BoostCore.ts index da991d86..40a90553 100644 --- a/packages/sdk/src/BoostCore.ts +++ b/packages/sdk/src/BoostCore.ts @@ -31,6 +31,7 @@ import { ERC721MintAction, type ERC721MintActionPayload, } from './Actions/ERC721MintAction'; +import { EventAction } from './Actions/EventAction'; import { type AllowList, allowListFromAddress } from './AllowLists/AllowList'; import { SimpleAllowList, @@ -86,6 +87,7 @@ import { NoContractAddressUponReceiptError, } from './errors'; import { + type EventActionPayload, type BoostPayload as OnChainBoostPayload, type ReadParams, type Target, @@ -748,6 +750,25 @@ export class BoostCore extends Deployable<[Address, Address]> { isBase, ); } + + /** + * Bound {@link EventAction} constructor that reuses the same configuration as the Boost Core instance. + * + * @example + * ```ts + * const action = core.EventAction('0x') // is roughly equivalent to + * const action = new EventAction({ config: core._config, account: core._account }, '0x') + */ + EventAction( + options: DeployablePayloadOrAddress, + isBase?: boolean, + ) { + return new EventAction( + { config: this._config, account: this._account }, + options, + isBase, + ); + } /** * Bound {@link ERC721MintAction} constructor that reuses the same configuration as the Boost Core instance. * diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index 3581bb2b..58f73c3f 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -9,6 +9,7 @@ export * from './utils'; export * from './Actions/Action'; export * from './Actions/ContractAction'; export * from './Actions/ERC721MintAction'; +export * from './Actions/EventAction'; // AllowLists diff --git a/packages/sdk/src/utils.ts b/packages/sdk/src/utils.ts index 78683ae9..382261ed 100644 --- a/packages/sdk/src/utils.ts +++ b/packages/sdk/src/utils.ts @@ -188,6 +188,238 @@ export type Target = { parameters: Hex; }; +/* + * Action Event Payloads + */ + +/** + * Filter types used to determine how criteria are evaluated. + * + * @export + * @enum {number} + */ +export enum FilterType { + EQUAL = 0, + NOT_EQUAL = 1, + GREATER_THAN = 2, + LESS_THAN = 3, + CONTAINS = 4, +} + +/** + * The primitive types supported for filtering. + * + * @export + * @enum {number} + */ +export enum PrimitiveType { + UINT = 0, + ADDRESS = 1, + BYTES = 2, + STRING = 3, +} + +/** + * Object representation of a `Criteria` struct used in event actions. + * + * @export + * @interface Criteria + * @typedef {Criteria} + */ +export interface Criteria { + /** + * The filter type used in this criteria. + * + * @type {FilterType} + */ + filterType: FilterType; + /** + * The primitive type of the field being filtered. + * + * @type {PrimitiveType} + */ + fieldType: PrimitiveType; + /** + * The index in the logs argument array where the field is located. + * + * @type {number} + */ + fieldIndex: number; + /** + * The filter data used for complex filtering. + * + * @type {Hex} + */ + filterData: Hex; +} + +/** + * Object representation of an `ActionEvent` struct used in event actions. + * + * @export + * @interface ActionEvent + * @typedef {ActionEvent} + */ +export interface ActionEvent { + /** + * The signature of the event. + * + * @type {Hex} + */ + eventSignature: Hex; + /** + * The type of action being performed. + * + * @type {number} + */ + actionType: number; + /** + * The address of the target contract. + * + * @type {Address} + */ + targetContract: Address; + /** + * The criteria used for this action event. + * + * @type {Criteria} + */ + actionParameter: Criteria; +} + +/** + * Object representation of an `InitPayload` struct used to initialize event actions. + * + * @export + * @interface EventActionPayload + * @typedef {EventActionPayload} + */ +export interface EventActionPayload { + /** + * The first action event. + * + * @type {ActionEvent} + */ + actionEventOne: ActionEvent; + /** + * The second action event. + * + * @type {ActionEvent} + */ + actionEventTwo: ActionEvent; + /** + * The third action event. + * + * @type {ActionEvent} + */ + actionEventThree: ActionEvent; + /** + * The fourth action event. + * + * @type {ActionEvent} + */ + actionEventFour: ActionEvent; +} + +/** + * Function to properly encode an event action payload. + * + * @param {InitPayload} param0 + * @param {ActionEvent} param0.actionEventOne - The first action event to initialize. + * @param {ActionEvent} param0.actionEventTwo - The second action event to initialize. + * @param {ActionEvent} param0.actionEventThree - The third action event to initialize. + * @param {ActionEvent} param0.actionEventFour - The fourth action event to initialize. + * @returns {Hex} + */ +export const prepareEventActionPayload = ({ + actionEventOne, + actionEventTwo, + actionEventThree, + actionEventFour, +}: EventActionPayload) => { + return encodeAbiParameters( + [ + { + type: 'tuple', + name: 'actionEventOne', + components: [ + { type: 'bytes4', name: 'eventSignature' }, + { type: 'uint8', name: 'actionType' }, + { type: 'address', name: 'targetContract' }, + { + type: 'tuple', + name: 'actionParameter', + components: [ + { type: 'uint8', name: 'filterType' }, + { type: 'uint8', name: 'fieldType' }, + { type: 'uint8', name: 'fieldIndex' }, + { type: 'bytes', name: 'filterData' }, + ], + }, + ], + }, + { + type: 'tuple', + name: 'actionEventTwo', + components: [ + { type: 'bytes4', name: 'eventSignature' }, + { type: 'uint8', name: 'actionType' }, + { type: 'address', name: 'targetContract' }, + { + type: 'tuple', + name: 'actionParameter', + components: [ + { type: 'uint8', name: 'filterType' }, + { type: 'uint8', name: 'fieldType' }, + { type: 'uint8', name: 'fieldIndex' }, + { type: 'bytes', name: 'filterData' }, + ], + }, + ], + }, + { + type: 'tuple', + name: 'actionEventThree', + components: [ + { type: 'bytes4', name: 'eventSignature' }, + { type: 'uint8', name: 'actionType' }, + { type: 'address', name: 'targetContract' }, + { + type: 'tuple', + name: 'actionParameter', + components: [ + { type: 'uint8', name: 'filterType' }, + { type: 'uint8', name: 'fieldType' }, + { type: 'uint8', name: 'fieldIndex' }, + { type: 'bytes', name: 'filterData' }, + ], + }, + ], + }, + { + type: 'tuple', + name: 'actionEventFour', + components: [ + { type: 'bytes4', name: 'eventSignature' }, + { type: 'uint8', name: 'actionType' }, + { type: 'address', name: 'targetContract' }, + { + type: 'tuple', + name: 'actionParameter', + components: [ + { type: 'uint8', name: 'filterType' }, + { type: 'uint8', name: 'fieldType' }, + { type: 'uint8', name: 'fieldIndex' }, + { type: 'bytes', name: 'filterData' }, + ], + }, + ], + }, + ], + [actionEventOne, actionEventTwo, actionEventThree, actionEventFour], + ); +}; + /** * Object representation of the `ERC20Incentive.InitPayload`. * diff --git a/packages/sdk/test/helpers.ts b/packages/sdk/test/helpers.ts index 12f4b2db..125689f3 100644 --- a/packages/sdk/test/helpers.ts +++ b/packages/sdk/test/helpers.ts @@ -7,6 +7,7 @@ import { writePointsInitialize, } from '@boostxyz/evm'; import ContractActionArtifact from '@boostxyz/evm/artifacts/contracts/actions/ContractAction.sol/ContractAction.json'; +import EventActionArtifact from '@boostxyz/evm/artifacts/contracts/actions/ContractAction.sol/ContractAction.json'; import ERC721MintActionArtifact from '@boostxyz/evm/artifacts/contracts/actions/ERC721MintAction.sol/ERC721MintAction.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'; @@ -30,6 +31,7 @@ import { type CreateBoostPayload, ERC20Incentive, ERC721MintAction, + EventAction, PointsIncentive, SignerValidator, SimpleAllowList, @@ -122,6 +124,14 @@ export async function deployFixtures( account, }), ); + const eventActionBase = await getDeployedContractAddress( + config, + deployContract(config, { + abi: EventActionArtifact.abi, + bytecode: ContractActionArtifact.bytecode as Hex, + account, + }), + ); const erc721MintActionBase = await getDeployedContractAddress( config, deployContract(config, { @@ -220,6 +230,9 @@ export async function deployFixtures( ContractAction: class TContractAction extends ContractAction { public static override base = contractActionBase; }, + EventAction: class TEventAction extends EventAction { + public static override base = eventActionBase; + }, ERC721MintAction: class TERC721MintAction extends ERC721MintAction { public static override base = erc721MintActionBase; }, From 7ad997057f333d8e10d22ca54646488167c799dd Mon Sep 17 00:00:00 2001 From: Quazia Date: Tue, 27 Aug 2024 19:31:13 -0400 Subject: [PATCH 15/17] fix(sdk): type EventActionArtifact --- packages/cli/src/commands/deploy.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/commands/deploy.ts b/packages/cli/src/commands/deploy.ts index 694b507e..bd869ecb 100644 --- a/packages/cli/src/commands/deploy.ts +++ b/packages/cli/src/commands/deploy.ts @@ -1,6 +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 EventActionArtifcat from '@boostxyz/evm/artifacts/contracts/actions/EventAction.sol/EventAction.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'; @@ -181,8 +181,8 @@ export const deploy: Command = async function deploy(opts) { const eventActionBase = await getDeployedContractAddress( config, deployContract(config, { - abi: EventActionArtifcat.abi, - bytecode: EventActionArtifcat.bytecode as Hex, + abi: EventActionArtifact.abi, + bytecode: EventActionArtifact.bytecode as Hex, account, }), ); From d45d4d83f65ac95b31e00f65a4ac203c90df128e Mon Sep 17 00:00:00 2001 From: Sam McCord Date: Wed, 28 Aug 2024 11:09:26 -0600 Subject: [PATCH 16/17] chore: minor tweaks --- README.md | 1 + packages/sdk/test/helpers.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0293948e..b2d86b55 100644 --- a/README.md +++ b/README.md @@ -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= diff --git a/packages/sdk/test/helpers.ts b/packages/sdk/test/helpers.ts index 125689f3..60723bb4 100644 --- a/packages/sdk/test/helpers.ts +++ b/packages/sdk/test/helpers.ts @@ -128,7 +128,7 @@ export async function deployFixtures( config, deployContract(config, { abi: EventActionArtifact.abi, - bytecode: ContractActionArtifact.bytecode as Hex, + bytecode: EventActionArtifact.bytecode as Hex, account, }), ); From eaf5d379ccdf49c7f99cc436a8fcb2d20bb59a07 Mon Sep 17 00:00:00 2001 From: Sam McCord Date: Wed, 28 Aug 2024 11:13:01 -0600 Subject: [PATCH 17/17] chore: add event action to sample .env --- packages/sdk/.env.sample | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/sdk/.env.sample b/packages/sdk/.env.sample index 93f6306d..8e4f9f73 100644 --- a/packages/sdk/.env.sample +++ b/packages/sdk/.env.sample @@ -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=