Skip to content

Commit 66f0d6a

Browse files
committed
feat: scaled price adaptor
1 parent 2ac5c2f commit 66f0d6a

File tree

6 files changed

+145
-3
lines changed

6 files changed

+145
-3
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// SPDX-License-Identifier: BUSL-1.1
2+
pragma solidity ^0.8.10;
3+
4+
import {AggregatorInterface} from '../../dependencies/chainlink/AggregatorInterface.sol';
5+
import {IScaledPriceAdaptor} from '../../interfaces/IScaledPriceAdaptor.sol';
6+
7+
/**
8+
* @title ScaledPriceAdaptor
9+
* @author Aave Labs
10+
* @dev Price Adaptor for Chainlink price feeds with non standard decimal USD feeds to 8 decimals.
11+
*/
12+
contract ScaledPriceAdaptor is IScaledPriceAdaptor {
13+
AggregatorInterface internal immutable _SOURCE;
14+
15+
uint8 internal constant _BASE_DECIMALS = 8;
16+
bool internal immutable _SCALE_UP;
17+
uint256 internal immutable _SCALE;
18+
19+
constructor(address source_) {
20+
_SOURCE = AggregatorInterface(source_);
21+
uint8 sourceDecimals = _SOURCE.decimals();
22+
_SCALE_UP = sourceDecimals < _BASE_DECIMALS;
23+
_SCALE = 10 ** (_SCALE_UP ? _BASE_DECIMALS - sourceDecimals : sourceDecimals - _BASE_DECIMALS);
24+
}
25+
26+
/// @inheritdoc IScaledPriceAdaptor
27+
function latestAnswer() external view returns (int256) {
28+
return
29+
_SCALE_UP ? _SOURCE.latestAnswer() * int256(_SCALE) : _SOURCE.latestAnswer() / int256(_SCALE);
30+
}
31+
32+
/// @inheritdoc IScaledPriceAdaptor
33+
function description() external view returns (string memory) {
34+
return string.concat(_SOURCE.description(), ' (USD Scaled)');
35+
}
36+
37+
/// @inheritdoc IScaledPriceAdaptor
38+
function decimals() external pure returns (uint8) {
39+
return _BASE_DECIMALS;
40+
}
41+
42+
/// @inheritdoc IScaledPriceAdaptor
43+
function scale() external view returns (bool, uint256) {
44+
return (_SCALE_UP, _SCALE);
45+
}
46+
47+
/// @inheritdoc IScaledPriceAdaptor
48+
function source() external view returns (address) {
49+
return address(_SOURCE);
50+
}
51+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// SPDX-License-Identifier: BUSL-1.1
2+
pragma solidity ^0.8.10;
3+
4+
interface IScaledPriceAdaptor {
5+
/**
6+
* @dev Units and direction used to scale answer to base decimals of 8.
7+
* @dev Looses price precision when scaling down by log10(scaleUnits).
8+
* @return scaleUp Whether to scale up or down.
9+
* @return scaleUnits The units to scale by.
10+
*/
11+
function scale() external view returns (bool scaleUp, uint256 scaleUnits);
12+
13+
/**
14+
* @dev The decimals of price adaptor.
15+
*/
16+
function decimals() external view returns (uint8);
17+
18+
/**
19+
* @dev Underlying chainlink price source.
20+
*/
21+
function source() external view returns (address);
22+
23+
/**
24+
* @dev Description of price adaptor.
25+
*/
26+
function description() external view returns (string memory);
27+
28+
/**
29+
* @dev Scaled `latestAnswer` from chainlink price feed.
30+
* @dev Looses price precision when scaling down by log10(scaleUnits).
31+
*/
32+
function latestAnswer() external view returns (int256);
33+
}

src/contracts/mocks/oracle/CLAggregators/MockAggregator.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ contract MockAggregator {
1919
return 1;
2020
}
2121

22-
function decimals() external pure returns (uint8) {
22+
function decimals() external view virtual returns (uint8) {
2323
return 8;
2424
}
2525
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.10;
3+
4+
import {MockAggregator} from './MockAggregator.sol';
5+
6+
contract MockAggregatorMetadata is MockAggregator {
7+
uint8 internal immutable _DECIMALS;
8+
9+
constructor(int256 initialAnswer_, uint8 decimals_) MockAggregator(initialAnswer_) {
10+
_DECIMALS = decimals_;
11+
}
12+
13+
function decimals() external view override returns (uint8) {
14+
return _DECIMALS;
15+
}
16+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// SPDX-License-Identifier: BUSL-1.1
2+
pragma solidity ^0.8.0;
3+
4+
import {MockAggregatorMetadata} from '../../../src/contracts/mocks/oracle/CLAggregators/MockAggregatorMetadata.sol';
5+
import {ScaledPriceAdaptor} from '../../../src/contracts/extensions/price-adaptors/ScaledPriceAdaptor.sol';
6+
import {TestnetProcedures} from '../../utils/TestnetProcedures.sol';
7+
8+
contract ScaledPriceAdaptorTests is TestnetProcedures {
9+
function test_adaptor_less_than_base() public {
10+
test_fuzz_adaptor({sourceDecimals: 2, price: 1e2});
11+
test_fuzz_adaptor({sourceDecimals: 6, price: 32.323e6});
12+
}
13+
14+
function test_adaptor_greater_than_base() public {
15+
test_fuzz_adaptor({sourceDecimals: 12, price: 1e12});
16+
}
17+
18+
function test_adaptor_equal_to_base() public {
19+
test_fuzz_adaptor({sourceDecimals: 8, price: 1e8});
20+
}
21+
22+
function test_fuzz_adaptor(uint256 sourceDecimals, int256 price) public {
23+
sourceDecimals = bound(sourceDecimals, 1, 36);
24+
price = bound(price, 1, int256(10 ** sourceDecimals));
25+
address source = address(new MockAggregatorMetadata(price, uint8(sourceDecimals)));
26+
ScaledPriceAdaptor adaptor = new ScaledPriceAdaptor(source);
27+
28+
(bool scaleUp, uint256 scale) = adaptor.scale();
29+
assertEq(adaptor.decimals(), 8);
30+
assertEq(scaleUp, adaptor.decimals() > sourceDecimals);
31+
assertEq(
32+
scale,
33+
10 ** (scaleUp ? adaptor.decimals() - sourceDecimals : sourceDecimals - adaptor.decimals())
34+
);
35+
assertEq(adaptor.latestAnswer(), scaleUp ? price * int256(scale) : price / int256(scale));
36+
assertEq(adaptor.source(), source);
37+
}
38+
}

tests/mocks/AaveV3TestListing.sol

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
pragma solidity ^0.8.0;
33

44
import '../../src/contracts/extensions/v3-config-engine/AaveV3Payload.sol';
5+
import {ScaledPriceAdaptor} from '../../src/contracts/extensions/price-adaptors/ScaledPriceAdaptor.sol';
56
import {TestnetRwaERC20} from '../../src/contracts/mocks/testnet-helpers/TestnetRwaERC20.sol';
67
import {TestnetERC20} from '../../src/contracts/mocks/testnet-helpers/TestnetERC20.sol';
78
import {MockAggregator} from '../../src/contracts/mocks/oracle/CLAggregators/MockAggregator.sol';
9+
import {MockAggregatorMetadata} from '../../src/contracts/mocks/oracle/CLAggregators/MockAggregatorMetadata.sol';
810
import {ACLManager} from '../../src/contracts/protocol/configuration/ACLManager.sol';
911
import {MarketReport} from '../../src/deployments/interfaces/IMarketReportTypes.sol';
1012
import {IPoolConfigurator, ConfiguratorInputTypes} from '../../src/contracts/interfaces/IPoolConfigurator.sol';
@@ -62,10 +64,12 @@ contract AaveV3TestListing is AaveV3Payload {
6264
WETH_MOCK_PRICE_FEED = address(new MockAggregator(1800e8));
6365

6466
BUIDL_ADDRESS = address(new TestnetRwaERC20('BUIDL', 'BUIDL', 6, erc20Owner));
65-
BUIDL_MOCK_PRICE_FEED = address(new MockAggregator(1e8));
67+
BUIDL_MOCK_PRICE_FEED = address(
68+
new ScaledPriceAdaptor(address(new MockAggregatorMetadata(1e2, 2)))
69+
);
6670

6771
USTB_ADDRESS = address(new TestnetRwaERC20('USTB', 'USTB', 6, erc20Owner));
68-
USTB_MOCK_PRICE_FEED = address(new MockAggregator(10e8));
72+
USTB_MOCK_PRICE_FEED = address(new ScaledPriceAdaptor(address(new MockAggregator(10e8))));
6973

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

0 commit comments

Comments
 (0)