Skip to content

Commit f74a65a

Browse files
committed
updated charm managers to use the new pricer
1 parent 5878b41 commit f74a65a

File tree

4 files changed

+237
-421
lines changed

4 files changed

+237
-421
lines changed
Lines changed: 61 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -1,98 +1,65 @@
11
// SPDX-License-Identifier: BUSL-1.1
2-
// solhint-disable-next-line compiler-version
3-
pragma solidity ^0.7.6;
4-
pragma abicoder v2;
2+
pragma solidity ^0.8.24;
53

6-
import { FullMath } from "@uniswap/v3-core/contracts/libraries/FullMath.sol";
7-
import { TickMath } from "@uniswap/v3-core/contracts/libraries/TickMath.sol";
8-
import { PositionKey } from "@uniswap/v3-periphery/contracts/libraries/PositionKey.sol";
4+
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
5+
import { AlphaVaultHelpers } from "../_utils/AlphaVaultHelpers.sol";
96

10-
import { ISpotPricingStrategy } from "./_interfaces/ISpotPricingStrategy.sol";
11-
import { IAlphaProVault } from "./_interfaces/external/IAlphaProVault.sol";
7+
import { IMetaOracle } from "../_interfaces/IMetaOracle.sol";
8+
import { IAlphaProVault } from "../_interfaces/external/IAlphaProVault.sol";
129
import { IUniswapV3Pool } from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol";
1310

1411
/// @title UsdcSpotManager
1512
/// @notice This contract is a programmatic manager for the USDC-SPOT Charm AlphaProVault.
16-
contract UsdcSpotManager {
17-
/// @dev Token Constants.
18-
uint256 public constant ONE_SPOT = 1e9;
19-
uint256 public constant ONE_USDC = 1e6;
13+
contract UsdcSpotManager is Ownable {
14+
//-------------------------------------------------------------------------
15+
// Libraries
16+
using AlphaVaultHelpers for IAlphaProVault;
17+
18+
//-------------------------------------------------------------------------
19+
// Constants & Immutables
2020

2121
/// @dev Decimals.
2222
uint256 public constant DECIMALS = 18;
2323
uint256 public constant ONE = (10 ** DECIMALS);
2424

25-
/// @dev We bound the deviation factor to 100.0.
26-
uint256 public constant MAX_DEVIATION = 100 * ONE; // 100.0
27-
28-
//-------------------------------------------------------------------------
29-
// Storage
30-
3125
/// @notice The USDC-SPOT charm alpha vault.
3226
IAlphaProVault public immutable VAULT;
3327

3428
/// @notice The underlying USDC-SPOT univ3 pool.
3529
IUniswapV3Pool public immutable POOL;
3630

37-
/// @notice The vault's token0, the USDC token.
38-
address public immutable USDC;
39-
40-
/// @notice The vault's token1, the SPOT token.
41-
address public immutable SPOT;
42-
43-
/// @notice Pricing strategy to price the SPOT token.
44-
ISpotPricingStrategy public pricingStrategy;
45-
46-
/// @notice The contract owner.
47-
address public owner;
48-
4931
//-------------------------------------------------------------------------
50-
// Manager storage
32+
// Storage
33+
34+
/// @notice The meta oracle which returns prices of AMPL asset family.
35+
IMetaOracle public oracle;
5136

5237
/// @notice The recorded deviation factor at the time of the last successful rebalance operation.
5338
uint256 public prevDeviation;
5439

55-
//--------------------------------------------------------------------------
56-
// Modifiers
57-
58-
modifier onlyOwner() {
59-
// solhint-disable-next-line custom-errors
60-
require(msg.sender == owner, "Unauthorized caller");
61-
_;
62-
}
63-
6440
//-----------------------------------------------------------------------------
6541
// Constructor and Initializer
6642

6743
/// @notice Constructor initializes the contract with provided addresses.
6844
/// @param vault_ Address of the AlphaProVault contract.
69-
/// @param pricingStrategy_ Address of the spot appraiser.
70-
constructor(IAlphaProVault vault_, ISpotPricingStrategy pricingStrategy_) {
71-
owner = msg.sender;
72-
45+
/// @param oracle_ Address of the MetaOracle contract.
46+
constructor(IAlphaProVault vault_, IMetaOracle oracle_) Ownable() {
7347
VAULT = vault_;
7448
POOL = vault_.pool();
75-
USDC = vault_.token0();
76-
SPOT = vault_.token1();
7749

78-
pricingStrategy = pricingStrategy_;
79-
// solhint-disable-next-line custom-errors
80-
require(pricingStrategy.decimals() == DECIMALS, "Invalid decimals");
50+
updateOracle(oracle_);
51+
52+
prevDeviation = 0;
8153
}
8254

8355
//--------------------------------------------------------------------------
8456
// Owner only methods
8557

86-
/// @notice Updates the owner role.
87-
function transferOwnership(address owner_) external onlyOwner {
88-
owner = owner_;
89-
}
90-
91-
/// @notice Updates the Spot pricing strategy reference.
92-
function updatePricingStrategy(
93-
ISpotPricingStrategy pricingStrategy_
94-
) external onlyOwner {
95-
pricingStrategy = pricingStrategy_;
58+
/// @notice Updates the MetaOracle.
59+
function updateOracle(IMetaOracle oracle_) public onlyOwner {
60+
// solhint-disable-next-line custom-errors
61+
require(DECIMALS == oracle_.decimals(), "UnexpectedDecimals");
62+
oracle = oracle_;
9663
}
9764

9865
/// @notice Updates the vault's liquidity range parameters.
@@ -116,7 +83,7 @@ contract UsdcSpotManager {
11683
// solhint-disable-next-line avoid-low-level-calls
11784
(bool success, bytes memory r) = address(VAULT).call(callData);
11885
// solhint-disable-next-line custom-errors
119-
require(success, "Vault call failed");
86+
require(success, "VaultExecutionFailed");
12087
return r;
12188
}
12289

@@ -125,107 +92,60 @@ contract UsdcSpotManager {
12592

12693
/// @notice Executes vault rebalance.
12794
function rebalance() public {
128-
(uint256 deviation, bool deviationValid) = computeDeviationFactor();
129-
130-
// We rebalance if the deviation factor has crossed ONE (in either direction).
131-
bool forceLiquidityUpdate = ((deviation <= ONE && prevDeviation > ONE) ||
132-
(deviation >= ONE && prevDeviation < ONE));
95+
(uint256 deviation, bool deviationValid) = oracle.spotPriceDeviation();
13396

13497
// Execute rebalance.
13598
// NOTE: the vault.rebalance() will revert if enough time has not elapsed.
13699
// We thus override with a force rebalance.
137100
// https://learn.charm.fi/charm/technical-references/core/alphaprovault#rebalance
138-
forceLiquidityUpdate ? _execForceRebalance() : VAULT.rebalance();
139-
140-
// We only activate the limit range liquidity, when
141-
// the vault sells SPOT and deviation is above ONE, or when
142-
// the vault buys SPOT and deviation is below ONE
143-
bool extraSpot = isOverweightSpot();
144-
bool activeLimitRange = deviationValid &&
145-
((deviation >= ONE && extraSpot) || (deviation <= ONE && !extraSpot));
101+
(deviationValid && shouldForceRebalance(deviation, prevDeviation))
102+
? VAULT.forceRebalance()
103+
: VAULT.rebalance();
146104

147105
// Trim positions after rebalance.
148-
if (!activeLimitRange) {
149-
_removeLimitLiquidity();
106+
if (!deviationValid || shouldRemoveLimitRange(deviation)) {
107+
VAULT.removeLimitLiquidity(POOL);
150108
}
151109

152-
// Update rebalance state.
153-
prevDeviation = deviation;
110+
// Update valid rebalance state.
111+
if (deviationValid) {
112+
prevDeviation = deviation;
113+
}
154114
}
155115

156-
/// @notice Computes the deviation between SPOT's market price and it's FMV price.
157-
/// @return The computed deviation factor.
158-
function computeDeviationFactor() public returns (uint256, bool) {
159-
uint256 spotMarketPrice = getSpotUSDPrice();
160-
(uint256 spotTargetPrice, bool spotTargetPriceValid) = pricingStrategy
161-
.perpPrice();
162-
(, bool usdcPriceValid) = pricingStrategy.usdPrice();
163-
bool deviationValid = (spotTargetPriceValid && usdcPriceValid);
164-
uint256 deviation = spotTargetPrice > 0
165-
? FullMath.mulDiv(spotMarketPrice, ONE, spotTargetPrice)
166-
: type(uint256).max;
167-
deviation = (deviation > MAX_DEVIATION) ? MAX_DEVIATION : deviation;
168-
return (deviation, deviationValid);
116+
//-----------------------------------------------------------------------------
117+
// External/Public view methods
118+
119+
/// @notice Checks if a rebalance has to be forced.
120+
function shouldForceRebalance(
121+
uint256 deviation,
122+
uint256 prevDeviation_
123+
) public pure returns (bool) {
124+
// We rebalance if the deviation factor has crossed ONE (in either direction).
125+
return ((deviation <= ONE && prevDeviation_ > ONE) ||
126+
(deviation >= ONE && prevDeviation_ < ONE));
169127
}
170128

171-
//-----------------------------------------------------------------------------
172-
// External Public view methods
173-
174-
/// @return The computed SPOT price in USD from the underlying univ3 pool.
175-
function getSpotUSDPrice() public view returns (uint256) {
176-
uint160 sqrtPriceX96 = TickMath.getSqrtRatioAtTick(VAULT.getTwap());
177-
uint256 ratioX192 = uint256(sqrtPriceX96) * sqrtPriceX96;
178-
uint256 usdcPerSpot = FullMath.mulDiv(ONE, (1 << 192), ratioX192);
179-
return FullMath.mulDiv(usdcPerSpot, ONE_SPOT, ONE_USDC);
129+
/// @notice Checks if limit range liquidity needs to be removed.
130+
function shouldRemoveLimitRange(uint256 deviation) public view returns (bool) {
131+
// We only activate the limit range liquidity, when
132+
// the vault sells SPOT and deviation is above ONE, or when
133+
// the vault buys SPOT and deviation is below ONE
134+
bool extraSpot = isOverweightSpot();
135+
bool activeLimitRange = ((deviation >= ONE && extraSpot) ||
136+
(deviation <= ONE && !extraSpot));
137+
return (!activeLimitRange);
180138
}
181139

182-
/// @notice Checks the vault is overweight SPOT, and looking to sell the extra SPOT for USDC.
140+
/// @notice Checks the vault is overweight SPOT and looking to sell the extra SPOT for USDC.
183141
function isOverweightSpot() public view returns (bool) {
184-
// NOTE: This assumes that in the underlying univ3 pool and
185-
// token0 is USDC and token1 is SPOT.
186-
int24 _marketPrice = VAULT.getTwap();
187-
int24 _limitLower = VAULT.limitLower();
188-
int24 _limitUpper = VAULT.limitUpper();
189-
int24 _limitPrice = (_limitLower + _limitUpper) / 2;
190-
// The limit range has more token1 than token0 if `_marketPrice >= _limitPrice`,
191-
// so the vault looks to sell token1.
192-
return (_marketPrice >= _limitPrice);
142+
// NOTE: In the underlying univ3 pool and token0 is USDC and token1 is SPOT.
143+
// Underweight Token0 implies that the limit range has less USDC and more SPOT.
144+
return VAULT.isUnderweightToken0();
193145
}
194146

195147
/// @return Number of decimals representing 1.0.
196148
function decimals() external pure returns (uint8) {
197149
return uint8(DECIMALS);
198150
}
199-
200-
//-----------------------------------------------------------------------------
201-
// Private methods
202-
203-
/// @dev A low-level method, which interacts directly with the vault and executes
204-
/// a rebalance even when enough time hasn't elapsed since the last rebalance.
205-
function _execForceRebalance() private {
206-
uint32 _period = VAULT.period();
207-
VAULT.setPeriod(0);
208-
VAULT.rebalance();
209-
VAULT.setPeriod(_period);
210-
}
211-
212-
/// @dev Removes the vault's limit range liquidity.
213-
/// To be invoked right after a rebalance operation, as it assumes that
214-
/// the vault has a active limit range liquidity.
215-
function _removeLimitLiquidity() private {
216-
int24 _limitLower = VAULT.limitLower();
217-
int24 _limitUpper = VAULT.limitUpper();
218-
(uint128 limitLiquidity, , , , ) = _position(_limitLower, _limitUpper);
219-
// docs: https://learn.charm.fi/charm/technical-references/core/alphaprovault#emergencyburn
220-
VAULT.emergencyBurn(_limitLower, _limitUpper, limitLiquidity);
221-
}
222-
223-
/// @dev Wrapper around `IUniswapV3Pool.positions()`.
224-
function _position(
225-
int24 tickLower,
226-
int24 tickUpper
227-
) private view returns (uint128, uint256, uint256, uint128, uint128) {
228-
bytes32 positionKey = PositionKey.compute(address(VAULT), tickLower, tickUpper);
229-
return POOL.positions(positionKey);
230-
}
231151
}

0 commit comments

Comments
 (0)