From 7fa36c2e6d0bcd2194766dc4101c57901789e166 Mon Sep 17 00:00:00 2001 From: Micky Mousse Date: Sat, 20 Nov 2021 00:41:10 -0500 Subject: [PATCH 1/3] fix: bug with sbtc_router --- contracts/SynthetixRouterStrategy.sol | 4 +- .../SynthetixRouter/test_sbtc_router_usage.py | 227 ++++++++++++++++++ 2 files changed, 230 insertions(+), 1 deletion(-) create mode 100644 tests/SynthetixRouter/test_sbtc_router_usage.py diff --git a/contracts/SynthetixRouterStrategy.sol b/contracts/SynthetixRouterStrategy.sol index 6fa5162..456df06 100644 --- a/contracts/SynthetixRouterStrategy.sol +++ b/contracts/SynthetixRouterStrategy.sol @@ -332,8 +332,10 @@ contract SynthetixRouterStrategy is RouterStrategy, Synthetix { _debtPayment = Math.min(_debtOutstanding, _balanceOfWant); + totalAssetsAfterProfit = totalAssetsAfterProfit.sub(_debtPayment); + if (totalDebt <= totalAssetsAfterProfit) { - _profit = _balanceOfWant.sub(_debtPayment); + _profit = totalAssetsAfterProfit.sub(totalDebt); } else { _loss = totalDebt.sub(totalAssetsAfterProfit); } diff --git a/tests/SynthetixRouter/test_sbtc_router_usage.py b/tests/SynthetixRouter/test_sbtc_router_usage.py new file mode 100644 index 0000000..889b351 --- /dev/null +++ b/tests/SynthetixRouter/test_sbtc_router_usage.py @@ -0,0 +1,227 @@ +import pytest +from brownie import chain, Contract, accounts, ZERO_ADDRESS, Wei, reverts +from eth_abi import encode_single + +DUST_THRESHOLD = 10_000 + + +def test_deposit_and_withdraw_wo_profit( + synth_strategy, susd_vault, sbtc_vault, sbtc, susd_whale +): + sbtc_router = synth_strategy + hedging_vault = susd_vault + + susd = Contract(sbtc_router.want()) + gov = accounts.at(hedging_vault.governance(), True) + + susd.approve(hedging_vault, 2 ** 256 - 1, {"from": susd_whale}) + + prev_assets = sbtc_router.estimatedTotalAssets() + susd_to_deposit = ( + 30_000 * 1e18 + if susd.balanceOf(susd_whale) > 30_000 * 1e18 + else susd.balanceOf(susd_whale) + ) + + assert susd_to_deposit > 0 + + hedging_vault.deposit(susd_to_deposit, {"from": susd_whale}) + + chain.sleep(360 + 1) + chain.mine(1) + + tx = sbtc_router.harvest({"from": gov}) + + print(f"First harvest {tx.events['Harvested']}") + assert tx.events["Harvested"]["loss"] == 0 + + new_assets = sbtc_router.estimatedTotalAssets() + + assert new_assets < (susd_to_deposit + prev_assets) + assert new_assets > (susd_to_deposit + prev_assets) * 0.9 + + chain.sleep(3600 + 1) + chain.mine(1) + + prev_sbtc_balance = sbtc_vault.totalAssets() + sbtc_router.depositInVault({"from": gov}) + after_sbtc_balance = sbtc_vault.totalAssets() + + assert after_sbtc_balance > prev_sbtc_balance + + chain.sleep(3600 + 1) + chain.mine(1) + + with reverts(): + hedging_vault.withdraw(susd_to_deposit, {"from": susd_whale}) + + chain.sleep(3600 + 1) + chain.mine(1) + sbtc_router.setMaxLoss(100, {"from": gov}) + sbtc_router.manualRemoveLiquidity( + hedging_vault.balanceOf(susd_whale) * hedging_vault.pricePerShare() / 1e18, + {"from": gov}, + ) + + chain.sleep(3600 + 1) + chain.mine(1) + + prev_hedge_assets = hedging_vault.totalAssets() + hedging_vault.withdraw( + hedging_vault.balanceOf(susd_whale), susd_whale, 100, {"from": susd_whale} + ) + after_hedge_assets = hedging_vault.totalAssets() + + assert prev_hedge_assets > after_hedge_assets + + +def test_deposit_and_withdraw_w_profit( + synth_strategy, susd_vault, sbtc_vault, sbtc, susd_whale, sbtc_whale +): + sbtc_router = synth_strategy + hedging_vault = susd_vault + + susd = Contract(sbtc_router.want()) + gov = accounts.at(hedging_vault.governance(), True) + + susd.approve(hedging_vault, 2 ** 256 - 1, {"from": susd_whale}) + + prev_assets = sbtc_router.estimatedTotalAssets() + susd_to_deposit = ( + 30_000 * 1e18 + if susd.balanceOf(susd_whale) > 30_000 * 1e18 + else susd.balanceOf(susd_whale) + ) + + assert susd_to_deposit > 0 + + hedging_vault.deposit(susd_to_deposit, {"from": susd_whale}) + + chain.sleep(360 + 1) + chain.mine(1) + + tx = sbtc_router.harvest({"from": gov}) + + print(f"First harvest {tx.events['Harvested']}") + assert tx.events["Harvested"]["loss"] == 0 + + new_assets = sbtc_router.estimatedTotalAssets() + + assert new_assets < (susd_to_deposit + prev_assets) + assert new_assets > (susd_to_deposit + prev_assets) * 0.9 + + chain.sleep(3600 + 1) + chain.mine(1) + + prev_sbtc_balance = sbtc_vault.totalAssets() + sbtc_router.depositInVault({"from": gov}) + after_sbtc_balance = sbtc_vault.totalAssets() + + assert after_sbtc_balance > prev_sbtc_balance + + chain.sleep(3600 + 1) + chain.mine(1) + + # produce profit + sbtc.transfer(sbtc_vault, sbtc.balanceOf(sbtc_whale), {"from": sbtc_whale}) + + chain.sleep(3600 + 1) + chain.mine(1) + sbtc_router.manualRemoveLiquidity( + hedging_vault.balanceOf(susd_whale) * hedging_vault.pricePerShare() / 1e18, + {"from": gov}, + ) + + chain.sleep(3600 + 1) + chain.mine(1) + + prev_hedge_assets = hedging_vault.totalAssets() + hedging_vault.withdraw({"from": susd_whale}) + after_hedge_assets = hedging_vault.totalAssets() + + assert prev_hedge_assets > after_hedge_assets + + +def test_deposit_harvest_and_revert_withdraw( + synth_strategy, susd_vault, sbtc_vault, sbtc, susd_whale, sbtc_whale +): + sbtc_router = synth_strategy + hedging_vault = susd_vault + + susd = Contract(sbtc_router.want()) + gov = accounts.at(hedging_vault.governance(), True) + + susd.approve(hedging_vault, 2 ** 256 - 1, {"from": susd_whale}) + + prev_assets = sbtc_router.estimatedTotalAssets() + susd_to_deposit = ( + 30_000 * 1e18 + if susd.balanceOf(susd_whale) > 30_000 * 1e18 + else susd.balanceOf(susd_whale) + ) + + assert susd_to_deposit > 0 + + hedging_vault.deposit(susd_to_deposit, {"from": susd_whale}) + + chain.sleep(360 + 1) + chain.mine(1) + + tx = sbtc_router.harvest({"from": gov}) + + print(f"First harvest {tx.events['Harvested']}") + assert tx.events["Harvested"]["loss"] == 0 + + new_assets = sbtc_router.estimatedTotalAssets() + + assert new_assets < (susd_to_deposit + prev_assets) + assert new_assets > (susd_to_deposit + prev_assets) * 0.9 + + chain.sleep(3600 + 1) + chain.mine(1) + + prev_sbtc_balance = sbtc_vault.totalAssets() + sbtc_router.depositInVault({"from": gov}) + after_sbtc_balance = sbtc_vault.totalAssets() + + assert after_sbtc_balance > prev_sbtc_balance + + chain.sleep(3600 + 1) + chain.mine(1) + sbtc_router.manualRemoveFullLiquidity({"from": gov}) + + chain.sleep(3600 + 1) + chain.mine(1) + + tx = sbtc_router.harvest({"from": gov}) + + print(f"Second harvest {tx.events['Harvested']}") + assert tx.events["Harvested"]["loss"] > 0 + + prev_hedge_assets = hedging_vault.totalAssets() + # money has just been exchange for synth so we need to wait 6 min to withdraw + with reverts(): + hedging_vault.withdraw({"from": susd_whale}) + + chain.sleep(3600 + 1) + chain.mine(1) + + tx = sbtc_router.harvest({"from": gov}) + + print(f"Second harvest {tx.events['Harvested']}") + assert tx.events["Harvested"]["loss"] > 0 + + chain.sleep(3600 + 1) + chain.mine(1) + + sbtc_router.manualRemoveFullLiquidity({"from": gov}) + + chain.sleep(3600 + 1) + chain.mine(1) + + hedging_vault.withdraw( + hedging_vault.balanceOf(susd_whale), susd_whale, 10, {"from": susd_whale} + ) + after_hedge_assets = hedging_vault.totalAssets() + + assert prev_hedge_assets > after_hedge_assets From 47d935c903e37f9244cad5287fc5c967f3f6a3b9 Mon Sep 17 00:00:00 2001 From: Micky Mousse Date: Sat, 20 Nov 2021 11:48:28 -0500 Subject: [PATCH 2/3] fix: prepareReturn bug --- contracts/SynthetixRouterStrategy.sol | 12 ++++++------ tests/SynthetixRouter/test_sbtc_router_usage.py | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/contracts/SynthetixRouterStrategy.sol b/contracts/SynthetixRouterStrategy.sol index 456df06..1f34f93 100644 --- a/contracts/SynthetixRouterStrategy.sol +++ b/contracts/SynthetixRouterStrategy.sol @@ -326,18 +326,18 @@ contract SynthetixRouterStrategy is RouterStrategy, Synthetix { uint256 _debtPayment ) { - uint256 totalDebt = vault.strategies(address(this)).totalDebt; - uint256 totalAssetsAfterProfit = estimatedTotalAssets(); + uint256 _totalDebt = vault.strategies(address(this)).totalDebt; + uint256 _estimatedTotal = estimatedTotalAssets(); uint256 _balanceOfWant = balanceOfWant(); _debtPayment = Math.min(_debtOutstanding, _balanceOfWant); - totalAssetsAfterProfit = totalAssetsAfterProfit.sub(_debtPayment); + uint256 _remainderWant = _balanceOfWant.sub(_debtPayment); - if (totalDebt <= totalAssetsAfterProfit) { - _profit = totalAssetsAfterProfit.sub(totalDebt); + if (_totalDebt <= _estimatedTotal) { + _profit = Math.min(_remainderWant, _estimatedTotal.sub(_totalDebt)); } else { - _loss = totalDebt.sub(totalAssetsAfterProfit); + _loss = _totalDebt.sub(_estimatedTotal); } } } diff --git a/tests/SynthetixRouter/test_sbtc_router_usage.py b/tests/SynthetixRouter/test_sbtc_router_usage.py index 889b351..7c79c11 100644 --- a/tests/SynthetixRouter/test_sbtc_router_usage.py +++ b/tests/SynthetixRouter/test_sbtc_router_usage.py @@ -208,7 +208,7 @@ def test_deposit_harvest_and_revert_withdraw( tx = sbtc_router.harvest({"from": gov}) - print(f"Second harvest {tx.events['Harvested']}") + print(f"Third harvest {tx.events['Harvested']}") assert tx.events["Harvested"]["loss"] > 0 chain.sleep(3600 + 1) @@ -220,7 +220,7 @@ def test_deposit_harvest_and_revert_withdraw( chain.mine(1) hedging_vault.withdraw( - hedging_vault.balanceOf(susd_whale), susd_whale, 10, {"from": susd_whale} + hedging_vault.balanceOf(susd_whale), susd_whale, 100, {"from": susd_whale} ) after_hedge_assets = hedging_vault.totalAssets() From 440cc5a89cc722384b71ee82175992bb75df8358 Mon Sep 17 00:00:00 2001 From: Micky Mousse Date: Sun, 21 Nov 2021 14:06:13 -0500 Subject: [PATCH 3/3] feat: test router in prod --- .../SynthetixRouter/test_sbtc_router_prod.py | 254 ++++++++++++++++++ 1 file changed, 254 insertions(+) create mode 100644 tests/SynthetixRouter/test_sbtc_router_prod.py diff --git a/tests/SynthetixRouter/test_sbtc_router_prod.py b/tests/SynthetixRouter/test_sbtc_router_prod.py new file mode 100644 index 0000000..25720a2 --- /dev/null +++ b/tests/SynthetixRouter/test_sbtc_router_prod.py @@ -0,0 +1,254 @@ +import pytest +from brownie import chain, Contract, accounts, ZERO_ADDRESS, Wei, reverts +from eth_abi import encode_single + +DUST_THRESHOLD = 100_000 + + +def test_deposit_and_withdraw_wo_profit(susd_whale): + old_router_strat = Contract("0x4a4A5549F4B2eF519ca9abA38f4e2d13c23e32B7") + sbtc_router = Contract("0x86fd69EDDcc0d185fC2678aA02Aeae67f614b76e") + hedging_vault = Contract(sbtc_router.vault()) + sbtc_vault = Contract(sbtc_router.yVault()) + + susd = Contract(sbtc_router.want()) + sbtc = Contract(sbtc_vault.token()) + gov = accounts.at(hedging_vault.governance(), True) + + hedging_vault.migrateStrategy(old_router_strat, sbtc_router, {"from": gov}) + + susd.approve(hedging_vault, 2 ** 256 - 1, {"from": susd_whale}) + + prev_assets = sbtc_router.estimatedTotalAssets() + susd_to_deposit = ( + 30_000 * 1e18 + if susd.balanceOf(susd_whale) > 30_000 * 1e18 + else susd.balanceOf(susd_whale) + ) + + assert susd_to_deposit > 0 + + hedging_vault.deposit(susd_to_deposit, {"from": susd_whale}) + + chain.sleep(360 + 1) + chain.mine(1) + + tx = sbtc_router.harvest({"from": gov}) + + print(f"First harvest {tx.events['Harvested']}") + assert tx.events["Harvested"]["loss"] == 0 + + new_assets = sbtc_router.estimatedTotalAssets() + + assert new_assets < (susd_to_deposit + prev_assets) + assert new_assets > (susd_to_deposit + prev_assets) * 0.9 + + chain.sleep(3600 + 1) + chain.mine(1) + + prev_sbtc_balance = sbtc_vault.totalAssets() + sbtc_router.depositInVault({"from": gov}) + after_sbtc_balance = sbtc_vault.totalAssets() + + assert after_sbtc_balance > prev_sbtc_balance + + chain.sleep(3600 + 1) + chain.mine(1) + + with reverts(): + hedging_vault.withdraw(susd_to_deposit, {"from": susd_whale}) + + chain.sleep(3600 + 1) + chain.mine(1) + sbtc_router.setMaxLoss(100, {"from": gov}) + sbtc_router.manualRemoveLiquidity( + hedging_vault.balanceOf(susd_whale) * hedging_vault.pricePerShare() / 1e18, + {"from": gov}, + ) + + chain.sleep(3600 + 1) + chain.mine(1) + + prev_hedge_assets = hedging_vault.totalAssets() + hedging_vault.withdraw( + hedging_vault.balanceOf(susd_whale), susd_whale, 100, {"from": susd_whale} + ) + after_hedge_assets = hedging_vault.totalAssets() + + assert prev_hedge_assets > after_hedge_assets + + +def test_deposit_and_withdraw_w_profit(susd_whale, sbtc_whale): + old_router_strat = Contract("0x4a4A5549F4B2eF519ca9abA38f4e2d13c23e32B7") + sbtc_router = Contract("0x86fd69EDDcc0d185fC2678aA02Aeae67f614b76e") + hedging_vault = Contract(sbtc_router.vault()) + sbtc_vault = Contract(sbtc_router.yVault()) + + susd = Contract(sbtc_router.want()) + sbtc = Contract(sbtc_vault.token()) + gov = accounts.at(hedging_vault.governance(), True) + + hedging_vault.migrateStrategy(old_router_strat, sbtc_router, {"from": gov}) + + susd.approve(hedging_vault, 2 ** 256 - 1, {"from": susd_whale}) + + prev_assets = sbtc_router.estimatedTotalAssets() + susd_to_deposit = ( + 30_000 * 1e18 + if susd.balanceOf(susd_whale) > 30_000 * 1e18 + else susd.balanceOf(susd_whale) + ) + + assert susd_to_deposit > 0 + + hedging_vault.deposit(susd_to_deposit, {"from": susd_whale}) + + chain.sleep(360 + 1) + chain.mine(1) + + tx = sbtc_router.harvest({"from": gov}) + + print(f"First harvest {tx.events['Harvested']}") + assert tx.events["Harvested"]["loss"] == 0 + + new_assets = sbtc_router.estimatedTotalAssets() + + assert new_assets < (susd_to_deposit + prev_assets) + assert new_assets > (susd_to_deposit + prev_assets) * 0.9 + + chain.sleep(3600 + 1) + chain.mine(1) + + prev_sbtc_balance = sbtc_vault.totalAssets() + sbtc_router.depositInVault({"from": gov}) + after_sbtc_balance = sbtc_vault.totalAssets() + + assert after_sbtc_balance > prev_sbtc_balance + + chain.sleep(3600 + 1) + chain.mine(1) + + # produce profit + sbtc.transfer(sbtc_vault, sbtc.balanceOf(sbtc_whale), {"from": sbtc_whale}) + + chain.sleep(3600 + 1) + chain.mine(1) + sbtc_router.manualRemoveLiquidity( + hedging_vault.balanceOf(susd_whale) * hedging_vault.pricePerShare() / 1e18, + {"from": gov}, + ) + + chain.sleep(3600 + 1) + chain.mine(1) + + prev_hedge_assets = hedging_vault.totalAssets() + hedging_vault.withdraw({"from": susd_whale}) + after_hedge_assets = hedging_vault.totalAssets() + + assert prev_hedge_assets > after_hedge_assets + + +def test_deposit_harvest_and_revert_withdraw(susd_whale, sbtc_whale): + old_router_strat = Contract("0x4a4A5549F4B2eF519ca9abA38f4e2d13c23e32B7") + sbtc_router = Contract("0x86fd69EDDcc0d185fC2678aA02Aeae67f614b76e") + hedging_vault = Contract(sbtc_router.vault()) + sbtc_vault = Contract(sbtc_router.yVault()) + + susd = Contract(sbtc_router.want()) + sbtc = Contract(sbtc_vault.token()) + gov = accounts.at(hedging_vault.governance(), True) + + hedging_vault.migrateStrategy(old_router_strat, sbtc_router, {"from": gov}) + + susd.approve(hedging_vault, 2 ** 256 - 1, {"from": susd_whale}) + + chain.sleep(360 + 1) + chain.mine(1) + + sbtc_router.setMaxLoss(100, {"from": gov}) + sbtc_router.manualRemoveLiquidity( + abs( + sbtc_router.estimatedTotalAssets() + - hedging_vault.strategies(sbtc_router)["totalDebt"] + ), + {"from": gov}, + ) + + chain.sleep(3600 + 1) + chain.mine(1) + + tx = sbtc_router.harvest({"from": gov}) + + print(f"Zero harvest {tx.events['Harvested']}") + assert tx.events["Harvested"]["loss"] == 0 + + chain.sleep(360 + 1) + chain.mine(1) + + prev_assets = sbtc_router.estimatedTotalAssets() + + susd_to_deposit = ( + 30_000 * 1e18 + if susd.balanceOf(susd_whale) > 30_000 * 1e18 + else susd.balanceOf(susd_whale) + ) + + assert susd_to_deposit > 0 + + hedging_vault.deposit(susd_to_deposit, {"from": susd_whale}) + + chain.sleep(360 + 1) + chain.mine(1) + + tx = sbtc_router.harvest({"from": gov}) + + print(f"First harvest {tx.events['Harvested']}") + assert tx.events["Harvested"]["profit"] == 0 + + new_assets = sbtc_router.estimatedTotalAssets() + + assert new_assets > (susd_to_deposit * 0.95 + prev_assets) + + chain.sleep(3600 + 1) + chain.mine(1) + + prev_sbtc_balance = sbtc_vault.totalAssets() + sbtc_router.depositInVault({"from": gov}) + after_sbtc_balance = sbtc_vault.totalAssets() + + assert after_sbtc_balance > prev_sbtc_balance + + chain.sleep(3600 + 1) + chain.mine(1) + # converts from sbtc to susd + sbtc_router.manualRemoveFullLiquidity({"from": gov}) + + chain.sleep(3600 + 1) + chain.mine(1) + + # converts from susd to sbtc + tx = sbtc_router.harvest({"from": gov}) + + print(f"Second harvest {tx.events['Harvested']}") + assert tx.events["Harvested"]["loss"] > 0 + + prev_hedge_assets = hedging_vault.totalAssets() + # money has just been exchange for synth so we need to wait 6 min to withdraw + with reverts(): + hedging_vault.withdraw({"from": susd_whale}) + + chain.sleep(3600 + 1) + chain.mine(1) + + # converts from sbtc to susd so it is free to withdraw + sbtc_router.manualRemoveFullLiquidity({"from": gov}) + + chain.sleep(3600 + 1) + chain.mine(1) + + hedging_vault.withdraw( + hedging_vault.balanceOf(susd_whale), susd_whale, 100, {"from": susd_whale} + ) + after_hedge_assets = hedging_vault.totalAssets() + + assert prev_hedge_assets > after_hedge_assets