diff --git a/README.md b/README.md index 75bdd78..77a945b 100644 --- a/README.md +++ b/README.md @@ -12,24 +12,34 @@ PRIVATE_KEY=... **Note**: `ETHERSCAN_KEY` should match the block explorer key of the network you are deploying. - -## Hardhat configuration - -Firstly, configure your network (arbitrum/bsc/mainnet) inside `./hardhat.config.ts`. - -Secondly, please also configure the `NETWORK` variable in `./scripts/configuration.ts` accordingly: - -```ts -export const NETWORK = SUPPORTED_CHAINS.MAINNET; -``` - ## Deployment ### Contract and Parameters preparation First step is to get your contract ready inside `./contracts/` folder as we currently have `./contracts/SwETHSY.sol`. -After that, you will have to fill in the parameters for `MarketConfiguration` in `./scripts/configuration.ts`. +After that, you will have to fill in the parameters for the following parameters corresponding to your interest bearing token: +```ts +/** + * @dev The following parameters are used to calculate the market deployment params + * @minApy and @maxApy are the minimum and maximum APY of the interest bearing asset + * @startTimestamp and @endTimestamp are the start and end time of the market + */ +const minApy = 0.01; // 1% +const maxApy = 0.05; // 5% +const startTimestamp = 1689206400; +const endTimestamp = 1750896000; + +export const MarketConfiguration = { + name: 'SY swETH', + symbol: 'SY-swETH', + doCacheIndex: true, + expiry: endTimestamp, + ...calculateParameters(minApy, maxApy, startTimestamp, endTimestamp), +}; +``` + +The remaining three fields in MarketConfiguration are `name`, `symbol` (quite straight-forward) and `doCacheIndex`. For `doCacheIndex`, we usually leave it as `true` for ethereum mainnet deployment and `false` for any other chain to save gas for PT/YT related transactions. ### Run deployment script @@ -70,7 +80,7 @@ export const AMOUNT_TO_SEED = toWei(0.01); ### Run script ``` -yarn hardhat run scripts/seed-liquidity.ts +yarn hardhat run scripts/seed-liquidity.ts --network ``` ## Final notes diff --git a/hardhat.config.ts b/hardhat.config.ts index 4ef0f5b..df386c4 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -26,7 +26,18 @@ const config: HardhatUserConfig = { }, mainnet: { url: 'https://rpc.ankr.com/eth', - accounts: [PRIVATE_KEY] + accounts: [PRIVATE_KEY], + chainId: 1 + }, + arbitrum: { + url: 'https://rpc.ankr.com/arbitrum', + accounts: [PRIVATE_KEY], + chainId: 42161 + }, + bsc: { + url: 'https://rpc.ankr.com/bsc', + accounts: [PRIVATE_KEY], + chainId: 56 } }, solidity: { diff --git a/scripts/configuration.ts b/scripts/configuration.ts index 25ae15f..9fa2734 100644 --- a/scripts/configuration.ts +++ b/scripts/configuration.ts @@ -1,16 +1,24 @@ import { ZERO_ADDRESS } from './consts'; import { toWei } from './helper'; import { SUPPORTED_CHAINS } from './types'; +import { calculateParameters } from './param-helper' -export const NETWORK = SUPPORTED_CHAINS.MAINNET; +/** + * @dev The following parameters are used to calculate the market deployment params + * @minApy and @maxApy are the minimum and maximum APY of the interest bearing asset + * @startTimestamp and @endTimestamp are the start and end time of the market + */ +const minApy = 0.01; // 1% +const maxApy = 0.05; // 5% +const startTimestamp = 1689206400; +const endTimestamp = 1750896000; export const MarketConfiguration = { name: 'SY swETH', symbol: 'SY-swETH', - expiry: 1750896000, - scalarRoot: toWei(112.2782), - initialRateAnchor: toWei(1.08711), doCacheIndex: true, + expiry: endTimestamp, + ...calculateParameters(minApy, maxApy, startTimestamp, endTimestamp), }; // address(0) is native @@ -23,4 +31,4 @@ export const UNDERLYING_TO_SEED_LIQUIDITY = ZERO_ADDRESS; // The toWei function multiply your input with 10^18 by default // So do consider using customized amount (BN.from(10).pow(6) for example) for other cases -export const AMOUNT_TO_SEED = toWei(0.01); \ No newline at end of file +export const AMOUNT_TO_SEED = toWei(0.01); diff --git a/scripts/consts.ts b/scripts/consts.ts index 616e7d4..1cbf77e 100644 --- a/scripts/consts.ts +++ b/scripts/consts.ts @@ -1,5 +1,5 @@ import { BigNumber as BN, Contract } from 'ethers'; -export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' +export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; export const INF = BN.from(2).pow(256).sub(1); -export const SAFE_WAIT_TIME = 15000; \ No newline at end of file +export const SAFE_WAIT_TIME = 15000; diff --git a/scripts/helper.ts b/scripts/helper.ts index 6ad52b8..e8d24fc 100644 --- a/scripts/helper.ts +++ b/scripts/helper.ts @@ -9,7 +9,14 @@ import bnbConfiguration from '@pendle/core-v2/deployments/56-core.json'; import { PendleContracts, SUPPORTED_CHAINS } from './types'; import { IERC20, IPAllAction, PendleMarketFactory, PendleYieldContractFactory } from '../typechain-types'; import { INF } from './consts'; -import { NETWORK } from './configuration'; + +export function getNetwork() { + return { + [1]: SUPPORTED_CHAINS.MAINNET, + [56]: SUPPORTED_CHAINS.BSC, + [42161]: SUPPORTED_CHAINS.ARBITRUM, + }[hre.network.config.chainId!]!; +} export function toWei(num: number): BN { return BN.from(Math.floor(10 ** 9 * num)).mul(10 ** 9); @@ -61,7 +68,7 @@ export async function getPendleContracts(): Promise { [SUPPORTED_CHAINS.MAINNET]: ethereumConfiguration, [SUPPORTED_CHAINS.ARBITRUM]: arbitrumConfiguration, [SUPPORTED_CHAINS.BSC]: bnbConfiguration, - }[NETWORK]; + }[getNetwork()]; return { router: await getContractAt('IPAllAction', config.router), @@ -84,4 +91,5 @@ export async function safeApproveInf(deployer: SignerWithAddress, token: string, const contract = await getContractAt('IERC20', token); const allowance = await contract.allowance(deployer.address, to); if (allowance.lt(INF.div(2))) await contract.connect(deployer).approve(to, INF); -} \ No newline at end of file +} + diff --git a/scripts/param-helper.ts b/scripts/param-helper.ts new file mode 100644 index 0000000..c841fa6 --- /dev/null +++ b/scripts/param-helper.ts @@ -0,0 +1,43 @@ +import { toWei } from "./helper"; + +/** + * Validates the given start and end timestamps. + * @param {number} startTimestamp - The Unix timestamp (in seconds) for the start time. + * @param {number} endTimestamp - The Unix timestamp (in seconds) for the end time. + * @throws {Error} Will throw an error if the start timestamp is after or equal to the end timestamp, or if the end timestamp does not correspond to a Thursday at 12 AM UTC. + */ +function validateTimestamps(startTimestamp: number, endTimestamp: number) { + if (startTimestamp >= endTimestamp) throw Error('Start timestamp must be before end timestamp'); + const endDate = new Date(endTimestamp * 1000); // convert Unix timestamp to JavaScript Date + const isValidEndDate = + endDate.getUTCDay() === 4 && + endDate.getUTCHours() === 0 && + endDate.getUTCMinutes() === 0 && + endDate.getUTCSeconds() === 0; + if (!isValidEndDate) throw Error('Maturity must be at Thursday 12 AM'); +} + +/** + * Calculates the parameters scalarRoot and rateAnchor. + * @param {number} rateMin - The minimum rate (e.g. 1% APY = 0.01). + * @param {number} rateMax - The maximum rate (e.g. 5% APY = 0.05). + * @param {number} startTimestamp - The Unix timestamp (in seconds) for the start time. + * @param {number} endTimestamp - The Unix timestamp (in seconds) for the end time. + * @return {Object} An object containing the scalarRoot and rateAnchor values. + * @throws {Error} Will throw an error if the start timestamp is after or equal to the end timestamp, or if the end timestamp does not correspond to a Thursday at 12 AM UTC. + */ +export function calculateParameters( + rateMin: number, + rateMax: number, + startTimestamp: number, + endTimestamp: number +): { scalarRoot: BN; initialRateAnchor: BN } { + validateTimestamps(startTimestamp, endTimestamp); + const yearsToExpiry = (endTimestamp - startTimestamp) / 31536000; + const rateMinScaled = Math.pow(rateMin + 1, yearsToExpiry); + const rateMaxScaled = Math.pow(rateMax + 1, yearsToExpiry); + const initialRateAnchor = (rateMinScaled + rateMaxScaled) / 2; + const rateDiff = Math.max(Math.abs(rateMaxScaled - initialRateAnchor), Math.abs(initialRateAnchor - rateMinScaled)); + const scalarRoot = (Math.log(9) * yearsToExpiry) / rateDiff; + return { scalarRoot: toWei(scalarRoot), initialRateAnchor: toWei(initialRateAnchor) }; +} diff --git a/scripts/seed-liquidity.ts b/scripts/seed-liquidity.ts index 536c889..14ad10c 100644 --- a/scripts/seed-liquidity.ts +++ b/scripts/seed-liquidity.ts @@ -1,9 +1,9 @@ import { ethers } from 'hardhat'; -import {delay,getContractAt,getPendleContracts, safeApproveInf } from './helper'; +import { delay, getContractAt, getPendleContracts, safeApproveInf } from './helper'; import { SUPPORTED_CHAINS } from './types'; import { INF, SAFE_WAIT_TIME, ZERO_ADDRESS } from './consts'; import { AMOUNT_TO_SEED, UNDERLYING_TO_SEED_LIQUIDITY } from './configuration'; -import marketAddresses from '../deployments/SY-swETH.json' +import marketAddresses from '../deployments/SY-swETH.json'; import { IERC20, IStandardizedYield } from '../typechain-types'; async function main() { @@ -21,7 +21,6 @@ async function main() { await delay(SAFE_WAIT_TIME, 'after approve underlying'); } - const SY = await getContractAt('IStandardizedYield', marketAddresses.SY); const PT = await getContractAt('IERC20', marketAddresses.PT); const YT = await getContractAt('IERC20', marketAddresses.YT); @@ -53,11 +52,10 @@ async function main() { needScale: false, }, }, - {...overrides} + { ...overrides } ); await delay(SAFE_WAIT_TIME, 'after mintSyFromToken'); - await SY.approve(pendleContracts.router.address, INF); await delay(SAFE_WAIT_TIME, 'Before minting PY...');