Skip to content

Commit

Permalink
Initialize hooks with data at installation time
Browse files Browse the repository at this point in the history
  • Loading branch information
nkrishang committed Mar 5, 2024
1 parent 68138be commit 9fde4e1
Show file tree
Hide file tree
Showing 19 changed files with 299 additions and 330 deletions.
37 changes: 27 additions & 10 deletions src/hook/HookInstaller.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ abstract contract HookInstaller is IHookInstaller {
/// @notice Emitted on attempt to write to hooks without permission.
error HookInstallerUnauthorizedWrite();

/// @notice Emitted on failure to initialize a hook on installation.
error HookInstallerInitializationFailed();

/*//////////////////////////////////////////////////////////////
VIEW FUNCTIONS
//////////////////////////////////////////////////////////////*/
Expand Down Expand Up @@ -75,11 +78,29 @@ abstract contract HookInstaller is IHookInstaller {
* @dev Maps all hook functions implemented by the hook to the hook's address.
* @param _hook The hook to install.
*/
function installHook(IHook _hook) external {
function installHook(IHook _hook, bytes calldata _initializeData) external payable {
if (address(_hook) == address(0)) {
revert HookInstallerInvalidHook();
}
if (!_canUpdateHooks(msg.sender)) {
revert HookNotAuthorized();
}
_installHook(_hook);

if (_initializeData.length > 0) {
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returnData) = address(_hook).call{value: msg.value}(_initializeData);
if (!success) {
if (returnData.length > 0) {
// solhint-disable-next-line no-inline-assembly
assembly {
revert(add(returnData, 32), mload(returnData))
}
} else {
revert HookInstallerInitializationFailed();
}
}
}
}

/**
Expand All @@ -88,6 +109,9 @@ abstract contract HookInstaller is IHookInstaller {
* @param _hook The hook to uninstall.
*/
function uninstallHook(IHook _hook) external {
if (address(_hook) == address(0)) {
revert HookInstallerInvalidHook();
}
if (!_canUpdateHooks(msg.sender)) {
revert HookNotAuthorized();
}
Expand All @@ -97,27 +121,20 @@ abstract contract HookInstaller is IHookInstaller {
/**
* @notice A generic entrypoint to write state of any of the installed hooks.
*/
function hookFunctionWrite(uint256 _hookFlag, uint256 _value, bytes calldata _data)
external
payable
returns (bytes memory)
{
function hookFunctionWrite(uint256 _hookFlag, bytes calldata _data) external payable returns (bytes memory) {
if (!_canWriteToHooks(msg.sender)) {
revert HookInstallerUnauthorizedWrite();
}
if (_hookFlag > 2 ** _maxHookFlag()) {
revert HookInstallerInvalidHook();
}
if (msg.value != _value) {
revert HookInstallerInvalidValue();
}

address target = getHookImplementation(_hookFlag);
if (target == address(0)) {
revert HookInstallerHookNotInstalled();
}

(bool success, bytes memory returndata) = target.call{value: _value}(_data);
(bool success, bytes memory returndata) = target.call{value: msg.value}(_data);
if (!success) {
_revert(returndata);
}
Expand Down
3 changes: 2 additions & 1 deletion src/interface/hook/IHookInstaller.sol
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@ interface IHookInstaller {
* @notice Installs a hook in the contract.
* @dev Maps all hook functions implemented by the hook to the hook's address.
* @param hook The hook to install.
* @param initializeData The initialization calldata.
*/
function installHook(IHook hook) external;
function installHook(IHook hook, bytes calldata initializeData) external payable;

/**
* @notice Uninstalls a hook in the contract.
Expand Down
71 changes: 32 additions & 39 deletions test/HookUpgrades.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ pragma solidity ^0.8.0;
import {Test} from "forge-std/Test.sol";
import {Merkle} from "@murky/Merkle.sol";

import {Multicallable} from "@solady/utils/Multicallable.sol";

import {CloneFactory} from "src/infra/CloneFactory.sol";
import {EIP1967Proxy} from "src/infra/EIP1967Proxy.sol";

Expand Down Expand Up @@ -121,15 +123,6 @@ contract HookUpgradesTest is Test {
vm.label(address(MintHookERC721Impl), "AllowlistMintHookERC721");
vm.label(address(MintHookERC1155Impl), "AllowlistMintHookERC1155");

// Developer installs hooks.
vm.startPrank(developer);

erc20Core.installHook(IHook(MintHookERC20Proxy));
erc721Core.installHook(IHook(MintHookERC721Proxy));
erc1155Core.installHook(IHook(MintHookERC1155Proxy));

vm.stopPrank();

// Developer sets claim conditions; non-zero price
address[] memory addresses = new address[](3);
addresses[0] = 0xDDdDddDdDdddDDddDDddDDDDdDdDDdDDdDDDDDDd;
Expand Down Expand Up @@ -158,44 +151,44 @@ contract HookUpgradesTest is Test {
allowlistMerkleRoot: root
});

vm.startPrank(developer);
erc20Core.hookFunctionWrite(
erc20Core.BEFORE_MINT_FLAG(),
0,
abi.encodeWithSelector(AllowlistMintHookERC20.setClaimCondition.selector, conditionERC20)
);
erc721Core.hookFunctionWrite(
erc721Core.BEFORE_MINT_FLAG(),
0,
abi.encodeWithSelector(AllowlistMintHookERC721.setClaimCondition.selector, conditionERC721)
);
erc1155Core.hookFunctionWrite(
erc1155Core.BEFORE_MINT_FLAG(),
0,
abi.encodeWithSelector(AllowlistMintHookERC1155.setClaimCondition.selector, 0, conditionERC1155)
);
vm.stopPrank();

// Developer sets fee config; sets self as primary sale recipient
AllowlistMintHookERC20.FeeConfig memory feeConfig;
feeConfig.primarySaleRecipient = developer;

bytes[] memory multicallInitializeDataERC20 = new bytes[](2);
multicallInitializeDataERC20[0] =
abi.encodeWithSelector(AllowlistMintHookERC20.setClaimCondition.selector, conditionERC20);
multicallInitializeDataERC20[1] =
abi.encodeWithSelector(AllowlistMintHookERC20.setDefaultFeeConfig.selector, feeConfig);

bytes[] memory multicallInitializeDataERC721 = new bytes[](2);
multicallInitializeDataERC721[0] =
abi.encodeWithSelector(AllowlistMintHookERC721.setClaimCondition.selector, conditionERC721);
multicallInitializeDataERC721[1] =
abi.encodeWithSelector(AllowlistMintHookERC721.setDefaultFeeConfig.selector, feeConfig);

bytes[] memory multicallInitializeDataERC1155 = new bytes[](2);
multicallInitializeDataERC1155[0] =
abi.encodeWithSelector(AllowlistMintHookERC1155.setClaimCondition.selector, 0, conditionERC1155);
multicallInitializeDataERC1155[1] =
abi.encodeWithSelector(AllowlistMintHookERC1155.setDefaultFeeConfig.selector, feeConfig);

// Developer installs hooks.
vm.startPrank(developer);
erc20Core.hookFunctionWrite(
erc20Core.BEFORE_MINT_FLAG(),
0,
abi.encodeWithSelector(AllowlistMintHookERC20.setDefaultFeeConfig.selector, feeConfig)

erc20Core.installHook(
IHook(MintHookERC20Proxy),
abi.encodeWithSelector(Multicallable.multicall.selector, multicallInitializeDataERC20)
);
erc721Core.hookFunctionWrite(
erc721Core.BEFORE_MINT_FLAG(),
0,
abi.encodeWithSelector(AllowlistMintHookERC721.setDefaultFeeConfig.selector, feeConfig)
erc721Core.installHook(
IHook(MintHookERC721Proxy),
abi.encodeWithSelector(Multicallable.multicall.selector, multicallInitializeDataERC721)
);
erc1155Core.hookFunctionWrite(
erc1155Core.BEFORE_MINT_FLAG(),
0,
abi.encodeWithSelector(AllowlistMintHookERC1155.setDefaultFeeConfig.selector, feeConfig)
erc1155Core.installHook(
IHook(MintHookERC1155Proxy),
abi.encodeWithSelector(Multicallable.multicall.selector, multicallInitializeDataERC1155)
);

vm.stopPrank();

// Set minting params
Expand Down
45 changes: 21 additions & 24 deletions test/benchmark/ERC1155CoreBenchmark.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ pragma solidity ^0.8.0;
import {Test} from "forge-std/Test.sol";
import {Merkle} from "@murky/Merkle.sol";

import {Multicallable} from "@solady/utils/Multicallable.sol";

import {CloneFactory} from "src/infra/CloneFactory.sol";
import {MinimalUpgradeableRouter} from "src/infra/MinimalUpgradeableRouter.sol";
import {MockOneHookImpl, MockFourHookImpl} from "test/mocks/MockHookImpl.sol";
Expand Down Expand Up @@ -115,17 +117,7 @@ contract ERC1155CoreBenchmarkTest is Test {

vm.startPrank(platformUser);

// Developer installs `AllowlistMintHookERC1155` hook
erc1155.installHook(IHook(hookProxyAddress));
erc1155.installHook(IHook(lazyMintHookProxyAddress));

// Developer sets up token metadata and claim conditions: gas incurred by developer.
erc1155.hookFunctionWrite(
erc1155.TOKEN_URI_FLAG(),
0,
abi.encodeWithSelector(LazyMintHook.lazyMint.selector, 3, "https://example.com/", "")
);

address[] memory addresses = new address[](3);
addresses[0] = 0xDDdDddDdDdddDDddDDddDDDDdDdDDdDDdDDDDDDd;
addresses[1] = 0x92Bb439374a091c7507bE100183d8D1Ed2c9dAD3;
Expand All @@ -143,22 +135,27 @@ contract ERC1155CoreBenchmarkTest is Test {
allowlistMerkleRoot: root
});

erc1155.hookFunctionWrite(
erc1155.BEFORE_MINT_FLAG(),
0,
abi.encodeWithSelector(AllowlistMintHookERC1155.setClaimCondition.selector, 0, condition)
);

AllowlistMintHookERC1155.FeeConfig memory feeConfig;
feeConfig.primarySaleRecipient = platformUser;
feeConfig.platformFeeRecipient = address(0x789);
feeConfig.platformFeeBps = 100; // 1%

erc1155.hookFunctionWrite(
erc1155.BEFORE_MINT_FLAG(),
0,
abi.encodeWithSelector(AllowlistMintHookERC1155.setDefaultFeeConfig.selector, feeConfig)
bytes[] memory multicallDataMintHook = new bytes[](2);

multicallDataMintHook[0] =
abi.encodeWithSelector(AllowlistMintHookERC1155.setDefaultFeeConfig.selector, 0, feeConfig);

multicallDataMintHook[1] =
abi.encodeWithSelector(AllowlistMintHookERC1155.setClaimCondition.selector, 0, condition);

bytes memory initializeDataLazyMint =
abi.encodeWithSelector(LazyMintHook.lazyMint.selector, 3, "https://example.com/", "");

// Developer installs `AllowlistMintHookERC1155` hook
erc1155.installHook(
IHook(hookProxyAddress), abi.encodeWithSelector(Multicallable.multicall.selector, multicallDataMintHook)
);
erc1155.installHook(IHook(lazyMintHookProxyAddress), initializeDataLazyMint);

vm.stopPrank();

Expand Down Expand Up @@ -326,7 +323,7 @@ contract ERC1155CoreBenchmarkTest is Test {

vm.resumeGasMetering();

hookConsumer.installHook(mockHook);
hookConsumer.installHook(mockHook, bytes(""));
}

function test_installfiveHooks() public {
Expand All @@ -342,7 +339,7 @@ contract ERC1155CoreBenchmarkTest is Test {

vm.resumeGasMetering();

hookConsumer.installHook(mockHook);
hookConsumer.installHook(mockHook, bytes(""));
}

function test_uninstallOneHooks() public {
Expand All @@ -352,7 +349,7 @@ contract ERC1155CoreBenchmarkTest is Test {
ERC1155Core hookConsumer = erc1155;

vm.prank(platformUser);
hookConsumer.installHook(mockHook);
hookConsumer.installHook(mockHook, bytes(""));

vm.prank(platformUser);

Expand All @@ -371,7 +368,7 @@ contract ERC1155CoreBenchmarkTest is Test {
hookConsumer.uninstallHook(IHook(hookProxyAddress));

vm.prank(platformUser);
hookConsumer.installHook(mockHook);
hookConsumer.installHook(mockHook, bytes(""));

vm.prank(platformUser);

Expand Down
33 changes: 17 additions & 16 deletions test/benchmark/ERC20CoreBenchmark.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ pragma solidity ^0.8.0;
import {Test} from "forge-std/Test.sol";
import {Merkle} from "@murky/Merkle.sol";

import {Multicallable} from "@solady/utils/Multicallable.sol";

import {CloneFactory} from "src/infra/CloneFactory.sol";
import {MinimalUpgradeableRouter} from "src/infra/MinimalUpgradeableRouter.sol";
import {MockOneHookImpl20, MockFourHookImpl20} from "test/mocks/MockHookImpl.sol";
Expand Down Expand Up @@ -106,9 +108,6 @@ contract ERC20CoreBenchmarkTest is Test {
vm.label(claimer, "Claimer");

// Developer installs `AllowlistMintHookERC20` hook
vm.startPrank(platformUser);
erc20.installHook(IHook(hookProxyAddress));

address[] memory addresses = new address[](3);
addresses[0] = 0xDDdDddDdDdddDDddDDddDDDDdDdDDdDDdDDDDDDd;
addresses[1] = 0x92Bb439374a091c7507bE100183d8D1Ed2c9dAD3;
Expand All @@ -125,21 +124,23 @@ contract ERC20CoreBenchmarkTest is Test {
availableSupply: availableSupply,
allowlistMerkleRoot: root
});
erc20.hookFunctionWrite(
erc20.BEFORE_MINT_FLAG(),
0,
abi.encodeWithSelector(AllowlistMintHookERC20.setClaimCondition.selector, condition)
);

AllowlistMintHookERC20.FeeConfig memory feeConfig;
feeConfig.primarySaleRecipient = platformUser;
feeConfig.platformFeeRecipient = address(0x789);
feeConfig.platformFeeBps = 100; // 1%

erc20.hookFunctionWrite(
erc20.BEFORE_MINT_FLAG(),
0,
abi.encodeWithSelector(AllowlistMintHookERC20.setDefaultFeeConfig.selector, feeConfig)
bytes[] memory multicallDataMintHook = new bytes[](2);

multicallDataMintHook[0] =
abi.encodeWithSelector(AllowlistMintHookERC20.setDefaultFeeConfig.selector, feeConfig);

multicallDataMintHook[1] = abi.encodeWithSelector(AllowlistMintHookERC20.setClaimCondition.selector, condition);

// Developer installs `AllowlistMintHookERC20` hook
vm.prank(platformUser);
erc20.installHook(
IHook(hookProxyAddress), abi.encodeWithSelector(Multicallable.multicall.selector, multicallDataMintHook)
);

vm.stopPrank();
Expand Down Expand Up @@ -306,7 +307,7 @@ contract ERC20CoreBenchmarkTest is Test {

vm.resumeGasMetering();

hookConsumer.installHook(mockHook);
hookConsumer.installHook(mockHook, bytes(""));
}

function test_installfiveHooks() public {
Expand All @@ -322,7 +323,7 @@ contract ERC20CoreBenchmarkTest is Test {

vm.resumeGasMetering();

hookConsumer.installHook(mockHook);
hookConsumer.installHook(mockHook, bytes(""));
}

function test_uninstallOneHook() public {
Expand All @@ -332,7 +333,7 @@ contract ERC20CoreBenchmarkTest is Test {
ERC20Core hookConsumer = erc20;

vm.prank(platformUser);
hookConsumer.installHook(mockHook);
hookConsumer.installHook(mockHook, bytes(""));

vm.prank(platformUser);

Expand All @@ -351,7 +352,7 @@ contract ERC20CoreBenchmarkTest is Test {
hookConsumer.uninstallHook(IHook(hookProxyAddress));

vm.prank(platformUser);
hookConsumer.installHook(mockHook);
hookConsumer.installHook(mockHook, bytes(""));

vm.prank(platformUser);

Expand Down
Loading

0 comments on commit 9fde4e1

Please sign in to comment.