diff --git a/foundry.toml b/foundry.toml index b1afa9b..b4293ed 100644 --- a/foundry.toml +++ b/foundry.toml @@ -9,6 +9,7 @@ number_underscore = 'thousands' wrap_comments = false [profile.default] +evm_version = "cancun" auto_detect_solc = true src = "src" out = "out" @@ -16,4 +17,4 @@ libs = ["lib"] [rpc_endpoints] mainnet = "${ARB_MAINNET_RPC}" -sepolia = "${ARB_SEPOLIA_RPC}" \ No newline at end of file +sepolia = "${ARB_SEPOLIA_RPC}" diff --git a/script/Registry.s.sol b/script/Registry.s.sol index 4bf4b55..3e8ec1a 100644 --- a/script/Registry.s.sol +++ b/script/Registry.s.sol @@ -101,3 +101,8 @@ address constant ARB = 0x912CE59144191C1204E64559FE8253a0e49E6548; address constant ETH_ARB_POOL = 0xe51635ae8136aBAc44906A8f230C2D235E9c195F; address constant MAINNET_DEPLOYER = 0xF78dA2A37049627636546E0cFAaB2aD664950917; + +//Pendle +address constant MAINNET_PENDLE_ORACLE = 0x9a9Fa8338dd5E5B2188006f1Cd2Ef26d921650C2; +address constant MAINNET_PENDLE_RETH_MARKET = 0x14FbC760eFaF36781cB0eb3Cb255aD976117B9Bd; +address constant MAINNET_PENDLE_STETH_MARKET = 0x08a152834de126d2ef83D612ff36e4523FD0017F; diff --git a/src/contracts/factories/pendle/PendleRelayerFactory.sol b/src/contracts/factories/pendle/PendleRelayerFactory.sol index 35d6649..06104e3 100644 --- a/src/contracts/factories/pendle/PendleRelayerFactory.sol +++ b/src/contracts/factories/pendle/PendleRelayerFactory.sol @@ -6,6 +6,7 @@ import {PendlePtToSyRelayerChild} from '@contracts/factories/pendle/PendlePtToSy import {PendleYtToSyRelayerChild} from '@contracts/factories/pendle/PendleYtToSyRelayerChild.sol'; import {PendleLpToSyRelayerChild} from '@contracts/factories/pendle/PendleLpToSyRelayerChild.sol'; import {Authorizable} from '@contracts/utils/Authorizable.sol'; +import 'forge-std/console2.sol'; contract PendleRelayerFactory is Authorizable { uint256 public relayerId; diff --git a/src/contracts/oracles/pendle/PendleLpToSyRelayer.sol b/src/contracts/oracles/pendle/PendleLpToSyRelayer.sol index b583f82..735029c 100644 --- a/src/contracts/oracles/pendle/PendleLpToSyRelayer.sol +++ b/src/contracts/oracles/pendle/PendleLpToSyRelayer.sol @@ -22,14 +22,14 @@ contract PendleLpToSyRelayer { constructor(address _market, address _oracle, uint32 _twapDuration) { require(_market != address(0) && _oracle != address(0), 'Invalid address'); - require(twapDuration != 0, 'Invalid TWAP duration'); + require(_twapDuration != 0, 'Invalid TWAP duration'); market = IPMarket(_market); oracle = IPOracle(_oracle); twapDuration = _twapDuration; (SY, PT, YT) = market.readTokens(); - symbol = string(abi.encodePacked(market.symbol())); + symbol = string(abi.encodePacked('LP => ', SY.symbol())); // test if oracle is ready (bool increaseCardinalityRequired,, bool oldestObservationSatisfied) = oracle.getOracleState(_market, _twapDuration); // It's required to call IPMarket(market).increaseObservationsCardinalityNext(cardinalityRequired) and wait diff --git a/src/contracts/oracles/pendle/PendlePtToSyRelayer.sol b/src/contracts/oracles/pendle/PendlePtToSyRelayer.sol index 3da9c48..94a9352 100644 --- a/src/contracts/oracles/pendle/PendlePtToSyRelayer.sol +++ b/src/contracts/oracles/pendle/PendlePtToSyRelayer.sol @@ -3,12 +3,13 @@ pragma solidity 0.7.6; import '@interfaces/oracles/pendle/IPOracle.sol'; import '@interfaces/oracles/pendle/IPMarket.sol'; - +import 'forge-std/console2.sol'; /** * @title PendleRelayer * @notice This contracts transforms a Pendle TWAP price feed into a standard IBaseOracle feed * */ + contract PendlePtToSyRelayer { IStandardizedYield public SY; IPPrincipalToken public PT; @@ -22,14 +23,15 @@ contract PendlePtToSyRelayer { constructor(address _market, address _oracle, uint32 _twapDuration) { require(_market != address(0) && _oracle != address(0), 'Invalid address'); - require(twapDuration != 0, 'Invalid TWAP duration'); + require(_twapDuration != uint32(0), 'Invalid TWAP duration'); market = IPMarket(_market); oracle = IPOracle(_oracle); twapDuration = _twapDuration; (SY, PT, YT) = market.readTokens(); - symbol = string(abi.encodePacked(market.symbol())); + symbol = string(abi.encodePacked(PT.symbol(), ' => ', SY.symbol())); + // test if oracle is ready (bool increaseCardinalityRequired,, bool oldestObservationSatisfied) = oracle.getOracleState(_market, _twapDuration); // It's required to call IPMarket(market).increaseObservationsCardinalityNext(cardinalityRequired) and wait diff --git a/src/contracts/oracles/pendle/PendleYtToSyRelayer.sol b/src/contracts/oracles/pendle/PendleYtToSyRelayer.sol index 094a2a3..c4474ff 100644 --- a/src/contracts/oracles/pendle/PendleYtToSyRelayer.sol +++ b/src/contracts/oracles/pendle/PendleYtToSyRelayer.sol @@ -22,14 +22,14 @@ contract PendleYtToSyRelayer { constructor(address _market, address _oracle, uint32 _twapDuration) { require(_market != address(0) && _oracle != address(0), 'Invalid address'); - require(twapDuration != 0, 'Invalid TWAP duration'); + require(_twapDuration != 0, 'Invalid TWAP duration'); market = IPMarket(_market); oracle = IPOracle(_oracle); twapDuration = _twapDuration; (SY, PT, YT) = market.readTokens(); - symbol = string(abi.encodePacked(market.symbol())); + symbol = string(abi.encodePacked(YT.symbol(), ' => ', SY.symbol())); // test if oracle is ready (bool increaseCardinalityRequired,, bool oldestObservationSatisfied) = oracle.getOracleState(_market, _twapDuration); // It's required to call IPMarket(market).increaseObservationsCardinalityNext(cardinalityRequired) and wait diff --git a/src/interfaces/factories/IPendleRelayerFactory.sol b/src/interfaces/factories/IPendleRelayerFactory.sol index de08356..44ec165 100644 --- a/src/interfaces/factories/IPendleRelayerFactory.sol +++ b/src/interfaces/factories/IPendleRelayerFactory.sol @@ -2,14 +2,16 @@ pragma solidity ^0.7.6; import {IBaseOracle} from '@interfaces/oracles/IBaseOracle.sol'; +import {IAuthorizable} from '@interfaces/utils/IAuthorizable.sol'; -interface IPendleRelayerFactory { +interface IPendleRelayerFactory is IAuthorizable { // --- Events --- event NewPendlePtRelayer(address indexed _market, address _oracle, uint32 _twapDuration); event NewPendleYtRelayer(address indexed _market, address _oracle, uint32 _twapDuration); event NewPendleLpRelayer(address indexed _market, address _oracle, uint32 _twapDuration); // --- Methods --- + function relayerId() external view returns (uint256); function deployPendlePtRelayer( address _market, diff --git a/src/interfaces/oracles/pendle/IPendleRelayer.sol b/src/interfaces/oracles/pendle/IPendleRelayer.sol new file mode 100644 index 0000000..1968db8 --- /dev/null +++ b/src/interfaces/oracles/pendle/IPendleRelayer.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.7.6; + +import {IBaseOracle} from '@interfaces/oracles/IBaseOracle.sol'; +import {IPMarket} from '@interfaces/oracles/pendle/IPMarket.sol'; +import {IPOracle} from '@interfaces/oracles/pendle/IPOracle.sol'; +import {IStandardizedYield} from '@interfaces/oracles/pendle/IStandardizedYield.sol'; +import {IPPrincipalToken} from '@interfaces/oracles/pendle/IPPrincipalToken.sol'; +import {IPYieldToken} from '@interfaces/oracles/pendle/IPYieldToken.sol'; + +interface IPendleRelayer is IBaseOracle { + function twapDuration() external view returns (uint32); + function market() external view returns (IPMarket); + function oracle() external view returns (IPOracle); + function SY() external view returns (IStandardizedYield); + function PT() external view returns (IPPrincipalToken); + function YT() external view returns (IPYieldToken); +} diff --git a/test/unit/RelayerFactories.t.sol b/test/unit/RelayerFactories.t.sol index 0452169..7aab628 100644 --- a/test/unit/RelayerFactories.t.sol +++ b/test/unit/RelayerFactories.t.sol @@ -11,10 +11,14 @@ import {CamelotRelayerFactory} from '@contracts/factories/CamelotRelayerFactory. import {CamelotRelayerChild} from '@contracts/factories/CamelotRelayerChild.sol'; import {ChainlinkRelayerFactory} from '@contracts/factories/ChainlinkRelayerFactory.sol'; import {ChainlinkRelayerChild} from '@contracts/factories/ChainlinkRelayerChild.sol'; +import {PendleRelayerFactory} from '@contracts/factories/pendle/PendleRelayerFactory.sol'; import {IBaseOracle} from '@interfaces/oracles/IBaseOracle.sol'; import {DenominatedOracleFactory} from '@contracts/factories/DenominatedOracleFactory.sol'; import {DenominatedOracleChild} from '@contracts/factories/DenominatedOracleChild.sol'; +import {MAINNET_PENDLE_ORACLE, MAINNET_PENDLE_RETH_MARKET} from '@script/Registry.s.sol'; import {IAuthorizable} from '@interfaces/utils/IAuthorizable.sol'; +import {IPendleRelayerFactory} from '@interfaces/factories/IPendleRelayerFactory.sol'; +import {IPendleRelayer} from '@interfaces/oracles/pendle/IPendleRelayer.sol'; abstract contract Base is DSTestPlus { address deployer = label('deployer'); @@ -381,3 +385,72 @@ contract Unit_DenominatedPriceOracleFactory_DeployDenominatedOracle is Base { ); } } + +contract Unit_PendleRelayerFactory_DeployPendleOracles is Base { + IPendleRelayerFactory public pendleFactory; + + function setUp() public virtual override { + super.setUp(); + vm.createSelectFork(vm.envString('ARB_MAINNET_RPC')); + pendleFactory = IPendleRelayerFactory(address(new PendleRelayerFactory())); + } + + function test_Deploy_PendleFactory() public { + assertEq(pendleFactory.relayerId(), 0); + assertEq(pendleFactory.authorizedAccounts()[0], address(this)); + } + + function test_Deploy_PT_Oracle() public { + IBaseOracle ptOracle = + pendleFactory.deployPendlePtRelayer(MAINNET_PENDLE_RETH_MARKET, MAINNET_PENDLE_ORACLE, uint32(900)); + assertTrue(keccak256(abi.encode(ptOracle.symbol())) != keccak256(abi.encode(''))); + assertEq(uint256(IPendleRelayer(address(ptOracle)).twapDuration()), 900); + assertEq(address(IPendleRelayer(address(ptOracle)).market()), MAINNET_PENDLE_RETH_MARKET); + assertEq(address(IPendleRelayer(address(ptOracle)).oracle()), MAINNET_PENDLE_ORACLE); + assertEq(address(IPendleRelayer(address(ptOracle)).PT()), 0x685155D3BD593508Fe32Be39729810A591ED9c87); + assertEq(address(IPendleRelayer(address(ptOracle)).YT()), 0xe822AE44EB2466B4E263b1cbC94b4833dDEf9700); + assertEq(address(IPendleRelayer(address(ptOracle)).SY()), 0xc0Cf4b266bE5B3229C49590B59E67A09c15b22f4); + } + + function test_Deploy_YT_Oracle() public { + IBaseOracle ytOracle = + pendleFactory.deployPendleYtRelayer(MAINNET_PENDLE_RETH_MARKET, MAINNET_PENDLE_ORACLE, uint32(900)); + assertTrue(keccak256(abi.encode(ytOracle.symbol())) != keccak256(abi.encode(''))); + assertEq(uint256(IPendleRelayer(address(ytOracle)).twapDuration()), 900); + assertEq(address(IPendleRelayer(address(ytOracle)).market()), MAINNET_PENDLE_RETH_MARKET); + assertEq(address(IPendleRelayer(address(ytOracle)).oracle()), MAINNET_PENDLE_ORACLE); + assertEq(address(IPendleRelayer(address(ytOracle)).PT()), 0x685155D3BD593508Fe32Be39729810A591ED9c87); + assertEq(address(IPendleRelayer(address(ytOracle)).YT()), 0xe822AE44EB2466B4E263b1cbC94b4833dDEf9700); + assertEq(address(IPendleRelayer(address(ytOracle)).SY()), 0xc0Cf4b266bE5B3229C49590B59E67A09c15b22f4); + } + + function test_Deploy_LP_Oracle() public { + IBaseOracle lpOracle = + pendleFactory.deployPendleLpRelayer(MAINNET_PENDLE_RETH_MARKET, MAINNET_PENDLE_ORACLE, uint32(900)); + assertTrue(keccak256(abi.encode(lpOracle.symbol())) != keccak256(abi.encode(''))); + assertEq(uint256(IPendleRelayer(address(lpOracle)).twapDuration()), 900); + assertEq(address(IPendleRelayer(address(lpOracle)).market()), MAINNET_PENDLE_RETH_MARKET); + assertEq(address(IPendleRelayer(address(lpOracle)).oracle()), MAINNET_PENDLE_ORACLE); + assertEq(address(IPendleRelayer(address(lpOracle)).PT()), 0x685155D3BD593508Fe32Be39729810A591ED9c87); + assertEq(address(IPendleRelayer(address(lpOracle)).YT()), 0xe822AE44EB2466B4E263b1cbC94b4833dDEf9700); + assertEq(address(IPendleRelayer(address(lpOracle)).SY()), 0xc0Cf4b266bE5B3229C49590B59E67A09c15b22f4); + } + + function test_Deploy_Oracle_Revert_Invalid_Twap() public { + vm.expectRevert('Invalid TWAP duration'); + pendleFactory.deployPendlePtRelayer(MAINNET_PENDLE_RETH_MARKET, MAINNET_PENDLE_ORACLE, uint32(0)); + vm.expectRevert('Invalid TWAP duration'); + pendleFactory.deployPendleYtRelayer(MAINNET_PENDLE_RETH_MARKET, MAINNET_PENDLE_ORACLE, uint32(0)); + vm.expectRevert('Invalid TWAP duration'); + pendleFactory.deployPendleLpRelayer(MAINNET_PENDLE_RETH_MARKET, MAINNET_PENDLE_ORACLE, uint32(0)); + } + + function test_Deploy_Oracle_Revert_Invalid_Address() public { + vm.expectRevert('Invalid address'); + pendleFactory.deployPendlePtRelayer(address(0), MAINNET_PENDLE_ORACLE, uint32(900)); + vm.expectRevert('Invalid address'); + pendleFactory.deployPendleYtRelayer(address(0), MAINNET_PENDLE_ORACLE, uint32(900)); + vm.expectRevert('Invalid address'); + pendleFactory.deployPendleLpRelayer(address(0), MAINNET_PENDLE_ORACLE, uint32(900)); + } +}