Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
a7362d7
fix: refactor swapRewardTokens as per @jmonteer comment
16slim May 18, 2022
6c7ff3e
fix: removed else case in findSwapTo as it wasn´t adding any value
16slim May 18, 2022
c85bdd8
fix: gas savings as per @jmonteer comments
16slim May 18, 2022
e7ae27f
fix: change modifier of setCRVPool to onlyGovernance
16slim May 18, 2022
3a40e2a
fix: minor gas savings
16slim May 18, 2022
66492ce
fix: convert pool to address
16slim May 18, 2022
799f98c
chore: change getReward for collectOwedTokens and burn 0 LP before ha…
16slim May 18, 2022
310c7f7
fix: minor gas saving
16slim May 18, 2022
7fdfe5c
feat: add revert if CRV index is not found
16slim May 18, 2022
e410586
fix: comment typo
16slim May 18, 2022
4007572
fix: comment typo
16slim May 18, 2022
384526e
chore: removed unused line
16slim May 18, 2022
4ce2a15
fix: turn harvest and harvestTrigger virtual so any inheriting Joint …
16slim May 19, 2022
be431e8
fix: change findSwapTo argument to from_token
16slim May 19, 2022
c2e4356
fix: re-organized the burn + collect functions
16slim May 19, 2022
35bbdf2
fix: remove setCRVPool function to avoid address injection
16slim May 19, 2022
5291fc6
fix: added force parameter to setTicksManually
16slim May 19, 2022
6bb769d
fix: edited the if condition and natspec on setTicksManually
16slim May 19, 2022
2ed73bc
fix: re-factor findSwapTo
16slim May 19, 2022
34a7e4e
feat: implemented swapTokenForTokenManually
16slim May 19, 2022
ab78825
chore: minor changes to tests to make them more robust
16slim May 19, 2022
da7d6d9
feat: new test for the manual operation of the strategy
16slim May 19, 2022
4091070
fix: remove modifier and empty implementation from swap manually
16slim May 20, 2022
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
92 changes: 57 additions & 35 deletions contracts/DEXes/UniV3StablesJoint.sol
Original file line number Diff line number Diff line change
Expand Up @@ -178,15 +178,6 @@ contract UniV3StablesJoint is NoHedgeJoint {
return positionInfo.liquidity;
}

/*
* @notice
* Function available for vault managers to set the CRV pool to use for swaps
* @param newPool, new CRV pool address to use
*/
function setCRVPool(address newPool) external onlyVaultManagers {
crvPool = newPool;
}

/*
* @notice
* Function available for vault managers to set the boolean value deciding wether
Expand All @@ -211,11 +202,15 @@ contract UniV3StablesJoint is NoHedgeJoint {
* @notice
* Function available for vault managers to set min & max values of the position. If,
* for any reason the ticks are not the value they should be, we always have the option
* to re-set them back to the necessary value
* to re-set them back to the necessary value using the force parameter
* @param _minTick, lower limit of position
* @param _maxTick, upper limit of position
* @param _minTick, lower limit of position
* @param forceChange, force parameter to ensure this function is not called randomly
*/
function setTicksManually(int24 _minTick, int24 _maxTick) external onlyVaultManagers {
function setTicksManually(int24 _minTick, int24 _maxTick, bool forceChange) external onlyVaultManagers {
if ((investedA > 0 || investedB > 0) && !forceChange) {
revert();
}
minTick = _minTick;
maxTick = _maxTick;
}
Expand Down Expand Up @@ -316,10 +311,10 @@ contract UniV3StablesJoint is NoHedgeJoint {
uint256 amount1Owed,
bytes calldata data
) external {
IUniswapV3Pool _pool = IUniswapV3Pool(pool);
// Only the pool can use this function
require(msg.sender == pool); // dev: callback only called by pool
require(msg.sender == address(_pool)); // dev: callback only called by pool
// Send the required funds to the pool
IUniswapV3Pool _pool = IUniswapV3Pool(pool);
IERC20(_pool.token0()).safeTransfer(address(_pool), amount0Owed);
IERC20(_pool.token1()).safeTransfer(address(_pool), amount1Owed);
}
Expand All @@ -338,10 +333,10 @@ contract UniV3StablesJoint is NoHedgeJoint {
int256 amount1Delta,
bytes calldata data
) external {
IUniswapV3Pool _pool = IUniswapV3Pool(pool);
// Only the pool can use this function
require(msg.sender == address(pool)); // dev: callback only called by pool
require(msg.sender == address(_pool)); // dev: callback only called by pool

IUniswapV3Pool _pool = IUniswapV3Pool(pool);

uint256 amountIn;
address tokenIn;
Expand All @@ -360,17 +355,12 @@ contract UniV3StablesJoint is NoHedgeJoint {

/*
* @notice
* Function claiming the earned rewards for the joint, sends the tokens to the joint
* contract
* Function used internally to collect the accrued fees by burn 0 of the LP position
* and collecting the owed tokens (only fees as no LP has been burnt)
* @return balance of tokens in the LP (invested amounts)
*/
function getReward() internal override {
IUniswapV3Pool(pool).collect(
address(this),
minTick,
maxTick,
type(uint128).max,
type(uint128).max
);
_burnAndCollect(0, minTick, maxTick);
}

/*
Expand Down Expand Up @@ -440,8 +430,7 @@ contract UniV3StablesJoint is NoHedgeJoint {
* @param amount, amount of liquidity to burn
*/
function burnLP(uint256 amount) internal override {
IUniswapV3Pool(pool).burn(minTick, maxTick, uint128(amount));
getReward();
_burnAndCollect(amount, minTick, maxTick);
// If entire position is closed, re-set the min and max ticks
IUniswapV3Pool.PositionInfo memory positionInfo = _positionInfo();
if (positionInfo.liquidity == 0){
Expand All @@ -455,6 +444,7 @@ contract UniV3StablesJoint is NoHedgeJoint {
* Function available to vault managers to burn the LP manually, if for any reason
* the ticks have been set to 0 (or any different value from the original LP), we make
* sure we can always get out of the position
* This function can be used to only collect fees by passing a 0 amount to burn
* @param _amount, amount of liquidity to burn
* @param _minTick, lower limit of position
* @param _maxTick, upper limit of position
Expand All @@ -464,22 +454,25 @@ contract UniV3StablesJoint is NoHedgeJoint {
int24 _minTick,
int24 _maxTick
) external onlyVaultManagers {
IUniswapV3Pool(pool).burn(_minTick, _maxTick, uint128(_amount));
_burnAndCollect(_amount, _minTick, _maxTick);
}

/*
* @notice
* Function available to vault managers to collect the pending rewards manually,
* if for any reason the ticks have been set to 0 (or any different value from the
* original LP), we make sure we can always get the rewards back
* Function available internally to burn the LP amount specified, for position
* defined by minTick and maxTick specified and collect the owed tokens
* @param _amount, amount of liquidity to burn
* @param _minTick, lower limit of position
* @param _maxTick, upper limit of position
*/
function collectRewardsManually(
function _burnAndCollect(
uint256 _amount,
int24 _minTick,
int24 _maxTick
) external onlyVaultManagers {
IUniswapV3Pool(pool).collect(
) internal {
IUniswapV3Pool _pool = IUniswapV3Pool(pool);
_pool.burn(_minTick, _maxTick, uint128(_amount));
_pool.collect(
address(this),
_minTick,
_maxTick,
Expand Down Expand Up @@ -570,7 +563,7 @@ contract UniV3StablesJoint is NoHedgeJoint {
// Order of swap
bool zeroForOne = _tokenFrom < _tokenTo;

// Use the uniswap helper view to simluate the swapin the uni v3 pool
// Use the uniswap helper view to simulate the swap in the uni v3 pool
(int256 _amount0, int256 _amount1, , ) = UniswapHelperViews.simulateSwap(
// pool to use
IUniswapV3Pool(pool),
Expand Down Expand Up @@ -615,6 +608,8 @@ contract UniV3StablesJoint is NoHedgeJoint {
return int128(1);
} else if (_pool.coins(2) == _token) {
return int128(2);
} else {
revert();
}
}

Expand All @@ -636,4 +631,31 @@ contract UniV3StablesJoint is NoHedgeJoint {
);
return IUniswapV3Pool(pool).positions(key);
}

/*
* @notice
* Function used by governance to swap tokens manually if needed, can be used when closing
* the LP position manually and need some re-balancing before sending funds back to the
* providers
* @param swapPath, path of addresses to swap, should be 2 and always tokenA <> tokenB
* @param swapInAmount, amount of swapPath[0] to swap for swapPath[1]
* @param minOutAmount, minimum amount of want out
* @return swapped amount
*/
function swapTokenForTokenManually(
address[] memory swapPath,
uint256 swapInAmount,
uint256 minOutAmount
) external onlyGovernance override returns (uint256) {
address _tokenA = tokenA;
address _tokenB = tokenB;
require(swapPath.length == 2);
require(swapPath[0] == _tokenA || swapPath[1] == _tokenA);
require(swapPath[0] == _tokenB || swapPath[1] == _tokenB);
return swap(
swapPath[0],
swapPath[1],
swapInAmount
);
}
}
87 changes: 36 additions & 51 deletions contracts/Joint.sol
Original file line number Diff line number Diff line change
Expand Up @@ -291,16 +291,9 @@ abstract contract Joint {
(uint256 currentBalanceA, uint256 currentBalanceB) = _closePosition();

// 2. SELL REWARDS FOR WANT
tokenAmount[] memory swappedToAmounts = swapRewardTokens();
for (uint256 i = 0; i < swappedToAmounts.length; i++) {
address rewardSwappedTo = swappedToAmounts[i].token;
uint256 rewardSwapOutAmount = swappedToAmounts[i].amount;
if (rewardSwappedTo == tokenA) {
currentBalanceA = currentBalanceA + rewardSwapOutAmount;
} else if (rewardSwappedTo == tokenB) {
currentBalanceB = currentBalanceB + rewardSwapOutAmount;
}
}
(uint256 rewardsSwappedToA, uint256 rewardsSwappedToB) = swapRewardTokens();
currentBalanceA += rewardsSwappedToA;
currentBalanceB += rewardsSwappedToB;

// 3. REBALANCE PORTFOLIO
// Calculate rebalance operation
Expand Down Expand Up @@ -382,11 +375,11 @@ abstract contract Joint {
}

// Keepers will claim and sell rewards mid-epoch (otherwise we sell only in the end)
function harvest() external onlyKeepers {
function harvest() external virtual onlyKeepers {
getReward();
}

function harvestTrigger() external view returns (bool) {
function harvestTrigger() external view virtual returns (bool) {
return balanceOfRewardToken()[0] > minRewardToHarvest;
}

Expand Down Expand Up @@ -632,19 +625,16 @@ abstract contract Joint {
* @param token, address of the token to swap from
* @return address of the token to swap to
*/
function findSwapTo(address token) internal view returns (address) {
if (tokenA == token) {
function findSwapTo(address from_token) internal view returns (address) {
if (tokenA == from_token) {
return tokenB;
} else if (tokenB == token) {
return tokenA;
} else if (_isReward(token)) {
if (tokenA == referenceToken || tokenB == referenceToken) {
return referenceToken;
}
} else if (tokenB == from_token) {
return tokenA;
} else {
revert("!swapTo");
}
if (tokenA == referenceToken || tokenB == referenceToken) {
return referenceToken;
}
return tokenA;
}

/*
Expand All @@ -658,11 +648,13 @@ abstract contract Joint {
internal
view
returns (address[] memory _path)
{
{
address _tokenA = tokenA;
address _tokenB = tokenB;
bool isReferenceToken = _token_in == address(referenceToken) ||
_token_out == address(referenceToken);
bool is_internal = (_token_in == tokenA && _token_out == tokenB) ||
(_token_in == tokenB && _token_out == tokenA);
bool is_internal = (_token_in == _tokenA && _token_out == _tokenB) ||
(_token_in == _tokenB && _token_out == _tokenA);
_path = new address[](isReferenceToken || is_internal ? 2 : 3);
_path[0] = _token_in;
if (isReferenceToken || is_internal) {
Expand All @@ -679,36 +671,29 @@ abstract contract Joint {

function withdrawLP() internal virtual {}

struct tokenAmount {
address token;
uint256 amount;
}

/*
* @notice
* Function available internally swapping amounts necessary to swap rewards
* @return tokenAmount array of the swap path followed
* @return amounts exchanged to tokenA and tokenB
*/
function swapRewardTokens()
internal
virtual
returns (tokenAmount[] memory)
returns (uint256 swappedToA, uint256 swappedToB)
{
tokenAmount[] memory _swapToAmounts = new tokenAmount[](
rewardTokens.length
);
address _tokenA = tokenA;
address _tokenB = tokenB;
for (uint256 i = 0; i < rewardTokens.length; i++) {
address reward = rewardTokens[i];
uint256 _rewardBal = IERC20(reward).balanceOf(address(this));
// If the reward token is either A or B, don't swap
if (reward == tokenA || reward == tokenB || _rewardBal == 0) {
_swapToAmounts[i] = tokenAmount(reward, 0);
if (reward == _tokenA || reward == _tokenB || _rewardBal == 0) {
continue;
// If the referenceToken is either A or B, swap rewards against it
} else if (tokenA == referenceToken || tokenB == referenceToken) {
_swapToAmounts[i] = tokenAmount(
referenceToken,
swap(reward, referenceToken, _rewardBal)
);
} else if (_tokenA == referenceToken) {
swappedToA += swap(reward, referenceToken, _rewardBal);
} else if (_tokenB == referenceToken) {
swappedToB += swap(reward, referenceToken, _rewardBal);
} else {
// Assume that position has already been liquidated
(uint256 ratioA, uint256 ratioB) = getRatios(
Expand All @@ -717,14 +702,15 @@ abstract contract Joint {
investedA,
investedB
);
address swapTo = (ratioA >= ratioB) ? tokenB : tokenA;
_swapToAmounts[i] = tokenAmount(
swapTo,
swap(reward, swapTo, _rewardBal)
);

if (ratioA >= ratioB) {
swappedToB += swap(reward, _tokenB, _rewardBal);
} else {
swappedToA += swap(reward, _tokenA, _rewardBal);
}
}
}
return _swapToAmounts;
return (swappedToA, swappedToB);
}

function swap(
Expand Down Expand Up @@ -872,13 +858,12 @@ abstract contract Joint {
address[] memory swapPath,
uint256 swapInAmount,
uint256 minOutAmount
) external onlyGovernance returns (uint256) {}
) external virtual returns (uint256);

/*
* @notice
* Function available to governance sweeping a specified token but tokenA and B
* @param expectedBalanceA, expected balance of tokenA to receive
* @param expectedBalanceB, expected balance of tokenB to receive
* @param _token, address of the token to sweep
*/
function sweep(address _token) external onlyGovernance {
require(_token != address(tokenA));
Expand Down
4 changes: 2 additions & 2 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,8 +231,8 @@ def tokenA(request, chain):
# "YFI", # YFI
# "WETH", # WETH
# 'LINK', # LINK
# 'USDT', # USDT
'DAI', # DAI
'USDT', # USDT
# 'DAI', # DAI
# "USDC", # USDC
# "WFTM",
# "MIM",
Expand Down
Loading