Skip to content

Commit

Permalink
Merge pull request #64 from bgd-labs/feat/eurc
Browse files Browse the repository at this point in the history
feat: eur stable price cap adapter
  • Loading branch information
brotherlymite authored Feb 28, 2025
2 parents 9a01d9c + 867d04b commit f3c3d7b
Show file tree
Hide file tree
Showing 5 changed files with 209 additions and 1 deletion.
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
27 changes: 26 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,23 @@ 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),
ratioDecimals: 8
})
)
);
}
}

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

contract DeployEURCBase is BaseScript {
function run() external broadcast {
GovV3Helpers.deployDeterministic(CapAdaptersCodeBase.EURCAdapterCode());
}
}
99 changes: 99 additions & 0 deletions src/contracts/misc-adapters/EURPriceCapAdapterStable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// 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 IEURPriceCapAdapterStable
uint8 public immutable RATIO_DECIMALS;

/// @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.baseToUsdAggregator;
ACL_MANAGER = capAdapterStableParams.aclManager;
RATIO_DECIMALS = capAdapterStableParams.ratioDecimals;
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) / int256(10 ** RATIO_DECIMALS);

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) / int256(10 ** RATIO_DECIMALS) < assetPrice) {
revert CapLowerThanActualPrice();
}

_priceCapRatio = priceCapRatio;

emit PriceCapRatioUpdated(priceCapRatio);
}
}
67 changes: 67 additions & 0 deletions src/interfaces/IEURPriceCapAdapterStable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// 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;
uint8 ratioDecimals;
}

/**
* @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 Number of decimals of the priceCap ratio
*/
function RATIO_DECIMALS() external view returns (uint8);

/**
* @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})
)
{}
}

0 comments on commit f3c3d7b

Please sign in to comment.