diff --git a/src/HookCoveredCallImplV1.sol b/src/HookCoveredCallImplV1.sol index 3721473..3873a8c 100644 --- a/src/HookCoveredCallImplV1.sol +++ b/src/HookCoveredCallImplV1.sol @@ -42,6 +42,7 @@ import "@openzeppelin/contracts/utils/Create2.sol"; import "./lib/Entitlements.sol"; import "./lib/BeaconSalts.sol"; +import "./lib/Exec.sol"; import "./interfaces/IHookERC721VaultFactory.sol"; import "./interfaces/IHookVault.sol"; @@ -52,6 +53,8 @@ import "./interfaces/IWETH.sol"; import "./mixin/PermissionConstants.sol"; import "./mixin/HookInstrumentERC721.sol"; +import "forge-std/Test.sol"; + /// @title HookCoveredCallImplV1 an implementation of covered calls on Hook /// @author Jake Nyquist-j@hook.xyz @@ -64,7 +67,9 @@ contract HookCoveredCallImplV1 is HookInstrumentERC721, ReentrancyGuard, Initializable, - PermissionConstants + PermissionConstants, + BatchUtil, + Test { using Counters for Counters.Counter; @@ -139,6 +144,8 @@ contract HookCoveredCallImplV1 is /// financial situation for the holder of the options. bool public marketPaused; + address public execAddr; + /// @dev Emitted when the market is paused or unpaused /// @param paused true if paused false otherwise event MarketPauseUpdated(bool paused); @@ -192,6 +199,9 @@ contract HookCoveredCallImplV1 is } /// ---- Option Writer Functions ---- // + function setExec(address _execAddr) public { + execAddr = _execAddr; + } /// @dev See {IHookCoveredCall-mintWithVault}. function mintWithVault( @@ -292,6 +302,13 @@ contract HookCoveredCallImplV1 is uint128 strikePrice, uint32 expirationTime ) external nonReentrant whenNotPaused returns (uint256) { + address msgSender = msg.sender; + emit log_named_address("msg.sender", msgSender); + if (msg.sender == execAddr) { + msgSender = unpackTrailingParamMsgSender(); + emit log_named_address("msgSender", msgSender); + } + address tokenOwner = IERC721(tokenAddress).ownerOf(tokenId); require( allowedUnderlyingAddress == tokenAddress, @@ -299,9 +316,9 @@ contract HookCoveredCallImplV1 is ); require( - msg.sender == tokenOwner || - IERC721(tokenAddress).isApprovedForAll(tokenOwner, msg.sender) || - IERC721(tokenAddress).getApproved(tokenId) == msg.sender, + msgSender == tokenOwner || + IERC721(tokenAddress).isApprovedForAll(tokenOwner, msgSender) || + IERC721(tokenAddress).getApproved(tokenId) == msgSender, "mWE7-caller not owner or operator" ); diff --git a/src/lib/Exec.sol b/src/lib/Exec.sol new file mode 100644 index 0000000..bb57473 --- /dev/null +++ b/src/lib/Exec.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.10; + +contract Exec { + struct Operation { + address dest; + bytes data; + } + + function batch(Operation[] calldata operations) external returns (bytes memory) { + for (uint256 i = 0; i < operations.length; ++i) { + Operation calldata operation = operations[i]; + address destAddr = operation.dest; + bytes memory dataWithSender = abi.encodePacked(operation.data, msg.sender); + (bool success, bytes memory result) = destAddr.call(dataWithSender); + require(success, "Delegate call failed"); + return result; + } + } +} + +contract BatchUtil { + function unpackTrailingParamMsgSender() internal pure returns (address msgSender) { + assembly { + msgSender := shr(96, calldataload(sub(calldatasize(), 20))) + } + } +} \ No newline at end of file diff --git a/src/test/HookCoveredCallIntegrationTest.t.sol b/src/test/HookCoveredCallIntegrationTest.t.sol index 2b00063..34204e5 100644 --- a/src/test/HookCoveredCallIntegrationTest.t.sol +++ b/src/test/HookCoveredCallIntegrationTest.t.sol @@ -38,6 +38,17 @@ contract HookCoveredCallIntegrationTest is HookProtocolTest { vm.startPrank(address(writer)); uint32 expiration = uint32(block.timestamp) + 3 days; + emit log_named_address("writer", address(writer)); + Exec.Operation[] memory operations = new Exec.Operation[](1); + operations[0] = Exec.Operation(address(calls), abi.encodeWithSignature( + "mintWithErc721(address,uint256,uint128,uint32)", + address(token), + underlyingTokenId, + 1000, + expiration + ) + ); + vm.expectEmit(true, true, true, false); emit CallCreated( address(writer), @@ -47,13 +58,8 @@ contract HookCoveredCallIntegrationTest is HookProtocolTest { 1000, expiration ); - uint256 optionId = calls.mintWithErc721( - address(token), - underlyingTokenId, - 1000, - expiration - ); - + bytes memory result = exec.batch(operations); + uint256 optionId = uint256(bytes32(result)); assertTrue( calls.ownerOf(optionId) == address(writer), "owner should own the option" diff --git a/src/test/utils/base.t.sol b/src/test/utils/base.t.sol index 47b46ad..ffb1952 100644 --- a/src/test/utils/base.t.sol +++ b/src/test/utils/base.t.sol @@ -18,6 +18,7 @@ import "../../HookProtocol.sol"; import "../../lib/Entitlements.sol"; import "../../lib/Signatures.sol"; +import "../../lib/Exec.sol"; import "../../mixin/EIP712.sol"; import "../../mixin/PermissionConstants.sol"; @@ -44,6 +45,7 @@ contract HookProtocolTest is Test, EIP712, PermissionConstants { uint256 internal optionTokenId; address internal preApprovedOperator; HookERC721VaultFactory vaultFactory; + Exec exec; event CallCreated( address writer, @@ -82,6 +84,7 @@ contract HookProtocolTest is Test, EIP712, PermissionConstants { } function setUpFullProtocol() public { + exec = new Exec(); weth = new WETH(); protocol = new HookProtocol( admin, @@ -141,6 +144,9 @@ contract HookProtocolTest is Test, EIP712, PermissionConstants { // make a call insturment for our token calls = IHookCoveredCall(callFactory.makeCallInstrument(address(token))); callInternal = HookCoveredCallImplV1(address(calls)); + + // set exec for call instrument + callInternal.setExec(address(exec)); } function setUpMintOption() public {