diff --git a/contracts/AutoRedemption.sol b/contracts/AutoRedemption.sol index 113c385..f51df05 100644 --- a/contracts/AutoRedemption.sol +++ b/contracts/AutoRedemption.sol @@ -12,7 +12,8 @@ import {IUniswapV3Pool} from "contracts/interfaces/IUniswapV3Pool.sol"; import {IQuoter} from "contracts/interfaces/IQuoter.sol"; import {TickMath} from "src/uniswap/TickMath.sol"; import {LiquidityAmounts} from "src/uniswap/LiquidityAmounts.sol"; -import {IERC20} from "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; +import {IERC20} from + "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; contract AutoRedemption is AutomationCompatibleInterface, FunctionsClient, ConfirmedOwner { using Functions for Functions.Request; @@ -31,24 +32,23 @@ contract AutoRedemption is AutomationCompatibleInterface, FunctionsClient, Confi mapping(address => address) hypervisorCollaterals; mapping(address => bytes) swapPaths; - string source = - "const { ethers } = await import('npm:ethers@6.10.0')" + string source = "const { ethers } = await import('npm:ethers@6.10.0')" "const apiResponse = await Functions.makeHttpRequest({" - "url: 'https://smart-vault-api.thestandard.io/redemption'" - "});" - "if (apiResponse.error) {" - "throw Error('Request failed');" - "}" - "const { data } = apiResponse;" - "const encoded = ethers.AbiCoder.defaultAbiCoder().encode(" - "['uint256', 'address', 'uint256']," - "[data.tokenID, data.collateral, data.value]" - ");" - "return ethers.getBytes(encoded);"; + "url: 'https://smart-vault-api.thestandard.io/redemption'" "});" "if (apiResponse.error) {" + "throw Error('Request failed');" "}" "const { data } = apiResponse;" + "const encoded = ethers.AbiCoder.defaultAbiCoder().encode(" "['uint256', 'address', 'uint256']," + "[data.tokenID, data.collateral, data.value]" ");" "return ethers.getBytes(encoded);"; constructor( - address _smartVaultManager, address _functionsRouter, address _pool, address _smartVaultIndex, address _swapRouter, - address _quoter, uint160 _triggerPrice, uint64 _subscriptionID, uint256 _lastLegacyVaultID + address _smartVaultManager, + address _functionsRouter, + address _pool, + address _smartVaultIndex, + address _swapRouter, + address _quoter, + uint160 _triggerPrice, + uint64 _subscriptionID, + uint256 _lastLegacyVaultID ) FunctionsClient(_functionsRouter) ConfirmedOwner(msg.sender) { smartVaultManager = _smartVaultManager; swapRouter = _swapRouter; @@ -81,21 +81,27 @@ contract AutoRedemption is AutomationCompatibleInterface, FunctionsClient, Confi function calculateUSDCToTargetPrice() private view returns (uint256 _usdc) { int24 _spacing = pool.tickSpacing(); - (uint160 _sqrtPriceX96,int24 _tick,,,,,) = pool.slot0(); + (uint160 _sqrtPriceX96, int24 _tick,,,,,) = pool.slot0(); int24 _upperTick = _tick / _spacing * _spacing; int24 _lowerTick = _upperTick - _spacing; uint128 _liquidity = pool.liquidity(); while (TickMath.getSqrtRatioAtTick(_lowerTick) < TARGET_PRICE) { uint256 _amount0; if (_tick > _lowerTick && _tick < _upperTick) { - (uint256 _amount0,) = LiquidityAmounts.getAmountsForLiquidity( - _sqrtPriceX96, TickMath.getSqrtRatioAtTick(_lowerTick), TickMath.getSqrtRatioAtTick(_upperTick), _liquidity + (uint256 _amount0,) = LiquidityAmounts.getAmountsForLiquidity( + _sqrtPriceX96, + TickMath.getSqrtRatioAtTick(_lowerTick), + TickMath.getSqrtRatioAtTick(_upperTick), + _liquidity ); } else { - (,int128 _liquidityNet,,,,,,) = pool.ticks(_lowerTick); + (, int128 _liquidityNet,,,,,,) = pool.ticks(_lowerTick); _liquidity += uint128(_liquidityNet); - (uint256 _amount0,) = LiquidityAmounts.getAmountsForLiquidity( - _sqrtPriceX96, TickMath.getSqrtRatioAtTick(_lowerTick), TickMath.getSqrtRatioAtTick(_upperTick), _liquidity + (uint256 _amount0,) = LiquidityAmounts.getAmountsForLiquidity( + _sqrtPriceX96, + TickMath.getSqrtRatioAtTick(_lowerTick), + TickMath.getSqrtRatioAtTick(_upperTick), + _liquidity ); } _usdc += _amount0; @@ -104,36 +110,42 @@ contract AutoRedemption is AutomationCompatibleInterface, FunctionsClient, Confi } } - function legacyAutoRedemption(address _smartVault, address _token, bytes memory _collateralToUSDCPath, uint256 _USDCTargetAmount, uint256 _estimatedCollateralValueUSD) private { - uint256 _collateralBalance = _token == address(0) ? - _smartVault.balance : - IERC20(_token).balanceOf(_smartVault); - (uint256 _approxAmountInRequired,,,) = IQuoter(quoter).quoteExactOutput(_collateralToUSDCPath, _USDCTargetAmount); + function legacyAutoRedemption( + address _smartVault, + address _token, + bytes memory _collateralToUSDCPath, + uint256 _USDCTargetAmount, + uint256 _estimatedCollateralValueUSD + ) private { + uint256 _collateralBalance = _token == address(0) ? _smartVault.balance : IERC20(_token).balanceOf(_smartVault); + (uint256 _approxAmountInRequired,,,) = + IQuoter(quoter).quoteExactOutput(_collateralToUSDCPath, _USDCTargetAmount); uint256 _amountIn = _approxAmountInRequired > _collateralBalance ? _collateralBalance : _approxAmountInRequired; IRedeemableLegacy(_smartVault).autoRedemption(swapRouter, _token, _collateralToUSDCPath, _amountIn); } - function fulfillRequest( - bytes32 requestId, - bytes memory response, - bytes memory err - ) internal override { + function fulfillRequest(bytes32 requestId, bytes memory response, bytes memory err) internal override { // TODO proper error handling // if (err) revert; if (requestId != lastRequestId) revert("wrong request"); uint256 _USDCTargetAmount = calculateUSDCToTargetPrice(); - (uint256 _tokenID, address _token, uint256 _estimatedCollateralValueUSD) = abi.decode(response,(uint256,address,uint256)); + (uint256 _tokenID, address _token, uint256 _estimatedCollateralValueUSD) = + abi.decode(response, (uint256, address, uint256)); bytes memory _collateralToUSDCPath = swapPaths[_token]; address _smartVault = ISmartVaultIndex(smartVaultIndex).getVaultAddress(_tokenID); if (_tokenID <= lastLegacyVaultID) { - legacyAutoRedemption(_smartVault, _token, _collateralToUSDCPath, _USDCTargetAmount, _estimatedCollateralValueUSD); + legacyAutoRedemption( + _smartVault, _token, _collateralToUSDCPath, _USDCTargetAmount, _estimatedCollateralValueUSD + ); } else { address _hypervisor; if (hypervisorCollaterals[_token] != address(0)) { _hypervisor = _token; _token = hypervisorCollaterals[_hypervisor]; } - IRedeemable(_smartVault).autoRedemption(swapRouter, quoter, _token, _collateralToUSDCPath, _USDCTargetAmount, _hypervisor); + IRedeemable(_smartVault).autoRedemption( + swapRouter, quoter, _token, _collateralToUSDCPath, _USDCTargetAmount, _hypervisor + ); } lastRequestId = bytes32(0); } diff --git a/contracts/SmartVaultV4.sol b/contracts/SmartVaultV4.sol index 7135e0c..bc7a375 100644 --- a/contracts/SmartVaultV4.sol +++ b/contracts/SmartVaultV4.sol @@ -292,11 +292,15 @@ contract SmartVaultV4 is ISmartVault, IRedeemable { } } - function calculateAmountIn(address _quoterAddress, address _collateralToken, bytes memory _swapPath, uint256 _USDCTargetAmount) private returns (uint256) { + function calculateAmountIn( + address _quoterAddress, + address _collateralToken, + bytes memory _swapPath, + uint256 _USDCTargetAmount + ) private returns (uint256) { (uint256 _quoteAmountIn,,,) = IQuoter(_quoterAddress).quoteExactOutput(_swapPath, _USDCTargetAmount); uint256 _collateralBalance = getAssetBalance(_collateralToken); - return _quoteAmountIn > _collateralBalance ? - _collateralBalance : _quoteAmountIn; + return _quoteAmountIn > _collateralBalance ? _collateralBalance : _quoteAmountIn; } function swapCollateral( @@ -308,21 +312,25 @@ contract SmartVaultV4 is ISmartVault, IRedeemable { ) private { uint256 _amountIn = calculateAmountIn(_quoterAddress, _collateralToken, _swapPath, _USDCTargetAmount); IERC20(_collateralToken).safeIncreaseAllowance(_swapRouterAddress, _amountIn); - ISwapRouter(_swapRouterAddress).exactInput(ISwapRouter.ExactInputParams({ - path: _swapPath, - recipient: address(this), - deadline: block.timestamp, - amountIn: _amountIn, - // minimum USD value of collateral out from swap - amountOutMinimum: calculator.tokenToUSD( - ITokenManager(ISmartVaultManagerV3(manager).tokenManager()).getTokenIfExists(_collateralToken), - _amountIn - ) - })); + ISwapRouter(_swapRouterAddress).exactInput( + ISwapRouter.ExactInputParams({ + path: _swapPath, + recipient: address(this), + deadline: block.timestamp, + amountIn: _amountIn, + // minimum USD value of collateral out from swap + amountOutMinimum: calculator.tokenToUSD( + ITokenManager(ISmartVaultManagerV3(manager).tokenManager()).getTokenIfExists(_collateralToken), + _amountIn + ) + }) + ); IERC20(_collateralToken).forceApprove(_swapRouterAddress, 0); } - function redeposit(uint256 _withdrawn, uint256 _collateralBalance, address _hypervisor, address _collateralToken) private { + function redeposit(uint256 _withdrawn, uint256 _collateralBalance, address _hypervisor, address _collateralToken) + private + { uint256 _redeposit = _withdrawn > _collateralBalance ? _collateralBalance : _withdrawn; address _yieldManager = ISmartVaultManagerV3(manager).yieldManager(); IERC20(_collateralToken).safeIncreaseAllowance(_yieldManager, _redeposit); @@ -429,7 +437,13 @@ contract SmartVaultV4 is ISmartVault, IRedeemable { ) revert Undercollateralised(); } - function merklClaim(address _distributor, address[] calldata users, address[] calldata tokens, uint256[] calldata amounts, bytes32[][] calldata proofs) external onlyOwner { + function merklClaim( + address _distributor, + address[] calldata users, + address[] calldata tokens, + uint256[] calldata amounts, + bytes32[][] calldata proofs + ) external onlyOwner { IMerklDistributor(_distributor).claim(users, tokens, amounts, proofs); } diff --git a/contracts/SmartVaultV4Legacy.sol b/contracts/SmartVaultV4Legacy.sol index 1286dfe..0dde80c 100644 --- a/contracts/SmartVaultV4Legacy.sol +++ b/contracts/SmartVaultV4Legacy.sol @@ -391,7 +391,13 @@ contract SmartVaultV4Legacy is ISmartVault, IRedeemableLegacy { ) revert Undercollateralised(); } - function merklClaim(address _distributor, address[] calldata users, address[] calldata tokens, uint256[] calldata amounts, bytes32[][] calldata proofs) external onlyOwner { + function merklClaim( + address _distributor, + address[] calldata users, + address[] calldata tokens, + uint256[] calldata amounts, + bytes32[][] calldata proofs + ) external onlyOwner { IMerklDistributor(_distributor).claim(users, tokens, amounts, proofs); } diff --git a/contracts/SmartVaultYieldManager.sol b/contracts/SmartVaultYieldManager.sol index 5d17696..07ac3a6 100644 --- a/contracts/SmartVaultYieldManager.sol +++ b/contracts/SmartVaultYieldManager.sol @@ -324,9 +324,7 @@ contract SmartVaultYieldManager is ISmartVaultYieldManager, Ownable { _otherDeposit(_collateralToken, _hypervisorData); } - function quickWithdraw(address _hypervisor, address _token) external returns (uint256 _withdrawn) { - - } + function quickWithdraw(address _hypervisor, address _token) external returns (uint256 _withdrawn) {} function addHypervisorData( address _collateralToken, diff --git a/contracts/interfaces/IMerklDistributor.sol b/contracts/interfaces/IMerklDistributor.sol index 8e31e6b..de7245f 100644 --- a/contracts/interfaces/IMerklDistributor.sol +++ b/contracts/interfaces/IMerklDistributor.sol @@ -2,5 +2,10 @@ pragma solidity 0.8.21; interface IMerklDistributor { - function claim(address[] calldata users, address[] calldata tokens, uint256[] calldata amounts, bytes32[][] calldata proofs) external; + function claim( + address[] calldata users, + address[] calldata tokens, + uint256[] calldata amounts, + bytes32[][] calldata proofs + ) external; } diff --git a/contracts/interfaces/IQuoter.sol b/contracts/interfaces/IQuoter.sol index 422395d..b2b16cc 100644 --- a/contracts/interfaces/IQuoter.sol +++ b/contracts/interfaces/IQuoter.sol @@ -135,4 +135,4 @@ interface IQuoter { uint32[] memory initializedTicksCrossedList, uint256 gasEstimate ); -} \ No newline at end of file +} diff --git a/contracts/interfaces/IUniswapV3Pool.sol b/contracts/interfaces/IUniswapV3Pool.sol index 13e977c..ddc2c91 100644 --- a/contracts/interfaces/IUniswapV3Pool.sol +++ b/contracts/interfaces/IUniswapV3Pool.sol @@ -26,15 +26,18 @@ interface IUniswapV3Pool { function increaseObservationCardinalityNext(uint16 observationCardinalityNext) external; function liquidity() external view returns (uint128); - - function ticks(int24 tick) external view returns ( - uint128 liquidityGross, - int128 liquidityNet, - uint256 feeGrowthOutside0X128, - uint256 feeGrowthOutside1X128, - int56 tickCumulativeOutside, - uint160 secondsPerLiquidityOutsideX128, - uint32 secondsOutside, - bool initialized - ); + + function ticks(int24 tick) + external + view + returns ( + uint128 liquidityGross, + int128 liquidityNet, + uint256 feeGrowthOutside0X128, + uint256 feeGrowthOutside1X128, + int56 tickCumulativeOutside, + uint160 secondsPerLiquidityOutsideX128, + uint32 secondsOutside, + bool initialized + ); }