diff --git a/packages/contracts/contracts/B.Protocol/APriceFeed.sol b/packages/contracts/contracts/B.Protocol/APriceFeed.sol new file mode 100644 index 000000000..c7201c41d --- /dev/null +++ b/packages/contracts/contracts/B.Protocol/APriceFeed.sol @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.11; + +interface BAMMLike { + function cBorrow() external view returns(address); +} + +interface AaveOracleLike { + function getAssetPrice(address underlying) external view returns (uint); +} + +interface AaveLendingPoolAddressesProviderLike { + function getPriceOracle() view external returns(AaveOracleLike); +} + +interface AaveToCTokenAdapterLike { + function underlying() external view returns (address); + function decimals() external view returns(uint); +} + +interface APriceFeedLike { + function decimals() view external returns(uint); + function getPriceFromBamm(address sender, address dst) view external returns(uint); +} + +interface IAToken { + function UNDERLYING_ASSET_ADDRESS() view external returns(address); + function decimals() external view returns(uint); +} + +contract ADegenFeed { + APriceFeedLike immutable priceFeed; + address immutable ctoken; + + constructor(address _priceFeed, address _ctoken) public { + priceFeed = APriceFeedLike(_priceFeed); + ctoken = _ctoken; + } + + function decimals() view public returns(uint) { + return priceFeed.decimals(); + } + + function latestRoundData() external view returns + ( + uint80 /* roundId */, + int256 answer, + uint256 /* startedAt */, + uint256 timestamp, + uint80 /* answeredInRound */ + ) + { + answer = int(priceFeed.getPriceFromBamm(msg.sender, ctoken)); + timestamp = block.timestamp; + } +} +/* +price feed for ctokens. a single feed for all ctokens. +*/ +contract APriceFeed { + AaveLendingPoolAddressesProviderLike immutable provider; + uint public constant decimals = 18; + + constructor(AaveLendingPoolAddressesProviderLike _provider) public { + provider = _provider; + } + + function getPrice(address src, address dst) public view returns(uint) { + AaveOracleLike oracle = provider.getPriceOracle(); + + address underlyingSrc = AaveToCTokenAdapterLike(src).underlying(); + address underlyingDst = IAToken(dst).UNDERLYING_ASSET_ADDRESS(); + + uint srcUnderlyingPrice = oracle.getAssetPrice(underlyingSrc); + uint dstUnderlyingPrice = oracle.getAssetPrice(underlyingDst); + + uint price = (10 ** decimals) * dstUnderlyingPrice / srcUnderlyingPrice; + + + return price; + } + + function getPriceFromBamm(address sender, address dst) public view returns(uint) { + address src = BAMMLike(sender).cBorrow(); + return getPrice(src, dst); + } + + function generateDegenFeed(address dstCToken) public returns(address) { + ADegenFeed degenFeed = new ADegenFeed(address(this), dstCToken); + return address(degenFeed); + } +} + + + diff --git a/packages/contracts/contracts/B.Protocol/AaveBAMM.sol b/packages/contracts/contracts/B.Protocol/AaveBAMM.sol new file mode 100644 index 000000000..0f994c750 --- /dev/null +++ b/packages/contracts/contracts/B.Protocol/AaveBAMM.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.11; + +import "./HundredBAMM.sol"; +import "./AaveToCTokenAdapter.sol"; + +contract AaveBAMM is HundredBAMM { + constructor( + address _cBorrow, + bool _isCETH, + uint _maxDiscount, + address payable _feePool) + public + HundredBAMM(_cBorrow, _isCETH, _maxDiscount, _feePool) + { + // give allowance to cBorrow + IERC20(AaveToCTokenAdapter(_cBorrow).aToken()).safeApprove(_cBorrow, uint(-1)); + } + + function addCollateral(ICToken ctoken, AggregatorV3Interface feed) override public onlyOwner { + IERC20(address(ctoken)).safeApprove(address(ctoken), uint(-1)); + + super.addCollateral(ctoken, feed); + } + + function removeCollateral(ICToken ctoken) override public onlyOwner { + IERC20(address(ctoken)).safeApprove(address(ctoken), uint(0)); + + super.removeCollateral(ctoken); + } + + function canLiquidate( + ICToken cTokenBorrowed, + ICToken cTokenCollateral, + uint repayAmount + ) + external + view + override + returns(bool) + { + if(address(cTokenBorrowed) != address(cBorrow.underlying())) return false; + bool validCollateral = false; + for(uint i = 0 ; i < collaterals.length ; i++) { + if(address(cTokenCollateral) == IAToken(address(collaterals[i])).UNDERLYING_ASSET_ADDRESS()) { + validCollateral = true; + break; + } + } + + if(! validCollateral) return false; + + // check if there is sufficient balance at the backstop + (uint err, uint ctokenBalance, /* borrow balance */, uint exchangeRateMantissa) = cBorrow.getAccountSnapshot(address(this)); + if(err != 0) return false; + + uint underlyingBalance = ctokenBalance.mul(exchangeRateMantissa) / 1e18; + + if(repayAmount > underlyingBalance) return false; + if(repayAmount > cBorrow.getCash()) return false; + + return true; + } +} \ No newline at end of file diff --git a/packages/contracts/contracts/B.Protocol/AaveToCTokenAdapter.sol b/packages/contracts/contracts/B.Protocol/AaveToCTokenAdapter.sol new file mode 100644 index 000000000..6f5b5d482 --- /dev/null +++ b/packages/contracts/contracts/B.Protocol/AaveToCTokenAdapter.sol @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.11; + +import { ICToken } from "./BAMM.sol"; +import { SafeERC20, IERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; +import "./../Dependencies/Ownable.sol"; + +interface ILendingPoolAddressesProvider { + function getLendingPool() external view returns(address); +} + +interface ILendingPool { + function withdraw( + address asset, + uint256 amount, + address to + ) external; + + function liquidationCall( + address collateralAsset, + address debtAsset, + address user, + uint256 debtToCover, + bool receiveAToken + ) external; +} + +interface IAToken is IERC20 { + function UNDERLYING_ASSET_ADDRESS() view external returns(address); + function decimals() view external returns(uint8); + function symbol() view external returns(string memory); +} + + +contract AaveToCTokenAdapter is ICToken, Ownable { + using SafeERC20 for IERC20; + + ILendingPoolAddressesProvider public immutable lendingPoolAddressesProvider; + IAToken public immutable aToken; + + constructor(IAToken _aToken, ILendingPoolAddressesProvider _lendingPoolAddressesProvider) public { + lendingPoolAddressesProvider = _lendingPoolAddressesProvider; + aToken = _aToken; + } + + // read functions + function balanceOf(address a) external override view returns (uint) { + return aToken.balanceOf(a); + } + + function underlying() public override view returns(IERC20) { + return IERC20(aToken.UNDERLYING_ASSET_ADDRESS()); + } + + function getCash() external override view returns(uint) { + return underlying().balanceOf(address(aToken)); + } + + function getAccountSnapshot(address account) + external + view + override + returns(uint err, uint cTokenBalance, uint borrowBalance, uint exchangeRateMantissa) + { + require(account == owner(), "getAccountSnapshot: only owner is supported"); + + err = 0; + cTokenBalance = aToken.balanceOf(account); + exchangeRateMantissa = 1e18; // 1, as balance returns the underlying value + borrowBalance = 0; // never borrow + } + + function decimals() public view returns(uint8) { + return aToken.decimals(); + } + + function symbol() public view returns(string memory) { + return aToken.symbol(); + } + + // admin funcrion - this is called only once, as bamm won't be able to call it again + function setBAMM(address bamm) external onlyOwner { + transferOwnership(bamm); + } + + // write functions + function redeemUnderlying(uint redeemAmount) external override onlyOwner returns (uint) { + IERC20(aToken).safeTransferFrom(msg.sender, address(this), redeemAmount); + address pool = lendingPoolAddressesProvider.getLendingPool(); + IERC20(aToken).safeApprove(pool, redeemAmount); + ILendingPool(pool).withdraw(address(underlying()), redeemAmount, msg.sender); + + return 0; + } + + function liquidateBorrow(address borrower, uint amount, address collateral) external override onlyOwner returns (uint) { + address collateralUnderlying = address(IAToken(collateral).UNDERLYING_ASSET_ADDRESS()); + address debt = address(underlying()); + + IERC20(debt).safeTransferFrom(msg.sender, address(this), amount); + address pool = lendingPoolAddressesProvider.getLendingPool(); + IERC20(debt).safeApprove(pool, amount); + + ILendingPool(pool).liquidationCall(collateralUnderlying, debt, borrower, amount, true); + + // send collateral atoken to bamm + IERC20(collateral).safeTransfer(msg.sender, IERC20(collateral).balanceOf(address(this))); + + return 0; + } + + function transfer(address to, uint amount) external onlyOwner returns (bool) { + IERC20(aToken).safeTransferFrom(msg.sender, to, amount); + + return true; + } + + function transferFrom(address from, address to, uint tokens) external onlyOwner returns (bool) { + require(to == owner(), "transferFrom: only pulling by owner is supported"); + + IERC20(aToken).safeTransferFrom(from, address(this), tokens); + IERC20(aToken).safeTransfer(to, tokens); + + return true; + } +} diff --git a/packages/contracts/contracts/B.Protocol/BAMM.sol b/packages/contracts/contracts/B.Protocol/BAMM.sol index f614bf279..65e8a3029 100644 --- a/packages/contracts/contracts/B.Protocol/BAMM.sol +++ b/packages/contracts/contracts/B.Protocol/BAMM.sol @@ -97,7 +97,7 @@ contract BAMM is TokenAdapter, PriceFormula, Ownable, ReentrancyGuard { emit ParamsSet(_A, _fee, _callerFee); } - function addCollateral(ICToken ctoken, AggregatorV3Interface feed) external onlyOwner { + function addCollateral(ICToken ctoken, AggregatorV3Interface feed) public virtual onlyOwner { IERC20 token = IERC20(address(ctoken)); // validations @@ -113,7 +113,7 @@ contract BAMM is TokenAdapter, PriceFormula, Ownable, ReentrancyGuard { cTokens[address(ctoken)] = true; } - function removeCollateral(ICToken ctoken) external onlyOwner { + function removeCollateral(ICToken ctoken) public virtual onlyOwner { IERC20 token = IERC20(address(ctoken)); for(uint i = 0 ; i < collaterals.length ; i++) { @@ -316,6 +316,7 @@ contract BAMM is TokenAdapter, PriceFormula, Ownable, ReentrancyGuard { ) external view + virtual returns(bool) { if(cTokenBorrowed != cBorrow) return false; diff --git a/packages/contracts/contracts/B.Protocol/ChainlinkTestnet.sol b/packages/contracts/contracts/B.Protocol/ChainlinkTestnet.sol index d19a65ef7..0efe32193 100644 --- a/packages/contracts/contracts/B.Protocol/ChainlinkTestnet.sol +++ b/packages/contracts/contracts/B.Protocol/ChainlinkTestnet.sol @@ -39,6 +39,10 @@ contract ChainlinkTestnet { if(time == 0 ) timestamp = now; else timestamp = time; } + + function latestAnswer() external view returns(int) { + return int(price); + } } contract FakePriceOracle { diff --git a/packages/contracts/contracts/B.Protocol/MockCToken.sol b/packages/contracts/contracts/B.Protocol/MockCToken.sol index 9f6f65fcf..05d3b2dba 100644 --- a/packages/contracts/contracts/B.Protocol/MockCToken.sol +++ b/packages/contracts/contracts/B.Protocol/MockCToken.sol @@ -144,3 +144,55 @@ contract MockUnitroller { } } + + +contract MockAave { + function getLendingPool() external pure returns (address) { + return address(0); + } + + function getPriceOracle() external pure returns (address) { + return address(0); + } + + function deposit( + address asset, + uint256 amount, + address onBehalfOf, + uint16 referralCode + ) external + { + + } + + function borrow( + address asset, + uint256 amount, + uint256 interestRateMode, + uint16 referralCode, + address onBehalfOf + ) external + { + + } + + function setAssetSources(address[] calldata assets, address[] calldata sources) + external + { + + } + + function owner() external pure returns(address) { + return address(0); + } + + function getAssetPrice(address asset) external pure returns(uint) { + return 8; + } +} + +contract ForceEthSend { + constructor(address payable dest) public payable { + selfdestruct(dest); + } +} \ No newline at end of file diff --git a/packages/contracts/scripts/deployHad.js b/packages/contracts/scripts/deployHad.js new file mode 100644 index 000000000..0bc3e3149 --- /dev/null +++ b/packages/contracts/scripts/deployHad.js @@ -0,0 +1,289 @@ +const { artifacts, web3 } = require("hardhat") + + +const deploy = async(lendingPoolAddressesProviderAddress, + hTokens, hstableNames, maxDiscount, adminOwner, txData) => { + console.log("deploying fee pool") + const FeePool = artifacts.require("FeeVault") + const feePoolContract = await FeePool.new(adminOwner, txData) + console.log("fee pool address", feePoolContract.address) + const _feePool = feePoolContract.address + + const hTokenNames = [] + const hStables = [] + //const hstableNames = [] + console.log('reading token names') + for(let i = 0 ; i < hTokens.length ; i++) { + const ctoken = await artifacts.require("MockCToken").at(hTokens[i]) + const name = await ctoken.symbol() + isStable = (hstableNames.indexOf(name) > -1) + hTokenNames.push(name) + if(isStable) { + hStables.push(hTokens[i]) + hstableNames.push(name) + } + + console.log(name, hTokens[i], isStable) + } + + console.log("deploy AFeed") + const AFeed = artifacts.require("APriceFeed") + const aFeed = await AFeed.new(lendingPoolAddressesProviderAddress,txData) + console.log("aFeed", aFeed.address) + + console.log("deploy collateral adder") + const Adder = artifacts.require("CollateralAdder") + const adder = await Adder.new(txData) + console.log("adder", adder.address) + + console.log("admin of adder", await adder.admin()) + + const _maxDiscount = maxDiscount + + const BAMM = artifacts.require("AaveBAMM") + + for(let i = 0 ; i < hStables.length ; i++) { + const hstable = hStables[i] + const name = hstableNames[i] + + console.log("deploying ctoken adapter", hstable) + const AaveToCToken = artifacts.require("AaveToCTokenAdapter.sol") + const cBorrow = await AaveToCToken.new(hstable, lendingPoolAddressesProviderAddress, txData) + console.log("cBorrow address", cBorrow.address) + + console.log("deploying bamm", hstable) + const _cBorrow = cBorrow.address + const bamm = await BAMM.new(_cBorrow, false, _maxDiscount, _feePool, txData) + console.log("bamm", name, bamm.address) + + console.log("set cborrow owner as bamm") + await cBorrow.setBAMM(bamm.address, txData) + + console.log("deploy all adapters") + const collateralAdapters = [] + for(const hToken of hTokens) { + if(hToken === hstable) continue + /* + + console.log("deploying adapter to ", hToken) + const aToken = await AaveToCToken.new(hToken, lendingPoolAddressesProviderAddress, txData) + console.log("adapter address" , aToken.address) + + console.log("setting bamm as adapter owner") + await aToken.setBAMM(bamm.address, txData) + + */ + + // collaterals do not need adapters + collateralAdapters.push(hToken) + + /* + console.log("add collateral to bamm - for testing") + await bamm.addCollateral(aToken.address, aToken.address) + */ + } + + console.log("setting fees") + await bamm.setParams("20", "100", "100", txData) + + console.log("transfer admin to adder") + await bamm.transferOwnership(adder.address, txData) + + console.log("add collaterals") + await adder.add(collateralAdapters, bamm.address, aFeed.address, txData) + console.log("done") + + /* + console.log("deploy admin") + const admin = await artifacts.require("Admin").new(unitrollerAddress, bamm.address, txData) + console.log("deployed admin", admin.address) + + console.log("transferOwnership to admin") + await bamm.transferOwnership(admin.address, txData) + + console.log("transfer admin of admin...") + await admin.transferOwnership(adminOwner, txData)*/ + } + + console.log("==================") + console.log("deployment is over") + } + + async function nervos_deploy() { + const providerAddress = "0x773E3fAAD7b17147eDD78eE796Ac127e5ad23855" + const aTokens = [ "0x7b6eeCD5EC8b82cC27359CD6F75818C8bA2B33cd", // aETH + "0xbDd52631272E28286DE79A1aE0AE51c5b1660064", // aBNB + "0xe609C8861DB284195510ff805E0AeAFd92bb980D", // aWBTC + "0xeC2BF7ec6aFCeC1594baf4F33736573d0a12C25E", // aCKB + "0x3D8714a8e553FB872Ac3790c49E9480aB1D31342", // aUSDC + "0x30D1911E7703aD37d76682735E50a925E4CB9139" // aUSDT + ] + const hstableNames = ["hUSDC.e"] + const maxDiscount = 400 + const feePoolOwner = "0xC7035d9319654fae4a0aBE9A88121B9D9C36900F" + + await deploy(providerAddress, aTokens, hstableNames, maxDiscount, feePoolOwner, {gas:2000000}) + } + + async function nervos_deploy_testnet() { + const providerAddress = "0x5492119c74B6c9f72ebF735856C213Dd03AC565F" + const aTokens = [ "0x8E66CC7Fe7800e93B489161d2aE51bF3E56e7aef", // aETH + "0x190b3b34A01A2CA210342AbFe7703bA6E53E741d", // aBNB + "0x9AfB608F2313BD7fBD62A515FF1ea8250cEfdF99", // aWBTC + "0x72261bd935FDcf36c1Ecd25A499Ad226471BE722", // aCKB + "0x64D0B6c2D4d4850da25e80d04F8917dDc99c72fa", // aUSDC + "0x683d2100f27e397118BC265c4059577c29cd87f8" // aUSDT + ] + const hstableNames = ["hUSDC.e"] + const maxDiscount = 400 + const feePoolOwner = "0xC7035d9319654fae4a0aBE9A88121B9D9C36900F" + + await deploy(providerAddress, aTokens, hstableNames, maxDiscount, feePoolOwner, {gas:2000000}) + } + + + + /* + async function ftmDeploy() { + const unitroller = "0x0f390559f258eb8591c8e31cf0905e97cf36ace2" + const hStables = ["hUSDC", "hUSDT", "hMIM", "hFRAX", "hDAI"] + const feePool = "0x8F95C99D8f2D03729C1300E59fc38299D831a7F7" + const adminOwner = "0xf7D44D5a28d5AF27a7F9c8fc6eFe0129e554d7c4" + deploy(unitroller, hStables, feePool, 400, adminOwner,{gas: 6000000}) + } + + + async function depositToBamm(unitrollerAddress, bamms, executer) { + console.log("user", executer) + + const BAMM = artifacts.require("HundredBAMM") + const unitrollerContract = await artifacts.require("MockUnitroller").at(unitrollerAddress) + console.log("fetching htokens") + const hTokens = await unitrollerContract.getAllMarkets() + + for(const bammAddress of bamms) { + const bamm = await BAMM.at(bammAddress) + console.log("get cborrow") + const stable = await bamm.LUSD() + console.log({stable}) + console.log("give allowance") + const token = await artifacts.require("MockToken").at(stable) + const balance = web3.utils.toBN(await token.balanceOf(executer)).div(web3.utils.toBN("4")) + console.log("ctoken balance", balance.toString()) + await token.approve(bammAddress, balance) + console.log("deposit to bamm") + await bamm.deposit(balance) + + console.log("sending htokens to bamm") + for(const htoken of hTokens) { + if(stable === htoken) continue + const collat = await artifacts.require("MockToken").at(htoken) + const balance = web3.utils.toBN(await collat.balanceOf(executer)).div(web3.utils.toBN("4")) + console.log("sending ", await collat.symbol(), "to ", "bamm ", await token.symbol()) + await collat.transfer(bammAddress, balance) + } + + console.log("withdraw from bamm") + const amount = web3.utils.toBN(await bamm.balanceOf(executer)).div(web3.utils.toBN("4")) + await bamm.withdraw(amount) + console.log("done with bamm") + } + } + +*/ + function normalize(bn, decimals) { + const factor = web3.utils.toBN("10").pow(web3.utils.toBN(18 - decimals)) + return web3.utils.fromWei(web3.utils.toBN(bn).mul(factor)) + } + + async function fethOraclePrices(hTokens, bamms) { + const BAMM = artifacts.require("HundredBAMM") + + for(const bammAddress of bamms) { + console.log("============================") + const bamm = await BAMM.at(bammAddress) + const stable = await bamm.LUSD() + const token = await artifacts.require("MockToken").at(stable) + const stableName = await token.symbol() + + for(const htoken of hTokens) { + if(stable === htoken) continue + const collat = await artifacts.require("MockToken").at(htoken) + const collatName = await collat.symbol() + + const price = await bamm.fetchPrice(htoken) + console.log(collatName + "/" + stableName, normalize(price,18), (await collat.decimals()).toString(), (await token.decimals()).toString()) + } + } + } + + //nervos_deploy() + + async function depositAndWithdraw(txData) { + const aUSDC = await artifacts.require("MockCToken").at("0x3D8714a8e553FB872Ac3790c49E9480aB1D31342") + const bamm = await artifacts.require("AaveBAMM").at("0xD839F4468A47Ac17321c28669029d069AB73F535") + + console.log("calling get adapter address") + const adapterAddress = await bamm.cBorrow() + console.log({adapterAddress}) + + const aUSDCAdapter = await artifacts.require("AaveToCTokenAdapter").at(adapterAddress) + + const yaron = "0xC7035d9319654fae4a0aBE9A88121B9D9C36900F" + + console.log("aUSDC balance", (await aUSDC.balanceOf(yaron)).toString()) + console.log((await bamm.cBorrow())) + console.log((await aUSDCAdapter.owner())) + + console.log("giving allowance to adapter") + await aUSDC.approve(aUSDCAdapter.address, "1234566789", {gas:200000}) + + console.log("calling deposit") + await bamm.deposit("100", {gas:3000000}) + + console.log("aUSDC balance of user", (await aUSDC.balanceOf(yaron)).toString()) + console.log("aUSDC balance of bamm", (await aUSDC.balanceOf(bamm.address)).toString()) + console.log("bamm balance of user", (await bamm.balanceOf(yaron)).toString()) + + console.log("withdraw half") + await bamm.withdraw("500000000000000000", {gas:2000000}) + + console.log("aUSDC balance of user", (await aUSDC.balanceOf(yaron)).toString()) + console.log("aUSDC balance of bamm", (await aUSDC.balanceOf(bamm.address)).toString()) + console.log("bamm balance of user", (await bamm.balanceOf(yaron)).toString()) + } + + //depositAndWithdraw({gas:20000000}) + //nervos_deploy() + depositAndWithdraw() + +/* + const aTokens = [ "0x7b6eeCD5EC8b82cC27359CD6F75818C8bA2B33cd", // aETH + "0xbDd52631272E28286DE79A1aE0AE51c5b1660064", // aBNB + "0xe609C8861DB284195510ff805E0AeAFd92bb980D", // aWBTC + "0xeC2BF7ec6aFCeC1594baf4F33736573d0a12C25E", // aCKB + "0x3D8714a8e553FB872Ac3790c49E9480aB1D31342", // aUSDC + "0x30D1911E7703aD37d76682735E50a925E4CB9139" // aUSDT +] + fethOraclePrices(aTokens, ["0x17bC5Bd342A6970335f035abe14D8B23910498Ae"]) + */ +/* + + //ftmDeploy() + //ftmDepositToHtoken("0x0f390559f258eb8591c8e31cf0905e97cf36ace2", "0xb69Dd1e9Fe391E1F36b01F00bb6a9d9303EE3E13") + //iotexDeploy() + + fethOraclePrices("0x8c6139FF1E9D7c1E32bDAFd79948d0895bA0a831", + ["0x4Db1d29eA5b51dDADcc5Ab26709dDA49e7eB1E71", + "0x8cF0B1c886Ee522427ef57F5601689352F8161eb", + "0x7D30d048F8693aF30A10aa5D6d281A7A7E6E1245", + "0xCE0A876996248421606F4ad8a09B1D3E15f69EfB"])*/ + +/* + depositToBamm("0x8c6139FF1E9D7c1E32bDAFd79948d0895bA0a831", + ["0x4Db1d29eA5b51dDADcc5Ab26709dDA49e7eB1E71","0x8cF0B1c886Ee522427ef57F5601689352F8161eb", + "0x7D30d048F8693aF30A10aa5D6d281A7A7E6E1245","0xCE0A876996248421606F4ad8a09B1D3E15f69EfB"], + "0xb69Dd1e9Fe391E1F36b01F00bb6a9d9303EE3E13") + +*/ + ///const deploy = async(unitrollerAddress, hstableNames, feePool, maxDiscount) \ No newline at end of file diff --git a/packages/contracts/test/B.Protocol/ForkTestAave.js b/packages/contracts/test/B.Protocol/ForkTestAave.js new file mode 100644 index 000000000..104e23021 --- /dev/null +++ b/packages/contracts/test/B.Protocol/ForkTestAave.js @@ -0,0 +1,319 @@ +const { assert, artifacts } = require("hardhat") +const deploymentHelper = require("./../../utils/deploymentHelpers.js") +const testHelpers = require("./../../utils/testHelpers.js") +const th = testHelpers.TestHelper +const dec = th.dec +const toBN = th.toBN +const mv = testHelpers.MoneyValues +const timeValues = testHelpers.TimeValues + +const CPriceFeed = artifacts.require("CPriceFeed.sol") +const BAMM = artifacts.require("AaveBAMM.sol") +const ForceEthSend = artifacts.require("ForceEthSend.sol") +const MockAave = artifacts.require("MockAave.sol") +const AaveToCToken = artifacts.require("AaveToCTokenAdapter.sol") +const CollateralAdder = artifacts.require("CollateralAdder.sol") +const MockToken = artifacts.require("MockToken") +const FakeChainlink = artifacts.require("ChainlinkTestnet") + +const ZERO = toBN('0') +const ZERO_ADDRESS = th.ZERO_ADDRESS +const maxBytes32 = th.maxBytes32 + +const getFrontEndTag = async (stabilityPool, depositor) => { + return (await stabilityPool.deposits(depositor))[1] +} + +contract('BAMM', async accounts => { + const [owner, + defaulter_1, defaulter_2, defaulter_3, + alice, bob, carol, dennis, erin, flyn, + A, B, C, D, E, F, + u1, u2, u3, u4, u5, + v1, v2, v3, v4, v5, + frontEnd_1, frontEnd_2, frontEnd_3, + shmuel, eitan + ] = accounts; + + let priceFeed + + const lendingPoolAddressesProvider = "0xd05e3E715d945B59290df0ae8eF85c1BdB684744" + const aWMATIC = "0x8dF3aad3a84da6b69A4DA8aeC3eA40d9091B2Ac4" + const aDAI = "0x27F8D03b3a2196956ED754baDc28D73be8830A6e" + const aUSDC = "0x1a13F4Ca1d028320A707D99520AbFefca3998b7F" + const yaron = "0xDdA7F2654D85653C92513B58305348Da627C7cf0" + const feePool = yaron + + const daiWhale = aDAI + const usdcWhale = aUSDC + const maticWhale = "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270" + const whale = yaron + + const daiAddress = "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063" + const usdcAddress = "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174" + + let aaveToCToken + let cDAI + let bamm + let aUSDCToken + let oracle + let oracleOwner + let lendingPool + let daiPriceOracle + let usdc + let dai + let priceDai2UsdcFeed + + describe("aave test", async () => { + + before(async () => { + console.log("get lending pool and oracle") + const provider = await MockAave.at(lendingPoolAddressesProvider) + lendingPool = await MockAave.at(await provider.getLendingPool()) + oracle = await MockAave.at(await provider.getPriceOracle()) + oracleOwner = await oracle.owner() + + console.log("impersonate accounts") + await hre.network.provider.request({method: "hardhat_impersonateAccount",params: [whale],}) + await hre.network.provider.request({method: "hardhat_impersonateAccount",params: [daiWhale],}) + await hre.network.provider.request({method: "hardhat_impersonateAccount",params: [usdcWhale],}) + await hre.network.provider.request({method: "hardhat_impersonateAccount",params: [maticWhale],}) + await hre.network.provider.request({method: "hardhat_impersonateAccount",params: [oracleOwner],}) + + console.log("send ether to whales") + await web3.eth.sendTransaction({from: maticWhale, to: whale, value: toBN(dec(100, 18))}) + await ForceEthSend.new(daiWhale, {from: maticWhale, value: dec(100, 18)}) + await ForceEthSend.new(usdcWhale, {from: maticWhale, value: dec(100, 18)}) + await ForceEthSend.new(oracleOwner, {from: maticWhale, value: dec(100, 18)}) + + console.log("send dai to whale") + dai = await MockToken.at(daiAddress) + await dai.transfer(whale, dec(1e6, 18), {from: daiWhale}) + + console.log("send usdc to whale") + usdc = await MockToken.at(usdcAddress) + await usdc.transfer(whale, dec(1e6, 6), {from: usdcWhale}) + + console.log("deploy fake chainlink") + daiPriceOracle = await FakeChainlink.new() + console.log("set price of dai to the one of usdc") + await daiPriceOracle.setPrice(await oracle.getAssetPrice(usdcAddress)) + console.log("set fake price oracle as official price") + await oracle.setAssetSources([daiAddress], [daiPriceOracle.address], {from: oracleOwner}) + + + aaveToCToken = await AaveToCToken.new(aUSDC, lendingPoolAddressesProvider, {from: whale}) + console.log("aaveToCToken", aaveToCToken.address) + console.log("aave to c", aaveToCToken.address) + + bamm = await BAMM.new(aaveToCToken.address, + false, + 400, + feePool, + {from: whale}) + + console.log("bamm address", bamm.address) + + await aaveToCToken.setBAMM(bamm.address, {from: whale}) + + aUSDCToken = await MockToken.at(aUSDC) + + console.log("Add cdai as collateral") + cDAI = await MockToken.at(aDAI) //*/await AaveToCToken.new(aDAI, lendingPoolAddressesProvider, {from: whale}) + console.log((await cDAI.decimals()).toString()) + + // setup a price feed + console.log("setup price feed") + priceDai2UsdcFeed = await FakeChainlink.new({from: whale}) + console.log("set price to 2") + await priceDai2UsdcFeed.setPrice(dec(2,18),{from: whale}) + console.log("add collateral") + await bamm.addCollateral(cDAI.address, priceDai2UsdcFeed.address, {from: whale}) + }) + + beforeEach(async () => { + + }) + + it("deposit aUSDC", async () => { + const amount = toBN(dec(200, 6)) + + console.log("give usdc allowance to lending pool") + await usdc.approve(lendingPool.address, amount, {from: whale}) + + console.log("deposit in lending pool") + await lendingPool.deposit(usdc.address, amount, whale, 0, {from: whale}) + + console.log("giving allowance of atokn") + await aUSDCToken.approve(aaveToCToken.address, amount, {from: whale}) + + console.log("depositing") + const balanceBefore = await aUSDCToken.balanceOf(whale) + await bamm.deposit(amount, {from: whale}) + const balanceAfter = await aUSDCToken.balanceOf(whale) + + assert.equal((await aUSDCToken.balanceOf(bamm.address)).toString(), amount.toString()) + assert.equal(toBN(balanceBefore).sub(balanceAfter).toString(), amount.toString()) + + console.log("withdrawing") + await bamm.withdraw(toBN(dec(1,18)).div(toBN(2)), {from: whale}) + const balanceAfterWithdraw = await aUSDCToken.balanceOf(whale) + + assert(in100WeiRadius((await aUSDCToken.balanceOf(bamm.address)).toString(), amount.div(toBN(2)).toString())) + assert(in100WeiRadius(toBN(balanceAfterWithdraw).sub(balanceAfter).toString(), amount.div(toBN(2)).toString())) + }) + + it("borrow usdc and get liquidated", async () => { + const joe = (await ForceEthSend.new(whale)).address // fresh new address + const daiAmount = dec(50, 18) + + console.log("impersonate accounts") + await hre.network.provider.request({method: "hardhat_impersonateAccount",params: [joe],}) + + console.log("send ether to joe") + await web3.eth.sendTransaction({from: maticWhale, to: joe, value: toBN(dec(100, 18))}) + + console.log("send dai to whale") + await dai.transfer(joe, daiAmount, {from: daiWhale}) + + console.log("give dai allowance to lending pool") + await dai.approve(lendingPool.address, daiAmount, {from: joe}) + console.log("deposit into pool") + await lendingPool.deposit(dai.address, daiAmount, joe, 0, {from: joe}) + + const usdcDebtAmount = dec(10, 6) + await lendingPool.borrow(usdc.address, usdcDebtAmount, 2, 0, joe, {from: joe}) + + console.log("set dai price oracle to low number") + const lowPrice = toBN(await oracle.getAssetPrice(usdcAddress)).div(toBN(10)) + await daiPriceOracle.setPrice(lowPrice.toString()) + + const debtToLiquidate = dec(1, 6) + const expectedLiquidationProceeds = dec(105, 17) // 1 usdc = 10 dai + 5% premium + + console.log("testing can liquidate1") + const canLiq1 = await bamm.canLiquidate(usdcAddress, daiAddress, debtToLiquidate) + assert.equal(canLiq1, true, "expected to be able to liquidate") + + console.log("testing can liquidate2") + const canLiq2 = await bamm.canLiquidate(usdcAddress, daiAddress, dec(10000000, 18)) + assert.equal(canLiq2, false, "expected to not be able to liquidate when not enough balance") + + console.log("testing can liquidate3") + const canLiq3 = await bamm.canLiquidate(usdcAddress, cDAI.address, debtToLiquidate) + assert.equal(canLiq3, false, "expected not to be able to liquidate when collateral is invalid") + + console.log("testing can liquidate4") + const canLiq4 = await bamm.canLiquidate(cDAI.address, daiAddress, debtToLiquidate) + assert.equal(canLiq4, false, "expected not to be able to liquidate when debt is invalid") + + console.log("calling liquidate borrow") + await bamm.liquidateBorrow(joe, debtToLiquidate, cDAI.address, {from: whale}) + const aDAIToken = await MockToken.at(aDAI) + assert(inWeiRadius((await aDAIToken.balanceOf(bamm.address)).toString(), expectedLiquidationProceeds.toString(), 1e6)) + + console.log("done") + }) + + it("swap", async () => { + console.log("set price feed") + // set 1 dai = 1 usdc + await priceDai2UsdcFeed.setPrice(dec(1, 18), {from: whale}) + + console.log("mint 1 usdc token") + const oneUSDC = dec(1, 6) + await usdc.approve(lendingPool.address, oneUSDC, {from: whale}) + await lendingPool.deposit(usdcAddress, oneUSDC, whale, 0, {from: whale}) + + console.log("giving allowance of atokn") + await aUSDCToken.approve(aaveToCToken.address, oneUSDC, {from: whale}) + + // swap + console.log("swap usdc to dai") + console.log("fetch price", (await bamm.fetchPrice(cDAI.address)).toString()) + console.log("dai balance", (await cDAI.balanceOf(bamm.address)).toString()) + const expectedSwapReturn = await bamm.getSwapAmount(oneUSDC, cDAI.address) + console.log("expected return", expectedSwapReturn.toString()) + + const newAddress = (await ForceEthSend.new(whale)).address + + await bamm.swap(oneUSDC, cDAI.address, 1, newAddress, "0x", {from: whale}) + + console.log("alice balance", (await cDAI.balanceOf(newAddress)).toString()) + assert.equal((await cDAI.balanceOf(newAddress)).toString(), expectedSwapReturn.toString()) + }) + + it("withdraw all", async () => { + const whaleBammBalance = await bamm.balanceOf(whale) + + const aUSDCBalBefore = await aUSDCToken.balanceOf(whale) + const aDaiBalBefore = await cDAI.balanceOf(whale) + + const bammUSDCBalance = await aUSDCToken.balanceOf(bamm.address) + const bammDaiBalance = await cDAI.balanceOf(bamm.address) + + await bamm.withdraw(whaleBammBalance, {from: whale}) + + const aUSDCBalAfter = await aUSDCToken.balanceOf(whale) + const aDaiBalAfter = await cDAI.balanceOf(whale) + + assert(in100WeiRadius(toBN(aUSDCBalAfter).sub(toBN(aUSDCBalBefore)), bammUSDCBalance)) + assert(inWeiRadius(toBN(aDaiBalAfter).sub(toBN(aDaiBalBefore)).toString(), toBN(bammDaiBalance).toString(), 1e10)) + }) + }) +}) + + +// TODO - use atoken as collateral. not ctoken. + +function almostTheSame(n1, n2) { + n1 = Number(web3.utils.fromWei(n1)) + n2 = Number(web3.utils.fromWei(n2)) + //console.log(n1,n2) + + if(n1 * 1000 > n2 * 1001) return false + if(n2 * 1000 > n1 * 1001) return false + return true +} + +function in100WeiRadius(n1, n2) { + const x = toBN(n1) + const y = toBN(n2) + + if(x.add(toBN(100)).lt(y)) return false + if(y.add(toBN(100)).lt(x)) return false + + return true +} + +function inWeiRadius(n1, n2, wei) { + //console.log({n1}, {n2}, {wei}) + const x = toBN(n1) + const y = toBN(n2) + + if(x.add(toBN(wei)).lt(y)) { + console.log("inWeiRadius:", x.toString(), y.toString()) + return false + } + if(y.add(toBN(wei)).lt(x)) { + console.log("inWeiRadius:", x.toString(), y.toString()) + return false + } + + return true +} + +async function assertRevert(txPromise, message = undefined) { + try { + const tx = await txPromise + // console.log("tx succeeded") + assert.isFalse(tx.receipt.status) // when this assert fails, the expected revert didn't occur, i.e. the tx succeeded + } catch (err) { + // console.log("tx failed") + assert.include(err.message, "revert") + + if (message) { + assert.include(err.message, message) + } + } +}