Skip to content

Commit a3a6060

Browse files
committed
Merge branch 'release/v4.10.0'
2 parents 6ffef09 + 8514e14 commit a3a6060

25 files changed

+565
-59
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## 4.10.0
4+
### Prudentia
5+
#### Controllers
6+
- Add flag to enable or disable compute ahead logic: If enabled, the controller's computeRate function will calculate the rate on-the-fly with clamping. Otherwise, it will return the last stored rate.
7+
- Add IonicRateController: A RateController that computes rates for Ionic tokens, accruing interest on the underlying tokens before pushing new rates.
8+
39
## v4.9.1
410
### Prudentia
511
#### Controllers

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Adrastia Periphery
22

33
[![standard-readme compliant](https://img.shields.io/badge/readme%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme)
4-
![4015 out of 4015 tests passing](https://img.shields.io/badge/tests-4015/4015%20passing-brightgreen.svg?style=flat-square)
4+
![4038 out of 4038 tests passing](https://img.shields.io/badge/tests-4038/4038%20passing-brightgreen.svg?style=flat-square)
55
![test-coverage 100%](https://img.shields.io/badge/test%20coverage-100%25-brightgreen.svg?style=flat-square)
66

77
Adrastia Periphery is a set of Solidity smart contracts that complement the [Adrastia Core](https://github.com/adrastia-oracle/adrastia-core) smart contracts.

contracts/rates/ManagedRateController.sol

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,18 @@ contract ManagedRateController is RateController, AccessControlEnumerable {
3232

3333
/**
3434
* @notice Constructs the ManagedRateController contract.
35+
* @param computeAhead_ True if the rates returned by computeRate should be computed on-the-fly with clamping;
36+
* false if the returned rates should be the same as the last pushed rates (from the buffer).
3537
* @param period_ The period for the rate controller.
3638
* @param initialBufferCardinality_ The initial buffer cardinality for the rate controller.
3739
* @param updatersMustBeEoa_ A flag indicating if updaters must be externally owned accounts.
3840
*/
3941
constructor(
42+
bool computeAhead_,
4043
uint32 period_,
4144
uint8 initialBufferCardinality_,
4245
bool updatersMustBeEoa_
43-
) RateController(period_, initialBufferCardinality_, updatersMustBeEoa_) {
46+
) RateController(computeAhead_, period_, initialBufferCardinality_, updatersMustBeEoa_) {
4447
initializeRoles();
4548
}
4649

contracts/rates/RateController.sol

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ abstract contract RateController is ERC165, HistoricalRates, IRateComputer, IUpd
4040
/// @dev This is a security feature to prevent malicious contracts from updating rates.
4141
bool public immutable updatersMustBeEoa;
4242

43+
/// @notice True if the rates returned by computeRate should be computed on-the-fly with clamping; false if the
44+
/// returned rates should be the same as the last pushed rates (from the buffer).
45+
bool public immutable computeAhead;
46+
4347
/// @notice Maps a token to its rate configuration.
4448
mapping(address => RateConfig) internal rateConfigs;
4549

@@ -82,14 +86,18 @@ abstract contract RateController is ERC165, HistoricalRates, IRateComputer, IUpd
8286
error PauseStatusUnchanged(address token, bool paused);
8387

8488
/// @notice Creates a new rate controller.
89+
/// @param computeAhead_ True if the rates returned by computeRate should be computed on-the-fly with clamping;
90+
/// false if the returned rates should be the same as the last pushed rates (from the buffer).
8591
/// @param period_ The period of the rate controller, in seconds. This is the frequency at which rates are updated.
8692
/// @param initialBufferCardinality_ The initial capacity of the rate buffer.
8793
/// @param updatersMustBeEoa_ True if all rate updaters must be EOA accounts; false otherwise.
8894
constructor(
95+
bool computeAhead_,
8996
uint32 period_,
9097
uint8 initialBufferCardinality_,
9198
bool updatersMustBeEoa_
9299
) HistoricalRates(initialBufferCardinality_) {
100+
computeAhead = computeAhead_;
93101
period = period_;
94102
updatersMustBeEoa = updatersMustBeEoa_;
95103
}
@@ -213,11 +221,24 @@ abstract contract RateController is ERC165, HistoricalRates, IRateComputer, IUpd
213221
}
214222
}
215223

216-
/// @inheritdoc IRateComputer
224+
/// @notice Computes the rate for a token. If computeAhead is true, the rate is computed on-the-fly with clamping;
225+
/// otherwise, the rate is the same as the last pushed rate (from the buffer).
226+
/// @param token The address of the token to compute the rate for.
227+
/// @return rate The rate for the token.
217228
function computeRate(address token) external view virtual override returns (uint64) {
218-
(, uint64 newRate) = computeRateAndClamp(token);
229+
if (computeAhead) {
230+
(, uint64 newRate) = computeRateAndClamp(token);
219231

220-
return newRate;
232+
return newRate;
233+
} else {
234+
BufferMetadata storage meta = rateBufferMetadata[token];
235+
if (meta.size == 0) {
236+
// We've never computed a rate, so revert.
237+
revert InsufficientData(token, 0, 1);
238+
}
239+
240+
return getLatestRate(token).current;
241+
}
221242
}
222243

223244
/// @inheritdoc IPeriodic
@@ -375,7 +396,7 @@ abstract contract RateController is ERC165, HistoricalRates, IRateComputer, IUpd
375396
return rateBuffers[token][meta.end];
376397
}
377398

378-
/// @notice Computes the rate for the given token.
399+
/// @notice Computes the target rate for the given token (without clamping).
379400
/// @dev This function calculates the rate for the specified token by summing its base rate
380401
/// and the weighted rates of its components. The component rates are computed using the `computeRate`
381402
/// function of each component and multiplied by the corresponding weight, then divided by 10,000.

contracts/rates/controllers/CapController.sol

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,18 @@ abstract contract CapController is RateController {
2626

2727
/**
2828
* @notice Constructs the CapController contract.
29+
* @param computeAhead_ True if the rates returned by computeRate should be computed on-the-fly with clamping;
30+
* false if the returned rates should be the same as the last pushed rates (from the buffer).
2931
* @param period_ The period of the rate controller.
3032
* @param initialBufferCardinality_ The initial cardinality of the rate buffers.
3133
* @param updatersMustBeEoa_ Whether or not the updaters must be EOA.
3234
*/
3335
constructor(
36+
bool computeAhead_,
3437
uint32 period_,
3538
uint8 initialBufferCardinality_,
3639
bool updatersMustBeEoa_
37-
) RateController(period_, initialBufferCardinality_, updatersMustBeEoa_) {}
40+
) RateController(computeAhead_, period_, initialBufferCardinality_, updatersMustBeEoa_) {}
3841

3942
/**
4043
* @notice Sets the change threshold for the specified token. When the rate changes by more than the threshold, an

contracts/rates/controllers/ManagedCapController.sol

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,18 @@ contract ManagedCapController is CapController, AccessControlEnumerable {
3232

3333
/**
3434
* @notice Constructs the ManagedCapController contract.
35+
* @param computeAhead_ True if the rates returned by computeRate should be computed on-the-fly with clamping;
36+
* false if the returned rates should be the same as the last pushed rates (from the buffer).
3537
* @param period_ The period for the rate controller.
3638
* @param initialBufferCardinality_ The initial buffer cardinality for the rate controller.
3739
* @param updatersMustBeEoa_ A flag indicating if updaters must be externally owned accounts.
3840
*/
3941
constructor(
42+
bool computeAhead_,
4043
uint32 period_,
4144
uint8 initialBufferCardinality_,
4245
bool updatersMustBeEoa_
43-
) CapController(period_, initialBufferCardinality_, updatersMustBeEoa_) {
46+
) CapController(computeAhead_, period_, initialBufferCardinality_, updatersMustBeEoa_) {
4447
initializeRoles();
4548
}
4649

contracts/rates/controllers/ManagedPidController.sol

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,18 @@ contract ManagedPidController is PidController, AccessControlEnumerable {
3030

3131
/// @notice Constructs the ManagedPidController.
3232
/// @param inputAndErrorOracle_ Oracle to provide input and error values.
33+
/// @param computeAhead_ True if the rates returned by computeRate should be computed on-the-fly with clamping;
34+
/// false if the returned rates should be the same as the last pushed rates (from the buffer).
3335
/// @param period_ The period for the rate controller.
3436
/// @param initialBufferCardinality_ Initial size of the buffer for rate storage.
3537
/// @param updatersMustBeEoa_ Flag to determine if updaters must be externally owned accounts.
3638
constructor(
3739
ILiquidityOracle inputAndErrorOracle_,
40+
bool computeAhead_,
3841
uint32 period_,
3942
uint8 initialBufferCardinality_,
4043
bool updatersMustBeEoa_
41-
) PidController(inputAndErrorOracle_, period_, initialBufferCardinality_, updatersMustBeEoa_) {
44+
) PidController(inputAndErrorOracle_, computeAhead_, period_, initialBufferCardinality_, updatersMustBeEoa_) {
4245
initializeRoles();
4346
}
4447

contracts/rates/controllers/PidController.sol

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -91,15 +91,18 @@ abstract contract PidController is RateController {
9191

9292
/// @notice Constructs the PidController.
9393
/// @param inputAndErrorOracle_ Default oracle to provide input and error values.
94+
/// @param computeAhead_ True if the rates returned by computeRate should be computed on-the-fly with clamping;
95+
/// false if the returned rates should be the same as the last pushed rates (from the buffer).
9496
/// @param period_ The period for the rate controller.
9597
/// @param initialBufferCardinality_ Initial size of the buffer for rate storage.
9698
/// @param updatersMustBeEoa_ Flag to determine if updaters must be externally owned accounts.
9799
constructor(
98100
ILiquidityOracle inputAndErrorOracle_,
101+
bool computeAhead_,
99102
uint32 period_,
100103
uint8 initialBufferCardinality_,
101104
bool updatersMustBeEoa_
102-
) RateController(period_, initialBufferCardinality_, updatersMustBeEoa_) {
105+
) RateController(computeAhead_, period_, initialBufferCardinality_, updatersMustBeEoa_) {
103106
if (period_ == 0) revert InvalidPeriod(period_);
104107

105108
validateInputAndErrorOracle(inputAndErrorOracle_, true);
@@ -165,18 +168,6 @@ abstract contract PidController is RateController {
165168
}
166169
}
167170

168-
/// @inheritdoc RateController
169-
/// @dev Returns the current rate (latest stored) for the token, reverting if the rate has never been computed.
170-
function computeRate(address token) external view virtual override returns (uint64) {
171-
BufferMetadata storage meta = rateBufferMetadata[token];
172-
if (meta.size == 0) {
173-
// We've never computed a rate, so revert.
174-
revert InsufficientData(token, 0, 1);
175-
}
176-
177-
return getLatestRate(token).current;
178-
}
179-
180171
/// @inheritdoc RateController
181172
/// @dev Updates are not needed if the PID config is uninitialized.
182173
function needsUpdate(bytes memory data) public view virtual override returns (bool b) {
@@ -409,15 +400,21 @@ abstract contract PidController is RateController {
409400

410401
// Compute output
411402
int256 output = pTerm + pidState.iTerm - dTerm;
403+
// Store last values to be used in the next iteration computation
412404
pidState.lastInput = input;
413405
pidState.lastError = err;
406+
407+
// Set target to the output rate (before clamping) and clamp to the range [0, 2^64) (to fit inside uint64).
414408
if (output < int256(0)) {
415409
target = 0;
416410
} else if (output >= int256(uint256(type(uint64).max))) {
417411
target = type(uint64).max;
418412
} else {
419413
target = uint64(uint256(output));
420414
}
415+
416+
// Clamp the output. Note that clampChange is false here but this parameter is ignored as we indicate this
417+
// is the output rate, signaling to use the rate controller's main clamping function which has change clamping.
421418
output = clampBigSignedRate(token, output, true, false, 0);
422419
// Clamping the output returns a value in the range [0, 2^64), so we can safely cast it to uint64.
423420
current = uint64(uint256(output));
@@ -430,6 +427,10 @@ abstract contract PidController is RateController {
430427
return target;
431428
}
432429

430+
function computeRateAndClamp(address token) internal view virtual override returns (uint64 target, uint64 newRate) {
431+
(target, newRate, ) = computeNextPidRate(token);
432+
}
433+
433434
/// @inheritdoc RateController
434435
function updateAndCompute(address token) internal virtual override returns (uint64 target, uint64 current) {
435436
PidState memory newPidState;

contracts/rates/controllers/proto/aave/AaveRateController.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ contract AaveRateController is RateController {
2929
uint32 period_,
3030
uint8 initialBufferCardinality_,
3131
bool updatersMustBeEoa_
32-
) RateController(period_, initialBufferCardinality_, updatersMustBeEoa_) {
32+
) RateController(false, period_, initialBufferCardinality_, updatersMustBeEoa_) {
3333
aclManager = aclManager_;
3434
}
3535

contracts/rates/controllers/proto/ionic/IonicPidController.sol

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,19 @@ contract IonicPidController is ManagedPidController {
1515
constructor(
1616
IComptroller comptroller_,
1717
ILiquidityOracle inputAndErrorOracle_,
18+
bool computeAhead_,
1819
uint32 period_,
1920
uint8 initialBufferCardinality_,
2021
bool updatersMustBeEoa_
21-
) ManagedPidController(inputAndErrorOracle_, period_, initialBufferCardinality_, updatersMustBeEoa_) {
22+
)
23+
ManagedPidController(
24+
inputAndErrorOracle_,
25+
computeAhead_,
26+
period_,
27+
initialBufferCardinality_,
28+
updatersMustBeEoa_
29+
)
30+
{
2231
comptroller = comptroller_;
2332
}
2433

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// SPDX-License-Identifier: BUSL-1.1
2+
pragma solidity =0.8.13;
3+
4+
import "../../../ManagedRateController.sol";
5+
import "../../../../vendor/ionic/IComptroller.sol";
6+
import "../../../../vendor/ionic/ICToken.sol";
7+
8+
contract IonicRateController is ManagedRateController {
9+
IComptroller public immutable comptroller;
10+
11+
error CTokenNotFound(address token);
12+
13+
error FailedToAccrueInterest(address token, address cToken, uint256 errorCode);
14+
15+
constructor(
16+
IComptroller comptroller_,
17+
bool computeAhead_,
18+
uint32 period_,
19+
uint8 initialBufferCardinality_,
20+
bool updatersMustBeEoa_
21+
) ManagedRateController(computeAhead_, period_, initialBufferCardinality_, updatersMustBeEoa_) {
22+
comptroller = comptroller_;
23+
}
24+
25+
/// @dev Overridden to accrue interest for the prior rate before pushing the new rate.
26+
function push(address token, RateLibrary.Rate memory rate) internal virtual override {
27+
// Try and accrue interest if we have a prior rate
28+
if (rateBufferMetadata[token].size > 0) {
29+
address cToken = comptroller.cTokensByUnderlying(token);
30+
if (cToken == address(0)) {
31+
// Note that this check is not applied for the first rate to allow for the initial rate to be set
32+
// before the cToken is added to the comptroller.
33+
revert CTokenNotFound(token);
34+
}
35+
36+
// Accrue interest for the prior rate before pushing the new rate
37+
uint256 accrueCode = ICToken(cToken).accrueInterest();
38+
if (accrueCode != 0) {
39+
revert FailedToAccrueInterest(token, cToken, accrueCode);
40+
}
41+
}
42+
43+
super.push(token, rate);
44+
}
45+
}

contracts/rates/controllers/proto/truefi/TrueFiAlocPidController.sol

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,19 @@ import "../../../../vendor/truefi/IAutomatedLineOfCredit.sol";
77
contract TrueFiAlocPidController is ManagedPidController {
88
constructor(
99
ILiquidityOracle inputAndErrorOracle_,
10+
bool computeAhead_,
1011
uint32 period_,
1112
uint8 initialBufferCardinality_,
1213
bool updatersMustBeEoa_
13-
) ManagedPidController(inputAndErrorOracle_, period_, initialBufferCardinality_, updatersMustBeEoa_) {}
14+
)
15+
ManagedPidController(
16+
inputAndErrorOracle_,
17+
computeAhead_,
18+
period_,
19+
initialBufferCardinality_,
20+
updatersMustBeEoa_
21+
)
22+
{}
1423

1524
/// @dev Overridden to accrue interest for the prior rate before pushing the new rate.
1625
function push(address alocAddress, RateLibrary.Rate memory rate) internal virtual override {

contracts/test/rates/RateControllerStub.sol

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,11 @@ contract RateControllerStub is ManagedRateController {
2121
mapping(address => OnPauseCall) public onPauseCalls;
2222

2323
constructor(
24+
bool computeAhead_,
2425
uint32 period_,
2526
uint8 initialBufferCardinality_,
2627
bool updatersMustBeEoa_
27-
) ManagedRateController(period_, initialBufferCardinality_, updatersMustBeEoa_) {}
28+
) ManagedRateController(computeAhead_, period_, initialBufferCardinality_, updatersMustBeEoa_) {}
2829

2930
function stubPush(address token, uint64 target, uint64 current, uint32 timestamp) public {
3031
RateLibrary.Rate memory rate;

contracts/test/rates/controllers/CapControllerStub.sol

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@ contract CapControllerStub is ManagedCapController {
1414
Config public config;
1515

1616
constructor(
17+
bool computeAhead_,
1718
uint32 period_,
1819
uint8 initialBufferCardinality_,
1920
bool updatersMustBeEoa_
20-
) ManagedCapController(period_, initialBufferCardinality_, updatersMustBeEoa_) {}
21+
) ManagedCapController(computeAhead_, period_, initialBufferCardinality_, updatersMustBeEoa_) {}
2122

2223
function overrideNeedsUpdate(bool overridden, bool needsUpdate_) public {
2324
config.needsUpdateOverridden = overridden;

contracts/test/rates/controllers/PidControllerStub.sol

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@ contract PidControllerStub is ManagedPidController, InputAndErrorAccumulatorStub
2222
mapping(address => OnPauseCall) public onPauseCalls;
2323

2424
constructor(
25+
bool computeAhead_,
2526
uint32 period_,
2627
uint8 initialBufferCardinality_,
2728
bool updatersMustBeEoa_
28-
) ManagedPidController(this, period_, initialBufferCardinality_, updatersMustBeEoa_) {}
29+
) ManagedPidController(this, computeAhead_, period_, initialBufferCardinality_, updatersMustBeEoa_) {}
2930

3031
function canUpdate(
3132
bytes memory data

0 commit comments

Comments
 (0)