Skip to content

Commit

Permalink
sDAI Corn Integration (#1172)
Browse files Browse the repository at this point in the history
* Added the skeleton for the Corn integration

* Implemented the `CornBase` contract

* Wired up the Corn Hyperdrive instance

* Added an instance test for the Corn integration

* Added deployment scripts for the Corn integration

* Addressed review feedback from @jrhea and @mcclurejt

* Added a test for the sDAI Corn integration

* Added a deploy script for sDAI Corn

* Remove FIXMEs

* Fixed some small issues with the forknet infrastructure

* Addressed review feedback from @mcclurejt
  • Loading branch information
jalextowle committed Sep 24, 2024
1 parent da3b263 commit 3e07b60
Show file tree
Hide file tree
Showing 8 changed files with 456 additions and 8 deletions.
4 changes: 0 additions & 4 deletions contracts/src/instances/corn/CornBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,6 @@ import { IHyperdrive } from "../../interfaces/IHyperdrive.sol";
import { HyperdriveBase } from "../../internal/HyperdriveBase.sol";
import { CornConversions } from "./CornConversions.sol";

// FIXME: Update this comment.
//
// FIXME: Make sure that the vault shares token is 0.
//
/// @author DELV
/// @title CornBase
/// @notice The base contract for the Corn Hyperdrive implementation.
Expand Down
6 changes: 4 additions & 2 deletions hardhat.config.mainnet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import baseConfig from "./hardhat.config";
import "./tasks";
import {
MAINNET_CORN_COORDINATOR,
MAINNET_CORN_LBTC_182DAY,
MAINNET_CORN_LBTC_91DAY,
MAINNET_CORN_SDAI_91DAY,
MAINNET_ERC4626_COORDINATOR,
MAINNET_EZETH_182DAY,
MAINNET_EZETH_COORDINATOR,
Expand Down Expand Up @@ -47,7 +48,8 @@ const config: HardhatUserConfig = {
MAINNET_MORPHO_BLUE_USDE_DAI_182DAY,
MAINNET_MORPHO_BLUE_WSTETH_USDA_182DAY,
MAINNET_STUSD_182DAY,
MAINNET_CORN_LBTC_182DAY,
MAINNET_CORN_LBTC_91DAY,
MAINNET_CORN_SDAI_91DAY,
],
checkpointRewarders: [],
checkpointSubrewarders: [],
Expand Down
2 changes: 1 addition & 1 deletion hardhat.config.mainnet_fork.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const config: HardhatUserConfig = {
networks: {
mainnet_fork: {
live: false,
url: env.HYPERDRIVE_ETHEREUM_URL ?? "http://anvil:8545",
url: env.HYPERDRIVE_ETHEREUM_URL!,
accounts: [env.DEPLOYER_PRIVATE_KEY ?? DEFAULT_PK],
hyperdriveDeploy: {
factories: [MAINNET_FACTORY],
Expand Down
84 changes: 84 additions & 0 deletions tasks/deploy/config/mainnet/corn-sdai-91days.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { Address, keccak256, parseEther, toBytes, zeroAddress } from "viem";
import {
HyperdriveInstanceConfig,
getLinkerDetails,
normalizeFee,
parseDuration,
toBytes32,
} from "../../lib";
import { SDAI_ADDRESS_MAINNET, THREE_MONTHS } from "../../lib/constants";
import { MAINNET_CORN_COORDINATOR_NAME } from "./corn-coordinator";
import { MAINNET_FACTORY_NAME } from "./factory";

// The name of the pool.
export const MAINNET_CORN_SDAI_91DAY_NAME =
"ElementDAO 91 Day Corn sDAI Hyperdrive";

// We use a contribution of 100 sDAI.
const CONTRIBUTION = parseEther("100");

export const MAINNET_CORN_SDAI_91DAY: HyperdriveInstanceConfig<"Corn"> = {
name: MAINNET_CORN_SDAI_91DAY_NAME,
prefix: "Corn",
coordinatorAddress: async (hre) =>
hre.hyperdriveDeploy.deployments.byName(MAINNET_CORN_COORDINATOR_NAME)
.address,
deploymentId: keccak256(toBytes(MAINNET_CORN_SDAI_91DAY_NAME)),
salt: toBytes32("0x69420"),
extraData: "0x",
contribution: CONTRIBUTION,
fixedAPR: parseEther("0.08"),
timestretchAPR: parseEther("0.075"),
options: async (hre) => ({
extraData: "0x",
asBase: true,
destination: (await hre.getNamedAccounts())["deployer"] as Address,
}),
// Prepare to deploy the contract by setting approvals.
prepare: async (hre) => {
let baseToken = await hre.viem.getContractAt(
"contracts/src/interfaces/IERC20.sol:IERC20",
SDAI_ADDRESS_MAINNET,
);
let tx = await baseToken.write.approve([
hre.hyperdriveDeploy.deployments.byName(
MAINNET_CORN_COORDINATOR_NAME,
).address,
CONTRIBUTION,
]);
let pc = await hre.viem.getPublicClient();
await pc.waitForTransactionReceipt({ hash: tx });
},
poolDeployConfig: async (hre) => {
let factoryContract = await hre.viem.getContractAt(
"HyperdriveFactory",
hre.hyperdriveDeploy.deployments.byName(MAINNET_FACTORY_NAME)
.address,
);
return {
baseToken: SDAI_ADDRESS_MAINNET,
vaultSharesToken: zeroAddress,
circuitBreakerDelta: parseEther("0.075"),
minimumShareReserves: parseEther("0.001"), // 1e15
minimumTransactionAmount: parseEther("0.001"), // 1e15
positionDuration: parseDuration(THREE_MONTHS),
checkpointDuration: parseDuration("1 day"),
timeStretch: 0n,
governance: await factoryContract.read.hyperdriveGovernance(),
feeCollector: await factoryContract.read.feeCollector(),
sweepCollector: await factoryContract.read.sweepCollector(),
checkpointRewarder: await factoryContract.read.checkpointRewarder(),
...(await getLinkerDetails(
hre,
hre.hyperdriveDeploy.deployments.byName(MAINNET_FACTORY_NAME)
.address,
)),
fees: {
curve: parseEther("0.01"),
flat: normalizeFee(parseEther("0.0005"), THREE_MONTHS),
governanceLP: parseEther("0.15"),
governanceZombie: parseEther("0.03"),
},
};
},
};
3 changes: 2 additions & 1 deletion tasks/deploy/config/mainnet/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from "./corn-coordinator";
export * from "./corn-lbtc-182day";
export * from "./corn-lbtc-91day";
export * from "./corn-sdai-91days";
export * from "./dai-182day";
export * from "./eeth-182day";
export * from "./eeth-coordinator";
Expand Down
173 changes: 173 additions & 0 deletions test/instances/corn/CornHyperdriveInstanceTest.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.22;

import { stdStorage, StdStorage } from "forge-std/Test.sol";
import { CornHyperdriveCoreDeployer } from "../../../contracts/src/deployers/corn/CornHyperdriveCoreDeployer.sol";
import { CornHyperdriveDeployerCoordinator } from "../../../contracts/src/deployers/corn/CornHyperdriveDeployerCoordinator.sol";
import { CornTarget0Deployer } from "../../../contracts/src/deployers/corn/CornTarget0Deployer.sol";
import { CornTarget1Deployer } from "../../../contracts/src/deployers/corn/CornTarget1Deployer.sol";
import { CornTarget2Deployer } from "../../../contracts/src/deployers/corn/CornTarget2Deployer.sol";
import { CornTarget3Deployer } from "../../../contracts/src/deployers/corn/CornTarget3Deployer.sol";
import { CornTarget4Deployer } from "../../../contracts/src/deployers/corn/CornTarget4Deployer.sol";
import { CornConversions } from "../../../contracts/src/instances/corn/CornConversions.sol";
import { IERC20 } from "../../../contracts/src/interfaces/IERC20.sol";
import { ICornHyperdrive } from "../../../contracts/src/interfaces/ICornHyperdrive.sol";
import { ICornSilo } from "../../../contracts/src/interfaces/ICornSilo.sol";
import { IHyperdrive } from "../../../contracts/src/interfaces/IHyperdrive.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 CornHyperdriveInstanceTest is InstanceTest {
using FixedPointMath for uint256;
using HyperdriveUtils for uint256;
using HyperdriveUtils for IHyperdrive;
using Lib for *;
using stdStorage for StdStorage;

/// @dev The Corn Silo.
ICornSilo internal immutable silo;

/// @notice Instantiates the instance testing suite with the configuration.
/// @param _config The instance test configuration.
/// @param _silo The Corn Silo.
constructor(
InstanceTestConfig memory _config,
ICornSilo _silo
) InstanceTest(_config) {
silo = _silo;
}

/// Overrides ///

/// @dev Gets the extra data used to deploy the Corn instance. This is empty.
/// @return The empty extra data.
function getExtraData() internal pure override returns (bytes memory) {
return new bytes(0);
}

/// @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 pure override returns (uint256) {
return CornConversions.convertToShares(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 pure override returns (uint256) {
return CornConversions.convertToBase(shareAmount);
}

/// @dev Deploys the Corn 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 CornHyperdriveDeployerCoordinator(
string.concat(config.name, "DeployerCoordinator"),
_factory,
address(new CornHyperdriveCoreDeployer(silo)),
address(new CornTarget0Deployer(silo)),
address(new CornTarget1Deployer(silo)),
address(new CornTarget2Deployer(silo)),
address(new CornTarget3Deployer(silo)),
address(new CornTarget4Deployer(silo))
)
);
}

/// @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 override returns (uint256, uint256) {
return (
config.baseToken.balanceOf(address(silo)),
silo.totalShares(address(config.baseToken))
);
}

/// @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),
silo.sharesOf(account, address(config.baseToken))
);
}

/// Getters ///

/// @dev Test the instances getters.
function test_getters() external view {
assertEq(
address(ICornHyperdrive(address(hyperdrive)).cornSilo()),
address(silo)
);
(, uint256 totalShares) = getTokenBalances(address(hyperdrive));
assertEq(hyperdrive.totalShares(), totalShares);
}

/// Price Per Share ///

/// @dev Fuzz test that verifies that the vault share price is the price
/// that dictates the conversion between base and shares.
/// @param basePaid The fuzz parameter for the base paid.
function test__pricePerVaultShare(uint256 basePaid) external {
// Ensure that the share price is the expected value.
(uint256 totalBase, uint256 totalShares) = getSupply();
uint256 vaultSharePrice = hyperdrive.getPoolInfo().vaultSharePrice;
assertEq(vaultSharePrice, totalBase.divDown(totalShares));

// Ensure that the share price accurately predicts the amount of shares
// that will be minted for depositing a given amount of shares. This will
// be an approximation.
basePaid = basePaid.normalizeToRange(
2 * hyperdrive.getPoolConfig().minimumTransactionAmount,
hyperdrive.calculateMaxLong()
);
(, uint256 hyperdriveSharesBefore) = getTokenBalances(
address(hyperdrive)
);
openLong(bob, basePaid);
(, uint256 hyperdriveSharesAfter) = getTokenBalances(
address(hyperdrive)
);
assertApproxEqAbs(
hyperdriveSharesAfter,
hyperdriveSharesBefore + basePaid.divDown(vaultSharePrice),
config.shareTolerance
);
}

/// 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 {
// Corn doesn't accrue interest, so we revert if the variable rate isn't
// zero.
require(variableRate == 0, "CornHyperdriveTest: variableRate isn't 0");

// Advance the time.
vm.warp(block.timestamp + timeDelta);
}
}
Loading

0 comments on commit 3e07b60

Please sign in to comment.