Skip to content

Commit

Permalink
Adds integration for Savings USDS on L2 chains + tests on Base (#1230)
Browse files Browse the repository at this point in the history
* add SavingsUSDSBase deployer coordinator and instance

* refactor savings-usds-base integration into savings-usds-l2. Add tests.

* linting fixes

* remove console imports

* linting fixes

* precision fixes

* benchmark test

* benchmark test

* remove commented code, address review comments

* remove unnecessary functions from IPSM interface

* testing for GH action: benchmark

* pin foundry build to nightly-75fc63b

* revert benchmark script to original version
  • Loading branch information
MazyGio authored Jan 7, 2025
1 parent 51aab64 commit 0a80019
Show file tree
Hide file tree
Showing 22 changed files with 1,283 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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
)
)
);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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)
);
}
}
Original file line number Diff line number Diff line change
@@ -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)
);
}
}
Original file line number Diff line number Diff line change
@@ -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)
);
}
}
Loading

0 comments on commit 0a80019

Please sign in to comment.