Skip to content

Commit

Permalink
tested closing borrows (through etherscan on the ladle)
Browse files Browse the repository at this point in the history
  • Loading branch information
alcueca committed Nov 6, 2023
1 parent a880335 commit 586c1f7
Show file tree
Hide file tree
Showing 3 changed files with 228 additions and 65 deletions.
144 changes: 144 additions & 0 deletions output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
No files changed, compilation skipped

Running 4 tests for test/Unwind.t.sol:UnwindTest
[PASS] test_closeLend() (gas: 4446031)
Logs:
FYDAI2209: Redeemed 874038457156458737001
FYDAI2209: Obtained 891290810571659890893 Dai Stablecoin
FYUSDC2209: Redeemed 10572960402
FYUSDC2209: Obtained 10801105695 USD Coin
FYETH2206: Redeemed 516792406942865521
FYETH2206: Obtained 517443283522987356 Wrapped Ether
FYETH2209: Redeemed 3071514926253883454
FYETH2209: Obtained 3074198083000828496 Wrapped Ether
FYFRAX2206: Redeemed 4778009003933814540
FYFRAX2206: Obtained 4778009003933814540 Frax
FYFRAX2209: Redeemed 450293943802284084002
FYFRAX2209: Obtained 450293943802284084002 Frax
FYETH2212: Redeemed 517009576912384790
FYETH2212: Obtained 517363231059231936 Wrapped Ether
FYDAI2212: Redeemed 2169123449581959176388
FYDAI2212: Obtained 2206187622774458218096 Dai Stablecoin
FYUSDC2212: Redeemed 726828497
FYUSDC2212: Obtained 742020444 USD Coin
FYFRAX2212: Redeemed 98395967000787659418
FYFRAX2212: Obtained 98395967000787659418 Frax
FYETH2303: Not enough join balance to redeem 49305177069456091243
FYDAI2303: Not enough join balance to redeem 155742829126709002996692
FYUSDC2303: Not enough join balance to redeem 480765014727
FYFRAX2303: Redeemed 18532199651966809832
FYFRAX2303: Obtained 18532199651966809832 Frax
FYETH2306: Not enough join balance to redeem 50893234478993045053
FYDAI2306: Not enough join balance to redeem 69565033016285121898491
FYUSDC2306: Not enough join balance to redeem 682445332479
FYFRAX2306: Redeemed 0
FYFRAX2306: Obtained 0 Frax
FYUSDT2303: Redeemed 0
FYUSDT2303: Obtained 0 Tether USD
FYUSDT2306: Redeemed 0
FYUSDT2306: Obtained 0 Tether USD
FYETH2306B: Not enough join balance to redeem 51137393239489811000
FYDAI2306B: Not enough join balance to redeem 67138863944130937500000
FYUSDC2306B: Not enough join balance to redeem 482374497345
FYUSDT2306B: Redeemed 0
FYUSDT2306B: Obtained 0 Tether USD
FYETH2309: Not enough join balance to redeem 95221144190374570599
FYDAI2309: Not enough join balance to redeem 174664828497776995079197
FYUSDC2309: Not enough join balance to redeem 669278729399
FYUSDT2309: Redeemed 0
FYUSDT2309: Obtained 0 Tether USD
FYFRAX2309: Redeemed 0
FYFRAX2309: Obtained 0 Frax
FYETH2312: Not mature yet, wait until 1703862000
FYDAI2312: Not mature yet, wait until 1703862000
FYUSDC2312: Not mature yet, wait until 1703862000
FYUSDT2312: Not mature yet, wait until 1703862000

[PASS] test_removePoolLiquidity() (gas: 4688435)
Logs:
FYDAI2209 LP: Burned 100000000000000000000
FYDAI2209 LP: Obtained 228502871611840946 Dai Stablecoin
FYDAI2209 LP: Obtained 102774704718300135173 FYDAI2209
FYUSDC2209 LP: Burned 100000000
FYUSDC2209 LP: Obtained 62928251 USD Coin
FYUSDC2209 LP: Obtained 37528901 FYUSDC2209
FYETH2206 LP: Burned 20000000000000000
FYETH2206 LP: Obtained 19843391620264438 Wrapped Ether
FYETH2206 LP: Obtained 170198861852961 FYETH2206
FYETH2209 LP: Burned 20000000000000000
FYETH2209 LP: Obtained 5482585309194814 Wrapped Ether
FYETH2209 LP: Obtained 14651939034972902 FYETH2209
FYFRAX2206 LP: Burned 100000000000000000000
FYFRAX2206 LP: Obtained 95224553880186822799 Frax
FYFRAX2206 LP: Obtained 4778009003933814540 FYFRAX2206
FYFRAX2209 LP: Burned 1113390066928335047098
FYFRAX2209 LP: Obtained 664716756024535422806 Frax
FYFRAX2209 LP: Obtained 450293943801588476757 FYFRAX2209
FYFRAX2212 LP: Burned 100000000000000000000
FYFRAX2212 LP: Obtained 3477341725657951587 Frax
FYFRAX2212 LP: Obtained 97381942677273731374 FYFRAX2212
FYFRAX2303 LP: Burned 100000000000000000000
FYFRAX2303 LP: Obtained 81521258725807684796 Frax
FYFRAX2303 LP: Obtained 18532199651966809832 FYFRAX2303
FYETH2309 LP: Burned 72965494953399697001
FYETH2309 LP: Obtained 27420794736759831517 Wrapped Ether
FYETH2309 LP: Obtained 45893879156306145802 FYETH2309
FYDAI2309 LP: Burned 29276093541189200772686
FYDAI2309 LP: Obtained 10862909020333163784572 Dai Stablecoin
FYDAI2309 LP: Obtained 18481584435112439681172 FYDAI2309
FYUSDC2309 LP: Burned 238056647337
FYUSDC2309 LP: Obtained 52403151418 USD Coin
FYUSDC2309 LP: Obtained 186855957731 FYUSDC2309
FYUSDT2309 LP: Burned 50000000
FYUSDT2309 LP: Obtained 50000000 Tether USD
FYUSDT2309 LP: Obtained 0 FYUSDT2309
FYETH2312 LP: Burned 15347519517416743612
FYETH2312 LP: Obtained 663028375484652750 Wrapped Ether
FYETH2312 LP: Obtained 14928536111815056522 FYETH2312
FYDAI2312 LP: Burned 16616211893811339107726
FYDAI2312 LP: Obtained 1798386451058797458480 Dai Stablecoin
FYDAI2312 LP: Obtained 15000000000000000000000 FYDAI2312
FYUSDC2312 LP: Burned 72229004184
FYUSDC2312 LP: Obtained 50822631609 USD Coin
FYUSDC2312 LP: Obtained 22176106845 FYUSDC2312
FYUSDT2312 LP: Burned 50000000
FYUSDT2312 LP: Obtained 50000000 Tether USD
FYUSDT2312 LP: Obtained 0 FYUSDT2312

[PASS] test_removeStrategyV1Liquidity() (gas: 1437948)
Logs:
Yield Strategy ETH 6M Mar Sep: Burned 125770477228476838662
Yield Strategy ETH 6M Mar Sep: Obtained 126553981178711828695 FYETH2303 LP
Yield Strategy DAI 6M Mar Sep: Burned 495603981537727718432306
Yield Strategy DAI 6M Mar Sep: Obtained 513389586981711751769593 FYDAI2303 LP
Yield Strategy USDC 6M Mar Sep: Burned 910929018275
Yield Strategy USDC 6M Mar Sep: Obtained 919736046989 FYUSDC2303 LP
Yield Strategy FRAX 6M Mar Sep: Burned 21381046315055623151880
Yield Strategy FRAX 6M Mar Sep: Obtained 21423614824263458524826 Yield Strategy FRAX 6M Mar Sep

[PASS] test_removeStrategyV2Liquidity() (gas: 2949162)
Logs:
Yield Strategy FRAX 6M Jun Dec: Burned 191859800892600206609
Yield Strategy FRAX 6M Jun Dec: Obtained 191859800892600206609 Frax
Yield Strategy ETH 6M Jun Dec: Burned 15340652240301902574
Yield Strategy ETH 6M Jun Dec: Obtained 15347519517416743612 FYETH2312 LP
Yield Strategy DAI 6M Jun Dec: Burned 16614305191942101174417
Yield Strategy DAI 6M Jun Dec: Obtained 16616211893811339107726 FYDAI2312 LP
Yield Strategy USDC 6M Jun Dec: Burned 72199699134
Yield Strategy USDC 6M Jun Dec: Obtained 72229004184 FYUSDC2312 LP
Yield Strategy USDT 6M Jun Dec: Burned 50000000
Yield Strategy USDT 6M Jun Dec: Obtained 50000000 FYUSDT2312 LP
Yield Strategy ETH 6M Mar Sep: Burned 72965494953399697001
Yield Strategy ETH 6M Mar Sep: Obtained 72965494953399697001 FYETH2309 LP
Yield Strategy DAI 6M Mar Sep: Burned 29276093541189200772686
Yield Strategy DAI 6M Mar Sep: Obtained 29276093541189200772686 FYDAI2309 LP
Yield Strategy USDC 6M Mar Sep: Burned 238056647337
Yield Strategy USDC 6M Mar Sep: Obtained 238056647337 FYUSDC2309 LP
Yield Strategy USDT 6M Mar Sep: Burned 50000000
Yield Strategy USDT 6M Mar Sep: Obtained 50000000 FYUSDT2309 LP
Yield Strategy FRAX 6M Mar Sep: Burned 21423614824263458524826
Yield Strategy FRAX 6M Mar Sep: Obtained 21423614824263458524826 Frax

Test result: ok. 4 passed; 0 failed; 0 skipped; finished in 44.29s

Ran 1 test suites: 4 tests passed, 0 failed, 0 skipped (4 total tests)
36 changes: 0 additions & 36 deletions src/Unwind.sol
Original file line number Diff line number Diff line change
Expand Up @@ -299,40 +299,4 @@ contract Unwind {
revert NotLendingAddress(target);
}
}

/// @dev Unwind a borrowing position, removing all collateral
/// User must have approved Unwind to take enough underlying to repay the loan.
/// Maybe approve max and then revoke, or use `howMuchDebt` and approve about 1.25x what was returned to be on the safe side.
function closeBorrow(bytes12 vaultId) public {
// Get the corresponding base join and vault data
DataTypes.Vault memory vault = cauldron.vaults(vaultId);
if (vault.owner == address(0)) {
revert VaultDoesNotExist(vaultId);
}

DataTypes.Series memory series = cauldron.series(vault.seriesId);
IJoin baseJoin = IJoin(ladle.joins(series.baseId));
ERC20 baseToken = ERC20(baseJoin.asset());

// Get the vault art and ink
DataTypes.Balances memory balances = cauldron.balances(vaultId);

// Convert the art into base
uint256 baseAmount = cauldron.debtToBase(vault.seriesId, balances.art) + 1; // Let's take an extra wei, in case we mess up the rounding

if (!checkAllowance(address(baseToken))) {
revert NotEnoughAllowance(address(baseToken));
}

// Transfer the base to the Join
baseToken.transferFrom(msg.sender, address(baseJoin), baseAmount + 1);

// Call ladle.close to repay art and withdraw ink
ladle.close(vaultId, msg.sender, -int128(balances.ink), -int128(balances.art)); // No one should have enough ink or art to make this overflow, and if they do, they are probably malicious, hurting only themselves.
}

/// @dev Show how much debt is outstanding on a vault in fyToken, minus interest.
function howMuchDebt(bytes12 vaultId) public view returns (uint256) {
return cauldron.balances(vaultId).art;
}
}
113 changes: 84 additions & 29 deletions test/Unwind.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ pragma solidity >=0.8.21 <0.9.0;

import { console2 } from "forge-std/console2.sol";
import { StdCheats } from "forge-std/StdCheats.sol";

import { ERC20, IJoin, IFYToken, ILadle, Unwind } from "../src/Unwind.sol";
import { PRBTest } from "lib/prb-test/src/PRBTest.sol";
import { ERC20, IJoin, IFYToken, IPool, IStrategy, IStrategyV1, ICauldron, ILadle, DataTypes, Unwind } from "../src/Unwind.sol";

/// @dev If this is your first time with Forge, read this tutorial in the Foundry Book:
/// https://book.getfoundry.sh/forge/writing-tests
contract UnwindTest is StdCheats {
contract UnwindTest is PRBTest, StdCheats {
error UnknownChain(uint256 chainId);
error UnknownAddress(address target);
error MustUpgradeStrategy(address strategy);
Expand All @@ -20,10 +20,17 @@ contract UnwindTest is StdCheats {

Unwind internal unwind;

bytes12[] internal sampleVaultIds;

/// @dev A function invoked before each test case is run.
function setUp() public virtual {
// Instantiate the contract-under-test.
unwind = new Unwind();
sampleVaultIds.push(0x8ed222243d2e7d89be3aa966);
sampleVaultIds.push(0x0a51ad005255a2c9cceffbfd);
sampleVaultIds.push(0x14a639e62b1c243211462143);
sampleVaultIds.push(0x52042d8840d0de8f6e132e98);
// sampleVaultIds.push(0x0899B797F67B7ED0E303D049); fCash breaks the test
}

/// @dev Test that the total supply of all fyToken can be redeemed, or give a reason of why not
Expand All @@ -35,6 +42,8 @@ contract UnwindTest is StdCheats {

// Check the join can redeem the fyTokens
IJoin baseJoin = IFYToken(target).join();
ERC20 base = ERC20(baseJoin.asset());
uint256 baseBalance = base.balanceOf(address(this));
uint256 joinBalance = baseJoin.storedBalance();
if (joinBalance < totalSupply) {
console2.log("%s: Not enough join balance to redeem %s", ERC20(target).name(), totalSupply);
Expand All @@ -45,6 +54,7 @@ contract UnwindTest is StdCheats {
ERC20(target).approve(address(unwind), type(uint256).max);
unwind.closeLend(target);
console2.log("%s: Redeemed %s", ERC20(target).name(), totalSupply);
console2.log("%s: Obtained %s %s", ERC20(target).name(), base.balanceOf(address(this)) - baseBalance, base.name());
}
}
}
Expand All @@ -56,11 +66,17 @@ contract UnwindTest is StdCheats {
address target = unwind.knownContracts(i);
if (unwind.contractTypes(target) == Unwind.Type.POOL) {
uint256 totalSupply = ERC20(target).totalSupply();
ERC20 base = ERC20(address(IPool(target).base()));
ERC20 fyToken = ERC20(address(IPool(target).fyToken()));
uint256 baseBalance = base.balanceOf(address(this));
uint256 fyTokenBalance = fyToken.balanceOf(address(this));

deal(target, address(this), totalSupply);
ERC20(target).approve(address(unwind), type(uint256).max);
unwind.removeLiquidity(target);
console2.log("%s: Burned %s", ERC20(target).name(), totalSupply);
console2.log("%s: Obtained %s %s", ERC20(target).name(), base.balanceOf(address(this)) - baseBalance, base.name());
console2.log("%s: Obtained %s %s", ERC20(target).name(), fyToken.balanceOf(address(this)) - fyTokenBalance, fyToken.name());
}
}
}
Expand All @@ -71,11 +87,15 @@ contract UnwindTest is StdCheats {
address target = unwind.knownContracts(i);
if (unwind.contractTypes(target) == Unwind.Type.STRATEGYV2) {
uint256 totalSupply = ERC20(target).totalSupply();
ERC20 base = ERC20(address(IStrategy(target).pool()));
if (address(base) == address(0)) base = ERC20(address(IStrategy(target).base())); // In case the strategy is divested
uint256 baseBalance = base.balanceOf(address(this));

deal(target, address(this), totalSupply);
ERC20(target).approve(address(unwind), type(uint256).max);
unwind.removeLiquidity(target);
console2.log("%s: Burned %s", ERC20(target).name(), totalSupply);
console2.log("%s: Obtained %s %s", ERC20(target).name(), base.balanceOf(address(this)) - baseBalance, base.name());
}
}
}
Expand All @@ -87,38 +107,73 @@ contract UnwindTest is StdCheats {
address target = unwind.knownContracts(i);
if (unwind.contractTypes(target) == Unwind.Type.STRATEGYV1) {
uint256 totalSupply = ERC20(target).totalSupply();
ERC20 base = ERC20(address(IStrategyV1(target).pool()));
uint256 baseBalance = base.balanceOf(address(this));

deal(target, address(this), totalSupply);
ERC20(target).approve(address(unwind), type(uint256).max);
unwind.removeLiquidity(target);
console2.log("%s: Burned %s", ERC20(target).name(), totalSupply);
console2.log("%s: Obtained %s %s", ERC20(target).name(), base.balanceOf(address(this)) - baseBalance, base.name());
}
}
}
//
// /// @dev Fuzz test that provides random values for an unsigned integer, but which rejects zero as an input.
// /// If you need more sophisticated input validation, you should use the `bound` utility instead.
// /// See https://twitter.com/PaulRBerg/status/1622558791685242880
// function testFuzz_Example(uint256 x) external {
// vm.assume(x != 0); // or x = bound(x, 1, 100)
// assertEq(foo.id(x), x, "value mismatch");
// }
//
// /// @dev Fork test that runs against an Ethereum Mainnet fork. For this to work, you need to set `API_KEY_ALCHEMY`
// /// in your environment You can get an API key for free at https://alchemy.com.
// function testFork_Example() external {
// // Silently pass this test if there is no API key.
// string memory alchemyApiKey = vm.envOr("API_KEY_ALCHEMY", string(""));
// if (bytes(alchemyApiKey).length == 0) {
// return;
// }
//
// // Otherwise, run the test against the mainnet fork.
// vm.createSelectFork({ urlOrAlias: "mainnet", blockNumber: 16_428_000 });
// address usdc = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
// address holder = 0x7713974908Be4BEd47172370115e8b1219F4A5f0;
// uint256 actualBalance = ERC20(usdc).balanceOf(holder);
// uint256 expectedBalance = 196_307_713.810457e6;
// assertEq(actualBalance, expectedBalance);
// }

function testCloseBorrow() external {
ICauldron cauldron = unwind.cauldron();
ILadle ladle = unwind.ladle();

for (uint i = 0; i < sampleVaultIds.length; i++) {
bytes12 vaultId = sampleVaultIds[i];

// Get the vault art and ink
DataTypes.Vault memory vault = cauldron.vaults(vaultId);
DataTypes.Balances memory balancesBefore = cauldron.balances(vaultId);

// Get the vault series and base token
DataTypes.Series memory series = cauldron.series(vault.seriesId);
IJoin baseJoin = IJoin(ladle.joins(series.baseId));
IJoin ilkJoin = IJoin(ladle.joins(vault.ilkId));
ERC20 baseToken = ERC20(baseJoin.asset());
ERC20 ilkToken = ERC20(ilkJoin.asset());

// Make sure the Join can return the collateral
deal(address(ilkToken), address(ilkJoin), ilkJoin.storedBalance() + balancesBefore.ink);
vm.prank(address(ladle));
ilkJoin.join(address(ladle), balancesBefore.ink);

// Convert the art into base
uint256 baseAmount = cauldron.debtToBase(vault.seriesId, balancesBefore.art) + 2; // Let's take an extra wei, in case we mess up the rounding

// Deal and approve the base token
deal(address(baseToken), vault.owner, baseAmount);

// Impersonate the vault owner
vm.startPrank(vault.owner);

// SOMETHING BREAKS IF COLLATERAL AND BASE ARE THE SAME, but only on the tests, don't worry.

// Clean collateral amount
ilkToken.transfer(address(0xdead), ilkToken.balanceOf(vault.owner));

baseToken.approve(address(baseJoin), type(uint256).max);

// Close the borrow
if (balancesBefore.art > 0) ladle.close(vaultId, vault.owner, -int128(balancesBefore.ink), -int128(balancesBefore.art));
else ladle.pour(vaultId, vault.owner, -int128(balancesBefore.ink), -int128(balancesBefore.art));

vm.stopPrank();

// Check that the borrow was closed and collateral returned
{
DataTypes.Balances memory balancesAfter = cauldron.balances(vaultId);
assertEq(balancesAfter.art, 0);
assertEq(balancesAfter.ink, 0);
}
assertEq(ilkToken.balanceOf(vault.owner), balancesBefore.ink);

console2.log("%s: Closed %s debt of %s", uint256(bytes32(vaultId)), balancesBefore.art, baseToken.name());
console2.log("%s: Obtained %s collateral of %s", uint256(bytes32(vaultId)), ilkToken.balanceOf(vault.owner), ilkToken.name());
}
}
}

0 comments on commit 586c1f7

Please sign in to comment.