diff --git a/contracts/Core.sol b/contracts/Core.sol index 65c1fc1..2fd2dd8 100644 --- a/contracts/Core.sol +++ b/contracts/Core.sol @@ -37,6 +37,7 @@ contract Core is GovernableProxy, ICore { event PeakWhitelisted(address indexed peak); event FeeCollected(uint amount); + event PricePerShareSynced(uint indexed pps); /** * @param _bBTC bBTC token address @@ -89,7 +90,7 @@ contract Core is GovernableProxy, ICore { * @notice Redeem bBTC * @dev Only whitelisted peaks can call this function * @param bBtc bBTC amount to redeem - * @return btc amount redeemed, scaled by 1e18 + * @return btc amount redeemed, scaled by 1e36 */ function redeem(uint bBtc, address account) override external returns (uint) { require(bBtc > 0, "REDEEMING_0_bBTC"); @@ -116,6 +117,17 @@ contract Core is GovernableProxy, ICore { return 1e18; } + function syncPricePerShare() public { + (address fxRoot, address fxChildTunnel) = getFxContracts(); + uint pps = pricePerShare(); + IFxStateSender(fxRoot).sendMessageToChild(fxChildTunnel, abi.encode(pps)); + emit PricePerShareSynced(pps); + } + + function getFxContracts() public view returns (address fxRoot, address fxChildTunnel) { + return (address(__gap[0]), address(__gap[1])); + } + /** * @notice Collect all the accumulated fee (denominated in bBTC) */ @@ -155,6 +167,13 @@ contract Core is GovernableProxy, ICore { peaks[peak] == PeakState.Extinct, "DUPLICATE_PEAK" ); + + address[] memory _peakAddresses = peakAddresses; + uint numPeaks = _peakAddresses.length; + for (uint i = 0; i < numPeaks; i++) { + require(_peakAddresses[i] != peak, "USE_setPeakStatus"); + } + IPeak(peak).portfolioValue(); // sanity check peakAddresses.push(peak); peaks[peak] = PeakState.Active; @@ -172,6 +191,9 @@ contract Core is GovernableProxy, ICore { peaks[peak] != PeakState.Extinct, "Peak is extinct" ); + if (state == PeakState.Extinct) { + require(IPeak(peak).portfolioValue() <= 1e15, "NON_TRIVIAL_FUNDS_IN_PEAK"); + } peaks[peak] = state; } @@ -204,8 +226,21 @@ contract Core is GovernableProxy, ICore { function setGuestList(address _guestList) external onlyGovernance { guestList = BadgerGuestListAPI(_guestList); } + + function setFxContracts(address _fxRoot, address _fxChildTunnel) external onlyGovernance { + require(_fxRoot != address(0) && _fxChildTunnel != address(0), "NULL_ADDRESS"); + __gap[0] = uint(_fxRoot); + __gap[1] = uint(_fxChildTunnel); + // sanity check + (address fxRoot, address fxChildTunnel) = getFxContracts(); + require(fxRoot == _fxRoot && fxChildTunnel == _fxChildTunnel, "INSANE"); + } } interface BadgerGuestListAPI { function authorized(address guest, uint256 amount, bytes32[] calldata merkleProof) external view returns (bool); } + +interface IFxStateSender { + function sendMessageToChild(address _receiver, bytes calldata _data) external; +} diff --git a/contracts/Zap.sol b/contracts/Zap.sol index 383ed36..30837cf 100644 --- a/contracts/Zap.sol +++ b/contracts/Zap.sol @@ -18,10 +18,10 @@ contract Zap is AccessControlDefended { IBadgerSettPeak public constant settPeak = IBadgerSettPeak(0x41671BA1abcbA387b9b2B752c205e22e916BE6e3); IByvWbtcPeak public constant byvWbtcPeak = IByvWbtcPeak(0x825218beD8BE0B30be39475755AceE0250C50627); - IbBTC public constant ibbtc = IbBTC(0xc4E15973E6fF2A35cC804c2CF9D2a1b817a8b40F); - + IERC20 public constant ibbtc = IERC20(0xc4E15973E6fF2A35cC804c2CF9D2a1b817a8b40F); IERC20 public constant ren = IERC20(0xEB4C2781e4ebA804CE9a9803C67d0893436bB27D); IERC20 public constant wbtc = IERC20(0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599); + IController public constant controller = IController(0x63cF44B2548e4493Fd099222A1eC79F3344D9682); struct Pool { IERC20 lpToken; @@ -64,6 +64,7 @@ contract Zap is AccessControlDefended { IERC20(address(pool.sett)).safeApprove(address(byvWbtcPeak), uint(-1)); } } + pools[2].lpToken.safeApprove(address(pools[2].deposit), uint(-1)); } /** @@ -81,10 +82,9 @@ contract Zap is AccessControlDefended { blockLocked returns(uint _ibbtc) { - Pool memory pool = pools[poolId]; - token.safeTransferFrom(msg.sender, address(this), amount); + Pool memory pool = pools[poolId]; if (poolId < 3) { // setts _addLiquidity(pool.deposit, amount, poolId + 2, idx); // pools are such that the #tokens they support is +2 from their poolId. pool.sett.deposit(pool.lpToken.balanceOf(address(this))); @@ -97,7 +97,7 @@ contract Zap is AccessControlDefended { } require(_ibbtc >= minOut, "INSUFFICIENT_IBBTC"); // used for capping slippage in curve pools - IERC20(address(ibbtc)).safeTransfer(msg.sender, _ibbtc); + ibbtc.safeTransfer(msg.sender, _ibbtc); } /** @@ -130,8 +130,8 @@ contract Zap is AccessControlDefended { /** * @notice Calculate the most optimal route and expected ibbtc amount when minting with wBTC / renBtc. * @dev Use returned params poolId, idx and bBTC in the call to mint(...) - The last param `minOut` in mint(...) should be a bit more than the returned bBTC value. - For instance 0.2% - 1% higher depending on slippage tolerange. + The last param `minOut` in mint(...) should be a bit less than the returned bBTC value. + For instance 0.2% - 1% lesser depending on slippage tolerange. * @param amount renBTC amount * @return poolId 0=crvRenWBTC, 1=crvRenWSBTC, 2=tbtc-sbtcCrv, 3=byvwbtc * @return idx Index of the supported token in the curve pool (poolId). Should be ignored for poolId=3 @@ -236,6 +236,160 @@ contract Zap is AccessControlDefended { uint _sett = _lp.mul(1e18).div(pool.sett.getPricePerFullShare()); return settPeak.calcMint(poolId, _sett); } + + // Redeem Methods + + function redeem(IERC20 token, uint amount, uint poolId, int128 idx, uint minOut) + external + defend + blockLocked + returns(uint out) + { + ibbtc.safeTransferFrom(msg.sender, address(this), amount); + + Pool memory pool = pools[poolId]; + if (poolId < 3) { // setts + settPeak.redeem(poolId, amount); + pool.sett.withdrawAll(); + pool.deposit.remove_liquidity_one_coin(pool.lpToken.balanceOf(address(this)), idx, minOut); + } else if (poolId == 3) { // byvwbtc + byvWbtcPeak.redeem(amount); + IbyvWbtc(address(pool.sett)).withdraw(); // withdraws all available + } else { + revert("INVALID_POOL_ID"); + } + out = token.balanceOf(address(this)); + token.safeTransfer(msg.sender, out); + } + + /** + * @notice Calculate the most optimal route and expected token amount when redeeming ibbtc. + * @dev Use returned params poolId, idx and out in the call to redeem(...) + The last param `redeem` in mint(...) should be a bit less than the returned `out` value. + For instance 0.2% - 1% lesser depending on slippage tolerange. + * @param amount ibbtc amount + * @return poolId 0=crvRenWBTC, 1=crvRenWSBTC, 2=tbtc-sbtcCrv, 3=byvwbtc + * @return idx Index of the supported token in the curve pool (poolId). Should be ignored for poolId=3 + * @return out Expected amount for token. Not for precise calculations. Doesn't factor in (deposit) fee charged by the curve pool / byvwbtc. + * @return fee Fee being charged by ibbtc + setts. Denominated in corresponding sett token + */ + function calcRedeem(address token, uint amount) external view returns(uint poolId, uint idx, uint out, uint fee) { + if (token == address(ren)) { + return calcRedeemInRen(amount); + } + if (token == address(wbtc)) { + return calcRedeemInWbtc(amount); + } + revert("INVALID_TOKEN"); + } + + /** + * @notice Calculate the most optimal route and expected renbtc amount when redeeming ibbtc. + * @dev Use returned params poolId, idx and renAmount in the call to redeem(...) + The last param `minOut` in redeem(...) should be a bit less than the returned renAmount value. + For instance 0.2% - 1% lesser depending on slippage tolerange. + * @param amount ibbtc amount + * @return poolId 0=crvRenWBTC, 1=crvRenWSBTC, 2=tbtc-sbtcCrv + * @return idx Index of the supported token in the curve pool (poolId) + * @return renAmount Expected renBtc. Not for precise calculations. Doesn't factor in fee charged by the curve pool + * @return fee Fee being charged by ibbtc system. Denominated in corresponding sett token + */ + function calcRedeemInRen(uint amount) public view returns(uint poolId, uint idx, uint renAmount, uint fee) { + uint _lp; + uint _fee; + uint _ren; + + // poolId=0, idx=0 + (_lp, fee) = ibbtcToCurveLP(0, amount); + renAmount = pools[0].deposit.calc_withdraw_one_coin(_lp, 0); + + (_lp, _fee) = ibbtcToCurveLP(1, amount); + _ren = pools[1].deposit.calc_withdraw_one_coin(_lp, 0); + if (_ren > renAmount) { + renAmount = _ren; + fee = _fee; + poolId = 1; + // idx=0 + } + + (_lp, _fee) = ibbtcToCurveLP(2, amount); + _ren = pools[2].deposit.calc_withdraw_one_coin(_lp, 1); + if (_ren > renAmount) { + renAmount = _ren; + fee = _fee; + poolId = 2; + idx = 1; + } + } + + /** + * @notice Calculate the most optimal route and expected wbtc amount when redeeming ibbtc. + * @dev Use returned params poolId, idx and wbtc in the call to redeem(...) + The last param `minOut` in redeem(...) should be a bit less than the returned wbtc value. + For instance 0.2% - 1% lesser depending on slippage tolerange. + * @param amount ibbtc amount + * @return poolId 0=crvRenWBTC, 1=crvRenWSBTC, 2=tbtc-sbtcCrv, 3=byvwbtc + * @return idx Index of the supported token in the curve pool (poolId) + * @return wBTCAmount Expected wbtc. Not for precise calculations. Doesn't factor in fee charged by the curve pool + * @return fee Fee being charged by ibbtc system. Denominated in corresponding sett token + */ + function calcRedeemInWbtc(uint amount) public view returns(uint poolId, uint idx, uint wBTCAmount, uint fee) { + uint _lp; + uint _fee; + uint _wbtc; + + // poolId=0, idx=0 + (_lp, fee) = ibbtcToCurveLP(0, amount); + wBTCAmount = pools[0].deposit.calc_withdraw_one_coin(_lp, 1); + idx = 1; + + (_lp, _fee) = ibbtcToCurveLP(1, amount); + _wbtc = pools[1].deposit.calc_withdraw_one_coin(_lp, 1); + if (_wbtc > wBTCAmount) { + wBTCAmount = _wbtc; + fee = _fee; + poolId = 1; + // idx=1 + } + + (_lp, _fee) = ibbtcToCurveLP(2, amount); + _wbtc = pools[2].deposit.calc_withdraw_one_coin(_lp, 2); + if (_wbtc > wBTCAmount) { + wBTCAmount = _wbtc; + fee = _fee; + poolId = 2; + idx = 2; + } + + uint _byvWbtc; + uint _max; + (_byvWbtc,_fee,_max) = byvWbtcPeak.calcRedeem(amount); + if (amount <= _max) { + uint strategyFee = _byvWbtc.mul(pools[3].sett.withdrawalFee()).div(10000); + _wbtc = _byvWbtc.sub(strategyFee).mul(pools[3].sett.pricePerShare()).div(1e8); + if (_wbtc > wBTCAmount) { + wBTCAmount = _wbtc; + fee = _fee.add(strategyFee); + poolId = 3; + } + } + } + + function ibbtcToCurveLP(uint poolId, uint bBtc) public view returns(uint lp, uint fee) { + uint sett; + uint max; + (sett,fee,max) = settPeak.calcRedeem(poolId, bBtc); + Pool memory pool = pools[poolId]; + if (bBtc > max) { + return (0,fee); + } else { + // pesimistically charge 0.5% on the withdrawal. + // Actual fee might be lesser if the vault keeps keeps a buffer + uint strategyFee = sett.mul(controller.strategies(pool.lpToken).withdrawalFee()).div(1000); + lp = sett.sub(strategyFee).mul(pool.sett.getPricePerFullShare()).div(1e18); + fee = fee.add(strategyFee); + } + } } interface ICurveFi { @@ -247,8 +401,15 @@ interface ICurveFi { function add_liquidity(uint[4] calldata amounts, uint min_mint_amount) external; function calc_token_amount(uint[4] calldata amounts, bool isDeposit) external view returns(uint); + + function remove_liquidity_one_coin(uint _token_amount, int128 i, uint min_amount) external; + function calc_withdraw_one_coin(uint _token_amount, int128 i) external view returns(uint); +} + +interface IStrategy { + function withdrawalFee() external view returns(uint); } -interface IyvWbtc { - function deposit(uint) external; +interface IController { + function strategies(IERC20 token) external view returns(IStrategy); } diff --git a/contracts/interfaces/IPeak.sol b/contracts/interfaces/IPeak.sol index 3576bbc..1cb11b6 100644 --- a/contracts/interfaces/IPeak.sol +++ b/contracts/interfaces/IPeak.sol @@ -15,6 +15,15 @@ interface IBadgerSettPeak is IPeak { external view returns(uint bBTC, uint fee); + + function redeem(uint poolId, uint inAmount) + external + returns (uint outAmount); + + function calcRedeem(uint poolId, uint bBtc) + external + view + returns(uint sett, uint fee, uint max); } interface IByvWbtcPeak is IPeak { @@ -26,4 +35,13 @@ interface IByvWbtcPeak is IPeak { external view returns(uint bBTC, uint fee); + + function redeem(uint inAmount) + external + returns (uint outAmount); + + function calcRedeem(uint bBtc) + external + view + returns(uint sett, uint fee, uint max); } diff --git a/contracts/interfaces/ISett.sol b/contracts/interfaces/ISett.sol index 2758706..0d94f5f 100644 --- a/contracts/interfaces/ISett.sol +++ b/contracts/interfaces/ISett.sol @@ -12,4 +12,8 @@ interface ISett is IERC20 { function getPricePerFullShare() external view returns (uint256); function balance() external view returns (uint256); + + // byvwbtc + function pricePerShare() external view returns (uint256); + function withdrawalFee() external view returns (uint256); } diff --git a/contracts/interfaces/IbyvWbtc.sol b/contracts/interfaces/IbyvWbtc.sol index 14b0cab..7358aad 100644 --- a/contracts/interfaces/IbyvWbtc.sol +++ b/contracts/interfaces/IbyvWbtc.sol @@ -7,4 +7,5 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; interface IbyvWbtc is IERC20 { function pricePerShare() external view returns (uint); function deposit(bytes32[] calldata merkleProof) external; + function withdraw() external returns (uint); } diff --git a/contracts/mocks/Sett.sol b/contracts/mocks/Sett.sol index 2bd4f70..959d8e4 100644 --- a/contracts/mocks/Sett.sol +++ b/contracts/mocks/Sett.sol @@ -38,4 +38,7 @@ contract Sett is ERC20, ISett { function balance() override external view returns (uint256) { return token.balanceOf(address(this)); } + + function pricePerShare() override external view returns (uint256) {} + function withdrawalFee() override external view returns (uint256) {} } diff --git a/contracts/mocks/byvWbtc.sol b/contracts/mocks/byvWbtc.sol index f3a76e6..799994e 100644 --- a/contracts/mocks/byvWbtc.sol +++ b/contracts/mocks/byvWbtc.sol @@ -20,4 +20,6 @@ contract byvWbtc is ERC20, IbyvWbtc { } function deposit(bytes32[] calldata merkleProof) override external {} + + function withdraw() override external returns (uint) {} } diff --git a/contracts/peaks/BadgerSettPeak.sol b/contracts/peaks/BadgerSettPeak.sol index c8b7a2b..f56b643 100644 --- a/contracts/peaks/BadgerSettPeak.sol +++ b/contracts/peaks/BadgerSettPeak.sol @@ -72,6 +72,7 @@ contract BadgerSettPeak is AccessControlDefended, IBadgerSettPeak { * @return outAmount Amount of Sett LP token */ function redeem(uint poolId, uint inAmount) + override external defend blockLocked @@ -103,6 +104,7 @@ contract BadgerSettPeak is AccessControlDefended, IBadgerSettPeak { * @return max Max amount of bBTC redeemable for chosen sett */ function calcRedeem(uint poolId, uint bBtc) + override external view returns(uint sett, uint fee, uint max) @@ -151,8 +153,8 @@ contract BadgerSettPeak is AccessControlDefended, IBadgerSettPeak { { return btc // is already scaled by 1e36 .mul(1e18) - .div(pool.sett.getPricePerFullShare()) - .div(pool.swap.get_virtual_price()); + .div(pool.swap.get_virtual_price()) + .div(pool.sett.getPricePerFullShare()); } /** diff --git a/contracts/peaks/BadgerYearnWbtcPeak.sol b/contracts/peaks/BadgerYearnWbtcPeak.sol index 3380ec6..f1f869e 100644 --- a/contracts/peaks/BadgerYearnWbtcPeak.sol +++ b/contracts/peaks/BadgerYearnWbtcPeak.sol @@ -62,6 +62,7 @@ contract BadgerYearnWbtcPeak is AccessControlDefended, IByvWbtcPeak { * @return outAmount Amount of byvWBTC token */ function redeem(uint inAmount) + override external defend blockLocked @@ -91,6 +92,7 @@ contract BadgerYearnWbtcPeak is AccessControlDefended, IByvWbtcPeak { * @return max Max amount of bBTC redeemable for byvWBTC */ function calcRedeem(uint bBtc) + override external view returns(uint sett, uint fee, uint max) @@ -124,8 +126,8 @@ contract BadgerYearnWbtcPeak is AccessControlDefended, IByvWbtcPeak { returns(uint) { return btc // this value is scaled by 1e36 - .div(1e20) - .div(byvWBTC.pricePerShare()); + .div(byvWBTC.pricePerShare()) + .div(1e20); } /** diff --git a/test/BadgerSettPeak.js b/test/BadgerSettPeak.js index 92b6718..9d79c29 100644 --- a/test/BadgerSettPeak.js +++ b/test/BadgerSettPeak.js @@ -44,13 +44,19 @@ describe('BadgerSettPeak', function() { expect(await core.peaks(badgerPeak.address)).to.eq(2) }) + it('cant mark peak with funds as extinct', async function() { + await expect( + core.setPeakStatus(badgerPeak.address, 0 /* Extinct */) + ).to.be.revertedWith('NON_TRIVIAL_FUNDS_IN_PEAK') + }) + // redeem works for dormant peak it('redeem', async function() { const [ aliceBbtc, accumulatedFee ] = await Promise.all([ bBTC.balanceOf(alice), core.accumulatedFee() ]) - const amount = aliceBbtc.mul(7).div(10) // not redeeming all + const amount = aliceBbtc await badgerPeak.redeem(0, amount) @@ -60,13 +66,6 @@ describe('BadgerSettPeak', function() { expect(await core.accumulatedFee()).to.eq(_fee.add(accumulatedFee)) }) - it('redeem fails for Extinct peak', async function() { - await core.setPeakStatus(badgerPeak.address, 0 /* Extinct */) - expect(await core.peaks(badgerPeak.address)).to.eq(0) - - await expect(badgerPeak.redeem(0, await bBTC.balanceOf(alice))).to.be.revertedWith('PEAK_EXTINCT') - }) - it('collectFee', async function() { const accumulatedFee = await core.accumulatedFee() @@ -74,6 +73,15 @@ describe('BadgerSettPeak', function() { expect(await bBTC.balanceOf(feeSink)).to.eq(accumulatedFee); expect(await core.accumulatedFee()).to.eq(ZERO) + + await badgerPeak.connect(ethers.provider.getSigner(feeSink)).redeem(0, accumulatedFee) + }) + + it('redeem fails for Extinct peak', async function() { + await core.setPeakStatus(badgerPeak.address, 0 /* Extinct */) + expect(await core.peaks(badgerPeak.address)).to.eq(0) + + await expect(badgerPeak.redeem(0, _1e18 /* dummy value */)).to.be.revertedWith('PEAK_EXTINCT') }) it('modifyWhitelistedCurvePools', async function() { diff --git a/test/fork/Zap.js b/test/fork/Zap.js index 62db173..147075a 100644 --- a/test/fork/Zap.js +++ b/test/fork/Zap.js @@ -3,7 +3,7 @@ const { expect } = require("chai"); const deployer = require('../deployer') const { - constants: { _1e8, _1e18, NULL }, + constants: { _1e8, _1e18, NULL, ZERO }, impersonateAccount } = require('../utils'); const badgerMultiSig = '0xB65cef03b9B89f99517643226d76e286ee999e77' @@ -61,7 +61,7 @@ describe('Zap (mainnet-fork)', function() { it('mint with renbtc', async function() { let amount = _1e8.mul(9) - const ren = await deployer.getRenbtc(alice, amount) + ren = await deployer.getRenbtc(alice, amount) await ren.approve(zap.address, amount) amount = amount.div(3) @@ -95,7 +95,7 @@ describe('Zap (mainnet-fork)', function() { it('mint with wbtc', async function() { let amount = _1e8.mul(12) - const wbtc = await deployer.getWbtc(alice, amount, wBTCWhale) + wbtc = await deployer.getWbtc(alice, amount, wBTCWhale) await wbtc.approve(zap.address, amount) amount = amount.div(4) @@ -133,6 +133,147 @@ describe('Zap (mainnet-fork)', function() { expect(ibbtc > 17.8).to.be.true }) + it('zap.calcRedeem', async function() { + amount = await bBTC.balanceOf(alice) + + let redeem = await zap.calcRedeem(ren.address, amount) + console.log({ redeem }) + redeem = await zap.calcRedeem(wbtc.address, amount) + console.log({ redeem }) + + expect(await ren.balanceOf(alice)).to.eq(ZERO) + expect(await wbtc.balanceOf(alice)).to.eq(ZERO) + + await bBTC.approve(zap.address, amount); + amount = amount.div(7) + }) + + it('redeem from crvRenWBTC', async function() { + const bcrvRenWBTC = await ethers.getContractAt('IERC20', deployer.crvPools.ren.sett) + let _then = await Promise.all([ + bBTC.balanceOf(alice), + ren.balanceOf(alice), + bcrvRenWBTC.balanceOf(badgerPeak.address) + ]) + + await zap.redeem(ren.address, amount, 0, 0, 0); + + let _now = await Promise.all([ + bBTC.balanceOf(alice), + ren.balanceOf(alice), + bcrvRenWBTC.balanceOf(badgerPeak.address) + ]) + assertions(_then, _now, amount) + + // wbtc + _then = await Promise.all([ + bBTC.balanceOf(alice), + wbtc.balanceOf(alice), + bcrvRenWBTC.balanceOf(badgerPeak.address) + ]) + + await zap.redeem(wbtc.address, amount, 0, 1, 0); + + _now = await Promise.all([ + bBTC.balanceOf(alice), + wbtc.balanceOf(alice), + bcrvRenWBTC.balanceOf(badgerPeak.address) + ]) + assertions(_then, _now, amount) + }) + + it('redeem from bcrvRenWSBTC', async function() { + const bcrvRenWSBTC = await ethers.getContractAt('IERC20', deployer.crvPools.sbtc.sett) + let _then = await Promise.all([ + bBTC.balanceOf(alice), + ren.balanceOf(alice), + bcrvRenWSBTC.balanceOf(badgerPeak.address) + ]) + + await zap.redeem(ren.address, amount, 1, 0, 0); + + let _now = await Promise.all([ + bBTC.balanceOf(alice), + ren.balanceOf(alice), + bcrvRenWSBTC.balanceOf(badgerPeak.address) + ]) + assertions(_then, _now, amount) + + // wbtc + _then = await Promise.all([ + bBTC.balanceOf(alice), + wbtc.balanceOf(alice), + bcrvRenWSBTC.balanceOf(badgerPeak.address) + ]) + + await zap.redeem(wbtc.address, amount, 1, 1, 0); + + _now = await Promise.all([ + bBTC.balanceOf(alice), + wbtc.balanceOf(alice), + bcrvRenWSBTC.balanceOf(badgerPeak.address) + ]) + assertions(_then, _now, amount) + }) + + it('redeem from btbtc_sbtcCrv', async function() { + const btbtc_sbtcCrv = await ethers.getContractAt('IERC20', deployer.crvPools.tbtc.sett) + let _then = await Promise.all([ + bBTC.balanceOf(alice), + ren.balanceOf(alice), + btbtc_sbtcCrv.balanceOf(badgerPeak.address) + ]) + + await zap.redeem(ren.address, amount, 2, 1, 0); + + let _now = await Promise.all([ + bBTC.balanceOf(alice), + ren.balanceOf(alice), + btbtc_sbtcCrv.balanceOf(badgerPeak.address) + ]) + assertions(_then, _now, amount) + + // wbtc + _then = await Promise.all([ + bBTC.balanceOf(alice), + wbtc.balanceOf(alice), + btbtc_sbtcCrv.balanceOf(badgerPeak.address) + ]) + + await zap.redeem(wbtc.address, amount, 2, 2, 0); + + _now = await Promise.all([ + bBTC.balanceOf(alice), + wbtc.balanceOf(alice), + btbtc_sbtcCrv.balanceOf(badgerPeak.address) + ]) + assertions(_then, _now, amount) + }) + + it('redeem from byvwbtc', async function() { + const byvWbtc = await ethers.getContractAt('IERC20', '0x4b92d19c11435614CD49Af1b589001b7c08cD4D5') + let _then = await Promise.all([ + bBTC.balanceOf(alice), + wbtc.balanceOf(alice), + byvWbtc.balanceOf(wbtcPeak.address) + ]) + + await zap.redeem(wbtc.address, amount, 3, -1 /* redundant */, 0); + + let _now = await Promise.all([ + bBTC.balanceOf(alice), + wbtc.balanceOf(alice), + byvWbtc.balanceOf(wbtcPeak.address) + ]) + assertions(_then, _now, amount) + }) + + function assertions(_then, _now, amount) { + expect(_now[0]).to.eq(_then[0].sub(amount)) + expect(_now[1].gt(_then[1])).to.be.true + expect(_now[2].lt(_then[2])).to.be.true + } + it('approveContractAccess', async function() { const ZapCall = await ethers.getContractFactory('ZapCall') const zapCall = await ZapCall.deploy()