Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: eur stable price cap adapter #64

Merged
merged 7 commits into from
Feb 28, 2025
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ deploy-weETH-zksync :; forge script --zksync scripts/DeployZkSync.s.sol:DeployWe
deploy-sUSDe-zksync :; forge script --zksync scripts/DeployZkSync.s.sol:DeploySUSDeZkSync --rpc-url zksync $(common-flags)
deploy-USDe-zksync :; forge script --zksync scripts/DeployZkSync.s.sol:DeployUSDeZkSync --rpc-url zksync $(common-flags)

deploy-eurc-base :; forge script scripts/DeployBase.s.sol:DeployEURCBase --rpc-url base $(common-flags)

# Utilities
download :; cast etherscan-source --chain ${chain} -d src/etherscan/${chain}_${address} ${address}
git-diff :
Expand Down
26 changes: 25 additions & 1 deletion scripts/DeployBase.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ pragma solidity ^0.8.0;
import {GovV3Helpers} from 'aave-helpers/GovV3Helpers.sol';
import {BaseScript} from 'solidity-utils/contracts/utils/ScriptUtils.sol';
import {AaveV3Base, AaveV3BaseAssets} from 'aave-address-book/AaveV3Base.sol';

import {EURPriceCapAdapterStable, IEURPriceCapAdapterStable, IChainlinkAggregator} from '../src/contracts/misc-adapters/EURPriceCapAdapterStable.sol';
import {CLRatePriceCapAdapter, IPriceCapAdapter} from '../src/contracts/CLRatePriceCapAdapter.sol';

library CapAdaptersCodeBase {
address public constant weETH_eETH_AGGREGATOR = 0x35e9D7001819Ea3B39Da906aE6b06A62cfe2c181;
address public constant ezETH_ETH_AGGREGATOR = 0xC4300B7CF0646F0Fe4C5B2ACFCCC4dCA1346f5d8;
address public constant EURC_PRICE_FEED = 0xDAe398520e2B67cd3f27aeF9Cf14D93D927f8250;
address public constant EUR_PRICE_FEED = 0xc91D87E81faB8f93699ECf7Ee9B44D11e1D53F0F;

function weETHAdapterCode() internal pure returns (bytes memory) {
return
Expand Down Expand Up @@ -52,6 +54,22 @@ library CapAdaptersCodeBase {
)
);
}

function EURCAdapterCode() internal pure returns (bytes memory) {
return
abi.encodePacked(
type(EURPriceCapAdapterStable).creationCode,
abi.encode(
IEURPriceCapAdapterStable.CapAdapterStableParamsEUR({
aclManager: AaveV3Base.ACL_MANAGER,
assetToUsdAggregator: IChainlinkAggregator(EURC_PRICE_FEED),
baseToUsdAggregator: IChainlinkAggregator(EUR_PRICE_FEED),
adapterDescription: 'Capped EURC/USD',
priceCapRatio: int256(1.04 * 1e8)
})
)
);
}
}

contract DeployWeEthBase is BaseScript {
Expand All @@ -65,3 +83,9 @@ contract DeployEzEthBase is BaseScript {
GovV3Helpers.deployDeterministic(CapAdaptersCodeBase.ezETHAdapterCode());
}
}

contract DeployEURCBase is BaseScript {
function run() external broadcast {
GovV3Helpers.deployDeterministic(CapAdaptersCodeBase.EURCAdapterCode());
}
}
4 changes: 2 additions & 2 deletions src/contracts/PriceCapAdapterStable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ contract PriceCapAdapterStable is IPriceCapAdapterStable {
}

/// @inheritdoc ICLSynchronicityPriceAdapter
function latestAnswer() external view override returns (int256) {
function latestAnswer() external view override virtual returns (int256) {
int256 basePrice = ASSET_TO_USD_AGGREGATOR.latestAnswer();
int256 priceCap = _priceCap;

Expand Down Expand Up @@ -74,7 +74,7 @@ contract PriceCapAdapterStable is IPriceCapAdapterStable {
* @notice Updates price cap
* @param priceCap the new price cap
*/
function _setPriceCap(int256 priceCap) internal {
function _setPriceCap(int256 priceCap) internal virtual {
int256 basePrice = ASSET_TO_USD_AGGREGATOR.latestAnswer();

if (priceCap < basePrice) {
Expand Down
95 changes: 95 additions & 0 deletions src/contracts/misc-adapters/EURPriceCapAdapterStable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.19;

import {IEURPriceCapAdapterStable, ICLSynchronicityPriceAdapter, IACLManager, IChainlinkAggregator} from '../../interfaces/IEURPriceCapAdapterStable.sol';

/**
* @title EURPriceCapAdapterStable
* @author BGD Labs
* @notice Price capped adapter to cap the price of the EUR asset using the
* @notice chainlink market feeds ASSET/USD and EUR/USD
*/
contract EURPriceCapAdapterStable is IEURPriceCapAdapterStable {
/// @inheritdoc IEURPriceCapAdapterStable
IChainlinkAggregator public immutable ASSET_TO_USD_AGGREGATOR;

/// @inheritdoc IEURPriceCapAdapterStable
IChainlinkAggregator public immutable BASE_TO_USD_AGGREGATOR;

/// @inheritdoc IEURPriceCapAdapterStable
IACLManager public immutable ACL_MANAGER;

/// @inheritdoc ICLSynchronicityPriceAdapter
uint8 public decimals;

/// @inheritdoc ICLSynchronicityPriceAdapter
string public description;

int256 internal _priceCapRatio;

/**
* @param capAdapterStableParams parameters to create eur stable cap adapter
*/
constructor(CapAdapterStableParamsEUR memory capAdapterStableParams) {
if (address(capAdapterStableParams.aclManager) == address(0)) {
revert ACLManagerIsZeroAddress();
}

ASSET_TO_USD_AGGREGATOR = capAdapterStableParams.assetToUsdAggregator;
BASE_TO_USD_AGGREGATOR = capAdapterStableParams.assetToUsdAggregator;
ACL_MANAGER = capAdapterStableParams.aclManager;
description = capAdapterStableParams.adapterDescription;
decimals = ASSET_TO_USD_AGGREGATOR.decimals();

_setPriceCapRatio(capAdapterStableParams.priceCapRatio);
}

/// @inheritdoc ICLSynchronicityPriceAdapter
function latestAnswer() external view returns (int256) {
int256 assetPrice = ASSET_TO_USD_AGGREGATOR.latestAnswer();
int256 basePrice = BASE_TO_USD_AGGREGATOR.latestAnswer();
int256 maxPrice = (basePrice * _priceCapRatio) / 1e8;

if (assetPrice > maxPrice) {
return maxPrice;
}

return assetPrice;
}

/// @inheritdoc IEURPriceCapAdapterStable
function setPriceCapRatio(int256 priceCapRatio) external {
if (!ACL_MANAGER.isRiskAdmin(msg.sender) && !ACL_MANAGER.isPoolAdmin(msg.sender)) {
revert CallerIsNotRiskOrPoolAdmin();
}

_setPriceCapRatio(priceCapRatio);
}

/// @inheritdoc IEURPriceCapAdapterStable
function getPriceCapRatio() external view returns (int256) {
return _priceCapRatio;
}

/// @inheritdoc IEURPriceCapAdapterStable
function isCapped() public view virtual returns (bool) {
return (ASSET_TO_USD_AGGREGATOR.latestAnswer() > this.latestAnswer());
}

/**
* @notice Updates price cap ratio
* @param priceCapRatio the new price cap ratio
*/
function _setPriceCapRatio(int256 priceCapRatio) internal virtual {
int256 assetPrice = ASSET_TO_USD_AGGREGATOR.latestAnswer();
int256 basePrice = BASE_TO_USD_AGGREGATOR.latestAnswer();

if ((basePrice * priceCapRatio) / 1e8 < assetPrice) {
revert CapLowerThanActualPrice();
}

_priceCapRatio = priceCapRatio;

emit PriceCapRatioUpdated(priceCapRatio);
}
}
61 changes: 61 additions & 0 deletions src/interfaces/IEURPriceCapAdapterStable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {IACLManager} from 'aave-address-book/AaveV3.sol';
import {IChainlinkAggregator} from 'cl-synchronicity-price-adapter/interfaces/IChainlinkAggregator.sol';
import {ICLSynchronicityPriceAdapter} from 'cl-synchronicity-price-adapter/interfaces/ICLSynchronicityPriceAdapter.sol';

interface IEURPriceCapAdapterStable is ICLSynchronicityPriceAdapter {
/**
* @notice Parameters to create eur stable cap adapter
* @param capAdapterStableParams parameters to create eur stable cap adapter
*/
struct CapAdapterStableParamsEUR {
IACLManager aclManager;
IChainlinkAggregator assetToUsdAggregator;
IChainlinkAggregator baseToUsdAggregator;
string adapterDescription;
int256 priceCapRatio;
}

/**
* @dev Emitted when the price cap ratio gets updated
* @param priceCapRatio the new price cap ratio
**/
event PriceCapRatioUpdated(int256 priceCapRatio);

/**
* @notice Price feed for (ASSET / USD) pair
*/
function ASSET_TO_USD_AGGREGATOR() external view returns (IChainlinkAggregator);

/**
* @notice Price feed for (BASE / USD) pair
*/
function BASE_TO_USD_AGGREGATOR() external view returns (IChainlinkAggregator);

/**
* @notice ACL manager contract
*/
function ACL_MANAGER() external view returns (IACLManager);

/**
* @notice Updates price cap ratio
* @param priceCapRatio the new price cap ratio
*/
function setPriceCapRatio(int256 priceCapRatio) external;

/**
* @notice Get price cap ratio value
*/
function getPriceCapRatio() external view returns (int256);

/**
* @notice Returns if the price is currently capped
*/
function isCapped() external view returns (bool);

error ACLManagerIsZeroAddress();
error CallerIsNotRiskOrPoolAdmin();
error CapLowerThanActualPrice();
}
15 changes: 15 additions & 0 deletions tests/base/EURCPriceCapAdapter.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

import '../BaseStableTest.sol';
import {CapAdaptersCodeBase} from '../../scripts/DeployBase.s.sol';

contract EURCBasePriceCapAdapterTest is BaseStableTest {
constructor()
BaseStableTest(
CapAdaptersCodeBase.EURCAdapterCode(),
10,
ForkParams({network: 'base', blockNumber: 26853575})
)
{}
}
Loading