From 7606a7ddb1f006ef27f8248a8295ff606f1e30d2 Mon Sep 17 00:00:00 2001 From: yaron velner Date: Wed, 20 Oct 2021 12:50:58 +0300 Subject: [PATCH 01/24] test env --- contracts/BKashiPair.sol | 37 +++++++++++++++++++++++++++++++ contracts/KashiPair.sol | 8 ++++--- contracts/mocks/KashiPairMock.sol | 20 ++++++++++++++++- 3 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 contracts/BKashiPair.sol diff --git a/contracts/BKashiPair.sol b/contracts/BKashiPair.sol new file mode 100644 index 0000000..b943b9e --- /dev/null +++ b/contracts/BKashiPair.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: UNLICENSED + + +pragma solidity 0.6.12; +pragma experimental ABIEncoderV2; +import "./KashiPair.sol"; + +contract BKashiPair is KashiPair { + address public bprotocol; + + event BProtocol(address bprotocol_); + + /// @notice The constructor is only used for the initial master contract. Subsequent clones are initialised via `init`. + constructor(IBentoBoxV1 bentoBox_) public KashiPair(bentoBox_) {} + + function setBProtocol(address bprotocol_) public onlyOwner { + require(bprotocol == address(0x0)/*, "BKashiPair: bprotocol alread initialized"*/); + bprotocol = bprotocol_; + emit BProtocol(bprotocol_); + } + + function liquidate( + address[] calldata users, + uint256[] calldata maxBorrowParts, + address to, + ISwapper swapper, + bool open + ) public + override + { + if(bprotocol != address(0x0) && bprotocol != address(0xdead)) { + require(msg.sender == bprotocol, "liquidate: not bprotocol"); + } + + super.liquidate(users, maxBorrowParts, to, swapper, open); + } +} diff --git a/contracts/KashiPair.sol b/contracts/KashiPair.sol index d66d685..8b53efd 100644 --- a/contracts/KashiPair.sol +++ b/contracts/KashiPair.sol @@ -504,10 +504,10 @@ contract KashiPair is ERC20, BoringOwnable, IMasterContract { callData = abi.encodePacked(callData, value1, value2); } - require(callee != address(bentoBox) && callee != address(this), "KashiPair: can't call"); + require(callee != address(bentoBox) && callee != address(this)); (bool success, bytes memory returnData) = callee.call{value: value}(callData); - require(success, "KashiPair: call failed"); + require(success); return (returnData, returnValues); } @@ -607,7 +607,9 @@ contract KashiPair is ERC20, BoringOwnable, IMasterContract { address to, ISwapper swapper, bool open - ) public { + ) public + virtual + { // Oracle can fail but we still need to allow liquidations (, uint256 _exchangeRate) = updateExchangeRate(); accrue(); diff --git a/contracts/mocks/KashiPairMock.sol b/contracts/mocks/KashiPairMock.sol index 428abc7..bc91f8b 100644 --- a/contracts/mocks/KashiPairMock.sol +++ b/contracts/mocks/KashiPairMock.sol @@ -3,8 +3,9 @@ pragma solidity 0.6.12; pragma experimental ABIEncoderV2; import "@sushiswap/bentobox-sdk/contracts/IBentoBoxV1.sol"; import "../KashiPair.sol"; +import "../BKashiPair.sol"; -contract KashiPairMock is KashiPair { +contract KashiPairMock is KashiPair { constructor(IBentoBoxV1 bentoBox) public KashiPair(bentoBox) { return; } @@ -14,3 +15,20 @@ contract KashiPairMock is KashiPair { accrue(); } } + +contract BKashiPairMock is BKashiPair { + constructor(IBentoBoxV1 bentoBox) public BKashiPair(bentoBox) { + return; + } + + function accrueTwice() public { + accrue(); + accrue(); + } + + function setBProtocolMock(address bprotocol_) public { + require(bprotocol == address(0x0)/*, "BKashiPair: bprotocol alread initialized"*/); + bprotocol = bprotocol_; + emit BProtocol(bprotocol_); + } +} From 0833352fe7edfdb17a4913ef791a8418db9e2a9c Mon Sep 17 00:00:00 2001 From: yaron velner Date: Wed, 20 Oct 2021 15:38:52 +0300 Subject: [PATCH 02/24] implementation --- contracts/B.Protocol/BAMM.sol | 247 ++++++++ contracts/B.Protocol/PriceFormula.sol | 52 ++ test/BProtocol.js | 854 ++++++++++++++++++++++++++ 3 files changed, 1153 insertions(+) create mode 100644 contracts/B.Protocol/BAMM.sol create mode 100644 contracts/B.Protocol/PriceFormula.sol create mode 100644 test/BProtocol.js diff --git a/contracts/B.Protocol/BAMM.sol b/contracts/B.Protocol/BAMM.sol new file mode 100644 index 0000000..c2ec54b --- /dev/null +++ b/contracts/B.Protocol/BAMM.sol @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity >=0.6.12; + +import "./PriceFormula.sol"; +import "@boringcrypto/boring-solidity/contracts/BoringOwnable.sol"; +import "@boringcrypto/boring-solidity/contracts/ERC20.sol"; +import "./../interfaces/IOracle.sol"; +import "./../interfaces/IKashiPair.sol"; // TODO the interface for abracadabra is a bit different + +contract BAMM is PriceFormula, BoringOwnable, ERC20 { + IOracle public immutable oracle; + ERC20 public immutable mim; + ERC20 public immutable collateral; + IKashiPair public immutable lendingPair; + bytes public oracleData; + + address public immutable feePool; + uint public constant MAX_FEE = 100; // 1% + uint public constant MAX_CALLER_FEE = 100; // 1% + uint public fee = 0; // fee in bps + uint public callerFee = 0; // fee in bps + uint public A = 20; + uint public constant MIN_A = 20; + uint public constant MAX_A = 200; + + uint public immutable maxDiscount; // max discount in bips + + uint constant public PRECISION = 1e18; + + uint public totalSupply; + + event ParamsSet(uint A, uint fee, uint callerFee); + event UserDeposit(address indexed user, uint wad, uint numShares); + event UserWithdraw(address indexed user, uint wad, uint gem, uint numShares); + event RebalanceSwap(address indexed user, uint wad, uint gem, uint timestamp); + + constructor( + address lendingPair_, + address oracle_, + address mim_, + address collateral_, + address feePool_, + uint maxDiscount_ + ) + public + { + lendingPair = IKashiPair(lendingPair_); + oracle = IOracle(oracle_); + mim = ERC20(mim_); + collateral = ERC20(collateral_); + oracleData = IKashiPair(lendingPair_).oracleData(); + + feePool = feePool_; + maxDiscount = maxDiscount_; + + ERC20(mim_).approve(lendingPair_, uint(-1)); // TODO - is it needed? + + // TODO - can we support only 18 decimals collateral? it depends on the price feed + //require(ERC20(collateral_).decimals() == 18, "only 18 decimals collaterals are supported"); + } + + function setParams(uint _A, uint _fee, uint _callerFee) external onlyOwner { + require(_fee <= MAX_FEE, "setParams: fee is too big"); + require(_callerFee <= MAX_CALLER_FEE, "setParams: caller fee is too big"); + require(_A >= MIN_A, "setParams: A too small"); + require(_A <= MAX_A, "setParams: A too big"); + + fee = _fee; + callerFee = _callerFee; + A = _A; + + emit ParamsSet(_A, _fee, _callerFee); + } + + function fetchPrice() public returns(uint) { + (bool succ, uint rate) = oracle.get(oracleData); + + if(succ) return 0; + else return rate; + } + + function peekPrice() public view returns(uint) { + (bool succ, uint rate) = oracle.peek(oracleData); + + if(succ) return 0; + else return rate; + } + + function deposit(uint wad) external { + // update share + uint usdValue = mim.balanceOf(address(this)); + uint gemValue = collateral.balanceOf(address(this)); + + uint price = fetchPrice(); + require(gemValue == 0 || price > 0, "deposit: feed is down"); + + uint totalValue = usdValue.add(gemValue.mul(price) / PRECISION); + + // this is in theory not reachable. if it is, better halt deposits + // the condition is equivalent to: (totalValue = 0) ==> (total = 0) + require(totalValue > 0 || totalSupply == 0, "deposit: system is rekt"); + + uint newShare = PRECISION; + if(totalSupply > 0) newShare = wad.mul(totalSupply) / totalValue; + + totalSupply = totalSupply.add(newShare); + balanceOf[msg.sender] = balanceOf[msg.sender].add(newShare); + + // deposit the wad + require(mim.transferFrom(msg.sender, address(this), wad), "deposit: transferFrom failed"); + + emit Transfer(address(0), msg.sender, newShare); + emit UserDeposit(msg.sender, wad, newShare); + } + + function withdraw(uint numShares) external { + require(balanceOf[msg.sender] >= numShares, "withdraw: insufficient balance"); + + uint usdValue = mim.balanceOf(address(this)); + uint gemValue = collateral.balanceOf(address(this)); + + uint usdAmount = usdValue.mul(numShares) / totalSupply; + uint gemAmount = gemValue.mul(numShares) / totalSupply; + + require(mim.transfer(msg.sender, usdAmount), "withdraw: transfer failed"); + + if(gemAmount > 0) { + require(collateral.transfer(msg.sender, gemAmount), "withdraw: transfer failed"); + } + + balanceOf[msg.sender] = balanceOf[msg.sender].sub(numShares); + totalSupply = totalSupply.sub(numShares); + + emit Transfer(msg.sender, address(0), numShares); + emit UserWithdraw(msg.sender, usdAmount, gemAmount, numShares); + } + + function addBps(uint n, int bps) internal pure returns(uint) { + require(bps <= 10000, "reduceBps: bps exceeds max"); + require(bps >= -10000, "reduceBps: bps exceeds min"); + + return n.mul(uint(10000 + bps)) / 10000; + } + + function getSwapGemAmount(uint wad) external view returns(uint gemAmount) { + uint oraclePrice = peekPrice(); + return getSwapGemAmount(wad, oraclePrice); + } + + function getSwapGemAmount(uint wad, uint gem2usdPrice) internal view returns(uint gemAmount) { + uint usdBalance = mim.balanceOf(address(this)); + uint gemBalance = collateral.balanceOf(address(this)); + + if(gem2usdPrice == 0) return (0); // feed is down + + uint gemUsdValue = gemBalance.mul(gem2usdPrice) / PRECISION; + uint maxReturn = addBps(wad.mul(PRECISION) / gem2usdPrice, int(maxDiscount)); + + uint xQty = wad; + uint xBalance = usdBalance; + uint yBalance = usdBalance.add(gemUsdValue.mul(2)); + + uint usdReturn = getReturn(xQty, xBalance, yBalance, A); + uint basicGemReturn = usdReturn.mul(PRECISION) / gem2usdPrice; + + if(gemBalance < basicGemReturn) basicGemReturn = gemBalance; // cannot give more than balance + if(maxReturn < basicGemReturn) basicGemReturn = maxReturn; + + gemAmount = basicGemReturn; + } + + // get gem in return to mim + function swap(uint wad, uint minGemReturn, address dest) public returns(uint) { + uint oraclePrice = fetchPrice(); + uint gemAmount = getSwapGemAmount(wad, oraclePrice); + + require(gemAmount >= minGemReturn, "swap: low return"); + + require(mim.transferFrom(msg.sender, address(this), wad), "swap/transferFrom-failed"); + + uint feeWad = (addBps(wad, int(fee))).sub(wad); + if(feeWad > 0) require(mim.transfer(feePool, feeWad), "swap/transfer-failed"); + + require(collateral.transfer(dest, gemAmount), "swap: transfer failed"); + + emit RebalanceSwap(msg.sender, wad, gemAmount, now); + + return gemAmount; + } + + function liquidate( + address[] calldata users, + uint256[] calldata maxBorrowParts, + address to, + ISwapper swapper, + bool open + ) public + returns(uint mimBalanceBefore, uint mimBalanceAfter) + { + mimBalanceBefore = mim.balanceOf(address(this)); + lendingPair.liquidate(users, maxBorrowParts, to, swapper, open); + mimBalanceAfter = mim.balanceOf(address(this)); + + uint callerReward = mimBalanceBefore.sub(mimBalanceAfter).mul(callerFee) / 10000; + + if(mimBalanceAfter >= callerReward) { + require(mim.transfer(msg.sender, callerReward), "liquidate: transfer failed"); + mimBalanceAfter = mimBalanceAfter.sub(callerReward); + } + } + + function liquidateLikeBoomer( + uint extraMim, + address[] calldata users, + uint256[] calldata maxBorrowParts, + address to, + ISwapper swapper, + bool open + ) public { + // take the mim + require(mim.transferFrom(msg.sender, address(this), extraMim), "liquidateLikeBoomer: transferFrom failed"); + + uint collatBalanceBefore = collateral.balanceOf(address(this)); + + // do the liquidation + (uint mimBalanceBefore, uint mimBalanceAfter) = liquidate(users, maxBorrowParts, to, swapper, open); + + uint collatBalanceAfter = collateral.balanceOf(address(this)); + + if(extraMim <= mimBalanceAfter) { + // boomer liquidation was not needed. just return the money + require(mim.transfer(msg.sender, extraMim), "liquidateLikeBoomer: transfer failed"); + return; + } + + // send mim leftover to liquidator + if(mimBalanceBefore.sub(mimBalanceAfter) >= extraMim) { + uint returnAmount = mimBalanceBefore.sub(mimBalanceAfter).sub(extraMim); + require(mim.transfer(msg.sender, returnAmount), "liquidateLikeBoomer: transfer failed"); + } + + // send collateral to liquidator + require(collateral.transfer(msg.sender, collatBalanceAfter.sub(collatBalanceBefore)), "liquidateLikeBoomer: transfer failed"); + } + +} \ No newline at end of file diff --git a/contracts/B.Protocol/PriceFormula.sol b/contracts/B.Protocol/PriceFormula.sol new file mode 100644 index 0000000..f5d3375 --- /dev/null +++ b/contracts/B.Protocol/PriceFormula.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.6.12; + +import "@boringcrypto/boring-solidity/contracts/libraries/BoringMath.sol"; + +contract PriceFormula { + using BoringMath for uint256; + + function getSumFixedPoint(uint x, uint y, uint A) public pure returns(uint) { + if(x == 0 && y == 0) return 0; + + uint sum = x.add(y); + + for(uint i = 0 ; i < 255 ; i++) { + uint dP = sum; + dP = dP.mul(sum) / (x.mul(2)).add(1); + dP = dP.mul(sum) / (y.mul(2)).add(1); + + uint prevSum = sum; + + uint n = (A.mul(2).mul(x.add(y)).add(dP.mul(2))).mul(sum); + uint d = (A.mul(2).sub(1).mul(sum)); + sum = n / d.add(dP.mul(3)); + + if(sum <= prevSum.add(1) && prevSum <= sum.add(1)) break; + } + + return sum; + } + + function getReturn(uint xQty, uint xBalance, uint yBalance, uint A) public pure returns(uint) { + uint sum = getSumFixedPoint(xBalance, yBalance, A); + + uint c = sum.mul(sum) / (xQty.add(xBalance)).mul(2); + c = c.mul(sum) / A.mul(4); + uint b = (xQty.add(xBalance)).add(sum / A.mul(2)); + uint yPrev = 0; + uint y = sum; + + for(uint i = 0 ; i < 255 ; i++) { + yPrev = y; + uint n = (y.mul(y)).add(c); + uint d = y.mul(2).add(b).sub(sum); + y = n / d; + + if(y <= yPrev.add(1) && yPrev <= y.add(1)) break; + } + + return yBalance.sub(y).sub(1); + } +} \ No newline at end of file diff --git a/test/BProtocol.js b/test/BProtocol.js new file mode 100644 index 0000000..81f5c06 --- /dev/null +++ b/test/BProtocol.js @@ -0,0 +1,854 @@ +const { ethers, deployments } = require("hardhat") +const { expect, assert } = require("chai") +const { + getBigNumber, + sansBorrowFee, + sansSafetyAmount, + advanceBlock, + advanceTime, + KashiPairPermit, + setMasterContractApproval, + setKashiPairContractApproval, + createFixture, + ADDRESS_ZERO, + BKashiPair, + advanceTimeAndBlock, +} = require("@sushiswap/hardhat-framework") +const { defaultAbiCoder } = require("ethers/lib/utils") + +let cmd, fixture + +async function debugInfo(thisObject) { + console.log("Alice Collateral in Bento", (await thisObject.bentoBox.balanceOf(thisObject.a.address, thisObject.alice.address)).toString()) + console.log("Bob Collateral in Bento", (await thisObject.bentoBox.balanceOf(thisObject.a.address, thisObject.bob.address)).toString()) + console.log( + "Swapper Collateral in Bento", + (await thisObject.bentoBox.balanceOf(thisObject.a.address, thisObject.swapper.address)).toString() + ) + console.log("Bento Collateral in Bento", (await thisObject.bentoBox.balanceOf(thisObject.a.address, thisObject.bentoBox.address)).toString()) + console.log( + "Pair Collateral in Bento", + (await thisObject.bentoBox.balanceOf(thisObject.a.address, thisObject.pairHelper.contract.address)).toString() + ) + console.log() + console.log("Alice Asset in Bento", (await thisObject.bentoBox.balanceOf(thisObject.b.address, thisObject.alice.address)).toString()) + console.log("Bob Asset in Bento", (await thisObject.bentoBox.balanceOf(thisObject.b.address, thisObject.bob.address)).toString()) + console.log("Swapper Asset in Bento", (await thisObject.bentoBox.balanceOf(thisObject.b.address, thisObject.swapper.address)).toString()) + console.log("Bento Asset in Bento", (await thisObject.bentoBox.balanceOf(thisObject.b.address, thisObject.bentoBox.address)).toString()) + console.log( + "Pair Asset in Bento", + (await thisObject.bentoBox.balanceOf(thisObject.b.address, thisObject.pairHelper.contract.address)).toString() + ) + console.log() + console.log("Alice CollateralShare in Pair", (await thisObject.pairHelper.contract.userCollateralShare(thisObject.alice.address)).toString()) + console.log("Alice BorrowPart in Pair", (await thisObject.pairHelper.contract.userBorrowPart(thisObject.alice.address)).toString()) + console.log("Alice Solvent", (await thisObject.pairHelper.contract.isSolvent(thisObject.alice.address, false)).toString()) +} + +describe("KashiPair Basic", function () { + before(async function () { + fixture = await createFixture(deployments, this, async (cmd) => { + await cmd.deploy("weth9", "WETH9Mock") + await cmd.deploy("bentoBox", "BentoBoxMock", this.weth9.address) + + await cmd.addToken("a", "Token A", "A", 18, this.ReturnFalseERC20Mock) + await cmd.addToken("b", "Token B", "B", 8, this.RevertingERC20Mock) + await cmd.addPair("sushiSwapPair", this.a, this.b, 50000, 50000) + + await cmd.deploy("strategy", "SimpleStrategyMock", this.bentoBox.address, this.a.address) + + await this.bentoBox.setStrategy(this.a.address, this.strategy.address) + await advanceTime(1209600, ethers) + await this.bentoBox.setStrategy(this.a.address, this.strategy.address) + await this.bentoBox.setStrategyTargetPercentage(this.a.address, 20) + + await cmd.deploy("erc20", "ERC20Mock", 10000000) + await cmd.deploy("BkashiPair", "BKashiPairMock", this.bentoBox.address) + await cmd.deploy("oracle", "OracleMock") + await cmd.deploy("swapper", "SushiSwapSwapper", this.bentoBox.address, this.factory.address, await this.factory.pairCodeHash()) + await this.BkashiPair.setSwapper(this.swapper.address, true) + await this.BkashiPair.setFeeTo(this.alice.address) + + await this.oracle.set(getBigNumber(1, 28)) + const oracleData = await this.oracle.getDataParameter() + + await cmd.addKashiPair("pairHelper", this.bentoBox, this.BkashiPair, this.a, this.b, this.oracle, oracleData) + + // Two different ways to approve the kashiPair + await setMasterContractApproval(this.bentoBox, this.alice, this.alice, this.alicePrivateKey, this.BkashiPair.address, true) + await setMasterContractApproval(this.bentoBox, this.bob, this.bob, this.bobPrivateKey, this.BkashiPair.address, true) + + await this.a.connect(this.fred).approve(this.bentoBox.address, getBigNumber(130)) + await expect(this.bentoBox.connect(this.fred).deposit(this.a.address, this.fred.address, this.fred.address, getBigNumber(100), 0)) + .to.emit(this.a, "Transfer") + .withArgs(this.fred.address, this.bentoBox.address, getBigNumber(100)) + .to.emit(this.bentoBox, "LogDeposit") + .withArgs(this.a.address, this.fred.address, this.fred.address, getBigNumber(100), getBigNumber(100)) + + await this.bentoBox.connect(this.fred).addProfit(this.a.address, getBigNumber(30)) + + await this.b.connect(this.fred).approve(this.bentoBox.address, getBigNumber(400, 8)) + await expect(this.bentoBox.connect(this.fred).deposit(this.b.address, this.fred.address, this.fred.address, getBigNumber(200, 8), 0)) + .to.emit(this.b, "Transfer") + .withArgs(this.fred.address, this.bentoBox.address, getBigNumber(200, 8)) + .to.emit(this.bentoBox, "LogDeposit") + .withArgs(this.b.address, this.fred.address, this.fred.address, getBigNumber(200, 8), getBigNumber(200, 8)) + + await this.bentoBox.connect(this.fred).addProfit(this.b.address, getBigNumber(200, 8)) + }) + }) + + beforeEach(async function () { + cmd = await fixture() + }) + + describe("Deployment", function () { + it("Assigns a name", async function () { + expect(await this.pairHelper.contract.name()).to.be.equal("Kashi Medium Risk Token A/Token B-Test") + }) + it("Assigns a symbol", async function () { + expect(await this.pairHelper.contract.symbol()).to.be.equal("kmA/B-TEST") + }) + + it("Assigns decimals", async function () { + expect(await this.pairHelper.contract.decimals()).to.be.equal(8) + }) + + it("totalSupply is reachable", async function () { + expect(await this.pairHelper.contract.totalSupply()).to.be.equal(0) + }) + }) + + describe("Init", function () { + it("Reverts init for collateral address 0", async function () { + const oracleData = await this.oracle.getDataParameter() + await expect( + cmd.addKashiPair("pairHelper", this.bentoBox, this.BkashiPair, ADDRESS_ZERO, this.b, this.oracle, oracleData) + ).to.be.revertedWith("KashiPair: bad pair") + }) + + it("Reverts init for initilised pair", async function () { + await expect(this.pairHelper.contract.init(this.pairHelper.initData)).to.be.revertedWith("KashiPair: already initialized") + }) + }) + + describe("Permit", function () { + it("should allow permit", async function () { + const nonce = await this.a.nonces(this.alice.address) + const deadline = (await this.alice.provider._internalBlockNumber).respTime + 10000 + await this.pairHelper.tokenPermit(this.a, this.alice, this.alicePrivateKey, 1, nonce, deadline) + }) + }) + + describe("Accrue", function () { + it("should take else path if accrue is called within same block", async function () { + await this.pairHelper.contract.accrueTwice() + }) + + it("should update the interest rate according to utilization", async function () { + await this.pairHelper.run((cmd) => [ + cmd.approveAsset(getBigNumber(700, 8)), + cmd.depositAsset(getBigNumber(290, 8)), + cmd.approveCollateral(getBigNumber(800)), + cmd.depositCollateral(getBigNumber(100)), + cmd.do(this.pairHelper.contract.borrow, this.alice.address, sansBorrowFee(getBigNumber(75, 8))), + cmd.do(this.pairHelper.contract.accrue), + cmd.do(this.oracle.set, "1100000000000000000"), + cmd.do(this.pairHelper.contract.updateExchangeRate), + ]) + + let borrowPartLeft = await this.pairHelper.contract.userBorrowPart(this.alice.address) + let collateralLeft = await this.pairHelper.contract.userCollateralShare(this.alice.address) + await this.pairHelper.run((cmd) => [cmd.repay(borrowPartLeft.sub(getBigNumber(1, 6)))]) + borrowPartLeft = await this.pairHelper.contract.userBorrowPart(this.alice.address) + + // run for a while with 0 utilization + let rate1 = (await this.pairHelper.contract.accrueInfo()).interestPerSecond + for (let i = 0; i < 20; i++) { + await advanceBlock(ethers) + } + await this.pairHelper.contract.accrue() + + // check results + let rate2 = (await this.pairHelper.contract.accrueInfo()).interestPerSecond + assert(rate2.lt(rate1), "rate has not adjusted down with low utilization") + + // then increase utilization to 90% + await this.pairHelper.run((cmd) => [ + cmd.depositCollateral(getBigNumber(400)), + cmd.do(this.pairHelper.contract.borrow, this.alice.address, sansBorrowFee(getBigNumber(270, 8))), + ]) + + // and run a while again + rate1 = (await this.pairHelper.contract.accrueInfo()).interestPerSecond + for (let i = 0; i < 20; i++) { + await advanceBlock(ethers) + } + + // check results + await this.pairHelper.contract.accrue() + rate2 = (await this.pairHelper.contract.accrueInfo()).interestPerSecond + expect(rate2).to.be.gt(rate1) + }) + + it("should reset interest rate if no more assets are available", async function () { + await this.pairHelper.run((cmd) => [ + cmd.approveAsset(getBigNumber(900, 8)), + cmd.depositAsset(getBigNumber(290, 8)), + cmd.approveCollateral(getBigNumber(200)), + cmd.depositCollateral(getBigNumber(100)), + cmd.do(this.pairHelper.contract.borrow, this.alice.address, sansBorrowFee(getBigNumber(75, 8))), + cmd.do(this.pairHelper.contract.accrue), + ]) + let borrowPartLeft = await this.pairHelper.contract.userBorrowPart(this.alice.address) + let balanceLeft = await this.pairHelper.contract.balanceOf(this.alice.address) + await this.pairHelper.run((cmd) => [cmd.repay(borrowPartLeft), cmd.do(this.pairHelper.contract.accrue)]) + expect((await this.pairHelper.contract.accrueInfo()).interestPerSecond).to.be.equal(317097920) + }) + + it("should lock interest rate at minimum", async function () { + let totalBorrowBefore = (await this.pairHelper.contract.totalBorrow()).amount + await this.pairHelper.run((cmd) => [ + cmd.approveAsset(getBigNumber(900, 8)), + cmd.depositAsset(getBigNumber(100, 8)), + cmd.approveCollateral(getBigNumber(200)), + cmd.depositCollateral(getBigNumber(100)), + cmd.do(this.pairHelper.contract.borrow, this.alice.address, 1), + cmd.do(this.pairHelper.contract.accrue), + ]) + await advanceTimeAndBlock(30000, ethers) + await this.pairHelper.contract.accrue() + await advanceTimeAndBlock(30000, ethers) + await this.pairHelper.contract.accrue() + + expect((await this.pairHelper.contract.accrueInfo()).interestPerSecond).to.be.equal(79274480) + }) + + it("should lock interest rate at maximum", async function () { + await this.pairHelper.run((cmd) => [ + cmd.approveAsset(getBigNumber(900, 8)), + cmd.depositAsset(getBigNumber(100, 8)), + cmd.approveCollateral(getBigNumber(300)), + cmd.depositCollateral(getBigNumber(300)), + cmd.do(this.pairHelper.contract.borrow, this.alice.address, sansBorrowFee(getBigNumber(100, 8))), + cmd.do(this.pairHelper.contract.accrue), + ]) + await this.pairHelper.contract.accrue() + await advanceTimeAndBlock(30000, ethers) + await this.pairHelper.contract.accrue() + await advanceTimeAndBlock(1500000, ethers) + await this.pairHelper.contract.accrue() + await advanceTimeAndBlock(1500000, ethers) + await this.pairHelper.contract.accrue() + + expect((await this.pairHelper.contract.accrueInfo()).interestPerSecond).to.be.equal(317097920000) + }) + + it("should emit Accrue if on target utilization", async function () { + await this.pairHelper.run((cmd) => [ + cmd.approveAsset(getBigNumber(900, 8)), + cmd.depositAsset(getBigNumber(100, 8)), + cmd.approveCollateral(getBigNumber(100)), + cmd.depositCollateral(getBigNumber(100)), + cmd.do(this.pairHelper.contract.borrow, this.alice.address, sansBorrowFee(getBigNumber(75, 8))), + ]) + await expect(this.pairHelper.contract.accrue()).to.emit(this.pairHelper.contract, "LogAccrue") + }) + }) + + describe("Is Solvent", function () { + // + }) + + describe("Update Exchange Rate", async function () { + it("should update exchange rate", async function () { + const ACTION_UPDATE_EXCHANGE_RATE = 11 + await this.pairHelper.contract.cook( + [ACTION_UPDATE_EXCHANGE_RATE], + [0], + [defaultAbiCoder.encode(["bool", "uint256", "uint256"], [true, 0, 0])] + ) + }) + }) + + describe("Add Asset", function () { + it("should add asset with skim", async function () { + await this.b.approve(this.bentoBox.address, getBigNumber(2, 8)) + await this.bentoBox.deposit(this.b.address, this.alice.address, this.alice.address, 0, getBigNumber(1, 8)) + await this.bentoBox.transfer(this.b.address, this.alice.address, this.pairHelper.contract.address, getBigNumber(1, 8)) + await this.pairHelper.run((cmd) => [cmd.do(this.pairHelper.contract.addAsset, this.alice.address, true, getBigNumber(1, 8))]) + expect(await this.pairHelper.contract.balanceOf(this.alice.address)).to.be.equal(getBigNumber(1, 8)) + }) + + it("should revert when trying to skim too much", async function () { + await this.b.approve(this.bentoBox.address, getBigNumber(2)) + await this.bentoBox.deposit(this.b.address, this.alice.address, this.alice.address, 0, getBigNumber(1, 8)) + await this.bentoBox.transfer(this.b.address, this.alice.address, this.pairHelper.contract.address, getBigNumber(1, 8)) + await expect( + this.pairHelper.run((cmd) => [cmd.do(this.pairHelper.contract.addAsset, this.alice.address, true, getBigNumber(2, 8))]) + ).to.be.revertedWith("KashiPair: Skim too much") + }) + + it("should revert if MasterContract is not approved", async function () { + await this.b.connect(this.carol).approve(this.bentoBox.address, 300) + await expect((await this.pairHelper.as(this.carol)).depositAsset(290)).to.be.revertedWith("BentoBox: Transfer not approved") + }) + + it("should take a deposit of assets from BentoBox", async function () { + await this.pairHelper.run((cmd) => [cmd.approveAsset(3000), cmd.depositAsset(3000)]) + expect(await this.pairHelper.contract.balanceOf(this.alice.address)).to.be.equal(1500) + }) + + it("should emit correct event on adding asset", async function () { + await this.b.approve(this.bentoBox.address, 3000) + await expect(this.pairHelper.depositAsset(2900)) + .to.emit(this.pairHelper.contract, "LogAddAsset") + .withArgs(this.alice.address, this.alice.address, 1450, 1450) + }) + }) + + describe("Remove Asset", function () { + it("should not allow a remove without assets", async function () { + await expect(this.pairHelper.withdrawAsset(1)).to.be.reverted + }) + + it("should allow to remove assets", async function () { + let bobHelper = await this.pairHelper.as(this.bob) + await bobHelper.run((cmd) => [cmd.approveAsset(getBigNumber(200, 8)), cmd.depositAsset(getBigNumber(200, 8))]) + expect(await this.pairHelper.contract.balanceOf(this.bob.address)).to.be.equal(getBigNumber(100, 8)) + await this.pairHelper.run((cmd) => [ + cmd.approveAsset(getBigNumber(200, 8)), + cmd.depositAsset(getBigNumber(200, 8)), + cmd.withdrawAsset(getBigNumber(100, 8)), + ]) + }) + }) + + describe("Add Collateral", function () { + it("should take a deposit of collateral", async function () { + await this.a.approve(this.bentoBox.address, 300) + await expect(this.pairHelper.depositCollateral(290)) + .to.emit(this.pairHelper.contract, "LogAddCollateral") + .withArgs(this.alice.address, this.alice.address, 223) + }) + }) + + describe("Remove Collateral", function () { + it("should not allow a remove without collateral", async function () { + await expect(this.pairHelper.withdrawCollateral(this.alice.address, 1)).to.be.revertedWith("BoringMath: Underflow") + }) + + it("should allow a direct removal of collateral", async function () { + await this.pairHelper.run((cmd) => [ + cmd.approveCollateral(getBigNumber(100)), + cmd.depositCollateral(getBigNumber(100)), + cmd.do(this.pairHelper.contract.removeCollateral, this.alice.address, getBigNumber(50)), + ]) + expect(await this.bentoBox.balanceOf(this.a.address, this.alice.address)).to.be.equal(getBigNumber(50)) + }) + + it("should not allow a remove of collateral if user is insolvent", async function () { + await this.pairHelper.run((cmd) => [ + cmd.approveAsset(getBigNumber(300, 8)), + cmd.depositAsset(getBigNumber(290, 8)), + cmd.approveCollateral(getBigNumber(100)), + cmd.depositCollateral(getBigNumber(100)), + cmd.do(this.pairHelper.contract.borrow, this.alice.address, sansBorrowFee(getBigNumber(75, 8))), + cmd.do(this.pairHelper.contract.accrue), + ]) + + await expect(this.pairHelper.withdrawCollateral(getBigNumber(1, 0))).to.be.revertedWith("KashiPair: user insolvent") + }) + + it("should allow to partial withdrawal of collateral", async function () { + await this.pairHelper.run((cmd) => [ + cmd.approveAsset(getBigNumber(700, 8)), + cmd.depositAsset(getBigNumber(290, 8)), + cmd.approveCollateral(getBigNumber(100)), + cmd.depositCollateral(getBigNumber(100)), + cmd.do(this.pairHelper.contract.borrow, this.alice.address, sansBorrowFee(getBigNumber(75, 8))), + cmd.do(this.pairHelper.contract.accrue), + cmd.do(this.oracle.set, "11000000000000000000000000000"), + cmd.do(this.pairHelper.contract.updateExchangeRate), + ]) + let borrowPartLeft = await this.pairHelper.contract.userBorrowPart(this.alice.address) + await this.pairHelper.run((cmd) => [cmd.repay(borrowPartLeft), cmd.withdrawCollateral(getBigNumber(50))]) + }) + + it("should allow to full withdrawal of collateral", async function () { + await this.pairHelper.run((cmd) => [ + cmd.approveAsset(getBigNumber(700, 8)), + cmd.depositAsset(getBigNumber(290, 8)), + cmd.approveCollateral(getBigNumber(100)), + cmd.depositCollateral(getBigNumber(100)), + cmd.do(this.pairHelper.contract.borrow, this.alice.address, sansBorrowFee(getBigNumber(75, 8))), + cmd.do(this.pairHelper.contract.accrue), + cmd.do(this.oracle.set, "11000000000000000000000000000"), + cmd.do(this.pairHelper.contract.updateExchangeRate), + ]) + let borrowPartLeft = await this.pairHelper.contract.userBorrowPart(this.alice.address) + await this.pairHelper.repay(borrowPartLeft) + let collateralLeft = await this.pairHelper.contract.userCollateralShare(this.alice.address) + await this.pairHelper.withdrawCollateral(sansSafetyAmount(collateralLeft)) + }) + }) + + describe("Borrow", function () { + it("should not allow borrowing without any assets", async function () { + await expect(this.pairHelper.contract.borrow(this.alice.address, 10000)).to.be.revertedWith("Kashi: below minimum") + await expect(this.pairHelper.contract.borrow(this.alice.address, 1)).to.be.revertedWith("Kashi: below minimum") + }) + + it("should not allow borrowing without any collateral", async function () { + await this.b.approve(this.bentoBox.address, 300) + await await this.pairHelper.depositAsset(290) + await expect(this.pairHelper.contract.borrow(this.alice.address, 1)).to.be.revertedWith("Kashi: below minimum") + }) + + it("should allow borrowing with collateral up to 75%", async function () { + await this.pairHelper.run((cmd) => [ + cmd.as(this.bob).approveAsset(getBigNumber(300, 8)), + cmd.as(this.bob).depositAsset(getBigNumber(290, 8)), + cmd.approveCollateral(getBigNumber(100)), + cmd.depositCollateral(getBigNumber(100)), + ]) + await expect(this.pairHelper.contract.borrow(this.alice.address, sansBorrowFee(getBigNumber(75, 8)))) + .to.emit(this.pairHelper.contract, "LogBorrow") + .withArgs(this.alice.address, this.alice.address, "7496251874", "3748125", "7499999999") + }) + + it("should allow borrowing to other with correct borrowPart", async function () { + await this.pairHelper.run((cmd) => [ + cmd.as(this.bob).approveAsset(getBigNumber(300, 8)), + cmd.as(this.bob).depositAsset(getBigNumber(290, 8)), + cmd.approveCollateral(getBigNumber(100)), + cmd.depositCollateral(getBigNumber(100)), + ]) + await expect(this.pairHelper.contract.borrow(this.bob.address, sansBorrowFee(getBigNumber(75, 8)))) + .to.emit(this.pairHelper.contract, "LogBorrow") + .withArgs(this.alice.address, this.bob.address, "7496251874", "3748125", "7499999999") + expect(await this.pairHelper.contract.userBorrowPart(this.alice.address)).to.be.equal("7499999999") + expect(await this.pairHelper.contract.userBorrowPart(this.bob.address)).to.be.equal("0") + }) + + it("should not allow any more borrowing", async function () { + await this.pairHelper.run((cmd) => [ + cmd.approveAsset(getBigNumber(300, 8)), + cmd.depositAsset(getBigNumber(290, 8)), + cmd.approveCollateral(getBigNumber(100)), + cmd.depositCollateral(getBigNumber(100)), + ]) + await this.pairHelper.contract.borrow(this.alice.address, sansBorrowFee(getBigNumber(75, 8))) + await expect(this.pairHelper.contract.borrow(this.alice.address, 1)).to.be.revertedWith("user insolvent") + }) + + /*it("should report insolvency due to interest", async function () { + await this.pairHelper.run((cmd) => [ + cmd.approveAsset(getBigNumber(300, 8)), + cmd.depositAsset(getBigNumber(290, 8)), + cmd.approveCollateral(getBigNumber(100)), + cmd.depositCollateral(getBigNumber(100)), + cmd.do(this.pairHelper.contract.borrow, this.alice.address, sansBorrowFee(getBigNumber(75, 8))), + cmd.do(this.pairHelper.contract.accrue), + ]) + expect(await this.pairHelper.contract.isSolvent(this.alice.address, false)).to.be.false + })*/ + }) + + describe("Repay", function () { + it("should allow to repay", async function () { + await this.pairHelper.run((cmd) => [ + cmd.approveAsset(getBigNumber(700, 8)), + cmd.depositAsset(getBigNumber(290, 8)), + cmd.approveCollateral(getBigNumber(100)), + cmd.depositCollateral(getBigNumber(100)), + cmd.borrow(sansBorrowFee(getBigNumber(75, 8))), + cmd.accrue(), + cmd.do(this.oracle.set, "11000000000000000000000000000"), + cmd.updateExchangeRate(), + cmd.repay(getBigNumber(30, 8)), + ]) + }) + + it("should allow to repay from BentoBox", async function () { + await this.pairHelper.run((cmd) => [ + cmd.approveAsset(getBigNumber(700, 8)), + cmd.depositAsset(getBigNumber(290, 8)), + cmd.approveCollateral(getBigNumber(100)), + cmd.depositCollateral(getBigNumber(100)), + cmd.borrow(sansBorrowFee(getBigNumber(75, 8))), + cmd.do(this.bentoBox.deposit, this.b.address, this.alice.address, this.alice.address, getBigNumber(70, 8), 0), + cmd.do(this.pairHelper.contract.repay, this.alice.address, false, getBigNumber(50, 8)), + ]) + }) + + it("should allow full repayment", async function () { + await this.pairHelper.run((cmd) => [ + cmd.approveAsset(getBigNumber(900, 8)), + cmd.approveCollateral(getBigNumber(100)), + cmd.depositAsset(getBigNumber(290, 8)), + cmd.depositCollateral(getBigNumber(100)), + cmd.borrow(sansBorrowFee(getBigNumber(75, 8))), + cmd.accrue(), + cmd.do(this.oracle.set, "11000000000000000000000000000"), + cmd.updateExchangeRate(), + ]) + + let part = await this.pairHelper.contract.userBorrowPart(this.alice.address) + + await this.pairHelper.run((cmd) => [cmd.repay(part)]) + }) + }) + + describe("Short", function () { + it("should not allow shorting if it does not return enough", async function () { + await expect( + this.pairHelper.run((cmd) => [ + cmd.as(this.bob).approveAsset(getBigNumber(1000, 8)), + cmd.as(this.bob).depositAsset(getBigNumber(1000, 8)), + cmd.approveCollateral(getBigNumber(100)), + cmd.depositCollateral(getBigNumber(100)), + cmd.short(this.swapper, getBigNumber(200, 8), getBigNumber(200)), + ]) + ).to.be.revertedWith("KashiPair: call failed") + }) + + it("should not allow shorting into insolvency", async function () { + await expect( + this.pairHelper.run((cmd) => [ + // Bob adds 1000 asset (amount) + cmd.as(this.bob).approveAsset(getBigNumber(1000, 8)), + cmd.as(this.bob).depositAsset(getBigNumber(1000, 8)), + // Alice adds 100 collateral (amount) + cmd.approveCollateral(getBigNumber(100)), + cmd.depositCollateral(getBigNumber(100)), + // Alice shorts by borrowing 500 assets shares for at least 50 shares collateral + cmd.short(this.swapper, getBigNumber(400, 8), getBigNumber(50)), + ]) + ).to.be.revertedWith("KashiPair: user insolvent") + }) + + it("should allow shorting", async function () { + await this.pairHelper.run((cmd) => [ + cmd.as(this.bob).approveAsset(getBigNumber(1000, 8)), + cmd.as(this.bob).depositAsset(getBigNumber(1000, 8)), + cmd.approveCollateral(getBigNumber(100)), + cmd.depositCollateral(getBigNumber(100)), + cmd.short(this.swapper, getBigNumber(250, 8), getBigNumber(176)), + ]) + }) + + it("should limit asset availability after shorting", async function () { + // Alice adds 1 asset + // Bob adds 1000 asset + // Alice adds 100 collateral + // Alice borrows 250 asset and deposits 230+ collateral + await this.pairHelper.run((cmd) => [ + cmd.approveAsset(getBigNumber(1, 8)), + cmd.depositAsset(getBigNumber(1, 8)), // Just a minimum balance for the BentoBox + cmd.as(this.bob).approveAsset(getBigNumber(1000, 8)), + cmd.as(this.bob).depositAsset(getBigNumber(1000, 8)), + cmd.approveCollateral(getBigNumber(100)), + cmd.depositCollateral(getBigNumber(100)), + cmd.short(this.swapper, getBigNumber(250, 8), getBigNumber(176)), + ]) + + const bobBal = await this.pairHelper.contract.balanceOf(this.bob.address) + expect(bobBal).to.be.equal(getBigNumber(500, 8)) + // virtual balance of 1000 is higher than the contract has + await expect(this.pairHelper.as(this.bob).withdrawAsset(bobBal)).to.be.revertedWith("BoringMath: Underflow") + await expect(this.pairHelper.as(this.bob).withdrawAsset(getBigNumber(376, 8))).to.be.revertedWith("BoringMath: Underflow") + await this.pairHelper.as(this.bob).withdrawAsset(getBigNumber(375, 8)) + }) + }) + + describe("Unwind", function () { + it("should allow unwinding the short", async function () { + await this.pairHelper.run((cmd) => [ + cmd.as(this.bob).approveAsset(getBigNumber(1000, 8)), + cmd.as(this.bob).depositAsset(getBigNumber(1000, 8)), + cmd.approveCollateral(getBigNumber(100)), + cmd.depositCollateral(getBigNumber(100)), + cmd.short(this.swapper, getBigNumber(250, 8), getBigNumber(176)), + ]) + + const collateralShare = await this.pairHelper.contract.userCollateralShare(this.alice.address) + const borrowPart = await this.pairHelper.contract.userBorrowPart(this.alice.address) + + await this.pairHelper.run((cmd) => [cmd.unwind(this.swapper, borrowPart, collateralShare)]) + }) + }) + + describe("Cook", function () { + it("can add 2 values to a call and receive 1 value back", async function () { + const ACTION_BENTO_DEPOSIT = 20 + const ACTION_CALL = 30 + + await cmd.deploy("externalFunctionMock", "ExternalFunctionMock") + + let data = this.externalFunctionMock.interface.encodeFunctionData("sum", [10, 10]) + + await this.pairHelper.run((cmd) => [cmd.approveAsset(getBigNumber(100, 8))]) + + await expect( + this.pairHelper.contract.cook( + [ACTION_BENTO_DEPOSIT, ACTION_CALL, ACTION_BENTO_DEPOSIT], + [0, 0, 0], + [ + defaultAbiCoder.encode( + ["address", "address", "int256", "int256"], + [this.b.address, this.alice.address, getBigNumber(25, 8), 0] + ), + defaultAbiCoder.encode( + ["address", "bytes", "bool", "bool", "uint8"], + [this.externalFunctionMock.address, data.slice(0, -128), true, true, 1] + ), + defaultAbiCoder.encode(["address", "address", "int256", "int256"], [this.b.address, this.alice.address, -1, 0]), + ] + ) + ) + .to.emit(this.externalFunctionMock, "Result") + .withArgs(getBigNumber(375, 7)) + + // (25 / 2) + (37.5 / 2) = 31.25 + expect(await this.bentoBox.balanceOf(this.b.address, this.alice.address)).to.be.equal("3125000000") + }) + + it("reverts on a call to the BentoBox", async function () { + const ACTION_CALL = 30 + await expect( + this.pairHelper.contract.cook( + [ACTION_CALL], + [0], + [defaultAbiCoder.encode(["address", "bytes", "bool", "bool", "uint8"], [this.bentoBox.address, "0x", false, false, 0])] + ) + ).to.be.revertedWith("KashiPair: can't call") + }) + + it("takes else path", async function () { + await expect( + this.pairHelper.contract.cook( + [99], + [0], + [ + defaultAbiCoder.encode( + ["address", "address", "int256", "int256"], + [this.b.address, this.alice.address, getBigNumber(25), 0] + ), + ] + ) + ) + }) + + it("get repays part", async function () { + const ACTION_GET_REPAY_PART = 7 + await this.pairHelper.contract.cook([ACTION_GET_REPAY_PART], [0], [defaultAbiCoder.encode(["int256"], [1])]) + }) + + it("executed Bento transfer multiple", async function () { + await this.pairHelper.run((cmd) => [ + cmd.approveAsset(getBigNumber(100, 8)), + cmd.do(this.bentoBox.deposit, this.b.address, this.alice.address, this.alice.address, getBigNumber(70, 8), 0), + ]) + const ACTION_BENTO_TRANSFER_MULTIPLE = 23 + await this.pairHelper.contract.cook( + [ACTION_BENTO_TRANSFER_MULTIPLE], + [0], + [defaultAbiCoder.encode(["address", "address[]", "uint256[]"], [this.b.address, [this.carol.address], [getBigNumber(10, 8)]])] + ) + }) + + it("allows to addAsset with approval", async function () { + const nonce = await this.bentoBox.nonces(this.alice.address) + await expect( + await this.pairHelper.run((cmd) => [ + cmd.approveAsset(getBigNumber(100, 8)), + cmd.depositAssetWithApproval(getBigNumber(100, 8), this.BkashiPair, this.alicePrivateKey, nonce), + ]) + ) + }) + }) + + describe.only("Liquidate", function () { + it("should not allow open liquidate yet", async function () { + await this.pairHelper.run((cmd) => [ + cmd.as(this.bob).approveAsset(getBigNumber(310, 8)), + cmd.as(this.bob).depositAsset(getBigNumber(290, 8)), + cmd.approveCollateral(getBigNumber(100)), + cmd.depositCollateral(getBigNumber(100)), + cmd.borrow(sansBorrowFee(getBigNumber(75, 8))), + cmd.accrue(), + cmd.do(this.bentoBox.connect(this.bob).deposit, this.b.address, this.bob.address, this.bob.address, getBigNumber(20, 8), 0), + ]) + + await expect( + this.pairHelper.contract + .connect(this.bob) + .liquidate([this.alice.address], [getBigNumber(20, 8)], this.bob.address, "0x0000000000000000000000000000000000000000", true) + ).to.be.revertedWith("KashiPair: all are solvent") + }) + + it("should allow open liquidate", async function () { + await this.pairHelper.run((cmd) => [ + cmd.as(this.bob).approveAsset(getBigNumber(310, 8)), + cmd.as(this.bob).depositAsset(getBigNumber(290, 8)), + cmd.approveCollateral(getBigNumber(100)), + cmd.depositCollateral(getBigNumber(100)), + cmd.borrow(sansBorrowFee(getBigNumber(75, 8))), + cmd.accrue(), + cmd.do(this.oracle.set, "11000000000000000000000000000"), + cmd.updateExchangeRate(), + cmd.do(this.bentoBox.connect(this.bob).deposit, this.b.address, this.bob.address, this.bob.address, getBigNumber(20, 8), 0), + cmd.do(this.pairHelper.contract.connect(this.bob).removeAsset, this.bob.address, getBigNumber(50, 8)), + ]) + await this.pairHelper.contract + .connect(this.bob) + .liquidate([this.alice.address], [getBigNumber(20, 8)], this.bob.address, "0x0000000000000000000000000000000000000000", true) + }) + + it("should not allow liquidate for non b.protocol", async function () { + //await this.BkashiPair.setBProtocol(this.bob.address) + //console.log(this.BKashiPair) + + await this.pairHelper.contract.connect(this.alice).setBProtocolMock(this.alice.address) + + await this.pairHelper.run((cmd) => [ + cmd.as(this.bob).approveAsset(getBigNumber(310, 8)), + cmd.as(this.bob).depositAsset(getBigNumber(290, 8)), + cmd.approveCollateral(getBigNumber(100)), + cmd.depositCollateral(getBigNumber(100)), + cmd.borrow(sansBorrowFee(getBigNumber(75, 8))), + cmd.accrue(), + cmd.do(this.oracle.set, "11000000000000000000000000000"), + cmd.updateExchangeRate(), + cmd.do(this.bentoBox.connect(this.bob).deposit, this.b.address, this.bob.address, this.bob.address, getBigNumber(20, 8), 0), + cmd.do(this.pairHelper.contract.connect(this.bob).removeAsset, this.bob.address, getBigNumber(50, 8)), + ]) + await expect( + this.pairHelper.contract + .connect(this.bob) + .liquidate([this.alice.address], [getBigNumber(20, 8)], this.bob.address, "0x0000000000000000000000000000000000000000", true) + ).to.be.revertedWith("liquidate: not bprotocol") + }) + + it("should allow open liquidate with swapper", async function () { + await this.pairHelper.run((cmd) => [ + cmd.as(this.bob).approveAsset(getBigNumber(310, 8)), + cmd.as(this.bob).depositAsset(getBigNumber(290, 8)), + cmd.approveCollateral(getBigNumber(100)), + cmd.depositCollateral(getBigNumber(100)), + cmd.borrow(sansBorrowFee(getBigNumber(75, 8))), + cmd.accrue(), + cmd.do(this.oracle.set, "11000000000000000000000000000"), + cmd.updateExchangeRate(), + cmd.do(this.bentoBox.connect(this.bob).deposit, this.b.address, this.bob.address, this.bob.address, getBigNumber(20, 8), 0), + ]) + await expect( + this.pairHelper.contract + .connect(this.bob) + .liquidate([this.alice.address], [getBigNumber(20, 8)], this.swapper.address, this.swapper.address, true) + ) + .to.emit(this.pairHelper.contract, "LogRemoveCollateral") + .to.emit(this.pairHelper.contract, "LogRepay") + }) + + it("should allow closed liquidate", async function () { + await this.pairHelper.run((cmd) => [ + // Bob adds 290 asset amount (145 shares) + cmd.as(this.bob).approveAsset(getBigNumber(310, 8)), + cmd.as(this.bob).depositAsset(getBigNumber(290, 8)), + // Alice adds 100 collateral amount (76 shares) + cmd.approveCollateral(getBigNumber(100)), + cmd.depositCollateral(getBigNumber(100)), + // Alice borrows 75 asset amount + cmd.borrow(sansBorrowFee(getBigNumber(75, 8))), + cmd.accrue(), + // Change oracle to put Alice into insolvency + cmd.do(this.oracle.set, "11000000000000000000000000000"), + //cmd.do(this.a.transfer, this.sushiSwapPair.address, getBigNumber(500)), + //cmd.do(this.sushiSwapPair.sync), + cmd.updateExchangeRate(), + ]) + + // Bob liquidates Alice for 20 asset parts (approx 20 asset amount = 10 asset shares) + await this.pairHelper.contract + .connect(this.bob) + .liquidate([this.alice.address], [getBigNumber(20, 8)], this.swapper.address, this.swapper.address, false) + }) + + it("should not allow closed liquidate with invalid swapper", async function () { + await this.pairHelper.run((cmd) => [ + cmd.as(this.bob).approveAsset(getBigNumber(340, 8)), + cmd.as(this.bob).depositAsset(getBigNumber(290, 8)), + cmd.approveCollateral(getBigNumber(100)), + cmd.depositCollateral(getBigNumber(100)), + cmd.borrow(sansBorrowFee(getBigNumber(75, 8))), + cmd.accrue(), + ]) + + await cmd.deploy( + "invalidSwapper", + "SushiSwapSwapper", + this.bentoBox.address, + this.factory.address, + await this.factory.pairCodeHash() + ) + await expect( + this.pairHelper.contract + .connect(this.bob) + .liquidate([this.alice.address], [getBigNumber(20, 8)], this.invalidSwapper.address, this.invalidSwapper.address, false) + ).to.be.revertedWith("KashiPair: Invalid swapper") + }) + }) + + describe("Withdraw Fees", function () { + it("should allow to withdraw fees", async function () { + await this.pairHelper.run((cmd) => [ + cmd.approveAsset(getBigNumber(700, 8)), + cmd.depositAsset(getBigNumber(290, 8)), + cmd.approveCollateral(getBigNumber(100)), + cmd.depositCollateral(getBigNumber(100)), + cmd.borrow(sansBorrowFee(getBigNumber(75, 8))), + cmd.repay(getBigNumber(50, 8)), + ]) + await expect(this.pairHelper.contract.withdrawFees()).to.emit(this.pairHelper.contract, "LogWithdrawFees") + }) + + it("should emit events even if dev fees are empty", async function () { + await this.pairHelper.run((cmd) => [ + cmd.approveAsset(getBigNumber(900, 8)), + cmd.approveCollateral(getBigNumber(100)), + cmd.depositAsset(getBigNumber(290, 8)), + cmd.depositCollateral(getBigNumber(100)), + cmd.borrow(sansBorrowFee(getBigNumber(75, 8))), + cmd.accrue(), + cmd.do(this.oracle.set, "11000000000000000000000000000"), + cmd.updateExchangeRate(), + ]) + + let part = await this.pairHelper.contract.userBorrowPart(this.alice.address) + + await this.pairHelper.run((cmd) => [cmd.repay(part)]) + await this.pairHelper.contract.withdrawFees() + await expect(this.pairHelper.contract.withdrawFees()).to.emit(this.pairHelper.contract, "LogWithdrawFees") + }) + }) + + describe("Set Fee To", function () { + it("Mutates fee to", async function () { + console.log(this.BkashiPair.address, this.pairHelper.contract.address) + await this.BkashiPair.setFeeTo(this.bob.address) + expect(await this.BkashiPair.feeTo()).to.be.equal(this.bob.address) + expect(await this.pairHelper.contract.feeTo()).to.be.equal(ADDRESS_ZERO) + }) + + it("Emit LogFeeTo event if dev attempts to set fee to", async function () { + await expect(this.BkashiPair.setFeeTo(this.bob.address)).to.emit(this.BkashiPair, "LogFeeTo").withArgs(this.bob.address) + }) + + it("Reverts if non-owner attempts to set fee to", async function () { + await expect(this.BkashiPair.connect(this.bob).setFeeTo(this.bob.address)).to.be.revertedWith("caller is not the owner") + await expect(this.pairHelper.contract.connect(this.bob).setFeeTo(this.bob.address)).to.be.revertedWith("caller is not the owner") + }) + }) +}) From 125d7207ad262d67f0ad01adfbb711e028127209 Mon Sep 17 00:00:00 2001 From: yaron velner Date: Sun, 24 Oct 2021 13:38:16 +0300 Subject: [PATCH 03/24] first bamm test --- contracts/B.Protocol/BAMM.sol | 35 +++++++++++++++++++---------------- test/BProtocol.js | 33 ++++++++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 17 deletions(-) diff --git a/contracts/B.Protocol/BAMM.sol b/contracts/B.Protocol/BAMM.sol index c2ec54b..55b2a6a 100644 --- a/contracts/B.Protocol/BAMM.sol +++ b/contracts/B.Protocol/BAMM.sol @@ -5,13 +5,16 @@ pragma solidity >=0.6.12; import "./PriceFormula.sol"; import "@boringcrypto/boring-solidity/contracts/BoringOwnable.sol"; import "@boringcrypto/boring-solidity/contracts/ERC20.sol"; +import "@boringcrypto/boring-solidity/contracts/libraries/BoringERC20.sol"; import "./../interfaces/IOracle.sol"; import "./../interfaces/IKashiPair.sol"; // TODO the interface for abracadabra is a bit different contract BAMM is PriceFormula, BoringOwnable, ERC20 { + using BoringERC20 for IERC20; + IOracle public immutable oracle; - ERC20 public immutable mim; - ERC20 public immutable collateral; + IERC20 public immutable mim; + IERC20 public immutable collateral; IKashiPair public immutable lendingPair; bytes public oracleData; @@ -47,8 +50,8 @@ contract BAMM is PriceFormula, BoringOwnable, ERC20 { { lendingPair = IKashiPair(lendingPair_); oracle = IOracle(oracle_); - mim = ERC20(mim_); - collateral = ERC20(collateral_); + mim = IERC20(mim_); + collateral = IERC20(collateral_); oracleData = IKashiPair(lendingPair_).oracleData(); feePool = feePool_; @@ -108,7 +111,7 @@ contract BAMM is PriceFormula, BoringOwnable, ERC20 { balanceOf[msg.sender] = balanceOf[msg.sender].add(newShare); // deposit the wad - require(mim.transferFrom(msg.sender, address(this), wad), "deposit: transferFrom failed"); + mim.safeTransferFrom(msg.sender, address(this), wad); emit Transfer(address(0), msg.sender, newShare); emit UserDeposit(msg.sender, wad, newShare); @@ -123,10 +126,10 @@ contract BAMM is PriceFormula, BoringOwnable, ERC20 { uint usdAmount = usdValue.mul(numShares) / totalSupply; uint gemAmount = gemValue.mul(numShares) / totalSupply; - require(mim.transfer(msg.sender, usdAmount), "withdraw: transfer failed"); + mim.safeTransfer(msg.sender, usdAmount); if(gemAmount > 0) { - require(collateral.transfer(msg.sender, gemAmount), "withdraw: transfer failed"); + collateral.safeTransfer(msg.sender, gemAmount); } balanceOf[msg.sender] = balanceOf[msg.sender].sub(numShares); @@ -177,12 +180,12 @@ contract BAMM is PriceFormula, BoringOwnable, ERC20 { require(gemAmount >= minGemReturn, "swap: low return"); - require(mim.transferFrom(msg.sender, address(this), wad), "swap/transferFrom-failed"); + mim.safeTransferFrom(msg.sender, address(this), wad); - uint feeWad = (addBps(wad, int(fee))).sub(wad); - if(feeWad > 0) require(mim.transfer(feePool, feeWad), "swap/transfer-failed"); + uint feeWad = addBps(wad, int(fee)).sub(wad); + if(feeWad > 0) mim.safeTransfer(feePool, feeWad); - require(collateral.transfer(dest, gemAmount), "swap: transfer failed"); + collateral.safeTransfer(dest, gemAmount); emit RebalanceSwap(msg.sender, wad, gemAmount, now); @@ -205,7 +208,7 @@ contract BAMM is PriceFormula, BoringOwnable, ERC20 { uint callerReward = mimBalanceBefore.sub(mimBalanceAfter).mul(callerFee) / 10000; if(mimBalanceAfter >= callerReward) { - require(mim.transfer(msg.sender, callerReward), "liquidate: transfer failed"); + mim.safeTransfer(msg.sender, callerReward); mimBalanceAfter = mimBalanceAfter.sub(callerReward); } } @@ -219,7 +222,7 @@ contract BAMM is PriceFormula, BoringOwnable, ERC20 { bool open ) public { // take the mim - require(mim.transferFrom(msg.sender, address(this), extraMim), "liquidateLikeBoomer: transferFrom failed"); + mim.safeTransferFrom(msg.sender, address(this), extraMim); uint collatBalanceBefore = collateral.balanceOf(address(this)); @@ -230,18 +233,18 @@ contract BAMM is PriceFormula, BoringOwnable, ERC20 { if(extraMim <= mimBalanceAfter) { // boomer liquidation was not needed. just return the money - require(mim.transfer(msg.sender, extraMim), "liquidateLikeBoomer: transfer failed"); + mim.safeTransfer(msg.sender, extraMim); return; } // send mim leftover to liquidator if(mimBalanceBefore.sub(mimBalanceAfter) >= extraMim) { uint returnAmount = mimBalanceBefore.sub(mimBalanceAfter).sub(extraMim); - require(mim.transfer(msg.sender, returnAmount), "liquidateLikeBoomer: transfer failed"); + mim.safeTransfer(msg.sender, returnAmount); } // send collateral to liquidator - require(collateral.transfer(msg.sender, collatBalanceAfter.sub(collatBalanceBefore)), "liquidateLikeBoomer: transfer failed"); + collateral.safeTransfer(msg.sender, collatBalanceAfter.sub(collatBalanceBefore)); } } \ No newline at end of file diff --git a/test/BProtocol.js b/test/BProtocol.js index 81f5c06..977a60d 100644 --- a/test/BProtocol.js +++ b/test/BProtocol.js @@ -12,6 +12,7 @@ const { createFixture, ADDRESS_ZERO, BKashiPair, + BAMM, advanceTimeAndBlock, } = require("@sushiswap/hardhat-framework") const { defaultAbiCoder } = require("ethers/lib/utils") @@ -52,7 +53,8 @@ describe("KashiPair Basic", function () { await cmd.deploy("bentoBox", "BentoBoxMock", this.weth9.address) await cmd.addToken("a", "Token A", "A", 18, this.ReturnFalseERC20Mock) - await cmd.addToken("b", "Token B", "B", 8, this.RevertingERC20Mock) + //await cmd.addToken("b", "Token B", "B", 8, this.RevertingERC20Mock) + await cmd.addToken("b", "Token B", "B", 18, this.RevertingERC20Mock) await cmd.addPair("sushiSwapPair", this.a, this.b, 50000, 50000) await cmd.deploy("strategy", "SimpleStrategyMock", this.bentoBox.address, this.a.address) @@ -95,6 +97,8 @@ describe("KashiPair Basic", function () { .withArgs(this.b.address, this.fred.address, this.fred.address, getBigNumber(200, 8), getBigNumber(200, 8)) await this.bentoBox.connect(this.fred).addProfit(this.b.address, getBigNumber(200, 8)) + + await cmd.deploy("BAMM", "BAMM", this.BkashiPair.address, this.oracle.address, this.a.address, this.b.address, this.alice.address, 400) }) }) @@ -669,6 +673,33 @@ describe("KashiPair Basic", function () { }) }) + describe.only("bamm", function () { + it("should deposit b at the bamm", async function () { + const bamm = this.BAMM + const depositAmonut = getBigNumber(2, 18); + const withdrawAmountShare = getBigNumber(5, 17); + const withdrawAmountMim = getBigNumber(1, 18); + + const bobBalBefore = await this.a.balanceOf(this.bob.address) + + // deposit + await this.a.connect(this.bob).approve(bamm.address, depositAmonut); + await bamm.connect(this.bob).deposit(depositAmonut); + + expect(await bamm.balanceOf(this.bob.address)).to.be.equal(getBigNumber(1, 18)) + expect((await this.a.balanceOf(this.bob.address)).add(depositAmonut)).to.be.equal(bobBalBefore) + expect((await this.a.balanceOf(bamm.address))).to.be.equal(depositAmonut) + + // withdraw + await bamm.connect(this.bob).withdraw(withdrawAmountShare) + + expect(await bamm.balanceOf(this.bob.address)).to.be.equal(getBigNumber(1, 18).sub(withdrawAmountShare)) + expect((await this.a.balanceOf(this.bob.address)).add(depositAmonut.sub(withdrawAmountMim))).to.be.equal(bobBalBefore) + expect((await this.a.balanceOf(bamm.address))).to.be.equal(depositAmonut.sub(withdrawAmountMim)) + }) + }) + + describe.only("Liquidate", function () { it("should not allow open liquidate yet", async function () { await this.pairHelper.run((cmd) => [ From 9bff0e68fe666ebb6c18debf0580bf057b38c1b5 Mon Sep 17 00:00:00 2001 From: yaron velner Date: Sun, 24 Oct 2021 14:39:30 +0300 Subject: [PATCH 04/24] withdraw test --- contracts/B.Protocol/BAMM.sol | 8 +++--- test/BProtocol.js | 51 +++++++++++++++++++++++++++++++++-- 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/contracts/B.Protocol/BAMM.sol b/contracts/B.Protocol/BAMM.sol index 55b2a6a..6efbcc2 100644 --- a/contracts/B.Protocol/BAMM.sol +++ b/contracts/B.Protocol/BAMM.sol @@ -79,15 +79,15 @@ contract BAMM is PriceFormula, BoringOwnable, ERC20 { function fetchPrice() public returns(uint) { (bool succ, uint rate) = oracle.get(oracleData); - if(succ) return 0; - else return rate; + if(succ) return rate; + else return 0; } function peekPrice() public view returns(uint) { (bool succ, uint rate) = oracle.peek(oracleData); - if(succ) return 0; - else return rate; + if(succ) return rate; + else return 0; } function deposit(uint wad) external { diff --git a/test/BProtocol.js b/test/BProtocol.js index 977a60d..0d15b3e 100644 --- a/test/BProtocol.js +++ b/test/BProtocol.js @@ -674,7 +674,7 @@ describe("KashiPair Basic", function () { }) describe.only("bamm", function () { - it("should deposit b at the bamm", async function () { + it("deposit and withdraw only with mim", async function () { const bamm = this.BAMM const depositAmonut = getBigNumber(2, 18); const withdrawAmountShare = getBigNumber(5, 17); @@ -695,8 +695,55 @@ describe("KashiPair Basic", function () { expect(await bamm.balanceOf(this.bob.address)).to.be.equal(getBigNumber(1, 18).sub(withdrawAmountShare)) expect((await this.a.balanceOf(this.bob.address)).add(depositAmonut.sub(withdrawAmountMim))).to.be.equal(bobBalBefore) - expect((await this.a.balanceOf(bamm.address))).to.be.equal(depositAmonut.sub(withdrawAmountMim)) + expect((await this.a.balanceOf(bamm.address))).to.be.equal(depositAmonut.sub(withdrawAmountMim)) + + // deposit with alice + await this.a.connect(this.alice).approve(bamm.address, depositAmonut) + await bamm.connect(this.alice).deposit(depositAmonut) + + expect(await bamm.balanceOf(this.alice.address)).to.be.equal(getBigNumber(1, 18)) // 1 shares + }) + + it("deposit and withdraw also with collateral", async function () { + const bamm = this.BAMM + const depositAmonut = getBigNumber(11, 17); + + // deposit + await this.a.connect(this.bob).approve(bamm.address, depositAmonut); + await bamm.connect(this.bob).deposit(depositAmonut); + + // transfer collateral + await this.b.connect(this.bob).transfer(bamm.address, getBigNumber(1, 18)) + await this.oracle.connect(this.alice).set(getBigNumber(11, 17).toString()) + + // now there are 1.1 of mim, and 1.1 worth of collateral + + // deposit 2.2 of mim + await this.a.connect(this.alice).approve(bamm.address, getBigNumber(22, 17)) + await bamm.connect(this.alice).deposit(getBigNumber(22, 17)) + + expect((await bamm.balanceOf(this.alice.address))).to.be.equal(getBigNumber(1, 18)) + + // now there are 3.3 of mim and 1.1 worth of collateral + + // withdraw half the deposit. get 3.3/4 = 0.825 mim and 1.0/4 = 0.25 of collateral + const aliceMimBalBefore = await this.a.balanceOf(this.alice.address) + const aliceColBalBefore = await this.b.balanceOf(this.alice.address) + + await bamm.connect(this.alice).withdraw(getBigNumber(5, 17)) + + const aliceMimBalAfter = await this.a.balanceOf(this.alice.address) + const aliceColBalAfter = await this.b.balanceOf(this.alice.address) + + + const expectedMimDelta = getBigNumber(825, 15) + const expectedColDelta = getBigNumber(25, 16) + + expect(aliceColBalBefore.add(expectedColDelta)).to.be.equal(aliceColBalAfter) + expect(aliceMimBalBefore.add(expectedMimDelta)).to.be.equal(aliceMimBalAfter) }) + + }) From d9ea5f5086a10e47ebbea0e95e09cd991723fcd9 Mon Sep 17 00:00:00 2001 From: yaron velner Date: Sun, 24 Oct 2021 21:51:19 +0300 Subject: [PATCH 05/24] bentobox --- contracts/B.Protocol/BAMM.sol | 85 +++++++++++++++++---------- test/BProtocol.js | 104 ++++++++++++++++++++++++++-------- 2 files changed, 134 insertions(+), 55 deletions(-) diff --git a/contracts/B.Protocol/BAMM.sol b/contracts/B.Protocol/BAMM.sol index 6efbcc2..845469c 100644 --- a/contracts/B.Protocol/BAMM.sol +++ b/contracts/B.Protocol/BAMM.sol @@ -8,6 +8,7 @@ import "@boringcrypto/boring-solidity/contracts/ERC20.sol"; import "@boringcrypto/boring-solidity/contracts/libraries/BoringERC20.sol"; import "./../interfaces/IOracle.sol"; import "./../interfaces/IKashiPair.sol"; // TODO the interface for abracadabra is a bit different +import "@sushiswap/bentobox-sdk/contracts/IBentoBoxV1.sol"; contract BAMM is PriceFormula, BoringOwnable, ERC20 { using BoringERC20 for IERC20; @@ -16,6 +17,7 @@ contract BAMM is PriceFormula, BoringOwnable, ERC20 { IERC20 public immutable mim; IERC20 public immutable collateral; IKashiPair public immutable lendingPair; + IBentoBoxV1 public immutable bentobox; bytes public oracleData; address public immutable feePool; @@ -57,7 +59,11 @@ contract BAMM is PriceFormula, BoringOwnable, ERC20 { feePool = feePool_; maxDiscount = maxDiscount_; - ERC20(mim_).approve(lendingPair_, uint(-1)); // TODO - is it needed? + IBentoBoxV1 box = IBentoBoxV1(IKashiPair(lendingPair_).bentoBox()); + box.setMasterContractApproval(address(this), box.masterContractOf(lendingPair_), true, 0, 0, 0); + bentobox = box; + + ERC20(mim_).approve(address(box), uint(-1)); // TODO - can we support only 18 decimals collateral? it depends on the price feed //require(ERC20(collateral_).decimals() == 18, "only 18 decimals collaterals are supported"); @@ -90,10 +96,9 @@ contract BAMM is PriceFormula, BoringOwnable, ERC20 { else return 0; } - function deposit(uint wad) external { + function deposit(uint wad, bool viaBentobox) external { // update share - uint usdValue = mim.balanceOf(address(this)); - uint gemValue = collateral.balanceOf(address(this)); + (uint usdValue, uint gemValue) = getBalances(); uint price = fetchPrice(); require(gemValue == 0 || price > 0, "deposit: feed is down"); @@ -111,25 +116,24 @@ contract BAMM is PriceFormula, BoringOwnable, ERC20 { balanceOf[msg.sender] = balanceOf[msg.sender].add(newShare); // deposit the wad - mim.safeTransferFrom(msg.sender, address(this), wad); + receiveToken(mim, wad, viaBentobox); emit Transfer(address(0), msg.sender, newShare); emit UserDeposit(msg.sender, wad, newShare); } - function withdraw(uint numShares) external { + function withdraw(uint numShares, bool viaBentobox) external { require(balanceOf[msg.sender] >= numShares, "withdraw: insufficient balance"); - uint usdValue = mim.balanceOf(address(this)); - uint gemValue = collateral.balanceOf(address(this)); + (uint usdValue, uint gemValue) = getBalances(); uint usdAmount = usdValue.mul(numShares) / totalSupply; uint gemAmount = gemValue.mul(numShares) / totalSupply; - mim.safeTransfer(msg.sender, usdAmount); + sendToken(mim, msg.sender, usdAmount, viaBentobox); if(gemAmount > 0) { - collateral.safeTransfer(msg.sender, gemAmount); + sendToken(collateral, msg.sender, gemAmount, viaBentobox); } balanceOf[msg.sender] = balanceOf[msg.sender].sub(numShares); @@ -152,8 +156,7 @@ contract BAMM is PriceFormula, BoringOwnable, ERC20 { } function getSwapGemAmount(uint wad, uint gem2usdPrice) internal view returns(uint gemAmount) { - uint usdBalance = mim.balanceOf(address(this)); - uint gemBalance = collateral.balanceOf(address(this)); + (uint usdBalance, uint gemBalance) = getBalances(); if(gem2usdPrice == 0) return (0); // feed is down @@ -174,18 +177,18 @@ contract BAMM is PriceFormula, BoringOwnable, ERC20 { } // get gem in return to mim - function swap(uint wad, uint minGemReturn, address dest) public returns(uint) { + function swap(uint wad, uint minGemReturn, address dest, bool viaBentobox) public returns(uint) { uint oraclePrice = fetchPrice(); uint gemAmount = getSwapGemAmount(wad, oraclePrice); require(gemAmount >= minGemReturn, "swap: low return"); - mim.safeTransferFrom(msg.sender, address(this), wad); + receiveToken(mim, wad, viaBentobox); uint feeWad = addBps(wad, int(fee)).sub(wad); - if(feeWad > 0) mim.safeTransfer(feePool, feeWad); + if(feeWad > 0) sendToken(mim, feePool, feeWad, true); - collateral.safeTransfer(dest, gemAmount); + sendToken(collateral, dest, gemAmount, viaBentobox); emit RebalanceSwap(msg.sender, wad, gemAmount, now); @@ -199,16 +202,16 @@ contract BAMM is PriceFormula, BoringOwnable, ERC20 { ISwapper swapper, bool open ) public - returns(uint mimBalanceBefore, uint mimBalanceAfter) + returns(uint mimBalanceBefore, uint collateralBalanceBefore, uint mimBalanceAfter, uint collateralBalanceAfter) { - mimBalanceBefore = mim.balanceOf(address(this)); + (mimBalanceBefore, collateralBalanceBefore) = getBalances(); lendingPair.liquidate(users, maxBorrowParts, to, swapper, open); - mimBalanceAfter = mim.balanceOf(address(this)); + (mimBalanceAfter, collateralBalanceAfter) = getBalances(); uint callerReward = mimBalanceBefore.sub(mimBalanceAfter).mul(callerFee) / 10000; if(mimBalanceAfter >= callerReward) { - mim.safeTransfer(msg.sender, callerReward); + sendToken(mim, to, callerReward, true); mimBalanceAfter = mimBalanceAfter.sub(callerReward); } } @@ -219,32 +222,52 @@ contract BAMM is PriceFormula, BoringOwnable, ERC20 { uint256[] calldata maxBorrowParts, address to, ISwapper swapper, - bool open + bool open, + bool viaBentobox ) public { // take the mim - mim.safeTransferFrom(msg.sender, address(this), extraMim); + receiveToken(mim, extraMim, viaBentobox); - uint collatBalanceBefore = collateral.balanceOf(address(this)); - // do the liquidation - (uint mimBalanceBefore, uint mimBalanceAfter) = liquidate(users, maxBorrowParts, to, swapper, open); - - uint collatBalanceAfter = collateral.balanceOf(address(this)); + (uint mimBalanceBefore, uint collatBalanceBefore, uint mimBalanceAfter, uint collatBalanceAfter) = + liquidate(users, maxBorrowParts, to, swapper, open); if(extraMim <= mimBalanceAfter) { - // boomer liquidation was not needed. just return the money - mim.safeTransfer(msg.sender, extraMim); + sendToken(mim, msg.sender, extraMim, viaBentobox); return; } // send mim leftover to liquidator if(mimBalanceBefore.sub(mimBalanceAfter) >= extraMim) { uint returnAmount = mimBalanceBefore.sub(mimBalanceAfter).sub(extraMim); - mim.safeTransfer(msg.sender, returnAmount); + sendToken(mim, msg.sender, returnAmount, viaBentobox); } // send collateral to liquidator - collateral.safeTransfer(msg.sender, collatBalanceAfter.sub(collatBalanceBefore)); + sendToken(collateral, to, collatBalanceAfter.sub(collatBalanceBefore), viaBentobox); + } + + function getBalances() public view returns(uint mimBalance, uint collateralBalance) { + mimBalance = bentobox.toAmount(mim, bentobox.balanceOf(mim, address(this)), false); + collateralBalance = bentobox.toAmount(collateral, bentobox.balanceOf(collateral, address(this)), false); } + function sendToken(IERC20 token, address to, uint amount, bool viaBentobox) internal { + if(viaBentobox) { + bentobox.transfer(token, address(this), to, bentobox.toShare(token, amount, false)); + } + else { + bentobox.withdraw(token, address(this), to, amount, 0); + } + } + + function receiveToken(IERC20 token, uint amount, bool viaBentobox) internal { + if(viaBentobox) { + bentobox.transfer(token, msg.sender, address(this), bentobox.toShare(token, amount, false)); + } + else { + token.safeTransferFrom(msg.sender, address(this), amount); + bentobox.deposit(token, address(this), address(this), amount, 0); + } + } } \ No newline at end of file diff --git a/test/BProtocol.js b/test/BProtocol.js index 0d15b3e..5d1d5c4 100644 --- a/test/BProtocol.js +++ b/test/BProtocol.js @@ -98,7 +98,11 @@ describe("KashiPair Basic", function () { await this.bentoBox.connect(this.fred).addProfit(this.b.address, getBigNumber(200, 8)) - await cmd.deploy("BAMM", "BAMM", this.BkashiPair.address, this.oracle.address, this.a.address, this.b.address, this.alice.address, 400) + + + const master = await this.bentoBox.masterContractOf(this.pairHelper.contract.address) + await this.bentoBox.whitelistMasterContract(master, true) + await cmd.deploy("BAMM", "BAMM", this.pairHelper.contract.address, this.oracle.address, this.b.address, this.a.address, this.alice.address, 400) }) }) @@ -674,32 +678,36 @@ describe("KashiPair Basic", function () { }) describe.only("bamm", function () { + async function getBentoBoxBalance(thisObject, token, address) { + const share = await thisObject.bentoBox.balanceOf(token, address) + return await thisObject.bentoBox.toAmount(token, share, false) + } it("deposit and withdraw only with mim", async function () { const bamm = this.BAMM const depositAmonut = getBigNumber(2, 18); const withdrawAmountShare = getBigNumber(5, 17); const withdrawAmountMim = getBigNumber(1, 18); - const bobBalBefore = await this.a.balanceOf(this.bob.address) + const bobBalBefore = await this.b.balanceOf(this.bob.address) // deposit - await this.a.connect(this.bob).approve(bamm.address, depositAmonut); - await bamm.connect(this.bob).deposit(depositAmonut); + await this.b.connect(this.bob).approve(bamm.address, depositAmonut); + await bamm.connect(this.bob).deposit(depositAmonut, false); expect(await bamm.balanceOf(this.bob.address)).to.be.equal(getBigNumber(1, 18)) - expect((await this.a.balanceOf(this.bob.address)).add(depositAmonut)).to.be.equal(bobBalBefore) - expect((await this.a.balanceOf(bamm.address))).to.be.equal(depositAmonut) + expect((await this.b.balanceOf(this.bob.address)).add(depositAmonut)).to.be.equal(bobBalBefore) + expect((await getBentoBoxBalance(this, this.b.address, bamm.address))).to.be.equal(depositAmonut) // withdraw - await bamm.connect(this.bob).withdraw(withdrawAmountShare) + await bamm.connect(this.bob).withdraw(withdrawAmountShare, false) expect(await bamm.balanceOf(this.bob.address)).to.be.equal(getBigNumber(1, 18).sub(withdrawAmountShare)) - expect((await this.a.balanceOf(this.bob.address)).add(depositAmonut.sub(withdrawAmountMim))).to.be.equal(bobBalBefore) - expect((await this.a.balanceOf(bamm.address))).to.be.equal(depositAmonut.sub(withdrawAmountMim)) + expect((await this.b.balanceOf(this.bob.address)).add(depositAmonut.sub(withdrawAmountMim))).to.be.equal(bobBalBefore) + expect((await getBentoBoxBalance(this, this.b.address, bamm.address))).to.be.equal(depositAmonut.sub(withdrawAmountMim)) // deposit with alice - await this.a.connect(this.alice).approve(bamm.address, depositAmonut) - await bamm.connect(this.alice).deposit(depositAmonut) + await this.b.connect(this.alice).approve(bamm.address, depositAmonut) + await bamm.connect(this.alice).deposit(depositAmonut, false) expect(await bamm.balanceOf(this.alice.address)).to.be.equal(getBigNumber(1, 18)) // 1 shares }) @@ -709,45 +717,93 @@ describe("KashiPair Basic", function () { const depositAmonut = getBigNumber(11, 17); // deposit - await this.a.connect(this.bob).approve(bamm.address, depositAmonut); - await bamm.connect(this.bob).deposit(depositAmonut); + await this.b.connect(this.bob).approve(bamm.address, depositAmonut); + await bamm.connect(this.bob).deposit(depositAmonut, false); // transfer collateral - await this.b.connect(this.bob).transfer(bamm.address, getBigNumber(1, 18)) + await this.a.connect(this.bob).approve(this.bentoBox.address, getBigNumber(1, 18)) + await this.bentoBox.connect(this.bob).deposit(this.a.address, this.bob.address, bamm.address, getBigNumber(1, 18), 0) + //await this.a.connect(this.bob).transfer(bamm.address, getBigNumber(1, 18)) await this.oracle.connect(this.alice).set(getBigNumber(11, 17).toString()) // now there are 1.1 of mim, and 1.1 worth of collateral // deposit 2.2 of mim - await this.a.connect(this.alice).approve(bamm.address, getBigNumber(22, 17)) - await bamm.connect(this.alice).deposit(getBigNumber(22, 17)) + await this.b.connect(this.alice).approve(bamm.address, getBigNumber(22, 17)) + await bamm.connect(this.alice).deposit(getBigNumber(22, 17), false) expect((await bamm.balanceOf(this.alice.address))).to.be.equal(getBigNumber(1, 18)) // now there are 3.3 of mim and 1.1 worth of collateral // withdraw half the deposit. get 3.3/4 = 0.825 mim and 1.0/4 = 0.25 of collateral - const aliceMimBalBefore = await this.a.balanceOf(this.alice.address) - const aliceColBalBefore = await this.b.balanceOf(this.alice.address) + const aliceMimBalBefore = await this.b.balanceOf(this.alice.address) + const aliceColBalBefore = await this.a.balanceOf(this.alice.address) - await bamm.connect(this.alice).withdraw(getBigNumber(5, 17)) + await bamm.connect(this.alice).withdraw(getBigNumber(5, 17), false) - const aliceMimBalAfter = await this.a.balanceOf(this.alice.address) - const aliceColBalAfter = await this.b.balanceOf(this.alice.address) + const aliceMimBalAfter = await this.b.balanceOf(this.alice.address) + const aliceColBalAfter = await this.a.balanceOf(this.alice.address) const expectedMimDelta = getBigNumber(825, 15) const expectedColDelta = getBigNumber(25, 16) - expect(aliceColBalBefore.add(expectedColDelta)).to.be.equal(aliceColBalAfter) + // sub 1 to compensate on rounding errors + expect(aliceColBalBefore.add(expectedColDelta.sub(1))).to.be.equal(aliceColBalAfter) expect(aliceMimBalBefore.add(expectedMimDelta)).to.be.equal(aliceMimBalAfter) }) + it("liquidate via bprotocol", async function () { + const bamm = this.BAMM + + //console.log(await this.pairHelper.contract.collateral(), this.a.address, await bamm.mim()) - }) + //await this.pairHelper.contract.connect(this.alice).setBProtocolMock(bamm.address) + await this.pairHelper.run((cmd) => [ + cmd.as(this.bob).approveAsset(getBigNumber(310, 8)), + cmd.as(this.bob).depositAsset(getBigNumber(290, 8)), + cmd.approveCollateral(getBigNumber(100)), + cmd.depositCollateral(getBigNumber(100)), + cmd.borrow(sansBorrowFee(getBigNumber(75, 8))), + cmd.accrue(), + cmd.do(this.oracle.set, "11000000000000000000000000000"), + cmd.updateExchangeRate(), + cmd.do(this.bentoBox.connect(this.bob).deposit, this.b.address, this.bob.address, this.bob.address, getBigNumber(20, 8), 0), + cmd.do(this.pairHelper.contract.connect(this.bob).removeAsset, this.bob.address, getBigNumber(50, 8)), + ]) + + //console.log("Try to liquiate") + //console.log(await this.bentoBox.balanceOf(this.b.address, this.bob.address), await this.bentoBox.balanceOf(this.a.address, this.bob.address)) + /* + await this.pairHelper.contract + .connect(this.bob) + .liquidate([this.alice.address], [getBigNumber(20, 8)], this.bob.address, "0x0000000000000000000000000000000000000000", true) +*/ + + //console.log("deposit started") + + // deposit + const depositAmonut = await this.b.balanceOf(this.bob.address); + + await this.b.connect(this.bob).approve(bamm.address, depositAmonut); + await bamm.connect(this.bob).deposit(depositAmonut, false); + /* + console.log(depositAmonut.toString()) + + console.log("deposit ended") + console.log(await this.bentoBox.balanceOf(this.b.address, bamm.address), await this.bentoBox.balanceOf(this.b.address, this.bob.address)) + + console.log(await this.pairHelper.contract.userBorrowPart(this.alice.address))*/ + await bamm.connect(this.bob).liquidate([this.alice.address], [getBigNumber(20, 8)], this.bob.address, "0x0000000000000000000000000000000000000000", true) + }) + + // liquidate normal test - TODO + + }) - describe.only("Liquidate", function () { + describe("Liquidate", function () { it("should not allow open liquidate yet", async function () { await this.pairHelper.run((cmd) => [ cmd.as(this.bob).approveAsset(getBigNumber(310, 8)), From 71afd770f20b4390a171631fcfcda3389c423bd7 Mon Sep 17 00:00:00 2001 From: yaron velner Date: Mon, 25 Oct 2021 10:21:45 +0300 Subject: [PATCH 06/24] liquidation test --- contracts/B.Protocol/BAMM.sol | 8 +++--- test/BProtocol.js | 51 +++++++++++++++++++++++------------ 2 files changed, 37 insertions(+), 22 deletions(-) diff --git a/contracts/B.Protocol/BAMM.sol b/contracts/B.Protocol/BAMM.sol index 845469c..b8463f0 100644 --- a/contracts/B.Protocol/BAMM.sol +++ b/contracts/B.Protocol/BAMM.sol @@ -199,13 +199,12 @@ contract BAMM is PriceFormula, BoringOwnable, ERC20 { address[] calldata users, uint256[] calldata maxBorrowParts, address to, - ISwapper swapper, - bool open + ISwapper swapper ) public returns(uint mimBalanceBefore, uint collateralBalanceBefore, uint mimBalanceAfter, uint collateralBalanceAfter) { (mimBalanceBefore, collateralBalanceBefore) = getBalances(); - lendingPair.liquidate(users, maxBorrowParts, to, swapper, open); + lendingPair.liquidate(users, maxBorrowParts, address(this), swapper, true); (mimBalanceAfter, collateralBalanceAfter) = getBalances(); uint callerReward = mimBalanceBefore.sub(mimBalanceAfter).mul(callerFee) / 10000; @@ -222,7 +221,6 @@ contract BAMM is PriceFormula, BoringOwnable, ERC20 { uint256[] calldata maxBorrowParts, address to, ISwapper swapper, - bool open, bool viaBentobox ) public { // take the mim @@ -230,7 +228,7 @@ contract BAMM is PriceFormula, BoringOwnable, ERC20 { // do the liquidation (uint mimBalanceBefore, uint collatBalanceBefore, uint mimBalanceAfter, uint collatBalanceAfter) = - liquidate(users, maxBorrowParts, to, swapper, open); + liquidate(users, maxBorrowParts, to, swapper); if(extraMim <= mimBalanceAfter) { sendToken(mim, msg.sender, extraMim, viaBentobox); diff --git a/test/BProtocol.js b/test/BProtocol.js index 5d1d5c4..12bc86e 100644 --- a/test/BProtocol.js +++ b/test/BProtocol.js @@ -682,6 +682,11 @@ describe("KashiPair Basic", function () { const share = await thisObject.bentoBox.balanceOf(token, address) return await thisObject.bentoBox.toAmount(token, share, false) } + + async function toAmount(thisObject, token, share) { + return await thisObject.bentoBox.toAmount(token, share, false) + } + it("deposit and withdraw only with mim", async function () { const bamm = this.BAMM const depositAmonut = getBigNumber(2, 18); @@ -761,6 +766,8 @@ describe("KashiPair Basic", function () { //await this.pairHelper.contract.connect(this.alice).setBProtocolMock(bamm.address) + const price = getBigNumber(11, 27); + await this.pairHelper.run((cmd) => [ cmd.as(this.bob).approveAsset(getBigNumber(310, 8)), cmd.as(this.bob).depositAsset(getBigNumber(290, 8)), @@ -768,39 +775,49 @@ describe("KashiPair Basic", function () { cmd.depositCollateral(getBigNumber(100)), cmd.borrow(sansBorrowFee(getBigNumber(75, 8))), cmd.accrue(), - cmd.do(this.oracle.set, "11000000000000000000000000000"), + cmd.do(this.oracle.set, price.toString()), cmd.updateExchangeRate(), cmd.do(this.bentoBox.connect(this.bob).deposit, this.b.address, this.bob.address, this.bob.address, getBigNumber(20, 8), 0), cmd.do(this.pairHelper.contract.connect(this.bob).removeAsset, this.bob.address, getBigNumber(50, 8)), ]) - //console.log("Try to liquiate") - //console.log(await this.bentoBox.balanceOf(this.b.address, this.bob.address), await this.bentoBox.balanceOf(this.a.address, this.bob.address)) - /* - await this.pairHelper.contract - .connect(this.bob) - .liquidate([this.alice.address], [getBigNumber(20, 8)], this.bob.address, "0x0000000000000000000000000000000000000000", true) -*/ - - //console.log("deposit started") + await bamm.setParams(20, 0, 100) // deposit const depositAmonut = await this.b.balanceOf(this.bob.address); await this.b.connect(this.bob).approve(bamm.address, depositAmonut); await bamm.connect(this.bob).deposit(depositAmonut, false); - /* - console.log(depositAmonut.toString()) - console.log("deposit ended") - console.log(await this.bentoBox.balanceOf(this.b.address, bamm.address), await this.bentoBox.balanceOf(this.b.address, this.bob.address)) + const liquidationShare = await this.pairHelper.contract.userBorrowPart(this.alice.address) //getBigNumber(20, 18); + const liquidationAmount = await toAmount(this, this.b.address, liquidationShare) + + const bammMimBalBefore = await getBentoBoxBalance(this, this.b.address, bamm.address) + const bammColBalBefore = await getBentoBoxBalance(this, this.a.address, bamm.address) + const pairMimBalBefore = await getBentoBoxBalance(this, this.b.address, this.pairHelper.contract.address) + const nullAddr = "0x0000000000000000000000000000000000000000" + const rewardAddress = "0x0000000000000000000000000000000000000007" + await bamm.connect(this.bob).liquidate([this.alice.address], [liquidationShare], rewardAddress, nullAddr) + const bammMimBalAfter = await getBentoBoxBalance(this, this.b.address, bamm.address) + const bammColBalAfter = await getBentoBoxBalance(this, this.a.address, bamm.address) + const pairMimBalAfter = await getBentoBoxBalance(this, this.b.address, this.pairHelper.contract.address) + + const deltaMimBamm = bammMimBalBefore.sub(bammMimBalAfter) + const deltaMimPair = pairMimBalAfter.sub(pairMimBalBefore) + + const rewardBalance = await getBentoBoxBalance(this, this.b.address, rewardAddress) - console.log(await this.pairHelper.contract.userBorrowPart(this.alice.address))*/ - await bamm.connect(this.bob).liquidate([this.alice.address], [getBigNumber(20, 8)], this.bob.address, "0x0000000000000000000000000000000000000000", true) + expect(deltaMimBamm.sub(rewardBalance)).to.be.equal(deltaMimPair) + expect(deltaMimPair.div(100)).to.be.equal(rewardBalance) + + const deltaCol = bammColBalAfter.sub(bammColBalBefore) + const deltaMimWithPermium = deltaMimPair.mul(112).div(100) + + const roundingFactor = getBigNumber(1, 11); + expect(deltaMimWithPermium.mul(price).div(getBigNumber(1,18)).div(roundingFactor)).to.be.equal(deltaCol.div(roundingFactor)) }) // liquidate normal test - TODO - }) describe("Liquidate", function () { From 9bbd21200beaaa8a6cc6f265039ced026401b741 Mon Sep 17 00:00:00 2001 From: yaron velner Date: Mon, 25 Oct 2021 12:00:50 +0300 Subject: [PATCH 07/24] swap test --- test/BProtocol.js | 93 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 90 insertions(+), 3 deletions(-) diff --git a/test/BProtocol.js b/test/BProtocol.js index 12bc86e..ec5a411 100644 --- a/test/BProtocol.js +++ b/test/BProtocol.js @@ -17,6 +17,8 @@ const { } = require("@sushiswap/hardhat-framework") const { defaultAbiCoder } = require("ethers/lib/utils") +const feePool = "0xFeE0000ee89f342a85543C489Ff400dA521156F2" + let cmd, fixture async function debugInfo(thisObject) { @@ -98,11 +100,11 @@ describe("KashiPair Basic", function () { await this.bentoBox.connect(this.fred).addProfit(this.b.address, getBigNumber(200, 8)) - + const master = await this.bentoBox.masterContractOf(this.pairHelper.contract.address) await this.bentoBox.whitelistMasterContract(master, true) - await cmd.deploy("BAMM", "BAMM", this.pairHelper.contract.address, this.oracle.address, this.b.address, this.a.address, this.alice.address, 400) + await cmd.deploy("BAMM", "BAMM", this.pairHelper.contract.address, this.oracle.address, this.b.address, this.a.address, feePool, 400) }) }) @@ -758,6 +760,91 @@ describe("KashiPair Basic", function () { expect(aliceColBalBefore.add(expectedColDelta.sub(1))).to.be.equal(aliceColBalAfter) expect(aliceMimBalBefore.add(expectedMimDelta)).to.be.equal(aliceMimBalAfter) }) +/* + it('test getSwapEthAmount', async () => { + // --- SETUP --- + + + // 4k liquidations + assert.equal(toBN(dec(6000, 18)).toString(), (await stabilityPool.getCompoundedLUSDDeposit(bamm.address)).toString()) + const ethGains = web3.utils.toBN("39799999999999999975") + + const lusdQty = dec(105, 18) + const expectedReturn = await bamm.getReturn(lusdQty, dec(6000, 18), toBN(dec(6000, 18)).add(ethGains.mul(toBN(2 * 105))), 200) + + // without fee + await bamm.setParams(200, 0, {from: bammOwner}) + const priceWithoutFee = await bamm.getSwapEthAmount(lusdQty) + assert.equal(priceWithoutFee.ethAmount.toString(), expectedReturn.mul(toBN(100)).div(toBN(100 * 105)).toString()) + + // with fee + await bamm.setParams(200, 100, {from: bammOwner}) + const priceWithFee = await bamm.getSwapEthAmount(lusdQty) + assert.equal(priceWithFee.ethAmount.toString(), expectedReturn.mul(toBN(99)).div(toBN(100 * 105)).toString()) + }) */ + + it("getSwapGemAmount", async function () { + const bamm = this.BAMM + const mimAmonut = getBigNumber(600, 18) + const colAmount = "3979999999999999997" // almost 4e17 + const price = getBigNumber(105, 18) + const wad = getBigNumber(105, 17) + + // deposit + await this.b.connect(this.bob).approve(bamm.address, mimAmonut); + await bamm.connect(this.bob).deposit(mimAmonut, false); + + // transfer collateral + await this.a.connect(this.bob).approve(this.bentoBox.address, colAmount) + await this.bentoBox.connect(this.bob).deposit(this.a.address, this.bob.address, bamm.address, colAmount, 0) + //await this.a.connect(this.bob).transfer(bamm.address, getBigNumber(1, 18)) + await this.oracle.connect(this.alice).set(price.toString()) + await bamm.fetchPrice() + + const expectedReturn = await bamm.getReturn(wad, mimAmonut, mimAmonut.add(price.mul(colAmount).mul(2).div(getBigNumber(1, 18))), 200) + + // without fee + await bamm.setParams(200, 0, 0) + const priceWithoutFee = await bamm.getSwapGemAmount(wad) + expect(priceWithoutFee).to.be.equal(expectedReturn.mul(getBigNumber(1,18)).div(price)) + + // with fee - price should be the same + await bamm.setParams(200, 100, 0) + const priceWithFee = await bamm.getSwapGemAmount(wad) + expect(priceWithFee).to.be.equal(expectedReturn.mul(getBigNumber(1,18)).div(price)) + }) + + it("swap", async function () { + const bamm = this.BAMM + const mimAmonut = getBigNumber(600, 18) + const colAmount = "3979999999999999997" // almost 4e17 + const price = getBigNumber(105, 18) + const wad = getBigNumber(105, 17) + + // deposit + await this.b.connect(this.bob).approve(bamm.address, mimAmonut); + await bamm.connect(this.bob).deposit(mimAmonut, false); + + // transfer collateral + await this.a.connect(this.bob).approve(this.bentoBox.address, colAmount) + await this.bentoBox.connect(this.bob).deposit(this.a.address, this.bob.address, bamm.address, colAmount, 0) + //await this.a.connect(this.bob).transfer(bamm.address, getBigNumber(1, 18)) + await this.oracle.connect(this.alice).set(price.toString()) + await bamm.fetchPrice() + + // with fee + await bamm.setParams(200, 100, 0) + const expectedCol = await bamm.getSwapGemAmount(wad) + + // do the swap + await this.b.connect(this.bob).approve(bamm.address, wad); + const dest = "0x0000000000000000000000000000000000000007" + + await bamm.connect(this.bob).swap(wad, 1, dest, false) + expect(await this.a.balanceOf(dest)).to.be.equal(expectedCol) + + expect(await getBentoBoxBalance(this, this.b.address, feePool)).to.be.equal(wad.div(100)) + }) it("liquidate via bprotocol", async function () { const bamm = this.BAMM @@ -820,7 +907,7 @@ describe("KashiPair Basic", function () { // liquidate normal test - TODO }) - describe("Liquidate", function () { + describe.only("Liquidate", function () { it("should not allow open liquidate yet", async function () { await this.pairHelper.run((cmd) => [ cmd.as(this.bob).approveAsset(getBigNumber(310, 8)), From 9a8b958216f9a94579f891310e6c75db439f87b9 Mon Sep 17 00:00:00 2001 From: yaron velner Date: Mon, 25 Oct 2021 12:06:42 +0300 Subject: [PATCH 08/24] polish --- contracts/B.Protocol/BAMM.sol | 2 +- contracts/BKashiPair.sol | 2 +- contracts/KashiPair.sol | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/B.Protocol/BAMM.sol b/contracts/B.Protocol/BAMM.sol index b8463f0..9648bbd 100644 --- a/contracts/B.Protocol/BAMM.sol +++ b/contracts/B.Protocol/BAMM.sol @@ -215,7 +215,7 @@ contract BAMM is PriceFormula, BoringOwnable, ERC20 { } } - function liquidateLikeBoomer( + function liquidateLikeTiran( uint extraMim, address[] calldata users, uint256[] calldata maxBorrowParts, diff --git a/contracts/BKashiPair.sol b/contracts/BKashiPair.sol index b943b9e..785ed37 100644 --- a/contracts/BKashiPair.sol +++ b/contracts/BKashiPair.sol @@ -14,7 +14,7 @@ contract BKashiPair is KashiPair { constructor(IBentoBoxV1 bentoBox_) public KashiPair(bentoBox_) {} function setBProtocol(address bprotocol_) public onlyOwner { - require(bprotocol == address(0x0)/*, "BKashiPair: bprotocol alread initialized"*/); + require(bprotocol == address(0x0), "BKashiPair: bprotocol alread initialized"); bprotocol = bprotocol_; emit BProtocol(bprotocol_); } diff --git a/contracts/KashiPair.sol b/contracts/KashiPair.sol index 8b53efd..927a155 100644 --- a/contracts/KashiPair.sol +++ b/contracts/KashiPair.sol @@ -141,9 +141,9 @@ contract KashiPair is ERC20, BoringOwnable, IMasterContract { /// @notice Serves as the constructor for clones, as clones can't have a regular constructor /// @dev `data` is abi encoded in the format: (IERC20 collateral, IERC20 asset, IOracle oracle, bytes oracleData) function init(bytes calldata data) public payable override { - require(address(collateral) == address(0), "KashiPair: already initialized"); + require(address(collateral) == address(0)); (collateral, asset, oracle, oracleData) = abi.decode(data, (IERC20, IERC20, IOracle, bytes)); - require(address(collateral) != address(0), "KashiPair: bad pair"); + require(address(collateral) != address(0)); accrueInfo.interestPerSecond = uint64(STARTING_INTEREST_PER_SECOND); // 1% APR, with 1e18 being 100% } From bd3ec513bed9c286b13af3c9642717fcf69987e0 Mon Sep 17 00:00:00 2001 From: shmuel Date: Wed, 27 Oct 2021 12:26:27 +0300 Subject: [PATCH 09/24] bentobox deposit --- contracts/B.Protocol/BAMM.sol | 1 + test/BProtocol.js | 48 +++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/contracts/B.Protocol/BAMM.sol b/contracts/B.Protocol/BAMM.sol index 9648bbd..2b388fc 100644 --- a/contracts/B.Protocol/BAMM.sol +++ b/contracts/B.Protocol/BAMM.sol @@ -61,6 +61,7 @@ contract BAMM is PriceFormula, BoringOwnable, ERC20 { IBentoBoxV1 box = IBentoBoxV1(IKashiPair(lendingPair_).bentoBox()); box.setMasterContractApproval(address(this), box.masterContractOf(lendingPair_), true, 0, 0, 0); + box.registerProtocol(); bentobox = box; ERC20(mim_).approve(address(box), uint(-1)); diff --git a/test/BProtocol.js b/test/BProtocol.js index ec5a411..6806076 100644 --- a/test/BProtocol.js +++ b/test/BProtocol.js @@ -719,6 +719,54 @@ describe("KashiPair Basic", function () { expect(await bamm.balanceOf(this.alice.address)).to.be.equal(getBigNumber(1, 18)) // 1 shares }) + it.only("deposit and withdraw only with mim via bentoBox", async function () { + const depositAmonut = getBigNumber(2, 18); + // bob bento deposit + await this.b.connect(this.bob).approve(this.bentoBox.address, depositAmonut); + await this.bentoBox.connect(this.bob).deposit(this.b.address, this.bob.address, this.bob.address, depositAmonut, 0) + // alice bento deposit + await this.b.connect(this.alice).approve(this.bentoBox.address, depositAmonut); + await this.bentoBox.connect(this.alice).deposit(this.b.address, this.alice.address, this.alice.address, depositAmonut, 0) + + x = await getBentoBoxBalance(this, this.b.address, this.bob.address) + console.log('bobBentoBal post bento', x.toString()) + const bobBentoBalBefore = await getBentoBoxBalance(this, this.b.address, this.bob.address) + + console.log("Alice Asset in Bento", (await getBentoBoxBalance(this, this.b.address, this.alice.address)).toString()) + console.log("Bob Asset in Bento", (bobBentoBalBefore).toString()) + const bamm = this.BAMM + const withdrawAmountShare = getBigNumber(5, 17); + const withdrawAmountMim = getBigNumber(1, 18); + + // deposit + // await this.b.connect(this.bob).approve(bamm.address, depositAmonut); + await setMasterContractApproval(this.bentoBox, this.bob, this.bob, this.bobPrivateKey, bamm.address, true) + await bamm.connect(this.bob).deposit(depositAmonut, true); + + x = await getBentoBoxBalance(this, this.b.address, this.bob.address) + console.log('bobBentoBal post deposit', x.toString()) + + + expect(await bamm.balanceOf(this.bob.address)).to.be.equal(getBigNumber(1, 18)) + expect((await getBentoBoxBalance(this, this.b.address, this.bob.address)).add(depositAmonut)).to.be.equal(bobBentoBalBefore) + expect((await getBentoBoxBalance(this, this.b.address, bamm.address))).to.be.equal(depositAmonut) + + // withdraw + await bamm.connect(this.bob).withdraw(withdrawAmountShare, true) + + x = await this.bentoBox.balanceOf(this.b.address, this.bob.address) + console.log('bobBentoBal post withdraw', x.toString()) + + expect((await getBentoBoxBalance(this, this.b.address, this.bob.address)).add(depositAmonut.sub(withdrawAmountMim))).to.be.equal(bobBentoBalBefore) + expect((await getBentoBoxBalance(this, this.b.address, bamm.address))).to.be.equal(depositAmonut.sub(withdrawAmountMim)) + + // deposit with alice + await setMasterContractApproval(this.bentoBox, this.alice, this.alice, this.alicePrivateKey, bamm.address, true) + await bamm.connect(this.alice).deposit(depositAmonut, true) + + expect(await bamm.balanceOf(this.alice.address)).to.be.equal(getBigNumber(1, 18)) // 1 shares + }) + it("deposit and withdraw also with collateral", async function () { const bamm = this.BAMM const depositAmonut = getBigNumber(11, 17); From 76d0cd6671bf95c25541d1c4d8d85e1e682e6f12 Mon Sep 17 00:00:00 2001 From: shmuel Date: Wed, 27 Oct 2021 13:08:54 +0300 Subject: [PATCH 10/24] cleanUp + adding a test for bob's bamm share --- test/BProtocol.js | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/test/BProtocol.js b/test/BProtocol.js index 6806076..370de77 100644 --- a/test/BProtocol.js +++ b/test/BProtocol.js @@ -728,8 +728,6 @@ describe("KashiPair Basic", function () { await this.b.connect(this.alice).approve(this.bentoBox.address, depositAmonut); await this.bentoBox.connect(this.alice).deposit(this.b.address, this.alice.address, this.alice.address, depositAmonut, 0) - x = await getBentoBoxBalance(this, this.b.address, this.bob.address) - console.log('bobBentoBal post bento', x.toString()) const bobBentoBalBefore = await getBentoBoxBalance(this, this.b.address, this.bob.address) console.log("Alice Asset in Bento", (await getBentoBoxBalance(this, this.b.address, this.alice.address)).toString()) @@ -742,10 +740,6 @@ describe("KashiPair Basic", function () { // await this.b.connect(this.bob).approve(bamm.address, depositAmonut); await setMasterContractApproval(this.bentoBox, this.bob, this.bob, this.bobPrivateKey, bamm.address, true) await bamm.connect(this.bob).deposit(depositAmonut, true); - - x = await getBentoBoxBalance(this, this.b.address, this.bob.address) - console.log('bobBentoBal post deposit', x.toString()) - expect(await bamm.balanceOf(this.bob.address)).to.be.equal(getBigNumber(1, 18)) expect((await getBentoBoxBalance(this, this.b.address, this.bob.address)).add(depositAmonut)).to.be.equal(bobBentoBalBefore) @@ -754,16 +748,13 @@ describe("KashiPair Basic", function () { // withdraw await bamm.connect(this.bob).withdraw(withdrawAmountShare, true) - x = await this.bentoBox.balanceOf(this.b.address, this.bob.address) - console.log('bobBentoBal post withdraw', x.toString()) - expect((await getBentoBoxBalance(this, this.b.address, this.bob.address)).add(depositAmonut.sub(withdrawAmountMim))).to.be.equal(bobBentoBalBefore) expect((await getBentoBoxBalance(this, this.b.address, bamm.address))).to.be.equal(depositAmonut.sub(withdrawAmountMim)) - + expect((await bamm.balanceOf(this.bob.address))).to.be.equal(withdrawAmountShare) + // deposit with alice await setMasterContractApproval(this.bentoBox, this.alice, this.alice, this.alicePrivateKey, bamm.address, true) await bamm.connect(this.alice).deposit(depositAmonut, true) - expect(await bamm.balanceOf(this.alice.address)).to.be.equal(getBigNumber(1, 18)) // 1 shares }) From 3915ce9c6a1342c135f8b6954b2fe478cb2c0909 Mon Sep 17 00:00:00 2001 From: shmuel Date: Wed, 27 Oct 2021 13:24:18 +0300 Subject: [PATCH 11/24] cleanUp --- test/BProtocol.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/BProtocol.js b/test/BProtocol.js index 370de77..55c5bd1 100644 --- a/test/BProtocol.js +++ b/test/BProtocol.js @@ -719,7 +719,7 @@ describe("KashiPair Basic", function () { expect(await bamm.balanceOf(this.alice.address)).to.be.equal(getBigNumber(1, 18)) // 1 shares }) - it.only("deposit and withdraw only with mim via bentoBox", async function () { + it("deposit and withdraw only with mim via bentoBox", async function () { const depositAmonut = getBigNumber(2, 18); // bob bento deposit await this.b.connect(this.bob).approve(this.bentoBox.address, depositAmonut); From 7ce78aaf219068e379392ea0f85b088901c89770 Mon Sep 17 00:00:00 2001 From: shmuel Date: Wed, 27 Oct 2021 15:43:46 +0300 Subject: [PATCH 12/24] deposit and withdraw also with collateral via bentoBox --- test/BProtocol.js | 62 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/test/BProtocol.js b/test/BProtocol.js index 55c5bd1..9745af6 100644 --- a/test/BProtocol.js +++ b/test/BProtocol.js @@ -737,7 +737,6 @@ describe("KashiPair Basic", function () { const withdrawAmountMim = getBigNumber(1, 18); // deposit - // await this.b.connect(this.bob).approve(bamm.address, depositAmonut); await setMasterContractApproval(this.bentoBox, this.bob, this.bob, this.bobPrivateKey, bamm.address, true) await bamm.connect(this.bob).deposit(depositAmonut, true); @@ -799,6 +798,67 @@ describe("KashiPair Basic", function () { expect(aliceColBalBefore.add(expectedColDelta.sub(1))).to.be.equal(aliceColBalAfter) expect(aliceMimBalBefore.add(expectedMimDelta)).to.be.equal(aliceMimBalAfter) }) + + const isEqualWithRoundingErrorFlexability = (a, b, rounding) => { + if(a.gte(b)){ + return a.sub(b).lte(rounding) + }else{ + return b.sub(a).lte(rounding) + } + } + + it("deposit and withdraw also with collateral via bentoBox", async function () { + const bamm = this.BAMM + const depositAmonut = getBigNumber(11, 17); + const aliceDepositAmonut = getBigNumber(22, 17); + + // bob bento deposit + await this.b.connect(this.bob).approve(this.bentoBox.address, depositAmonut); + await this.bentoBox.connect(this.bob).deposit(this.b.address, this.bob.address, this.bob.address, depositAmonut, 0) + // alice bento deposit + await this.b.connect(this.alice).approve(this.bentoBox.address, aliceDepositAmonut); + await this.bentoBox.connect(this.alice).deposit(this.b.address, this.alice.address, this.alice.address, aliceDepositAmonut, 0) + + // deposit + await setMasterContractApproval(this.bentoBox, this.bob, this.bob, this.bobPrivateKey, bamm.address, true) + await bamm.connect(this.bob).deposit(depositAmonut, true); + + // transfer collateral + await this.a.connect(this.bob).approve(this.bentoBox.address, getBigNumber(1, 18)) + await this.bentoBox.connect(this.bob).deposit(this.a.address, this.bob.address, bamm.address, getBigNumber(1, 18), 0) + // //await this.a.connect(this.bob).transfer(bamm.address, getBigNumber(1, 18)) + await this.oracle.connect(this.alice).set(getBigNumber(11, 17).toString()) + + // now there are 1.1 of mim, and 1.1 worth of collateral + + // deposit 2.2 of mim + await setMasterContractApproval(this.bentoBox, this.alice, this.alice, this.alicePrivateKey, bamm.address, true) + await bamm.connect(this.alice).deposit(aliceDepositAmonut, true) + + expect((await bamm.balanceOf(this.alice.address))).to.be.equal(getBigNumber(1, 18)) + + // now there are 3.3 of mim and 1.1 worth of collateral + + // withdraw half the deposit. get 3.3/4 = 0.825 mim and 1.0/4 = 0.25 of collateral + const aliceMimBentoBalBefore = await getBentoBoxBalance(this, this.b.address, this.alice.address) + const aliceColBentoBalBefore = await getBentoBoxBalance(this, this.a.address, this.alice.address) + + await bamm.connect(this.alice).withdraw(getBigNumber(5, 17), true) + + const aliceMimBentoBalAfter = await getBentoBoxBalance(this, this.b.address, this.alice.address) + const aliceColBentoBalAfter = await getBentoBoxBalance(this, this.a.address, this.alice.address) + + + const expectedMimDelta = getBigNumber(825, 15) + const expectedColDelta = getBigNumber(25, 16) + + // sub 1 to compensate on rounding errors + const roundingErrorFlex = getBigNumber(5, 0) + expect(isEqualWithRoundingErrorFlexability(aliceColBentoBalBefore.add(expectedColDelta), aliceColBentoBalAfter, roundingErrorFlex)).to.be.equal(true) + expect(aliceMimBentoBalBefore.add(expectedMimDelta)).to.be.equal(aliceMimBentoBalAfter) + }) + + /* it('test getSwapEthAmount', async () => { // --- SETUP --- From 0832eab86716a4a353a696d40bbc38822e09d793 Mon Sep 17 00:00:00 2001 From: shmuel Date: Wed, 27 Oct 2021 16:26:20 +0300 Subject: [PATCH 13/24] swap via bentoBox --- test/BProtocol.js | 53 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/test/BProtocol.js b/test/BProtocol.js index 9745af6..04ce91f 100644 --- a/test/BProtocol.js +++ b/test/BProtocol.js @@ -679,6 +679,14 @@ describe("KashiPair Basic", function () { }) }) + const isEqualWithRoundingErrorFlexability = (a, b, rounding) => { + if(a.gte(b)){ + return a.sub(b).lte(rounding) + }else{ + return b.sub(a).lte(rounding) + } + } + describe.only("bamm", function () { async function getBentoBoxBalance(thisObject, token, address) { const share = await thisObject.bentoBox.balanceOf(token, address) @@ -799,14 +807,6 @@ describe("KashiPair Basic", function () { expect(aliceMimBalBefore.add(expectedMimDelta)).to.be.equal(aliceMimBalAfter) }) - const isEqualWithRoundingErrorFlexability = (a, b, rounding) => { - if(a.gte(b)){ - return a.sub(b).lte(rounding) - }else{ - return b.sub(a).lte(rounding) - } - } - it("deposit and withdraw also with collateral via bentoBox", async function () { const bamm = this.BAMM const depositAmonut = getBigNumber(11, 17); @@ -945,6 +945,43 @@ describe("KashiPair Basic", function () { expect(await getBentoBoxBalance(this, this.b.address, feePool)).to.be.equal(wad.div(100)) }) + it.only("swap via bentoBox", async function () { + const bamm = this.BAMM + const mimAmonut = getBigNumber(600, 18) + const colAmount = "3979999999999999997" // almost 4e17 + const price = getBigNumber(105, 18) + const wad = getBigNumber(105, 17) + + // deposit + await this.b.connect(this.bob).approve(bamm.address, mimAmonut); + await bamm.connect(this.bob).deposit(mimAmonut, false); + + // transfer collateral + await this.a.connect(this.bob).approve(this.bentoBox.address, colAmount) + await this.bentoBox.connect(this.bob).deposit(this.a.address, this.bob.address, bamm.address, colAmount, 0) + //await this.a.connect(this.bob).transfer(bamm.address, getBigNumber(1, 18)) + await this.oracle.connect(this.alice).set(price.toString()) + await bamm.fetchPrice() + + // with fee + await bamm.setParams(200, 100, 0) + const expectedCol = await bamm.getSwapGemAmount(wad) + + // set up bentoBox + await this.b.connect(this.bob).approve(this.bentoBox.address, wad); + await this.bentoBox.connect(this.bob).deposit(this.b.address, this.bob.address, this.bob.address, wad, 0) + + // do the swap + await setMasterContractApproval(this.bentoBox, this.bob, this.bob, this.bobPrivateKey, bamm.address, true) + const dest = "0x0000000000000000000000000000000000000007" + + await bamm.connect(this.bob).swap(wad, 1, dest, true) + + const roundingErrorFlex = getBigNumber(2, 0) + expect(isEqualWithRoundingErrorFlexability((await getBentoBoxBalance(this, this.a.address, dest)), expectedCol, roundingErrorFlex)).to.be.true + expect(await getBentoBoxBalance(this, this.b.address, feePool)).to.be.equal(wad.div(100)) + }) + it("liquidate via bprotocol", async function () { const bamm = this.BAMM From cb75c397ad8e2de614f74cf337cfbb8223bb18dc Mon Sep 17 00:00:00 2001 From: shmuel Date: Wed, 27 Oct 2021 16:26:48 +0300 Subject: [PATCH 14/24] cleanUp --- test/BProtocol.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/BProtocol.js b/test/BProtocol.js index 04ce91f..2029a36 100644 --- a/test/BProtocol.js +++ b/test/BProtocol.js @@ -945,7 +945,7 @@ describe("KashiPair Basic", function () { expect(await getBentoBoxBalance(this, this.b.address, feePool)).to.be.equal(wad.div(100)) }) - it.only("swap via bentoBox", async function () { + it("swap via bentoBox", async function () { const bamm = this.BAMM const mimAmonut = getBigNumber(600, 18) const colAmount = "3979999999999999997" // almost 4e17 From af8085e47e9a09d230a3c082429a6c13d5c8477a Mon Sep 17 00:00:00 2001 From: shmuel Date: Wed, 27 Oct 2021 16:28:17 +0300 Subject: [PATCH 15/24] cleanUp --- test/BProtocol.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/BProtocol.js b/test/BProtocol.js index 2029a36..a000f1c 100644 --- a/test/BProtocol.js +++ b/test/BProtocol.js @@ -738,8 +738,8 @@ describe("KashiPair Basic", function () { const bobBentoBalBefore = await getBentoBoxBalance(this, this.b.address, this.bob.address) - console.log("Alice Asset in Bento", (await getBentoBoxBalance(this, this.b.address, this.alice.address)).toString()) - console.log("Bob Asset in Bento", (bobBentoBalBefore).toString()) + // console.log("Alice Asset in Bento", (await getBentoBoxBalance(this, this.b.address, this.alice.address)).toString()) + // console.log("Bob Asset in Bento", (bobBentoBalBefore).toString()) const bamm = this.BAMM const withdrawAmountShare = getBigNumber(5, 17); const withdrawAmountMim = getBigNumber(1, 18); From bd7ca795f302526c8c143ee294a7a7f759ad30c1 Mon Sep 17 00:00:00 2001 From: shmuel Date: Sun, 31 Oct 2021 15:37:56 +0200 Subject: [PATCH 16/24] liquidate like a tiran + bugFix + happy path test --- contracts/B.Protocol/BAMM.sol | 13 +++---- test/BProtocol.js | 67 +++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 6 deletions(-) diff --git a/contracts/B.Protocol/BAMM.sol b/contracts/B.Protocol/BAMM.sol index 2b388fc..6f3c67a 100644 --- a/contracts/B.Protocol/BAMM.sol +++ b/contracts/B.Protocol/BAMM.sol @@ -210,7 +210,7 @@ contract BAMM is PriceFormula, BoringOwnable, ERC20 { uint callerReward = mimBalanceBefore.sub(mimBalanceAfter).mul(callerFee) / 10000; - if(mimBalanceAfter >= callerReward) { + if((mimBalanceAfter >= callerReward) && (to != address(0))) { sendToken(mim, to, callerReward, true); mimBalanceAfter = mimBalanceAfter.sub(callerReward); } @@ -226,22 +226,23 @@ contract BAMM is PriceFormula, BoringOwnable, ERC20 { ) public { // take the mim receiveToken(mim, extraMim, viaBentobox); - // do the liquidation (uint mimBalanceBefore, uint collatBalanceBefore, uint mimBalanceAfter, uint collatBalanceAfter) = - liquidate(users, maxBorrowParts, to, swapper); + liquidate(users, maxBorrowParts, address(0), swapper); if(extraMim <= mimBalanceAfter) { sendToken(mim, msg.sender, extraMim, viaBentobox); return; } + uint liquidationSize = mimBalanceBefore.sub(mimBalanceAfter); + require(liquidationSize <= extraMim, "liquidateLikeTiran: insufficent extraMim"); // send mim leftover to liquidator - if(mimBalanceBefore.sub(mimBalanceAfter) >= extraMim) { - uint returnAmount = mimBalanceBefore.sub(mimBalanceAfter).sub(extraMim); + if(liquidationSize < extraMim.sub(1)) { + uint returnAmount = extraMim.sub(liquidationSize); + returnAmount = returnAmount.sub(1); sendToken(mim, msg.sender, returnAmount, viaBentobox); } - // send collateral to liquidator sendToken(collateral, to, collatBalanceAfter.sub(collatBalanceBefore), viaBentobox); } diff --git a/test/BProtocol.js b/test/BProtocol.js index a000f1c..0902a95 100644 --- a/test/BProtocol.js +++ b/test/BProtocol.js @@ -1020,6 +1020,8 @@ describe("KashiPair Basic", function () { const pairMimBalBefore = await getBentoBoxBalance(this, this.b.address, this.pairHelper.contract.address) const nullAddr = "0x0000000000000000000000000000000000000000" const rewardAddress = "0x0000000000000000000000000000000000000007" + + // liquidate await bamm.connect(this.bob).liquidate([this.alice.address], [liquidationShare], rewardAddress, nullAddr) const bammMimBalAfter = await getBentoBoxBalance(this, this.b.address, bamm.address) const bammColBalAfter = await getBentoBoxBalance(this, this.a.address, bamm.address) @@ -1040,10 +1042,74 @@ describe("KashiPair Basic", function () { expect(deltaMimWithPermium.mul(price).div(getBigNumber(1,18)).div(roundingFactor)).to.be.equal(deltaCol.div(roundingFactor)) }) + it("liquidateLikeTiran", async function () { + const bamm = this.BAMM + const price = getBigNumber(11, 27); + + // bob bento deposit setup + const depositAmonut = getBigNumber(1000, 0) + await this.b.connect(this.bob).approve(this.bentoBox.address, depositAmonut); + await this.bentoBox.connect(this.bob).deposit(this.b.address, this.bob.address, this.bob.address, depositAmonut, 0) + await setMasterContractApproval(this.bentoBox, this.bob, this.bob, this.bobPrivateKey, bamm.address, true) + await bamm.connect(this.bob).deposit(depositAmonut, true); + + + await this.pairHelper.run((cmd) => [ + cmd.as(this.bob).approveAsset(getBigNumber(310, 8)), + cmd.as(this.bob).depositAsset(getBigNumber(290, 8)), + cmd.approveCollateral(getBigNumber(100)), + cmd.depositCollateral(getBigNumber(100)), + cmd.borrow(sansBorrowFee(getBigNumber(75, 8))), + cmd.accrue(), + cmd.do(this.oracle.set, price.toString()), + cmd.updateExchangeRate(), + cmd.do(this.bentoBox.connect(this.bob).deposit, this.b.address, this.bob.address, this.bob.address, getBigNumber(20, 8), 0), + cmd.do(this.pairHelper.contract.connect(this.bob).removeAsset, this.bob.address, getBigNumber(50, 8)), + ]) + + await bamm.setParams(20, 0, 100) + + // deposit + const liquidationShare = await this.pairHelper.contract.userBorrowPart(this.alice.address) //getBigNumber(20, 18); + const liquidationAmount = (await toAmount(this, this.b.address, liquidationShare)).add(11) + const bammMimBalBefore = await getBentoBoxBalance(this, this.b.address, bamm.address) + const bobMimBalBefore = await this.b.balanceOf(this.bob.address) + const bammColBalBefore = await getBentoBoxBalance(this, this.a.address, bamm.address) + const pairMimBalBefore = await getBentoBoxBalance(this, this.b.address, this.pairHelper.contract.address) + const nullAddr = "0x0000000000000000000000000000000000000000" + const rewardAddress = "0x0000000000000000000000000000000000000007" + const rewardBalanceBefore = await getBentoBoxBalance(this, this.b.address, rewardAddress) + + // liquidate + await this.b.connect(this.bob).approve(bamm.address, liquidationAmount); + await bamm.connect(this.bob).liquidateLikeTiran(liquidationAmount, [this.alice.address], [liquidationShare], rewardAddress, nullAddr, false) + const bammMimBalAfter = await getBentoBoxBalance(this, this.b.address, bamm.address) + const bammColBalAfter = await getBentoBoxBalance(this, this.a.address, bamm.address) + const pairMimBalAfter = await getBentoBoxBalance(this, this.b.address, this.pairHelper.contract.address) + + const deltaMimBamm = bammMimBalBefore.sub(bammMimBalAfter) + const deltaMimPair = pairMimBalAfter.sub(pairMimBalBefore) + const bobMimBalAfter = await this.b.balanceOf(this.bob.address) + const rewardBalance = await getBentoBoxBalance(this, this.b.address, rewardAddress) + + // check bamm MIM is the same + expect(bammMimBalAfter).to.be.equal(bammMimBalBefore) + + // check reward address contains collateral & mim bonus is 1% of the liquidation + expect(rewardBalance).to.be.equal(0) + + expect(isEqualWithRoundingErrorFlexability(bobMimBalBefore.sub(deltaMimPair), bobMimBalAfter, 2)).to.be.true + const liquidatedCollateral = bammColBalAfter.sub(bammColBalBefore) + const rewardBalanceCol = await getBentoBoxBalance(this, this.b.address, rewardAddress) + + expect(liquidatedCollateral).to.be.equal(rewardBalanceCol) + }) + // liquidate normal test - TODO }) describe.only("Liquidate", function () { + it("should not allow open liquidate yet", async function () { await this.pairHelper.run((cmd) => [ cmd.as(this.bob).approveAsset(getBigNumber(310, 8)), @@ -1173,6 +1239,7 @@ describe("KashiPair Basic", function () { .liquidate([this.alice.address], [getBigNumber(20, 8)], this.invalidSwapper.address, this.invalidSwapper.address, false) ).to.be.revertedWith("KashiPair: Invalid swapper") }) + }) describe("Withdraw Fees", function () { From bddd3626831b7e1984a17aa62086aa16d761f166 Mon Sep 17 00:00:00 2001 From: shmuel Date: Mon, 1 Nov 2021 15:10:25 +0200 Subject: [PATCH 17/24] liquidate like tiran --- test/BProtocol.js | 80 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 73 insertions(+), 7 deletions(-) diff --git a/test/BProtocol.js b/test/BProtocol.js index 0902a95..95a0c69 100644 --- a/test/BProtocol.js +++ b/test/BProtocol.js @@ -1090,19 +1090,85 @@ describe("KashiPair Basic", function () { const deltaMimBamm = bammMimBalBefore.sub(bammMimBalAfter) const deltaMimPair = pairMimBalAfter.sub(pairMimBalBefore) const bobMimBalAfter = await this.b.balanceOf(this.bob.address) - const rewardBalance = await getBentoBoxBalance(this, this.b.address, rewardAddress) - + + const rewardBalance = await this.a.balanceOf(rewardAddress) // check bamm MIM is the same expect(bammMimBalAfter).to.be.equal(bammMimBalBefore) // check reward address contains collateral & mim bonus is 1% of the liquidation - expect(rewardBalance).to.be.equal(0) - expect(isEqualWithRoundingErrorFlexability(bobMimBalBefore.sub(deltaMimPair), bobMimBalAfter, 2)).to.be.true - const liquidatedCollateral = bammColBalAfter.sub(bammColBalBefore) - const rewardBalanceCol = await getBentoBoxBalance(this, this.b.address, rewardAddress) - expect(liquidatedCollateral).to.be.equal(rewardBalanceCol) + expect(bammColBalBefore).to.be.equal(bammColBalAfter) // no collateral added to the BAMM + + const roundingFactor = getBigNumber(1, 11); + const deltaMimWithPermium = deltaMimPair.mul(112).div(100) + const collateralInMim = deltaMimWithPermium.mul(price).div(getBigNumber(1,18)).div(roundingFactor) + const roundedRewardBalance = rewardBalance.div(roundingFactor) + + expect(collateralInMim).to.be.equal(roundedRewardBalance) + }) + + it("liquidateLikeTiran via bentoBox", async function () { + const bamm = this.BAMM + const price = getBigNumber(11, 27); + + // bamm initial deposit setup + const depositAmonut = getBigNumber(1000, 0) + await this.b.connect(this.bob).approve(this.bentoBox.address, depositAmonut); + await this.bentoBox.connect(this.bob).deposit(this.b.address, this.bob.address, this.bob.address, depositAmonut, 0) + await setMasterContractApproval(this.bentoBox, this.bob, this.bob, this.bobPrivateKey, bamm.address, true) + await bamm.connect(this.bob).deposit(depositAmonut, true); + + await this.pairHelper.run((cmd) => [ + cmd.as(this.bob).approveAsset(getBigNumber(310, 8)), + cmd.as(this.bob).depositAsset(getBigNumber(290, 8)), + cmd.approveCollateral(getBigNumber(100)), + cmd.depositCollateral(getBigNumber(100)), + cmd.borrow(sansBorrowFee(getBigNumber(75, 8))), + cmd.accrue(), + cmd.do(this.oracle.set, price.toString()), + cmd.updateExchangeRate(), + cmd.do(this.bentoBox.connect(this.bob).deposit, this.b.address, this.bob.address, this.bob.address, getBigNumber(20, 8), 0), + cmd.do(this.pairHelper.contract.connect(this.bob).removeAsset, this.bob.address, getBigNumber(50, 8)), + ]) + + await bamm.setParams(20, 0, 100) + + const liquidationShare = await this.pairHelper.contract.userBorrowPart(this.alice.address) //getBigNumber(20, 18); + const liquidationAmount = (await toAmount(this, this.b.address, liquidationShare)).add(11) + + await this.b.connect(this.bob).approve(this.bentoBox.address, liquidationAmount.mul(2)); + await this.bentoBox.connect(this.bob).deposit(this.b.address, this.bob.address, this.bob.address, liquidationAmount.mul(2), 0) + + const bammMimBalBefore = await getBentoBoxBalance(this, this.b.address, bamm.address) + const bobMimBentoBalBefore = await getBentoBoxBalance(this, this.b.address, this.bob.address) + const pairMimBalBefore = await getBentoBoxBalance(this, this.b.address, this.pairHelper.contract.address) + const nullAddr = "0x0000000000000000000000000000000000000000" + const rewardAddress = "0x0000000000000000000000000000000000000007" + const rewardBalanceBefore = await getBentoBoxBalance(this, this.b.address, rewardAddress) + + // liquidate + await this.b.connect(this.bob).approve(bamm.address, liquidationAmount); + await bamm.connect(this.bob).liquidateLikeTiran(liquidationAmount, [this.alice.address], [liquidationShare], rewardAddress, nullAddr, true) + const bammMimBalAfter = await getBentoBoxBalance(this, this.b.address, bamm.address) + const pairMimBalAfter = await getBentoBoxBalance(this, this.b.address, this.pairHelper.contract.address) + + const deltaMimBamm = bammMimBalBefore.sub(bammMimBalAfter) + const deltaMimPair = pairMimBalAfter.sub(pairMimBalBefore) + const bobMimBalAfter = await this.b.balanceOf(this.bob.address) + const bobMimBentoBalAfter = await getBentoBoxBalance(this, this.b.address, this.bob.address) + const rewardBalance = await getBentoBoxBalance(this, this.a.address, rewardAddress) + // check bamm MIM is the same + expect(bammMimBalAfter).to.be.equal(bammMimBalBefore) + + expect(isEqualWithRoundingErrorFlexability(bobMimBentoBalBefore.sub(deltaMimPair), bobMimBentoBalAfter, 2)).to.be.true + + const roundingFactor = getBigNumber(1, 11); + const deltaMimWithPermium = deltaMimPair.mul(112).div(100) + const collateralInMim = deltaMimWithPermium.mul(price).div(getBigNumber(1,18)).div(roundingFactor) + const roundedRewardBalance = rewardBalance.div(roundingFactor) + + expect(collateralInMim).to.be.equal(roundedRewardBalance) }) // liquidate normal test - TODO From 65ab6e1db2daf58054bbeeef78ed60f128252f63 Mon Sep 17 00:00:00 2001 From: shmuel Date: Wed, 3 Nov 2021 09:28:30 +0200 Subject: [PATCH 18/24] liquidateLikeTiran --- test/BProtocol.js | 378 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 266 insertions(+), 112 deletions(-) diff --git a/test/BProtocol.js b/test/BProtocol.js index 95a0c69..c8130d8 100644 --- a/test/BProtocol.js +++ b/test/BProtocol.js @@ -687,15 +687,16 @@ describe("KashiPair Basic", function () { } } - describe.only("bamm", function () { - async function getBentoBoxBalance(thisObject, token, address) { - const share = await thisObject.bentoBox.balanceOf(token, address) - return await thisObject.bentoBox.toAmount(token, share, false) - } + async function getBentoBoxBalance(thisObject, token, address) { + const share = await thisObject.bentoBox.balanceOf(token, address) + return await thisObject.bentoBox.toAmount(token, share, false) + } - async function toAmount(thisObject, token, share) { - return await thisObject.bentoBox.toAmount(token, share, false) - } + async function toAmount(thisObject, token, share) { + return await thisObject.bentoBox.toAmount(token, share, false) + } + + describe("bamm", function () { it("deposit and withdraw only with mim", async function () { const bamm = this.BAMM @@ -1041,7 +1042,145 @@ describe("KashiPair Basic", function () { const roundingFactor = getBigNumber(1, 11); expect(deltaMimWithPermium.mul(price).div(getBigNumber(1,18)).div(roundingFactor)).to.be.equal(deltaCol.div(roundingFactor)) }) + }) + + describe("Liquidate", function () { + + it("should not allow open liquidate yet", async function () { + await this.pairHelper.run((cmd) => [ + cmd.as(this.bob).approveAsset(getBigNumber(310, 8)), + cmd.as(this.bob).depositAsset(getBigNumber(290, 8)), + cmd.approveCollateral(getBigNumber(100)), + cmd.depositCollateral(getBigNumber(100)), + cmd.borrow(sansBorrowFee(getBigNumber(75, 8))), + cmd.accrue(), + cmd.do(this.bentoBox.connect(this.bob).deposit, this.b.address, this.bob.address, this.bob.address, getBigNumber(20, 8), 0), + ]) + + await expect( + this.pairHelper.contract + .connect(this.bob) + .liquidate([this.alice.address], [getBigNumber(20, 8)], this.bob.address, "0x0000000000000000000000000000000000000000", true) + ).to.be.revertedWith("KashiPair: all are solvent") + }) + it("should allow open liquidate", async function () { + await this.pairHelper.run((cmd) => [ + cmd.as(this.bob).approveAsset(getBigNumber(310, 8)), + cmd.as(this.bob).depositAsset(getBigNumber(290, 8)), + cmd.approveCollateral(getBigNumber(100)), + cmd.depositCollateral(getBigNumber(100)), + cmd.borrow(sansBorrowFee(getBigNumber(75, 8))), + cmd.accrue(), + cmd.do(this.oracle.set, "11000000000000000000000000000"), + cmd.updateExchangeRate(), + cmd.do(this.bentoBox.connect(this.bob).deposit, this.b.address, this.bob.address, this.bob.address, getBigNumber(20, 8), 0), + cmd.do(this.pairHelper.contract.connect(this.bob).removeAsset, this.bob.address, getBigNumber(50, 8)), + ]) + await this.pairHelper.contract + .connect(this.bob) + .liquidate([this.alice.address], [getBigNumber(20, 8)], this.bob.address, "0x0000000000000000000000000000000000000000", true) + }) + + it("should not allow liquidate for non b.protocol", async function () { + //await this.BkashiPair.setBProtocol(this.bob.address) + //console.log(this.BKashiPair) + + await this.pairHelper.contract.connect(this.alice).setBProtocolMock(this.alice.address) + + await this.pairHelper.run((cmd) => [ + cmd.as(this.bob).approveAsset(getBigNumber(310, 8)), + cmd.as(this.bob).depositAsset(getBigNumber(290, 8)), + cmd.approveCollateral(getBigNumber(100)), + cmd.depositCollateral(getBigNumber(100)), + cmd.borrow(sansBorrowFee(getBigNumber(75, 8))), + cmd.accrue(), + cmd.do(this.oracle.set, "11000000000000000000000000000"), + cmd.updateExchangeRate(), + cmd.do(this.bentoBox.connect(this.bob).deposit, this.b.address, this.bob.address, this.bob.address, getBigNumber(20, 8), 0), + cmd.do(this.pairHelper.contract.connect(this.bob).removeAsset, this.bob.address, getBigNumber(50, 8)), + ]) + await expect( + this.pairHelper.contract + .connect(this.bob) + .liquidate([this.alice.address], [getBigNumber(20, 8)], this.bob.address, "0x0000000000000000000000000000000000000000", true) + ).to.be.revertedWith("liquidate: not bprotocol") + }) + + it("should allow open liquidate with swapper", async function () { + await this.pairHelper.run((cmd) => [ + cmd.as(this.bob).approveAsset(getBigNumber(310, 8)), + cmd.as(this.bob).depositAsset(getBigNumber(290, 8)), + cmd.approveCollateral(getBigNumber(100)), + cmd.depositCollateral(getBigNumber(100)), + cmd.borrow(sansBorrowFee(getBigNumber(75, 8))), + cmd.accrue(), + cmd.do(this.oracle.set, "11000000000000000000000000000"), + cmd.updateExchangeRate(), + cmd.do(this.bentoBox.connect(this.bob).deposit, this.b.address, this.bob.address, this.bob.address, getBigNumber(20, 8), 0), + ]) + await expect( + this.pairHelper.contract + .connect(this.bob) + .liquidate([this.alice.address], [getBigNumber(20, 8)], this.swapper.address, this.swapper.address, true) + ) + .to.emit(this.pairHelper.contract, "LogRemoveCollateral") + .to.emit(this.pairHelper.contract, "LogRepay") + }) + + it("should allow closed liquidate", async function () { + await this.pairHelper.run((cmd) => [ + // Bob adds 290 asset amount (145 shares) + cmd.as(this.bob).approveAsset(getBigNumber(310, 8)), + cmd.as(this.bob).depositAsset(getBigNumber(290, 8)), + // Alice adds 100 collateral amount (76 shares) + cmd.approveCollateral(getBigNumber(100)), + cmd.depositCollateral(getBigNumber(100)), + // Alice borrows 75 asset amount + cmd.borrow(sansBorrowFee(getBigNumber(75, 8))), + cmd.accrue(), + // Change oracle to put Alice into insolvency + cmd.do(this.oracle.set, "11000000000000000000000000000"), + //cmd.do(this.a.transfer, this.sushiSwapPair.address, getBigNumber(500)), + //cmd.do(this.sushiSwapPair.sync), + cmd.updateExchangeRate(), + ]) + + // Bob liquidates Alice for 20 asset parts (approx 20 asset amount = 10 asset shares) + await this.pairHelper.contract + .connect(this.bob) + .liquidate([this.alice.address], [getBigNumber(20, 8)], this.swapper.address, this.swapper.address, false) + }) + + it("should not allow closed liquidate with invalid swapper", async function () { + await this.pairHelper.run((cmd) => [ + cmd.as(this.bob).approveAsset(getBigNumber(340, 8)), + cmd.as(this.bob).depositAsset(getBigNumber(290, 8)), + cmd.approveCollateral(getBigNumber(100)), + cmd.depositCollateral(getBigNumber(100)), + cmd.borrow(sansBorrowFee(getBigNumber(75, 8))), + cmd.accrue(), + ]) + + await cmd.deploy( + "invalidSwapper", + "SushiSwapSwapper", + this.bentoBox.address, + this.factory.address, + await this.factory.pairCodeHash() + ) + await expect( + this.pairHelper.contract + .connect(this.bob) + .liquidate([this.alice.address], [getBigNumber(20, 8)], this.invalidSwapper.address, this.invalidSwapper.address, false) + ).to.be.revertedWith("KashiPair: Invalid swapper") + }) + + }) + + + describe.only('liquidateLikeTiran', function () { + it("liquidateLikeTiran", async function () { const bamm = this.BAMM const price = getBigNumber(11, 27); @@ -1078,7 +1217,6 @@ describe("KashiPair Basic", function () { const pairMimBalBefore = await getBentoBoxBalance(this, this.b.address, this.pairHelper.contract.address) const nullAddr = "0x0000000000000000000000000000000000000000" const rewardAddress = "0x0000000000000000000000000000000000000007" - const rewardBalanceBefore = await getBentoBoxBalance(this, this.b.address, rewardAddress) // liquidate await this.b.connect(this.bob).approve(bamm.address, liquidationAmount); @@ -1108,6 +1246,77 @@ describe("KashiPair Basic", function () { expect(collateralInMim).to.be.equal(roundedRewardBalance) }) + it("when BAMM has sufficient funds liquidateLikeTiran should return tiran money", async function () { + const bamm = this.BAMM + const price = getBigNumber(11, 27); + + await this.pairHelper.run((cmd) => [ + cmd.as(this.bob).approveAsset(getBigNumber(310, 8)), + cmd.as(this.bob).depositAsset(getBigNumber(290, 8)), + cmd.approveCollateral(getBigNumber(100)), + cmd.depositCollateral(getBigNumber(100)), + cmd.borrow(sansBorrowFee(getBigNumber(75, 8))), + cmd.accrue(), + cmd.do(this.oracle.set, price.toString()), + cmd.updateExchangeRate(), + cmd.do(this.bentoBox.connect(this.bob).deposit, this.b.address, this.bob.address, this.bob.address, getBigNumber(20, 8), 0), + cmd.do(this.pairHelper.contract.connect(this.bob).removeAsset, this.bob.address, getBigNumber(50, 8)), + ]) + + await bamm.setParams(20, 0, 100) + + // deposit + const liquidationShare = await this.pairHelper.contract.userBorrowPart(this.alice.address) //getBigNumber(20, 18); + const liquidationAmount = (await toAmount(this, this.b.address, liquidationShare)).add(11) + + // making sure bamm has sufficient funds + const depositAmonut = liquidationAmount + await this.b.connect(this.bob).approve(this.bentoBox.address, depositAmonut); + await this.bentoBox.connect(this.bob).deposit(this.b.address, this.bob.address, this.bob.address, depositAmonut, 0) + await setMasterContractApproval(this.bentoBox, this.bob, this.bob, this.bobPrivateKey, bamm.address, true) + await bamm.connect(this.bob).deposit(depositAmonut, true); + + const bammMimBalBefore = await getBentoBoxBalance(this, this.b.address, bamm.address) + const bobMimBalBefore = await this.b.balanceOf(this.bob.address) + const bammColBalBefore = await getBentoBoxBalance(this, this.a.address, bamm.address) + const pairMimBalBefore = await getBentoBoxBalance(this, this.b.address, this.pairHelper.contract.address) + const nullAddr = "0x0000000000000000000000000000000000000000" + const rewardAddress = "0x0000000000000000000000000000000000000007" + + // liquidate + await this.b.connect(this.bob).approve(bamm.address, liquidationAmount); + await bamm.connect(this.bob).liquidateLikeTiran(liquidationAmount, [this.alice.address], [liquidationShare], rewardAddress, nullAddr, false) + const bammMimBalAfter = await getBentoBoxBalance(this, this.b.address, bamm.address) + const bammColBalAfter = await getBentoBoxBalance(this, this.a.address, bamm.address) + const pairMimBalAfter = await getBentoBoxBalance(this, this.b.address, this.pairHelper.contract.address) + + const deltaMimBamm = bammMimBalBefore.sub(bammMimBalAfter) + const deltaMimPair = pairMimBalAfter.sub(pairMimBalBefore) + const bobMimBalAfter = await this.b.balanceOf(this.bob.address) + + const rewardBalance = await this.a.balanceOf(rewardAddress) + + // tirans funds sould be returned + expect(bobMimBalAfter).to.be.equal(bobMimBalBefore) + + // tirans collateral should not get any collateral reward + expect(rewardBalance).to.be.equal(0) + + // check bamm MIM makes sense + expect( + isEqualWithRoundingErrorFlexability(bammMimBalBefore.sub(deltaMimPair), bammMimBalAfter, 2) + ).to.be.true + + const roundingFactor = getBigNumber(1, 11); + const deltaMimWithPermium = deltaMimPair.mul(112).div(100) + const collateralInMim = deltaMimWithPermium.mul(price).div(getBigNumber(1,18)).div(roundingFactor) + const roundedBammCollBalance = bammColBalAfter.div(roundingFactor) + + expect(bammColBalBefore).to.be.equal(0) + // liquidated collateral should be in the BAMM + expect(collateralInMim).to.be.equal(roundedBammCollBalance) + }) + it("liquidateLikeTiran via bentoBox", async function () { const bamm = this.BAMM const price = getBigNumber(11, 27); @@ -1145,7 +1354,6 @@ describe("KashiPair Basic", function () { const pairMimBalBefore = await getBentoBoxBalance(this, this.b.address, this.pairHelper.contract.address) const nullAddr = "0x0000000000000000000000000000000000000000" const rewardAddress = "0x0000000000000000000000000000000000000007" - const rewardBalanceBefore = await getBentoBoxBalance(this, this.b.address, rewardAddress) // liquidate await this.b.connect(this.bob).approve(bamm.address, liquidationAmount); @@ -1155,7 +1363,6 @@ describe("KashiPair Basic", function () { const deltaMimBamm = bammMimBalBefore.sub(bammMimBalAfter) const deltaMimPair = pairMimBalAfter.sub(pairMimBalBefore) - const bobMimBalAfter = await this.b.balanceOf(this.bob.address) const bobMimBentoBalAfter = await getBentoBoxBalance(this, this.b.address, this.bob.address) const rewardBalance = await getBentoBoxBalance(this, this.a.address, rewardAddress) // check bamm MIM is the same @@ -1171,30 +1378,18 @@ describe("KashiPair Basic", function () { expect(collateralInMim).to.be.equal(roundedRewardBalance) }) - // liquidate normal test - TODO - }) - - describe.only("Liquidate", function () { + it("liquidateLikeTiran insufficent funds", async function () { + const bamm = this.BAMM + const price = getBigNumber(11, 27); - it("should not allow open liquidate yet", async function () { - await this.pairHelper.run((cmd) => [ - cmd.as(this.bob).approveAsset(getBigNumber(310, 8)), - cmd.as(this.bob).depositAsset(getBigNumber(290, 8)), - cmd.approveCollateral(getBigNumber(100)), - cmd.depositCollateral(getBigNumber(100)), - cmd.borrow(sansBorrowFee(getBigNumber(75, 8))), - cmd.accrue(), - cmd.do(this.bentoBox.connect(this.bob).deposit, this.b.address, this.bob.address, this.bob.address, getBigNumber(20, 8), 0), - ]) + // bob bento deposit setup + const depositAmonut = getBigNumber(1000, 0) + await this.b.connect(this.bob).approve(this.bentoBox.address, depositAmonut); + await this.bentoBox.connect(this.bob).deposit(this.b.address, this.bob.address, this.bob.address, depositAmonut, 0) + await setMasterContractApproval(this.bentoBox, this.bob, this.bob, this.bobPrivateKey, bamm.address, true) + await bamm.connect(this.bob).deposit(depositAmonut, true); - await expect( - this.pairHelper.contract - .connect(this.bob) - .liquidate([this.alice.address], [getBigNumber(20, 8)], this.bob.address, "0x0000000000000000000000000000000000000000", true) - ).to.be.revertedWith("KashiPair: all are solvent") - }) - it("should allow open liquidate", async function () { await this.pairHelper.run((cmd) => [ cmd.as(this.bob).approveAsset(getBigNumber(310, 8)), cmd.as(this.bob).depositAsset(getBigNumber(290, 8)), @@ -1202,110 +1397,66 @@ describe("KashiPair Basic", function () { cmd.depositCollateral(getBigNumber(100)), cmd.borrow(sansBorrowFee(getBigNumber(75, 8))), cmd.accrue(), - cmd.do(this.oracle.set, "11000000000000000000000000000"), + cmd.do(this.oracle.set, price.toString()), cmd.updateExchangeRate(), cmd.do(this.bentoBox.connect(this.bob).deposit, this.b.address, this.bob.address, this.bob.address, getBigNumber(20, 8), 0), cmd.do(this.pairHelper.contract.connect(this.bob).removeAsset, this.bob.address, getBigNumber(50, 8)), ]) - await this.pairHelper.contract - .connect(this.bob) - .liquidate([this.alice.address], [getBigNumber(20, 8)], this.bob.address, "0x0000000000000000000000000000000000000000", true) - }) - - it("should not allow liquidate for non b.protocol", async function () { - //await this.BkashiPair.setBProtocol(this.bob.address) - //console.log(this.BKashiPair) - await this.pairHelper.contract.connect(this.alice).setBProtocolMock(this.alice.address) + await bamm.setParams(20, 0, 100) - await this.pairHelper.run((cmd) => [ - cmd.as(this.bob).approveAsset(getBigNumber(310, 8)), - cmd.as(this.bob).depositAsset(getBigNumber(290, 8)), - cmd.approveCollateral(getBigNumber(100)), - cmd.depositCollateral(getBigNumber(100)), - cmd.borrow(sansBorrowFee(getBigNumber(75, 8))), - cmd.accrue(), - cmd.do(this.oracle.set, "11000000000000000000000000000"), - cmd.updateExchangeRate(), - cmd.do(this.bentoBox.connect(this.bob).deposit, this.b.address, this.bob.address, this.bob.address, getBigNumber(20, 8), 0), - cmd.do(this.pairHelper.contract.connect(this.bob).removeAsset, this.bob.address, getBigNumber(50, 8)), - ]) - await expect( - this.pairHelper.contract - .connect(this.bob) - .liquidate([this.alice.address], [getBigNumber(20, 8)], this.bob.address, "0x0000000000000000000000000000000000000000", true) - ).to.be.revertedWith("liquidate: not bprotocol") - }) + // deposit + const liquidationShare = await this.pairHelper.contract.userBorrowPart(this.alice.address) //getBigNumber(20, 18); + const liquidationAmount = getBigNumber(1, 4)//(await toAmount(this, this.b.address, liquidationShare)).add(11) + const nullAddr = "0x0000000000000000000000000000000000000000" + const rewardAddress = "0x0000000000000000000000000000000000000007" - it("should allow open liquidate with swapper", async function () { - await this.pairHelper.run((cmd) => [ - cmd.as(this.bob).approveAsset(getBigNumber(310, 8)), - cmd.as(this.bob).depositAsset(getBigNumber(290, 8)), - cmd.approveCollateral(getBigNumber(100)), - cmd.depositCollateral(getBigNumber(100)), - cmd.borrow(sansBorrowFee(getBigNumber(75, 8))), - cmd.accrue(), - cmd.do(this.oracle.set, "11000000000000000000000000000"), - cmd.updateExchangeRate(), - cmd.do(this.bentoBox.connect(this.bob).deposit, this.b.address, this.bob.address, this.bob.address, getBigNumber(20, 8), 0), - ]) + await this.b.connect(this.bob).approve(bamm.address, liquidationAmount); await expect( - this.pairHelper.contract - .connect(this.bob) - .liquidate([this.alice.address], [getBigNumber(20, 8)], this.swapper.address, this.swapper.address, true) - ) - .to.emit(this.pairHelper.contract, "LogRemoveCollateral") - .to.emit(this.pairHelper.contract, "LogRepay") + bamm.connect(this.bob) + .liquidateLikeTiran(liquidationAmount, [this.alice.address], [liquidationShare], rewardAddress, nullAddr, false) + ).to.be.revertedWith('BoringMath: Underflow') }) - it("should allow closed liquidate", async function () { + it("liquidateLikeTiran tiran calims to provide all funds but provides half", async function () { + const bamm = this.BAMM + const price = getBigNumber(11, 27); + const halfTheLiquidationAmount = getBigNumber(7500000027, 0).div(2) + // bob bento deposit setup + const depositAmonut = halfTheLiquidationAmount + await this.b.connect(this.bob).approve(this.bentoBox.address, depositAmonut); + await this.bentoBox.connect(this.bob).deposit(this.b.address, this.bob.address, this.bob.address, depositAmonut, 0) + await setMasterContractApproval(this.bentoBox, this.bob, this.bob, this.bobPrivateKey, bamm.address, true) + await bamm.connect(this.bob).deposit(depositAmonut, true); + + await this.pairHelper.run((cmd) => [ - // Bob adds 290 asset amount (145 shares) cmd.as(this.bob).approveAsset(getBigNumber(310, 8)), cmd.as(this.bob).depositAsset(getBigNumber(290, 8)), - // Alice adds 100 collateral amount (76 shares) cmd.approveCollateral(getBigNumber(100)), cmd.depositCollateral(getBigNumber(100)), - // Alice borrows 75 asset amount cmd.borrow(sansBorrowFee(getBigNumber(75, 8))), cmd.accrue(), - // Change oracle to put Alice into insolvency - cmd.do(this.oracle.set, "11000000000000000000000000000"), - //cmd.do(this.a.transfer, this.sushiSwapPair.address, getBigNumber(500)), - //cmd.do(this.sushiSwapPair.sync), + cmd.do(this.oracle.set, price.toString()), cmd.updateExchangeRate(), + cmd.do(this.bentoBox.connect(this.bob).deposit, this.b.address, this.bob.address, this.bob.address, getBigNumber(20, 8), 0), + cmd.do(this.pairHelper.contract.connect(this.bob).removeAsset, this.bob.address, getBigNumber(50, 8)), ]) - // Bob liquidates Alice for 20 asset parts (approx 20 asset amount = 10 asset shares) - await this.pairHelper.contract - .connect(this.bob) - .liquidate([this.alice.address], [getBigNumber(20, 8)], this.swapper.address, this.swapper.address, false) - }) + await bamm.setParams(20, 0, 100) - it("should not allow closed liquidate with invalid swapper", async function () { - await this.pairHelper.run((cmd) => [ - cmd.as(this.bob).approveAsset(getBigNumber(340, 8)), - cmd.as(this.bob).depositAsset(getBigNumber(290, 8)), - cmd.approveCollateral(getBigNumber(100)), - cmd.depositCollateral(getBigNumber(100)), - cmd.borrow(sansBorrowFee(getBigNumber(75, 8))), - cmd.accrue(), - ]) + // deposit + const liquidationShare = await this.pairHelper.contract.userBorrowPart(this.alice.address) //getBigNumber(20, 18); + const liquidationAmount = halfTheLiquidationAmount + const nullAddr = "0x0000000000000000000000000000000000000000" + const rewardAddress = "0x0000000000000000000000000000000000000007" - await cmd.deploy( - "invalidSwapper", - "SushiSwapSwapper", - this.bentoBox.address, - this.factory.address, - await this.factory.pairCodeHash() - ) + await this.b.connect(this.bob).approve(bamm.address, liquidationAmount); await expect( - this.pairHelper.contract - .connect(this.bob) - .liquidate([this.alice.address], [getBigNumber(20, 8)], this.invalidSwapper.address, this.invalidSwapper.address, false) - ).to.be.revertedWith("KashiPair: Invalid swapper") + bamm.connect(this.bob) + .liquidateLikeTiran(liquidationAmount, [this.alice.address], [liquidationShare], rewardAddress, nullAddr, false) + ).to.be.revertedWith('liquidateLikeTiran: insufficent extraMim') }) - }) describe("Withdraw Fees", function () { @@ -1338,6 +1489,9 @@ describe("KashiPair Basic", function () { await this.pairHelper.run((cmd) => [cmd.repay(part)]) await this.pairHelper.contract.withdrawFees() await expect(this.pairHelper.contract.withdrawFees()).to.emit(this.pairHelper.contract, "LogWithdrawFees") + + const bammMimDelta = bammMimBalAfter.sub(bammMimBalBefore) + const tiranMimDelta = tiranMimBalAfter.sub(tiranMimBalBefore) }) }) From 888beafdfd30929520ebbbfd28a96ce12dcb5b48 Mon Sep 17 00:00:00 2001 From: shmuel Date: Wed, 3 Nov 2021 10:42:17 +0200 Subject: [PATCH 19/24] remove liquidateLikeTiran describe block --- test/BProtocol.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/test/BProtocol.js b/test/BProtocol.js index c8130d8..7fa520f 100644 --- a/test/BProtocol.js +++ b/test/BProtocol.js @@ -696,8 +696,7 @@ describe("KashiPair Basic", function () { return await thisObject.bentoBox.toAmount(token, share, false) } - describe("bamm", function () { - + describe.only("bamm", function () { it("deposit and withdraw only with mim", async function () { const bamm = this.BAMM const depositAmonut = getBigNumber(2, 18); @@ -1175,11 +1174,6 @@ describe("KashiPair Basic", function () { .liquidate([this.alice.address], [getBigNumber(20, 8)], this.invalidSwapper.address, this.invalidSwapper.address, false) ).to.be.revertedWith("KashiPair: Invalid swapper") }) - - }) - - - describe.only('liquidateLikeTiran', function () { it("liquidateLikeTiran", async function () { const bamm = this.BAMM From 3c702f68c0ee006971fb21a3d5bbd4ed9eff32bf Mon Sep 17 00:00:00 2001 From: shmuel Date: Wed, 3 Nov 2021 11:01:50 +0200 Subject: [PATCH 20/24] initial-state --- test/BProtocol.js | 374 ++++++++++++++-------------------------------- 1 file changed, 113 insertions(+), 261 deletions(-) diff --git a/test/BProtocol.js b/test/BProtocol.js index 7fa520f..4ca3a60 100644 --- a/test/BProtocol.js +++ b/test/BProtocol.js @@ -687,16 +687,16 @@ describe("KashiPair Basic", function () { } } - async function getBentoBoxBalance(thisObject, token, address) { - const share = await thisObject.bentoBox.balanceOf(token, address) - return await thisObject.bentoBox.toAmount(token, share, false) - } + describe.only("bamm", function () { + async function getBentoBoxBalance(thisObject, token, address) { + const share = await thisObject.bentoBox.balanceOf(token, address) + return await thisObject.bentoBox.toAmount(token, share, false) + } - async function toAmount(thisObject, token, share) { - return await thisObject.bentoBox.toAmount(token, share, false) - } + async function toAmount(thisObject, token, share) { + return await thisObject.bentoBox.toAmount(token, share, false) + } - describe.only("bamm", function () { it("deposit and withdraw only with mim", async function () { const bamm = this.BAMM const depositAmonut = getBigNumber(2, 18); @@ -1041,140 +1041,7 @@ describe("KashiPair Basic", function () { const roundingFactor = getBigNumber(1, 11); expect(deltaMimWithPermium.mul(price).div(getBigNumber(1,18)).div(roundingFactor)).to.be.equal(deltaCol.div(roundingFactor)) }) - }) - - describe("Liquidate", function () { - - it("should not allow open liquidate yet", async function () { - await this.pairHelper.run((cmd) => [ - cmd.as(this.bob).approveAsset(getBigNumber(310, 8)), - cmd.as(this.bob).depositAsset(getBigNumber(290, 8)), - cmd.approveCollateral(getBigNumber(100)), - cmd.depositCollateral(getBigNumber(100)), - cmd.borrow(sansBorrowFee(getBigNumber(75, 8))), - cmd.accrue(), - cmd.do(this.bentoBox.connect(this.bob).deposit, this.b.address, this.bob.address, this.bob.address, getBigNumber(20, 8), 0), - ]) - - await expect( - this.pairHelper.contract - .connect(this.bob) - .liquidate([this.alice.address], [getBigNumber(20, 8)], this.bob.address, "0x0000000000000000000000000000000000000000", true) - ).to.be.revertedWith("KashiPair: all are solvent") - }) - - it("should allow open liquidate", async function () { - await this.pairHelper.run((cmd) => [ - cmd.as(this.bob).approveAsset(getBigNumber(310, 8)), - cmd.as(this.bob).depositAsset(getBigNumber(290, 8)), - cmd.approveCollateral(getBigNumber(100)), - cmd.depositCollateral(getBigNumber(100)), - cmd.borrow(sansBorrowFee(getBigNumber(75, 8))), - cmd.accrue(), - cmd.do(this.oracle.set, "11000000000000000000000000000"), - cmd.updateExchangeRate(), - cmd.do(this.bentoBox.connect(this.bob).deposit, this.b.address, this.bob.address, this.bob.address, getBigNumber(20, 8), 0), - cmd.do(this.pairHelper.contract.connect(this.bob).removeAsset, this.bob.address, getBigNumber(50, 8)), - ]) - await this.pairHelper.contract - .connect(this.bob) - .liquidate([this.alice.address], [getBigNumber(20, 8)], this.bob.address, "0x0000000000000000000000000000000000000000", true) - }) - it("should not allow liquidate for non b.protocol", async function () { - //await this.BkashiPair.setBProtocol(this.bob.address) - //console.log(this.BKashiPair) - - await this.pairHelper.contract.connect(this.alice).setBProtocolMock(this.alice.address) - - await this.pairHelper.run((cmd) => [ - cmd.as(this.bob).approveAsset(getBigNumber(310, 8)), - cmd.as(this.bob).depositAsset(getBigNumber(290, 8)), - cmd.approveCollateral(getBigNumber(100)), - cmd.depositCollateral(getBigNumber(100)), - cmd.borrow(sansBorrowFee(getBigNumber(75, 8))), - cmd.accrue(), - cmd.do(this.oracle.set, "11000000000000000000000000000"), - cmd.updateExchangeRate(), - cmd.do(this.bentoBox.connect(this.bob).deposit, this.b.address, this.bob.address, this.bob.address, getBigNumber(20, 8), 0), - cmd.do(this.pairHelper.contract.connect(this.bob).removeAsset, this.bob.address, getBigNumber(50, 8)), - ]) - await expect( - this.pairHelper.contract - .connect(this.bob) - .liquidate([this.alice.address], [getBigNumber(20, 8)], this.bob.address, "0x0000000000000000000000000000000000000000", true) - ).to.be.revertedWith("liquidate: not bprotocol") - }) - - it("should allow open liquidate with swapper", async function () { - await this.pairHelper.run((cmd) => [ - cmd.as(this.bob).approveAsset(getBigNumber(310, 8)), - cmd.as(this.bob).depositAsset(getBigNumber(290, 8)), - cmd.approveCollateral(getBigNumber(100)), - cmd.depositCollateral(getBigNumber(100)), - cmd.borrow(sansBorrowFee(getBigNumber(75, 8))), - cmd.accrue(), - cmd.do(this.oracle.set, "11000000000000000000000000000"), - cmd.updateExchangeRate(), - cmd.do(this.bentoBox.connect(this.bob).deposit, this.b.address, this.bob.address, this.bob.address, getBigNumber(20, 8), 0), - ]) - await expect( - this.pairHelper.contract - .connect(this.bob) - .liquidate([this.alice.address], [getBigNumber(20, 8)], this.swapper.address, this.swapper.address, true) - ) - .to.emit(this.pairHelper.contract, "LogRemoveCollateral") - .to.emit(this.pairHelper.contract, "LogRepay") - }) - - it("should allow closed liquidate", async function () { - await this.pairHelper.run((cmd) => [ - // Bob adds 290 asset amount (145 shares) - cmd.as(this.bob).approveAsset(getBigNumber(310, 8)), - cmd.as(this.bob).depositAsset(getBigNumber(290, 8)), - // Alice adds 100 collateral amount (76 shares) - cmd.approveCollateral(getBigNumber(100)), - cmd.depositCollateral(getBigNumber(100)), - // Alice borrows 75 asset amount - cmd.borrow(sansBorrowFee(getBigNumber(75, 8))), - cmd.accrue(), - // Change oracle to put Alice into insolvency - cmd.do(this.oracle.set, "11000000000000000000000000000"), - //cmd.do(this.a.transfer, this.sushiSwapPair.address, getBigNumber(500)), - //cmd.do(this.sushiSwapPair.sync), - cmd.updateExchangeRate(), - ]) - - // Bob liquidates Alice for 20 asset parts (approx 20 asset amount = 10 asset shares) - await this.pairHelper.contract - .connect(this.bob) - .liquidate([this.alice.address], [getBigNumber(20, 8)], this.swapper.address, this.swapper.address, false) - }) - - it("should not allow closed liquidate with invalid swapper", async function () { - await this.pairHelper.run((cmd) => [ - cmd.as(this.bob).approveAsset(getBigNumber(340, 8)), - cmd.as(this.bob).depositAsset(getBigNumber(290, 8)), - cmd.approveCollateral(getBigNumber(100)), - cmd.depositCollateral(getBigNumber(100)), - cmd.borrow(sansBorrowFee(getBigNumber(75, 8))), - cmd.accrue(), - ]) - - await cmd.deploy( - "invalidSwapper", - "SushiSwapSwapper", - this.bentoBox.address, - this.factory.address, - await this.factory.pairCodeHash() - ) - await expect( - this.pairHelper.contract - .connect(this.bob) - .liquidate([this.alice.address], [getBigNumber(20, 8)], this.invalidSwapper.address, this.invalidSwapper.address, false) - ).to.be.revertedWith("KashiPair: Invalid swapper") - }) - it("liquidateLikeTiran", async function () { const bamm = this.BAMM const price = getBigNumber(11, 27); @@ -1211,6 +1078,7 @@ describe("KashiPair Basic", function () { const pairMimBalBefore = await getBentoBoxBalance(this, this.b.address, this.pairHelper.contract.address) const nullAddr = "0x0000000000000000000000000000000000000000" const rewardAddress = "0x0000000000000000000000000000000000000007" + const rewardBalanceBefore = await getBentoBoxBalance(this, this.b.address, rewardAddress) // liquidate await this.b.connect(this.bob).approve(bamm.address, liquidationAmount); @@ -1240,77 +1108,6 @@ describe("KashiPair Basic", function () { expect(collateralInMim).to.be.equal(roundedRewardBalance) }) - it("when BAMM has sufficient funds liquidateLikeTiran should return tiran money", async function () { - const bamm = this.BAMM - const price = getBigNumber(11, 27); - - await this.pairHelper.run((cmd) => [ - cmd.as(this.bob).approveAsset(getBigNumber(310, 8)), - cmd.as(this.bob).depositAsset(getBigNumber(290, 8)), - cmd.approveCollateral(getBigNumber(100)), - cmd.depositCollateral(getBigNumber(100)), - cmd.borrow(sansBorrowFee(getBigNumber(75, 8))), - cmd.accrue(), - cmd.do(this.oracle.set, price.toString()), - cmd.updateExchangeRate(), - cmd.do(this.bentoBox.connect(this.bob).deposit, this.b.address, this.bob.address, this.bob.address, getBigNumber(20, 8), 0), - cmd.do(this.pairHelper.contract.connect(this.bob).removeAsset, this.bob.address, getBigNumber(50, 8)), - ]) - - await bamm.setParams(20, 0, 100) - - // deposit - const liquidationShare = await this.pairHelper.contract.userBorrowPart(this.alice.address) //getBigNumber(20, 18); - const liquidationAmount = (await toAmount(this, this.b.address, liquidationShare)).add(11) - - // making sure bamm has sufficient funds - const depositAmonut = liquidationAmount - await this.b.connect(this.bob).approve(this.bentoBox.address, depositAmonut); - await this.bentoBox.connect(this.bob).deposit(this.b.address, this.bob.address, this.bob.address, depositAmonut, 0) - await setMasterContractApproval(this.bentoBox, this.bob, this.bob, this.bobPrivateKey, bamm.address, true) - await bamm.connect(this.bob).deposit(depositAmonut, true); - - const bammMimBalBefore = await getBentoBoxBalance(this, this.b.address, bamm.address) - const bobMimBalBefore = await this.b.balanceOf(this.bob.address) - const bammColBalBefore = await getBentoBoxBalance(this, this.a.address, bamm.address) - const pairMimBalBefore = await getBentoBoxBalance(this, this.b.address, this.pairHelper.contract.address) - const nullAddr = "0x0000000000000000000000000000000000000000" - const rewardAddress = "0x0000000000000000000000000000000000000007" - - // liquidate - await this.b.connect(this.bob).approve(bamm.address, liquidationAmount); - await bamm.connect(this.bob).liquidateLikeTiran(liquidationAmount, [this.alice.address], [liquidationShare], rewardAddress, nullAddr, false) - const bammMimBalAfter = await getBentoBoxBalance(this, this.b.address, bamm.address) - const bammColBalAfter = await getBentoBoxBalance(this, this.a.address, bamm.address) - const pairMimBalAfter = await getBentoBoxBalance(this, this.b.address, this.pairHelper.contract.address) - - const deltaMimBamm = bammMimBalBefore.sub(bammMimBalAfter) - const deltaMimPair = pairMimBalAfter.sub(pairMimBalBefore) - const bobMimBalAfter = await this.b.balanceOf(this.bob.address) - - const rewardBalance = await this.a.balanceOf(rewardAddress) - - // tirans funds sould be returned - expect(bobMimBalAfter).to.be.equal(bobMimBalBefore) - - // tirans collateral should not get any collateral reward - expect(rewardBalance).to.be.equal(0) - - // check bamm MIM makes sense - expect( - isEqualWithRoundingErrorFlexability(bammMimBalBefore.sub(deltaMimPair), bammMimBalAfter, 2) - ).to.be.true - - const roundingFactor = getBigNumber(1, 11); - const deltaMimWithPermium = deltaMimPair.mul(112).div(100) - const collateralInMim = deltaMimWithPermium.mul(price).div(getBigNumber(1,18)).div(roundingFactor) - const roundedBammCollBalance = bammColBalAfter.div(roundingFactor) - - expect(bammColBalBefore).to.be.equal(0) - // liquidated collateral should be in the BAMM - expect(collateralInMim).to.be.equal(roundedBammCollBalance) - }) - it("liquidateLikeTiran via bentoBox", async function () { const bamm = this.BAMM const price = getBigNumber(11, 27); @@ -1348,6 +1145,7 @@ describe("KashiPair Basic", function () { const pairMimBalBefore = await getBentoBoxBalance(this, this.b.address, this.pairHelper.contract.address) const nullAddr = "0x0000000000000000000000000000000000000000" const rewardAddress = "0x0000000000000000000000000000000000000007" + const rewardBalanceBefore = await getBentoBoxBalance(this, this.b.address, rewardAddress) // liquidate await this.b.connect(this.bob).approve(bamm.address, liquidationAmount); @@ -1357,6 +1155,7 @@ describe("KashiPair Basic", function () { const deltaMimBamm = bammMimBalBefore.sub(bammMimBalAfter) const deltaMimPair = pairMimBalAfter.sub(pairMimBalBefore) + const bobMimBalAfter = await this.b.balanceOf(this.bob.address) const bobMimBentoBalAfter = await getBentoBoxBalance(this, this.b.address, this.bob.address) const rewardBalance = await getBentoBoxBalance(this, this.a.address, rewardAddress) // check bamm MIM is the same @@ -1372,18 +1171,30 @@ describe("KashiPair Basic", function () { expect(collateralInMim).to.be.equal(roundedRewardBalance) }) - it("liquidateLikeTiran insufficent funds", async function () { - const bamm = this.BAMM - const price = getBigNumber(11, 27); + // liquidate normal test - TODO + }) - // bob bento deposit setup - const depositAmonut = getBigNumber(1000, 0) - await this.b.connect(this.bob).approve(this.bentoBox.address, depositAmonut); - await this.bentoBox.connect(this.bob).deposit(this.b.address, this.bob.address, this.bob.address, depositAmonut, 0) - await setMasterContractApproval(this.bentoBox, this.bob, this.bob, this.bobPrivateKey, bamm.address, true) - await bamm.connect(this.bob).deposit(depositAmonut, true); + describe.only("Liquidate", function () { + + it("should not allow open liquidate yet", async function () { + await this.pairHelper.run((cmd) => [ + cmd.as(this.bob).approveAsset(getBigNumber(310, 8)), + cmd.as(this.bob).depositAsset(getBigNumber(290, 8)), + cmd.approveCollateral(getBigNumber(100)), + cmd.depositCollateral(getBigNumber(100)), + cmd.borrow(sansBorrowFee(getBigNumber(75, 8))), + cmd.accrue(), + cmd.do(this.bentoBox.connect(this.bob).deposit, this.b.address, this.bob.address, this.bob.address, getBigNumber(20, 8), 0), + ]) + await expect( + this.pairHelper.contract + .connect(this.bob) + .liquidate([this.alice.address], [getBigNumber(20, 8)], this.bob.address, "0x0000000000000000000000000000000000000000", true) + ).to.be.revertedWith("KashiPair: all are solvent") + }) + it("should allow open liquidate", async function () { await this.pairHelper.run((cmd) => [ cmd.as(this.bob).approveAsset(getBigNumber(310, 8)), cmd.as(this.bob).depositAsset(getBigNumber(290, 8)), @@ -1391,38 +1202,21 @@ describe("KashiPair Basic", function () { cmd.depositCollateral(getBigNumber(100)), cmd.borrow(sansBorrowFee(getBigNumber(75, 8))), cmd.accrue(), - cmd.do(this.oracle.set, price.toString()), + cmd.do(this.oracle.set, "11000000000000000000000000000"), cmd.updateExchangeRate(), cmd.do(this.bentoBox.connect(this.bob).deposit, this.b.address, this.bob.address, this.bob.address, getBigNumber(20, 8), 0), cmd.do(this.pairHelper.contract.connect(this.bob).removeAsset, this.bob.address, getBigNumber(50, 8)), ]) - - await bamm.setParams(20, 0, 100) - - // deposit - const liquidationShare = await this.pairHelper.contract.userBorrowPart(this.alice.address) //getBigNumber(20, 18); - const liquidationAmount = getBigNumber(1, 4)//(await toAmount(this, this.b.address, liquidationShare)).add(11) - const nullAddr = "0x0000000000000000000000000000000000000000" - const rewardAddress = "0x0000000000000000000000000000000000000007" - - await this.b.connect(this.bob).approve(bamm.address, liquidationAmount); - await expect( - bamm.connect(this.bob) - .liquidateLikeTiran(liquidationAmount, [this.alice.address], [liquidationShare], rewardAddress, nullAddr, false) - ).to.be.revertedWith('BoringMath: Underflow') + await this.pairHelper.contract + .connect(this.bob) + .liquidate([this.alice.address], [getBigNumber(20, 8)], this.bob.address, "0x0000000000000000000000000000000000000000", true) }) - it("liquidateLikeTiran tiran calims to provide all funds but provides half", async function () { - const bamm = this.BAMM - const price = getBigNumber(11, 27); - const halfTheLiquidationAmount = getBigNumber(7500000027, 0).div(2) - // bob bento deposit setup - const depositAmonut = halfTheLiquidationAmount - await this.b.connect(this.bob).approve(this.bentoBox.address, depositAmonut); - await this.bentoBox.connect(this.bob).deposit(this.b.address, this.bob.address, this.bob.address, depositAmonut, 0) - await setMasterContractApproval(this.bentoBox, this.bob, this.bob, this.bobPrivateKey, bamm.address, true) - await bamm.connect(this.bob).deposit(depositAmonut, true); + it("should not allow liquidate for non b.protocol", async function () { + //await this.BkashiPair.setBProtocol(this.bob.address) + //console.log(this.BKashiPair) + await this.pairHelper.contract.connect(this.alice).setBProtocolMock(this.alice.address) await this.pairHelper.run((cmd) => [ cmd.as(this.bob).approveAsset(getBigNumber(310, 8)), @@ -1431,26 +1225,87 @@ describe("KashiPair Basic", function () { cmd.depositCollateral(getBigNumber(100)), cmd.borrow(sansBorrowFee(getBigNumber(75, 8))), cmd.accrue(), - cmd.do(this.oracle.set, price.toString()), + cmd.do(this.oracle.set, "11000000000000000000000000000"), cmd.updateExchangeRate(), cmd.do(this.bentoBox.connect(this.bob).deposit, this.b.address, this.bob.address, this.bob.address, getBigNumber(20, 8), 0), cmd.do(this.pairHelper.contract.connect(this.bob).removeAsset, this.bob.address, getBigNumber(50, 8)), ]) + await expect( + this.pairHelper.contract + .connect(this.bob) + .liquidate([this.alice.address], [getBigNumber(20, 8)], this.bob.address, "0x0000000000000000000000000000000000000000", true) + ).to.be.revertedWith("liquidate: not bprotocol") + }) - await bamm.setParams(20, 0, 100) + it("should allow open liquidate with swapper", async function () { + await this.pairHelper.run((cmd) => [ + cmd.as(this.bob).approveAsset(getBigNumber(310, 8)), + cmd.as(this.bob).depositAsset(getBigNumber(290, 8)), + cmd.approveCollateral(getBigNumber(100)), + cmd.depositCollateral(getBigNumber(100)), + cmd.borrow(sansBorrowFee(getBigNumber(75, 8))), + cmd.accrue(), + cmd.do(this.oracle.set, "11000000000000000000000000000"), + cmd.updateExchangeRate(), + cmd.do(this.bentoBox.connect(this.bob).deposit, this.b.address, this.bob.address, this.bob.address, getBigNumber(20, 8), 0), + ]) + await expect( + this.pairHelper.contract + .connect(this.bob) + .liquidate([this.alice.address], [getBigNumber(20, 8)], this.swapper.address, this.swapper.address, true) + ) + .to.emit(this.pairHelper.contract, "LogRemoveCollateral") + .to.emit(this.pairHelper.contract, "LogRepay") + }) - // deposit - const liquidationShare = await this.pairHelper.contract.userBorrowPart(this.alice.address) //getBigNumber(20, 18); - const liquidationAmount = halfTheLiquidationAmount - const nullAddr = "0x0000000000000000000000000000000000000000" - const rewardAddress = "0x0000000000000000000000000000000000000007" + it("should allow closed liquidate", async function () { + await this.pairHelper.run((cmd) => [ + // Bob adds 290 asset amount (145 shares) + cmd.as(this.bob).approveAsset(getBigNumber(310, 8)), + cmd.as(this.bob).depositAsset(getBigNumber(290, 8)), + // Alice adds 100 collateral amount (76 shares) + cmd.approveCollateral(getBigNumber(100)), + cmd.depositCollateral(getBigNumber(100)), + // Alice borrows 75 asset amount + cmd.borrow(sansBorrowFee(getBigNumber(75, 8))), + cmd.accrue(), + // Change oracle to put Alice into insolvency + cmd.do(this.oracle.set, "11000000000000000000000000000"), + //cmd.do(this.a.transfer, this.sushiSwapPair.address, getBigNumber(500)), + //cmd.do(this.sushiSwapPair.sync), + cmd.updateExchangeRate(), + ]) - await this.b.connect(this.bob).approve(bamm.address, liquidationAmount); + // Bob liquidates Alice for 20 asset parts (approx 20 asset amount = 10 asset shares) + await this.pairHelper.contract + .connect(this.bob) + .liquidate([this.alice.address], [getBigNumber(20, 8)], this.swapper.address, this.swapper.address, false) + }) + + it("should not allow closed liquidate with invalid swapper", async function () { + await this.pairHelper.run((cmd) => [ + cmd.as(this.bob).approveAsset(getBigNumber(340, 8)), + cmd.as(this.bob).depositAsset(getBigNumber(290, 8)), + cmd.approveCollateral(getBigNumber(100)), + cmd.depositCollateral(getBigNumber(100)), + cmd.borrow(sansBorrowFee(getBigNumber(75, 8))), + cmd.accrue(), + ]) + + await cmd.deploy( + "invalidSwapper", + "SushiSwapSwapper", + this.bentoBox.address, + this.factory.address, + await this.factory.pairCodeHash() + ) await expect( - bamm.connect(this.bob) - .liquidateLikeTiran(liquidationAmount, [this.alice.address], [liquidationShare], rewardAddress, nullAddr, false) - ).to.be.revertedWith('liquidateLikeTiran: insufficent extraMim') + this.pairHelper.contract + .connect(this.bob) + .liquidate([this.alice.address], [getBigNumber(20, 8)], this.invalidSwapper.address, this.invalidSwapper.address, false) + ).to.be.revertedWith("KashiPair: Invalid swapper") }) + }) describe("Withdraw Fees", function () { @@ -1483,9 +1338,6 @@ describe("KashiPair Basic", function () { await this.pairHelper.run((cmd) => [cmd.repay(part)]) await this.pairHelper.contract.withdrawFees() await expect(this.pairHelper.contract.withdrawFees()).to.emit(this.pairHelper.contract, "LogWithdrawFees") - - const bammMimDelta = bammMimBalAfter.sub(bammMimBalBefore) - const tiranMimDelta = tiranMimBalAfter.sub(tiranMimBalBefore) }) }) @@ -1506,4 +1358,4 @@ describe("KashiPair Basic", function () { await expect(this.pairHelper.contract.connect(this.bob).setFeeTo(this.bob.address)).to.be.revertedWith("caller is not the owner") }) }) -}) +}) \ No newline at end of file From 10e4ceef196a5bb0cbbfe81959d9b77a6de61e8b Mon Sep 17 00:00:00 2001 From: shmuel Date: Wed, 3 Nov 2021 11:07:25 +0200 Subject: [PATCH 21/24] fixing order of tests --- test/BProtocol.js | 157 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 153 insertions(+), 4 deletions(-) diff --git a/test/BProtocol.js b/test/BProtocol.js index 4ca3a60..9734da9 100644 --- a/test/BProtocol.js +++ b/test/BProtocol.js @@ -859,6 +859,8 @@ describe("KashiPair Basic", function () { }) + + /* it('test getSwapEthAmount', async () => { // --- SETUP --- @@ -1078,7 +1080,6 @@ describe("KashiPair Basic", function () { const pairMimBalBefore = await getBentoBoxBalance(this, this.b.address, this.pairHelper.contract.address) const nullAddr = "0x0000000000000000000000000000000000000000" const rewardAddress = "0x0000000000000000000000000000000000000007" - const rewardBalanceBefore = await getBentoBoxBalance(this, this.b.address, rewardAddress) // liquidate await this.b.connect(this.bob).approve(bamm.address, liquidationAmount); @@ -1108,6 +1109,7 @@ describe("KashiPair Basic", function () { expect(collateralInMim).to.be.equal(roundedRewardBalance) }) + it("liquidateLikeTiran via bentoBox", async function () { const bamm = this.BAMM const price = getBigNumber(11, 27); @@ -1145,7 +1147,6 @@ describe("KashiPair Basic", function () { const pairMimBalBefore = await getBentoBoxBalance(this, this.b.address, this.pairHelper.contract.address) const nullAddr = "0x0000000000000000000000000000000000000000" const rewardAddress = "0x0000000000000000000000000000000000000007" - const rewardBalanceBefore = await getBentoBoxBalance(this, this.b.address, rewardAddress) // liquidate await this.b.connect(this.bob).approve(bamm.address, liquidationAmount); @@ -1155,7 +1156,6 @@ describe("KashiPair Basic", function () { const deltaMimBamm = bammMimBalBefore.sub(bammMimBalAfter) const deltaMimPair = pairMimBalAfter.sub(pairMimBalBefore) - const bobMimBalAfter = await this.b.balanceOf(this.bob.address) const bobMimBentoBalAfter = await getBentoBoxBalance(this, this.b.address, this.bob.address) const rewardBalance = await getBentoBoxBalance(this, this.a.address, rewardAddress) // check bamm MIM is the same @@ -1171,7 +1171,156 @@ describe("KashiPair Basic", function () { expect(collateralInMim).to.be.equal(roundedRewardBalance) }) - // liquidate normal test - TODO + it("when BAMM has sufficient funds liquidateLikeTiran should return tiran money", async function () { + const bamm = this.BAMM + const price = getBigNumber(11, 27); + + await this.pairHelper.run((cmd) => [ + cmd.as(this.bob).approveAsset(getBigNumber(310, 8)), + cmd.as(this.bob).depositAsset(getBigNumber(290, 8)), + cmd.approveCollateral(getBigNumber(100)), + cmd.depositCollateral(getBigNumber(100)), + cmd.borrow(sansBorrowFee(getBigNumber(75, 8))), + cmd.accrue(), + cmd.do(this.oracle.set, price.toString()), + cmd.updateExchangeRate(), + cmd.do(this.bentoBox.connect(this.bob).deposit, this.b.address, this.bob.address, this.bob.address, getBigNumber(20, 8), 0), + cmd.do(this.pairHelper.contract.connect(this.bob).removeAsset, this.bob.address, getBigNumber(50, 8)), + ]) + + await bamm.setParams(20, 0, 100) + + // deposit + const liquidationShare = await this.pairHelper.contract.userBorrowPart(this.alice.address) //getBigNumber(20, 18); + const liquidationAmount = (await toAmount(this, this.b.address, liquidationShare)).add(11) + + // making sure bamm has sufficient funds + const depositAmonut = liquidationAmount + await this.b.connect(this.bob).approve(this.bentoBox.address, depositAmonut); + await this.bentoBox.connect(this.bob).deposit(this.b.address, this.bob.address, this.bob.address, depositAmonut, 0) + await setMasterContractApproval(this.bentoBox, this.bob, this.bob, this.bobPrivateKey, bamm.address, true) + await bamm.connect(this.bob).deposit(depositAmonut, true); + + const bammMimBalBefore = await getBentoBoxBalance(this, this.b.address, bamm.address) + const bobMimBalBefore = await this.b.balanceOf(this.bob.address) + const bammColBalBefore = await getBentoBoxBalance(this, this.a.address, bamm.address) + const pairMimBalBefore = await getBentoBoxBalance(this, this.b.address, this.pairHelper.contract.address) + const nullAddr = "0x0000000000000000000000000000000000000000" + const rewardAddress = "0x0000000000000000000000000000000000000007" + + // liquidate + await this.b.connect(this.bob).approve(bamm.address, liquidationAmount); + await bamm.connect(this.bob).liquidateLikeTiran(liquidationAmount, [this.alice.address], [liquidationShare], rewardAddress, nullAddr, false) + const bammMimBalAfter = await getBentoBoxBalance(this, this.b.address, bamm.address) + const bammColBalAfter = await getBentoBoxBalance(this, this.a.address, bamm.address) + const pairMimBalAfter = await getBentoBoxBalance(this, this.b.address, this.pairHelper.contract.address) + + const deltaMimBamm = bammMimBalBefore.sub(bammMimBalAfter) + const deltaMimPair = pairMimBalAfter.sub(pairMimBalBefore) + const bobMimBalAfter = await this.b.balanceOf(this.bob.address) + + const rewardBalance = await this.a.balanceOf(rewardAddress) + + // tirans funds sould be returned + expect(bobMimBalAfter).to.be.equal(bobMimBalBefore) + + // tirans collateral should not get any collateral reward + expect(rewardBalance).to.be.equal(0) + + // check bamm MIM makes sense + expect( + isEqualWithRoundingErrorFlexability(bammMimBalBefore.sub(deltaMimPair), bammMimBalAfter, 2) + ).to.be.true + + const roundingFactor = getBigNumber(1, 11); + const deltaMimWithPermium = deltaMimPair.mul(112).div(100) + const collateralInMim = deltaMimWithPermium.mul(price).div(getBigNumber(1,18)).div(roundingFactor) + const roundedBammCollBalance = bammColBalAfter.div(roundingFactor) + + expect(bammColBalBefore).to.be.equal(0) + // liquidated collateral should be in the BAMM + expect(collateralInMim).to.be.equal(roundedBammCollBalance) + }) + + it("liquidateLikeTiran insufficent funds", async function () { + const bamm = this.BAMM + const price = getBigNumber(11, 27); + + // bob bento deposit setup + const depositAmonut = getBigNumber(1000, 0) + await this.b.connect(this.bob).approve(this.bentoBox.address, depositAmonut); + await this.bentoBox.connect(this.bob).deposit(this.b.address, this.bob.address, this.bob.address, depositAmonut, 0) + await setMasterContractApproval(this.bentoBox, this.bob, this.bob, this.bobPrivateKey, bamm.address, true) + await bamm.connect(this.bob).deposit(depositAmonut, true); + + + await this.pairHelper.run((cmd) => [ + cmd.as(this.bob).approveAsset(getBigNumber(310, 8)), + cmd.as(this.bob).depositAsset(getBigNumber(290, 8)), + cmd.approveCollateral(getBigNumber(100)), + cmd.depositCollateral(getBigNumber(100)), + cmd.borrow(sansBorrowFee(getBigNumber(75, 8))), + cmd.accrue(), + cmd.do(this.oracle.set, price.toString()), + cmd.updateExchangeRate(), + cmd.do(this.bentoBox.connect(this.bob).deposit, this.b.address, this.bob.address, this.bob.address, getBigNumber(20, 8), 0), + cmd.do(this.pairHelper.contract.connect(this.bob).removeAsset, this.bob.address, getBigNumber(50, 8)), + ]) + + await bamm.setParams(20, 0, 100) + + // deposit + const liquidationShare = await this.pairHelper.contract.userBorrowPart(this.alice.address) //getBigNumber(20, 18); + const liquidationAmount = getBigNumber(1, 4)//(await toAmount(this, this.b.address, liquidationShare)).add(11) + const nullAddr = "0x0000000000000000000000000000000000000000" + const rewardAddress = "0x0000000000000000000000000000000000000007" + + await this.b.connect(this.bob).approve(bamm.address, liquidationAmount); + await expect( + bamm.connect(this.bob) + .liquidateLikeTiran(liquidationAmount, [this.alice.address], [liquidationShare], rewardAddress, nullAddr, false) + ).to.be.revertedWith('BoringMath: Underflow') + }) + + it("liquidateLikeTiran tiran calims to provide all funds but provides half", async function () { + const bamm = this.BAMM + const price = getBigNumber(11, 27); + const halfTheLiquidationAmount = getBigNumber(7500000027, 0).div(2) + // bob bento deposit setup + const depositAmonut = halfTheLiquidationAmount + await this.b.connect(this.bob).approve(this.bentoBox.address, depositAmonut); + await this.bentoBox.connect(this.bob).deposit(this.b.address, this.bob.address, this.bob.address, depositAmonut, 0) + await setMasterContractApproval(this.bentoBox, this.bob, this.bob, this.bobPrivateKey, bamm.address, true) + await bamm.connect(this.bob).deposit(depositAmonut, true); + + + await this.pairHelper.run((cmd) => [ + cmd.as(this.bob).approveAsset(getBigNumber(310, 8)), + cmd.as(this.bob).depositAsset(getBigNumber(290, 8)), + cmd.approveCollateral(getBigNumber(100)), + cmd.depositCollateral(getBigNumber(100)), + cmd.borrow(sansBorrowFee(getBigNumber(75, 8))), + cmd.accrue(), + cmd.do(this.oracle.set, price.toString()), + cmd.updateExchangeRate(), + cmd.do(this.bentoBox.connect(this.bob).deposit, this.b.address, this.bob.address, this.bob.address, getBigNumber(20, 8), 0), + cmd.do(this.pairHelper.contract.connect(this.bob).removeAsset, this.bob.address, getBigNumber(50, 8)), + ]) + + await bamm.setParams(20, 0, 100) + + // deposit + const liquidationShare = await this.pairHelper.contract.userBorrowPart(this.alice.address) //getBigNumber(20, 18); + const liquidationAmount = halfTheLiquidationAmount + const nullAddr = "0x0000000000000000000000000000000000000000" + const rewardAddress = "0x0000000000000000000000000000000000000007" + + await this.b.connect(this.bob).approve(bamm.address, liquidationAmount); + await expect( + bamm.connect(this.bob) + .liquidateLikeTiran(liquidationAmount, [this.alice.address], [liquidationShare], rewardAddress, nullAddr, false) + ).to.be.revertedWith('liquidateLikeTiran: insufficent extraMim') + }) }) describe.only("Liquidate", function () { From 12d7f1e25b0439c24d53839a603ee097482f2673 Mon Sep 17 00:00:00 2001 From: shmuel Date: Wed, 3 Nov 2021 11:10:47 +0200 Subject: [PATCH 22/24] cleanUp --- test/BProtocol.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/BProtocol.js b/test/BProtocol.js index 9734da9..f0aa738 100644 --- a/test/BProtocol.js +++ b/test/BProtocol.js @@ -859,8 +859,6 @@ describe("KashiPair Basic", function () { }) - - /* it('test getSwapEthAmount', async () => { // --- SETUP --- From ce31403184248419608fea23682a698bdf41b79a Mon Sep 17 00:00:00 2001 From: shmuel Date: Thu, 4 Nov 2021 09:46:56 +0200 Subject: [PATCH 23/24] fail tests --- test/BProtocol.js | 84 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/test/BProtocol.js b/test/BProtocol.js index f0aa738..60a805b 100644 --- a/test/BProtocol.js +++ b/test/BProtocol.js @@ -858,6 +858,90 @@ describe("KashiPair Basic", function () { expect(aliceMimBentoBalBefore.add(expectedMimDelta)).to.be.equal(aliceMimBentoBalAfter) }) + it.only("should fail when setParams is not called by owner", async function() { + const bamm = this.BAMM + await expect( + bamm.connect(this.bob).setParams(200, 100, 0) + ).to.be.revertedWith('Ownable: caller is not the owner') + }) + + it.only("should fail when setParams is with a fee above max fee", async function() { + const bamm = this.BAMM + await expect( + bamm.setParams(200, 101, 0) + ).to.be.revertedWith('setParams: fee is too big') + }) + + it.only("should fail when setParams is with a caller fee above max caller fee", async function() { + const bamm = this.BAMM + await expect( + bamm.setParams(200, 100, 101) + ).to.be.revertedWith('setParams: caller fee is too big') + }) + + it.only("should fail when setParams is with A param above max ", async function() { + const bamm = this.BAMM + await expect( + bamm.setParams(201, 100, 100) + ).to.be.revertedWith('setParams: A too big') + }) + + it.only("should fail when setParams is with A param below minimum ", async function() { + const bamm = this.BAMM + await expect( + bamm.setParams(19, 100, 100) + ).to.be.revertedWith('setParams: A too small') + }) + + it.only("should fail when setParams is with A param below minimum ", async function() { + const bamm = this.BAMM + await expect( + bamm.setParams(19, 100, 100) + ).to.be.revertedWith('setParams: A too small') + }) + + it.only("should fail to withdraw more than share", async function() { + const bamm = this.BAMM + const withdrawAmountShare = getBigNumber(1, 18); + // bob has 0 + expect(await bamm.balanceOf(this.bob.address)).to.be.equal(0) + // try to withdraw + await expect( + bamm.connect(this.bob).withdraw(withdrawAmountShare, false) + ).to.be.revertedWith('withdraw: insufficient balance') + }) + + it.only("swap should fail when swapper sets minimum gem to more than possible", async function () { + const bamm = this.BAMM + const mimAmonut = getBigNumber(600, 18) + const colAmount = "3979999999999999997" // almost 4e17 + const price = getBigNumber(105, 18) + const wad = getBigNumber(105, 17) + + // deposit + await this.b.connect(this.bob).approve(bamm.address, mimAmonut); + await bamm.connect(this.bob).deposit(mimAmonut, false); + + // transfer collateral + await this.a.connect(this.bob).approve(this.bentoBox.address, colAmount) + await this.bentoBox.connect(this.bob).deposit(this.a.address, this.bob.address, bamm.address, colAmount, 0) + //await this.a.connect(this.bob).transfer(bamm.address, getBigNumber(1, 18)) + await this.oracle.connect(this.alice).set(price.toString()) + await bamm.fetchPrice() + + // with fee + await bamm.setParams(200, 100, 0) + const expectedCol = await bamm.getSwapGemAmount(wad) + + // do the swap + await this.b.connect(this.bob).approve(bamm.address, wad); + const dest = "0x0000000000000000000000000000000000000007" + const minGem = expectedCol.mul(10) + await expect( + bamm.connect(this.bob).swap(wad, minGem, dest, false) + ).to.be.revertedWith("swap: low return") + }) + /* it('test getSwapEthAmount', async () => { From dd5cdcea68b9e693ebf11ed6a1a708c907c6096b Mon Sep 17 00:00:00 2001 From: shmuel Date: Thu, 4 Nov 2021 10:20:59 +0200 Subject: [PATCH 24/24] test price feed is down --- test/BProtocol.js | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/test/BProtocol.js b/test/BProtocol.js index 60a805b..9917d9a 100644 --- a/test/BProtocol.js +++ b/test/BProtocol.js @@ -858,49 +858,49 @@ describe("KashiPair Basic", function () { expect(aliceMimBentoBalBefore.add(expectedMimDelta)).to.be.equal(aliceMimBentoBalAfter) }) - it.only("should fail when setParams is not called by owner", async function() { + it("should fail when setParams is not called by owner", async function() { const bamm = this.BAMM await expect( bamm.connect(this.bob).setParams(200, 100, 0) ).to.be.revertedWith('Ownable: caller is not the owner') }) - it.only("should fail when setParams is with a fee above max fee", async function() { + it("should fail when setParams is with a fee above max fee", async function() { const bamm = this.BAMM await expect( bamm.setParams(200, 101, 0) ).to.be.revertedWith('setParams: fee is too big') }) - it.only("should fail when setParams is with a caller fee above max caller fee", async function() { + it("should fail when setParams is with a caller fee above max caller fee", async function() { const bamm = this.BAMM await expect( bamm.setParams(200, 100, 101) ).to.be.revertedWith('setParams: caller fee is too big') }) - it.only("should fail when setParams is with A param above max ", async function() { + it("should fail when setParams is with A param above max ", async function() { const bamm = this.BAMM await expect( bamm.setParams(201, 100, 100) ).to.be.revertedWith('setParams: A too big') }) - it.only("should fail when setParams is with A param below minimum ", async function() { + it("should fail when setParams is with A param below minimum ", async function() { const bamm = this.BAMM await expect( bamm.setParams(19, 100, 100) ).to.be.revertedWith('setParams: A too small') }) - it.only("should fail when setParams is with A param below minimum ", async function() { + it("should fail when setParams is with A param below minimum ", async function() { const bamm = this.BAMM await expect( bamm.setParams(19, 100, 100) ).to.be.revertedWith('setParams: A too small') }) - it.only("should fail to withdraw more than share", async function() { + it("should fail to withdraw more than share", async function() { const bamm = this.BAMM const withdrawAmountShare = getBigNumber(1, 18); // bob has 0 @@ -911,7 +911,7 @@ describe("KashiPair Basic", function () { ).to.be.revertedWith('withdraw: insufficient balance') }) - it.only("swap should fail when swapper sets minimum gem to more than possible", async function () { + it("swap should fail when swapper sets minimum gem to more than possible", async function () { const bamm = this.BAMM const mimAmonut = getBigNumber(600, 18) const colAmount = "3979999999999999997" // almost 4e17 @@ -942,6 +942,27 @@ describe("KashiPair Basic", function () { ).to.be.revertedWith("swap: low return") }) + it("when gem balance exist and price is 0 should fail with price feed is down", async function () { + const bamm = this.BAMM + const depositAmonut = getBigNumber(2, 18); + + // making sure price is zero + await this.pairHelper.run((cmd) => [ + cmd.do(this.oracle.set, getBigNumber(0, 18)), + cmd.updateExchangeRate() + ]) + // making sure gem balance is bigger than 0 + + await this.a.connect(this.bob).approve(this.bentoBox.address, depositAmonut) + await this.bentoBox.connect(this.bob).deposit(this.a.address, this.bob.address, bamm.address, depositAmonut, 0) + + //trying to deposit + await this.b.connect(this.bob).approve(bamm.address, depositAmonut); + await expect( + bamm.connect(this.bob).deposit(depositAmonut, false) + ).to.be.revertedWith("deposit: feed is down") + }) + /* it('test getSwapEthAmount', async () => {