Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
54 changes: 54 additions & 0 deletions src/contracts/extensions/price-adapters/ScaledPriceAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.10;

import {AggregatorInterface} from '../../dependencies/chainlink/AggregatorInterface.sol';
import {IScaledPriceAdapter} from '../../interfaces/IScaledPriceAdapter.sol';

/**
* @title ScaledPriceAdapter
* @author Aave Labs
* @dev Price Adapter for Chainlink price feeds to scale price to standard USD unit of 8 decimals.
*/
contract ScaledPriceAdapter is IScaledPriceAdapter {
AggregatorInterface internal immutable _SOURCE;

uint8 internal constant _BASE_DECIMALS = 8;
bool internal immutable _SCALE_UP;
uint256 internal immutable _SCALE_FACTOR;

constructor(address source_) {
_SOURCE = AggregatorInterface(source_);
uint8 sourceDecimals = _SOURCE.decimals();
_SCALE_UP = sourceDecimals < _BASE_DECIMALS;
_SCALE_FACTOR =
10 ** (_SCALE_UP ? _BASE_DECIMALS - sourceDecimals : sourceDecimals - _BASE_DECIMALS);
}

/// @inheritdoc IScaledPriceAdapter
function latestAnswer() external view returns (int256) {
return
_SCALE_UP
? _SOURCE.latestAnswer() * int256(_SCALE_FACTOR)
: _SOURCE.latestAnswer() / int256(_SCALE_FACTOR);
}

/// @inheritdoc IScaledPriceAdapter
function description() external view returns (string memory) {
return string.concat(_SOURCE.description(), ' (USD Scaled)');
}

/// @inheritdoc IScaledPriceAdapter
function decimals() external pure returns (uint8) {
return _BASE_DECIMALS;
}

/// @inheritdoc IScaledPriceAdapter
function scale() external view returns (bool, uint256) {
return (_SCALE_UP, _SCALE_FACTOR);
}

/// @inheritdoc IScaledPriceAdapter
function source() external view returns (address) {
return address(_SOURCE);
}
}
33 changes: 33 additions & 0 deletions src/contracts/interfaces/IScaledPriceAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.10;

interface IScaledPriceAdapter {
/**
* @dev Direction and units used to scale latestAnswer to base decimals of 8.
* @dev Loses price precision by log10(scaleFactor) when scaling down.
* @return scaleUp Whether to scale up or down.
* @return scaleFactor The units to scale by.
*/
function scale() external view returns (bool scaleUp, uint256 scaleFactor);

/**
* @dev The decimals of price adapter.
*/
function decimals() external view returns (uint8);

/**
* @dev Underlying chainlink price source.
*/
function source() external view returns (address);

/**
* @dev Description of price adapter.
*/
function description() external view returns (string memory);

/**
* @dev Scaled `latestAnswer` from chainlink price feed.
* @dev Loses price precision by log10(scaleFactor) when scaling down.
*/
function latestAnswer() external view returns (int256);
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ contract MockAggregator {
return 1;
}

function decimals() external pure returns (uint8) {
function decimals() external view virtual returns (uint8) {
return 8;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

import {MockAggregator} from './MockAggregator.sol';

contract MockAggregatorMetadata is MockAggregator {
uint8 internal immutable _DECIMALS;

constructor(int256 initialAnswer_, uint8 decimals_) MockAggregator(initialAnswer_) {
_DECIMALS = decimals_;
}

function decimals() external view override returns (uint8) {
return _DECIMALS;
}
}
43 changes: 43 additions & 0 deletions tests/extensions/price-adaptors/ScaledPriceAdapter.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

import {MockAggregatorMetadata} from '../../../src/contracts/mocks/oracle/CLAggregators/MockAggregatorMetadata.sol';
import {ScaledPriceAdapter} from '../../../src/contracts/extensions/price-adapters/ScaledPriceAdapter.sol';
import {TestnetProcedures} from '../../utils/TestnetProcedures.sol';

contract ScaledPriceAdapterTests is TestnetProcedures {
function test_adapter_less_than_base() public {
test_fuzz_adapter({sourceDecimals: 2, price: 1e2});
test_fuzz_adapter({sourceDecimals: 6, price: 32.323e6});
}

function test_adapter_greater_than_base() public {
test_fuzz_adapter({sourceDecimals: 12, price: 1e12});
}

function test_adapter_equal_to_base() public {
test_fuzz_adapter({sourceDecimals: 8, price: 1e8});
}

function test_fuzz_adapter(uint256 sourceDecimals, int256 price) public {
sourceDecimals = bound(sourceDecimals, 1, 36);
price = bound(price, 1, int256(10 ** sourceDecimals));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is wrong no? because price will only go up to 1 USD then?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah yeah, good call; was originally written differently & got overlooked. the min is incorrect as well, the adaptor should forward 0 price as well
thinking of setting 1e10 source feed units

    price = bound(price, 0, int256(10 ** (10 + sourceDecimals)));

address source = address(new MockAggregatorMetadata(price, uint8(sourceDecimals)));
ScaledPriceAdapter adapter = new ScaledPriceAdapter(source);

(bool scaleUp, uint256 scale) = adapter.scale();
assertEq(adapter.decimals(), 8);
assertEq(scaleUp, adapter.decimals() > sourceDecimals);
assertEq(
scale,
10 ** (scaleUp ? adapter.decimals() - sourceDecimals : sourceDecimals - adapter.decimals())
);
assertEq(adapter.latestAnswer(), scaleUp ? price * int256(scale) : price / int256(scale));
assertEq(adapter.source(), source);
}

function test_adapter_invalid_source_feed() public {
vm.expectRevert();
new ScaledPriceAdapter(makeAddr('invalid'));
}
}
12 changes: 9 additions & 3 deletions tests/mocks/AaveV3TestListing.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
pragma solidity ^0.8.0;

import '../../src/contracts/extensions/v3-config-engine/AaveV3Payload.sol';
import {ScaledPriceAdapter} from '../../src/contracts/extensions/price-adapters/ScaledPriceAdapter.sol';
import {TestnetRwaERC20} from '../../src/contracts/mocks/testnet-helpers/TestnetRwaERC20.sol';
import {TestnetERC20} from '../../src/contracts/mocks/testnet-helpers/TestnetERC20.sol';
import {MockAggregator} from '../../src/contracts/mocks/oracle/CLAggregators/MockAggregator.sol';
import {MockAggregatorMetadata} from '../../src/contracts/mocks/oracle/CLAggregators/MockAggregatorMetadata.sol';
import {ACLManager} from '../../src/contracts/protocol/configuration/ACLManager.sol';
import {MarketReport} from '../../src/deployments/interfaces/IMarketReportTypes.sol';
import {IPoolConfigurator, ConfiguratorInputTypes} from '../../src/contracts/interfaces/IPoolConfigurator.sol';
Expand Down Expand Up @@ -62,13 +64,17 @@ contract AaveV3TestListing is AaveV3Payload {
WETH_MOCK_PRICE_FEED = address(new MockAggregator(1800e8));

BUIDL_ADDRESS = address(new TestnetRwaERC20('BUIDL', 'BUIDL', 6, erc20Owner));
BUIDL_MOCK_PRICE_FEED = address(new MockAggregator(1e8));
BUIDL_MOCK_PRICE_FEED = address(
new ScaledPriceAdapter(address(new MockAggregatorMetadata(1e2, 2)))
);

USTB_ADDRESS = address(new TestnetRwaERC20('USTB', 'USTB', 6, erc20Owner));
USTB_MOCK_PRICE_FEED = address(new MockAggregator(10e8));
USTB_MOCK_PRICE_FEED = address(
new ScaledPriceAdapter(address(new MockAggregatorMetadata(10e6, 6)))
);

WTGXX_ADDRESS = address(new TestnetRwaERC20('WTGXX', 'WTGXX', 18, erc20Owner));
WTGXX_MOCK_PRICE_FEED = address(new MockAggregator(1e8));
WTGXX_MOCK_PRICE_FEED = address(new ScaledPriceAdapter(address(new MockAggregator(1e8))));

GHO_ADDRESS = address(new TestnetERC20('GHO', 'GHO', 18, erc20Owner));
GHO_MOCK_PRICE_FEED = address(new MockAggregator(1e8));
Expand Down
Loading