diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index e4be2afb8..9ba65f7a8 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -29,7 +29,7 @@ jobs: - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 with: - version: nightly + version: nightly-75fc63be4fc9241a1981a55c12b6e300fd82a51b - name: Install Dependencies run: forge install - uses: actions/setup-python@v4 diff --git a/contracts/src/deployers/savings-usds-l2/SavingsUSDSL2HyperdriveCoreDeployer.sol b/contracts/src/deployers/savings-usds-l2/SavingsUSDSL2HyperdriveCoreDeployer.sol new file mode 100644 index 000000000..a12e0640e --- /dev/null +++ b/contracts/src/deployers/savings-usds-l2/SavingsUSDSL2HyperdriveCoreDeployer.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.24; + +import { IHyperdrive } from "../../interfaces/IHyperdrive.sol"; +import { IHyperdriveAdminController } from "../../interfaces/IHyperdriveAdminController.sol"; +import { IHyperdriveCoreDeployer } from "../../interfaces/IHyperdriveCoreDeployer.sol"; +import { IPSM } from "../../interfaces/IPSM.sol"; +import { SavingsUSDSL2Hyperdrive } from "../../instances/savings-usds-l2/SavingsUSDSL2Hyperdrive.sol"; + +/// @author DELV +/// @title SavingsUSDSL2HyperdriveCoreDeployer +/// @notice The core deployer for the SavingsUSDSL2Hyperdrive implementation. +/// @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 SavingsUSDSL2HyperdriveCoreDeployer is IHyperdriveCoreDeployer { + /// @notice Deploys a Hyperdrive instance with the given parameters. + /// @param __name The name of the Hyperdrive pool. + /// @param _config The configuration of the Hyperdrive pool. + /// @param _adminController The admin controller that will specify the + /// admin parameters for this instance. + /// @param _target0 The target0 address. + /// @param _target1 The target1 address. + /// @param _target2 The target2 address. + /// @param _target3 The target3 address. + /// @param _target4 The target4 address. + /// @param _salt The create2 salt used in the deployment. + /// @return The address of the newly deployed SavingsUSDSL2Hyperdrive instance. + function deployHyperdrive( + string memory __name, + IHyperdrive.PoolConfig memory _config, + IHyperdriveAdminController _adminController, + bytes memory _extraData, + address _target0, + address _target1, + address _target2, + address _target3, + address _target4, + bytes32 _salt + ) external returns (address) { + // The PSM contract. This is where the base token will be swapped + // for shares. + require(_extraData.length >= 20, "Invalid _extraData length"); + IPSM PSM = abi.decode(_extraData, (IPSM)); + + return ( + address( + // NOTE: We hash the sender with the salt to prevent the + // front-running of deployments. + new SavingsUSDSL2Hyperdrive{ + salt: keccak256(abi.encode(msg.sender, _salt)) + }( + __name, + _config, + _adminController, + _target0, + _target1, + _target2, + _target3, + _target4, + PSM + ) + ) + ); + } +} diff --git a/contracts/src/deployers/savings-usds-l2/SavingsUSDSL2HyperdriveDeployerCoordinator.sol b/contracts/src/deployers/savings-usds-l2/SavingsUSDSL2HyperdriveDeployerCoordinator.sol new file mode 100644 index 000000000..d524ab4d7 --- /dev/null +++ b/contracts/src/deployers/savings-usds-l2/SavingsUSDSL2HyperdriveDeployerCoordinator.sol @@ -0,0 +1,174 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.24; + +import { ERC20 } from "openzeppelin/token/ERC20/ERC20.sol"; +import { SafeERC20 } from "openzeppelin/token/ERC20/utils/SafeERC20.sol"; +import { SavingsUSDSL2Conversions } from "../../instances/savings-usds-l2/SavingsUSDSL2Conversions.sol"; +import { IHyperdrive } from "../../interfaces/IHyperdrive.sol"; +import { IPSM } from "../../interfaces/IPSM.sol"; +import { IHyperdriveDeployerCoordinator } from "../../interfaces/IHyperdriveDeployerCoordinator.sol"; +import { SAVINGS_USDS_L2_HYPERDRIVE_DEPLOYER_COORDINATOR_KIND } from "../../libraries/Constants.sol"; +import { ONE } from "../../libraries/FixedPointMath.sol"; +import { HyperdriveDeployerCoordinator } from "../HyperdriveDeployerCoordinator.sol"; + +/// @author DELV +/// @title SavingsUSDSL2HyperdriveDeployerCoordinator +/// @notice The deployer coordinator for the SavingsUSDSL2Hyperdrive +/// implementation. +/// @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 SavingsUSDSL2HyperdriveDeployerCoordinator is + HyperdriveDeployerCoordinator +{ + using SafeERC20 for ERC20; + + /// @notice The deployer coordinator's kind. + string public constant override kind = + SAVINGS_USDS_L2_HYPERDRIVE_DEPLOYER_COORDINATOR_KIND; + + /// @notice Instantiates the deployer coordinator. + /// @param _name The deployer coordinator's name. + /// @param _factory The factory that this deployer will be registered with. + /// @param _coreDeployer The core deployer. + /// @param _target0Deployer The target0 deployer. + /// @param _target1Deployer The target1 deployer. + /// @param _target2Deployer The target2 deployer. + /// @param _target3Deployer The target3 deployer. + /// @param _target4Deployer The target4 deployer. + constructor( + string memory _name, + address _factory, + address _coreDeployer, + address _target0Deployer, + address _target1Deployer, + address _target2Deployer, + address _target3Deployer, + address _target4Deployer + ) + HyperdriveDeployerCoordinator( + _name, + _factory, + _coreDeployer, + _target0Deployer, + _target1Deployer, + _target2Deployer, + _target3Deployer, + _target4Deployer + ) + {} + + /// @dev Prepares the coordinator for initialization by drawing funds from + /// the LP, if necessary. + /// @param _hyperdrive The Hyperdrive instance that is being initialized. + /// @param _lp The LP that is initializing the pool. + /// @param _contribution The amount of capital to supply. The units of this + /// quantity are either base or vault shares, depending on the value + /// of `_options.asBase`. + /// @param _options The options that configure how the initialization is + /// settled. + /// @return value The value that should be sent in the initialize transaction. + function _prepareInitialize( + IHyperdrive _hyperdrive, + address _lp, + uint256 _contribution, + IHyperdrive.Options memory _options + ) internal override returns (uint256 value) { + // If base is the deposit asset, the initialization will be paid in the + // base token. + address token; + if (_options.asBase) { + token = _hyperdrive.baseToken(); + } + // Otherwise, the initialization will be paid in vault shares. + else { + token = _hyperdrive.vaultSharesToken(); + } + + // Take custody of the contribution and approve Hyperdrive to pull the + // tokens. + ERC20(token).safeTransferFrom(_lp, address(this), _contribution); + ERC20(token).forceApprove(address(_hyperdrive), _contribution); + + return value; + } + + /// @notice Convert an amount of vault shares to an amount of base. + /// @param _shareAmount The vault shares amount. + /// @return The base amount. + function convertToBase( + IPSM _PSM, + uint256 _shareAmount + ) public view returns (uint256) { + return SavingsUSDSL2Conversions.convertToBase(_PSM, _shareAmount); + } + + /// @notice Convert an amount of base to an amount of vault shares. + /// @param _baseAmount The base amount. + /// @return The vault shares amount. + function convertToShares( + IPSM _PSM, + uint256 _baseAmount + ) public view returns (uint256) { + return SavingsUSDSL2Conversions.convertToShares(_PSM, _baseAmount); + } + + /// @dev We override the message value check since this integration is + /// not payable. + function _checkMessageValue() internal view override { + if (msg.value != 0) { + revert IHyperdriveDeployerCoordinator.NotPayable(); + } + } + + /// @notice Checks the pool configuration to ensure that it is valid. + /// @param _deployConfig The deploy configuration of the Hyperdrive pool. + /// @param _extraData The extra data containing the PSM address. + function _checkPoolConfig( + IHyperdrive.PoolDeployConfig memory _deployConfig, + bytes memory _extraData + ) internal view override { + // The Sky PSM contract. This is where the base token will be + // swapped for shares. + require(_extraData.length >= 20, "Invalid _extraData length"); + IPSM PSM = abi.decode(_extraData, (IPSM)); + + // Perform the default checks. + super._checkPoolConfig(_deployConfig, _extraData); + + // Ensure that the vault shares token address is properly configured. + if (address(_deployConfig.vaultSharesToken) != address(PSM.susds())) { + revert IHyperdriveDeployerCoordinator.InvalidVaultSharesToken(); + } + + // Ensure that the base token address is properly configured. + if (address(_deployConfig.baseToken) != address(PSM.usds())) { + revert IHyperdriveDeployerCoordinator.InvalidBaseToken(); + } + + // Ensure that the minimum share reserves are equal to 1e15. This value + // has been tested to prevent arithmetic overflows in the + // `_updateLiquidity` function. + if (_deployConfig.minimumShareReserves != 1e15) { + revert IHyperdriveDeployerCoordinator.InvalidMinimumShareReserves(); + } + + // Ensure that the minimum transaction amount are equal to 1e15. This + // value has been tested to prevent precision issues. + if (_deployConfig.minimumTransactionAmount != 1e15) { + revert IHyperdriveDeployerCoordinator + .InvalidMinimumTransactionAmount(); + } + } + + /// @dev Gets the initial vault share price of the Hyperdrive pool. + /// @return The initial vault share price of the Hyperdrive pool. + function _getInitialVaultSharePrice( + IHyperdrive.PoolDeployConfig memory, // unused _deployConfig + bytes memory _extraData + ) internal view override returns (uint256) { + require(_extraData.length >= 20, "Invalid _extraData length"); + IPSM _PSM = abi.decode(_extraData, (IPSM)); + return convertToBase(_PSM, ONE); + } +} diff --git a/contracts/src/deployers/savings-usds-l2/SavingsUSDSL2Target0Deployer.sol b/contracts/src/deployers/savings-usds-l2/SavingsUSDSL2Target0Deployer.sol new file mode 100644 index 000000000..9bd6ccfae --- /dev/null +++ b/contracts/src/deployers/savings-usds-l2/SavingsUSDSL2Target0Deployer.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.24; + +import { SavingsUSDSL2Target0 } from "../../instances/savings-usds-l2/SavingsUSDSL2Target0.sol"; +import { IHyperdrive } from "../../interfaces/IHyperdrive.sol"; +import { IHyperdriveAdminController } from "../../interfaces/IHyperdriveAdminController.sol"; +import { IHyperdriveTargetDeployer } from "../../interfaces/IHyperdriveTargetDeployer.sol"; +import { IPSM } from "../../interfaces/IPSM.sol"; + +/// @author DELV +/// @title SavingsUSDSL2Target0Deployer +/// @notice The target0 deployer for the SavingsUSDSL2Hyperdrive implementation. +/// @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 SavingsUSDSL2Target0Deployer is IHyperdriveTargetDeployer { + /// @notice Deploys a target0 instance with the given parameters. + /// @param _config The configuration of the Hyperdrive pool. + /// @param _adminController The admin controller that will specify the + /// admin parameters for this instance. + /// @param _salt The create2 salt used in the deployment. + /// @return The address of the newly deployed SavingsUSDSL2Target0 instance. + function deployTarget( + IHyperdrive.PoolConfig memory _config, + IHyperdriveAdminController _adminController, + bytes memory _extraData, + bytes32 _salt + ) external returns (address) { + // The PSM contract. This is where the base token will be swapped + // for shares. + require(_extraData.length >= 20, "Invalid _extraData length"); + IPSM PSM = abi.decode(_extraData, (IPSM)); + + return + address( + // NOTE: We hash the sender with the salt to prevent the + // front-running of deployments. + new SavingsUSDSL2Target0{ + salt: keccak256(abi.encode(msg.sender, _salt)) + }(_config, _adminController, PSM) + ); + } +} diff --git a/contracts/src/deployers/savings-usds-l2/SavingsUSDSL2Target1Deployer.sol b/contracts/src/deployers/savings-usds-l2/SavingsUSDSL2Target1Deployer.sol new file mode 100644 index 000000000..bc3e861f6 --- /dev/null +++ b/contracts/src/deployers/savings-usds-l2/SavingsUSDSL2Target1Deployer.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.24; + +import { SavingsUSDSL2Target1 } from "../../instances/savings-usds-l2/SavingsUSDSL2Target1.sol"; +import { IHyperdrive } from "../../interfaces/IHyperdrive.sol"; +import { IHyperdriveAdminController } from "../../interfaces/IHyperdriveAdminController.sol"; +import { IHyperdriveTargetDeployer } from "../../interfaces/IHyperdriveTargetDeployer.sol"; +import { IPSM } from "../../interfaces/IPSM.sol"; + +/// @author DELV +/// @title SavingsUSDSL2Target1Deployer +/// @notice The target1 deployer for the SavingsUSDSL2Hyperdrive implementation. +/// @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 SavingsUSDSL2Target1Deployer is IHyperdriveTargetDeployer { + /// @notice Deploys a target1 instance with the given parameters. + /// @param _config The configuration of the Hyperdrive pool. + /// @param _adminController The admin controller that will specify the + /// admin parameters for this instance. + /// @param _salt The create2 salt used in the deployment. + /// @return The address of the newly deployed SavingsUSDSL2Target1 instance. + function deployTarget( + IHyperdrive.PoolConfig memory _config, + IHyperdriveAdminController _adminController, + bytes memory _extraData, + bytes32 _salt + ) external returns (address) { + // The PSM contract. This is where the base token will be swapped + // for shares. + require(_extraData.length >= 20, "Invalid _extraData length"); + IPSM PSM = abi.decode(_extraData, (IPSM)); + + return + address( + // NOTE: We hash the sender with the salt to prevent the + // front-running of deployments. + new SavingsUSDSL2Target1{ + salt: keccak256(abi.encode(msg.sender, _salt)) + }(_config, _adminController, PSM) + ); + } +} diff --git a/contracts/src/deployers/savings-usds-l2/SavingsUSDSL2Target2Deployer.sol b/contracts/src/deployers/savings-usds-l2/SavingsUSDSL2Target2Deployer.sol new file mode 100644 index 000000000..cbb5557d2 --- /dev/null +++ b/contracts/src/deployers/savings-usds-l2/SavingsUSDSL2Target2Deployer.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.24; + +import { SavingsUSDSL2Target2 } from "../../instances/savings-usds-l2/SavingsUSDSL2Target2.sol"; +import { IHyperdrive } from "../../interfaces/IHyperdrive.sol"; +import { IHyperdriveAdminController } from "../../interfaces/IHyperdriveAdminController.sol"; +import { IHyperdriveTargetDeployer } from "../../interfaces/IHyperdriveTargetDeployer.sol"; +import { IPSM } from "../../interfaces/IPSM.sol"; + +/// @author DELV +/// @title SavingsUSDSL2Target2Deployer +/// @notice The target2 deployer for the SavingsUSDSL2Hyperdrive implementation. +/// @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 SavingsUSDSL2Target2Deployer is IHyperdriveTargetDeployer { + /// @notice Deploys a target2 instance with the given parameters. + /// @param _config The configuration of the Hyperdrive pool. + /// @param _adminController The admin controller that will specify the + /// admin parameters for this instance. + /// @param _salt The create2 salt used in the deployment. + /// @return The address of the newly deployed SavingsUSDSL2Target2 instance. + function deployTarget( + IHyperdrive.PoolConfig memory _config, + IHyperdriveAdminController _adminController, + bytes memory _extraData, + bytes32 _salt + ) external returns (address) { + // The PSM contract. This is where the base token will be swapped + // for shares. + require(_extraData.length >= 20, "Invalid _extraData length"); + IPSM PSM = abi.decode(_extraData, (IPSM)); + + return + address( + // NOTE: We hash the sender with the salt to prevent the + // front-running of deployments. + new SavingsUSDSL2Target2{ + salt: keccak256(abi.encode(msg.sender, _salt)) + }(_config, _adminController, PSM) + ); + } +} diff --git a/contracts/src/deployers/savings-usds-l2/SavingsUSDSL2Target3Deployer.sol b/contracts/src/deployers/savings-usds-l2/SavingsUSDSL2Target3Deployer.sol new file mode 100644 index 000000000..8648d5799 --- /dev/null +++ b/contracts/src/deployers/savings-usds-l2/SavingsUSDSL2Target3Deployer.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.24; + +import { SavingsUSDSL2Target3 } from "../../instances/savings-usds-l2/SavingsUSDSL2Target3.sol"; +import { IHyperdrive } from "../../interfaces/IHyperdrive.sol"; +import { IHyperdriveAdminController } from "../../interfaces/IHyperdriveAdminController.sol"; +import { IHyperdriveTargetDeployer } from "../../interfaces/IHyperdriveTargetDeployer.sol"; +import { IPSM } from "../../interfaces/IPSM.sol"; + +/// @author DELV +/// @title SavingsUSDSL2Target3Deployer +/// @notice The target3 deployer for the SavingsUSDSL2Hyperdrive implementation. +/// @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 SavingsUSDSL2Target3Deployer is IHyperdriveTargetDeployer { + /// @notice Deploys a target3 instance with the given parameters. + /// @param _config The configuration of the Hyperdrive pool. + /// @param _adminController The admin controller that will specify the + /// admin parameters for this instance. + /// @param _salt The create2 salt used in the deployment. + /// @return The address of the newly deployed SavingsUSDSL2Target3 instance. + function deployTarget( + IHyperdrive.PoolConfig memory _config, + IHyperdriveAdminController _adminController, + bytes memory _extraData, + bytes32 _salt + ) external returns (address) { + // The PSM contract. This is where the base token will be swapped + // for shares. + require(_extraData.length >= 20, "Invalid _extraData length"); + IPSM PSM = abi.decode(_extraData, (IPSM)); + + return + address( + // NOTE: We hash the sender with the salt to prevent the + // front-running of deployments. + new SavingsUSDSL2Target3{ + salt: keccak256(abi.encode(msg.sender, _salt)) + }(_config, _adminController, PSM) + ); + } +} diff --git a/contracts/src/deployers/savings-usds-l2/SavingsUSDSL2Target4Deployer.sol b/contracts/src/deployers/savings-usds-l2/SavingsUSDSL2Target4Deployer.sol new file mode 100644 index 000000000..1a88dc26a --- /dev/null +++ b/contracts/src/deployers/savings-usds-l2/SavingsUSDSL2Target4Deployer.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.24; + +import { SavingsUSDSL2Target4 } from "../../instances/savings-usds-l2/SavingsUSDSL2Target4.sol"; +import { IHyperdrive } from "../../interfaces/IHyperdrive.sol"; +import { IHyperdriveAdminController } from "../../interfaces/IHyperdriveAdminController.sol"; +import { IHyperdriveTargetDeployer } from "../../interfaces/IHyperdriveTargetDeployer.sol"; +import { IPSM } from "../../interfaces/IPSM.sol"; + +/// @author DELV +/// @title SavingsUSDSL2Target4Deployer +/// @notice The target4 deployer for the SavingsUSDSL2Hyperdrive implementation. +/// @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 SavingsUSDSL2Target4Deployer is IHyperdriveTargetDeployer { + /// @notice Deploys a target4 instance with the given parameters. + /// @param _config The configuration of the Hyperdrive pool. + /// @param _adminController The admin controller that will specify the + /// admin parameters for this instance. + /// @param _salt The create2 salt used in the deployment. + /// @return The address of the newly deployed SavingsUSDSL2Target4 instance. + function deployTarget( + IHyperdrive.PoolConfig memory _config, + IHyperdriveAdminController _adminController, + bytes memory _extraData, + bytes32 _salt + ) external returns (address) { + // The PSM contract. This is where the base token will be swapped + // for shares. + require(_extraData.length >= 20, "Invalid _extraData length"); + IPSM PSM = abi.decode(_extraData, (IPSM)); + + return + address( + // NOTE: We hash the sender with the salt to prevent the + // front-running of deployments. + new SavingsUSDSL2Target4{ + salt: keccak256(abi.encode(msg.sender, _salt)) + }(_config, _adminController, PSM) + ); + } +} diff --git a/contracts/src/instances/savings-usds-l2/SavingsUSDSL2Base.sol b/contracts/src/instances/savings-usds-l2/SavingsUSDSL2Base.sol new file mode 100644 index 000000000..7e931cfdc --- /dev/null +++ b/contracts/src/instances/savings-usds-l2/SavingsUSDSL2Base.sol @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.24; + +import { ERC20 } from "openzeppelin/token/ERC20/ERC20.sol"; +import { SafeERC20 } from "openzeppelin/token/ERC20/utils/SafeERC20.sol"; +import { IPSM } from "../../interfaces/IPSM.sol"; +import { IHyperdrive } from "../../interfaces/IHyperdrive.sol"; +import { HyperdriveBase } from "../../internal/HyperdriveBase.sol"; +import { SavingsUSDSL2Conversions } from "../../instances/savings-usds-l2/SavingsUSDSL2Conversions.sol"; + +/// @author DELV +/// @title SavingsUSDSL2Base +/// @notice The base contract for the SavingsUSDSL2 Hyperdrive implementation. +/// @dev This Hyperdrive implementation is designed to work with standard +/// SavingsUSDSL2 vaults. Non-standard implementations may not work correctly +/// and should be carefully checked. +/// @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. +abstract contract SavingsUSDSL2Base is HyperdriveBase { + using SafeERC20 for ERC20; + + IPSM internal immutable _PSM; + + /// @notice instantiates the SavingsUSDSL2Base contract. + /// @param __PSM The PSM contract. + constructor(IPSM __PSM) { + // Initialize the PSM immutable. + _PSM = __PSM; + } + + /// Yield Source /// + + /// @dev Accepts a deposit from the user in base. + /// @param _baseAmount The base amount to deposit. + /// @return The shares that were minted in the deposit. + /// @return The amount of ETH to refund. Since this yield source isn't + /// payable, this is always zero. + function _depositWithBase( + uint256 _baseAmount, + bytes calldata // unused + ) internal override returns (uint256, uint256) { + // Take custody of the deposit in base. + ERC20(address(_baseToken)).safeTransferFrom( + msg.sender, + address(this), + _baseAmount + ); + + // Deposit the base into the yield source. + // + // NOTE: We increase the required approval amount by 1 wei so that + // the vault ends with an approval of 1 wei. This makes future + // approvals cheaper by keeping the storage slot warm. + ERC20(address(_baseToken)).forceApprove(address(_PSM), _baseAmount + 1); + + // Depositing amounts to swapping USDS for SUSDS in the PSM. + uint256 sharesMinted = _PSM.swapExactIn( + address(_baseToken), + address(_vaultSharesToken), + _baseAmount, + _convertToShares(_baseAmount), + address(this), + 0 + ); + + return (sharesMinted, 0); + } + + /// @dev Process a deposit in vault shares. + /// @param _shareAmount The vault shares amount to deposit. + function _depositWithShares( + uint256 _shareAmount, + bytes calldata // unused _extraData + ) internal override { + ERC20(address(_vaultSharesToken)).safeTransferFrom( + msg.sender, + address(this), + _shareAmount + ); + } + + /// @dev Process a withdrawal in base and send the proceeds to the + /// destination. + + /// @param _shareAmount The amount of vault shares to withdraw. + /// @param _destination The destination of the withdrawal. + /// @return amountWithdrawn The amount of base withdrawn. + function _withdrawWithBase( + uint256 _shareAmount, + address _destination, + bytes calldata // unused + ) internal override returns (uint256 amountWithdrawn) { + // Withdrawing amounts to swapping SUSDS back for USDS in the PSM. + ERC20(address(_vaultSharesToken)).forceApprove( + address(_PSM), + _shareAmount + ); + amountWithdrawn = _PSM.swapExactIn( + address(_vaultSharesToken), + address(_baseToken), + _shareAmount, + _convertToBase(_shareAmount), + _destination, + 0 + ); + + return amountWithdrawn; + } + + /// @dev Process a withdrawal in vault shares and send the proceeds to the + /// destination. + /// @param _shareAmount The amount of vault shares to withdraw. + /// @param _destination The destination of the withdrawal. + function _withdrawWithShares( + uint256 _shareAmount, + address _destination, + bytes calldata // unused + ) internal override { + // Transfer vault shares to the destination. + ERC20(address(_vaultSharesToken)).safeTransfer( + _destination, + _shareAmount + ); + } + + /// @dev Convert an amount of vault shares to an amount of base. + /// @param _shareAmount The vault shares amount. + /// @return The base amount. + function _convertToBase( + uint256 _shareAmount + ) internal view override returns (uint256) { + return SavingsUSDSL2Conversions.convertToBase(_PSM, _shareAmount); + } + + /// @dev Convert an amount of base to an amount of vault shares. + /// @param _baseAmount The base amount. + /// @return The vault shares amount. + function _convertToShares( + uint256 _baseAmount + ) internal view override returns (uint256) { + return SavingsUSDSL2Conversions.convertToShares(_PSM, _baseAmount); + } + + /// @dev Gets the total amount of shares held by the pool in the yield + /// source. + /// @return shareAmount The total amount of shares. + function _totalShares() + internal + view + override + returns (uint256 shareAmount) + { + return _vaultSharesToken.balanceOf(address(this)); + } + + /// @dev We override the message value check since this integration is + /// not payable. + function _checkMessageValue() internal view override { + if (msg.value != 0) { + revert IHyperdrive.NotPayable(); + } + } +} diff --git a/contracts/src/instances/savings-usds-l2/SavingsUSDSL2Conversions.sol b/contracts/src/instances/savings-usds-l2/SavingsUSDSL2Conversions.sol new file mode 100644 index 000000000..dfbef159d --- /dev/null +++ b/contracts/src/instances/savings-usds-l2/SavingsUSDSL2Conversions.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.24; + +import { FixedPointMath } from "../../libraries/FixedPointMath.sol"; +import { IPSM } from "../../interfaces/IPSM.sol"; +import { IRateProvider } from "../../interfaces/IRateProvider.sol"; + +/// @author DELV +/// @title SavingsUSDSL2Conversions +/// @notice The conversion logic for the SavingsUSDSL2 Hyperdrive integration. +/// @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. +library SavingsUSDSL2Conversions { + using FixedPointMath for uint256; + /// @dev Convert an amount of vault shares to an amount of base. + /// @param _PSM The PSM contract. + /// @param _shareAmount The vault shares amount. + /// @return The base amount. + function convertToBase( + IPSM _PSM, + uint256 _shareAmount + ) internal view returns (uint256) { + /// Sky's internal accounting uses RAY units (1e27), so we want to + /// ensure that we're using the same precision. + return + _shareAmount.mulDivDown( + IRateProvider(_PSM.rateProvider()).getConversionRate(), + 1e27 + ); + } + + /// @dev Convert an amount of base to an amount of vault shares. + /// @param _PSM The PSM contract. + /// @param _baseAmount The base amount. + /// @return The vault shares amount. + function convertToShares( + IPSM _PSM, + uint256 _baseAmount + ) internal view returns (uint256) { + /// Sky's internal accounting uses RAY units (1e27), so we want to + /// ensure that we're using the same precision. + return + _baseAmount.mulDivDown( + 1e27, + IRateProvider(_PSM.rateProvider()).getConversionRate() + ); + } +} diff --git a/contracts/src/instances/savings-usds-l2/SavingsUSDSL2Hyperdrive.sol b/contracts/src/instances/savings-usds-l2/SavingsUSDSL2Hyperdrive.sol new file mode 100644 index 000000000..872fe75ab --- /dev/null +++ b/contracts/src/instances/savings-usds-l2/SavingsUSDSL2Hyperdrive.sol @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.24; + +import { ERC20 } from "openzeppelin/token/ERC20/ERC20.sol"; +import { SafeERC20 } from "openzeppelin/token/ERC20/utils/SafeERC20.sol"; +import { Hyperdrive } from "../../external/Hyperdrive.sol"; +import { IHyperdrive } from "../../interfaces/IHyperdrive.sol"; +import { IHyperdriveAdminController } from "../../interfaces/IHyperdriveAdminController.sol"; +import { IPSM } from "../../interfaces/IPSM.sol"; +import { SavingsUSDSL2Base } from "./SavingsUSDSL2Base.sol"; + +/// ______ __ _________ _____ +/// ___ / / /____ ___________________________ /_________(_)__ ______ +/// __ /_/ /__ / / /__ __ \ _ \_ ___/ __ /__ ___/_ /__ | / / _ \ +/// _ __ / _ /_/ /__ /_/ / __/ / / /_/ / _ / _ / __ |/ // __/ +/// /_/ /_/ _\__, / _ ___/\___//_/ \__,_/ /_/ /_/ _____/ \___/ +/// /____/ /_/ +/// XXX ++ ++ XXX +/// ############ XXXXX ++0+ +0++ XXXXX ########### +/// ##////////////######## ++00++ ++00++ ########///////////## +/// ##////////////########## ++000++ ++000++ ##########///////////## +/// ##%%%%%%///// ###### ++0000+ +0000++ ###### /////%%%%%%## +/// %%%%%%%%&& ## ++0000+ +0000++ ## &&%%%%%%%%% +/// %&&& ## +o000+ +000o+ ## &&&% +/// ## ++00+- -+00++ ## +/// #% ++0+ +0++ %# +/// ###-:Oo.++++.oO:-### +/// ##: 00++++++00 :## +/// #S###########* 0++00+++00++0 *##########S# +/// #S % $ 0+++0 $ % S# +/// #S ---------- %+++++:#:+++++%----------- S# +/// #S ------------- %++++: ### :++++%------------ S# +/// S ---------------%++++*\ | /*++++%------------- S +/// #S --------------- %++++ ~W~ ++++%666--o UUUU o- S# +/// #S? --------------- %+++++~+++++%&&&8 o \ / o ?S# +/// ?*????**+++;::,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,::;+++**????*? +/// #?+////////////////////////////////////////////////////////////////+?# +/// #;;;;;//////////////////////////////////////////////////////////////;;;;;# +/// S;;;;;;;;;//////////////////////////////////////////////////////////;;;;;;;;;S +/// /;;;;;;;;;;;///////////////////////////////////////////////////////;;;;;;;;;;;;\ +/// |||OOOOOOOO||OOOOOOOO=========== __ ___ ===========OOOOOOOO||OOOOOOOO||| +/// |||OOOOOOOO||OOOOOOOO===========| \[__ | \ /===========OOOOOOOO||OOOOOOOO||| +/// |||OOOOOOOO||OOOOOOOO===========|__/[___|___ \/ ===========OOOOOOOO||OOOOOOOO||| +/// |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| +/// |||////////000000000000\\\\\\\\|:::::::::::::::|////////00000000000\\\\\\\\\\||| +/// SSS\\\\\\\\000000000000////////|:::::0x666:::::|\\\\\\\\00000000000//////////SSS +/// SSS|||||||||||||||||||||||||||||:::::::::::::::||||||||||||||||||||||||||||||SSS +/// SSSSSSSS|_______________|______________||_______________|______________|SSSSSSSS +/// SSSSSSSS SSSSSSSS +/// SSSSSSSS SSSSSSSS +/// +/// @author DELV +/// @title SavingsUSDSL2Hyperdrive +/// @notice A Hyperdrive instance that uses a SavingsUSDSL2 vault as the yield source. +/// @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 SavingsUSDSL2Hyperdrive is Hyperdrive, SavingsUSDSL2Base { + using SafeERC20 for ERC20; + + /// @notice Instantiates Hyperdrive with a SavingsUSDSL2 vault as the yield source. + /// @param __name The pool's name. + /// @param _config The configuration of the Hyperdrive pool. + /// @param __adminController The admin controller that will specify the + /// admin parameters for this instance. + /// @param _target0 The target0 address. + /// @param _target1 The target1 address. + /// @param _target2 The target2 address. + /// @param _target3 The target3 address. + /// @param _target4 The target4 address. + /// @param _PSM the PSM contract. + constructor( + string memory __name, + IHyperdrive.PoolConfig memory _config, + IHyperdriveAdminController __adminController, + address _target0, + address _target1, + address _target2, + address _target3, + address _target4, + IPSM _PSM + ) + Hyperdrive( + __name, + _config, + __adminController, + _target0, + _target1, + _target2, + _target3, + _target4 + ) + SavingsUSDSL2Base(_PSM) + {} +} diff --git a/contracts/src/instances/savings-usds-l2/SavingsUSDSL2Target0.sol b/contracts/src/instances/savings-usds-l2/SavingsUSDSL2Target0.sol new file mode 100644 index 000000000..090249e21 --- /dev/null +++ b/contracts/src/instances/savings-usds-l2/SavingsUSDSL2Target0.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.24; + +import { HyperdriveTarget0 } from "../../external/HyperdriveTarget0.sol"; +import { IHyperdrive } from "../../interfaces/IHyperdrive.sol"; +import { IHyperdriveAdminController } from "../../interfaces/IHyperdriveAdminController.sol"; +import { SAVINGS_USDS_L2_HYPERDRIVE_KIND } from "../../libraries/Constants.sol"; +import { SavingsUSDSL2Base } from "./SavingsUSDSL2Base.sol"; +import { IPSM } from "../../interfaces/IPSM.sol"; + +/// @author DELV +/// @title SavingsUSDSL2Target0 +/// @notice SavingsUSDSL2Hyperdrive's target0 logic contract. This contract contains +/// all of the getters for Hyperdrive as well as some stateful +/// functions. +/// @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 SavingsUSDSL2Target0 is HyperdriveTarget0, SavingsUSDSL2Base { + /// @notice Initializes the target0 contract. + /// @param _config The configuration of the Hyperdrive pool. + /// @param __adminController The admin controller that will specify the + /// admin parameters for this instance. + /// @param _PSM the PSM contract. + constructor( + IHyperdrive.PoolConfig memory _config, + IHyperdriveAdminController __adminController, + IPSM _PSM + ) HyperdriveTarget0(_config, __adminController) SavingsUSDSL2Base(_PSM) {} + + /// @notice Returns the instance's kind. + /// @return The instance's kind. + function kind() external pure override returns (string memory) { + _revert(abi.encode(SAVINGS_USDS_L2_HYPERDRIVE_KIND)); + } + + /// @notice Gets the PSM contract. This is where USDS is swapped for + /// SUSDS. + /// @return The contract address. + function psm() external view returns (address) { + _revert(abi.encode(address(_PSM))); + } +} diff --git a/contracts/src/instances/savings-usds-l2/SavingsUSDSL2Target1.sol b/contracts/src/instances/savings-usds-l2/SavingsUSDSL2Target1.sol new file mode 100644 index 000000000..7b536a447 --- /dev/null +++ b/contracts/src/instances/savings-usds-l2/SavingsUSDSL2Target1.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.24; + +import { HyperdriveTarget1 } from "../../external/HyperdriveTarget1.sol"; +import { IHyperdrive } from "../../interfaces/IHyperdrive.sol"; +import { IHyperdriveAdminController } from "../../interfaces/IHyperdriveAdminController.sol"; +import { SavingsUSDSL2Base } from "./SavingsUSDSL2Base.sol"; +import { IPSM } from "../../interfaces/IPSM.sol"; + +/// @author DELV +/// @title SavingsUSDSL2Target1 +/// @notice SavingsUSDSL2Hyperdrive's target1 logic contract. This contract contains +/// several stateful functions that couldn't fit into the Hyperdrive +/// contract. +/// @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 SavingsUSDSL2Target1 is HyperdriveTarget1, SavingsUSDSL2Base { + /// @notice Initializes the target1 contract. + /// @param _config The configuration of the Hyperdrive pool. + /// @param __adminController The admin controller that will specify the + /// admin parameters for this instance. + /// @param _PSM the PSM contract. + constructor( + IHyperdrive.PoolConfig memory _config, + IHyperdriveAdminController __adminController, + IPSM _PSM + ) HyperdriveTarget1(_config, __adminController) SavingsUSDSL2Base(_PSM) {} +} diff --git a/contracts/src/instances/savings-usds-l2/SavingsUSDSL2Target2.sol b/contracts/src/instances/savings-usds-l2/SavingsUSDSL2Target2.sol new file mode 100644 index 000000000..bf0269c65 --- /dev/null +++ b/contracts/src/instances/savings-usds-l2/SavingsUSDSL2Target2.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.24; + +import { HyperdriveTarget2 } from "../../external/HyperdriveTarget2.sol"; +import { IHyperdrive } from "../../interfaces/IHyperdrive.sol"; +import { IHyperdriveAdminController } from "../../interfaces/IHyperdriveAdminController.sol"; +import { SavingsUSDSL2Base } from "./SavingsUSDSL2Base.sol"; +import { IPSM } from "../../interfaces/IPSM.sol"; + +/// @author DELV +/// @title SavingsUSDSL2Target2 +/// @notice SavingsUSDSL2Hyperdrive's target2 logic contract. This contract contains +/// several stateful functions that couldn't fit into the Hyperdrive +/// contract. +/// @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 SavingsUSDSL2Target2 is HyperdriveTarget2, SavingsUSDSL2Base { + /// @notice Initializes the target2 contract. + /// @param _config The configuration of the Hyperdrive pool. + /// @param __adminController The admin controller that will specify the + /// admin parameters for this instance. + /// @param _PSM the PSM contract. + constructor( + IHyperdrive.PoolConfig memory _config, + IHyperdriveAdminController __adminController, + IPSM _PSM + ) HyperdriveTarget2(_config, __adminController) SavingsUSDSL2Base(_PSM) {} +} diff --git a/contracts/src/instances/savings-usds-l2/SavingsUSDSL2Target3.sol b/contracts/src/instances/savings-usds-l2/SavingsUSDSL2Target3.sol new file mode 100644 index 000000000..76f9515b6 --- /dev/null +++ b/contracts/src/instances/savings-usds-l2/SavingsUSDSL2Target3.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.24; + +import { HyperdriveTarget3 } from "../../external/HyperdriveTarget3.sol"; +import { IHyperdrive } from "../../interfaces/IHyperdrive.sol"; +import { IHyperdriveAdminController } from "../../interfaces/IHyperdriveAdminController.sol"; +import { SavingsUSDSL2Base } from "./SavingsUSDSL2Base.sol"; +import { IPSM } from "../../interfaces/IPSM.sol"; + +/// @author DELV +/// @title SavingsUSDSL2Target3 +/// @notice SavingsUSDSL2Hyperdrive's target3 logic contract. This contract contains +/// several stateful functions that couldn't fit into the Hyperdrive +/// contract. +/// @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 SavingsUSDSL2Target3 is HyperdriveTarget3, SavingsUSDSL2Base { + /// @notice Initializes the target3 contract. + /// @param _config The configuration of the Hyperdrive pool. + /// @param __adminController The admin controller that will specify the + /// admin parameters for this instance. + /// @param _PSM the PSM contract. + constructor( + IHyperdrive.PoolConfig memory _config, + IHyperdriveAdminController __adminController, + IPSM _PSM + ) HyperdriveTarget3(_config, __adminController) SavingsUSDSL2Base(_PSM) {} +} diff --git a/contracts/src/instances/savings-usds-l2/SavingsUSDSL2Target4.sol b/contracts/src/instances/savings-usds-l2/SavingsUSDSL2Target4.sol new file mode 100644 index 000000000..3aee7214e --- /dev/null +++ b/contracts/src/instances/savings-usds-l2/SavingsUSDSL2Target4.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.24; + +import { HyperdriveTarget4 } from "../../external/HyperdriveTarget4.sol"; +import { IHyperdrive } from "../../interfaces/IHyperdrive.sol"; +import { IHyperdriveAdminController } from "../../interfaces/IHyperdriveAdminController.sol"; +import { SavingsUSDSL2Base } from "./SavingsUSDSL2Base.sol"; +import { IPSM } from "../../interfaces/IPSM.sol"; + +/// @author DELV +/// @title SavingsUSDSL2BaseTarget4 +/// @notice SavingsUSDSL2BaseHyperdrive's target4 logic contract. This contract contains +/// several stateful functions that couldn't fit into the Hyperdrive +/// contract. +/// @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 SavingsUSDSL2Target4 is HyperdriveTarget4, SavingsUSDSL2Base { + /// @notice Initializes the target4 contract. + /// @param _config The configuration of the Hyperdrive pool. + /// @param __adminController The admin controller that will specify the + /// admin parameters for this instance. + /// @param _PSM the PSM contract. + constructor( + IHyperdrive.PoolConfig memory _config, + IHyperdriveAdminController __adminController, + IPSM _PSM + ) HyperdriveTarget4(_config, __adminController) SavingsUSDSL2Base(_PSM) {} +} diff --git a/contracts/src/interfaces/IPSM.sol b/contracts/src/interfaces/IPSM.sol new file mode 100644 index 000000000..53c07d41a --- /dev/null +++ b/contracts/src/interfaces/IPSM.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.24; + +import "./IERC20.sol"; + +interface IPSM { + // convertToBase = USDS in, SUSDS out + // convertToShares = SUSDS in, USDS out + function previewSwapExactIn( + address assetIn, + address assetOut, + uint256 amountIn + ) external view returns (uint256); + + function rateProvider() external view returns (address); + + function susds() external view returns (IERC20); + + function usds() external view returns (IERC20); + + function totalAssets() external view returns (uint256); + + function totalShares() external view returns (uint256); + + function swapExactIn( + address assetIn, + address assetOut, + uint256 amountIn, + uint256 minAmountOut, + address receiver, + uint256 referralCode + ) external returns (uint256 amountOut); +} diff --git a/contracts/src/interfaces/IRateProvider.sol b/contracts/src/interfaces/IRateProvider.sol new file mode 100644 index 000000000..1f379b3a1 --- /dev/null +++ b/contracts/src/interfaces/IRateProvider.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.24; + +interface IRateProvider { + function getChi() external view returns (uint256); + + function getRho() external view returns (uint256); + + function getSSR() external view returns (uint256); + + function getConversionRate() external view returns (uint256); +} diff --git a/contracts/src/libraries/Constants.sol b/contracts/src/libraries/Constants.sol index d57b45d1e..018cb653e 100644 --- a/contracts/src/libraries/Constants.sol +++ b/contracts/src/libraries/Constants.sol @@ -73,6 +73,9 @@ string constant RSETH_LINEA_HYPERDRIVE_DEPLOYER_COORDINATOR_KIND = "RsETHLineaHy /// @dev The kind of the StETHHyperdrive deployer coordinator. string constant STETH_HYPERDRIVE_DEPLOYER_COORDINATOR_KIND = "StETHHyperdriveDeployerCoordinator"; +/// @dev The kind of the SavingsUSDSL2Hyperdrive deployer coordinator. +string constant SAVINGS_USDS_L2_HYPERDRIVE_DEPLOYER_COORDINATOR_KIND = "SavingsUSDSL2HyperdriveDeployerCoordinator"; + /// @dev The kind of the StakingUSDSHyperdrive deployer coordinator. string constant STAKING_USDS_HYPERDRIVE_DEPLOYER_COORDINATOR_KIND = "StakingUSDSHyperdriveDeployerCoordinator"; @@ -121,6 +124,9 @@ string constant RSETH_LINEA_HYPERDRIVE_KIND = "RsETHLineaHyperdrive"; /// @dev The kind of StETHHyperdrive. string constant STETH_HYPERDRIVE_KIND = "StETHHyperdrive"; +/// @dev The kind of the SavingsUSDSL2Hyperdrive. +string constant SAVINGS_USDS_L2_HYPERDRIVE_KIND = "SavingsUSDSL2Hyperdrive"; + /// @dev The kind of StakingUSDSHyperdrive. string constant STAKING_USDS_HYPERDRIVE_KIND = "StakingUSDSHyperdrive"; diff --git a/python/gas_benchmarks.py b/python/gas_benchmarks.py index 4025d2a7a..14bc1f1aa 100644 --- a/python/gas_benchmarks.py +++ b/python/gas_benchmarks.py @@ -122,6 +122,9 @@ with open(OUTPUT_PATH, "w", encoding="utf-8") as f: json.dump(capture, f) + print(capture) + print(f"Wrote gas benchmarks to {OUTPUT_PATH}") + except subprocess.CalledProcessError as e: print(e.output) exit(1) diff --git a/test/instances/savings-usds-l2/SavingsUSDSL2HyperdriveInstanceTest.t.sol b/test/instances/savings-usds-l2/SavingsUSDSL2HyperdriveInstanceTest.t.sol new file mode 100644 index 000000000..ddfd0dc77 --- /dev/null +++ b/test/instances/savings-usds-l2/SavingsUSDSL2HyperdriveInstanceTest.t.sol @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.20; + +import { stdStorage, StdStorage } from "forge-std/Test.sol"; +import { SavingsUSDSL2HyperdriveCoreDeployer } from "../../../contracts/src/deployers/savings-usds-l2/SavingsUSDSL2HyperdriveCoreDeployer.sol"; +import { SavingsUSDSL2HyperdriveDeployerCoordinator } from "../../../contracts/src/deployers/savings-usds-l2/SavingsUSDSL2HyperdriveDeployerCoordinator.sol"; +import { SavingsUSDSL2Target0Deployer } from "../../../contracts/src/deployers/savings-usds-l2/SavingsUSDSL2Target0Deployer.sol"; +import { SavingsUSDSL2Target1Deployer } from "../../../contracts/src/deployers/savings-usds-l2/SavingsUSDSL2Target1Deployer.sol"; +import { SavingsUSDSL2Target2Deployer } from "../../../contracts/src/deployers/savings-usds-l2/SavingsUSDSL2Target2Deployer.sol"; +import { SavingsUSDSL2Target3Deployer } from "../../../contracts/src/deployers/savings-usds-l2/SavingsUSDSL2Target3Deployer.sol"; +import { SavingsUSDSL2Target4Deployer } from "../../../contracts/src/deployers/savings-usds-l2/SavingsUSDSL2Target4Deployer.sol"; +import { SavingsUSDSL2Conversions } from "../../../contracts/src/instances/savings-usds-l2/SavingsUSDSL2Conversions.sol"; +import { IERC20 } from "../../../contracts/src/interfaces/IERC20.sol"; +import { IHyperdrive } from "../../../contracts/src/interfaces/IHyperdrive.sol"; +import { IPSM } from "../../../contracts/src/interfaces/IPSM.sol"; +import { IRateProvider } from "../../../contracts/src/interfaces/IRateProvider.sol"; +import { FixedPointMath } from "../../../contracts/src/libraries/FixedPointMath.sol"; +import { InstanceTest } from "../../utils/InstanceTest.sol"; +import { HyperdriveUtils } from "../../utils/HyperdriveUtils.sol"; +import { Lib } from "../../utils/Lib.sol"; + +contract SavingsUSDSL2HyperdriveInstanceTest is InstanceTest { + using FixedPointMath for uint256; + using HyperdriveUtils for uint256; + using HyperdriveUtils for IHyperdrive; + using Lib for *; + using stdStorage for StdStorage; + + /// @dev The RAY constant from sUSDS. + uint256 internal constant RAY = 1e27; + + /// @dev The PSM contract. + IPSM internal PSM; + + /// @notice Instantiates the instance testing suite with the configuration. + /// @param _config The instance test configuration. + constructor( + InstanceTestConfig memory _config, + IPSM _PSM + ) InstanceTest(_config) { + PSM = _PSM; + } + + /// Overrides /// + + /// @dev Gets the extra data used to deploy the Aerodrome LP instance. This + /// is empty. + /// @return The empty extra data. + function getExtraData() internal view override returns (bytes memory) { + return abi.encode(PSM); + } + + /// @dev Converts base amount to the equivalent about in shares. + /// @param baseAmount The base amount. + /// @return The converted share amount. + function convertToShares( + uint256 baseAmount + ) internal view override returns (uint256) { + return SavingsUSDSL2Conversions.convertToShares(PSM, baseAmount); + } + + /// @dev Converts share amount to the equivalent amount in base. + /// @param shareAmount The share amount. + /// @return The converted base amount. + function convertToBase( + uint256 shareAmount + ) internal view override returns (uint256) { + return SavingsUSDSL2Conversions.convertToBase(PSM, shareAmount); + } + + /// @dev Deploys the AerodromeLp Hyperdrive deployer coordinator contract. + /// @param _factory The address of the Hyperdrive factory. + /// @return The coordinator address. + function deployCoordinator( + address _factory + ) internal override returns (address) { + vm.startPrank(alice); + return + address( + new SavingsUSDSL2HyperdriveDeployerCoordinator( + string.concat(config.name, "DeployerCoordinator"), + _factory, + address(new SavingsUSDSL2HyperdriveCoreDeployer()), + address(new SavingsUSDSL2Target0Deployer()), + address(new SavingsUSDSL2Target1Deployer()), + address(new SavingsUSDSL2Target2Deployer()), + address(new SavingsUSDSL2Target3Deployer()), + address(new SavingsUSDSL2Target4Deployer()) + ) + ); + } + + /// @dev Fetches the total supply of the base and share tokens. + /// @return The total supply of base. + /// @return The total supply of vault shares. + function getSupply() + internal + view + virtual + override + returns (uint256, uint256) + { + return ( + PSM.usds().balanceOf(address(PSM)), + convertToShares(PSM.usds().balanceOf(address(PSM))) + ); + } + + /// @dev Fetches the token balance information of an account. + /// @param account The account to query. + /// @return The balance of base. + /// @return The balance of vault shares. + function getTokenBalances( + address account + ) internal view override returns (uint256, uint256) { + return ( + config.baseToken.balanceOf(account), + config.vaultSharesToken.balanceOf(account) + ); + } + + /// Helpers /// + + /// @dev Advance time and accrue interest. + /// @param timeDelta The time to advance. + /// @param variableRate The variable rate. + function advanceTime( + uint256 timeDelta, + int256 variableRate + ) internal override { + IRateProvider rateProvider = IRateProvider(PSM.rateProvider()); + uint256 chi = rateProvider.getChi(); + uint256 rho = rateProvider.getRho(); + uint256 ssr = rateProvider.getSSR(); + chi = (_rpow(ssr, block.timestamp - rho) * chi) / RAY; + + // Accrue interest in the SUSDS market. This amounts to manually + // updating the chi value, which is the exchange rate. + (chi, ) = chi.calculateInterest(variableRate, timeDelta); + + // Advance the time. + vm.warp(block.timestamp + timeDelta); + + // Update the SUSDS market state. + vm.store( + address(rateProvider), + bytes32(uint256(1)), + bytes32((uint256(block.timestamp) << 216) | (chi << 96) | ssr) + ); + } + + /// @dev The ray pow method from sUSDS. + /// @param x The base of the exponentiation. + /// @param n The exponent of the exponentiation. + /// @param z The result of the exponentiation. + function _rpow(uint256 x, uint256 n) internal pure returns (uint256 z) { + assembly { + switch x + case 0 { + switch n + case 0 { + z := RAY + } + default { + z := 0 + } + } + default { + switch mod(n, 2) + case 0 { + z := RAY + } + default { + z := x + } + let half := div(RAY, 2) // for rounding. + for { + n := div(n, 2) + } n { + n := div(n, 2) + } { + let xx := mul(x, x) + if iszero(eq(div(xx, x), x)) { + revert(0, 0) + } + let xxRound := add(xx, half) + if lt(xxRound, xx) { + revert(0, 0) + } + x := div(xxRound, RAY) + if mod(n, 2) { + let zx := mul(z, x) + if and(iszero(iszero(x)), iszero(eq(div(zx, x), z))) { + revert(0, 0) + } + let zxRound := add(zx, half) + if lt(zxRound, zx) { + revert(0, 0) + } + z := div(zxRound, RAY) + } + } + } + } + } +} diff --git a/test/instances/savings-usds-l2/SavingsUSDS_Base_Hyperdrive.t.sol b/test/instances/savings-usds-l2/SavingsUSDS_Base_Hyperdrive.t.sol new file mode 100644 index 000000000..4a32069b8 --- /dev/null +++ b/test/instances/savings-usds-l2/SavingsUSDS_Base_Hyperdrive.t.sol @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.20; + +import { stdStorage, StdStorage } from "forge-std/Test.sol"; +import { IERC20 } from "../../../contracts/src/interfaces/IERC20.sol"; +import { IHyperdrive } from "../../../contracts/src/interfaces/IHyperdrive.sol"; +import { IPSM } from "../../../contracts/src/interfaces/IPSM.sol"; +import { SavingsUSDSL2HyperdriveInstanceTest } from "./SavingsUSDSL2HyperdriveInstanceTest.t.sol"; +import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; + +contract SavingsUSDS_L2_Base_Hyperdrive is SavingsUSDSL2HyperdriveInstanceTest { + using stdStorage for StdStorage; + using Strings for uint256; + + /// @dev The PSM contract on Base. + IPSM internal immutable _PSM = + IPSM(address(0x1601843c5E9bC251A3272907010AFa41Fa18347E)); + + /// @dev The tokens on Base. + IERC20 internal immutable USDS = + IERC20(address(0x820C137fa70C8691f0e44Dc420a5e53c168921Dc)); + IERC20 internal immutable SUSDS = + IERC20(address(0x5875eEE11Cf8398102FdAd704C9E96607675467a)); + + /// @dev Whale accounts on Base. + /// there are no whales, using the PSM module itself + address internal USDS_TOKEN_WHALE = + address(0x2f45724d7E384b38D5C97206e78470544304887F); + address[] internal baseTokenWhaleAccounts = [USDS_TOKEN_WHALE]; + address internal SUSDS_TOKEN_WHALE = + address(0xE971427F9a3C7a282D51E7FBE8A6DFfD257eBdDA); + address[] internal vaultSharesTokenWhaleAccounts = [SUSDS_TOKEN_WHALE]; + + /// @notice Instantiates the instance testing suite with the configuration. + constructor() + SavingsUSDSL2HyperdriveInstanceTest( + InstanceTestConfig({ + name: "Hyperdrive", + kind: "SavingsUSDSL2Hyperdrive", + decimals: 18, + baseTokenWhaleAccounts: baseTokenWhaleAccounts, + vaultSharesTokenWhaleAccounts: vaultSharesTokenWhaleAccounts, + baseToken: USDS, + vaultSharesToken: IERC20(SUSDS), + shareTolerance: 1e3, + minimumShareReserves: 1e15, + minimumTransactionAmount: 1e15, + positionDuration: POSITION_DURATION, + fees: IHyperdrive.Fees({ + curve: 0, + flat: 0, + governanceLP: 0, + governanceZombie: 0 + }), + enableBaseDeposits: true, + enableShareDeposits: true, + enableBaseWithdraws: true, + enableShareWithdraws: true, + baseWithdrawError: new bytes(0), + isRebasing: false, + shouldAccrueInterest: true, + // The base test tolerances. + closeLongWithBaseTolerance: 1e3, + closeShortWithBaseUpperBoundTolerance: 10, + closeShortWithBaseTolerance: 100, + roundTripLpInstantaneousWithBaseTolerance: 1e8, + roundTripLpWithdrawalSharesWithBaseTolerance: 1e8, + roundTripLongInstantaneousWithBaseUpperBoundTolerance: 1e3, + roundTripLongInstantaneousWithBaseTolerance: 1e8, + roundTripLongMaturityWithBaseUpperBoundTolerance: 1e3, + roundTripLongMaturityWithBaseTolerance: 1e5, + roundTripShortInstantaneousWithBaseUpperBoundTolerance: 1e3, + roundTripShortInstantaneousWithBaseTolerance: 1e5, + roundTripShortMaturityWithBaseTolerance: 1e5, + // The share test tolerances. + closeLongWithSharesTolerance: 1e3, + closeShortWithSharesTolerance: 100, + roundTripLpInstantaneousWithSharesTolerance: 1e7, + roundTripLpWithdrawalSharesWithSharesTolerance: 1e7, + roundTripLongInstantaneousWithSharesUpperBoundTolerance: 1e3, + roundTripLongInstantaneousWithSharesTolerance: 1e5, + roundTripLongMaturityWithSharesUpperBoundTolerance: 1e3, + roundTripLongMaturityWithSharesTolerance: 1e5, + roundTripShortInstantaneousWithSharesUpperBoundTolerance: 1e3, + roundTripShortInstantaneousWithSharesTolerance: 1e5, + roundTripShortMaturityWithSharesTolerance: 1e5, + // The verification tolerances. + verifyDepositTolerance: 5, + verifyWithdrawalTolerance: 2 + }), + _PSM + ) + {} + + /// @notice Forge function that is invoked to setup the testing environment. + function setUp() public override __base_fork(23_839_241) { + // Invoke the Instance testing suite setup. + super.setUp(); + } +}