Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions src/HookBeaconProxy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (proxy/beacon/BeaconProxy.sol)

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/proxy/beacon/IBeacon.sol";
import "@openzeppelin/contracts/proxy/Proxy.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Upgrade.sol";

/**
* @dev This contract implements a proxy that gets the implementation address for each call from a {UpgradeableBeacon}.
*
* The beacon address is stored in storage slot `uint256(keccak256('eip1967.proxy.beacon')) - 1`, so that it doesn't
* conflict with the storage layout of the implementation behind the proxy.
*
* This is an extension of the OpenZeppelin beacaon proxy, however differs in that it is initializeable, which means
* it is usable with Create2.
*/
contract HookBeaconProxy is Proxy, ERC1967Upgrade {
/// @dev The constructor is empty in this case because the proxy is initializeable
constructor() {}

/**
* @dev Initializes the proxy with `beacon`.
*
* If `data` is nonempty, it's used as data in a delegate call to the implementation returned by the beacon. This
* will typically be an encoded function call, and allows initializating the storage of the proxy like a Solidity
* constructor.
*
* Requirements:
*
* - `beacon` must be a contract with the interface {IBeacon}.
*/

function initializeBeacon(address beacon, bytes memory data) public {
assert(
_BEACON_SLOT == bytes32(uint256(keccak256("eip1967.proxy.beacon")) - 1)
);
_upgradeBeaconToAndCall(beacon, data, false);
}

/**
* @dev Returns the current beacon address.
*/
function _beacon() internal view virtual returns (address) {
return _getBeacon();
}

/**
* @dev Returns the current implementation address of the associated beacon.
*/
function _implementation() internal view virtual override returns (address) {
return IBeacon(_getBeacon()).implementation();
}
}
29 changes: 0 additions & 29 deletions src/HookCoveredCall.sol

This file was deleted.

34 changes: 29 additions & 5 deletions src/HookCoveredCallFactory.sol
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
//SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;

import "./HookCoveredCall.sol";
import "./interfaces/IHookCoveredCallFactory.sol";
import "./interfaces/IHookProtocol.sol";

import "./interfaces/IInitializeableBeacon.sol";
import "./HookBeaconProxy.sol";

import "./mixin/PermissionConstants.sol";

import "@openzeppelin/contracts/utils/Create2.sol";

/// @dev See {IHookCoveredCallFactory}.
/// @dev Operating the factory requires specific permissions within the protocol.
contract HookCoveredCallFactory is
Expand Down Expand Up @@ -52,21 +56,41 @@ contract HookCoveredCallFactory is
"makeCallInstrument -- Only admins can make instruments"
);

getCallInstrument[assetAddress] = address(
new HookCoveredCall{salt: keccak256(abi.encode(assetAddress))}(
_beacon,
IInitializeableBeacon bp = IInitializeableBeacon(
Create2.deploy(
0,
_callInsturmentSalt(assetAddress),
type(HookBeaconProxy).creationCode
)
);

bp.initializeBeacon(
_beacon,
/// This is the ABI encoded initializer on the IHookERC721Vault.sol
abi.encodeWithSignature(
"initialize(address,address,address,address)",
_protocol,
assetAddress,
address(_protocol),
_protocol.vaultContract(),
_preapprovedMarketplace
)
);

getCallInstrument[assetAddress] = address(bp);

emit CoveredCallInsturmentCreated(
assetAddress,
getCallInstrument[assetAddress]
);

return getCallInstrument[assetAddress];
}

function _callInsturmentSalt(address underlyingAddress)
internal
pure
returns (bytes32)
{
return keccak256(abi.encode(underlyingAddress));
}
}
71 changes: 68 additions & 3 deletions src/HookCoveredCallImplV1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/utils/Create2.sol";

import "./lib/Entitlements.sol";
import "./lib/BeaconSalts.sol";

import "./interfaces/IHookERC721VaultFactory.sol";
import "./interfaces/IHookVault.sol";
Expand Down Expand Up @@ -161,7 +163,14 @@ contract HookCoveredCallImplV1 is
vault.getHoldsAsset(assetId),
"mintWithVault-- asset must be in vault"
);

require(
_allowedVaultImplementation(
vaultAddress,
allowedUnderlyingAddress,
assetId
),
"mintWithVault -- can only mint with vaults created in protocol"
);
// the beneficial owner is the only one able to impose entitlements, so
// we need to require that they've done so here.
address writer = vault.getBeneficialOwner(assetId);
Expand Down Expand Up @@ -208,6 +217,14 @@ contract HookCoveredCallImplV1 is
expirationTime == vault.entitlementExpiration(assetId),
"mintWithVault -- entitlement expiration must match call expiration"
);
require(
_allowedVaultImplementation(
vaultAddress,
allowedUnderlyingAddress,
assetId
),
"mintWithVault -- can only mint with vaults created in protocol"
);

// the beneficial owner owns the asset so
// they should recieve the option.
Expand All @@ -225,7 +242,6 @@ contract HookCoveredCallImplV1 is
uint256 expirationTime
) external whenNotPaused returns (uint256) {
address tokenOwner = IERC721(tokenAddress).ownerOf(tokenId);
uint256 assetId = 0; /// assume that the token is using an individual vault.
require(
allowedUnderlyingAddress == tokenAddress,
"mintWithErc721 -- token must be on the project allowlist"
Expand All @@ -248,6 +264,19 @@ contract HookCoveredCallImplV1 is
tokenAddress,
tokenId
);
uint256 assetId = 0;
if (
address(vault) ==
Create2.computeAddress(
BeaconSalts.multiVaultSalt(tokenAddress),
BeaconSalts.ByteCodeHash,
address(_erc721VaultFactory)
)
) {
// If the vault is a multi-vault, it requries that the assetId matches the
// tokenId, instead of having a standard assetI of 0
assetId = tokenId;
}

/// IMPORTANT: the entitlement entitles the user to this contract address. That means that even if this
// implementation code were upgraded, the contract at this address (i.e. with the new implementation) would
Expand All @@ -256,7 +285,7 @@ contract HookCoveredCallImplV1 is
beneficialOwner: tokenOwner,
operator: address(this),
vaultAddress: address(vault),
assetId: assetId, /// assume that the asset within the vault has assetId 0
assetId: assetId,
expiry: expirationTime
});

Expand Down Expand Up @@ -362,6 +391,42 @@ contract HookCoveredCallImplV1 is
_;
}

/// @dev method to verify that a particular vault was created by the protocol's vault factory
/// @param vaultAddress location where the vault is deployed
/// @param underlyingAddress address of underlying asset
/// @param assetId id of the asset within the vault
function _allowedVaultImplementation(
address vaultAddress,
address underlyingAddress,
uint256 assetId
) internal view returns (bool) {
// First check if the multiVault is the one to save a bit of gas
// in the case the user is optimizing for gas savings (by using MultiVault)
if (
vaultAddress ==
Create2.computeAddress(
BeaconSalts.multiVaultSalt(underlyingAddress),
BeaconSalts.ByteCodeHash,
address(_erc721VaultFactory)
)
) {
return true;
}

if (
vaultAddress ==
Create2.computeAddress(
BeaconSalts.soloVaultSalt(underlyingAddress, assetId),
BeaconSalts.ByteCodeHash,
address(_erc721VaultFactory)
)
) {
return true;
}

return false;
}

/// @dev See {IHookCoveredCall-bid}.
function bid(uint256 optionId)
external
Expand Down
24 changes: 0 additions & 24 deletions src/HookERC721MultiVault.sol

This file was deleted.

24 changes: 0 additions & 24 deletions src/HookERC721Vault.sol

This file was deleted.

Loading