generated from open-dollar/contracts-template
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #10 from open-dollar/leverage-mgmt
Calculate & Swap
- Loading branch information
Showing
8 changed files
with
267 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
// SPDX-License-Identifier: GPL-3.0 | ||
pragma solidity 0.8.20; | ||
|
||
import {ISAFEEngine} from '@opendollar/interfaces/ISAFEEngine.sol'; | ||
import {IVault721} from '@opendollar/interfaces/proxies/IVault721.sol'; | ||
import {IODSafeManager} from '@opendollar/interfaces/proxies/IODSafeManager.sol'; | ||
import {Math, RAY} from '@opendollar/libraries/Math.sol'; | ||
|
||
contract LeverageCalculator { | ||
using Math for uint256; | ||
|
||
IVault721 public immutable VAULT721; | ||
ISAFEEngine public immutable SAFEENGINE; | ||
|
||
constructor(address _vault721) { | ||
VAULT721 = IVault721(_vault721); | ||
SAFEENGINE = ISAFEEngine(IODSafeManager(VAULT721.safeManager()).safeEngine()); | ||
} | ||
|
||
/// @dev calculate max single-swap leverage based on initial locked collateral | ||
function calculateSingleLeverage(uint256 _safeId) public view returns (uint256 _leverage) { | ||
(bytes32 _cType, address _safeHandler) = getNFVIds(_safeId); | ||
(uint256 _collateral, uint256 _debt) = getNFVLockedAndDebt(_cType, _safeHandler); | ||
(uint256 _accumulatedRate, uint256 _safetyPrice) = getCData(_cType); | ||
|
||
uint256 _maxSafetyDebt = _collateral.wmul(_safetyPrice).wdiv(_accumulatedRate); | ||
|
||
if (_maxSafetyDebt > _debt) { | ||
_leverage = _maxSafetyDebt - _debt; | ||
} | ||
} | ||
|
||
/// @dev calculate max loop-swap leverage based on initial locked collateral | ||
function calculateMultipleLeverage(uint256 _safeId) external view returns (uint256 _leverage) { | ||
uint256 _accumulator; | ||
uint256 _debtIterator; | ||
|
||
_debtIterator = calculateSingleLeverage(_safeId); | ||
|
||
while (_debtIterator > 200 ether) { | ||
_accumulator += _debtIterator; | ||
// TODO: recalculate collateral or execute leverage swap | ||
_debtIterator = calculateSingleLeverage(_safeId); | ||
} | ||
} | ||
|
||
/// @dev calculate max flashloan leverage based on initial locked collateral | ||
function calculateFlashLeverage(uint256 _safeId) external view returns (uint256 _leverage) { | ||
// TODO: calculate max leverage | ||
return _safeId; | ||
} | ||
|
||
/// @return _internalDebt internal account of COIN for an account (internal) | ||
function getCoinBalance(address _proxy) public view returns (uint256 _internalDebt) { | ||
_internalDebt = SAFEENGINE.coinBalance(_proxy) / RAY; | ||
} | ||
|
||
/// @dev get cType and safe handler of NFV | ||
function getNFVIds(uint256 _safeId) public view returns (bytes32 _cType, address _safeHandler) { | ||
IVault721.NFVState memory nftState = VAULT721.getNfvState(_safeId); | ||
_cType = nftState.cType; | ||
_safeHandler = nftState.safeHandler; | ||
} | ||
|
||
/// @dev get locked collateral and generated debt | ||
function getNFVLockedAndDebt( | ||
bytes32 _cType, | ||
address _safeHandler | ||
) public view returns (uint256 _collateral, uint256 _debt) { | ||
ISAFEEngine.SAFE memory _safeData = SAFEENGINE.safes(_cType, _safeHandler); | ||
_collateral = _safeData.lockedCollateral; | ||
_debt = _safeData.generatedDebt; | ||
} | ||
|
||
/// @dev get accumulated rate and safety price for a cType | ||
function getCData(bytes32 _cType) public view returns (uint256 _accumulatedRate, uint256 _safetyPrice) { | ||
ISAFEEngine.SAFEEngineCollateralData memory _safeEngCData = SAFEENGINE.cData(_cType); | ||
_accumulatedRate = _safeEngCData.accumulatedRate; | ||
_safetyPrice = _safeEngCData.safetyPrice; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
// SPDX-License-Identifier: GPL-3.0 | ||
pragma solidity 0.8.20; | ||
|
||
contract LeverageSwapper { | ||
// Todo create and send order to PARASWAP_AUGUSTUS_RFQ | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
// SPDX-License-Identifier: GPL-3.0 | ||
pragma solidity 0.8.20; | ||
|
||
import {IERC20} from '@openzeppelin/token/ERC20/IERC20.sol'; | ||
import {MintableERC20} from '@opendollar/contracts/for-test/MintableERC20.sol'; | ||
import {TKN} from '@opendollar/test/e2e/Common.t.sol'; | ||
import {CommonTest} from 'test/CommonTest.t.sol'; | ||
|
||
contract E2ELeverageCalculator is CommonTest { | ||
address public constant LEVERAGE_HANDLER = address(0x1111); | ||
address public leverageHandlerProxy; | ||
|
||
function setUp() public virtual override { | ||
super.setUp(); | ||
MintableERC20(token).mint(address(this), DEPOSIT); | ||
|
||
leverageHandlerProxy = _deployOrFind(LEVERAGE_HANDLER); | ||
|
||
vm.prank(LEVERAGE_HANDLER); | ||
IERC20(token).approve(leverageHandlerProxy, type(uint256).max); | ||
|
||
vm.prank(aliceProxy); | ||
safeManager.allowSAFE(vaults[aliceProxy], leverageHandlerProxy, true); | ||
} | ||
|
||
function testLockCollateral() public { | ||
/// @notice collateral locked | ||
_lockCollateral(TKN, vaults[aliceProxy], DEPOSIT, aliceProxy); | ||
(uint256 _collateral,) = leverageCalculator.getNFVLockedAndDebt(TKN, aliceNFV.safeHandler); | ||
assertEq(_collateral, DEPOSIT); | ||
} | ||
|
||
/// @notice exited coins transfered to proxy | ||
function testLockAndGenerateDebtToProxy() public { | ||
_lockCollateral(TKN, vaults[aliceProxy], DEPOSIT, aliceProxy); | ||
_genDebtToProxy(vaults[aliceProxy], MINT, aliceProxy); | ||
(, uint256 _debt) = leverageCalculator.getNFVLockedAndDebt(TKN, aliceNFV.safeHandler); | ||
assertEq(_debt, MINT); | ||
uint256 _internalDebt = leverageCalculator.getCoinBalance(aliceProxy); | ||
assertEq(_internalDebt, 0); | ||
} | ||
|
||
/// @notice internal coins are available to proxy | ||
function testLockAndGenerateInternalDebt() public { | ||
_lockCollateral(TKN, vaults[aliceProxy], DEPOSIT, aliceProxy); | ||
_genInternalDebt(vaults[aliceProxy], MINT, aliceProxy); | ||
(, uint256 _debt) = leverageCalculator.getNFVLockedAndDebt(TKN, aliceNFV.safeHandler); | ||
assertEq(_debt, MINT); | ||
uint256 _internalDebt = leverageCalculator.getCoinBalance(aliceProxy); | ||
assertEq(_internalDebt, _debt); | ||
} | ||
|
||
/// @notice 1/2 internal coins were exited to proxy | ||
function testLockAndGenerateInternalDebtAndPartialExitToProxy() public { | ||
_lockCollateral(TKN, vaults[aliceProxy], DEPOSIT, aliceProxy); | ||
_genInternalDebt(vaults[aliceProxy], MINT, aliceProxy); | ||
assertEq(systemCoin.balanceOf(aliceProxy), 0); | ||
|
||
_exitCoinToAccount(aliceProxy, aliceProxy, MINT / 2); | ||
(, uint256 _debt) = leverageCalculator.getNFVLockedAndDebt(TKN, aliceNFV.safeHandler); | ||
assertEq(_debt, MINT); | ||
|
||
uint256 _internalDebt = leverageCalculator.getCoinBalance(aliceProxy); | ||
assertEq(_internalDebt, _debt / 2); | ||
assertEq(systemCoin.balanceOf(aliceProxy), MINT / 2); | ||
} | ||
|
||
/// @notice leverageAmount used to make swap and leverage safe | ||
function testCalcSingleSwap() public { | ||
_lockCollateral(TKN, vaults[aliceProxy], DEPOSIT, aliceProxy); | ||
(uint256 _initCollateral,) = leverageCalculator.getNFVLockedAndDebt(TKN, aliceNFV.safeHandler); | ||
|
||
uint256 _leverageAmount = leverageCalculator.calculateSingleLeverage(vaults[aliceProxy]); | ||
_genDebtToAccount(LEVERAGE_HANDLER, vaults[aliceProxy], _leverageAmount, aliceProxy); | ||
assertEq(systemCoin.balanceOf(LEVERAGE_HANDLER), _leverageAmount); | ||
|
||
vm.prank(LEVERAGE_HANDLER); | ||
_swapIn(LEVERAGE_HANDLER, _leverageAmount); | ||
_swapOut(LEVERAGE_HANDLER); | ||
|
||
assertEq(systemCoin.balanceOf(LEVERAGE_HANDLER), 0); | ||
assertEq(IERC20(token).balanceOf(LEVERAGE_HANDLER), _leverageAmount); | ||
|
||
vm.prank(LEVERAGE_HANDLER); | ||
_lockCollateral(TKN, vaults[aliceProxy], _leverageAmount, leverageHandlerProxy); | ||
(uint256 _finalCollateral,) = leverageCalculator.getNFVLockedAndDebt(TKN, aliceNFV.safeHandler); | ||
|
||
/// @dev this maths because collateral and debt tokens are equal: 1-to-1 | ||
assertEq(_initCollateral + _leverageAmount, _finalCollateral); | ||
} | ||
|
||
/// @notice (leverageAmount - initial debt) used to make swap and leverage safe | ||
function testCalcSingleSwapWithPreviousDebtMinted() public { | ||
_lockCollateral(TKN, vaults[aliceProxy], DEPOSIT, aliceProxy); | ||
(uint256 _initCollateral,) = leverageCalculator.getNFVLockedAndDebt(TKN, aliceNFV.safeHandler); | ||
uint256 _leverageAmount = leverageCalculator.calculateSingleLeverage(vaults[aliceProxy]); | ||
|
||
_genDebtToAccount(address(0x9999), vaults[aliceProxy], MINT / 2, aliceProxy); | ||
uint256 _newLeverageAmount = leverageCalculator.calculateSingleLeverage(vaults[aliceProxy]); | ||
assertEq(_newLeverageAmount, _leverageAmount - MINT / 2); | ||
|
||
_genDebtToAccount(LEVERAGE_HANDLER, vaults[aliceProxy], _newLeverageAmount, aliceProxy); | ||
assertEq(systemCoin.balanceOf(LEVERAGE_HANDLER), _newLeverageAmount); | ||
|
||
vm.prank(LEVERAGE_HANDLER); | ||
_swapIn(LEVERAGE_HANDLER, _newLeverageAmount); | ||
_swapOut(LEVERAGE_HANDLER); | ||
|
||
assertEq(systemCoin.balanceOf(LEVERAGE_HANDLER), 0); | ||
assertEq(IERC20(token).balanceOf(LEVERAGE_HANDLER), _newLeverageAmount); | ||
|
||
vm.prank(LEVERAGE_HANDLER); | ||
_lockCollateral(TKN, vaults[aliceProxy], _newLeverageAmount, leverageHandlerProxy); | ||
(uint256 _finalCollateral,) = leverageCalculator.getNFVLockedAndDebt(TKN, aliceNFV.safeHandler); | ||
|
||
/// @dev this maths because collateral and debt tokens are equal: 1-to-1 | ||
assertEq(_initCollateral + _newLeverageAmount, _finalCollateral); | ||
} | ||
|
||
/// SIMULATION FUNCTIONS | ||
mapping(address => uint256) public swapIn; | ||
|
||
/** | ||
* @dev to simulate swaps in tests | ||
*/ | ||
function _swapIn(address _sender, uint256 _amount) internal { | ||
require(systemCoin.transfer(address(this), _amount), 'SwapInFail'); | ||
swapIn[_sender] = _amount; | ||
} | ||
|
||
function _swapOut(address _receiver) internal { | ||
IERC20(token).transfer(_receiver, swapIn[_receiver]); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -72,10 +72,10 @@ | |
"@nodelib/fs.scandir" "2.1.5" | ||
fastq "^1.6.0" | ||
|
||
"@opendollar/[email protected]5e2be44d": | ||
version "0.0.0-5e2be44d" | ||
resolved "https://registry.yarnpkg.com/@opendollar/contracts/-/contracts-0.0.0-5e2be44d.tgz#ce0b878acc42fade52665e8835236cceddedf7a6" | ||
integrity sha512-A3luZUGi5au79JVqERpbO1jJ/MaGfIZUZJzuQ3a3DzqLa3HU0mbVHu4S93D0mmwvw7TJPIr1vuRLYdhYlR4vYQ== | ||
"@opendollar/[email protected]e31c2151": | ||
version "0.0.0-e31c2151" | ||
resolved "https://registry.yarnpkg.com/@opendollar/contracts/-/contracts-0.0.0-e31c2151.tgz#d96fea17c8f865f6b35c74a80d0e95cdfed10943" | ||
integrity sha512-mffuIIDH6YV7gxEbUQghPtKvCamcFoFG4YWnWozjtDec/vrusm2TCjrAKvzV9v8+Sha0dkr6ROryfyMxZIH+IQ== | ||
dependencies: | ||
"@defi-wonderland/solidity-utils" "0.0.0-4298c6c6" | ||
"@openzeppelin/contracts" "4.9.6" | ||
|
@@ -765,14 +765,14 @@ flatted@^2.0.0: | |
resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" | ||
integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== | ||
|
||
"forge-std@git+https://github.com/foundry-rs/forge-std.git#e8a047e3f40f13fa37af6fe14e6e06283d9a060e": | ||
version "1.5.6" | ||
resolved "git+https://github.com/foundry-rs/forge-std.git#e8a047e3f40f13fa37af6fe14e6e06283d9a060e" | ||
|
||
"forge-std@https://github.com/foundry-rs/forge-std": | ||
version "1.8.2" | ||
resolved "https://github.com/foundry-rs/forge-std#52715a217dc51d0de15877878ab8213f6cbbbab5" | ||
|
||
"forge-std@https://github.com/foundry-rs/forge-std.git#e8a047e3f40f13fa37af6fe14e6e06283d9a060e": | ||
version "1.5.6" | ||
resolved "https://github.com/foundry-rs/forge-std.git#e8a047e3f40f13fa37af6fe14e6e06283d9a060e" | ||
|
||
form-data-encoder@^2.1.2: | ||
version "2.1.4" | ||
resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-2.1.4.tgz#261ea35d2a70d48d30ec7a9603130fa5515e9cd5" | ||
|