From 683c22a535e16c4ba82f407ad928c5225e53d5be Mon Sep 17 00:00:00 2001 From: "Julian M. Rodriguez" <56316686+julianmrodri@users.noreply.github.com> Date: Mon, 3 Jul 2023 18:06:23 -0300 Subject: [PATCH] Facade read custom redeem (#21) Co-authored-by: Taylor Brent --- contracts/facade/FacadeAct.sol | 69 +++++++++++++++---- contracts/facade/FacadeRead.sol | 66 ++++++++++++++---- contracts/interfaces/IFacadeAct.sol | 39 ++++++----- contracts/interfaces/IFacadeRead.sol | 17 ++++- contracts/interfaces/IRToken.sol | 2 +- contracts/interfaces/IRevenueTrader.sol | 4 +- contracts/p0/RToken.sol | 27 +++----- contracts/p1/RToken.sol | 20 +++--- test/Facade.test.ts | 66 ++++++++++++++++-- test/RToken.test.ts | 15 ---- .../mainnet-test/FacadeActVersion.test.ts | 28 ++++++-- .../mainnet-test/StaticATokens.test.ts | 2 +- 12 files changed, 253 insertions(+), 102 deletions(-) diff --git a/contracts/facade/FacadeAct.sol b/contracts/facade/FacadeAct.sol index ca3fe7d5b9..7c6d952a8c 100644 --- a/contracts/facade/FacadeAct.sol +++ b/contracts/facade/FacadeAct.sol @@ -47,6 +47,17 @@ contract FacadeAct is IFacadeAct, Multicall { _settleTrade(revenueTrader, toSettle[i]); } + // if 2.1.0, distribute tokenToBuy + bytes1 majorVersion = bytes(revenueTrader.version())[0]; + if ( + toSettle.length > 0 && + (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_1) + ) { + address(revenueTrader).functionCall( + abi.encodeWithSignature("manageToken(address)", revenueTrader.tokenToBuy()) + ); + } + // Transfer revenue backingManager -> revenueTrader _forwardRevenue(revenueTrader.main().backingManager(), toStart); @@ -60,8 +71,11 @@ contract FacadeAct is IFacadeAct, Multicall { /// Includes consideration of when to distribute the RevenueTrader tokenToBuy /// @return erc20s The ERC20s that have auctions that can be started /// @return canStart If the ERC20 auction can be started - /// @return surpluses {qTok} The surplus amount + /// @return surpluses {qTok} The surplus amounts currently held, ignoring reward balances /// @return minTradeAmounts {qTok} The minimum amount worth trading + /// @return bmRewards {qTok} The amounts would be claimed by backingManager.claimRewards() + /// @return revTraderRewards {qTok} The amounts that would be claimed by trader.claimRewards() + /// @dev Note that `surpluses` + `bmRewards` + `revTraderRewards` /// @custom:static-call function revenueOverview(IRevenueTrader revenueTrader) external @@ -69,19 +83,25 @@ contract FacadeAct is IFacadeAct, Multicall { IERC20[] memory erc20s, bool[] memory canStart, uint256[] memory surpluses, - uint256[] memory minTradeAmounts + uint256[] memory minTradeAmounts, + uint256[] memory bmRewards, + uint256[] memory revTraderRewards ) { + IBackingManager bm = revenueTrader.main().backingManager(); uint192 minTradeVolume = revenueTrader.minTradeVolume(); // {UoA} Registry memory reg = revenueTrader.main().assetRegistry().getRegistry(); // Forward ALL revenue - _forwardRevenue(revenueTrader.main().backingManager(), reg.erc20s); + _forwardRevenue(bm, reg.erc20s); erc20s = new IERC20[](reg.erc20s.length); canStart = new bool[](reg.erc20s.length); surpluses = new uint256[](reg.erc20s.length); minTradeAmounts = new uint256[](reg.erc20s.length); + bmRewards = new uint256[](reg.erc20s.length); + revTraderRewards = new uint256[](reg.erc20s.length); + // Calculate which erc20s should have auctions started for (uint256 i = 0; i < reg.erc20s.length; ++i) { erc20s[i] = reg.erc20s[i]; @@ -108,6 +128,27 @@ contract FacadeAct is IFacadeAct, Multicall { canStart[i] = true; } } + + // Calculate rewards + // Reward counts are disjoint with `surpluses` and `canStart` + for (uint256 i = 0; i < reg.erc20s.length; ++i) { + bmRewards[i] = reg.erc20s[i].balanceOf(address(bm)); + } + // solhint-disable-next-line no-empty-blocks + try bm.claimRewards() {} catch {} // same between 2.1.0 and 3.0.0 + for (uint256 i = 0; i < reg.erc20s.length; ++i) { + bmRewards[i] = reg.erc20s[i].balanceOf(address(bm)) - bmRewards[i]; + } + for (uint256 i = 0; i < reg.erc20s.length; ++i) { + revTraderRewards[i] = reg.erc20s[i].balanceOf(address(revenueTrader)); + } + // solhint-disable-next-line no-empty-blocks + try revenueTrader.claimRewards() {} catch {} // same between 2.1.0 and 3.0.0 + for (uint256 i = 0; i < reg.erc20s.length; ++i) { + revTraderRewards[i] = + reg.erc20s[i].balanceOf(address(revenueTrader)) - + revTraderRewards[i]; + } } /// To use this, call via callStatic. @@ -140,7 +181,7 @@ contract FacadeAct is IFacadeAct, Multicall { } } - // If no auctions ongoing, try to find a new auction to start + // If no auctions ongoing, to find a new auction to start if (bm.tradesOpen() == 0) { _rebalance(bm); @@ -165,12 +206,7 @@ contract FacadeAct is IFacadeAct, Multicall { // Settle auctions trader.settleTrade(toSettle); } else if (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_1) { - // solhint-disable-next-line avoid-low-level-calls - (bool success, ) = address(trader).call{ value: 0 }( - // previous versions did not return anything - abi.encodeWithSignature("settleTrade(address)", toSettle) - ); - success = success; // hush warning + address(trader).functionCall(abi.encodeWithSignature("settleTrade(address)", toSettle)); } else { _revertUnrecognizedVersion(); } @@ -178,11 +214,16 @@ contract FacadeAct is IFacadeAct, Multicall { function _forwardRevenue(IBackingManager bm, IERC20[] memory toStart) private { bytes1 majorVersion = bytes(bm.version())[0]; + // Need to use try-catch here in order to still show revenueOverview when basket not ready if (majorVersion == MAJOR_VERSION_3) { // solhint-disable-next-line no-empty-blocks try bm.forwardRevenue(toStart) {} catch {} } else if (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_1) { - address(bm).functionCall(abi.encodeWithSignature("manageTokens(address[])", toStart)); + // solhint-disable-next-line avoid-low-level-calls + (bool success, ) = address(bm).call{ value: 0 }( + abi.encodeWithSignature("manageTokens(address[])", toStart) + ); + success = success; // hush warning } else { _revertUnrecognizedVersion(); } @@ -196,8 +237,7 @@ contract FacadeAct is IFacadeAct, Multicall { bytes1 majorVersion = bytes(revenueTrader.version())[0]; if (majorVersion == MAJOR_VERSION_3) { - // solhint-disable-next-line no-empty-blocks - try revenueTrader.manageTokens(toStart, kinds) {} catch {} + revenueTrader.manageTokens(toStart, kinds); } else if (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_1) { for (uint256 i = 0; i < toStart.length; ++i) { address(revenueTrader).functionCall( @@ -213,8 +253,7 @@ contract FacadeAct is IFacadeAct, Multicall { bytes1 majorVersion = bytes(bm.version())[0]; if (majorVersion == MAJOR_VERSION_3) { - // solhint-disable-next-line no-empty-blocks - try bm.rebalance(TradeKind.DUTCH_AUCTION) {} catch {} + bm.rebalance(TradeKind.DUTCH_AUCTION); } else if (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_1) { IERC20[] memory emptyERC20s = new IERC20[](0); address(bm).functionCall( diff --git a/contracts/facade/FacadeRead.sol b/contracts/facade/FacadeRead.sol index 1491dae9d4..4896b2d756 100644 --- a/contracts/facade/FacadeRead.sol +++ b/contracts/facade/FacadeRead.sol @@ -96,15 +96,16 @@ contract FacadeRead is IFacadeRead { } /// @return tokens The erc20s returned for the redemption - /// @return withdrawals The balances necessary to issue `amount` RToken - /// @return isProrata True if the redemption is prorata and not full + /// @return withdrawals The balances the reedemer would receive after a full redemption + /// @return available The amount actually available, for each token + /// @dev If available[i] < withdrawals[i], then RToken.redeem() would revert /// @custom:static-call function redeem(IRToken rToken, uint256 amount) external returns ( address[] memory tokens, uint256[] memory withdrawals, - bool isProrata + uint256[] memory available ) { IMain main = rToken.main(); @@ -122,23 +123,62 @@ contract FacadeRead is IFacadeRead { // D18{BU} = D18{BU} * {qRTok} / {qRTok} uint192 basketsRedeemed = rTok.basketsNeeded().muluDivu(amount, supply); - (tokens, withdrawals) = bh.quote(basketsRedeemed, FLOOR); + available = new uint256[](tokens.length); - // Bound each withdrawal by the prorata share, in case we're currently under-collateralized - address backingManager = address(main.backingManager()); - for (uint256 i = 0; i < tokens.length; ++i) { + // Calculate prorata amounts + for (uint256 i = 0; i < tokens.length; i++) { // {qTok} = {qTok} * {qRTok} / {qRTok} - uint256 prorata = mulDiv256( - IERC20Upgradeable(tokens[i]).balanceOf(backingManager), + available[i] = mulDiv256( + IERC20(tokens[i]).balanceOf(address(main.backingManager())), amount, supply ); // FLOOR + } + } - if (prorata < withdrawals[i]) { - withdrawals[i] = prorata; - isProrata = true; - } + /// @return tokens The erc20s returned for the redemption + /// @return withdrawals The balances necessary to issue `amount` RToken + /// @custom:static-call + function redeemCustom( + IRToken rToken, + uint256 amount, + uint48[] memory basketNonces, + uint192[] memory portions + ) external returns (address[] memory tokens, uint256[] memory withdrawals) { + IMain main = rToken.main(); + require(!main.frozen(), "frozen"); + + // Call collective state keepers. + main.poke(); + + uint256 supply = rToken.totalSupply(); + + // === Get basket redemption amounts === + uint256 portionsSum; + for (uint256 i = 0; i < portions.length; ++i) { + portionsSum += portions[i]; + } + require(portionsSum == FIX_ONE, "portions do not add up to FIX_ONE"); + + // D18{BU} = D18{BU} * {qRTok} / {qRTok} + uint192 basketsRedeemed = rToken.basketsNeeded().muluDivu(amount, supply); + (tokens, withdrawals) = main.basketHandler().quoteCustomRedemption( + basketNonces, + portions, + basketsRedeemed + ); + + // ==== Prorate redemption ==== + // Bound each withdrawal by the prorata share, in case currently under-collateralized + for (uint256 i = 0; i < tokens.length; i++) { + // {qTok} = {qTok} * {qRTok} / {qRTok} + uint256 prorata = mulDiv256( + IERC20(tokens[i]).balanceOf(address(main.backingManager())), + amount, + supply + ); // FLOOR + if (prorata < withdrawals[i]) withdrawals[i] = prorata; } } diff --git a/contracts/interfaces/IFacadeAct.sol b/contracts/interfaces/IFacadeAct.sol index d9370e6a52..61085f0a72 100644 --- a/contracts/interfaces/IFacadeAct.sol +++ b/contracts/interfaces/IFacadeAct.sol @@ -35,6 +35,29 @@ interface IFacadeAct { TradeKind[] memory kinds ) external; + // === Static Calls === + + /// To use this, call via callStatic. + /// Includes consideration of when to distribute the RevenueTrader tokenToBuy + /// @return erc20s The ERC20s that have auctions that can be started + /// @return canStart If the ERC20 auction can be started + /// @return surpluses {qTok} The surplus amounts currently held, ignoring reward balances + /// @return minTradeAmounts {qTok} The minimum amount worth trading + /// @return bmRewards {qTok} The amounts would be claimed by backingManager.claimRewards() + /// @return revTraderRewards {qTok} The amounts that would be claimed by trader.claimRewards() + /// @dev Note that `surpluses` + `bmRewards` + `revTraderRewards` + /// @custom:static-call + function revenueOverview(IRevenueTrader revenueTrader) + external + returns ( + IERC20[] memory erc20s, + bool[] memory canStart, + uint256[] memory surpluses, + uint256[] memory minTradeAmounts, + uint256[] memory bmRewards, + uint256[] memory revTraderRewards + ); + /// To use this, call via callStatic. /// If canStart is true, call backingManager.rebalance(). May require settling a /// trade first; see auctionsSettleable. @@ -51,20 +74,4 @@ interface IFacadeAct { IERC20 buy, uint256 sellAmount ); - - /// To use this, call via callStatic. - /// Includes consideration of when to distribute the RevenueTrader tokenToBuy - /// @return erc20s The ERC20s that have auctions that can be started - /// @return canStart If the ERC20 auction can be started - /// @return surpluses {qTok} The surplus amount - /// @return minTradeAmounts {qTok} The minimum amount worth trading - /// @custom:static-call - function revenueOverview(IRevenueTrader revenueTrader) - external - returns ( - IERC20[] memory erc20s, - bool[] memory canStart, - uint256[] memory surpluses, - uint256[] memory minTradeAmounts - ); } diff --git a/contracts/interfaces/IFacadeRead.sol b/contracts/interfaces/IFacadeRead.sol index 61e2dbd521..44af758dec 100644 --- a/contracts/interfaces/IFacadeRead.sol +++ b/contracts/interfaces/IFacadeRead.sol @@ -31,17 +31,28 @@ interface IFacadeRead { ); /// @return tokens The erc20s returned for the redemption - /// @return withdrawals The balances necessary to issue `amount` RToken - /// @return isProrata True if the redemption is prorata and not full + /// @return withdrawals The balances the reedemer would receive after a full redemption + /// @return available The amount actually available, for each token + /// @dev If available[i] < withdrawals[i], then RToken.redeem() would revert /// @custom:static-call function redeem(IRToken rToken, uint256 amount) external returns ( address[] memory tokens, uint256[] memory withdrawals, - bool isProrata + uint256[] memory available ); + /// @return tokens The erc20s returned for the redemption + /// @return withdrawals The balances the reedemer would receive after redemption + /// @custom:static-call + function redeemCustom( + IRToken rToken, + uint256 amount, + uint48[] memory basketNonces, + uint192[] memory portions + ) external returns (address[] memory tokens, uint256[] memory withdrawals); + /// @return erc20s The ERC20 addresses in the current basket /// @return uoaShares The proportion of the basket associated with each ERC20 /// @return targets The bytes32 representations of the target unit associated with each ERC20 diff --git a/contracts/interfaces/IRToken.sol b/contracts/interfaces/IRToken.sol index 31bf6cc9f9..3111563053 100644 --- a/contracts/interfaces/IRToken.sol +++ b/contracts/interfaces/IRToken.sol @@ -107,7 +107,7 @@ interface IRToken is IComponent, IERC20MetadataUpgradeable, IERC20PermitUpgradea uint192[] memory portions, address[] memory expectedERC20sOut, uint256[] memory minAmounts - ) external returns (address[] memory erc20sOut, uint256[] memory amountsOut); + ) external; /// Mint an amount of RToken equivalent to baskets BUs, scaling basketsNeeded up /// Callable only by BackingManager diff --git a/contracts/interfaces/IRevenueTrader.sol b/contracts/interfaces/IRevenueTrader.sol index 415b906d69..4b07ff70bc 100644 --- a/contracts/interfaces/IRevenueTrader.sol +++ b/contracts/interfaces/IRevenueTrader.sol @@ -31,9 +31,11 @@ interface IRevenueTrader is IComponent, ITrading { /// @param kinds The kinds of auctions to launch: DUTCH_AUCTION | BATCH_AUCTION /// @custom:interaction function manageTokens(IERC20[] memory erc20s, TradeKind[] memory kinds) external; + + function tokenToBuy() external view returns (IERC20); } // solhint-disable-next-line no-empty-blocks interface TestIRevenueTrader is IRevenueTrader, TestITrading { - function tokenToBuy() external view returns (IERC20); + } diff --git a/contracts/p0/RToken.sol b/contracts/p0/RToken.sol index 2dc33334a0..a943324371 100644 --- a/contracts/p0/RToken.sol +++ b/contracts/p0/RToken.sol @@ -186,12 +186,7 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { uint192[] memory portions, address[] memory expectedERC20sOut, uint256[] memory minAmounts - ) - external - notFrozen - exchangeRateIsValidAfter - returns (address[] memory erc20sOut, uint256[] memory amountsOut) - { + ) external notFrozen exchangeRateIsValidAfter { require(amount > 0, "Cannot redeem zero"); require(amount <= balanceOf(_msgSender()), "insufficient balance"); @@ -218,11 +213,9 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { require(portionsSum == FIX_ONE, "portions do not add up to FIX_ONE"); } - (erc20sOut, amountsOut) = main.basketHandler().quoteCustomRedemption( - basketNonces, - portions, - basketsRedeemed - ); + (address[] memory erc20s, uint256[] memory amounts) = main + .basketHandler() + .quoteCustomRedemption(basketNonces, portions, basketsRedeemed); // === Save initial recipient balances === @@ -235,21 +228,21 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { { bool allZero = true; // Bound each withdrawal by the prorata share, in case currently under-collateralized - for (uint256 i = 0; i < erc20sOut.length; i++) { + for (uint256 i = 0; i < erc20s.length; i++) { // {qTok} = {qTok} * {qRTok} / {qRTok} uint256 prorata = mulDiv256( - IERC20(erc20sOut[i]).balanceOf(address(main.backingManager())), + IERC20(erc20s[i]).balanceOf(address(main.backingManager())), amount, supply ); // FLOOR - if (prorata < amountsOut[i]) amountsOut[i] = prorata; + if (prorata < amounts[i]) amounts[i] = prorata; // Send withdrawal - if (amountsOut[i] > 0) { - IERC20(erc20sOut[i]).safeTransferFrom( + if (amounts[i] > 0) { + IERC20(erc20s[i]).safeTransferFrom( address(main.backingManager()), recipient, - amountsOut[i] + amounts[i] ); allZero = false; } diff --git a/contracts/p1/RToken.sol b/contracts/p1/RToken.sol index 91bad04ba6..942e2d2501 100644 --- a/contracts/p1/RToken.sol +++ b/contracts/p1/RToken.sol @@ -247,7 +247,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { uint192[] memory portions, address[] memory expectedERC20sOut, uint256[] memory minAmounts - ) external notFrozen returns (address[] memory erc20sOut, uint256[] memory amountsOut) { + ) external notFrozen { // == Refresh == assetRegistry.refresh(); // solhint-disable-next-line no-empty-blocks @@ -275,7 +275,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { // === Get basket redemption amounts === - (erc20sOut, amountsOut) = basketHandler.quoteCustomRedemption( + (address[] memory erc20s, uint256[] memory amounts) = basketHandler.quoteCustomRedemption( basketNonces, portions, baskets @@ -283,18 +283,18 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { // ==== Prorate redemption ==== // i.e, set amounts = min(amounts, balances * amount / totalSupply) - // where balances[i] = erc20sOut[i].balanceOf(backingManager) + // where balances[i] = erc20s[i].balanceOf(backingManager) // Bound each withdrawal by the prorata share, in case we're currently under-collateralized - for (uint256 i = 0; i < erc20sOut.length; ++i) { + for (uint256 i = 0; i < erc20s.length; ++i) { // {qTok} = {qTok} * {qRTok} / {qRTok} uint256 prorata = mulDiv256( - IERC20(erc20sOut[i]).balanceOf(address(backingManager)), + IERC20(erc20s[i]).balanceOf(address(backingManager)), amount, supply ); // FLOOR - if (prorata < amountsOut[i]) amountsOut[i] = prorata; + if (prorata < amounts[i]) amounts[i] = prorata; } // === Save initial recipient balances === @@ -310,15 +310,15 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { // Distribute tokens; revert if empty redemption { bool allZero = true; - for (uint256 i = 0; i < erc20sOut.length; ++i) { - if (amountsOut[i] == 0) continue; // unregistered ERC20s will have 0 amount + for (uint256 i = 0; i < erc20s.length; ++i) { + if (amounts[i] == 0) continue; // unregistered ERC20s will have 0 amount if (allZero) allZero = false; // Send withdrawal - IERC20Upgradeable(erc20sOut[i]).safeTransferFrom( + IERC20Upgradeable(erc20s[i]).safeTransferFrom( address(backingManager), recipient, - amountsOut[i] + amounts[i] ); } if (allZero) revert("empty redemption"); diff --git a/test/Facade.test.ts b/test/Facade.test.ts index 96ae875896..1ebc9145e4 100644 --- a/test/Facade.test.ts +++ b/test/Facade.test.ts @@ -298,7 +298,7 @@ describe('FacadeRead + FacadeAct contracts', () => { }) it('Should return redeemable quantities correctly', async () => { - const [toks, quantities, isProrata] = await facade.callStatic.redeem( + const [toks, quantities, available] = await facade.callStatic.redeem( rToken.address, issueAmount ) @@ -311,17 +311,73 @@ describe('FacadeRead + FacadeAct contracts', () => { expect(quantities[1]).to.equal(issueAmount.div(4).div(bn('1e12'))) expect(quantities[2]).to.equal(issueAmount.div(4)) expect(quantities[3]).to.equal(issueAmount.div(4).mul(50).div(bn('1e10'))) - expect(isProrata).to.equal(false) + expect(available[0]).to.equal(issueAmount.div(4)) + expect(available[1]).to.equal(issueAmount.div(4).div(bn('1e12'))) + expect(available[2]).to.equal(issueAmount.div(4)) + expect(available[3]).to.equal(issueAmount.div(4).mul(50).div(bn('1e10'))) + + // redeemCustom + const [toksCustom, quantitiesCustom] = await facade.callStatic.redeemCustom( + rToken.address, + issueAmount, + [await basketHandler.nonce()], + [fp('1')] + ) + expect(toksCustom.length).to.equal(4) + expect(toksCustom[0]).to.equal(token.address) + expect(toksCustom[1]).to.equal(usdc.address) + expect(toksCustom[2]).to.equal(aToken.address) + expect(toksCustom[3]).to.equal(cTokenVault.address) + expect(quantitiesCustom[0]).to.equal(issueAmount.div(4)) + expect(quantitiesCustom[1]).to.equal(issueAmount.div(4).div(bn('1e12'))) + expect(quantitiesCustom[2]).to.equal(issueAmount.div(4)) + expect(quantitiesCustom[3]).to.equal(issueAmount.div(4).mul(50).div(bn('1e10'))) // Prorata case -- burn half await token.burn(await main.backingManager(), issueAmount.div(8)) - const [newToks, newQuantities, newIsProrata] = await facade.callStatic.redeem( + const [newToks, newQuantities, newAvailable] = await facade.callStatic.redeem( rToken.address, issueAmount ) expect(newToks[0]).to.equal(token.address) - expect(newQuantities[0]).to.equal(issueAmount.div(8)) - expect(newIsProrata).to.equal(true) + expect(newQuantities[0]).to.equal(issueAmount.div(4)) + expect(newAvailable[0]).to.equal(issueAmount.div(4).div(2)) + + // redeemCustom + const [newToksCustom, newQuantitiesCustom] = await facade.callStatic.redeemCustom( + rToken.address, + issueAmount, + [await basketHandler.nonce()], + [fp('1')] + ) + expect(newToksCustom.length).to.equal(4) + expect(newToksCustom[0]).to.equal(token.address) + expect(newToksCustom[1]).to.equal(usdc.address) + expect(newToksCustom[2]).to.equal(aToken.address) + expect(newToksCustom[3]).to.equal(cTokenVault.address) + expect(newQuantitiesCustom[0]).to.equal(issueAmount.div(4).div(2)) + expect(newQuantitiesCustom[1]).to.equal(issueAmount.div(4).div(bn('1e12'))) + expect(newQuantitiesCustom[2]).to.equal(issueAmount.div(4)) + expect(newQuantitiesCustom[3]).to.equal(issueAmount.div(4).mul(50).div(bn('1e10'))) + + // refreshBasket() + await basketHandler.connect(owner).refreshBasket() + await expect(facade.callStatic.redeem(rToken.address, issueAmount)).not.to.be.reverted + const [prevBasketTokens, prevBasketQuantities] = await facade.callStatic.redeemCustom( + rToken.address, + issueAmount, + [(await basketHandler.nonce()) - 1], + [fp('1')] + ) + expect(prevBasketTokens.length).to.equal(4) + expect(prevBasketTokens[0]).to.equal(token.address) + expect(prevBasketTokens[1]).to.equal(usdc.address) + expect(prevBasketTokens[2]).to.equal(aToken.address) + expect(prevBasketTokens[3]).to.equal(cTokenVault.address) + expect(prevBasketQuantities[0]).to.equal(issueAmount.div(4).div(2)) + expect(prevBasketQuantities[1]).to.equal(issueAmount.div(4).div(bn('1e12'))) + expect(prevBasketQuantities[2]).to.equal(issueAmount.div(4)) + expect(prevBasketQuantities[3]).to.equal(issueAmount.div(4).mul(50).div(bn('1e10'))) }) it('Should revert when returning redeemable quantities if frozen', async () => { diff --git a/test/RToken.test.ts b/test/RToken.test.ts index 6e9a8fb32a..3db13c8253 100644 --- a/test/RToken.test.ts +++ b/test/RToken.test.ts @@ -53,7 +53,6 @@ import { PRICE_TIMEOUT, VERSION, } from './fixtures' -import { expectEqualArrays } from './utils/matchers' import { cartesianProduct } from './utils/cases' import { useEnv } from '#/utils/env' import { mintCollaterals } from './utils/tokens' @@ -1537,20 +1536,6 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { redeemAmount ) - // Check simulation result - const [actualERC20s, actualQuantities] = await rToken - .connect(addr1) - .callStatic.redeemCustom( - addr1.address, - redeemAmount, - basketNonces, - portions, - quote.erc20s, - quote.quantities - ) - expectEqualArrays(actualERC20s, quote.erc20s) - expectEqualArrays(actualQuantities, quote.quantities) - await rToken .connect(addr1) .redeemCustom( diff --git a/test/integration/mainnet-test/FacadeActVersion.test.ts b/test/integration/mainnet-test/FacadeActVersion.test.ts index 50c7155fb5..8c39a6eebd 100644 --- a/test/integration/mainnet-test/FacadeActVersion.test.ts +++ b/test/integration/mainnet-test/FacadeActVersion.test.ts @@ -97,7 +97,6 @@ describeFork( const FacadeActFactory = await ethers.getContractFactory('FacadeAct') newFacadeAct = await FacadeActFactory.deploy() - // These surpluses move around in CI, so I think it's fine just to assert the right ones are >0 const expectedSurpluses = [ bn('13498155707558299290000'), bn('9076'), @@ -114,11 +113,29 @@ describeFork( bn('0'), bn('6413550000'), ] - const [, , surpluses] = await newFacadeAct.callStatic.revenueOverview(revenueTrader.address) + const expectedBmRewards = [ + bn('0'), + bn('0'), + bn('0'), + bn('9999'), + bn('0'), + bn('0'), + bn('0'), + bn('0'), + bn('0'), + bn('0'), + bn('0'), + bn('0'), + bn('0'), + bn('0'), + ] + const [, , surpluses, , bmRewards, revTraderRewards] = + await newFacadeAct.callStatic.revenueOverview(revenueTrader.address) + for (let i = 0; i < surpluses.length; i++) { - if (expectedSurpluses[i].gt(0)) { - expect(surpluses[i]).gt(0) - } + if (expectedSurpluses[i].gt(0)) expect(surpluses[i]).gte(expectedSurpluses[i]) + if (expectedBmRewards[i].gt(0)) expect(bmRewards[i]).gte(expectedBmRewards[i]) + expect(revTraderRewards[i]).to.equal(0) } }) @@ -131,6 +148,7 @@ describeFork( await expect( newFacadeAct.runRevenueAuctions(revenueTrader.address, [], [await main.rToken()], [0]) ).to.emit(revenueTrader, 'TradeStarted') + expect(await revenueTrader.tradesOpen()).to.equal(2) }) }) diff --git a/test/integration/mainnet-test/StaticATokens.test.ts b/test/integration/mainnet-test/StaticATokens.test.ts index 15f181b751..01208c2093 100644 --- a/test/integration/mainnet-test/StaticATokens.test.ts +++ b/test/integration/mainnet-test/StaticATokens.test.ts @@ -222,7 +222,7 @@ describeFork(`Static ATokens - Mainnet Check - Mainnet Forking P${IMPLEMENTATION }) // Wrap aDAI - Underlying = false - expect(await aDai.balanceOf(addr1.address)).to.equal(initialBal) + expect(await aDai.balanceOf(addr1.address)).to.be.closeTo(initialBal, 1) expect(await stataDai.balanceOf(addr1.address)).to.equal(0) // Wrap aDAI into a staticaDAI