Skip to content

Commit b707a9a

Browse files
ckoopmannChristian KoopmannFlattestWhite
authored
fix: Change access control for rebalance method to allow non-operator accounts to call it. (#123)
* Use onlyAllowedCaller instead of onlyOperator to control access to rebalance method * Undo accidental change in BaseExtension * Remove onlyEOA from docstring comment * Extended `minPositions` configuration to potentially allow opening up access to `rebalance` method (#125) * Extend minPosition configuration to allow opening up rebalance method * Refactoring to make precommit linter happy * Refactoring of FixedRebalanceExtension.sol * Return current positions from rebalance method * Add tests for returned position values * Increase tolerance in integration tests * Fix wrong indentation Co-authored-by: Richard Guan <[email protected]> * Remove extra space from test name Co-authored-by: Richard Guan <[email protected]> Co-authored-by: Christian Koopmann <[email protected]> Co-authored-by: Richard Guan <[email protected]>
1 parent 90cec68 commit b707a9a

File tree

3 files changed

+431
-209
lines changed

3 files changed

+431
-209
lines changed

contracts/adapters/FixedRebalanceExtension.sol

Lines changed: 63 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,13 @@ contract FixedRebalanceExtension is BaseExtension {
4949

5050
// /* ============ Events ============ */
5151

52-
event AllocationsUpdated(uint256[] _maturities, uint256[] _allocations);
52+
event AllocationsUpdated(uint256[] _maturities, uint256[] _allocations, uint256[] _minPositions);
5353

5454
// /* ============ State Variables ============ */
5555

5656
uint256[] internal maturities; // Array of relative maturities in seconds (i.e. 3 months / 6 months)
5757
uint256[] internal allocations; // Relative allocations
58+
uint256[] internal minPositions; // Minimum positions to achieve after every full rebalancing (assuming share = 100 %)
5859

5960
ISetToken public immutable setToken;
6061
INotionalTradeModule internal immutable notionalTradeModule;
@@ -77,7 +78,8 @@ contract FixedRebalanceExtension is BaseExtension {
7778
address _underlyingToken,
7879
address _assetToken,
7980
uint256[] memory _maturities,
80-
uint256[] memory _allocations
81+
uint256[] memory _allocations,
82+
uint256[] memory _minPositions
8183
)
8284
public
8385
BaseExtension(_manager)
@@ -90,7 +92,7 @@ contract FixedRebalanceExtension is BaseExtension {
9092
assetToken = _assetToken;
9193
currencyId = _notionalProxy.getCurrencyId(_assetToken);
9294

93-
_setAllocations(_maturities, _allocations);
95+
_setAllocations(_maturities, _allocations, _minPositions);
9496
}
9597

9698
// /* ============ External Functions ============ */
@@ -110,24 +112,24 @@ contract FixedRebalanceExtension is BaseExtension {
110112
* @param _maturities Relative maturities (i.e. "3 months") in seconds
111113
* @param _allocations Relative allocations (i.e. 0.9 = 90%) with 18 decimals corresponding to the respective maturity
112114
*/
113-
function setAllocations(uint256[] memory _maturities, uint256[] memory _allocations) external onlyOperator {
114-
_setAllocations(_maturities, _allocations);
115+
function setAllocations(uint256[] memory _maturities, uint256[] memory _allocations, uint256[] memory _minPositions) external onlyOperator {
116+
_setAllocations(_maturities, _allocations, _minPositions);
115117
}
116118

117119
/**
118-
* ONLY OPERATOR: Rebalances the positions towards the configured allocation percentages.
120+
* ONLY ALLOWED CALLER: Rebalances the positions towards the configured allocation percentages.
119121
*
120-
* @param _share Relative share of the necessary trade volume to execute (allows for splitting the rebalance over multiple transactions
121-
* @param _minPositions Minimum positions (in set token units) for each maturity after rebalance. (slippage protection)
122+
* @param _share Relative share of the necessary trade volume to execute (allows for splitting the rebalance over multiple transactions
123+
* @param _rebalanceMinPositions Minimum positions (in set token units) for each maturity after this rebalance operation.
124+
* @dev Will revert if _rebalanceMinPositions is lower than the minPositions configured by the operator (weighted by share)
122125
*/
123-
function rebalance(uint256 _share, uint256[] memory _minPositions) external onlyOperator {
126+
function rebalance(uint256 _share, uint256[] memory _rebalanceMinPositions) external onlyAllowedCaller(msg.sender) returns(uint256[] memory) {
124127
require(_share > 0, "Share must be greater than 0");
125128
require(_share <= 1 ether, "Share cannot exceed 100%");
126129

127-
// TODO: Review if we want to open up this method for anyone to call.
128-
_sellOverweightPositions(_share);
130+
uint256[] memory currentPositionsBefore = _sellOverweightPositions(_share);
129131
_buyUnderweightPositions(_share);
130-
_checkCurrentPositions(_minPositions);
132+
return _checkCurrentPositions(currentPositionsBefore, _rebalanceMinPositions, _share);
131133
}
132134

133135
// Aggregates all fCash positions + asset token position into a single value
@@ -150,24 +152,41 @@ contract FixedRebalanceExtension is BaseExtension {
150152
}
151153
}
152154

153-
// Get maturities and allocations
154-
function getAllocations() external view returns (uint256[] memory, uint256[] memory) {
155-
return (maturities, allocations);
155+
// Get maturities, allocations and minPositions
156+
function getAllocations() external view returns (uint256[] memory, uint256[] memory, uint256[] memory) {
157+
return (maturities, allocations, minPositions);
156158
}
157159

158160
// Convert relative to aboslute maturity
159161
function relativeToAbsoluteMaturity(uint256 _relativeMaturity) external view returns (uint256) {
160162
return _relativeToAbsoluteMaturity(_relativeMaturity);
161163
}
162164

165+
// Return current token position for each maturity;
166+
function getCurrentPositions()
167+
external
168+
view
169+
returns(uint256[] memory currentPositions)
170+
{
171+
currentPositions = new uint256[](maturities.length);
172+
for(uint i = 0; i < maturities.length; i++) {
173+
uint256 maturity = _relativeToAbsoluteMaturity(maturities[i]);
174+
address wrappedfCash = wrappedfCashFactory.computeAddress(currencyId, uint40(maturity));
175+
int256 currentPositionSigned = setToken.getDefaultPositionRealUnit(wrappedfCash);
176+
require(currentPositionSigned >= 0, "Negative position");
177+
currentPositions[i] = uint256(currentPositionSigned);
178+
}
179+
}
180+
181+
163182

164183

165184
// /* ============ Internal Functions ============ */
166185

167186
// @dev Sells fCash positions that are currently above their targeted allocation
168187
// @dev _maturity absolute maturity of fCash to redeem
169188
// @param _share Relative share of the necessary trade volume to execute (allows for splitting the rebalance over multiple transactions
170-
function _sellOverweightPositions(uint256 _share) internal {
189+
function _sellOverweightPositions(uint256 _share) internal returns(uint256[] memory){
171190
(
172191
uint256[] memory overweightPositions,
173192
uint256[] memory currentPositions,
@@ -179,6 +198,7 @@ contract FixedRebalanceExtension is BaseExtension {
179198
_redeemFCash(absoluteMaturities[i], receiveAmount, currentPositions[i]);
180199
}
181200
}
201+
return currentPositions;
182202
}
183203

184204
// @dev Buys fCash positions that are currently below their targeted allocation
@@ -197,20 +217,40 @@ contract FixedRebalanceExtension is BaseExtension {
197217
}
198218
}
199219

200-
function _checkCurrentPositions(uint256[] memory _minPositions)
220+
// @dev Checks that the positions after rebalance are above the _rebalanceMinPositions specified in rebalanceCall
221+
// @dev Also verifies that _rebalanceMinPositions are above the minPositions configured by the operator (weighted by _share)
222+
function _checkCurrentPositions(uint256[] memory _positionsBefore, uint256[] memory _rebalanceMinPositions, uint256 _share)
201223
internal
202224
view
225+
returns(uint256[] memory currentPositions)
203226
{
204-
require(_minPositions.length == maturities.length, "Min positions must be same length as maturities");
227+
require(_rebalanceMinPositions.length == maturities.length , "Min positions must be same length as maturities");
228+
currentPositions = new uint256[](maturities.length);
205229
for(uint i = 0; i < maturities.length; i++) {
230+
231+
uint256 weightedMinPosition = _getWeightedMinPosition(minPositions[i], _positionsBefore[i], _share);
232+
require(_rebalanceMinPositions[i] >= weightedMinPosition, "Caller provided min position must not be less than operator specified value weighted by share");
206233
uint256 maturity = _relativeToAbsoluteMaturity(maturities[i]);
207234
address wrappedfCash = wrappedfCashFactory.computeAddress(currencyId, uint40(maturity));
208235
int256 currentPositionSigned = setToken.getDefaultPositionRealUnit(wrappedfCash);
209236
require(currentPositionSigned >= 0, "Negative position");
210-
require(uint256(currentPositionSigned) >= _minPositions[i], "Position below min");
237+
require(uint256(currentPositionSigned) >= _rebalanceMinPositions[i], "Position below min");
238+
currentPositions[i] = uint256(currentPositionSigned);
211239
}
212240
}
213241

242+
// @dev Calculates the minimumPosition for a given maturity by taking the weighted average between the _minPosition configured by the operator and the position before the rebalance call.
243+
function _getWeightedMinPosition(uint256 _minPosition, uint256 _positionBefore, uint256 _share) internal pure returns(uint256) {
244+
if(_minPosition > _positionBefore) {
245+
// If the position was below the min position before you have to increase it by at least _share % of the difference
246+
return _positionBefore.add(_minPosition.sub(_positionBefore).preciseMul(_share));
247+
} else {
248+
// If the position was above the min position before you can only decrease it by maximum _share % of the difference
249+
return _positionBefore.sub(_positionBefore.sub(_minPosition).preciseMul(_share));
250+
}
251+
}
252+
253+
214254
// @dev Get positions that are currently above their targeted weight
215255
function _getOverweightPositions()
216256
internal
@@ -380,21 +420,21 @@ contract FixedRebalanceExtension is BaseExtension {
380420

381421

382422
// @dev Sets configured allocations for the setToken
383-
function _setAllocations(uint256[] memory _maturities, uint256[] memory _allocations) internal {
384-
require(_maturities.length == _allocations.length, "Maturities and allocations must be same length");
423+
function _setAllocations(uint256[] memory _maturities, uint256[] memory _allocations, uint256[] memory _minPositions) internal {
424+
require((_maturities.length == _allocations.length) && (_maturities.length == _minPositions.length), "Maturities, minPositions and allocations must be same length");
385425
uint256 totalAllocation = 0;
386426
for (uint256 i = 0; i < _maturities.length; i++) {
387427
totalAllocation = totalAllocation.add(_allocations[i]);
388428
}
389429
require(totalAllocation == PreciseUnitMath.preciseUnit(), "Allocations must sum to 1");
390430
maturities = _maturities;
391-
emit AllocationsUpdated(_maturities, _allocations);
392431
allocations = _allocations;
432+
minPositions = _minPositions;
433+
emit AllocationsUpdated(_maturities, _allocations, _minPositions);
393434
}
394435

395436
// @dev Returns the token via which to trade
396437
function _getTradeToken() internal view returns(address) {
397438
return tradeViaUnderlying ? underlyingToken : assetToken;
398439
}
399-
400440
}

0 commit comments

Comments
 (0)