diff --git a/contracts/src/deployers/HyperdriveDeployerCoordinator.sol b/contracts/src/deployers/HyperdriveDeployerCoordinator.sol index c0e6da631..ffb03f616 100644 --- a/contracts/src/deployers/HyperdriveDeployerCoordinator.sol +++ b/contracts/src/deployers/HyperdriveDeployerCoordinator.sol @@ -63,7 +63,7 @@ abstract contract HyperdriveDeployerCoordinator is address public immutable target4Deployer; /// @notice A mapping from deployer to deployment ID to deployment. - mapping(address => mapping(bytes32 => Deployment)) public deployments; + mapping(address => mapping(bytes32 => Deployment)) internal _deployments; /// @notice Instantiates the deployer coordinator. /// @param _coreDeployer The core deployer. @@ -100,7 +100,7 @@ abstract contract HyperdriveDeployerCoordinator is bytes32 _salt ) external returns (address) { // Ensure that the Hyperdrive entrypoint has not already been deployed. - Deployment memory deployment = deployments[msg.sender][_deploymentId]; + Deployment memory deployment = _deployments[msg.sender][_deploymentId]; if (deployment.hyperdrive != address(0)) { revert IHyperdriveDeployerCoordinator.HyperdriveAlreadyDeployed(); } @@ -132,23 +132,31 @@ abstract contract HyperdriveDeployerCoordinator is revert IHyperdriveDeployerCoordinator.MismatchedExtraData(); } + // Check the pool configuration to ensure that it's a valid + // configuration for this instance. This was already done when deploying + // target0, but we check again as a precaution in case the check relies + // on state that can change. + _checkPoolConfig(_deployConfig); + // Convert the deploy config into the pool config and set the initial // vault share price. IHyperdrive.PoolConfig memory config = _copyPoolConfig(_deployConfig); config.initialVaultSharePrice = deployment.initialSharePrice; - // Deploy the Hyperdrive instance. - return - IHyperdriveCoreDeployer(coreDeployer).deploy( - config, - _extraData, - deployment.target0, - deployment.target1, - deployment.target2, - deployment.target3, - deployment.target4, - _salt - ); + // Deploy the Hyperdrive instance and add it to the deployment struct. + address hyperdrive = IHyperdriveCoreDeployer(coreDeployer).deploy( + config, + _extraData, + deployment.target0, + deployment.target1, + deployment.target2, + deployment.target3, + deployment.target4, + _salt + ); + _deployments[msg.sender][_deploymentId].hyperdrive = hyperdrive; + + return hyperdrive; } /// @notice Deploys a Hyperdrive target instance with the given parameters. @@ -174,7 +182,7 @@ abstract contract HyperdriveDeployerCoordinator is // Ensure that the deployment is a fresh deployment. We can check this // by ensuring that the config hash is not set. if ( - deployments[msg.sender][_deploymentId].configHash != bytes32(0) + _deployments[msg.sender][_deploymentId].configHash != bytes32(0) ) { revert IHyperdriveDeployerCoordinator.DeploymentAlreadyExists(); } @@ -204,26 +212,26 @@ abstract contract HyperdriveDeployerCoordinator is ); // Store the deployment. - deployments[msg.sender][_deploymentId].configHash = configHash; - deployments[msg.sender][_deploymentId] + _deployments[msg.sender][_deploymentId].configHash = configHash; + _deployments[msg.sender][_deploymentId] .extraDataHash = extraDataHash; - deployments[msg.sender][_deploymentId] + _deployments[msg.sender][_deploymentId] .initialSharePrice = initialSharePrice; - deployments[msg.sender][_deploymentId].target0 = target; + _deployments[msg.sender][_deploymentId].target0 = target; return target; } // Ensure that the deployment is not a fresh deployment. We can check // this by ensuring that the config hash is set. - if (deployments[msg.sender][_deploymentId].configHash == bytes32(0)) { + if (_deployments[msg.sender][_deploymentId].configHash == bytes32(0)) { revert IHyperdriveDeployerCoordinator.DeploymentDoesNotExist(); } // Ensure that the provided config matches the config hash. if ( keccak256(abi.encode(_deployConfig)) != - deployments[msg.sender][_deploymentId].configHash + _deployments[msg.sender][_deploymentId].configHash ) { revert IHyperdriveDeployerCoordinator.MismatchedConfig(); } @@ -231,15 +239,21 @@ abstract contract HyperdriveDeployerCoordinator is // Ensure that the provided extra data matches the extra data hash. if ( keccak256(_extraData) != - deployments[msg.sender][_deploymentId].extraDataHash + _deployments[msg.sender][_deploymentId].extraDataHash ) { revert IHyperdriveDeployerCoordinator.MismatchedExtraData(); } + // Check the pool configuration to ensure that it's a valid + // configuration for this instance. This was already done when deploying + // target0, but we check again as a precaution in case the check relies + // on state that can change. + _checkPoolConfig(_deployConfig); + // Convert the deploy config into the pool config and set the initial // vault share price. IHyperdrive.PoolConfig memory config = _copyPoolConfig(_deployConfig); - config.initialVaultSharePrice = deployments[msg.sender][_deploymentId] + config.initialVaultSharePrice = _deployments[msg.sender][_deploymentId] .initialSharePrice; // If the target index is greater than 0, then we're deploying one of @@ -247,7 +261,7 @@ abstract contract HyperdriveDeployerCoordinator is // more than once, and their addresses are stored in the deployment // state. if (_targetIndex == 1) { - if (deployments[msg.sender][_deploymentId].target1 != address(0)) { + if (_deployments[msg.sender][_deploymentId].target1 != address(0)) { revert IHyperdriveDeployerCoordinator.TargetAlreadyDeployed(); } target = IHyperdriveTargetDeployer(target1Deployer).deploy( @@ -255,9 +269,9 @@ abstract contract HyperdriveDeployerCoordinator is _extraData, _salt ); - deployments[msg.sender][_deploymentId].target1 = target; + _deployments[msg.sender][_deploymentId].target1 = target; } else if (_targetIndex == 2) { - if (deployments[msg.sender][_deploymentId].target2 != address(0)) { + if (_deployments[msg.sender][_deploymentId].target2 != address(0)) { revert IHyperdriveDeployerCoordinator.TargetAlreadyDeployed(); } target = IHyperdriveTargetDeployer(target2Deployer).deploy( @@ -265,9 +279,9 @@ abstract contract HyperdriveDeployerCoordinator is _extraData, _salt ); - deployments[msg.sender][_deploymentId].target2 = target; + _deployments[msg.sender][_deploymentId].target2 = target; } else if (_targetIndex == 3) { - if (deployments[msg.sender][_deploymentId].target3 != address(0)) { + if (_deployments[msg.sender][_deploymentId].target3 != address(0)) { revert IHyperdriveDeployerCoordinator.TargetAlreadyDeployed(); } target = IHyperdriveTargetDeployer(target3Deployer).deploy( @@ -275,9 +289,9 @@ abstract contract HyperdriveDeployerCoordinator is _extraData, _salt ); - deployments[msg.sender][_deploymentId].target3 = target; + _deployments[msg.sender][_deploymentId].target3 = target; } else if (_targetIndex == 4) { - if (deployments[msg.sender][_deploymentId].target4 != address(0)) { + if (_deployments[msg.sender][_deploymentId].target4 != address(0)) { revert IHyperdriveDeployerCoordinator.TargetAlreadyDeployed(); } target = IHyperdriveTargetDeployer(target4Deployer).deploy( @@ -285,7 +299,7 @@ abstract contract HyperdriveDeployerCoordinator is _extraData, _salt ); - deployments[msg.sender][_deploymentId].target4 = target; + _deployments[msg.sender][_deploymentId].target4 = target; } else { revert IHyperdriveDeployerCoordinator.InvalidTargetIndex(); } @@ -293,7 +307,18 @@ abstract contract HyperdriveDeployerCoordinator is return target; } - /// @notice Checks the pool configuration to ensure that it is valid. + /// @notice Gets the deployment specified by the deployer and deployment ID. + /// @param _deployer The deployer. + /// @param _deploymentId The deployment ID. + /// @return The deployment. + function deployments( + address _deployer, + bytes32 _deploymentId + ) external view returns (Deployment memory) { + return _deployments[_deployer][_deploymentId]; + } + + /// @dev Checks the pool configuration to ensure that it is valid. /// @param _deployConfig The deploy configuration of the Hyperdrive pool. function _checkPoolConfig( IHyperdrive.PoolDeployConfig memory _deployConfig diff --git a/contracts/src/external/HyperdriveTarget0.sol b/contracts/src/external/HyperdriveTarget0.sol index 6fa16f751..a97d8556b 100644 --- a/contracts/src/external/HyperdriveTarget0.sol +++ b/contracts/src/external/HyperdriveTarget0.sol @@ -203,6 +203,13 @@ abstract contract HyperdriveTarget0 is /// Getters /// + /// @notice Gets the pauser status of an address. + /// @param _account The account to check. + /// @return The pauser status. + function isPauser(address _account) external view returns (bool) { + _revert(abi.encode(_pausers[_account])); + } + /// @notice Gets the base token. /// @return The base token. function baseToken() external view returns (address) { diff --git a/contracts/src/factory/HyperdriveFactory.sol b/contracts/src/factory/HyperdriveFactory.sol index 70dac1a26..d1a0736ca 100644 --- a/contracts/src/factory/HyperdriveFactory.sol +++ b/contracts/src/factory/HyperdriveFactory.sol @@ -20,20 +20,9 @@ contract HyperdriveFactory is IHyperdriveFactory { using FixedPointMath for uint256; using SafeERC20 for ERC20; - /// @notice The resolution for the checkpoint duration. Every checkpoint - /// duration must be a multiple of this resolution. - uint256 public checkpointDurationResolution; - /// @notice The governance address that updates the factory's configuration. address public governance; - /// @notice The number of times the factory's deployer has been updated. - uint256 public versionCounter = 1; - - /// @notice A mapping from deployed Hyperdrive instances to the version - /// of the deployer that deployed them. - mapping(address instance => uint256 version) public isOfficial; - /// @notice The governance address used when new instances are deployed. address public hyperdriveGovernance; @@ -46,6 +35,10 @@ contract HyperdriveFactory is IHyperdriveFactory { /// @notice The fee collector used when new instances are deployed. address public feeCollector; + /// @notice The resolution for the checkpoint duration. Every checkpoint + /// duration must be a multiple of this resolution. + uint256 public checkpointDurationResolution; + /// @notice The minimum checkpoint duration that can be used by new /// deployments. uint256 public minCheckpointDuration; @@ -133,6 +126,12 @@ contract HyperdriveFactory is IHyperdriveFactory { /// by governance. mapping(address => bool) public isDeployerCoordinator; + /// @notice A mapping from deployed Hyperdrive instances to the deployer + /// coordintor that deployed them. This is useful for verifying + /// the bytecode that was used to deploy the instance. + mapping(address instance => address deployCoordinator) + public instancesToDeployerCoordinators; + /// @dev Array of all instances deployed by this factory. address[] internal _instances; @@ -615,9 +614,16 @@ contract HyperdriveFactory is IHyperdriveFactory { // Add this instance to the registry and emit an event with the // deployment configuration. - isOfficial[address(hyperdrive)] = versionCounter; + instancesToDeployerCoordinators[ + address(hyperdrive) + ] = _deployerCoordinator; _config.governance = hyperdriveGovernance; - emit Deployed(versionCounter, address(hyperdrive), _config, _extraData); + emit Deployed( + _deployerCoordinator, + address(hyperdrive), + _config, + _extraData + ); // Add the newly deployed Hyperdrive instance to the registry. _instances.push(address(hyperdrive)); diff --git a/contracts/src/interfaces/IForwarderFactory.sol b/contracts/src/interfaces/IERC20ForwarderFactory.sol similarity index 95% rename from contracts/src/interfaces/IForwarderFactory.sol rename to contracts/src/interfaces/IERC20ForwarderFactory.sol index f48d7416a..0348e5a35 100644 --- a/contracts/src/interfaces/IForwarderFactory.sol +++ b/contracts/src/interfaces/IERC20ForwarderFactory.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.20; import { IERC20Forwarder } from "./IERC20Forwarder.sol"; import { IMultiToken } from "./IMultiToken.sol"; -interface IForwarderFactory { +interface IERC20ForwarderFactory { /// Errors /// error InvalidForwarderAddress(); diff --git a/contracts/src/interfaces/IHyperdrive.sol b/contracts/src/interfaces/IHyperdrive.sol index 7f36d8906..43d6bd82d 100644 --- a/contracts/src/interfaces/IHyperdrive.sol +++ b/contracts/src/interfaces/IHyperdrive.sol @@ -206,6 +206,10 @@ interface IHyperdrive is /// @notice Thrown when a permit signature is expired. error ExpiredDeadline(); + /// @notice Thrown when a user doesn't have a sufficient balance to perform + /// an action. + error InsufficientBalance(); + /// @notice Thrown when the pool doesn't have sufficient liquidity to /// complete the trade. error InsufficientLiquidity(InsufficientLiquidityReason reason); diff --git a/contracts/src/interfaces/IHyperdriveEvents.sol b/contracts/src/interfaces/IHyperdriveEvents.sol index adeeed29e..e4bd2d1ff 100644 --- a/contracts/src/interfaces/IHyperdriveEvents.sol +++ b/contracts/src/interfaces/IHyperdriveEvents.sol @@ -85,4 +85,6 @@ interface IHyperdriveEvents is IMultiTokenEvents { event GovernanceUpdated(address indexed newGovernance); event PauserUpdated(address indexed newPauser); + + event PauseStatusUpdated(bool isPaused); } diff --git a/contracts/src/interfaces/IHyperdriveFactory.sol b/contracts/src/interfaces/IHyperdriveFactory.sol index ed5f2636f..c8a227104 100644 --- a/contracts/src/interfaces/IHyperdriveFactory.sol +++ b/contracts/src/interfaces/IHyperdriveFactory.sol @@ -7,7 +7,7 @@ interface IHyperdriveFactory { /// Events /// event Deployed( - uint256 indexed version, + address indexed deployerCoordinator, address hyperdrive, IHyperdrive.PoolDeployConfig config, bytes extraData diff --git a/contracts/src/interfaces/IHyperdriveRead.sol b/contracts/src/interfaces/IHyperdriveRead.sol index 554c5c981..8d0064443 100644 --- a/contracts/src/interfaces/IHyperdriveRead.sol +++ b/contracts/src/interfaces/IHyperdriveRead.sol @@ -34,6 +34,8 @@ interface IHyperdriveRead is IMultiTokenRead { function getPoolInfo() external view returns (IHyperdrive.PoolInfo memory); + function isPauser(address _account) external view returns (bool); + function load( uint256[] calldata _slots ) external view returns (bytes32[] memory); diff --git a/contracts/src/internal/HyperdriveAdmin.sol b/contracts/src/internal/HyperdriveAdmin.sol index 1c42a41ab..09d0133f5 100644 --- a/contracts/src/internal/HyperdriveAdmin.sol +++ b/contracts/src/internal/HyperdriveAdmin.sol @@ -47,18 +47,26 @@ abstract contract HyperdriveAdmin is IHyperdriveEvents, HyperdriveBase { /// @dev Allows an authorized address to pause this contract. /// @param _status True to pause all deposits and false to unpause them. function _pause(bool _status) internal { - if (!_pausers[msg.sender]) revert IHyperdrive.Unauthorized(); - _marketState.isPaused = _status; + // Ensure that the sender is authorized to pause the contract. + if (!_pausers[msg.sender]) { + revert IHyperdrive.Unauthorized(); + } - // FIXME: This needs to emit an event. + // Update the paused status and emit an event. + _marketState.isPaused = _status; + emit PauseStatusUpdated(_status); } /// @dev Allows governance to change governance. /// @param _who The new governance address. function _setGovernance(address _who) internal { - if (msg.sender != _governance) revert IHyperdrive.Unauthorized(); - _governance = _who; + // Ensure that the sender is governance. + if (msg.sender != _governance) { + revert IHyperdrive.Unauthorized(); + } + // Update the governance address and emit an event. + _governance = _who; emit GovernanceUpdated(_who); } @@ -66,7 +74,12 @@ abstract contract HyperdriveAdmin is IHyperdriveEvents, HyperdriveBase { /// @param who The address to change. /// @param status The new pauser status. function _setPauser(address who, bool status) internal { - if (msg.sender != _governance) revert IHyperdrive.Unauthorized(); + // Ensure that the sender is governance. + if (msg.sender != _governance) { + revert IHyperdrive.Unauthorized(); + } + + // Update the pauser status and emit an event. _pausers[who] = status; emit PauserUpdated(who); } diff --git a/contracts/src/internal/HyperdriveCheckpoint.sol b/contracts/src/internal/HyperdriveCheckpoint.sol index ebeda8af5..e44c34626 100644 --- a/contracts/src/internal/HyperdriveCheckpoint.sol +++ b/contracts/src/internal/HyperdriveCheckpoint.sol @@ -111,6 +111,12 @@ abstract contract HyperdriveCheckpoint is uint256 maturedShortsAmount = _totalSupply[shortAssetId]; bool positionsClosed; if (maturedShortsAmount > 0) { + // Since we're closing out short positions, we'll need to distribute + // excess idle once the accounting updates have been performed. + positionsClosed = true; + + // Apply the governance and LP proceeds from closing out the matured + // short positions to the state. ( uint256 shareProceeds, uint256 governanceFee @@ -128,30 +134,35 @@ abstract contract HyperdriveCheckpoint is int256(shareProceeds), // keep the effective share reserves constant _checkpointTime ); - // NOTE: Round up to underestimate the short proceeds. - uint256 shareReservesDelta = maturedShortsAmount.divUp( - _vaultSharePrice - ); - // NOTE: We divDown then mulDown to mimic the exact rounding that occurs - // when the short is closed and the fee is calculated in _calculateFeesGivenBonds(). - shareReservesDelta += maturedShortsAmount - .divDown(_vaultSharePrice) - .mulDown(_flatFee); + + // Add the governance fee back to the share proceeds. We removed it + // from the LP's share proceeds since the fee is paid to governance; + // however, the shorts must pay the flat fee. + shareProceeds += governanceFee; + + // Calculate the share proceeds owed to the matured short positions. + // Since the shorts have matured and the bonds have matured to a + // value of 1, this is the amount of variable interest that the + // shorts earned minus the flat fee. + // // NOTE: Round down to underestimate the short proceeds. shareProceeds = HyperdriveMath.calculateShortProceedsDown( maturedShortsAmount, - shareReservesDelta, + shareProceeds, openVaultSharePrice, _vaultSharePrice, _vaultSharePrice, _flatFee ); + + // Add the short proceeds to the zombie base proceeds and share + // reserves. + // // NOTE: Round down to underestimate the short proceeds. _marketState.zombieBaseProceeds += shareProceeds .mulDown(_vaultSharePrice) .toUint112(); _marketState.zombieShareReserves += shareProceeds.toUint128(); - positionsClosed = true; } // Close out all of the long positions that matured at the beginning of @@ -162,6 +173,12 @@ abstract contract HyperdriveCheckpoint is ); uint256 maturedLongsAmount = _totalSupply[longAssetId]; if (maturedLongsAmount > 0) { + // Since we're closing out long positions, we'll need to distribute + // excess idle once the accounting updates have been performed. + positionsClosed = true; + + // Apply the governance and LP proceeds from closing out the matured + // long positions to the state. ( uint256 shareProceeds, uint256 governanceFee @@ -185,12 +202,14 @@ abstract contract HyperdriveCheckpoint is // share proceeds to the zombie share reserves. shareProceeds -= governanceFee; + // Add the long proceeds to the zombie base proceeds and share + // reserves. + // // NOTE: Round down to underestimate the long proceeds. _marketState.zombieBaseProceeds += shareProceeds .mulDown(_vaultSharePrice) .toUint112(); _marketState.zombieShareReserves += shareProceeds.toUint128(); - positionsClosed = true; } // If we closed any positions, update the global long exposure and diff --git a/contracts/src/internal/HyperdriveMultiToken.sol b/contracts/src/internal/HyperdriveMultiToken.sol index 308a60a5a..fe178eb93 100644 --- a/contracts/src/internal/HyperdriveMultiToken.sol +++ b/contracts/src/internal/HyperdriveMultiToken.sol @@ -23,11 +23,12 @@ import { HyperdriveBase } from "./HyperdriveBase.sol"; /// only, and is not intended to, and does not, have any /// particular legal or regulatory significance. abstract contract HyperdriveMultiToken is IHyperdriveEvents, HyperdriveBase { - /// @notice This modifier checks the caller is the create2 validated ERC20 bridge. + /// @notice This modifier checks the caller is the create2 validated + /// ERC20 bridge. /// @param tokenID The internal token identifier. modifier onlyLinker(uint256 tokenID) { - // If the caller does not match the address hash, we revert because it is not - // allowed to access permission-ed methods. + // If the caller does not match the address hash, we revert because it + // is not allowed to access permissioned methods. if (msg.sender != _deriveForwarderAddress(tokenID)) { revert IHyperdrive.InvalidERC20Bridge(); } @@ -35,9 +36,9 @@ abstract contract HyperdriveMultiToken is IHyperdriveEvents, HyperdriveBase { _; } - /// @dev Transfers several assets from one account to another - /// @param from the source account. - /// @param to the destination account. + /// @dev Transfers several assets from one account to another. + /// @param from The source account. + /// @param to The destination account. /// @param ids The array of token ids of the asset to transfer. /// @param values The amount of each token to transfer. function _batchTransferFrom( @@ -150,6 +151,12 @@ abstract contract HyperdriveMultiToken is IHyperdriveEvents, HyperdriveBase { /// @param amount The number of tokens to remove. /// @dev Must be used from inheriting contracts. function _burn(uint256 tokenID, address from, uint256 amount) internal { + // Check to see if the balance is sufficient. If it isn't, throw an + // insufficient balance error. + if (_balanceOf[tokenID][from] < amount) { + revert IHyperdrive.InsufficientBalance(); + } + // Decrement from the source and supply. _balanceOf[tokenID][from] -= amount; _totalSupply[tokenID] -= amount; diff --git a/contracts/src/token/ERC20Forwarder.sol b/contracts/src/token/ERC20Forwarder.sol index c3438b0ba..5c2e8c076 100644 --- a/contracts/src/token/ERC20Forwarder.sol +++ b/contracts/src/token/ERC20Forwarder.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.20; import { IERC20Forwarder } from "../interfaces/IERC20Forwarder.sol"; -import { IForwarderFactory } from "../interfaces/IForwarderFactory.sol"; +import { IERC20ForwarderFactory } from "../interfaces/IERC20ForwarderFactory.sol"; import { IHyperdrive } from "../interfaces/IHyperdrive.sol"; import { IMultiToken } from "../interfaces/IMultiToken.sol"; @@ -19,31 +19,38 @@ import { IMultiToken } from "../interfaces/IMultiToken.sol"; /// only, and is not intended to, and does not, have any /// particular legal or regulatory significance. contract ERC20Forwarder is IERC20Forwarder { - // The contract which contains the actual state for this 'ERC20' + /// @notice The target token ID that this ERC20 interface forwards to. IMultiToken public immutable token; - // The ID for this contract's 'ERC20' as a sub token of the main token + + /// @notice The target token ID that this ERC20 interface forwards to. uint256 public immutable tokenId; - // A mapping to track the permit signature nonces + + /// @notice A mapping from a user to their nonce for permit signatures. mapping(address user => uint256 nonce) public nonces; - // EIP712 + + /// @notice The EIP712 domain separator for this contract bytes32 public immutable DOMAIN_SEPARATOR; // solhint-disable-line var-name-mixedcase + + /// @notice The EIP712 typehash for the permit struct used by this contract + /// to validate permit signatures. bytes32 public constant PERMIT_TYPEHASH = keccak256( "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" ); - /// @notice Constructs this contract by initializing the immutables - /// @dev To give the contract a constant deploy code hash we call back - /// into the factory to load info instead of using calldata. + /// @notice Initializes the ERC20 forwarder. + /// @dev To give the contract a constant deploy code hash we call back into + /// the factory to load info instead of using calldata. constructor() { - // The deployer is the factory - IForwarderFactory factory = IForwarderFactory(msg.sender); - // We load the data we need to init + // The deployer is the factory. + IERC20ForwarderFactory factory = IERC20ForwarderFactory(msg.sender); + + // Load the initialization data from the factory. (token, tokenId) = factory.getDeployDetails(); - // Computes the EIP 712 domain separator which prevents user signed messages for - // this contract to be replayed in other contracts. - // https://eips.ethereum.org/EIPS/eip-712 + // Computes the EIP712 domain separator which prevents user signed + // messages for this contract to be replayed in other contracts: + // https://eips.ethereum.org/EIPS/eip-712. DOMAIN_SEPARATOR = keccak256( abi.encode( keccak256( @@ -57,37 +64,38 @@ contract ERC20Forwarder is IERC20Forwarder { ); } - /// @notice Returns the decimals for this 'ERC20', we are opinionated - /// so we just return 18 in all cases - /// @return Always 18 + /// @notice Returns the decimals for this ERC20 interface. Hyperdrive's + /// sub-tokens always use 18 decimals. + /// @return The amount of decimals (always 18). function decimals() external pure override returns (uint8) { return 18; } - /// @notice Returns the name of this sub token by calling into the - /// main token to load it. - /// @return Returns the name of this token + /// @notice Returns this token's name. This is the name of the underlying + /// MultiToken sub-token. + /// @return Returns the token's name. function name() external view override returns (string memory) { return token.name(tokenId); } - /// @notice Returns the totalSupply of the sub token by calling into the - /// main token to load it. - /// @return Returns the totalSupply of this token + /// @notice Returns this token's total supply. This is the total supply + /// of the underlying MultiToken sub-token. + /// @return Returns the total supply of this token. function totalSupply() external view override returns (uint256) { return token.totalSupply(tokenId); } - /// @notice Returns the symbol of this sub token by calling into the - /// main token to load it. - /// @return Returns the symbol of this token + /// @notice Returns this token's symbol. This is the symbol of the + /// underlying MultiToken sub-token. + /// @return Returns the token's symbol. function symbol() external view override returns (string memory) { return token.symbol(tokenId); } - /// @notice Returns the balance of this sub token through an ERC20 compliant - /// interface. - /// @return The balance of the queried account. + /// @notice Returns a user's token balance. This is the balance of the user + /// in the underlying MultiToken sub-token. + /// @param who The owner of the tokens. + /// @return Returns the user's balance. function balanceOf(address who) external view override returns (uint256) { return token.balanceOf(tokenId, who); } @@ -96,33 +104,37 @@ contract ERC20Forwarder is IERC20Forwarder { /// If spender is approved for all tokens in the main contract /// it will return Max(uint256) otherwise it returns the allowance /// the allowance for just this asset. - /// @param owner The account who's tokens would be spent - /// @param spender The account who might be able to spend tokens - /// @return The amount of the owner's tokens the spender can spend + /// @param owner The account who's tokens would be spent. + /// @param spender The account who might be able to spend tokens. + /// @return The amount of the owner's tokens the spender can spend. function allowance( address owner, address spender ) external view override returns (uint256) { - // If the owner is approved for all they can spend an unlimited amount + // If the owner is approved for all they can spend an unlimited amount. if (token.isApprovedForAll(owner, spender)) { return type(uint256).max; - } else { - // otherwise they can only spend up the their per token approval for - // the owner + } + // Otherwise they can only spend up the their per-token approval for + // the owner. + else { return token.perTokenApprovals(tokenId, owner, spender); } } - /// @notice Sets an approval for just this sub-token for the caller in the main token - /// @param spender The address which can spend tokens of the caller + /// @notice Sets an approval for just this sub-token for the caller in the + /// main token. + /// @param spender The address which can spend tokens of the caller. /// @param amount The amount which the spender is allowed to spend, if it is - /// set to uint256.max it is infinite and will not be reduced by transfer. - /// @return True if approval successful, false if not. The contract also reverts - /// on failure so only true is possible. + /// set to uint256.max it is infinite and will not be reduced by + /// transfer. + /// @return True if approval successful, false if not. The contract also + /// reverts on failure so only true is possible. function approve(address spender, uint256 amount) external returns (bool) { - // The main token handles the internal approval logic + // The main token handles the internal approval logic. token.setApprovalBridge(tokenId, spender, amount, msg.sender); - // Emit a ERC20 compliant approval event + + // Emit a ERC20 compliant approval event. emit Approval(msg.sender, spender, amount); return true; } @@ -130,8 +142,8 @@ contract ERC20Forwarder is IERC20Forwarder { /// @notice Forwards a call to transfer from the msg.sender to the recipient. /// @param recipient The recipient of the token transfer /// @param amount The amount of token to transfer - /// @return True if transfer successful, false if not. The contract also reverts - /// on failed transfer so only true is possible. + /// @return True if transfer successful, false if not. The contract also + /// reverts on failed transfer so only true is possible. function transfer( address recipient, uint256 amount @@ -143,23 +155,25 @@ contract ERC20Forwarder is IERC20Forwarder { amount, msg.sender ); - // Emits an ERC20 compliant transfer event + + // Emits an ERC20 compliant transfer event. emit Transfer(msg.sender, recipient, amount); return true; } - /// @notice Forwards a call to transferFrom to move funds from an owner to a recipient - /// @param source The source of the tokens to be transferred - /// @param recipient The recipient of the tokens - /// @param amount The amount of tokens to be transferred - /// @return Returns true for success false for failure, also reverts on fail, so will - /// always return true. + /// @notice Forwards a call to transferFrom to move funds from an owner to a + /// recipient. + /// @param source The source of the tokens to be transferred. + /// @param recipient The recipient of the tokens. + /// @param amount The amount of tokens to be transferred. + /// @return Returns true for success false for failure, also reverts on + /// fail, so will always return true. function transferFrom( address source, address recipient, uint256 amount ) external returns (bool) { - // The token handles the approval logic checking and transfer + // The token handles the approval logic checking and transfer. token.transferFromBridge( tokenId, source, @@ -167,23 +181,30 @@ contract ERC20Forwarder is IERC20Forwarder { amount, msg.sender ); - // Emits an ERC20 compliant transfer event + + // Emits an ERC20 compliant transfer event. emit Transfer(source, recipient, amount); return true; } - /// @notice This function allows a caller who is not the owner of an account to execute the functionality of 'approve' with the owners signature. - /// @param owner the owner of the account which is having the new approval set - /// @param spender the address which will be allowed to spend owner's tokens - /// @param value the new allowance value - /// @param deadline the timestamp which the signature must be submitted by to be valid - /// @param v Extra ECDSA data which allows public key recovery from signature assumed to be 27 or 28 - /// @param r The r component of the ECDSA signature - /// @param s The s component of the ECDSA signature - /// @dev The signature for this function follows EIP 712 standard and should be generated with the - /// eth_signTypedData JSON RPC call instead of the eth_sign JSON RPC call. If using out of date - /// parity signing libraries the v component may need to be adjusted. Also it is very rare but possible - /// for v to be other values, those values are not supported. + /// @notice This function allows a caller who is not the owner of an account + /// to execute the functionality of 'approve' with the owners + /// signature. + /// @dev The signature for this function follows EIP712 standard and should + /// be generated with the eth_signTypedData JSON RPC call instead of + /// the eth_sign JSON RPC call. If using out of date parity signing + /// libraries the v component may need to be adjusted. Also it is very + /// rare but possible for v to be other values. Those values are not + /// supported. + /// @param owner The owner of the account which is having the new approval set. + /// @param spender The address which will be allowed to spend owner's tokens. + /// @param value The new allowance value. + /// @param deadline The timestamp which the signature must be submitted by + /// to be valid. + /// @param v Extra ECDSA data which allows public key recovery from + /// signature assumed to be 27 or 28. + /// @param r The r component of the ECDSA signature. + /// @param s The s component of the ECDSA signature. function permit( address owner, address spender, @@ -193,16 +214,17 @@ contract ERC20Forwarder is IERC20Forwarder { bytes32 r, bytes32 s ) external { - // Require that the signature is not expired + // Require that the signature is not expired. if (block.timestamp > deadline) { revert IERC20Forwarder.ExpiredDeadline(); } - // Require that the owner is not zero + // Require that the owner is not zero. if (owner == address(0)) { revert IERC20Forwarder.RestrictedZeroAddress(); } + // Get the current nonce for the owner and calculate the EIP712 struct. uint256 nonce = nonces[owner]; bytes32 structHash = keccak256( abi.encodePacked( @@ -221,17 +243,18 @@ contract ERC20Forwarder is IERC20Forwarder { ) ); - // Check that the signature is valid + // Check that the signature is valid. address signer = ecrecover(structHash, v, r, s); if (signer != owner) { revert InvalidSignature(); } - // Increment the signature nonce + // Increment the signature nonce. unchecked { nonces[owner] = nonce + 1; } - // Set the approval to the new value + + // Set the approval to the new value. token.setApprovalBridge(tokenId, spender, value, owner); emit Approval(owner, spender, value); } diff --git a/contracts/src/token/ForwarderFactory.sol b/contracts/src/token/ERC20ForwarderFactory.sol similarity index 81% rename from contracts/src/token/ForwarderFactory.sol rename to contracts/src/token/ERC20ForwarderFactory.sol index 87ebb48df..c46e3382c 100644 --- a/contracts/src/token/ForwarderFactory.sol +++ b/contracts/src/token/ERC20ForwarderFactory.sol @@ -2,15 +2,13 @@ pragma solidity 0.8.20; import { IERC20Forwarder } from "../interfaces/IERC20Forwarder.sol"; -import { IForwarderFactory } from "../interfaces/IForwarderFactory.sol"; +import { IERC20ForwarderFactory } from "../interfaces/IERC20ForwarderFactory.sol"; import { IHyperdrive } from "../interfaces/IHyperdrive.sol"; import { IMultiToken } from "../interfaces/IMultiToken.sol"; import { ERC20Forwarder } from "./ERC20Forwarder.sol"; -// FIXME: Rename this to ERC20ForwarderFactory for clarity. -// /// @author DELV -/// @title ForwarderFactory +/// @title ERC20ForwarderFactory /// @notice Our MultiToken contract consists of fungible sub-tokens that /// are similar to ERC20 tokens. In order to support ERC20 compatibility /// we can deploy interfaces which are ERC20s. @@ -20,23 +18,21 @@ import { ERC20Forwarder } from "./ERC20Forwarder.sol"; /// @custom:disclaimer The language used in this code is for coding convenience /// only, and is not intended to, and does not, have any /// particular legal or regulatory significance. -contract ForwarderFactory is IForwarderFactory { - // The transient state variables used in deployment - // Note - It saves us a bit of gas to not fully zero them at any point +contract ERC20ForwarderFactory is IERC20ForwarderFactory { + /// @notice The transient MultiToken addressed used in deployment. IMultiToken private _token = IMultiToken(address(1)); + + /// @notice The transient token ID addressed used in deployment. uint256 private _tokenId = 1; - // For reference + /// @notice The hash of the bytecode of the ERC20 forwarder contract. bytes32 public constant ERC20LINK_HASH = keccak256(type(ERC20Forwarder).creationCode); - // FIXME: This should emit an event to make discoverability easier. - // /// @notice Uses create2 to deploy a forwarder at a predictable address as /// part of our ERC20 multitoken implementation. - /// @param __token The multitoken which the forwarder should link to. - /// @param __tokenId The id of the sub token from the multitoken which we are - /// creating an interface for. + /// @param __token The MultiToken targeted by this factory. + /// @param __tokenId The sub-token ID targeted by this factory. /// @return Returns the address of the deployed forwarder. function create( IMultiToken __token, @@ -54,7 +50,7 @@ contract ForwarderFactory is IForwarderFactory { // As a consistency check we check that this is in the right address. if (!(address(deployed) == getForwarder(__token, __tokenId))) { - revert IForwarderFactory.InvalidForwarderAddress(); + revert IERC20ForwarderFactory.InvalidForwarderAddress(); } // Reset the transient state. diff --git a/script/DevnetMigration.s.sol b/script/DevnetMigration.s.sol index 20991f190..6d2a8fbd0 100644 --- a/script/DevnetMigration.s.sol +++ b/script/DevnetMigration.s.sol @@ -24,7 +24,7 @@ import { IERC4626 } from "contracts/src/interfaces/IERC4626.sol"; import { IHyperdrive } from "contracts/src/interfaces/IHyperdrive.sol"; import { ILido } from "contracts/src/interfaces/ILido.sol"; import { HyperdriveMath } from "contracts/src/libraries/HyperdriveMath.sol"; -import { ForwarderFactory } from "contracts/src/token/ForwarderFactory.sol"; +import { ERC20ForwarderFactory } from "contracts/src/token/ERC20ForwarderFactory.sol"; import { ERC20Mintable } from "contracts/test/ERC20Mintable.sol"; import { MockERC4626 } from "contracts/test/MockERC4626.sol"; import { MockHyperdriveMath } from "contracts/test/MockHyperdriveMath.sol"; @@ -343,7 +343,7 @@ contract DevnetMigration is Script { { address[] memory defaultPausers = new address[](1); defaultPausers[0] = config.admin; - ForwarderFactory forwarderFactory = new ForwarderFactory(); + ERC20ForwarderFactory forwarderFactory = new ERC20ForwarderFactory(); HyperdriveFactory.FactoryConfig memory factoryConfig = HyperdriveFactory.FactoryConfig({ governance: msg.sender, diff --git a/test/combinatorial/MultiToken._transferFrom.t.sol b/test/combinatorial/MultiToken._transferFrom.t.sol index 8f86d8cc0..adbf1e732 100644 --- a/test/combinatorial/MultiToken._transferFrom.t.sol +++ b/test/combinatorial/MultiToken._transferFrom.t.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.20; import "forge-std/Test.sol"; import "forge-std/console2.sol"; -import { ForwarderFactory } from "contracts/src/token/ForwarderFactory.sol"; +import { ERC20ForwarderFactory } from "contracts/src/token/ERC20ForwarderFactory.sol"; import { MockMultiToken, IMockMultiToken } from "contracts/test/MockMultiToken.sol"; import { CombinatorialTest } from "test/utils/CombinatorialTest.sol"; diff --git a/test/instances/erc4626/ERC4626Hyperdrive.t.sol b/test/instances/erc4626/ERC4626Hyperdrive.t.sol index ae1bdd859..41de71a86 100644 --- a/test/instances/erc4626/ERC4626Hyperdrive.t.sol +++ b/test/instances/erc4626/ERC4626Hyperdrive.t.sol @@ -22,7 +22,7 @@ import { IHyperdriveDeployerCoordinator } from "contracts/src/interfaces/IHyperd import { AssetId } from "contracts/src/libraries/AssetId.sol"; import { FixedPointMath, ONE } from "contracts/src/libraries/FixedPointMath.sol"; import { HyperdriveMath } from "contracts/src/libraries/HyperdriveMath.sol"; -import { ForwarderFactory } from "contracts/src/token/ForwarderFactory.sol"; +import { ERC20ForwarderFactory } from "contracts/src/token/ERC20ForwarderFactory.sol"; import { ERC20Mintable } from "contracts/test/ERC20Mintable.sol"; import { MockERC4626, ERC20 } from "contracts/test/MockERC4626.sol"; import { MockERC4626Hyperdrive } from "contracts/test/MockERC4626Hyperdrive.sol"; @@ -84,7 +84,7 @@ contract ERC4626HyperdriveTest is HyperdriveTest { ); address[] memory defaults = new address[](1); defaults[0] = bob; - forwarderFactory = new ForwarderFactory(); + forwarderFactory = new ERC20ForwarderFactory(); factory = new HyperdriveFactory( HyperdriveFactory.FactoryConfig({ governance: alice, @@ -370,7 +370,7 @@ contract ERC4626HyperdriveTest is HyperdriveTest { // Verify that the correct events were emitted. verifyFactoryEvents( - factory, + deployerCoordinator, hyperdrive, alice, contribution, diff --git a/test/instances/erc4626/ERC4626Validation.t.sol b/test/instances/erc4626/ERC4626Validation.t.sol index ad6b13810..7a323fdb3 100644 --- a/test/instances/erc4626/ERC4626Validation.t.sol +++ b/test/instances/erc4626/ERC4626Validation.t.sol @@ -16,7 +16,7 @@ import { IHyperdriveDeployerCoordinator } from "contracts/src/interfaces/IHyperd import { AssetId } from "contracts/src/libraries/AssetId.sol"; import { FixedPointMath, ONE } from "contracts/src/libraries/FixedPointMath.sol"; import { HyperdriveMath } from "contracts/src/libraries/HyperdriveMath.sol"; -import { ForwarderFactory } from "contracts/src/token/ForwarderFactory.sol"; +import { ERC20ForwarderFactory } from "contracts/src/token/ERC20ForwarderFactory.sol"; import { MockERC4626Hyperdrive } from "contracts/test/MockERC4626Hyperdrive.sol"; import { HyperdriveTest } from "test/utils/HyperdriveTest.sol"; import { HyperdriveUtils } from "test/utils/HyperdriveUtils.sol"; @@ -67,7 +67,7 @@ abstract contract ERC4626ValidationTest is HyperdriveTest { address[] memory defaults = new address[](1); defaults[0] = bob; - forwarderFactory = new ForwarderFactory(); + forwarderFactory = new ERC20ForwarderFactory(); // Hyperdrive factory to produce ERC4626 instances for stethERC4626 factory = new HyperdriveFactory( @@ -295,7 +295,7 @@ abstract contract ERC4626ValidationTest is HyperdriveTest { // Verify that the correct events were emitted during creation verifyFactoryEvents( - factory, + deployerCoordinator, hyperdrive, alice, contribution, diff --git a/test/instances/erc4626/UsdcERC4626.t.sol b/test/instances/erc4626/UsdcERC4626.t.sol index e86066e39..25cfca035 100644 --- a/test/instances/erc4626/UsdcERC4626.t.sol +++ b/test/instances/erc4626/UsdcERC4626.t.sol @@ -17,7 +17,7 @@ import { ILido } from "contracts/src/interfaces/ILido.sol"; import { AssetId } from "contracts/src/libraries/AssetId.sol"; import { FixedPointMath, ONE } from "contracts/src/libraries/FixedPointMath.sol"; import { HyperdriveMath } from "contracts/src/libraries/HyperdriveMath.sol"; -import { ForwarderFactory } from "contracts/src/token/ForwarderFactory.sol"; +import { ERC20ForwarderFactory } from "contracts/src/token/ERC20ForwarderFactory.sol"; import { HyperdriveTest } from "test/utils/HyperdriveTest.sol"; import { MockERC4626Hyperdrive } from "contracts/test/MockERC4626Hyperdrive.sol"; import { ERC20Mintable } from "contracts/test/ERC20Mintable.sol"; @@ -74,7 +74,7 @@ contract UsdcERC4626 is ERC4626ValidationTest { address[] memory defaults = new address[](1); defaults[0] = bob; - forwarderFactory = new ForwarderFactory(); + forwarderFactory = new ERC20ForwarderFactory(); // Hyperdrive factory to produce ERC4626 instances for UsdcERC4626. factory = new HyperdriveFactory( diff --git a/test/instances/steth/StETHHyperdrive.t.sol b/test/instances/steth/StETHHyperdrive.t.sol index c51e844ab..88c0ce115 100644 --- a/test/instances/steth/StETHHyperdrive.t.sol +++ b/test/instances/steth/StETHHyperdrive.t.sol @@ -16,7 +16,7 @@ import { ILido } from "contracts/src/interfaces/ILido.sol"; import { AssetId } from "contracts/src/libraries/AssetId.sol"; import { FixedPointMath, ONE } from "contracts/src/libraries/FixedPointMath.sol"; import { HyperdriveMath } from "contracts/src/libraries/HyperdriveMath.sol"; -import { ForwarderFactory } from "contracts/src/token/ForwarderFactory.sol"; +import { ERC20ForwarderFactory } from "contracts/src/token/ERC20ForwarderFactory.sol"; import { ERC20Mintable } from "contracts/test/ERC20Mintable.sol"; import { ETH } from "test/utils/Constants.sol"; import { HyperdriveTest } from "test/utils/HyperdriveTest.sol"; @@ -51,7 +51,7 @@ contract StETHHyperdriveTest is HyperdriveTest { vm.startPrank(deployer); address[] memory defaults = new address[](1); defaults[0] = bob; - forwarderFactory = new ForwarderFactory(); + forwarderFactory = new ERC20ForwarderFactory(); factory = new HyperdriveFactory( HyperdriveFactory.FactoryConfig({ governance: alice, @@ -329,7 +329,7 @@ contract StETHHyperdriveTest is HyperdriveTest { // Verify that the correct events were emitted. verifyFactoryEvents( - factory, + deployerCoordinator, hyperdrive, bob, contribution, diff --git a/test/integrations/deployers/DeployerCoordinator.t.sol b/test/integrations/deployers/DeployerCoordinator.t.sol new file mode 100644 index 000000000..20c0c1feb --- /dev/null +++ b/test/integrations/deployers/DeployerCoordinator.t.sol @@ -0,0 +1,441 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.20; + +import { IERC20 } from "contracts/src/interfaces/IERC20.sol"; +import { IHyperdrive } from "contracts/src/interfaces/IHyperdrive.sol"; +import { IHyperdriveDeployerCoordinator } from "contracts/src/interfaces/IHyperdriveDeployerCoordinator.sol"; +import { HyperdriveDeployerCoordinator } from "contracts/src/deployers/HyperdriveDeployerCoordinator.sol"; +import { ERC4626HyperdriveCoreDeployer } from "contracts/src/deployers/erc4626/ERC4626HyperdriveCoreDeployer.sol"; +import { ERC4626Target0Deployer } from "contracts/src/deployers/erc4626/ERC4626Target0Deployer.sol"; +import { ERC4626Target1Deployer } from "contracts/src/deployers/erc4626/ERC4626Target1Deployer.sol"; +import { ERC4626Target2Deployer } from "contracts/src/deployers/erc4626/ERC4626Target2Deployer.sol"; +import { ERC4626Target3Deployer } from "contracts/src/deployers/erc4626/ERC4626Target3Deployer.sol"; +import { ERC4626Target4Deployer } from "contracts/src/deployers/erc4626/ERC4626Target4Deployer.sol"; +import { ONE } from "contracts/src/libraries/FixedPointMath.sol"; +import { ERC20Mintable } from "contracts/test/ERC20Mintable.sol"; +import { MockERC4626 } from "contracts/test/MockERC4626.sol"; +import { HyperdriveTest } from "test/utils/HyperdriveTest.sol"; +import { Lib } from "test/utils/Lib.sol"; + +contract MockHyperdriveDeployerCoordinator is HyperdriveDeployerCoordinator { + bool internal _checkPoolConfigStatus = true; + + constructor( + address _coreDeployer, + address _target0Deployer, + address _target1Deployer, + address _target2Deployer, + address _target3Deployer, + address _target4Deployer + ) + HyperdriveDeployerCoordinator( + _coreDeployer, + _target0Deployer, + _target1Deployer, + _target2Deployer, + _target3Deployer, + _target4Deployer + ) + {} + + function setCheckPoolConfigStatus(bool _status) external { + _checkPoolConfigStatus = _status; + } + + function _checkPoolConfig( + IHyperdrive.PoolDeployConfig memory + ) internal view override { + require( + _checkPoolConfigStatus, + "MockDeployerCoordinator: invalid config" + ); + } + + function _getInitialVaultSharePrice( + bytes memory + ) internal pure override returns (uint256) { + return ONE; + } +} + +contract DeployerCoordinatorTest is HyperdriveTest { + using Lib for *; + + bytes32 constant DEPLOYMENT_ID = bytes32(uint256(0xdeadbeef)); + bytes32 constant SALT = bytes32(uint256(0xdecafc0ffee)); + + bytes internal extraData; + IHyperdrive.PoolDeployConfig internal config; + + MockERC4626 internal vault; + MockHyperdriveDeployerCoordinator internal coordinator; + + function setUp() public override { + super.setUp(); + + // Deploy a base token and ERC4626 vault. Encode the vault into extra + // data. + vm.stopPrank(); + vm.startPrank(alice); + ERC20Mintable baseToken = new ERC20Mintable( + "Base Token", + "BASE", + 18, + address(0), + false + ); + vault = new MockERC4626( + baseToken, + "Vault", + "VAULT", + 18, + address(0), + false + ); + extraData = abi.encode(vault); + + // Create a deployment config. + config = testDeployConfig(0.05e18, 365 days); + config.baseToken = IERC20(address(baseToken)); + + // Deploy the coordinator. + coordinator = new MockHyperdriveDeployerCoordinator( + address(new ERC4626HyperdriveCoreDeployer()), + address(new ERC4626Target0Deployer()), + address(new ERC4626Target1Deployer()), + address(new ERC4626Target2Deployer()), + address(new ERC4626Target3Deployer()), + address(new ERC4626Target4Deployer()) + ); + } + + function test_deployTarget_failure_deploymentAlreadyExists() external { + // Deploy a target0 instance. + coordinator.deployTarget(DEPLOYMENT_ID, config, extraData, 0, SALT); + + // Attempt to deploy a target0 instance again. + vm.expectRevert( + IHyperdriveDeployerCoordinator.DeploymentAlreadyExists.selector + ); + coordinator.deployTarget(DEPLOYMENT_ID, config, extraData, 0, SALT); + } + + function test_deployTarget_failure_deploymentDoesNotExist() external { + // Attempt to deploy a target1 instance without first deploying target0. + vm.expectRevert( + IHyperdriveDeployerCoordinator.DeploymentDoesNotExist.selector + ); + coordinator.deployTarget(DEPLOYMENT_ID, config, extraData, 1, SALT); + } + + function test_deployTarget_failure_mismatchedConfig() external { + // Deploy a target0 instance. + coordinator.deployTarget(DEPLOYMENT_ID, config, extraData, 0, SALT); + + // Attempt to deploy a target1 instance with a mismatched config. + config.baseToken = IERC20(address(0)); + vm.expectRevert( + IHyperdriveDeployerCoordinator.MismatchedConfig.selector + ); + coordinator.deployTarget(DEPLOYMENT_ID, config, extraData, 1, SALT); + } + + function test_deployTarget_failure_mismatchedExtraData() external { + // Deploy a target0 instance. + coordinator.deployTarget(DEPLOYMENT_ID, config, extraData, 0, SALT); + + // Attempt to deploy a target1 instance with mismatched extra data. + vm.expectRevert( + IHyperdriveDeployerCoordinator.MismatchedExtraData.selector + ); + coordinator.deployTarget(DEPLOYMENT_ID, config, new bytes(0), 1, SALT); + } + + function test_deployTarget_failure_invalidCheckPoolConfigTarget0() + external + { + // Attempt to deploy a target0 instance with an invalid config. + coordinator.setCheckPoolConfigStatus(false); + vm.expectRevert("MockDeployerCoordinator: invalid config"); + coordinator.deployTarget(DEPLOYMENT_ID, config, extraData, 0, SALT); + } + + function test_deployTarget_failure_invalidCheckPoolConfigTarget1() + external + { + // Deploy a target0 instance. + coordinator.deployTarget(DEPLOYMENT_ID, config, extraData, 0, SALT); + + // Attempt to deploy a target1 instance with an invalid config. + coordinator.setCheckPoolConfigStatus(false); + vm.expectRevert("MockDeployerCoordinator: invalid config"); + coordinator.deployTarget(DEPLOYMENT_ID, config, extraData, 1, SALT); + } + + function test_deployTarget_failure_target1AlreadyDeployed() external { + // Deploy a target0 instance. + coordinator.deployTarget(DEPLOYMENT_ID, config, extraData, 0, SALT); + + // Deploy a target1 instance. + coordinator.deployTarget(DEPLOYMENT_ID, config, extraData, 1, SALT); + + // Attempt to deploy target1 again. + vm.expectRevert( + IHyperdriveDeployerCoordinator.TargetAlreadyDeployed.selector + ); + coordinator.deployTarget(DEPLOYMENT_ID, config, extraData, 1, SALT); + } + + function test_deployTarget_failure_target2AlreadyDeployed() external { + // Deploy a target0 instance. + coordinator.deployTarget(DEPLOYMENT_ID, config, extraData, 0, SALT); + + // Deploy a target2 instance. + coordinator.deployTarget(DEPLOYMENT_ID, config, extraData, 2, SALT); + + // Attempt to deploy target2 again. + vm.expectRevert( + IHyperdriveDeployerCoordinator.TargetAlreadyDeployed.selector + ); + coordinator.deployTarget(DEPLOYMENT_ID, config, extraData, 2, SALT); + } + + function test_deployTarget_failure_target3AlreadyDeployed() external { + // Deploy a target0 instance. + coordinator.deployTarget(DEPLOYMENT_ID, config, extraData, 0, SALT); + + // Deploy a target3 instance. + coordinator.deployTarget(DEPLOYMENT_ID, config, extraData, 3, SALT); + + // Attempt to deploy target3 again. + vm.expectRevert( + IHyperdriveDeployerCoordinator.TargetAlreadyDeployed.selector + ); + coordinator.deployTarget(DEPLOYMENT_ID, config, extraData, 3, SALT); + } + + function test_deployTarget_failure_target4AlreadyDeployed() external { + // Deploy a target0 instance. + coordinator.deployTarget(DEPLOYMENT_ID, config, extraData, 0, SALT); + + // Deploy a target4 instance. + coordinator.deployTarget(DEPLOYMENT_ID, config, extraData, 4, SALT); + + // Attempt to deploy target4 again. + vm.expectRevert( + IHyperdriveDeployerCoordinator.TargetAlreadyDeployed.selector + ); + coordinator.deployTarget(DEPLOYMENT_ID, config, extraData, 4, SALT); + } + + function test_deployTarget_failure_invalidTargetIndex() external { + // Deploy a target0 instance. + coordinator.deployTarget(DEPLOYMENT_ID, config, extraData, 0, SALT); + + // Attempt to deploy a 5th target instance. + vm.expectRevert( + IHyperdriveDeployerCoordinator.InvalidTargetIndex.selector + ); + coordinator.deployTarget(DEPLOYMENT_ID, config, extraData, 5, SALT); + } + + function test_deployTarget_success() external { + // Deploy a target0 instance. + address target0 = coordinator.deployTarget( + DEPLOYMENT_ID, + config, + extraData, + 0, + SALT + ); + + // Ensure that the deployment was configured correctly. + HyperdriveDeployerCoordinator.Deployment memory deployment = coordinator + .deployments(alice, DEPLOYMENT_ID); + assertEq(deployment.configHash, keccak256(abi.encode(config))); + assertEq(deployment.extraDataHash, keccak256(extraData)); + assertEq(deployment.initialSharePrice, ONE); + assertEq(deployment.target0, address(target0)); + + // Deploy the other target instances. + address[] memory targets = new address[](4); + for (uint256 i = 1; i < 5; i++) { + targets[i - 1] = coordinator.deployTarget( + DEPLOYMENT_ID, + config, + extraData, + i, + SALT + ); + } + + // Ensure that the deployment was configured correctly. + deployment = coordinator.deployments(alice, DEPLOYMENT_ID); + assertEq(deployment.target1, targets[0]); + assertEq(deployment.target2, targets[1]); + assertEq(deployment.target3, targets[2]); + assertEq(deployment.target4, targets[3]); + } + + function test_deploy_hyperdriveAlreadyDeployed() external { + // Deploy the target instances and a Hyperdrive instance. + for (uint256 i = 0; i < 5; i++) { + coordinator.deployTarget(DEPLOYMENT_ID, config, extraData, i, SALT); + } + coordinator.deploy(DEPLOYMENT_ID, config, extraData, SALT); + + // Attempt to deploy a Hyperdrive instance again. + vm.expectRevert( + IHyperdriveDeployerCoordinator.HyperdriveAlreadyDeployed.selector + ); + coordinator.deploy(DEPLOYMENT_ID, config, extraData, SALT); + } + + function test_deploy_deploymentDoesNotExist() external { + // Attempt to deploy a Hyperdrive instance without deploying any of the + // target instances. + vm.expectRevert( + IHyperdriveDeployerCoordinator.DeploymentDoesNotExist.selector + ); + coordinator.deploy(DEPLOYMENT_ID, config, extraData, SALT); + } + + function test_deploy_incompleteDeploymentTarget1() external { + // Deploy all of the target instances except for target1. + for (uint256 i = 0; i < 5; i++) { + if (i == 1) { + continue; + } + coordinator.deployTarget(DEPLOYMENT_ID, config, extraData, i, SALT); + } + + // Attempt to deploy a Hyperdrive instance. + vm.expectRevert( + IHyperdriveDeployerCoordinator.IncompleteDeployment.selector + ); + coordinator.deploy(DEPLOYMENT_ID, config, extraData, SALT); + } + + function test_deploy_incompleteDeploymentTarget2() external { + // Deploy all of the target instances except for target2. + for (uint256 i = 0; i < 5; i++) { + if (i == 2) { + continue; + } + coordinator.deployTarget(DEPLOYMENT_ID, config, extraData, i, SALT); + } + + // Attempt to deploy a Hyperdrive instance. + vm.expectRevert( + IHyperdriveDeployerCoordinator.IncompleteDeployment.selector + ); + coordinator.deploy(DEPLOYMENT_ID, config, extraData, SALT); + } + + function test_deploy_incompleteDeploymentTarget3() external { + // Deploy all of the target instances except for target3. + for (uint256 i = 0; i < 5; i++) { + if (i == 3) { + continue; + } + coordinator.deployTarget(DEPLOYMENT_ID, config, extraData, i, SALT); + } + + // Attempt to deploy a Hyperdrive instance. + vm.expectRevert( + IHyperdriveDeployerCoordinator.IncompleteDeployment.selector + ); + coordinator.deploy(DEPLOYMENT_ID, config, extraData, SALT); + } + + function test_deploy_incompleteDeploymentTarget4() external { + // Deploy all of the target instances except for target4. + for (uint256 i = 0; i < 5; i++) { + if (i == 4) { + continue; + } + coordinator.deployTarget(DEPLOYMENT_ID, config, extraData, i, SALT); + } + + // Attempt to deploy a Hyperdrive instance. + vm.expectRevert( + IHyperdriveDeployerCoordinator.IncompleteDeployment.selector + ); + coordinator.deploy(DEPLOYMENT_ID, config, extraData, SALT); + } + + function test_deploy_mismatchedConfig() external { + // Deploy all of the target instances. + for (uint256 i = 0; i < 5; i++) { + coordinator.deployTarget(DEPLOYMENT_ID, config, extraData, i, SALT); + } + + // Attempt to deploy a Hyperdrive instance with a mismatched config. + config.fees.curve = config.fees.curve + 1; + vm.expectRevert( + IHyperdriveDeployerCoordinator.MismatchedConfig.selector + ); + coordinator.deploy(DEPLOYMENT_ID, config, extraData, SALT); + } + + function test_deploy_mismatchedExtraData() external { + // Deploy all of the target instances. + for (uint256 i = 0; i < 5; i++) { + coordinator.deployTarget(DEPLOYMENT_ID, config, extraData, i, SALT); + } + + // Attempt to deploy a Hyperdrive instance with mismatched extra data. + extraData = abi.encodePacked(extraData, extraData); + vm.expectRevert( + IHyperdriveDeployerCoordinator.MismatchedExtraData.selector + ); + coordinator.deploy(DEPLOYMENT_ID, config, extraData, SALT); + } + + function test_deploy_invalidCheckPoolConfig() external { + // Deploy all of the target instances. + for (uint256 i = 0; i < 5; i++) { + coordinator.deployTarget(DEPLOYMENT_ID, config, extraData, i, SALT); + } + + // Attempt to deploy a Hyperdrive instance with an invalid pool config. + coordinator.setCheckPoolConfigStatus(false); + vm.expectRevert("MockDeployerCoordinator: invalid config"); + coordinator.deploy(DEPLOYMENT_ID, config, extraData, SALT); + } + + function test_deploy_success() external { + // Deploy all of the target instances. + address[] memory targets = new address[](5); + for (uint256 i = 0; i < 5; i++) { + targets[i] = coordinator.deployTarget( + DEPLOYMENT_ID, + config, + extraData, + i, + SALT + ); + } + + // Deploy a Hyperdrive instance. + address hyperdrive = coordinator.deploy( + DEPLOYMENT_ID, + config, + extraData, + SALT + ); + + // Ensure that the deployment was configured correctly. + HyperdriveDeployerCoordinator.Deployment memory deployment = coordinator + .deployments(alice, DEPLOYMENT_ID); + assertEq(deployment.configHash, keccak256(abi.encode(config))); + assertEq(deployment.extraDataHash, keccak256(extraData)); + assertEq(deployment.initialSharePrice, ONE); + assertEq(deployment.target0, targets[0]); + assertEq(deployment.target1, targets[1]); + assertEq(deployment.target2, targets[2]); + assertEq(deployment.target3, targets[3]); + assertEq(deployment.target4, targets[4]); + assertEq(deployment.hyperdrive, hyperdrive); + } +} diff --git a/test/integrations/factory/HyperdriveFactory.t.sol b/test/integrations/factory/HyperdriveFactory.t.sol index ab1f8b2f0..42ecb63ec 100644 --- a/test/integrations/factory/HyperdriveFactory.t.sol +++ b/test/integrations/factory/HyperdriveFactory.t.sol @@ -17,7 +17,7 @@ import { IHyperdriveFactory } from "contracts/src/interfaces/IHyperdriveFactory. import { IHyperdriveDeployerCoordinator } from "contracts/src/interfaces/IHyperdriveDeployerCoordinator.sol"; import { AssetId } from "contracts/src/libraries/AssetId.sol"; import { FixedPointMath, ONE } from "contracts/src/libraries/FixedPointMath.sol"; -import { ForwarderFactory } from "contracts/src/token/ForwarderFactory.sol"; +import { ERC20ForwarderFactory } from "contracts/src/token/ERC20ForwarderFactory.sol"; import { ERC20Mintable } from "contracts/test/ERC20Mintable.sol"; import { MockHyperdriveDeployer, MockHyperdriveTargetDeployer } from "contracts/test/MockHyperdriveDeployer.sol"; import { HyperdriveTest } from "test/utils/HyperdriveTest.sol"; @@ -2155,7 +2155,7 @@ contract HyperdriveFactoryBaseTest is HyperdriveTest { ); address[] memory defaults = new address[](1); defaults[0] = bob; - forwarderFactory = new ForwarderFactory(); + forwarderFactory = new ERC20ForwarderFactory(); factory = new HyperdriveFactory( HyperdriveFactory.FactoryConfig({ governance: alice, @@ -2438,7 +2438,7 @@ contract ERC4626FactoryMultiDeployTest is HyperdriveFactoryBaseTest { // Verify that the correct events were emitted. verifyFactoryEvents( - factory, + deployerCoordinator, hyperdrive1, charlie, CONTRIBUTION, @@ -2538,7 +2538,7 @@ contract ERC4626FactoryMultiDeployTest is HyperdriveFactoryBaseTest { // Verify that the correct events were emitted. verifyFactoryEvents( - factory, + deployerCoordinator1, hyperdrive2, charlie, CONTRIBUTION, @@ -2645,7 +2645,7 @@ contract ERC4626FactoryMultiDeployTest is HyperdriveFactoryBaseTest { // Verify that the correct events were emitted. verifyFactoryEvents( - factory, + deployerCoordinator, hyperdrive3, dan, CONTRIBUTION, diff --git a/test/units/ERC20Forwarder.t.sol b/test/units/ERC20Forwarder.t.sol index b64eaf6b1..9b1e1255d 100644 --- a/test/units/ERC20Forwarder.t.sol +++ b/test/units/ERC20Forwarder.t.sol @@ -5,13 +5,13 @@ import { IERC20Forwarder } from "contracts/src/interfaces/IERC20Forwarder.sol"; import { IMultiToken } from "contracts/src/interfaces/IMultiToken.sol"; import { AssetId } from "contracts/src/libraries/AssetId.sol"; import { ERC20Forwarder } from "contracts/src/token/ERC20Forwarder.sol"; -import { ForwarderFactory } from "contracts/src/token/ForwarderFactory.sol"; +import { ERC20ForwarderFactory } from "contracts/src/token/ERC20ForwarderFactory.sol"; import { MockAssetId } from "contracts/test/MockAssetId.sol"; import { MockMultiToken, IMockMultiToken } from "contracts/test/MockMultiToken.sol"; import { BaseTest } from "test/utils/BaseTest.sol"; import { Lib } from "test/utils/Lib.sol"; -contract ERC20ForwarderFactoryTest is BaseTest { +contract ERC20ERC20ForwarderFactoryTest is BaseTest { using Lib for *; IMockMultiToken multiToken; @@ -25,7 +25,7 @@ contract ERC20ForwarderFactoryTest is BaseTest { function setUp() public override { super.setUp(); vm.startPrank(deployer); - forwarderFactory = new ForwarderFactory(); + forwarderFactory = new ERC20ForwarderFactory(); bytes32 codeHash = keccak256(type(ERC20Forwarder).creationCode); multiToken = IMockMultiToken( address(new MockMultiToken(codeHash, address(forwarderFactory))) @@ -49,7 +49,7 @@ contract ERC20ForwarderFactoryTest is BaseTest { assertEq(forwarder.balanceOf(bob), AMOUNT); } - function testForwarderFactory() public { + function testERC20ForwarderFactory() public { (IMultiToken token, uint256 tokenID) = forwarderFactory .getDeployDetails(); assertEq(address(token), address((IMultiToken(address(1))))); diff --git a/test/units/MultiToken.t.sol b/test/units/MultiToken.t.sol index 5068e84f5..5e3bf822d 100644 --- a/test/units/MultiToken.t.sol +++ b/test/units/MultiToken.t.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.20; import { IHyperdrive } from "contracts/src/interfaces/IHyperdrive.sol"; import { AssetId } from "contracts/src/libraries/AssetId.sol"; -import { ForwarderFactory } from "contracts/src/token/ForwarderFactory.sol"; +import { ERC20ForwarderFactory } from "contracts/src/token/ERC20ForwarderFactory.sol"; import { MockAssetId } from "contracts/test/MockAssetId.sol"; import { MockMultiToken, IMockMultiToken } from "contracts/test/MockMultiToken.sol"; import { BaseTest } from "test/utils/BaseTest.sol"; @@ -21,7 +21,7 @@ contract MultiTokenTest is BaseTest { function setUp() public override { super.setUp(); vm.startPrank(deployer); - forwarderFactory = new ForwarderFactory(); + forwarderFactory = new ERC20ForwarderFactory(); multiToken = IMockMultiToken( address(new MockMultiToken(bytes32(0), address(forwarderFactory))) ); diff --git a/test/units/hyperdrive/Admin.t.sol b/test/units/hyperdrive/Admin.t.sol new file mode 100644 index 000000000..c1c34d4b4 --- /dev/null +++ b/test/units/hyperdrive/Admin.t.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.20; + +import { IHyperdrive } from "contracts/src/interfaces/IHyperdrive.sol"; +import { HyperdriveTest } from "test/utils/HyperdriveTest.sol"; + +contract AdminTest is HyperdriveTest { + function test_pause_failure_unauthorized() external { + // Ensure that an unauthorized user cannot pause the contract. + vm.stopPrank(); + vm.startPrank(alice); + vm.expectRevert(IHyperdrive.Unauthorized.selector); + hyperdrive.pause(true); + } + + function test_pause_success() external { + // Ensure that an authorized pauser can pause the contract. + vm.stopPrank(); + vm.startPrank(pauser); + vm.expectEmit(true, true, true, true); + emit PauseStatusUpdated(true); + hyperdrive.pause(true); + + // Ensure that the pause status was updated. + assert(hyperdrive.getMarketState().isPaused); + } + + function test_setGovernance_failure_unauthorized() external { + // Ensure that an unauthorized user cannot set the governance address. + vm.stopPrank(); + vm.startPrank(alice); + vm.expectRevert(IHyperdrive.Unauthorized.selector); + hyperdrive.setGovernance(alice); + } + + function test_setGovernance_success() external { + address newGovernance = alice; + + // Ensure that governance can set the governance address. + vm.stopPrank(); + vm.startPrank(hyperdrive.getPoolConfig().governance); + vm.expectEmit(true, true, true, true); + emit GovernanceUpdated(newGovernance); + hyperdrive.setGovernance(newGovernance); + + // Ensure that the governance address was updated. + assertEq(hyperdrive.getPoolConfig().governance, newGovernance); + } + + function test_setPauser_failure_unauthorized() external { + // Ensure that an unauthorized user cannot set a pauser address. + vm.stopPrank(); + vm.startPrank(alice); + vm.expectRevert(IHyperdrive.Unauthorized.selector); + hyperdrive.setPauser(alice, true); + } + + function test_setPauser_success() external { + address newPauser = alice; + + // Ensure that governance can set the governance address. + vm.stopPrank(); + vm.startPrank(hyperdrive.getPoolConfig().governance); + vm.expectEmit(true, true, true, true); + emit PauserUpdated(newPauser); + hyperdrive.setPauser(newPauser, true); + + // Ensure that the pauser address was updated. + assert(hyperdrive.isPauser(newPauser)); + } +} diff --git a/test/units/hyperdrive/CloseLongTest.t.sol b/test/units/hyperdrive/CloseLongTest.t.sol index 775d7c029..68ff2ecc9 100644 --- a/test/units/hyperdrive/CloseLongTest.t.sol +++ b/test/units/hyperdrive/CloseLongTest.t.sol @@ -63,7 +63,7 @@ contract CloseLongTest is HyperdriveTest { // Attempt to close too many longs. This should fail. vm.stopPrank(); vm.startPrank(bob); - vm.expectRevert(stdError.arithmeticError); + vm.expectRevert(IHyperdrive.InsufficientBalance.selector); hyperdrive.closeLong( maturityTime, bondAmount + 1, diff --git a/test/units/hyperdrive/CloseShortTest.t.sol b/test/units/hyperdrive/CloseShortTest.t.sol index cc31b3534..b8db0c742 100644 --- a/test/units/hyperdrive/CloseShortTest.t.sol +++ b/test/units/hyperdrive/CloseShortTest.t.sol @@ -62,7 +62,7 @@ contract CloseShortTest is HyperdriveTest { // Attempt to close too many shorts. This should fail. vm.stopPrank(); vm.startPrank(bob); - vm.expectRevert(stdError.arithmeticError); + vm.expectRevert(IHyperdrive.InsufficientBalance.selector); hyperdrive.closeShort( maturityTime, bondAmount + 1, diff --git a/test/units/hyperdrive/RemoveLiquidityTest.t.sol b/test/units/hyperdrive/RemoveLiquidityTest.t.sol index 3e8bad474..ef1b563b7 100644 --- a/test/units/hyperdrive/RemoveLiquidityTest.t.sol +++ b/test/units/hyperdrive/RemoveLiquidityTest.t.sol @@ -58,7 +58,7 @@ contract RemoveLiquidityTest is HyperdriveTest { // Alice attempts to remove 0 lp shares. vm.stopPrank(); vm.startPrank(alice); - vm.expectRevert(stdError.arithmeticError); + vm.expectRevert(IHyperdrive.InsufficientBalance.selector); hyperdrive.removeLiquidity( lpShares + 1, 0, diff --git a/test/units/libraries/HyperdriveMath.t.sol b/test/units/libraries/HyperdriveMath.t.sol index b7888bf03..6b118f081 100644 --- a/test/units/libraries/HyperdriveMath.t.sol +++ b/test/units/libraries/HyperdriveMath.t.sol @@ -5,7 +5,7 @@ import { IHyperdrive } from "contracts/src/interfaces/IHyperdrive.sol"; import { FixedPointMath, ONE } from "contracts/src/libraries/FixedPointMath.sol"; import { HyperdriveMath } from "contracts/src/libraries/HyperdriveMath.sol"; import { YieldSpaceMath } from "contracts/src/libraries/HyperdriveMath.sol"; -import { ForwarderFactory } from "contracts/src/token/ForwarderFactory.sol"; +import { ERC20ForwarderFactory } from "contracts/src/token/ERC20ForwarderFactory.sol"; import { IMockHyperdrive } from "contracts/test/MockHyperdrive.sol"; import { MockHyperdriveMath } from "contracts/test/MockHyperdriveMath.sol"; import { HyperdriveUtils } from "test/utils/HyperdriveUtils.sol"; diff --git a/test/utils/BaseTest.sol b/test/utils/BaseTest.sol index a79c7ffed..c0ea596ce 100644 --- a/test/utils/BaseTest.sol +++ b/test/utils/BaseTest.sol @@ -4,10 +4,10 @@ pragma solidity 0.8.20; import { console2 } from "forge-std/console2.sol"; import { Test } from "forge-std/Test.sol"; import { IERC20 } from "contracts/src/interfaces/IERC20.sol"; -import { ForwarderFactory } from "contracts/src/token/ForwarderFactory.sol"; +import { ERC20ForwarderFactory } from "contracts/src/token/ERC20ForwarderFactory.sol"; contract BaseTest is Test { - ForwarderFactory forwarderFactory; + ERC20ForwarderFactory forwarderFactory; address alice; address bob; diff --git a/test/utils/HyperdriveTest.sol b/test/utils/HyperdriveTest.sol index a04d4d163..aca059599 100644 --- a/test/utils/HyperdriveTest.sol +++ b/test/utils/HyperdriveTest.sol @@ -11,7 +11,7 @@ import { FixedPointMath, ONE } from "contracts/src/libraries/FixedPointMath.sol" import { HyperdriveMath } from "contracts/src/libraries/HyperdriveMath.sol"; import { LPMath } from "contracts/src/libraries/LPMath.sol"; import { YieldSpaceMath } from "contracts/src/libraries/YieldSpaceMath.sol"; -import { ForwarderFactory } from "contracts/src/token/ForwarderFactory.sol"; +import { ERC20ForwarderFactory } from "contracts/src/token/ERC20ForwarderFactory.sol"; import { ERC20Mintable } from "contracts/test/ERC20Mintable.sol"; import { MockHyperdrive, MockHyperdriveTarget0, MockHyperdriveTarget1 } from "contracts/test/MockHyperdrive.sol"; import { BaseTest } from "test/utils/BaseTest.sol"; @@ -886,20 +886,14 @@ contract HyperdriveTest is IHyperdriveEvents, BaseTest { /// Event Utils /// event Deployed( - uint256 indexed version, + address indexed deployerCoordinator, address hyperdrive, IHyperdrive.PoolDeployConfig config, bytes extraData ); - event CollectGovernanceFee( - address indexed collector, - uint256 baseFees, - uint256 vaultSharePrice - ); - function verifyFactoryEvents( - HyperdriveFactory factory, + address deployerCoordinator, IHyperdrive _hyperdrive, address deployer, uint256 contribution, @@ -922,7 +916,7 @@ contract HyperdriveTest is IHyperdriveEvents, BaseTest { assertEq(filteredLogs[0].topics[0], Deployed.selector); assertEq( uint256(filteredLogs[0].topics[1]), - factory.versionCounter() + uint256(uint160(deployerCoordinator)) ); // Verify the event data.