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: expose latest answer on static a token #3

Merged
merged 13 commits into from
Aug 8, 2024
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,5 @@ coverage :; forge coverage --report lcov && \
download :; cast etherscan-source --chain ${chain} -d src/etherscan/${chain}_${address} ${address}
git-diff :
@mkdir -p diffs
@npx prettier ${before} ${after} --write
@printf '%s\n%s\n%s\n' "\`\`\`diff" "$$(git diff --no-index --diff-algorithm=patience --ignore-space-at-eol ${before} ${after})" "\`\`\`" > diffs/${out}.md
16 changes: 16 additions & 0 deletions src/periphery/contracts/static-a-token/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,19 @@ For this project, the security procedures applied/being finished are:

- The test suite of the codebase itself.
- Certora audit/property checking for all the dynamics of the `stataToken`, including respecting all the specs of [EIP-4626](https://eips.ethereum.org/EIPS/eip-4626).

## Upgrade Notes Umbrella
Copy link
Author

Choose a reason for hiding this comment

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

ignore for review, will rework once all is merged on a single branch.


- Interface inheritance has been changed so that `IStaticATokenLM` implements `IERC4626`, making it easier for integrators to work with the interface.
- The static A tokens are given a `rescuable`, which can be used by the ACL admin to rescue tokens locked to the contract.
- Permit params have been excluded from the METADEPOSIT_TYPEHASH as they are not necessary. Even if someone were to frontrun the permit via mempool observation the permit is wrapped in a `try..catch` to prevent griefing attacks.
- The static a token not implements pausability, which allows the ACL admin to pause all transfers.

The storage layout diff was generated via:
```
git checkout main
forge inspect src/periphery/contracts/static-a-token/StaticATokenLM.sol:StaticATokenLM storage-layout --pretty > reports/StaticATokenStorageBefore.md
git checkout project-a
forge inspect src/periphery/contracts/static-a-token/StaticATokenLM.sol:StaticATokenLM storage-layout --pretty > reports/StaticATokenStorageAfter.md
make git-diff before=reports/StaticATokenStorageBefore.md after=reports/StaticATokenStorageAfter.md out=StaticATokenStorageDiff
```
17 changes: 15 additions & 2 deletions src/periphery/contracts/static-a-token/StaticATokenLM.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
pragma solidity ^0.8.10;

import {IPool} from '../../../core/contracts/interfaces/IPool.sol';
import {IPoolAddressesProvider} from '../../../core/contracts/interfaces/IPoolAddressesProvider.sol';
import {IAaveOracle} from '../../../core/contracts/interfaces/IAaveOracle.sol';
import {DataTypes, ReserveConfiguration} from '../../../core/contracts/protocol/libraries/configuration/ReserveConfiguration.sol';
import {WadRayMath} from '../../../core/contracts/protocol/libraries/math/WadRayMath.sol';
import {MathUtils} from '../../../core/contracts/protocol/libraries/math/MathUtils.sol';
Expand Down Expand Up @@ -55,6 +57,7 @@ contract StaticATokenLM is
uint256 public constant STATIC__ATOKEN_LM_REVISION = 3;

IPool public immutable POOL;
IPoolAddressesProvider immutable POOL_ADDRESSES_PROVIDER;
IRewardsController public immutable INCENTIVES_CONTROLLER;

IERC20 internal _aToken;
Expand All @@ -67,6 +70,7 @@ contract StaticATokenLM is
_disableInitializers();
POOL = pool;
INCENTIVES_CONTROLLER = rewardsController;
POOL_ADDRESSES_PROVIDER = pool.ADDRESSES_PROVIDER();
}

modifier onlyPauseGuardian() {
Expand All @@ -75,7 +79,7 @@ contract StaticATokenLM is
}

function canPause(address actor) public view returns (bool) {
return IACLManager(POOL.ADDRESSES_PROVIDER().getACLManager()).isEmergencyAdmin(actor);
return IACLManager(POOL_ADDRESSES_PROVIDER.getACLManager()).isEmergencyAdmin(actor);
}

///@inheritdoc IInitializableStaticATokenLM
Expand Down Expand Up @@ -103,7 +107,7 @@ contract StaticATokenLM is

/// @inheritdoc IRescuable
function whoCanRescue() public view override returns (address) {
return POOL.ADDRESSES_PROVIDER().getACLAdmin();
return POOL_ADDRESSES_PROVIDER.getACLAdmin();
}

///@inheritdoc IStaticATokenLM
Expand Down Expand Up @@ -468,6 +472,15 @@ contract StaticATokenLM is
return _withdraw(owner, receiver, shares, 0, withdrawFromAave);
}

///@inheritdoc IStaticATokenLM
function latestAnswer() external view returns (int256) {
return
int256(
(IAaveOracle(POOL_ADDRESSES_PROVIDER.getPriceOracle()).getAssetPrice(_aTokenUnderlying) *
POOL.getReserveNormalizedIncome(_aTokenUnderlying)) / 1e27
);
}

function _deposit(
address depositor,
address receiver,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,4 +219,11 @@ interface IStaticATokenLM is IInitializableStaticATokenLM, IERC4626 {
* @param paused boolean determining if the token should be paused or unpaused
*/
function setPaused(bool paused) external;

/**
* @notice Returns the current asset price of the stataToken.
* The price is calculated as `underlying_price * exchangeRate`, but it's important to note that `underlying_price` is the price obtained by the aave-oracle, which means it's subject to caps and similar imposed by the aave DAO on the oracle.
* @return int256 the current asset price in 8 decimals.
sakulstra marked this conversation as resolved.
Show resolved Hide resolved
*/
function latestAnswer() external view returns (int256);
}
24 changes: 24 additions & 0 deletions tests/periphery/static-a-token/StaticATokenLM.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {RayMathExplicitRounding} from '../../../src/periphery/contracts/librarie
import {IStaticATokenLM} from '../../../src/periphery/contracts/static-a-token/interfaces/IStaticATokenLM.sol';
import {SigUtils} from '../../utils/SigUtils.sol';
import {BaseTest, TestnetERC20} from './TestBase.sol';
import {IPool} from '../../../src/core/contracts/interfaces/IPool.sol';

contract StaticATokenLMTest is BaseTest {
using RayMathExplicitRounding for uint256;
Expand Down Expand Up @@ -48,6 +49,29 @@ contract StaticATokenLMTest is BaseTest {
);
}

function test_latestAnswer_priceShouldBeEqualOnDefaultIndex() public {
vm.mockCall(
address(POOL),
abi.encodeWithSelector(IPool.getReserveNormalizedIncome.selector),
abi.encode(1e27)
);
uint256 stataPrice = uint256(staticATokenLM.latestAnswer());
uint256 underlyingPrice = contracts.aaveOracle.getAssetPrice(UNDERLYING);
assertEq(stataPrice, underlyingPrice);
}

function test_latestAnswer_priceShouldReflectIndexAccrual(uint256 liquidityIndex) public {
liquidityIndex = bound(liquidityIndex, 1e27, 1e29);
vm.mockCall(
address(POOL),
abi.encodeWithSelector(IPool.getReserveNormalizedIncome.selector),
abi.encode(liquidityIndex)
);
uint256 stataPrice = uint256(staticATokenLM.latestAnswer());
uint256 underlyingPrice = contracts.aaveOracle.getAssetPrice(UNDERLYING);
assertEq(stataPrice, (underlyingPrice * liquidityIndex) / 1e27);
}

function test_convertersAndPreviews() public view {
uint128 amount = 5 ether;
uint256 shares = staticATokenLM.convertToShares(amount);
Expand Down
Loading