From d3135f3c6f902bad6755bfc4d387fea0b7e9ea39 Mon Sep 17 00:00:00 2001 From: rudewalt Date: Mon, 2 Jun 2025 14:47:10 +0300 Subject: [PATCH 1/3] add euler oracle --- .../contracts/oracles/EulerPriceOracle.sol | 57 +++++++++++++++++++ .../test/int/others/EulerOracle.eth.spec.ts | 40 +++++++++++++ packages/periphery/test/shared/utils.ts | 11 ++++ 3 files changed, 108 insertions(+) create mode 100644 packages/periphery/contracts/oracles/EulerPriceOracle.sol create mode 100644 packages/periphery/test/int/others/EulerOracle.eth.spec.ts diff --git a/packages/periphery/contracts/oracles/EulerPriceOracle.sol b/packages/periphery/contracts/oracles/EulerPriceOracle.sol new file mode 100644 index 00000000..4facde90 --- /dev/null +++ b/packages/periphery/contracts/oracles/EulerPriceOracle.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.19; + +import "@openzeppelin/contracts/access/Ownable2Step.sol"; +import "@openzeppelin/contracts/utils/math/Math.sol"; +import "@marginly/contracts/contracts/interfaces/IPriceOracle.sol"; + +interface IEulerPriceOracle { + /// @notice One-sided price: How much quote token you would get for inAmount of base token, assuming no price spread. + /// @param inAmount The amount of `base` to convert. + /// @param base The token that is being priced. + /// @param quote The token that is the unit of account. + /// @return outAmount The amount of `quote` that is equivalent to `inAmount` of `base`. + function getQuote(uint256 inAmount, address base, address quote) external view returns (uint256 outAmount); +} + +/** + * @title Adapter for EulerPriceOracle + * @dev USD has the ISO code of 840, so address(840) which corresponds to 0x0000000000000000000000000000000000000348 + * https://github.com/euler-xyz/euler-price-oracle/blob/master/docs/whitepaper.md#euler-price-oracles + */ +contract EulerPriceOracle is IPriceOracle, Ownable2Step { + uint256 private constant X96ONE = 2 ** 96; + + mapping(address quoteToken => mapping(address baseToken => address eulerPriceOracle)) public getParams; + + error EulerPriceOracle__ZeroPrice(); + error EulerPriceOracle__NotInitialized(address quoteToken, address baseToken); + + //@dev eulerPriceOracle must be bidirectional + function addPair(address quoteToken, address baseToken, address eulerPriceOracle) public onlyOwner { + uint256 priceX96 = IEulerPriceOracle(eulerPriceOracle).getQuote(X96ONE, baseToken, quoteToken); + if (priceX96 == 0) revert EulerPriceOracle__ZeroPrice(); + + uint256 priceX96Reverse = IEulerPriceOracle(eulerPriceOracle).getQuote(X96ONE, quoteToken, baseToken); + if (priceX96Reverse == 0) revert EulerPriceOracle__ZeroPrice(); + + getParams[quoteToken][baseToken] = eulerPriceOracle; + getParams[baseToken][quoteToken] = eulerPriceOracle; + } + + function getBalancePrice(address quoteToken, address baseToken) external view returns (uint256) { + return _getPriceX96(quoteToken, baseToken); + } + + function getMargincallPrice(address quoteToken, address baseToken) external view returns (uint256) { + return _getPriceX96(quoteToken, baseToken); + } + + function _getPriceX96(address quoteToken, address baseToken) private view returns (uint256 priceX96) { + address eulerPriceOracle = getParams[quoteToken][baseToken]; + if (eulerPriceOracle == address(0)) revert EulerPriceOracle__NotInitialized(quoteToken, baseToken); + + priceX96 = IEulerPriceOracle(eulerPriceOracle).getQuote(X96ONE, baseToken, quoteToken); + if (priceX96 == 0) revert EulerPriceOracle__ZeroPrice(); + } +} diff --git a/packages/periphery/test/int/others/EulerOracle.eth.spec.ts b/packages/periphery/test/int/others/EulerOracle.eth.spec.ts new file mode 100644 index 00000000..8a34349d --- /dev/null +++ b/packages/periphery/test/int/others/EulerOracle.eth.spec.ts @@ -0,0 +1,40 @@ +import { ethers } from 'hardhat'; +import { Signer } from 'ethers'; +import { getDecimalsDiff, printPrices } from '../../shared/common'; +import { EulerPriceOracle } from '../../../typechain-types'; +import { resetFork } from '../../shared/utils'; + +describe('EulerOracle', () => { + const FORK_BLOCK_NUMBER = 22616500; + + let oracle: EulerPriceOracle; + let owner: Signer; + + before(async () => { + await resetFork(FORK_BLOCK_NUMBER); + + [owner] = await ethers.getSigners(); + const factory = await ethers.getContractFactory('EulerPriceOracle'); + oracle = (await factory.deploy()) as EulerPriceOracle; + }); + + it('wstETH/WETH', async () => { + const wstETH = '0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0'; + const WETH = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'; + //lidoFundamentalOracle + const eulerOracle = '0x7c37aB8Cd76Ee8888ad7F19C1F8a3A6D1622e9B8'; + + oracle.connect(owner).addPair(WETH, wstETH, eulerOracle); + + const balancePrice = await oracle.getBalancePrice(WETH, wstETH); + const mcPrice = await oracle.getMargincallPrice(WETH, wstETH); + + let decimalsDiff = await getDecimalsDiff(WETH, wstETH); + printPrices(balancePrice, mcPrice, decimalsDiff); + + const invertedPrice = await oracle.getBalancePrice(wstETH, WETH); + const invertedMcPrice = await oracle.getMargincallPrice(wstETH, WETH); + decimalsDiff = await getDecimalsDiff(wstETH, WETH); + printPrices(invertedPrice, invertedMcPrice, decimalsDiff); + }); +}); diff --git a/packages/periphery/test/shared/utils.ts b/packages/periphery/test/shared/utils.ts index 597c1277..1e10f294 100644 --- a/packages/periphery/test/shared/utils.ts +++ b/packages/periphery/test/shared/utils.ts @@ -22,6 +22,9 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import { parseEther } from 'ethers/lib/utils'; import { MarginlyAdmin, MarginlyRouter } from '../../typechain-types'; import { UniswapV3Adapter } from '@marginly/router/typechain-types'; +import { HardhatRuntimeEnvironment } from 'hardhat/types'; +import { reset } from '@nomicfoundation/hardhat-network-helpers'; +const hre = require('hardhat'); /// @dev theme paddle front firm patient burger forward little enter pause rule limb export const FeeHolder = '0x4c576Bf4BbF1d9AB9c359414e5D2b466bab085fa'; @@ -226,3 +229,11 @@ export async function moveTimeOnFork(timestamp: number) { await ethers.provider.send('evm_setNextBlockTimestamp', [timestamp]); await ethers.provider.send('evm_mine', []); } + +export async function resetFork(blockNumber?: number) { + const hardhatConfig = (hre).config; + const forkingBlockNumber = hardhatConfig.networks.hardhat.forking?.blockNumber; + const forkingUrl = hardhatConfig.networks.hardhat.forking?.url; + + await reset(forkingUrl, blockNumber ?? forkingBlockNumber); +} From 32c649a8066ef118466240bb4a05599f80d5ed93 Mon Sep 17 00:00:00 2001 From: rudewalt Date: Tue, 3 Jun 2025 11:26:49 +0300 Subject: [PATCH 2/3] deploy new pools, add euler oracle --- packages/deploy/src/config.ts | 17 +- .../deploy/ethereum-v1.5-pendle/config.json | 54 ++- .../config_30_05_2025.json | 378 ------------------ .../ethereum-v1.5-pendle/deployment.json | 12 + .../states/2025-02-28.json | 16 + .../src/deployer/PriceOracleDeployer.ts | 110 +++-- packages/deploy/src/deployer/configs.ts | 32 +- .../hardhat-configs/eth-fork.config.ts | 2 +- 8 files changed, 202 insertions(+), 419 deletions(-) delete mode 100644 packages/deploy/src/data/deploy/ethereum-v1.5-pendle/config_30_05_2025.json diff --git a/packages/deploy/src/config.ts b/packages/deploy/src/config.ts index 0f5410ca..4e40e1b1 100644 --- a/packages/deploy/src/config.ts +++ b/packages/deploy/src/config.ts @@ -56,7 +56,8 @@ export type PriceOracleDeployConfig = | CurveOracleDeployConfig | MarginlyCompositeOracleDeployConfig | PriceOracleProxyDeployConfig - | AavePriceOracleDeployConfig; + | AavePriceOracleDeployConfig + | EulerOracleDeployConfig; export interface UniswapV3TickOracleDeployConfig { type: 'uniswapV3'; @@ -289,6 +290,16 @@ export interface AavePriceOracleDeployConfig { }[]; } +export interface EulerOracleDeployConfig { + type: 'euler'; + id: string; + settings: { + quoteTokenId: string; + baseTokenId: string; + eulerOracle: string; + }[]; +} + export function isUniswapV3OracleConfig(config: PriceOracleDeployConfig): config is UniswapV3TickOracleDeployConfig { return config.type === 'uniswapV3'; } @@ -343,6 +354,10 @@ export function isAavePriceOracleConfig(config: PriceOracleDeployConfig): config return config.type === 'aave'; } +export function isEulerPriceOracleConfig(config: PriceOracleDeployConfig): config is EulerOracleDeployConfig { + return config.type === 'euler'; +} + interface MarginlyDeployConfigUniswapGenuine { type: 'genuine' | undefined; factory: string; diff --git a/packages/deploy/src/data/deploy/ethereum-v1.5-pendle/config.json b/packages/deploy/src/data/deploy/ethereum-v1.5-pendle/config.json index 31863fa5..75dc39a4 100644 --- a/packages/deploy/src/data/deploy/ethereum-v1.5-pendle/config.json +++ b/packages/deploy/src/data/deploy/ethereum-v1.5-pendle/config.json @@ -163,6 +163,17 @@ } ] }, + { + "id": "eulerOracle", + "type": "euler", + "settings": [ + { + "quoteTokenId": "weth", + "baseTokenId": "wsteth", + "eulerOracle": "0x7c37aB8Cd76Ee8888ad7F19C1F8a3A6D1622e9B8" + } + ] + }, { "id": "compositeOracle", "type": "composite", @@ -187,6 +198,13 @@ "baseTokenId": "pt-usds-14aug2025", "quoteIntermediateOracleId": "chainlinkOracle", "intermediateBaseOracleId": "pendleMarketOracle" + }, + { + "quoteTokenId": "weth", + "intermediateTokenId": "wsteth", + "baseTokenId": "pt-steth-25dec2025", + "quoteIntermediateOracleId": "eulerOracle", + "intermediateBaseOracleId": "pendleMarketOracle" } ] } @@ -199,11 +217,27 @@ }, "marginlyPools": [ { - "id": "pt-susde-25sep2025-susde", - "baseTokenId": "pt-susde-25sep2025", + "id": "pt-steth-25dec2025-weth", + "baseTokenId": "pt-steth-25dec2025", + "quoteTokenId": "weth", + "priceOracleId": "compositeOracle", + "defaultSwapCallData": "33030145", + "params": { + "interestRate": "0.2%", + "maxLeverage": "20", + "swapFee": "0%", + "fee": "0%", + "mcSlippage": "0.1%", + "positionMinAmount": "0.05", + "quoteLimit": "250" + } + }, + { + "id": "pt-usde-31jul2025-susde", + "baseTokenId": "pt-usde-31jul2025", "quoteTokenId": "susde", - "priceOracleId": "pendleMarketOracle", - "defaultSwapCallData": "20447233", + "priceOracleId": "compositeOracle", + "defaultSwapCallData": "33030145", "params": { "interestRate": "0.6%", "maxLeverage": "20", @@ -211,15 +245,15 @@ "fee": "0%", "mcSlippage": "0.1%", "positionMinAmount": "5", - "quoteLimit": "500000" + "quoteLimit": "1000000" } }, { - "id": "pt-wstusr-25sep2025-usr", - "baseTokenId": "pt-wstusr-25sep2025", - "quoteTokenId": "usr", + "id": "pt-usds-14aug2025-susde", + "baseTokenId": "pt-usds-14aug2025", + "quoteTokenId": "susde", "priceOracleId": "compositeOracle", - "defaultSwapCallData": "35127297", + "defaultSwapCallData": "33030145", "params": { "interestRate": "0.6%", "maxLeverage": "20", @@ -227,7 +261,7 @@ "fee": "0%", "mcSlippage": "0.1%", "positionMinAmount": "5", - "quoteLimit": "2000000" + "quoteLimit": "1000000" } } ], diff --git a/packages/deploy/src/data/deploy/ethereum-v1.5-pendle/config_30_05_2025.json b/packages/deploy/src/data/deploy/ethereum-v1.5-pendle/config_30_05_2025.json deleted file mode 100644 index e09f4b6e..00000000 --- a/packages/deploy/src/data/deploy/ethereum-v1.5-pendle/config_30_05_2025.json +++ /dev/null @@ -1,378 +0,0 @@ -{ - "systemContextDefaults": { - "ethNodeUri": "https://ethereum-rpc.publicnode.com" - }, - "connection": { - "assertChainId": 1, - "ethOptions": {} - }, - "tokens": [ - { - "id": "weth", - "address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", - "assertSymbol": "WETH", - "assertDecimals": 18 - }, - { - "id": "susde", - "address": "0x9d39a5de30e57443bff2a8307a4256c8797a3497", - "assertSymbol": "sUSDe", - "assertDecimals": 18 - }, - { - "id": "usde", - "address": "0x4c9edd5852cd905f086c759e8383e09bff1e68b3", - "assertSymbol": "USDe", - "assertDecimals": 18 - }, - { - "id": "wsteth", - "address": "0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0", - "assertSymbol": "wstETH", - "assertDecimals": 18 - }, - { - "id": "usds", - "address": "0xdc035d45d973e3ec169d2276ddab16f1e407384f", - "assertSymbol": "USDS", - "assertDecimals": 18 - }, - { - "id": "usr", - "address": "0x66a1E37c9b0eAddca17d3662D6c05F4DECf3e110", - "assertSymbol": "USR", - "assertDecimals": 18 - }, - { - "id": "wstusr", - "address": "0x1202f5c7b4b9e47a1a484e8b270be34dbbc75055", - "assertSymbol": "wstUSR", - "assertDecimals": 18 - }, - { - "id": "pt-susde-25sep2025", - "address": "0x9f56094c450763769ba0ea9fe2876070c0fd5f77", - "assertSymbol": "PT-sUSDE-25SEP2025", - "assertDecimals": 18 - }, - { - "id": "pt-wstusr-25sep2025", - "address": "0x23e60d1488525bf4685f53b3aa8e676c30321066", - "assertSymbol": "PT-wstUSR-25SEP2025", - "assertDecimals": 18 - }, - { - "id": "pt-steth-25dec2025", - "address": "0xf99985822fb361117fcf3768d34a6353e6022f5f", - "assertSymbol": "PT-stETH-25DEC2025", - "assertDecimals": 18 - }, - { - "id": "pt-usde-31jul2025", - "address": "0x917459337caac939d41d7493b3999f571d20d667", - "assertSymbol": "PT-USDe-31JUL2025", - "assertDecimals": 18 - }, - { - "id": "pt-usds-14aug2025", - "address": "0xffec096c087c13cc268497b89a613cace4df9a48", - "assertSymbol": "PT-USDS-14AUG2025", - "assertDecimals": 18 - } - ], - "prices": [], - "priceOracles": [ - { - "id": "pendleMarketOracle", - "type": "pendleMarket", - "pendlePtLpOracle": "0x66a1096C6366b2529274dF4f5D8247827fe4CEA8", - "settings": [ - { - "quoteTokenId": "susde", - "baseTokenId": "pt-susde-25sep2025", - "pendleMarket": "0xa36b60a14a1a5247912584768c6e53e1a269a9f7", - "secondsAgo": "30 min", - "secondsAgoLiquidation": "5 sec" - }, - { - "quoteTokenId": "wstusr", - "baseTokenId": "pt-wstusr-25sep2025", - "pendleMarket": "0x09fa04aac9c6d1c6131352ee950cd67ecc6d4fb9", - "secondsAgo": "30 min", - "secondsAgoLiquidation": "5 sec" - }, - { - "quoteTokenId": "usde", - "baseTokenId": "pt-usde-31jul2025", - "pendleMarket": "0x9df192d13d61609d1852461c4850595e1f56e714", - "secondsAgo": "30 min", - "secondsAgoLiquidation": "5 sec" - }, - { - "quoteTokenId": "wsteth", - "baseTokenId": "pt-steth-25dec2025", - "pendleMarket": "0xc374f7ec85f8c7de3207a10bb1978ba104bda3b2", - "secondsAgo": "30 min", - "secondsAgoLiquidation": "5 sec" - }, - { - "quoteTokenId": "usds", - "baseTokenId": "pt-usds-14aug2025", - "pendleMarket": "0xdace1121e10500e9e29d071f01593fd76b000f08", - "secondsAgo": "30 min", - "secondsAgoLiquidation": "5 sec" - } - ] - }, - { - "id": "chainlinkOracle", - "type": "chainlink", - "sequencerFeed": "0x0000000000000000000000000000000000000000", - "settings": [ - { - "type": "double", - "quoteTokenId": "susde", - "intermediateTokenId": "usd", - "baseTokenId": "usde", - "quoteAggregatorV3": "0xFF3BC18cCBd5999CE63E788A1c250a88626aD099", - "baseAggregatorV3": "0xa569d910839Ae8865Da8F8e70FfFb0cBA869F961", - "maxPriceAge": "1500 min" - }, - { - "type": "double", - "quoteTokenId": "susde", - "intermediateTokenId": "usd", - "baseTokenId": "usds", - "quoteAggregatorV3": "0xFF3BC18cCBd5999CE63E788A1c250a88626aD099", - "baseAggregatorV3": "0xfF30586cD0F29eD462364C7e81375FC0C71219b1", - "maxPriceAge": "1500 min" - } - ] - }, - { - "id": "pythOracle", - "type": "pyth", - "pyth": "0x4305FB66699C3B2702D4d05CF36551390A4c69C6", - "settings": [ - { - "type": "single", - "quoteTokenId": "usr", - "baseTokenId": "wstusr", - "pythPriceId": "0xb74c2bc175c2dab850ce5a5451608501c293fe8410cb4aba7449dd1c355ab706", - "maxPriceAge": "1440 min" - } - ] - }, - { - "id": "compositeOracle", - "type": "composite", - "settings": [ - { - "quoteTokenId": "usr", - "intermediateTokenId": "wstusr", - "baseTokenId": "pt-wstusr-25sep2025", - "quoteIntermediateOracleId": "pythOracle", - "intermediateBaseOracleId": "pendleMarketOracle" - }, - { - "quoteTokenId": "susde", - "intermediateTokenId": "usde", - "baseTokenId": "pt-usde-31jul2025", - "quoteIntermediateOracleId": "chainlinkOracle", - "intermediateBaseOracleId": "pendleMarketOracle" - }, - { - "quoteTokenId": "susde", - "intermediateTokenId": "usds", - "baseTokenId": "pt-usds-14aug2025", - "quoteIntermediateOracleId": "chainlinkOracle", - "intermediateBaseOracleId": "pendleMarketOracle" - } - ] - } - ], - "marginlyFactory": { - "feeHolder": "0x601A564628f9467ea76945fdDC6F9C7604eE1C1E", - "techPositionOwner": "0xd48658962b93aa404fD56baa7FD07977a0EB05a9", - "wethTokenId": "weth", - "timelockOwner": "0x8cDAf202eBe2f38488074DcFCa08c0B0cB7B8Aa5" - }, - "marginlyPools": [ - { - "id": "pt-susde-25sep2025-susde", - "baseTokenId": "pt-susde-25sep2025", - "quoteTokenId": "susde", - "priceOracleId": "pendleMarketOracle", - "defaultSwapCallData": "20447233", - "params": { - "interestRate": "0.6%", - "maxLeverage": "20", - "swapFee": "0%", - "fee": "0%", - "mcSlippage": "0.1%", - "positionMinAmount": "5", - "quoteLimit": "500000" - } - }, - { - "id": "pt-wstusr-25sep2025-usr", - "baseTokenId": "pt-wstusr-25sep2025", - "quoteTokenId": "usr", - "priceOracleId": "compositeOracle", - "defaultSwapCallData": "35127297", - "params": { - "interestRate": "0.6%", - "maxLeverage": "20", - "swapFee": "0%", - "fee": "0%", - "mcSlippage": "0.1%", - "positionMinAmount": "5", - "quoteLimit": "2000000" - } - } - ], - "adapters": [ - { - "dexId": 19, - "adapterName": "PendleMarketAdapter", - "pools": [ - { - "poolAddress": "0xa36b60a14a1a5247912584768c6e53e1a269a9f7", - "slippage": "45", - "tokenAId": "pt-susde-25sep2025", - "tokenBId": "susde" - } - ] - }, - { - "dexId": 33, - "adapterName": "PendlePtToAssetAdapter", - "pools": [ - { - "pendleMarket": "0x09fa04aac9c6d1c6131352ee950cd67ecc6d4fb9", - "slippage": 35, - "tokenAId": "pt-wstusr-25sep2025", - "tokenBId": "usr" - } - ] - }, - { - "dexId": 31, - "adapterName": "PendleCurveRouterNgAdapter", - "curveRouter": "0x45312ea0eFf7E09C83CBE249fa1d7598c4C8cd4e", - "pools": [ - { - "description": "sUSDE/PT-USDS", - "pendleMarket": "0xdace1121e10500e9e29d071f01593fd76b000f08", - "slippage": 35, - "curveDxAdjustTokenToPt": -103100, - "curveDxAdjustPtToToken": 116000, - "curveRoute": [ - "0xdc035d45d973e3ec169d2276ddab16f1e407384f", - "0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD", - "0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD", - "0x3CEf1AFC0E8324b57293a6E7cE663781bbEFBB79", - "0x9d39a5de30e57443bff2a8307a4256c8797a3497", - "0x0000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000" - ], - "curveSwapParams": [ - [0, 1, 9, 0, 0], - [1, 0, 1, 1, 2], - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0] - ], - "curvePools": [ - "0x0000000000000000000000000000000000000000", - "0x3CEf1AFC0E8324b57293a6E7cE663781bbEFBB79", - "0x0000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000" - ] - }, - { - "description": "sUSDE/PT-USDE", - "pendleMarket": "0x9df192d13d61609d1852461c4850595e1f56e714", - "slippage": 35, - "curveDxAdjustTokenToPt": -145000, - "curveDxAdjustPtToToken": 175000, - "curveRoute": [ - "0x4c9edd5852cd905f086c759e8383e09bff1e68b3", - "0x5dc1BF6f1e983C0b21EfB003c105133736fA0743", - "0x853d955aCEf822Db058eb8505911ED77F175b99e", - "0xcE6431D21E3fb1036CE9973a3312368ED96F5CE7", - "0x83F20F44975D03b1b09e64809B757c47f942BEeA", - "0x167478921b907422F8E88B43C4Af2B8BEa278d3A", - "0x9d39a5de30e57443bff2a8307a4256c8797a3497", - "0x0000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000" - ], - "curveSwapParams": [ - [1, 0, 1, 1, 2], - [0, 1, 1, 1, 2], - [0, 1, 1, 1, 2], - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0] - ], - "curvePools": [ - "0x5dc1BF6f1e983C0b21EfB003c105133736fA0743", - "0xcE6431D21E3fb1036CE9973a3312368ED96F5CE7", - "0x167478921b907422F8E88B43C4Af2B8BEa278d3A", - "0x0000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000" - ] - }, - { - "description": "WETH / PT-stETH", - "pendleMarket": "0xc374f7ec85f8c7de3207a10bb1978ba104bda3b2", - "slippage": 35, - "curveDxAdjustTokenToPt": -900, - "curveDxAdjustPtToToken": 1000, - "curveRoute": [ - "0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0", - "0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0", - "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", - "0xDC24316b9AE028F1497c275EB9192a3Ea0f67022", - "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - "0x0000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000" - ], - "curveSwapParams": [ - [1, 0, 8, 0, 0], - [1, 0, 1, 1, 2], - [1, 0, 8, 0, 0], - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0] - ], - "curvePools": [ - "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", - "0x5dc1BF6f1e983C0b21EfB003c105133736fA0743", - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - "0x0000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000" - ] - } - ] - } - ], - "marginlyKeeper": { - "aaveKeeper": { - "aavePoolAddressesProvider": "0x2f39d218133AFaB8F2B819B1066c7E434Ad94E9e" - }, - "uniswapKeeper": true, - "balancerKeeper": { - "balancerVault": "0xBA12222222228d8Ba445958a75a0704d566BF2C8" - } - } -} diff --git a/packages/deploy/src/data/deploy/ethereum-v1.5-pendle/deployment.json b/packages/deploy/src/data/deploy/ethereum-v1.5-pendle/deployment.json index 637f8b33..189ed08c 100644 --- a/packages/deploy/src/data/deploy/ethereum-v1.5-pendle/deployment.json +++ b/packages/deploy/src/data/deploy/ethereum-v1.5-pendle/deployment.json @@ -104,6 +104,10 @@ "id": "pt-rsweth-26dec2024-rsweth", "address": "0x6b79E799Ebdb9e867A1fe268A0703cbAA532C60d" }, + { + "id": "pt-steth-25dec2025-weth", + "address": "0x47cD53c42d87ff7B371a5d6095f075dBdCc0bdA9" + }, { "id": "pt-susde-25sep2025-susde", "address": "0x849A31f96b240a58a2A70168e904d7C70B654c18" @@ -128,6 +132,14 @@ "id": "pt-usde-25jul2024-usdt", "address": "0xbd4a9ca12c5f3c12d1378843c0b91585475aed51" }, + { + "id": "pt-usde-31jul2025-susde", + "address": "0xFB016330aF77480D096381BE839d9C622F3642E9" + }, + { + "id": "pt-usds-14aug2025-susde", + "address": "0x5003ce3828f13839443dCa6ECD88b1FC5030c86d" + }, { "id": "pt-usr-29may2025-usr", "address": "0xfab0743428608412C609bed79aCf4878A38C99c4" diff --git a/packages/deploy/src/data/deploy/ethereum-v1.5-pendle/states/2025-02-28.json b/packages/deploy/src/data/deploy/ethereum-v1.5-pendle/states/2025-02-28.json index 55c8f033..50aa7928 100644 --- a/packages/deploy/src/data/deploy/ethereum-v1.5-pendle/states/2025-02-28.json +++ b/packages/deploy/src/data/deploy/ethereum-v1.5-pendle/states/2025-02-28.json @@ -263,6 +263,22 @@ "marginlyPool_pt-wstusr-25sep2025-usr": { "address": "0x11CCf4a4Db276eeAa4b13eDac4F945ce82bF2dA8", "txHash": "0x23b3a534ba69783c64b7b216c365c753e8233b6b317b18637f5213412e76193e" + }, + "priceOracle_eulerOracle": { + "address": "0xc83Fe597c6ED9E7Ae02A75096Faa4966567204c4", + "txHash": "0x5f063c3dc64a7b7b448db6282dcdaeaf88ba8b8923bf99d2b6bb8c451dba589b" + }, + "marginlyPool_pt-steth-25dec2025-weth": { + "address": "0x47cD53c42d87ff7B371a5d6095f075dBdCc0bdA9", + "txHash": "0x0ba30e6b8d17c8ad38fca48808ec2ca1501bf79c96b940eed322d9c08d684743" + }, + "marginlyPool_pt-usde-31jul2025-susde": { + "address": "0xFB016330aF77480D096381BE839d9C622F3642E9", + "txHash": "0x659f845908b2bdbbeea012367fc3c8483aae0b70342bc64f0dc085d1ece25bd1" + }, + "marginlyPool_pt-usds-14aug2025-susde": { + "address": "0x5003ce3828f13839443dCa6ECD88b1FC5030c86d", + "txHash": "0x3e146fe53a0ec00b230f3f955a1b68e31dc6f0505bc0ad62a8f33f0b734a941c" } } } \ No newline at end of file diff --git a/packages/deploy/src/deployer/PriceOracleDeployer.ts b/packages/deploy/src/deployer/PriceOracleDeployer.ts index a244b5b2..0a8afeaf 100644 --- a/packages/deploy/src/deployer/PriceOracleDeployer.ts +++ b/packages/deploy/src/deployer/PriceOracleDeployer.ts @@ -4,6 +4,7 @@ import { AlgebraOracleConfig, ChainlinkOracleConfig, CurveOracleConfig, + EulerPriceOracleConfig, isAavePriceOracle, isAlgebraDoubleOracle, isAlgebraOracle, @@ -11,6 +12,7 @@ import { isCurveOracle, isDoublePairChainlinkOracleConfig, isDoublePairPythOracleConfig, + isEulerPriceOracle, isMarginlyCompositeOracle, isPendleMarketOracle, isPendleOracle, @@ -201,17 +203,24 @@ export class PriceOracleDeployer extends BaseDeployer { var setupOracleScope = this.logger.beginScope(`SetUp ${config.id}`); for (const setting of config.settings) { + const settingName = `${setting.baseToken.id}/${setting.quoteToken.id}`; if (isSinglePairChainlinkOracleConfig(setting)) { const { address: baseToken } = tokenRepository.getTokenInfo(setting.baseToken.id); const { address: quoteToken } = tokenRepository.getTokenInfo(setting.quoteToken.id); const maxPriceAge = setting.maxPriceAge.toSeconds(); - await priceOracle.setPair( - quoteToken.toString(), - baseToken.toString(), - setting.aggregatorV3.toString(), - maxPriceAge - ); + const currentParams = await priceOracle.getParams(quoteToken.toString(), baseToken.toString()); + if (!BigNumber.from(currentParams.maxPriceAge).eq(maxPriceAge)) { + this.logger.log(`Set up ${settingName}\n`); + + const tx = await priceOracle.setPair( + quoteToken.toString(), + baseToken.toString(), + setting.aggregatorV3.toString(), + maxPriceAge + ); + tx.wait(); + } await this.checkOraclePrice(config.id, priceOracle, quoteToken.toString(), baseToken.toString()); } else if (isDoublePairChainlinkOracleConfig(setting)) { @@ -228,26 +237,35 @@ export class PriceOracleDeployer extends BaseDeployer { const maxPriceAge = setting.maxPriceAge.toSeconds(); - let tx = await priceOracle.setPair( - intermediateToken.toString(), - quoteToken.toString(), - setting.quoteAggregatorV3.toString(), - maxPriceAge - ); - await tx.wait(); - tx = await priceOracle.setPair( - intermediateToken.toString(), - baseToken.toString(), - setting.baseAggregatorV3.toString(), - maxPriceAge - ); - await tx.wait(); - tx = await priceOracle.setCompositePair( - quoteToken.toString(), - intermediateToken.toString(), - baseToken.toString() - ); - await tx.wait(); + const intermQuoteTokenParams = await priceOracle.getParams(quoteToken.toString(), intermediateToken.toString()); + const baseIntermediateParams = await priceOracle.getParams(intermediateToken.toString(), baseToken.toString()); + if ( + !BigNumber.from(intermQuoteTokenParams.maxPriceAge).eq(maxPriceAge) || + !BigNumber.from(baseIntermediateParams.maxPriceAge).eq(maxPriceAge) + ) { + this.logger.log(`Setup double ${settingName}\n`); + + let tx = await priceOracle.setPair( + intermediateToken.toString(), + quoteToken.toString(), + setting.quoteAggregatorV3.toString(), + maxPriceAge + ); + await tx.wait(); + tx = await priceOracle.setPair( + intermediateToken.toString(), + baseToken.toString(), + setting.baseAggregatorV3.toString(), + maxPriceAge + ); + await tx.wait(); + tx = await priceOracle.setCompositePair( + quoteToken.toString(), + intermediateToken.toString(), + baseToken.toString() + ); + await tx.wait(); + } await this.checkOraclePrice(config.id, priceOracle, quoteToken.toString(), baseToken.toString()); } else { @@ -589,11 +607,11 @@ export class PriceOracleDeployer extends BaseDeployer { const { address: intermediateToken } = tokenRepository.getTokenInfo(setting.intermediateToken.id); const { address: quoteToken } = tokenRepository.getTokenInfo(setting.quoteToken.id); - this.logger.log(`Add setting ${setting.baseToken.id}/${setting.quoteToken.id}`); - const currentParams = await priceOracle.getParams(quoteToken.toString(), baseToken.toString()); if (currentParams.intermediateToken.toLowerCase() !== intermediateToken.toString().toLowerCase()) { + this.logger.log(`Add setting ${setting.baseToken.id}/${setting.quoteToken.id}`); + const quoteIntermediateOracle = this.getRequiredPriceOracle(setting.quoteIntermediateOracleId); const baseIntermediateOracle = this.getRequiredPriceOracle(setting.intermediateBaseOracleId); @@ -693,6 +711,40 @@ export class PriceOracleDeployer extends BaseDeployer { return deploymentResult; } + private async deployEulerPriceOracle( + config: EulerPriceOracleConfig, + tokenRepository: ITokenRepository + ): Promise { + const deploymentResult = this.deploy( + 'EulerPriceOracle', + [], + `priceOracle_${config.id}`, + this.readMarginlyPeripheryOracleContract + ); + const priceOracle = (await deploymentResult).contract; + var setupOracleScope = this.logger.beginScope(`SetUp ${config.id}`); + + for (const setting of config.settings) { + const { address: baseToken } = tokenRepository.getTokenInfo(setting.baseToken.id); + const { address: quoteToken } = tokenRepository.getTokenInfo(setting.quoteToken.id); + const eulerOracle = setting.eulerOracle.toString(); + + const currentParams = await priceOracle.getParams(quoteToken.toString(), baseToken.toString()); + + if (currentParams.toLowerCase() !== eulerOracle.toLowerCase()) { + this.logger.log(`Add setting ${setting.baseToken.id}/${setting.quoteToken.id}`); + const tx = await priceOracle.addPair(quoteToken.toString(), baseToken.toString(), eulerOracle); + await tx.wait(); + } + + await this.checkOraclePrice(config.id, priceOracle, quoteToken.toString(), baseToken.toString()); + } + + setupOracleScope.close(); + + return deploymentResult; + } + public async deployPriceOracle( priceOracle: PriceOracleConfig, tokenRepository: ITokenRepository @@ -722,6 +774,8 @@ export class PriceOracleDeployer extends BaseDeployer { deploymentResult = await this.deployPriceOracleProxy(priceOracle, tokenRepository); } else if (isAavePriceOracle(priceOracle)) { deploymentResult = await this.deployAavePriceOracle(priceOracle, tokenRepository); + } else if (isEulerPriceOracle(priceOracle)) { + deploymentResult = await this.deployEulerPriceOracle(priceOracle, tokenRepository); } else { throw new Error(`Unknown priceOracle type`); } diff --git a/packages/deploy/src/deployer/configs.ts b/packages/deploy/src/deployer/configs.ts index 38f244b9..73324191 100644 --- a/packages/deploy/src/deployer/configs.ts +++ b/packages/deploy/src/deployer/configs.ts @@ -11,6 +11,7 @@ import { isCurveOracleConfig, isDoublePairChainlinkOracleDeployConfig, isDoublePairPythOracleDeployConfig, + isEulerPriceOracleConfig, isMarginlyCompositeOracleConfig, isMarginlyDeployConfigExistingToken, isMarginlyDeployConfigMintableToken, @@ -308,7 +309,8 @@ export type PriceOracleConfig = | CurveOracleConfig | MarginlyCompositeOracleConfig | PriceOracleProxyConfig - | AavePriceOracleConfig; + | AavePriceOracleConfig + | EulerPriceOracleConfig; export interface UniswapV3TickOracleConfig { id: string; @@ -514,6 +516,16 @@ export interface AavePriceOracleConfig { }[]; } +export interface EulerPriceOracleConfig { + id: string; + type: 'euler'; + settings: { + quoteToken: MarginlyConfigToken; + baseToken: MarginlyConfigToken; + eulerOracle: EthAddress; + }[]; +} + export function isUniswapV3Oracle(config: PriceOracleConfig): config is UniswapV3TickOracleConfig { return config.type === 'uniswapV3'; } @@ -562,6 +574,10 @@ export function isAavePriceOracle(config: PriceOracleConfig): config is AavePric return config.type === 'aave'; } +export function isEulerPriceOracle(config: PriceOracleConfig): config is EulerPriceOracleConfig { + return config.type === 'euler'; +} + export class StrictMarginlyDeployConfig { public readonly connection: EthConnectionConfig; public readonly tokens: MarginlyConfigToken[]; @@ -1204,6 +1220,20 @@ export class StrictMarginlyDeployConfig { }), }; + priceOracles.set(priceOracleId, strictConfig); + } else if (isEulerPriceOracleConfig(priceOracleConfig)) { + const strictConfig: EulerPriceOracleConfig = { + id: priceOracleId, + type: priceOracleConfig.type, + settings: priceOracleConfig.settings.map((x) => { + return { + quoteToken: this.getRequiredToken(tokens, x.quoteTokenId), + baseToken: this.getRequiredToken(tokens, x.baseTokenId), + eulerOracle: EthAddress.parse(x.eulerOracle), + }; + }), + }; + priceOracles.set(priceOracleId, strictConfig); } } diff --git a/packages/periphery/hardhat-configs/eth-fork.config.ts b/packages/periphery/hardhat-configs/eth-fork.config.ts index 7e6296a5..5515a4db 100644 --- a/packages/periphery/hardhat-configs/eth-fork.config.ts +++ b/packages/periphery/hardhat-configs/eth-fork.config.ts @@ -8,7 +8,7 @@ const config = { hardhat: { forking: { enabled: true, - url: 'https://rpc.ankr.com/eth', + url: 'https://ethereum-rpc.publicnode.com', blockNumber: 21814800, }, }, From ee26935059f47b956886f38cb920ebe87cac63eb Mon Sep 17 00:00:00 2001 From: rudewalt Date: Wed, 4 Jun 2025 11:35:36 +0300 Subject: [PATCH 3/3] check params --- .../int/PendleCurveRouterNg_WETH_stETH.eth.spec.ts | 11 ++++++----- .../int/PendleCurveRouterNg_sUSDE_USDE.eth.spec.ts | 9 ++++++--- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/packages/router/test/int/PendleCurveRouterNg_WETH_stETH.eth.spec.ts b/packages/router/test/int/PendleCurveRouterNg_WETH_stETH.eth.spec.ts index 48ff4112..d390ca81 100644 --- a/packages/router/test/int/PendleCurveRouterNg_WETH_stETH.eth.spec.ts +++ b/packages/router/test/int/PendleCurveRouterNg_WETH_stETH.eth.spec.ts @@ -38,8 +38,8 @@ async function initializeRouter(): Promise<{ const routeInput: PendleCurveRouterNgAdapter.RouteInputStruct = { pendleMarket: pendleMarket, slippage: 35, // 20/100 = 20% - curveDxAdjustTokenToPt: -900, // - curveDxAdjustPtToToken: 1000, // + curveDxAdjustPtToToken: 2000, // + curveDxAdjustTokenToPt: -800, // curveRoute: [ '0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0', // wstETH '0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0', // wstETH -> stETH @@ -93,6 +93,8 @@ async function initializeRouter(): Promise<{ ); expect(await ptToken.balanceOf(user.address)).to.be.eq(parseUnits('10000', 18)); + console.log(routeInput); + return { ptToken, quoteToken: WETH, @@ -108,8 +110,7 @@ async function initializeRouter(): Promise<{ // Tests for running in ethereum mainnet fork describe('Pendle PT-STETH - wETH', () => { before(async () => { - //await resetFork(22388154); //2025-05-02 - await resetFork(22588100); //2025-05-29 + await resetFork(22623241); }); describe('Pendle swap pre maturity', () => { @@ -135,7 +136,7 @@ describe('Pendle PT-STETH - wETH', () => { } = await initializeRouter()); }); - it.only('Curve check route', async () => { + it.skip('Curve check route', async () => { const curveRouter = ICurveRouterNg__factory.connect(curveRouterAddress, user); const invertedRoute: string[] = []; for (let i = 0; i < 11; i++) { diff --git a/packages/router/test/int/PendleCurveRouterNg_sUSDE_USDE.eth.spec.ts b/packages/router/test/int/PendleCurveRouterNg_sUSDE_USDE.eth.spec.ts index 81431f12..671573e4 100644 --- a/packages/router/test/int/PendleCurveRouterNg_sUSDE_USDE.eth.spec.ts +++ b/packages/router/test/int/PendleCurveRouterNg_sUSDE_USDE.eth.spec.ts @@ -33,8 +33,8 @@ async function initializeRouter(): Promise<{ const routeInput: PendleCurveRouterNgAdapter.RouteInputStruct = { pendleMarket: pendleMarket, slippage: 35, // 20/100 = 20% - curveDxAdjustTokenToPt: -145_000, // - curveDxAdjustPtToToken: 175_000, // + curveDxAdjustPtToToken: 175_800, // + curveDxAdjustTokenToPt: -149_000, // curveRoute: [ '0x4c9edd5852cd905f086c759e8383e09bff1e68b3', //usde '0x5dc1BF6f1e983C0b21EfB003c105133736fA0743', // usde -> frax @@ -86,6 +86,8 @@ async function initializeRouter(): Promise<{ parseUnits('10000', 18) ); + console.log(routeInput); + return { ptToken, sUSDEToken: sUSDEToken, @@ -100,7 +102,8 @@ async function initializeRouter(): Promise<{ // Tests for running in ethereum mainnet fork describe('Pendle PT-USDE - sUSDE', () => { before(async () => { - await resetFork(22388154); //2025-05-02 + await resetFork(22622910); + //await resetFork(22388154); //2025-05-02 //await resetFork(22588100); //2025-05-29 });