Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
24 changes: 17 additions & 7 deletions src/spoke/AaveOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,39 @@ import {IAaveOracle, IPriceOracle} from 'src/spoke/interfaces/IAaveOracle.sol';
/// @notice Provides reserve prices.
/// @dev Oracles are spoke-specific, due to the usage of reserve id as index of the `_sources` mapping.
contract AaveOracle is IAaveOracle {
/// @inheritdoc IPriceOracle
address public immutable SPOKE;

/// @inheritdoc IPriceOracle
uint8 public immutable DECIMALS;

/// @dev The address of the deployer.
address private immutable _DEPLOYER;

/// @inheritdoc IAaveOracle
string public DESCRIPTION;

/// @inheritdoc IPriceOracle
address public SPOKE;

mapping(uint256 reserveId => AggregatorV3Interface) internal _sources;

/// @dev Constructor.
/// @dev `decimals` must match the spoke's decimals for compatibility.
/// @param spoke_ The address of the spoke contract.
/// @param decimals_ The number of decimals for the oracle.
/// @param description_ The description of the oracle.
constructor(address spoke_, uint8 decimals_, string memory description_) {
require(spoke_ != address(0), InvalidAddress());
SPOKE = spoke_;
constructor(uint8 decimals_, string memory description_) {
_DEPLOYER = msg.sender;
DECIMALS = decimals_;
DESCRIPTION = description_;
}

/// @inheritdoc IAaveOracle
function setSpoke(address spoke) external {
require(msg.sender == _DEPLOYER, OnlyDeployer());
require(spoke != address(0), InvalidAddress());
require(SPOKE == address(0), SpokeAlreadySet());
SPOKE = spoke;
emit SetSpoke(spoke);
}

/// @inheritdoc IAaveOracle
function setReserveSource(uint256 reserveId, address source) external {
require(msg.sender == SPOKE, OnlySpoke());
Expand Down
16 changes: 16 additions & 0 deletions src/spoke/interfaces/IAaveOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@ interface IAaveOracle is IPriceOracle {
/// @param source The price feed source of the reserve.
event UpdateReserveSource(uint256 indexed reserveId, address indexed source);

/// @dev Emitted when the spoke is set.
/// @param spoke The address of the spoke.
event SetSpoke(address indexed spoke);

/// @dev Thrown when the caller is not the deployer.
error OnlyDeployer();

/// @dev Thrown when the spoke is already set.
error SpokeAlreadySet();

/// @dev Thrown when the price feed source uses a different number of decimals than the oracle.
/// @param reserveId The identifier of the reserve.
error InvalidSourceDecimals(uint256 reserveId);
Expand All @@ -28,6 +38,12 @@ interface IAaveOracle is IPriceOracle {
/// @dev Thrown when the given address is invalid.
error InvalidAddress();

/// @notice Sets the spoke.
/// @dev Can only be called once by the deployer.
/// @dev The spoke should be set before any other function is called.
/// @param spoke The address of the spoke.
function setSpoke(address spoke) external;

/// @notice Sets the price feed source of a reserve.
/// @dev Must be called by the spoke.
/// @dev The source must implement the AggregatorV3Interface.
Expand Down
22 changes: 17 additions & 5 deletions tests/Base.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ import {console2 as console} from 'forge-std/console2.sol';

// dependencies
import {AggregatorV3Interface} from 'src/dependencies/chainlink/AggregatorV3Interface.sol';
import {TransparentUpgradeableProxy, ITransparentUpgradeableProxy} from 'src/dependencies/openzeppelin/TransparentUpgradeableProxy.sol';
import {
TransparentUpgradeableProxy,
ITransparentUpgradeableProxy
} from 'src/dependencies/openzeppelin/TransparentUpgradeableProxy.sol';
import {IERC20Metadata} from 'src/dependencies/openzeppelin/IERC20Metadata.sol';
import {SafeCast} from 'src/dependencies/openzeppelin/SafeCast.sol';
import {IERC20Errors} from 'src/dependencies/openzeppelin/IERC20Errors.sol';
Expand Down Expand Up @@ -44,7 +47,11 @@ import {AccessManagerEnumerable} from 'src/access/AccessManagerEnumerable.sol';
import {HubConfigurator, IHubConfigurator} from 'src/hub/HubConfigurator.sol';
import {Hub, IHub, IHubBase} from 'src/hub/Hub.sol';
import {SharesMath} from 'src/hub/libraries/SharesMath.sol';
import {AssetInterestRateStrategy, IAssetInterestRateStrategy, IBasicInterestRateStrategy} from 'src/hub/AssetInterestRateStrategy.sol';
import {
AssetInterestRateStrategy,
IAssetInterestRateStrategy,
IBasicInterestRateStrategy
} from 'src/hub/AssetInterestRateStrategy.sol';

// spoke
import {Spoke, ISpoke, ISpokeBase} from 'src/spoke/Spoke.sol';
Expand Down Expand Up @@ -2228,8 +2235,10 @@ abstract contract Base is Test {
string memory _oracleDesc
) internal pausePrank returns (ISpoke, IAaveOracle) {
address deployer = makeAddr('deployer');
address predictedSpoke = vm.computeCreateAddress(deployer, vm.getNonce(deployer));
IAaveOracle oracle = new AaveOracle(predictedSpoke, 8, _oracleDesc);

vm.prank(deployer);
IAaveOracle oracle = new AaveOracle(8, _oracleDesc);

address spokeImpl = address(new SpokeInstance(address(oracle)));
ISpoke spoke = ISpoke(
_proxify(
Expand All @@ -2239,7 +2248,10 @@ abstract contract Base is Test {
abi.encodeCall(Spoke.initialize, (_accessManager))
)
);
assertEq(address(spoke), predictedSpoke, 'predictedSpoke');

vm.prank(deployer);
oracle.setSpoke(address(spoke));

assertEq(spoke.ORACLE(), address(oracle));
assertEq(oracle.SPOKE(), address(spoke));
return (spoke, oracle);
Expand Down
64 changes: 49 additions & 15 deletions tests/unit/AaveOracle.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ contract AaveOracleTest is Base {
uint8 private constant _oracleDecimals = 8;
string private constant _description = 'Spoke 1 (USD)';

address public deployer = makeAddr('DEPLOYER');

address private _source1 = makeAddr('SOURCE1');
address private _source2 = makeAddr('SOURCE2');

Expand All @@ -22,43 +24,71 @@ contract AaveOracleTest is Base {

function setUp() public override {
deployFixtures();
oracle = new AaveOracle(address(spoke1), _oracleDecimals, _description);
}

function test_deploy_revertsWith_InvalidAddress() public {
vm.expectRevert(IAaveOracle.InvalidAddress.selector);
new AaveOracle(address(0), uint8(vm.randomUint()), string(vm.randomBytes(64)));
vm.startPrank(deployer);
oracle = new AaveOracle(_oracleDecimals, _description);
oracle.setSpoke(address(spoke1));
vm.stopPrank();
}

function test_constructor() public {
oracle = new AaveOracle(address(spoke1), _oracleDecimals, _description);
vm.prank(deployer);
oracle = new AaveOracle(_oracleDecimals, _description);

test_spoke();
testDECIMALS();
_testSpoke(address(0));
test_DECIMALS();
test_description();
}

function test_fuzz_constructor(uint8 decimals) public {
decimals = bound(decimals, 0, 18).toUint8();
oracle = new AaveOracle(address(spoke1), decimals, _description);
oracle = new AaveOracle(decimals, _description);

test_spoke();
_testSpoke(address(0));
assertEq(oracle.DECIMALS(), decimals);
test_description();
}

function test_spoke() public view {
assertEq(oracle.SPOKE(), address(spoke1));
}

function testDECIMALS() public view {
function test_DECIMALS() public view {
assertEq(oracle.DECIMALS(), _oracleDecimals);
}

function test_description() public view {
assertEq(oracle.DESCRIPTION(), _description);
}

function test_setSpoke_revertsWith_OnlyDeployer(address user) public {
vm.assume(user != deployer);

vm.expectRevert(IAaveOracle.OnlyDeployer.selector);
vm.prank(user);
oracle.setSpoke(address(spoke1));
}

function test_setSpoke_revertsWith_InvalidAddress() public {
vm.expectRevert(IAaveOracle.InvalidAddress.selector);

vm.prank(deployer);
oracle.setSpoke(address(0));
}

function test_setSpoke_revertsWith_SpokeAlreadySet() public {
vm.expectRevert(IAaveOracle.SpokeAlreadySet.selector);
vm.prank(deployer);
oracle.setSpoke(address(spoke1));
}

function test_setSpoke() public {
vm.prank(deployer);
oracle = new AaveOracle(_oracleDecimals, _description);

vm.expectEmit(address(oracle));
emit IAaveOracle.SetSpoke(address(spoke1));

vm.prank(deployer);
oracle.setSpoke(address(spoke1));
}

function test_setReserveSource_revertsWith_OnlySpoke() public {
vm.expectRevert(IPriceOracle.OnlySpoke.selector);

Expand Down Expand Up @@ -197,4 +227,8 @@ contract AaveOracleTest is Base {
)
);
}

function _testSpoke(address expectedSpoke) internal view {
assertEq(oracle.SPOKE(), expectedSpoke);
}
}
Loading