Skip to content

Commit

Permalink
Merge pull request #57 from wildcat-finance/use-c2-for-instances
Browse files Browse the repository at this point in the history
Use create2 for deploying hooks instances (resolves #44)
  • Loading branch information
d1ll0n authored Oct 11, 2024
2 parents 467baa0 + 0528234 commit 1a52863
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 12 deletions.
38 changes: 36 additions & 2 deletions src/HooksFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,26 @@ contract HooksFactory is SphereXProtectedRegisteredBase, ReentrancyGuard, IHooks

address public immutable override sanctionsSentinel;

/**
* @dev Return the contract name "WildcatHooksFactory"
*/
function name() external pure returns (string memory) {
// Use yul to avoid duplicate memory allocation and reduce code size
// Uses words at 0x20, 0x40, 0x60
// 0x20 is overwritten with the ABI offset (32)
// 0x40 contains the free pointer which will be 1 byte when this function executes.
// The length of the string (19) is written to the last byte of the free pointer word.
// 0x60 is the zero slot, so it will not have any dirty bits when this function executes.
// It is overwritten with the name bytes in the same operation as the length.
assembly {
mstore(0x53, 0x1357696c64636174486f6f6b73466163746f7279)
mstore(0x20, 0x20)
return(0x20, 0x60)
}
}

address[] internal _hooksTemplates;
mapping(address borrower => address[] hooksInstances) internal _hooksInstancesByBorrower;
mapping(address hooksTemplate => address[] markets) internal _marketsByHooksTemplate;
mapping(address hooksTemplate => HooksTemplate details) internal _templateDetails;
mapping(address hooksInstance => address hooksTemplate)
Expand Down Expand Up @@ -282,6 +301,18 @@ contract HooksFactory is SphereXProtectedRegisteredBase, ReentrancyGuard, IHooks
hooksInstance = _deployHooksInstance(hooksTemplate, constructorArgs);
}

function getHooksInstancesForBorrower(
address borrower
) external view override returns (address[] memory) {
return _hooksInstancesByBorrower[borrower];
}

function getHooksInstancesCountForBorrower(
address borrower
) external view override returns (uint256) {
return _hooksInstancesByBorrower[borrower].length;
}

function isHooksInstance(address hooksInstance) external view override returns (bool) {
return getHooksTemplateForInstance[hooksInstance] != address(0);
}
Expand All @@ -298,7 +329,10 @@ contract HooksFactory is SphereXProtectedRegisteredBase, ReentrancyGuard, IHooks
revert HooksTemplateNotAvailable();
}

uint256 numHooksForBorrower = _hooksInstancesByBorrower[msg.sender].length;
bytes32 salt;
assembly {
salt := or(shl(96, caller()), numHooksForBorrower)
let initCodePointer := mload(0x40)
let initCodeSize := sub(extcodesize(hooksTemplate), 1)
// Copy code from target address to memory starting at byte 1
Expand All @@ -316,12 +350,13 @@ contract HooksFactory is SphereXProtectedRegisteredBase, ReentrancyGuard, IHooks
// Get the full size of the initcode with the constructor args
let initCodeSizeWithArgs := add(add(initCodeSize, 0x60), constructorArgsSize)
// Deploy the contract with the initcode
hooksInstance := create(0, initCodePointer, initCodeSizeWithArgs)
hooksInstance := create2(0, initCodePointer, initCodeSizeWithArgs, salt)
if iszero(hooksInstance) {
mstore(0x00, 0x30116425) // DeploymentFailed()
revert(0x1c, 0x04)
}
}
_hooksInstancesByBorrower[msg.sender].push(hooksInstance);

emit HooksInstanceDeployed(hooksInstance, hooksTemplate);
getHooksTemplateForInstance[hooksInstance] = hooksTemplate;
Expand Down Expand Up @@ -586,7 +621,6 @@ contract HooksFactory is SphereXProtectedRegisteredBase, ReentrancyGuard, IHooks
}
}


/**
* @dev Push any changes to the fee configuration of `hooksTemplate` to all markets
* using any instances of that template at `_marketsByHooksTemplate[hooksTemplate]`.
Expand Down
5 changes: 5 additions & 0 deletions src/IHooksFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ interface IHooksFactoryEventsAndErrors {
error InvalidFeeConfiguration();
error SaltDoesNotContainSender();
error MarketAlreadyExists();
error HooksInstanceAlreadyExists();
error NameOrSymbolTooLong();
error AssetBlacklisted();
error SetProtocolFeeBipsFailed();
Expand Down Expand Up @@ -189,6 +190,10 @@ interface IHooksFactory is IHooksFactoryEventsAndErrors {
bytes calldata constructorArgs
) external returns (address hooksDeployment);

function getHooksInstancesForBorrower(address borrower) external view returns (address[] memory);

function getHooksInstancesCountForBorrower(address borrower) external view returns (uint256);

/// @dev Check if a hooks instance was deployed by the factory.
function isHooksInstance(address hooks) external view returns (bool);

Expand Down
78 changes: 69 additions & 9 deletions test/HooksFactory.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,11 @@ contract HooksFactoryTest is Test, Assertions {
address hooksTemplate,
bytes memory constructorArgs
) internal returns (MockHooks hooksInstance) {
address expectedHooksInstance = _setUpDeployHooksInstance(hooksTemplate);
address expectedHooksInstance = _setUpDeployHooksInstance(
hooksTemplate,
address(this),
constructorArgs
);
_expectEventsDeployHooksInstance(hooksTemplate, expectedHooksInstance);
// Check event
hooksInstance = MockHooks(hooksFactory.deployHooksInstance(hooksTemplate, constructorArgs));
Expand All @@ -475,13 +479,57 @@ contract HooksFactoryTest is Test, Assertions {
);
}

function getNextInstanceAddress(
address hooksTemplate,
address borrower,
bytes memory constructorArgs
) internal view returns (address) {
uint256 initCodeHash;

assembly {
let initCodePointer := mload(0x40)
let initCodeSize := sub(extcodesize(hooksTemplate), 1)
// Copy code from target address to memory starting at byte 1
extcodecopy(hooksTemplate, initCodePointer, 1, initCodeSize)
let endInitCodePointer := add(initCodePointer, initCodeSize)
// Write the address of the caller as the first parameter
mstore(endInitCodePointer, borrower)
// Write the offset to the encoded constructor args
mstore(add(endInitCodePointer, 0x20), 0x40)
// Write the length of the encoded constructor args
let constructorArgsSize := mload(constructorArgs)
mstore(add(endInitCodePointer, 0x40), constructorArgsSize)
// Copy constructor args to initcode after the bytes length
mcopy(add(endInitCodePointer, 0x60), add(constructorArgs, 0x20), constructorArgsSize)
// Get the full size of the initcode with the constructor args
let initCodeSizeWithArgs := add(add(initCodeSize, 0x60), constructorArgsSize)
initCodeHash := keccak256(initCodePointer, initCodeSizeWithArgs)
}

uint256 numPreviousInstances = hooksFactory.getHooksInstancesCountForBorrower(borrower);
bytes32 salt;

assembly {
salt := or(shl(96, borrower), numPreviousInstances)
}

return
LibStoredInitCode.calculateCreate2Address(
LibStoredInitCode.getCreate2Prefix(address(hooksFactory)),
salt,
initCodeHash
);
}
function _setUpDeployHooksInstance(
address hooksTemplate
address hooksTemplate,
address borrower,
bytes memory constructorArgs
) internal returns (address expectedAddress) {
expectedAddress = computeCreateAddress(
address(hooksFactory),
vm.getNonce(address(hooksFactory))
);
// expectedAddress = computeCreateAddress(
// address(hooksFactory),
// vm.getNonce(address(hooksFactory))
// );
expectedAddress = getNextInstanceAddress(hooksTemplate, borrower, constructorArgs);
assertFalse(hooksFactory.isHooksInstance(expectedAddress), 'isHooksInstance before deploy');
}

Expand Down Expand Up @@ -904,7 +952,11 @@ contract HooksFactoryTest is Test, Assertions {
_validateAddHooksTemplate(hooksTemplate, 'name', feesInput);

CreateMarketAndHooksContext memory context;
context.expectedHooksInstance = _setUpDeployHooksInstance(hooksTemplate);
context.expectedHooksInstance = _setUpDeployHooksInstance(
hooksTemplate,
address(this),
abi.encode(paramsInput.templateHooksConfig.toHooksDeploymentConfig())
);
_setUpDeployMarket(feesInput);

paramsInput.marketHooksConfig.hooksAddress = context.expectedHooksInstance;
Expand Down Expand Up @@ -966,7 +1018,11 @@ contract HooksFactoryTest is Test, Assertions {
_validateAddHooksTemplate(hooksTemplate, 'name', feesInput);

CreateMarketAndHooksContext memory context;
context.expectedHooksInstance = _setUpDeployHooksInstance(hooksTemplate);
context.expectedHooksInstance = _setUpDeployHooksInstance(
hooksTemplate,
address(this),
abi.encode(paramsInput.templateHooksConfig.toHooksDeploymentConfig())
);
_setUpDeployMarket(feesInput);

paramsInput.marketHooksConfig.hooksAddress = context.expectedHooksInstance;
Expand Down Expand Up @@ -1040,7 +1096,11 @@ contract HooksFactoryTest is Test, Assertions {
_validateAddHooksTemplate(hooksTemplate, 'name', feesInput);

CreateMarketAndHooksContext memory context;
context.expectedHooksInstance = _setUpDeployHooksInstance(hooksTemplate);
context.expectedHooksInstance = _setUpDeployHooksInstance(
hooksTemplate,
address(this),
abi.encode(paramsInput.templateHooksConfig.toHooksDeploymentConfig())
);
_setUpDeployMarket(feesInput);

paramsInput.marketHooksConfig.hooksAddress = context.expectedHooksInstance;
Expand Down
60 changes: 59 additions & 1 deletion test/shared/Test.sol
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,48 @@ contract Test is ForgeTest, Prankster, Assertions {
event MarketAdded(address indexed controller, address market);
event NewSenderOnEngine(address sender);

function getNextInstanceAddress(
address hooksTemplate,
address borrower,
bytes memory constructorArgs
) internal view returns (address) {
uint256 initCodeHash;

assembly {
let initCodePointer := mload(0x40)
let initCodeSize := sub(extcodesize(hooksTemplate), 1)
// Copy code from target address to memory starting at byte 1
extcodecopy(hooksTemplate, initCodePointer, 1, initCodeSize)
let endInitCodePointer := add(initCodePointer, initCodeSize)
// Write the address of the caller as the first parameter
mstore(endInitCodePointer, borrower)
// Write the offset to the encoded constructor args
mstore(add(endInitCodePointer, 0x20), 0x40)
// Write the length of the encoded constructor args
let constructorArgsSize := mload(constructorArgs)
mstore(add(endInitCodePointer, 0x40), constructorArgsSize)
// Copy constructor args to initcode after the bytes length
mcopy(add(endInitCodePointer, 0x60), add(constructorArgs, 0x20), constructorArgsSize)
// Get the full size of the initcode with the constructor args
let initCodeSizeWithArgs := add(add(initCodeSize, 0x60), constructorArgsSize)
initCodeHash := keccak256(initCodePointer, initCodeSizeWithArgs)
}

uint256 numPreviousInstances = hooksFactory.getHooksInstancesCountForBorrower(borrower);
bytes32 salt;

assembly {
salt := or(shl(96, borrower), numPreviousInstances)
}

return
LibStoredInitCode.calculateCreate2Address(
LibStoredInitCode.getCreate2Prefix(address(hooksFactory)),
salt,
initCodeHash
);
}

function deployHooksInstance(
MarketInputParameters memory parameters,
bool authorizeAll
Expand All @@ -207,8 +249,13 @@ contract Test is ForgeTest, Prankster, Assertions {
startPrank(parameters.borrower);
bool emptyConfig = HooksConfig.unwrap(parameters.hooksConfig) == 0;
if (parameters.hooksConfig.hooksAddress() == address(0)) {
uint previousCount = hooksFactory.getHooksInstancesCountForBorrower(parameters.borrower);
hooksInstance = AccessControlHooks(
computeCreateAddress(address(hooksFactory), vm.getNonce(address(hooksFactory)))
getNextInstanceAddress(
parameters.hooksTemplate,
parameters.borrower,
parameters.deployHooksConstructorArgs
)
);
vm.expectEmit(address(hooksFactory));
emit IHooksFactoryEventsAndErrors.HooksInstanceDeployed(
Expand All @@ -224,6 +271,17 @@ contract Test is ForgeTest, Prankster, Assertions {
'hooksInstance address'
);
parameters.hooksConfig = parameters.hooksConfig.setHooksAddress(address(hooksInstance));
assertEq(
hooksFactory.getHooksInstancesCountForBorrower(parameters.borrower),
previousCount + 1,
'did not update instance count for borrower'
);
address[] memory instances = hooksFactory.getHooksInstancesForBorrower(parameters.borrower);
assertEq(
instances[instances.length - 1],
address(hooksInstance),
'did not add instance to borrower list'
);
} else {
hooksInstance = AccessControlHooks(parameters.hooksConfig.hooksAddress());
}
Expand Down

0 comments on commit 1a52863

Please sign in to comment.