From e892d52c51c77e8ee3176fab34916a2a930f9d41 Mon Sep 17 00:00:00 2001 From: yaron velner Date: Mon, 5 Jul 2021 21:58:51 +0300 Subject: [PATCH 01/11] b.compound --- .../connectors/b.protocol/compound/events.sol | 62 ++++ .../b.protocol/compound/helpers.sol | 26 ++ .../b.protocol/compound/interface.sol | 40 ++ .../connectors/b.protocol/compound/main.sol | 347 ++++++++++++++++++ test/b.protocol/b.compound.test.js | 128 +++++++ 5 files changed, 603 insertions(+) create mode 100644 contracts/mainnet/connectors/b.protocol/compound/events.sol create mode 100644 contracts/mainnet/connectors/b.protocol/compound/helpers.sol create mode 100644 contracts/mainnet/connectors/b.protocol/compound/interface.sol create mode 100644 contracts/mainnet/connectors/b.protocol/compound/main.sol create mode 100644 test/b.protocol/b.compound.test.js diff --git a/contracts/mainnet/connectors/b.protocol/compound/events.sol b/contracts/mainnet/connectors/b.protocol/compound/events.sol new file mode 100644 index 00000000..4e4b5033 --- /dev/null +++ b/contracts/mainnet/connectors/b.protocol/compound/events.sol @@ -0,0 +1,62 @@ +pragma solidity ^0.7.0; + +contract Events { + event LogDeposit( + address indexed token, + address cToken, + uint256 tokenAmt, + uint256 getId, + uint256 setId + ); + + event LogWithdraw( + address indexed token, + address cToken, + uint256 tokenAmt, + uint256 getId, + uint256 setId + ); + + event LogBorrow( + address indexed token, + address cToken, + uint256 tokenAmt, + uint256 getId, + uint256 setId + ); + + event LogPayback( + address indexed token, + address cToken, + uint256 tokenAmt, + uint256 getId, + uint256 setId + ); + + event LogDepositCToken( + address indexed token, + address cToken, + uint256 tokenAmt, + uint256 cTokenAmt, + uint256 getId, + uint256 setId + ); + + event LogWithdrawCToken( + address indexed token, + address cToken, + uint256 tokenAmt, + uint256 cTokenAmt, + uint256 getId, + uint256 setId + ); + + event LogLiquidate( + address indexed borrower, + address indexed tokenToPay, + address indexed tokenInReturn, + uint256 tokenAmt, + uint256 getId, + uint256 setId + ); +} diff --git a/contracts/mainnet/connectors/b.protocol/compound/helpers.sol b/contracts/mainnet/connectors/b.protocol/compound/helpers.sol new file mode 100644 index 00000000..74e149b1 --- /dev/null +++ b/contracts/mainnet/connectors/b.protocol/compound/helpers.sol @@ -0,0 +1,26 @@ +pragma solidity ^0.7.0; + +import { DSMath } from "./../../../common/math.sol"; +import { Basic } from "./../../../common/basic.sol"; +import { ComptrollerInterface, CompoundMappingInterface, BComptrollerInterface } from "./interface.sol"; + +abstract contract Helpers is DSMath, Basic { + /** + * @dev Compound Comptroller + */ + ComptrollerInterface internal constant troller = ComptrollerInterface(0x9dB10B9429989cC13408d7368644D4A1CB704ea3); + + /** + * @dev Compound Mapping + */ + CompoundMappingInterface internal constant compMapping = CompoundMappingInterface(0xA8F9D4aA7319C54C04404765117ddBf9448E2082); + + /** + * @dev B.Compound Mapping + */ + function getMapping(string calldata tokenId) public returns(address token, address btoken) { + address ctoken; + (token, ctoken) = compMapping.getMapping(tokenId); + btoken = BComptrollerInterface(address(troller)).c2b(ctoken); + } +} diff --git a/contracts/mainnet/connectors/b.protocol/compound/interface.sol b/contracts/mainnet/connectors/b.protocol/compound/interface.sol new file mode 100644 index 00000000..4788e448 --- /dev/null +++ b/contracts/mainnet/connectors/b.protocol/compound/interface.sol @@ -0,0 +1,40 @@ +pragma solidity ^0.7.0; + +interface CTokenInterface { + function mint(uint mintAmount) external returns (uint); + function redeem(uint redeemTokens) external returns (uint); + function borrow(uint borrowAmount) external returns (uint); + function repayBorrow(uint repayAmount) external returns (uint); + function repayBorrowBehalf(address borrower, uint repayAmount) external returns (uint); // For ERC20 + function liquidateBorrow(address borrower, uint repayAmount, address cTokenCollateral) external returns (uint); + + function borrowBalanceCurrent(address account) external returns (uint); + function redeemUnderlying(uint redeemAmount) external returns (uint); + function exchangeRateCurrent() external returns (uint); + + function balanceOf(address owner) external view returns (uint256 balance); +} + +interface CETHInterface { + function mint() external payable; + function repayBorrow() external payable; + function repayBorrowBehalf(address borrower) external payable; + function liquidateBorrow(address borrower, address cTokenCollateral) external payable; +} + +interface ComptrollerInterface { + function enterMarkets(address[] calldata cTokens) external returns (uint[] memory); + function exitMarket(address cTokenAddress) external returns (uint); + function getAssetsIn(address account) external view returns (address[] memory); + function getAccountLiquidity(address account) external view returns (uint, uint, uint); + function claimComp(address) external; +} + +interface CompoundMappingInterface { + function cTokenMapping(string calldata tokenId) external view returns (address); + function getMapping(string calldata tokenId) external view returns (address, address); +} + +interface BComptrollerInterface { + function c2b(address ctoken) external view returns(address); +} \ No newline at end of file diff --git a/contracts/mainnet/connectors/b.protocol/compound/main.sol b/contracts/mainnet/connectors/b.protocol/compound/main.sol new file mode 100644 index 00000000..ecc062ad --- /dev/null +++ b/contracts/mainnet/connectors/b.protocol/compound/main.sol @@ -0,0 +1,347 @@ +pragma solidity ^0.7.0; +pragma experimental ABIEncoderV2; + +/** + * @title B.Compound. + * @dev Lending & Borrowing. + */ + +import { TokenInterface } from "../../../common/interfaces.sol"; +import { Stores } from "../../../common/stores.sol"; +import { Helpers } from "./helpers.sol"; +import { Events } from "./events.sol"; +import { CETHInterface, CTokenInterface } from "./interface.sol"; + +abstract contract BCompoundResolver is Events, Helpers { + /** + * @dev Deposit ETH/ERC20_Token. + * @notice Deposit a token to Compound for lending / collaterization. + * @param token The address of the token to deposit. (For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param cToken The address of the corresponding cToken. + * @param amt The amount of the token to deposit. (For max: `uint256(-1)`) + * @param getId ID to retrieve amt. + * @param setId ID stores the amount of tokens deposited. + */ + function depositRaw( + address token, + address cToken, + uint256 amt, + uint256 getId, + uint256 setId + ) public payable returns (string memory _eventName, bytes memory _eventParam) { + uint _amt = getUint(getId, amt); + + require(token != address(0) && cToken != address(0), "invalid token/ctoken address"); + + if (token == ethAddr) { + _amt = _amt == uint(-1) ? address(this).balance : _amt; + CETHInterface(cToken).mint{value: _amt}(); + } else { + TokenInterface tokenContract = TokenInterface(token); + _amt = _amt == uint(-1) ? tokenContract.balanceOf(address(this)) : _amt; + approve(tokenContract, cToken, _amt); + require(CTokenInterface(cToken).mint(_amt) == 0, "deposit-failed"); + } + setUint(setId, _amt); + + _eventName = "LogDeposit(address,address,uint256,uint256,uint256)"; + _eventParam = abi.encode(token, cToken, _amt, getId, setId); + } + + /** + * @dev Deposit ETH/ERC20_Token using the Mapping. + * @notice Deposit a token to Compound for lending / collaterization. + * @param tokenId The token id of the token to deposit.(For eg: ETH-A) + * @param amt The amount of the token to deposit. (For max: `uint256(-1)`) + * @param getId ID to retrieve amt. + * @param setId ID stores the amount of tokens deposited. + */ + function deposit( + string calldata tokenId, + uint256 amt, + uint256 getId, + uint256 setId + ) external payable returns (string memory _eventName, bytes memory _eventParam) { + (address token, address cToken) = getMapping(tokenId); + (_eventName, _eventParam) = depositRaw(token, cToken, amt, getId, setId); + } + + /** + * @dev Withdraw ETH/ERC20_Token. + * @notice Withdraw deposited token from Compound + * @param token The address of the token to withdraw. (For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param cToken The address of the corresponding cToken. + * @param amt The amount of the token to withdraw. (For max: `uint256(-1)`) + * @param getId ID to retrieve amt. + * @param setId ID stores the amount of tokens withdrawn. + */ + function withdrawRaw( + address token, + address cToken, + uint256 amt, + uint256 getId, + uint256 setId + ) public payable returns (string memory _eventName, bytes memory _eventParam) { + uint _amt = getUint(getId, amt); + + require(token != address(0) && cToken != address(0), "invalid token/ctoken address"); + + CTokenInterface cTokenContract = CTokenInterface(cToken); + if (_amt == uint(-1)) { + TokenInterface tokenContract = TokenInterface(token); + uint initialBal = token == ethAddr ? address(this).balance : tokenContract.balanceOf(address(this)); + require(cTokenContract.redeem(cTokenContract.balanceOf(address(this))) == 0, "full-withdraw-failed"); + uint finalBal = token == ethAddr ? address(this).balance : tokenContract.balanceOf(address(this)); + _amt = finalBal - initialBal; + } else { + require(cTokenContract.redeemUnderlying(_amt) == 0, "withdraw-failed"); + } + setUint(setId, _amt); + + _eventName = "LogWithdraw(address,address,uint256,uint256,uint256)"; + _eventParam = abi.encode(token, cToken, _amt, getId, setId); + } + + /** + * @dev Withdraw ETH/ERC20_Token using the Mapping. + * @notice Withdraw deposited token from Compound + * @param tokenId The token id of the token to withdraw.(For eg: ETH-A) + * @param amt The amount of the token to withdraw. (For max: `uint256(-1)`) + * @param getId ID to retrieve amt. + * @param setId ID stores the amount of tokens withdrawn. + */ + function withdraw( + string calldata tokenId, + uint256 amt, + uint256 getId, + uint256 setId + ) external payable returns (string memory _eventName, bytes memory _eventParam) { + (address token, address cToken) = getMapping(tokenId); + (_eventName, _eventParam) = withdrawRaw(token, cToken, amt, getId, setId); + } + + /** + * @dev Borrow ETH/ERC20_Token. + * @notice Borrow a token using Compound + * @param token The address of the token to borrow. (For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param cToken The address of the corresponding cToken. + * @param amt The amount of the token to borrow. + * @param getId ID to retrieve amt. + * @param setId ID stores the amount of tokens borrowed. + */ + function borrowRaw( + address token, + address cToken, + uint256 amt, + uint256 getId, + uint256 setId + ) public payable returns (string memory _eventName, bytes memory _eventParam) { + uint _amt = getUint(getId, amt); + + require(token != address(0) && cToken != address(0), "invalid token/ctoken address"); + + require(CTokenInterface(cToken).borrow(_amt) == 0, "borrow-failed"); + setUint(setId, _amt); + + _eventName = "LogBorrow(address,address,uint256,uint256,uint256)"; + _eventParam = abi.encode(token, cToken, _amt, getId, setId); + } + + /** + * @dev Borrow ETH/ERC20_Token using the Mapping. + * @notice Borrow a token using Compound + * @param tokenId The token id of the token to borrow.(For eg: DAI-A) + * @param amt The amount of the token to borrow. + * @param getId ID to retrieve amt. + * @param setId ID stores the amount of tokens borrowed. + */ + function borrow( + string calldata tokenId, + uint256 amt, + uint256 getId, + uint256 setId + ) external payable returns (string memory _eventName, bytes memory _eventParam) { + (address token, address cToken) = getMapping(tokenId); + (_eventName, _eventParam) = borrowRaw(token, cToken, amt, getId, setId); + } + + /** + * @dev Payback borrowed ETH/ERC20_Token. + * @notice Payback debt owed. + * @param token The address of the token to payback. (For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param cToken The address of the corresponding cToken. + * @param amt The amount of the token to payback. (For max: `uint256(-1)`) + * @param getId ID to retrieve amt. + * @param setId ID stores the amount of tokens paid back. + */ + function paybackRaw( + address token, + address cToken, + uint256 amt, + uint256 getId, + uint256 setId + ) public payable returns (string memory _eventName, bytes memory _eventParam) { + uint _amt = getUint(getId, amt); + + require(token != address(0) && cToken != address(0), "invalid token/ctoken address"); + + CTokenInterface cTokenContract = CTokenInterface(cToken); + _amt = _amt == uint(-1) ? cTokenContract.borrowBalanceCurrent(address(this)) : _amt; + + if (token == ethAddr) { + require(address(this).balance >= _amt, "not-enough-eth"); + CETHInterface(cToken).repayBorrow{value: _amt}(); + } else { + TokenInterface tokenContract = TokenInterface(token); + require(tokenContract.balanceOf(address(this)) >= _amt, "not-enough-token"); + approve(tokenContract, cToken, _amt); + require(cTokenContract.repayBorrow(_amt) == 0, "repay-failed."); + } + setUint(setId, _amt); + + _eventName = "LogPayback(address,address,uint256,uint256,uint256)"; + _eventParam = abi.encode(token, cToken, _amt, getId, setId); + } + + /** + * @dev Payback borrowed ETH/ERC20_Token using the Mapping. + * @notice Payback debt owed. + * @param tokenId The token id of the token to payback.(For eg: COMP-A) + * @param amt The amount of the token to payback. (For max: `uint256(-1)`) + * @param getId ID to retrieve amt. + * @param setId ID stores the amount of tokens paid back. + */ + function payback( + string calldata tokenId, + uint256 amt, + uint256 getId, + uint256 setId + ) external payable returns (string memory _eventName, bytes memory _eventParam) { + (address token, address cToken) = getMapping(tokenId); + (_eventName, _eventParam) = paybackRaw(token, cToken, amt, getId, setId); + } + + /** + * @dev Deposit ETH/ERC20_Token. + * @notice Same as depositRaw. The only difference is this method stores cToken amount in set ID. + * @param token The address of the token to deposit. (For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param cToken The address of the corresponding cToken. + * @param amt The amount of the token to deposit. (For max: `uint256(-1)`) + * @param getId ID to retrieve amt. + * @param setId ID stores the amount of cTokens received. + */ + function depositCTokenRaw( + address token, + address cToken, + uint256 amt, + uint256 getId, + uint256 setId + ) public payable returns (string memory _eventName, bytes memory _eventParam) { + uint _amt = getUint(getId, amt); + + require(token != address(0) && cToken != address(0), "invalid token/ctoken address"); + + CTokenInterface ctokenContract = CTokenInterface(cToken); + uint initialBal = ctokenContract.balanceOf(address(this)); + + if (token == ethAddr) { + _amt = _amt == uint(-1) ? address(this).balance : _amt; + CETHInterface(cToken).mint{value: _amt}(); + } else { + TokenInterface tokenContract = TokenInterface(token); + _amt = _amt == uint(-1) ? tokenContract.balanceOf(address(this)) : _amt; + approve(tokenContract, cToken, _amt); + require(ctokenContract.mint(_amt) == 0, "deposit-ctoken-failed."); + } + + uint _cAmt; + + { + uint finalBal = ctokenContract.balanceOf(address(this)); + _cAmt = sub(finalBal, initialBal); + + setUint(setId, _cAmt); + } + + _eventName = "LogDepositCToken(address,address,uint256,uint256,uint256,uint256)"; + _eventParam = abi.encode(token, cToken, _amt, _cAmt, getId, setId); + } + + /** + * @dev Deposit ETH/ERC20_Token using the Mapping. + * @notice Same as deposit. The only difference is this method stores cToken amount in set ID. + * @param tokenId The token id of the token to depositCToken.(For eg: DAI-A) + * @param amt The amount of the token to deposit. (For max: `uint256(-1)`) + * @param getId ID to retrieve amt. + * @param setId ID stores the amount of cTokens received. + */ + function depositCToken( + string calldata tokenId, + uint256 amt, + uint256 getId, + uint256 setId + ) external payable returns (string memory _eventName, bytes memory _eventParam) { + (address token, address cToken) = getMapping(tokenId); + (_eventName, _eventParam) = depositCTokenRaw(token, cToken, amt, getId, setId); + } + + /** + * @dev Withdraw CETH/CERC20_Token using cToken Amt. + * @notice Same as withdrawRaw. The only difference is this method fetch cToken amount in get ID. + * @param token The address of the token to withdraw. (For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param cToken The address of the corresponding cToken. + * @param cTokenAmt The amount of cTokens to withdraw + * @param getId ID to retrieve cTokenAmt + * @param setId ID stores the amount of tokens withdrawn. + */ + function withdrawCTokenRaw( + address token, + address cToken, + uint cTokenAmt, + uint getId, + uint setId + ) public payable returns (string memory _eventName, bytes memory _eventParam) { + uint _cAmt = getUint(getId, cTokenAmt); + require(token != address(0) && cToken != address(0), "invalid token/ctoken address"); + + CTokenInterface cTokenContract = CTokenInterface(cToken); + TokenInterface tokenContract = TokenInterface(token); + _cAmt = _cAmt == uint(-1) ? cTokenContract.balanceOf(address(this)) : _cAmt; + + uint withdrawAmt; + { + uint initialBal = token != ethAddr ? tokenContract.balanceOf(address(this)) : address(this).balance; + require(cTokenContract.redeem(_cAmt) == 0, "redeem-failed"); + uint finalBal = token != ethAddr ? tokenContract.balanceOf(address(this)) : address(this).balance; + + withdrawAmt = sub(finalBal, initialBal); + } + + setUint(setId, withdrawAmt); + + _eventName = "LogWithdrawCToken(address,address,uint256,uint256,uint256,uint256)"; + _eventParam = abi.encode(token, cToken, withdrawAmt, _cAmt, getId, setId); + } + + /** + * @dev Withdraw CETH/CERC20_Token using cToken Amt & the Mapping. + * @notice Same as withdraw. The only difference is this method fetch cToken amount in get ID. + * @param tokenId The token id of the token to withdraw CToken.(For eg: ETH-A) + * @param cTokenAmt The amount of cTokens to withdraw + * @param getId ID to retrieve cTokenAmt + * @param setId ID stores the amount of tokens withdrawn. + */ + function withdrawCToken( + string calldata tokenId, + uint cTokenAmt, + uint getId, + uint setId + ) external payable returns (string memory _eventName, bytes memory _eventParam) { + (address token, address cToken) = getMapping(tokenId); + (_eventName, _eventParam) = withdrawCTokenRaw(token, cToken, cTokenAmt, getId, setId); + } +} + +contract ConnectV1BCompound is BCompoundResolver { + string public name = "B.Compound-v1.0"; +} diff --git a/test/b.protocol/b.compound.test.js b/test/b.protocol/b.compound.test.js new file mode 100644 index 00000000..5d08235d --- /dev/null +++ b/test/b.protocol/b.compound.test.js @@ -0,0 +1,128 @@ +const { expect } = require("chai"); +const hre = require("hardhat"); +const { web3, deployments, waffle, ethers } = hre; +const { provider, deployContract } = waffle + +const deployAndEnableConnector = require("../../scripts/deployAndEnableConnector.js") +const buildDSAv2 = require("../../scripts/buildDSAv2") +const encodeSpells = require("../../scripts/encodeSpells.js") +const getMasterSigner = require("../../scripts/getMasterSigner") + +const addresses = require("../../scripts/constant/addresses"); +const abis = require("../../scripts/constant/abis"); +const constants = require("../../scripts/constant/constant"); +const tokens = require("../../scripts/constant/tokens"); + +const connectV2CompoundArtifacts = require("../../artifacts/contracts/mainnet/connectors/b.protocol/compound/main.sol/ConnectV1BCompound.json") + +describe("B.Compound", function () { + const connectorName = "B.COMPOUND-TEST-A" + + let dsaWallet0 + let masterSigner; + let instaConnectorsV2; + let connector; + + const wallets = provider.getWallets() + const [wallet0, wallet1, wallet2, wallet3] = wallets + before(async () => { + masterSigner = await getMasterSigner(wallet3) + instaConnectorsV2 = await ethers.getContractAt(abis.core.connectorsV2, addresses.core.connectorsV2); + connector = await deployAndEnableConnector({ + connectorName, + contractArtifact: connectV2CompoundArtifacts, + signer: masterSigner, + connectors: instaConnectorsV2 + }) + console.log("Connector address", connector.address) + }) + + it("Should have contracts deployed.", async function () { + expect(!!instaConnectorsV2.address).to.be.true; + expect(!!connector.address).to.be.true; + expect(!!masterSigner.address).to.be.true; + expect(await connector.name()).to.be.equal("B.Compound-v1.0"); + }); + + describe("DSA wallet setup", function () { + it("Should build DSA v2", async function () { + dsaWallet0 = await buildDSAv2(wallet0.address) + expect(!!dsaWallet0.address).to.be.true; + }); + + it("Deposit ETH into DSA wallet", async function () { + await wallet0.sendTransaction({ + to: dsaWallet0.address, + value: ethers.utils.parseEther("10") + }); + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte(ethers.utils.parseEther("10")); + }); + }); + + describe("Main", function () { + + it("Should deposit ETH in Compound", async function () { + const amount = ethers.utils.parseEther("1") // 1 ETH + const spells = [ + { + connector: connectorName, + method: "deposit", + args: ["ETH-A", amount, 0, 0] + } + ] + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address) + const receipt = await tx.wait() + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.lte(ethers.utils.parseEther("9")); + }); + + it("Should borrow and payback DAI from Compound", async function () { + const amount = ethers.utils.parseEther("100") // 100 DAI + const setId = "83478237" + const spells = [ + { + connector: connectorName, + method: "borrow", + args: ["DAI-A", amount, 0, setId] + }, + { + connector: connectorName, + method: "payback", + args: ["DAI-A", 0, setId, 0] + } + ] + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address) + const receipt = await tx.wait() + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.lte(ethers.utils.parseEther("9")); + }); + + it("Should deposit all ETH in Compound", async function () { + const spells = [ + { + connector: connectorName, + method: "deposit", + args: ["ETH-A", constants.max_value, 0, 0] + } + ] + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address) + const receipt = await tx.wait() + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.lte(ethers.utils.parseEther("0")); + }); + + it("Should withdraw all ETH from Compound", async function () { + const spells = [ + { + connector: connectorName, + method: "withdraw", + args: ["ETH-A", constants.max_value, 0, 0] + } + ] + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address) + const receipt = await tx.wait() + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte(ethers.utils.parseEther("10")); + }); + }) +}) From f448d4eaa411154d141e762fcedfe6a681375202 Mon Sep 17 00:00:00 2001 From: yaron velner Date: Mon, 5 Jul 2021 22:34:11 +0300 Subject: [PATCH 02/11] makerdao before tests --- .../connectors/b.protocol/makerdao/events.sol | 26 + .../b.protocol/makerdao/helpers.sol | 130 +++++ .../b.protocol/makerdao/interface.sol | 65 +++ .../connectors/b.protocol/makerdao/main.sol | 524 ++++++++++++++++++ 4 files changed, 745 insertions(+) create mode 100644 contracts/mainnet/connectors/b.protocol/makerdao/events.sol create mode 100644 contracts/mainnet/connectors/b.protocol/makerdao/helpers.sol create mode 100644 contracts/mainnet/connectors/b.protocol/makerdao/interface.sol create mode 100644 contracts/mainnet/connectors/b.protocol/makerdao/main.sol diff --git a/contracts/mainnet/connectors/b.protocol/makerdao/events.sol b/contracts/mainnet/connectors/b.protocol/makerdao/events.sol new file mode 100644 index 00000000..702a70f3 --- /dev/null +++ b/contracts/mainnet/connectors/b.protocol/makerdao/events.sol @@ -0,0 +1,26 @@ +pragma solidity ^0.7.0; + +contract Events { + event LogOpen(uint256 indexed vault, bytes32 indexed ilk); + event LogClose(uint256 indexed vault, bytes32 indexed ilk); + event LogTransfer(uint256 indexed vault, bytes32 indexed ilk, address newOwner); + event LogDeposit(uint256 indexed vault, bytes32 indexed ilk, uint256 tokenAmt, uint256 getId, uint256 setId); + event LogWithdraw(uint256 indexed vault, bytes32 indexed ilk, uint256 tokenAmt, uint256 getId, uint256 setId); + event LogBorrow(uint256 indexed vault, bytes32 indexed ilk, uint256 tokenAmt, uint256 getId, uint256 setId); + event LogPayback(uint256 indexed vault, bytes32 indexed ilk, uint256 tokenAmt, uint256 getId, uint256 setId); + event LogWithdrawLiquidated(uint256 indexed vault, bytes32 indexed ilk, uint256 tokenAmt, uint256 getId, uint256 setId); + event LogExitDai(uint256 indexed vault, bytes32 indexed ilk, uint256 tokenAmt, uint256 getId, uint256 setId); + event LogDepositDai(uint256 tokenAmt, uint256 getId, uint256 setId); + event LogWithdrawDai(uint256 tokenAmt, uint256 getId, uint256 setId); + + event LogDepositAndBorrow( + uint256 indexed vault, + bytes32 indexed ilk, + uint256 depositAmt, + uint256 borrowAmt, + uint256 getIdDeposit, + uint256 getIdBorrow, + uint256 setIdDeposit, + uint256 setIdBorrow + ); +} \ No newline at end of file diff --git a/contracts/mainnet/connectors/b.protocol/makerdao/helpers.sol b/contracts/mainnet/connectors/b.protocol/makerdao/helpers.sol new file mode 100644 index 00000000..37d0388d --- /dev/null +++ b/contracts/mainnet/connectors/b.protocol/makerdao/helpers.sol @@ -0,0 +1,130 @@ +pragma solidity ^0.7.0; + +import { DSMath } from "../../../common/math.sol"; +import { Basic } from "../../../common/basic.sol"; +import { TokenInterface } from "./../../../common/interfaces.sol"; +import { BManagerLike, DaiJoinInterface, PotLike, VatLike, JugLike } from "./interface.sol"; + +abstract contract Helpers is DSMath, Basic { + /** + * @dev Manager Interface + */ + BManagerLike internal constant managerContract = BManagerLike(0x3f30c2381CD8B917Dd96EB2f1A4F96D91324BBed); + + /** + * @dev DAI Join + */ + DaiJoinInterface internal constant daiJoinContract = DaiJoinInterface(0x9759A6Ac90977b93B58547b4A71c78317f391A28); + + /** + * @dev Pot + */ + PotLike internal constant potContract = PotLike(0x197E90f9FAD81970bA7976f33CbD77088E5D7cf7); + + /** + * @dev Maker MCD Jug Address. + */ + JugLike internal constant mcdJug = JugLike(0x19c0976f590D67707E62397C87829d896Dc0f1F1); + + /** + * @dev Return Close Vault Address. + */ + address internal constant giveAddr = 0x4dD58550eb15190a5B3DfAE28BB14EeC181fC267; + + /** + * @dev Get Vault's ilk. + */ + function getVaultData(uint vault) internal view returns (bytes32 ilk, address urn) { + ilk = managerContract.ilks(vault); + urn = managerContract.urns(vault); + } + + /** + * @dev Gem Join address is ETH type collateral. + */ + function isEth(address tknAddr) internal pure returns (bool) { + return tknAddr == wethAddr ? true : false; + } + + /** + * @dev Get Vault Debt Amount. + */ + function _getVaultDebt( + address vat, + bytes32 ilk, + address urn, + uint vault + ) internal view returns (uint wad) { + (, uint rate,,,) = VatLike(vat).ilks(ilk); + (, uint art) = VatLike(vat).urns(ilk, urn); + uint cushion = managerContract.cushion(vault); + art = add(art, cushion); + uint dai = VatLike(vat).dai(urn); + + uint rad = sub(mul(art, rate), dai); + wad = rad / RAY; + + wad = mul(wad, RAY) < rad ? wad + 1 : wad; + } + + /** + * @dev Get Borrow Amount. + */ + function _getBorrowAmt( + address vat, + address urn, + bytes32 ilk, + uint amt + ) internal returns (int dart) + { + uint rate = mcdJug.drip(ilk); + uint dai = VatLike(vat).dai(urn); + if (dai < mul(amt, RAY)) { + dart = toInt(sub(mul(amt, RAY), dai) / rate); + dart = mul(uint(dart), rate) < mul(amt, RAY) ? dart + 1 : dart; + } + } + + /** + * @dev Get Payback Amount. + */ + function _getWipeAmt( + address vat, + uint amt, + address urn, + bytes32 ilk, + uint vault + ) internal view returns (int dart) + { + (, uint rate,,,) = VatLike(vat).ilks(ilk); + (, uint art) = VatLike(vat).urns(ilk, urn); + uint cushion = managerContract.cushion(vault); + art = add(art, cushion); + dart = toInt(amt / rate); + dart = uint(dart) <= art ? - dart : - toInt(art); + } + + /** + * @dev Convert String to bytes32. + */ + function stringToBytes32(string memory str) internal pure returns (bytes32 result) { + require(bytes(str).length != 0, "string-empty"); + // solium-disable-next-line security/no-inline-assembly + assembly { + result := mload(add(str, 32)) + } + } + + /** + * @dev Get vault ID. If `vault` is 0, get last opened vault. + */ + function getVault(uint vault) internal view returns (uint _vault) { + if (vault == 0) { + require(managerContract.count(address(this)) > 0, "no-vault-opened"); + _vault = managerContract.last(address(this)); + } else { + _vault = vault; + } + } + +} \ No newline at end of file diff --git a/contracts/mainnet/connectors/b.protocol/makerdao/interface.sol b/contracts/mainnet/connectors/b.protocol/makerdao/interface.sol new file mode 100644 index 00000000..7a8c4ca8 --- /dev/null +++ b/contracts/mainnet/connectors/b.protocol/makerdao/interface.sol @@ -0,0 +1,65 @@ +pragma solidity ^0.7.0; + +import { TokenInterface } from "../../../common/interfaces.sol"; + +interface ManagerLike { + function cdpCan(address, uint, address) external view returns (uint); + function ilks(uint) external view returns (bytes32); + function last(address) external view returns (uint); + function count(address) external view returns (uint); + function owns(uint) external view returns (address); + function urns(uint) external view returns (address); + function vat() external view returns (address); + function open(bytes32, address) external returns (uint); + function give(uint, address) external; + function frob(uint, int, int) external; + function flux(uint, address, uint) external; + function move(uint, address, uint) external; +} + +interface BManagerLike is ManagerLike { + function cushion(uint) external view returns(uint); +} + +interface VatLike { + function can(address, address) external view returns (uint); + function ilks(bytes32) external view returns (uint, uint, uint, uint, uint); + function dai(address) external view returns (uint); + function urns(bytes32, address) external view returns (uint, uint); + function frob( + bytes32, + address, + address, + address, + int, + int + ) external; + function hope(address) external; + function move(address, address, uint) external; + function gem(bytes32, address) external view returns (uint); +} + +interface TokenJoinInterface { + function dec() external returns (uint); + function gem() external returns (TokenInterface); + function join(address, uint) external payable; + function exit(address, uint) external; +} + +interface DaiJoinInterface { + function vat() external returns (VatLike); + function dai() external returns (TokenInterface); + function join(address, uint) external payable; + function exit(address, uint) external; +} + +interface JugLike { + function drip(bytes32) external returns (uint); +} + +interface PotLike { + function pie(address) external view returns (uint); + function drip() external returns (uint); + function join(uint) external; + function exit(uint) external; +} diff --git a/contracts/mainnet/connectors/b.protocol/makerdao/main.sol b/contracts/mainnet/connectors/b.protocol/makerdao/main.sol new file mode 100644 index 00000000..2f94c1e4 --- /dev/null +++ b/contracts/mainnet/connectors/b.protocol/makerdao/main.sol @@ -0,0 +1,524 @@ +pragma solidity ^0.7.0; + +/** + * @title MakerDAO. + * @dev Collateralized Borrowing. + */ + +import { TokenInterface, AccountInterface } from "./../../../common/interfaces.sol"; +import { Helpers } from "./helpers.sol"; +import { Events } from "./events.sol"; +import { VatLike, TokenJoinInterface } from "./interface.sol"; + +abstract contract BMakerResolver is Helpers, Events { + /** + * @dev Open Vault + * @notice Open a MakerDAO Vault + * @param colType Type of Collateral.(eg: 'ETH-A') + */ + function open(string calldata colType) external payable returns (string memory _eventName, bytes memory _eventParam) { + bytes32 ilk = stringToBytes32(colType); + require(instaMapping.gemJoinMapping(ilk) != address(0), "wrong-col-type"); + uint256 vault = managerContract.open(ilk, address(this)); + + _eventName = "LogOpen(uint256,bytes32)"; + _eventParam = abi.encode(vault, ilk); + } + + /** + * @dev Close Vault + * @notice Close a MakerDAO Vault + * @param vault Vault ID to close. + */ + function close(uint256 vault) external payable returns (string memory _eventName, bytes memory _eventParam) { + uint _vault = getVault(vault); + (bytes32 ilk, address urn) = getVaultData(_vault); + (uint ink, uint art) = VatLike(managerContract.vat()).urns(ilk, urn); + + require(ink == 0 && art == 0, "vault-has-assets"); + require(managerContract.owns(_vault) == address(this), "not-owner"); + + managerContract.give(_vault, giveAddr); + + _eventName = "LogClose(uint256,bytes32)"; + _eventParam = abi.encode(_vault, ilk); + } + + /** + * @dev Transfer Vault + * @notice Transfer a MakerDAO Vault to "nextOwner" + * @param vault Vault ID to close. + * @param nextOwner Address of the next owner of the vault. + */ + function transfer( + uint vault, + address nextOwner + ) external payable returns (string memory _eventName, bytes memory _eventParam) { + require(AccountInterface(address(this)).isAuth(nextOwner), "nextOwner-is-not-auth"); + + uint256 _vault = getVault(vault); + (bytes32 ilk,) = getVaultData(_vault); + + require(managerContract.owns(_vault) == address(this), "not-owner"); + + managerContract.give(_vault, nextOwner); + + _eventName = "LogTransfer(uint256,bytes32,address)"; + _eventParam = abi.encode(_vault, ilk, nextOwner); + } + + /** + * @dev Deposit ETH/ERC20_Token Collateral. + * @notice Deposit collateral to a MakerDAO vault + * @param vault Vault ID. (Use 0 for last opened vault) + * @param amt The amount of tokens to deposit. (For max: `uint256(-1)`) + * @param getId ID to retrieve amt. + * @param setId ID stores the amount of tokens deposited. + */ + function deposit( + uint256 vault, + uint256 amt, + uint256 getId, + uint256 setId + ) external payable returns (string memory _eventName, bytes memory _eventParam) { + uint _amt = getUint(getId, amt); + uint _vault = getVault(vault); + (bytes32 ilk, address urn) = getVaultData(_vault); + + address colAddr = instaMapping.gemJoinMapping(ilk); + TokenJoinInterface tokenJoinContract = TokenJoinInterface(colAddr); + TokenInterface tokenContract = tokenJoinContract.gem(); + + if (isEth(address(tokenContract))) { + _amt = _amt == uint(-1) ? address(this).balance : _amt; + tokenContract.deposit{value: _amt}(); + } else { + _amt = _amt == uint(-1) ? tokenContract.balanceOf(address(this)) : _amt; + } + + approve(tokenContract, address(colAddr), _amt); + tokenJoinContract.join(address(this), _amt); + + VatLike(managerContract.vat()).frob( // TODO zelda - make manager.frob + ilk, + urn, + address(this), + address(this), + toInt(convertTo18(tokenJoinContract.dec(), _amt)), + 0 + ); + + setUint(setId, _amt); + + _eventName = "LogDeposit(uint256,bytes32,uint256,uint256,uint256)"; + _eventParam = abi.encode(_vault, ilk, _amt, getId, setId); + } + + /** + * @dev Withdraw ETH/ERC20_Token Collateral. + * @notice Withdraw collateral from a MakerDAO vault + * @param vault Vault ID. (Use 0 for last opened vault) + * @param amt The amount of tokens to withdraw. (For max: `uint256(-1)`) + * @param getId ID to retrieve amt. + * @param setId ID stores the amount of tokens withdrawn. + */ + function withdraw( + uint256 vault, + uint256 amt, + uint256 getId, + uint256 setId + ) external payable returns (string memory _eventName, bytes memory _eventParam) { + uint _amt = getUint(getId, amt); + uint _vault = getVault(vault); + (bytes32 ilk, address urn) = getVaultData(_vault); + + address colAddr = instaMapping.gemJoinMapping(ilk); + TokenJoinInterface tokenJoinContract = TokenJoinInterface(colAddr); + + uint _amt18; + if (_amt == uint(-1)) { + (_amt18,) = VatLike(managerContract.vat()).urns(ilk, urn); + _amt = convert18ToDec(tokenJoinContract.dec(), _amt18); + } else { + _amt18 = convertTo18(tokenJoinContract.dec(), _amt); + } + + managerContract.frob( + _vault, + -toInt(_amt18), + 0 + ); + + managerContract.flux( + _vault, + address(this), + _amt18 + ); + + TokenInterface tokenContract = tokenJoinContract.gem(); + + if (isEth(address(tokenContract))) { + tokenJoinContract.exit(address(this), _amt); + tokenContract.withdraw(_amt); + } else { + tokenJoinContract.exit(address(this), _amt); + } + + setUint(setId, _amt); + + _eventName = "LogWithdraw(uint256,bytes32,uint256,uint256,uint256)"; + _eventParam = abi.encode(_vault, ilk, _amt, getId, setId); + } + + /** + * @dev Borrow DAI. + * @notice Borrow DAI using a MakerDAO vault + * @param vault Vault ID. (Use 0 for last opened vault) + * @param amt The amount of DAI to borrow. + * @param getId ID to retrieve amt. + * @param setId ID stores the amount of DAI borrowed. + */ + function borrow( + uint256 vault, + uint256 amt, + uint256 getId, + uint256 setId + ) external payable returns (string memory _eventName, bytes memory _eventParam) { + uint _amt = getUint(getId, amt); + uint _vault = getVault(vault); + (bytes32 ilk, address urn) = getVaultData(_vault); + + VatLike vatContract = VatLike(managerContract.vat()); + + managerContract.frob( + _vault, + 0, + _getBorrowAmt( + address(vatContract), + urn, + ilk, + _amt + ) + ); + + managerContract.move( + _vault, + address(this), + toRad(_amt) + ); + + if (vatContract.can(address(this), address(daiJoinContract)) == 0) { + vatContract.hope(address(daiJoinContract)); + } + + daiJoinContract.exit(address(this), _amt); + + setUint(setId, _amt); + + _eventName = "LogBorrow(uint256,bytes32,uint256,uint256,uint256)"; + _eventParam = abi.encode(_vault, ilk, _amt, getId, setId); + } + + /** + * @dev Payback borrowed DAI. + * @notice Payback DAI debt owed by a MakerDAO vault + * @param vault Vault ID. (Use 0 for last opened vault) + * @param amt The amount of DAI to payback. (For max: `uint256(-1)`) + * @param getId ID to retrieve amt. + * @param setId ID stores the amount of DAI paid back. + */ + function payback( + uint256 vault, + uint256 amt, + uint256 getId, + uint256 setId + ) external payable returns (string memory _eventName, bytes memory _eventParam) { + uint _amt = getUint(getId, amt); + uint _vault = getVault(vault); + (bytes32 ilk, address urn) = getVaultData(_vault); + + address vat = managerContract.vat(); + + uint _maxDebt = _getVaultDebt(vat, ilk, urn, vault); + + _amt = _amt == uint(-1) ? _maxDebt : _amt; + + require(_maxDebt >= _amt, "paying-excess-debt"); + + approve(daiJoinContract.dai(), address(daiJoinContract), _amt); + daiJoinContract.join(urn, _amt); + + managerContract.frob( + _vault, + 0, + _getWipeAmt( + vat, + VatLike(vat).dai(urn), + urn, + ilk, + _vault + ) + ); + + setUint(setId, _amt); + + _eventName = "LogPayback(uint256,bytes32,uint256,uint256,uint256)"; + _eventParam = abi.encode(_vault, ilk, _amt, getId, setId); + } + + /** + * @dev Withdraw leftover ETH/ERC20_Token after Liquidation. + * @notice Withdraw leftover collateral after Liquidation. + * @param vault Vault ID. (Use 0 for last opened vault) + * @param amt token amount to Withdraw. (For max: `uint256(-1)`) + * @param getId ID to retrieve amt. + * @param setId ID stores the amount of collateral withdrawn. + */ + function withdrawLiquidated( + uint256 vault, + uint256 amt, + uint256 getId, + uint256 setId + ) external payable returns (string memory _eventName, bytes memory _eventParam) { + uint _amt = getUint(getId, amt); + (bytes32 ilk, address urn) = getVaultData(vault); + + address colAddr = instaMapping.gemJoinMapping(ilk); + TokenJoinInterface tokenJoinContract = TokenJoinInterface(colAddr); + + uint _amt18; + if (_amt == uint(-1)) { + _amt18 = VatLike(managerContract.vat()).gem(ilk, urn); + _amt = convert18ToDec(tokenJoinContract.dec(), _amt18); + } else { + _amt18 = convertTo18(tokenJoinContract.dec(), _amt); + } + + managerContract.flux( + vault, + address(this), + _amt18 + ); + + TokenInterface tokenContract = tokenJoinContract.gem(); + tokenJoinContract.exit(address(this), _amt); + if (isEth(address(tokenContract))) { + tokenContract.withdraw(_amt); + } + + setUint(setId, _amt); + + _eventName = "LogWithdrawLiquidated(uint256,bytes32,uint256,uint256,uint256)"; + _eventParam = abi.encode(vault, ilk, _amt, getId, setId); + } + + struct MakerData { + uint _vault; + address colAddr; + TokenJoinInterface tokenJoinContract; + VatLike vatContract; + TokenInterface tokenContract; + } + /** + * @dev Deposit ETH/ERC20_Token Collateral and Borrow DAI. + * @notice Deposit collateral and borrow DAI. + * @param vault Vault ID. (Use 0 for last opened vault) + * @param depositAmt The amount of tokens to deposit. (For max: `uint256(-1)`) + * @param borrowAmt The amount of DAI to borrow. + * @param getIdDeposit ID to retrieve depositAmt. + * @param getIdBorrow ID to retrieve borrowAmt. + * @param setIdDeposit ID stores the amount of tokens deposited. + * @param setIdBorrow ID stores the amount of DAI borrowed. + */ + function depositAndBorrow( + uint256 vault, + uint256 depositAmt, + uint256 borrowAmt, + uint256 getIdDeposit, + uint256 getIdBorrow, + uint256 setIdDeposit, + uint256 setIdBorrow + ) external payable returns (string memory _eventName, bytes memory _eventParam) { + MakerData memory makerData; + uint _amtDeposit = getUint(getIdDeposit, depositAmt); + uint _amtBorrow = getUint(getIdBorrow, borrowAmt); + + makerData._vault = getVault(vault); + (bytes32 ilk, address urn) = getVaultData(makerData._vault); + + makerData.colAddr = instaMapping.gemJoinMapping(ilk); + makerData.tokenJoinContract = TokenJoinInterface(makerData.colAddr); + makerData.vatContract = VatLike(managerContract.vat()); + makerData.tokenContract = makerData.tokenJoinContract.gem(); + + if (isEth(address(makerData.tokenContract))) { + _amtDeposit = _amtDeposit == uint(-1) ? address(this).balance : _amtDeposit; + makerData.tokenContract.deposit{value: _amtDeposit}(); + } else { + _amtDeposit = _amtDeposit == uint(-1) ? makerData.tokenContract.balanceOf(address(this)) : _amtDeposit; + } + + approve(makerData.tokenContract, address(makerData.colAddr), _amtDeposit); + makerData.tokenJoinContract.join(urn, _amtDeposit); + + managerContract.frob( + makerData._vault, + toInt(convertTo18(makerData.tokenJoinContract.dec(), _amtDeposit)), + _getBorrowAmt( + address(makerData.vatContract), + urn, + ilk, + _amtBorrow + ) + ); + + managerContract.move( + makerData._vault, + address(this), + toRad(_amtBorrow) + ); + + if (makerData.vatContract.can(address(this), address(daiJoinContract)) == 0) { + makerData.vatContract.hope(address(daiJoinContract)); + } + + daiJoinContract.exit(address(this), _amtBorrow); + + setUint(setIdDeposit, _amtDeposit); + setUint(setIdBorrow, _amtBorrow); + + _eventName = "LogDepositAndBorrow(uint256,bytes32,uint256,uint256,uint256,uint256,uint256,uint256)"; + _eventParam = abi.encode( + makerData._vault, + ilk, + _amtDeposit, + _amtBorrow, + getIdDeposit, + getIdBorrow, + setIdDeposit, + setIdBorrow + ); + } + + /** + * @dev Exit DAI from urn. + * @notice Exit DAI from urn. + * @param vault Vault ID. (Use 0 for last opened vault) + * @param amt The amount of DAI to exit. (For max: `uint256(-1)`) + * @param getId ID to retrieve amt. + * @param setId ID stores the amount of DAI exited. + */ + function exitDai( + uint256 vault, + uint256 amt, + uint256 getId, + uint256 setId + ) external payable returns (string memory _eventName, bytes memory _eventParam) { + uint _amt = getUint(getId, amt); + uint _vault = getVault(vault); + (bytes32 ilk, address urn) = getVaultData(_vault); + + VatLike vatContract = VatLike(managerContract.vat()); + if(_amt == uint(-1)) { + _amt = vatContract.dai(urn); + _amt = _amt / 10 ** 27; + } + + managerContract.move( + _vault, + address(this), + toRad(_amt) + ); + + if (vatContract.can(address(this), address(daiJoinContract)) == 0) { + vatContract.hope(address(daiJoinContract)); + } + + daiJoinContract.exit(address(this), _amt); + + setUint(setId, _amt); + + _eventName = "LogExitDai(uint256,bytes32,uint256,uint256,uint256)"; + _eventParam = abi.encode(_vault, ilk, _amt, getId, setId); + } + + /** + * @dev Deposit DAI in DSR. + * @notice Deposit DAI in DSR. + * @param amt The amount of DAI to deposit. (For max: `uint256(-1)`) + * @param getId ID to retrieve amt. + * @param setId ID stores the amount of DAI deposited. + */ + function depositDai( + uint256 amt, + uint256 getId, + uint256 setId + ) external payable returns (string memory _eventName, bytes memory _eventParam) { + uint _amt = getUint(getId, amt); + + _amt = _amt == uint(-1) ? + daiJoinContract.dai().balanceOf(address(this)) : + _amt; + + VatLike vat = daiJoinContract.vat(); + uint chi = potContract.drip(); + + approve(daiJoinContract.dai(), address(daiJoinContract), _amt); + daiJoinContract.join(address(this), _amt); + if (vat.can(address(this), address(potContract)) == 0) { + vat.hope(address(potContract)); + } + + potContract.join(mul(_amt, RAY) / chi); + setUint(setId, _amt); + + _eventName = "LogDepositDai(uint256,uint256,uint256)"; + _eventParam = abi.encode(_amt, getId, setId); + } + + /** + * @dev Withdraw DAI from DSR. + * @notice Withdraw DAI from DSR. + * @param amt The amount of DAI to withdraw. (For max: `uint256(-1)`) + * @param getId ID to retrieve amt. + * @param setId ID stores the amount of DAI withdrawn. + */ + function withdrawDai( + uint256 amt, + uint256 getId, + uint256 setId + ) external payable returns (string memory _eventName, bytes memory _eventParam) { + uint _amt = getUint(getId, amt); + + VatLike vat = daiJoinContract.vat(); + + uint chi = potContract.drip(); + uint pie; + if (_amt == uint(-1)) { + pie = potContract.pie(address(this)); + _amt = mul(chi, pie) / RAY; + } else { + pie = mul(_amt, RAY) / chi; + } + + potContract.exit(pie); + + uint bal = vat.dai(address(this)); + if (vat.can(address(this), address(daiJoinContract)) == 0) { + vat.hope(address(daiJoinContract)); + } + daiJoinContract.exit( + address(this), + bal >= mul(_amt, RAY) ? _amt : bal / RAY + ); + + setUint(setId, _amt); + + _eventName = "LogWithdrawDai(uint256,uint256,uint256)"; + _eventParam = abi.encode(_amt, getId, setId); + } +} + +contract ConnectV1BMakerDAO is BMakerResolver { + string public constant name = "B.MakerDAO-v1.0"; +} From 07705981d62e080da9654f2db083456a833f2770 Mon Sep 17 00:00:00 2001 From: yaron velner Date: Tue, 6 Jul 2021 00:08:32 +0300 Subject: [PATCH 03/11] basic b.makerdao tests --- .../b.protocol/makerdao/interface.sol | 3 +- test/b.protocol/b.maker.test.js | 198 ++++++++++++++++++ 2 files changed, 200 insertions(+), 1 deletion(-) create mode 100644 test/b.protocol/b.maker.test.js diff --git a/contracts/mainnet/connectors/b.protocol/makerdao/interface.sol b/contracts/mainnet/connectors/b.protocol/makerdao/interface.sol index 7a8c4ca8..d291e384 100644 --- a/contracts/mainnet/connectors/b.protocol/makerdao/interface.sol +++ b/contracts/mainnet/connectors/b.protocol/makerdao/interface.sol @@ -18,7 +18,8 @@ interface ManagerLike { } interface BManagerLike is ManagerLike { - function cushion(uint) external view returns(uint); + function cushion(uint) external view returns (uint); + function cdpi() external view returns (uint); } interface VatLike { diff --git a/test/b.protocol/b.maker.test.js b/test/b.protocol/b.maker.test.js new file mode 100644 index 00000000..3eff7d76 --- /dev/null +++ b/test/b.protocol/b.maker.test.js @@ -0,0 +1,198 @@ +const { expect } = require("chai"); +const hre = require("hardhat"); +const { web3, deployments, waffle, ethers } = hre; +const { provider, deployContract } = waffle + +const deployAndEnableConnector = require("../../scripts/deployAndEnableConnector.js") +const buildDSAv2 = require("../../scripts/buildDSAv2") +const encodeSpells = require("../../scripts/encodeSpells.js") +const getMasterSigner = require("../../scripts/getMasterSigner") + +const addresses = require("../../scripts/constant/addresses"); +const abis = require("../../scripts/constant/abis"); +const constants = require("../../scripts/constant/constant"); +const tokens = require("../../scripts/constant/tokens"); + +const connectorMakerArtifacts = require("../../artifacts/contracts/mainnet/connectors/b.protocol/makerdao/main.sol/ConnectV1BMakerDAO.json") + +describe("B.Maker", function () { + const connectorName = "B.MAKER-TEST-A" + + let dsaWallet0 + let masterSigner; + let instaConnectorsV2; + let connector; + let managerWeb3Contract; + let vatWeb3Contract; + let daiWeb3Contract; + + const wallets = provider.getWallets() + const [wallet0, wallet1, wallet2, wallet3] = wallets + before(async () => { + masterSigner = await getMasterSigner(wallet3) + instaConnectorsV2 = await ethers.getContractAt(abis.core.connectorsV2, addresses.core.connectorsV2); + connector = await deployAndEnableConnector({ + connectorName, + contractArtifact: connectorMakerArtifacts, + signer: masterSigner, + connectors: instaConnectorsV2 + }) + + const cdpManagerArtifact = await hre.artifacts.readArtifact("BManagerLike"); + const vatArtifact = await hre.artifacts.readArtifact("../artifacts/contracts/mainnet/connectors/b.protocol/makerdao/interface.sol:VatLike"); + + managerWeb3Contract = new web3.eth.Contract(cdpManagerArtifact.abi, "0x3f30c2381CD8B917Dd96EB2f1A4F96D91324BBed") + vatWeb3Contract = new web3.eth.Contract(vatArtifact.abi, await managerWeb3Contract.methods.vat().call()) + daiWeb3Contract = new web3.eth.Contract(abis.basic.erc20, tokens.dai.address) + + console.log("Connector address", connector.address) + }) + + it("Should have contracts deployed.", async function () { + expect(!!instaConnectorsV2.address).to.be.true; + expect(!!connector.address).to.be.true; + expect(!!masterSigner.address).to.be.true; + expect(await connector.name()).to.be.equal("B.MakerDAO-v1.0"); + }); + + describe("DSA wallet setup", function () { + it("Should build DSA v2", async function () { + dsaWallet0 = await buildDSAv2(wallet0.address) + expect(!!dsaWallet0.address).to.be.true; + }); + + it("Deposit ETH into DSA wallet", async function () { + await wallet0.sendTransaction({ + to: dsaWallet0.address, + value: ethers.utils.parseEther("10") + }); + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte(ethers.utils.parseEther("10")); + }); + }); + + describe("Main", function () { + let vault + let ilk + let urn + + it("Should open ETH-A vault Maker", async function () { + vault = Number(await managerWeb3Contract.methods.cdpi().call()) + 1 + const spells = [ + { + connector: connectorName, + method: "open", + args: ["ETH-A"] + } + ] + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address) + const receipt = await tx.wait() + + expect(await managerWeb3Contract.methods.owns(vault).call()).to.be.equal(dsaWallet0.address) + + ilk = await managerWeb3Contract.methods.ilks(vault).call() + expect(ilk).to.be.equal("0x4554482d41000000000000000000000000000000000000000000000000000000") + + urn = await managerWeb3Contract.methods.urns(vault).call() + }); + + it("Should deposit", async function () { + const amount = ethers.utils.parseEther("7") // 7 ETH + const setId = "83478237" + + const spells = [ + { + connector: connectorName, + method: "deposit", + args: [vault, amount, 0, setId] + } + ] + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address) + const receipt = await tx.wait() + + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte(ethers.utils.parseEther("3")) + + const urnData = await vatWeb3Contract.methods.urns(ilk, urn).call() + expect(urnData[0]).to.be.equal(amount) // ink + expect(urnData[1]).to.be.equal("0") // art + + }); + + it("Should withdraw", async function () { + const amount = ethers.utils.parseEther("1") // 1 ETH + const setId = "83478237" + + const spells = [ + { + connector: connectorName, + method: "withdraw", + args: [vault, amount, 0, setId] + } + ] + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address) + const receipt = await tx.wait() + + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte(ethers.utils.parseEther("4")) + + const urnData = await vatWeb3Contract.methods.urns(ilk, urn).call() + expect(urnData[0]).to.be.equal(ethers.utils.parseEther("6")) // ink + expect(urnData[1]).to.be.equal("0") // art + + }); + + it("Should borrow", async function () { + const amount = ethers.utils.parseEther("6000") // 6000 dai + const setId = "83478237" + + const spells = [ + { + connector: connectorName, + method: "borrow", + args: [vault, amount, 0, setId] + } + ] + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address) + const receipt = await tx.wait() + + const urnData = await vatWeb3Contract.methods.urns(ilk, urn).call() + expect(urnData[0]).to.be.equal(ethers.utils.parseEther("6")) // ink + expect(urnData[1]).to.be.equal(await daiToArt(vatWeb3Contract, ilk, amount)) // art + + expect(await daiWeb3Contract.methods.balanceOf(dsaWallet0.address).call()).to.be.equal(amount) + }); + + it("Should repay", async function () { + const amount = ethers.utils.parseEther("500") // 500 dai + const setId = "83478237" + + const spells = [ + { + connector: connectorName, + method: "payback", + args: [vault, amount, 0, setId] + } + ] + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address) + const receipt = await tx.wait() + + const urnData = await vatWeb3Contract.methods.urns(ilk, urn).call() + expect(urnData[0]).to.be.equal(ethers.utils.parseEther("6")) // ink + expect(urnData[1]).to.be.equal(await daiToArt(vatWeb3Contract, ilk, ethers.utils.parseEther("5500"))) // art + expect(await daiWeb3Contract.methods.balanceOf(dsaWallet0.address).call()).to.be.equal(ethers.utils.parseEther("5500")) + }); + }) +}) + +async function daiToArt(vatWeb3Contract, ilk, dai) { + const ilks = await vatWeb3Contract.methods.ilks(ilk).call() + const rate = ilks[1] // second parameter + const _1e27 = ethers.utils.parseEther("1000000000") // 1e9 * 1e18 + const art = dai.mul(_1e27).div(rate) + + return art.add(1) +} + From 6fa171df235ad083a29f51a37f9fc5729d464b17 Mon Sep 17 00:00:00 2001 From: yaron velner Date: Tue, 6 Jul 2021 09:17:12 +0300 Subject: [PATCH 04/11] deposit and borrow test --- test/b.protocol/b.maker.test.js | 45 +++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/test/b.protocol/b.maker.test.js b/test/b.protocol/b.maker.test.js index 3eff7d76..b14822bd 100644 --- a/test/b.protocol/b.maker.test.js +++ b/test/b.protocol/b.maker.test.js @@ -48,6 +48,13 @@ describe("B.Maker", function () { console.log("Connector address", connector.address) }) + it("test veryClose.", async function () { + expect(veryClose(1000001, 1000000)).to.be.true + expect(veryClose(1000000, 1000001)).to.be.true + expect(veryClose(1003000, 1000001)).to.be.false + expect(veryClose(1000001, 1000300)).to.be.false + }); + it("Should have contracts deployed.", async function () { expect(!!instaConnectorsV2.address).to.be.true; expect(!!connector.address).to.be.true; @@ -183,6 +190,32 @@ describe("B.Maker", function () { expect(urnData[0]).to.be.equal(ethers.utils.parseEther("6")) // ink expect(urnData[1]).to.be.equal(await daiToArt(vatWeb3Contract, ilk, ethers.utils.parseEther("5500"))) // art expect(await daiWeb3Contract.methods.balanceOf(dsaWallet0.address).call()).to.be.equal(ethers.utils.parseEther("5500")) + }); + + it("Should depositAndBorrow", async function () { + const borrowAmount = ethers.utils.parseEther("1000") // 500 dai + const depositAmount = ethers.utils.parseEther("1") // 1 dai + + const setId = "83478237" + + const spells = [ + { + connector: connectorName, + method: "depositAndBorrow", + args: [vault, depositAmount, borrowAmount, 0, 0, 0, 0] + } + ] + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address) + const receipt = await tx.wait() + + const urnData = await vatWeb3Contract.methods.urns(ilk, urn).call() + expect(urnData[0]).to.be.equal(ethers.utils.parseEther("7")) // ink + expect(await daiWeb3Contract.methods.balanceOf(dsaWallet0.address).call()).to.be.equal(ethers.utils.parseEther("6500")) + // calculation is not precise as the jug was dripped + expect(veryClose(urnData[1], await daiToArt(vatWeb3Contract, ilk, ethers.utils.parseEther("6500")))).to.be.true + //expect(urnData[1]).to.be.equal(await daiToArt(vatWeb3Contract, ilk, ethers.utils.parseEther("6500"))) // art + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte(ethers.utils.parseEther("1")) }); }) }) @@ -196,3 +229,15 @@ async function daiToArt(vatWeb3Contract, ilk, dai) { return art.add(1) } +function veryClose(n1, n2) { + n1 = web3.utils.toBN(n1) + n2 = web3.utils.toBN(n2) + + _10000 = web3.utils.toBN(10000) + _9999 = web3.utils.toBN(9999) + + if(n1.mul(_10000).lt(n2.mul(_9999))) return false + if(n2.mul(_10000).lt(n1.mul(_9999))) return false + + return true +} From 2fac6a94ff0d4a6e4940488f7bea9c16712db3c6 Mon Sep 17 00:00:00 2001 From: yaron velner Date: Tue, 6 Jul 2021 11:26:32 +0300 Subject: [PATCH 05/11] close test --- test/b.protocol/b.maker.test.js | 89 ++++++++++++++++++++++++++++++++- 1 file changed, 88 insertions(+), 1 deletion(-) diff --git a/test/b.protocol/b.maker.test.js b/test/b.protocol/b.maker.test.js index b14822bd..0ed96cb4 100644 --- a/test/b.protocol/b.maker.test.js +++ b/test/b.protocol/b.maker.test.js @@ -19,6 +19,7 @@ describe("B.Maker", function () { const connectorName = "B.MAKER-TEST-A" let dsaWallet0 + let dsaWallet1 let masterSigner; let instaConnectorsV2; let connector; @@ -66,6 +67,9 @@ describe("B.Maker", function () { it("Should build DSA v2", async function () { dsaWallet0 = await buildDSAv2(wallet0.address) expect(!!dsaWallet0.address).to.be.true; + + dsaWallet1 = await buildDSAv2(wallet1.address) + expect(!!dsaWallet1.address).to.be.true; }); it("Deposit ETH into DSA wallet", async function () { @@ -74,6 +78,12 @@ describe("B.Maker", function () { value: ethers.utils.parseEther("10") }); expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte(ethers.utils.parseEther("10")); + + await wallet1.sendTransaction({ + to: dsaWallet1.address, + value: ethers.utils.parseEther("10") + }); + expect(await ethers.provider.getBalance(dsaWallet1.address)).to.be.gte(ethers.utils.parseEther("10")); }); }); @@ -193,7 +203,7 @@ describe("B.Maker", function () { }); it("Should depositAndBorrow", async function () { - const borrowAmount = ethers.utils.parseEther("1000") // 500 dai + const borrowAmount = ethers.utils.parseEther("1000") // 1000 dai const depositAmount = ethers.utils.parseEther("1") // 1 dai const setId = "83478237" @@ -216,6 +226,83 @@ describe("B.Maker", function () { expect(veryClose(urnData[1], await daiToArt(vatWeb3Contract, ilk, ethers.utils.parseEther("6500")))).to.be.true //expect(urnData[1]).to.be.equal(await daiToArt(vatWeb3Contract, ilk, ethers.utils.parseEther("6500"))) // art expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte(ethers.utils.parseEther("1")) + }); + + it("Should close", async function () { + // open a new vault + const newVault = vault + 1 + let spells = [ + { + connector: connectorName, + method: "open", + args: ["ETH-A"] + } + ] + + let tx = await dsaWallet1.connect(wallet1).cast(...encodeSpells(spells), wallet1.address) + let receipt = await tx.wait() + + expect(await managerWeb3Contract.methods.owns(newVault).call()).to.be.equal(dsaWallet1.address) + + ilk = await managerWeb3Contract.methods.ilks(newVault).call() + expect(ilk).to.be.equal("0x4554482d41000000000000000000000000000000000000000000000000000000") + + urn = await managerWeb3Contract.methods.urns(newVault).call() + + // deposit and borrow + const borrowAmount = ethers.utils.parseEther("6000") // 6000 dai + const depositAmount = ethers.utils.parseEther("5") // 5 ETH + + spells = [ + { + connector: connectorName, + method: "depositAndBorrow", + args: [newVault, depositAmount, borrowAmount, 0, 0, 0, 0] + } + ] + + tx = await dsaWallet1.connect(wallet1).cast(...encodeSpells(spells), wallet1.address) + receipt = await tx.wait() + + const setId = 0 + + // repay borrow + spells = [ + { + connector: connectorName, + method: "payback", + args: [newVault, borrowAmount, 0, setId] + } + ] + + tx = await dsaWallet1.connect(wallet1).cast(...encodeSpells(spells), wallet1.address) + receipt = await tx.wait() + + // withdraw deposit + spells = [ + { + connector: connectorName, + method: "withdraw", + args: [newVault, depositAmount, 0, setId] + } + ] + + tx = await dsaWallet1.connect(wallet1).cast(...encodeSpells(spells), wallet1.address) + receipt = await tx.wait() + + // close + spells = [ + { + connector: connectorName, + method: "close", + args: [newVault] + } + ] + + tx = await dsaWallet1.connect(wallet1).cast(...encodeSpells(spells), wallet1.address) + receipt = await tx.wait() + + expect(await managerWeb3Contract.methods.owns(newVault).call()).not.to.be.equal(dsaWallet1.address) }); }) }) From 8a70ccddff81dd3d6bb959aad9c5d40460614843 Mon Sep 17 00:00:00 2001 From: yaron velner Date: Tue, 6 Jul 2021 11:42:25 +0300 Subject: [PATCH 06/11] hardhat contracts instead of web3 contracts in test --- test/b.protocol/b.maker.test.js | 62 +++++++++++++++------------------ 1 file changed, 29 insertions(+), 33 deletions(-) diff --git a/test/b.protocol/b.maker.test.js b/test/b.protocol/b.maker.test.js index 0ed96cb4..57b687cd 100644 --- a/test/b.protocol/b.maker.test.js +++ b/test/b.protocol/b.maker.test.js @@ -18,14 +18,14 @@ const connectorMakerArtifacts = require("../../artifacts/contracts/mainnet/conne describe("B.Maker", function () { const connectorName = "B.MAKER-TEST-A" - let dsaWallet0 - let dsaWallet1 + let dsaWallet0; + let dsaWallet1; let masterSigner; let instaConnectorsV2; let connector; - let managerWeb3Contract; - let vatWeb3Contract; - let daiWeb3Contract; + let manager; + let vat; + let dai; const wallets = provider.getWallets() const [wallet0, wallet1, wallet2, wallet3] = wallets @@ -39,12 +39,9 @@ describe("B.Maker", function () { connectors: instaConnectorsV2 }) - const cdpManagerArtifact = await hre.artifacts.readArtifact("BManagerLike"); - const vatArtifact = await hre.artifacts.readArtifact("../artifacts/contracts/mainnet/connectors/b.protocol/makerdao/interface.sol:VatLike"); - - managerWeb3Contract = new web3.eth.Contract(cdpManagerArtifact.abi, "0x3f30c2381CD8B917Dd96EB2f1A4F96D91324BBed") - vatWeb3Contract = new web3.eth.Contract(vatArtifact.abi, await managerWeb3Contract.methods.vat().call()) - daiWeb3Contract = new web3.eth.Contract(abis.basic.erc20, tokens.dai.address) + manager = await ethers.getContractAt("BManagerLike", "0x3f30c2381CD8B917Dd96EB2f1A4F96D91324BBed") + vat = await ethers.getContractAt("../artifacts/contracts/mainnet/connectors/b.protocol/makerdao/interface.sol:VatLike", await manager.vat()) + dai = await ethers.getContractAt("../artifacts/contracts/mainnet/common/interfaces.sol:TokenInterface", tokens.dai.address) console.log("Connector address", connector.address) }) @@ -93,7 +90,7 @@ describe("B.Maker", function () { let urn it("Should open ETH-A vault Maker", async function () { - vault = Number(await managerWeb3Contract.methods.cdpi().call()) + 1 + vault = Number(await manager.cdpi()) + 1 const spells = [ { connector: connectorName, @@ -105,12 +102,12 @@ describe("B.Maker", function () { const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address) const receipt = await tx.wait() - expect(await managerWeb3Contract.methods.owns(vault).call()).to.be.equal(dsaWallet0.address) + expect(await manager.owns(vault)).to.be.equal(dsaWallet0.address) - ilk = await managerWeb3Contract.methods.ilks(vault).call() + ilk = await manager.ilks(vault) expect(ilk).to.be.equal("0x4554482d41000000000000000000000000000000000000000000000000000000") - urn = await managerWeb3Contract.methods.urns(vault).call() + urn = await manager.urns(vault) }); it("Should deposit", async function () { @@ -130,7 +127,7 @@ describe("B.Maker", function () { expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte(ethers.utils.parseEther("3")) - const urnData = await vatWeb3Contract.methods.urns(ilk, urn).call() + const urnData = await vat.urns(ilk, urn) expect(urnData[0]).to.be.equal(amount) // ink expect(urnData[1]).to.be.equal("0") // art @@ -153,7 +150,7 @@ describe("B.Maker", function () { expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte(ethers.utils.parseEther("4")) - const urnData = await vatWeb3Contract.methods.urns(ilk, urn).call() + const urnData = await vat.urns(ilk, urn) expect(urnData[0]).to.be.equal(ethers.utils.parseEther("6")) // ink expect(urnData[1]).to.be.equal("0") // art @@ -174,11 +171,11 @@ describe("B.Maker", function () { const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address) const receipt = await tx.wait() - const urnData = await vatWeb3Contract.methods.urns(ilk, urn).call() + const urnData = await vat.urns(ilk, urn) expect(urnData[0]).to.be.equal(ethers.utils.parseEther("6")) // ink - expect(urnData[1]).to.be.equal(await daiToArt(vatWeb3Contract, ilk, amount)) // art + expect(urnData[1]).to.be.equal(await daiToArt(vat, ilk, amount)) // art - expect(await daiWeb3Contract.methods.balanceOf(dsaWallet0.address).call()).to.be.equal(amount) + expect(await dai.balanceOf(dsaWallet0.address)).to.be.equal(amount) }); it("Should repay", async function () { @@ -196,10 +193,10 @@ describe("B.Maker", function () { const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address) const receipt = await tx.wait() - const urnData = await vatWeb3Contract.methods.urns(ilk, urn).call() + const urnData = await vat.urns(ilk, urn) expect(urnData[0]).to.be.equal(ethers.utils.parseEther("6")) // ink - expect(urnData[1]).to.be.equal(await daiToArt(vatWeb3Contract, ilk, ethers.utils.parseEther("5500"))) // art - expect(await daiWeb3Contract.methods.balanceOf(dsaWallet0.address).call()).to.be.equal(ethers.utils.parseEther("5500")) + expect(urnData[1]).to.be.equal(await daiToArt(vat, ilk, ethers.utils.parseEther("5500"))) // art + expect(await dai.balanceOf(dsaWallet0.address)).to.be.equal(ethers.utils.parseEther("5500")) }); it("Should depositAndBorrow", async function () { @@ -219,12 +216,11 @@ describe("B.Maker", function () { const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address) const receipt = await tx.wait() - const urnData = await vatWeb3Contract.methods.urns(ilk, urn).call() + const urnData = await vat.urns(ilk, urn) expect(urnData[0]).to.be.equal(ethers.utils.parseEther("7")) // ink - expect(await daiWeb3Contract.methods.balanceOf(dsaWallet0.address).call()).to.be.equal(ethers.utils.parseEther("6500")) + expect(await dai.balanceOf(dsaWallet0.address)).to.be.equal(ethers.utils.parseEther("6500")) // calculation is not precise as the jug was dripped - expect(veryClose(urnData[1], await daiToArt(vatWeb3Contract, ilk, ethers.utils.parseEther("6500")))).to.be.true - //expect(urnData[1]).to.be.equal(await daiToArt(vatWeb3Contract, ilk, ethers.utils.parseEther("6500"))) // art + expect(veryClose(urnData[1], await daiToArt(vat, ilk, ethers.utils.parseEther("6500")))).to.be.true expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte(ethers.utils.parseEther("1")) }); @@ -242,12 +238,12 @@ describe("B.Maker", function () { let tx = await dsaWallet1.connect(wallet1).cast(...encodeSpells(spells), wallet1.address) let receipt = await tx.wait() - expect(await managerWeb3Contract.methods.owns(newVault).call()).to.be.equal(dsaWallet1.address) + expect(await manager.owns(newVault)).to.be.equal(dsaWallet1.address) - ilk = await managerWeb3Contract.methods.ilks(newVault).call() + ilk = await manager.ilks(newVault) expect(ilk).to.be.equal("0x4554482d41000000000000000000000000000000000000000000000000000000") - urn = await managerWeb3Contract.methods.urns(newVault).call() + urn = await manager.urns(newVault) // deposit and borrow const borrowAmount = ethers.utils.parseEther("6000") // 6000 dai @@ -302,13 +298,13 @@ describe("B.Maker", function () { tx = await dsaWallet1.connect(wallet1).cast(...encodeSpells(spells), wallet1.address) receipt = await tx.wait() - expect(await managerWeb3Contract.methods.owns(newVault).call()).not.to.be.equal(dsaWallet1.address) + expect(await manager.owns(newVault)).not.to.be.equal(dsaWallet1.address) }); }) }) -async function daiToArt(vatWeb3Contract, ilk, dai) { - const ilks = await vatWeb3Contract.methods.ilks(ilk).call() +async function daiToArt(vat, ilk, dai) { + const ilks = await vat.ilks(ilk) const rate = ilks[1] // second parameter const _1e27 = ethers.utils.parseEther("1000000000") // 1e9 * 1e18 const art = dai.mul(_1e27).div(rate) From 6f356a3a46b3a3646beebfb61f3cc93591078164 Mon Sep 17 00:00:00 2001 From: yaron velner Date: Wed, 4 Aug 2021 00:44:18 +0300 Subject: [PATCH 07/11] b.protocol: deposit to cdp manager and not to vat directly --- .../mainnet/connectors/b.protocol/makerdao/main.sol | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/contracts/mainnet/connectors/b.protocol/makerdao/main.sol b/contracts/mainnet/connectors/b.protocol/makerdao/main.sol index 2f94c1e4..a839fb3a 100644 --- a/contracts/mainnet/connectors/b.protocol/makerdao/main.sol +++ b/contracts/mainnet/connectors/b.protocol/makerdao/main.sol @@ -97,13 +97,10 @@ abstract contract BMakerResolver is Helpers, Events { } approve(tokenContract, address(colAddr), _amt); - tokenJoinContract.join(address(this), _amt); + tokenJoinContract.join(urn, _amt); - VatLike(managerContract.vat()).frob( // TODO zelda - make manager.frob - ilk, - urn, - address(this), - address(this), + managerContract.frob( + _vault, toInt(convertTo18(tokenJoinContract.dec(), _amt)), 0 ); From ed791eba4766eade746252f2ba9e6c1acee32159 Mon Sep 17 00:00:00 2001 From: yaron velner Date: Mon, 23 Aug 2021 13:06:49 +0300 Subject: [PATCH 08/11] b.liquity --- .../connectors/b.protocol/liquity/events.sol | 22 +++ .../connectors/b.protocol/liquity/helpers.sol | 18 ++ .../b.protocol/liquity/interface.sol | 17 ++ .../connectors/b.protocol/liquity/main.sol | 86 +++++++++ test/b.protocol/b.liquity.test.js | 181 ++++++++++++++++++ 5 files changed, 324 insertions(+) create mode 100644 contracts/mainnet/connectors/b.protocol/liquity/events.sol create mode 100644 contracts/mainnet/connectors/b.protocol/liquity/helpers.sol create mode 100644 contracts/mainnet/connectors/b.protocol/liquity/interface.sol create mode 100644 contracts/mainnet/connectors/b.protocol/liquity/main.sol create mode 100644 test/b.protocol/b.liquity.test.js diff --git a/contracts/mainnet/connectors/b.protocol/liquity/events.sol b/contracts/mainnet/connectors/b.protocol/liquity/events.sol new file mode 100644 index 00000000..0590b125 --- /dev/null +++ b/contracts/mainnet/connectors/b.protocol/liquity/events.sol @@ -0,0 +1,22 @@ +pragma solidity ^0.7.6; + +contract Events { + + /* Stability Pool */ + event LogStabilityDeposit( + address indexed borrower, + uint amount, + uint lqtyGain, + uint getDepositId, + uint setDepositId, + uint setLqtyGainId + ); + event LogStabilityWithdraw( + address indexed borrower, + uint numShares, + uint lqtyGain, + uint getWithdrawId, + uint setWithdrawId, + uint setLqtyGainId + ); +} \ No newline at end of file diff --git a/contracts/mainnet/connectors/b.protocol/liquity/helpers.sol b/contracts/mainnet/connectors/b.protocol/liquity/helpers.sol new file mode 100644 index 00000000..f9463de4 --- /dev/null +++ b/contracts/mainnet/connectors/b.protocol/liquity/helpers.sol @@ -0,0 +1,18 @@ +pragma solidity ^0.7.6; + +import { DSMath } from "../../../common/math.sol"; +import { Basic } from "../../../common/basic.sol"; + +import { TokenInterface } from "../../../common/interfaces.sol"; + +import { + StabilityPoolLike, + BAMMLike +} from "./interface.sol"; + +abstract contract Helpers is DSMath, Basic { + StabilityPoolLike internal constant stabilityPool = StabilityPoolLike(0x66017D22b0f8556afDd19FC67041899Eb65a21bb); + TokenInterface internal constant lqtyToken = TokenInterface(0x6DEA81C8171D0bA574754EF6F8b412F2Ed88c54D); + TokenInterface internal constant lusdToken = TokenInterface(0x5f98805A4E8be255a32880FDeC7F6728C6568bA0); + BAMMLike internal constant BAMM = BAMMLike(0x0d3AbAA7E088C2c82f54B2f47613DA438ea8C598); +} \ No newline at end of file diff --git a/contracts/mainnet/connectors/b.protocol/liquity/interface.sol b/contracts/mainnet/connectors/b.protocol/liquity/interface.sol new file mode 100644 index 00000000..dd2ad2fd --- /dev/null +++ b/contracts/mainnet/connectors/b.protocol/liquity/interface.sol @@ -0,0 +1,17 @@ +pragma solidity ^0.7.6; + +interface StabilityPoolLike { + function provideToSP(uint _amount, address _frontEndTag) external; + function withdrawFromSP(uint _amount) external; + function withdrawETHGainToTrove(address _upperHint, address _lowerHint) external; + function getDepositorETHGain(address _depositor) external view returns (uint); + function getDepositorLQTYGain(address _depositor) external view returns (uint); + function getCompoundedLUSDDeposit(address _depositor) external view returns (uint); +} + +interface BAMMLike { + function deposit(uint lusdAmount) external; + function withdraw(uint numShares) external; + function balanceOf(address a) external view returns(uint); + function totalSupply() external view returns(uint); +} \ No newline at end of file diff --git a/contracts/mainnet/connectors/b.protocol/liquity/main.sol b/contracts/mainnet/connectors/b.protocol/liquity/main.sol new file mode 100644 index 00000000..3d19afef --- /dev/null +++ b/contracts/mainnet/connectors/b.protocol/liquity/main.sol @@ -0,0 +1,86 @@ +pragma solidity ^0.7.6; + +/** + * @title B.Liquity. + * @dev Lending & Borrowing. + */ +import { + StabilityPoolLike, + BAMMLike +} from "./interface.sol"; +import { Stores } from "../../../common/stores.sol"; +import { Helpers } from "./helpers.sol"; +import { Events } from "./events.sol"; + +abstract contract BLiquityResolver is Events, Helpers { + /* Begin: Stability Pool */ + + /** + * @dev Deposit LUSD into Stability Pool + * @notice Deposit LUSD into Stability Pool + * @param amount Amount of LUSD to deposit into Stability Pool + * @param getDepositId Optional storage slot to retrieve the amount of LUSD from + * @param setDepositId Optional storage slot to store the final amount of LUSD deposited + * @param setLqtyGainId Optional storage slot to store any LQTY gains in + */ + function deposit( + uint amount, + uint getDepositId, + uint setDepositId, + uint setLqtyGainId + ) external payable returns (string memory _eventName, bytes memory _eventParam) { + amount = getUint(getDepositId, amount); + + amount = amount == uint(-1) ? lusdToken.balanceOf(address(this)) : amount; + + uint lqtyBalanceBefore = lqtyToken.balanceOf(address(this)); + + lusdToken.approve(address(BAMM), amount); + BAMM.deposit(amount); + + uint lqtyBalanceAfter = lqtyToken.balanceOf(address(this)); + uint lqtyGain = sub(lqtyBalanceAfter, lqtyBalanceBefore); + + setUint(setDepositId, amount); + setUint(setLqtyGainId, lqtyGain); + + _eventName = "LogStabilityDeposit(address,uint256,uint256,uint256,uint256,uint256)"; + _eventParam = abi.encode(address(this), amount,lqtyGain, getDepositId, setDepositId, setLqtyGainId); + } + + /** + * @dev Withdraw user deposited LUSD from Stability Pool + * @notice Withdraw LUSD from Stability Pool + * @param numShares amount of shares to withdraw from the BAMM + * @param getWithdrawId Optional storage slot to retrieve the amount of LUSD to withdraw from + * @param setWithdrawId Optional storage slot to store the withdrawn LUSD + * @param setLqtyGainId Optional storage slot to store any LQTY gains in + */ + function withdraw( + uint numShares, + uint getWithdrawId, + uint setWithdrawId, + uint setLqtyGainId + ) external payable returns (string memory _eventName, bytes memory _eventParam) { + numShares = getUint(getWithdrawId, numShares); + + numShares = numShares == uint(-1) ? BAMM.balanceOf(address(this)) : numShares; + + uint lqtyBalanceBefore = lqtyToken.balanceOf(address(this)); + + BAMM.withdraw(numShares); + + uint lqtyBalanceAfter = lqtyToken.balanceOf(address(this)); + uint lqtyGain = sub(lqtyBalanceAfter, lqtyBalanceBefore); + + setUint(setWithdrawId, numShares); + setUint(setLqtyGainId, lqtyGain); + + _eventName = "LogStabilityWithdraw(address,uint256,uint256,uint256,uint256,uint256)"; + _eventParam = abi.encode(address(this), numShares, lqtyGain, getWithdrawId, setWithdrawId, setLqtyGainId); + } +} + +contract ConnectV2BLiquity is BLiquityResolver { + string public name = "B.Liquity-v1"; +} \ No newline at end of file diff --git a/test/b.protocol/b.liquity.test.js b/test/b.protocol/b.liquity.test.js new file mode 100644 index 00000000..8a180345 --- /dev/null +++ b/test/b.protocol/b.liquity.test.js @@ -0,0 +1,181 @@ +const { expect } = require("chai"); +const hre = require("hardhat"); +const { web3, deployments, waffle, ethers } = hre; +const { provider, deployContract } = waffle + +const deployAndEnableConnector = require("../../scripts/deployAndEnableConnector.js") +const buildDSAv2 = require("../../scripts/buildDSAv2") +const encodeSpells = require("../../scripts/encodeSpells.js") +const getMasterSigner = require("../../scripts/getMasterSigner") + +const addresses = require("../../scripts/constant/addresses"); +const abis = require("../../scripts/constant/abis"); +const constants = require("../../scripts/constant/constant"); +const tokens = require("../../scripts/constant/tokens"); + +const connectorLiquityArtifacts = require("../../artifacts/contracts/mainnet/connectors/b.protocol/liquity/main.sol/ConnectV2BLiquity.json") + +const LUSD_WHALE = "0x66017D22b0f8556afDd19FC67041899Eb65a21bb" // stability pool + +const BAMM_ADDRESS = "0x0d3AbAA7E088C2c82f54B2f47613DA438ea8C598" + +describe("B.Liquity", function () { + const connectorName = "B.LIQUITY-TEST-A" + + let dsaWallet0; + let dsaWallet1; + let masterSigner; + let instaConnectorsV2; + let connector; + let manager; + let vat; + let lusd; + let bammToken; + let stabilityPool; + + const wallets = provider.getWallets() + const [wallet0, wallet1, wallet2, wallet3] = wallets + before(async () => { + masterSigner = await getMasterSigner(wallet3) + instaConnectorsV2 = await ethers.getContractAt(abis.core.connectorsV2, addresses.core.connectorsV2); + connector = await deployAndEnableConnector({ + connectorName, + contractArtifact: connectorLiquityArtifacts, + signer: masterSigner, + connectors: instaConnectorsV2 + }) + + lusd = await ethers.getContractAt("../artifacts/contracts/mainnet/common/interfaces.sol:TokenInterface", "0x5f98805A4E8be255a32880FDeC7F6728C6568bA0") + bammToken = await ethers.getContractAt("../artifacts/contracts/mainnet/connectors/b.protocol/liquity/interface.sol:BAMMLike", BAMM_ADDRESS) + stabilityPool = await ethers.getContractAt("../artifacts/contracts/mainnet/connectors/b.protocol/liquity/interface.sol:StabilityPoolLike", "0x66017D22b0f8556afDd19FC67041899Eb65a21bb") + + console.log("Connector address", connector.address) + }) + + it("test veryClose.", async function () { + expect(veryClose(1000001, 1000000)).to.be.true + expect(veryClose(1000000, 1000001)).to.be.true + expect(veryClose(1003000, 1000001)).to.be.false + expect(veryClose(1000001, 1000300)).to.be.false + }); + + it("Should have contracts deployed.", async function () { + expect(!!instaConnectorsV2.address).to.be.true; + expect(!!connector.address).to.be.true; + expect(!!masterSigner.address).to.be.true; + expect(await connector.name()).to.be.equal("B.Liquity-v1"); + }); + + describe("DSA wallet setup", function () { + it("Should build DSA v2", async function () { + dsaWallet0 = await buildDSAv2(wallet0.address) + expect(!!dsaWallet0.address).to.be.true; + + dsaWallet1 = await buildDSAv2(wallet1.address) + expect(!!dsaWallet1.address).to.be.true; + }); + + it("Deposit LUSD into DSA wallet", async function () { + await hre.network.provider.request({ + method: "hardhat_impersonateAccount", + params: [LUSD_WHALE], + }); + + const signer = await hre.ethers.provider.getSigner(LUSD_WHALE); + await lusd.connect(signer).transfer(dsaWallet0.address, ethers.utils.parseEther("100000")) + + expect(await lusd.balanceOf(dsaWallet0.address)).to.equal(ethers.utils.parseEther("100000")); + }); + }); + + describe("Main", function () { + it("should deposit 10k LUSD", async function () { + const totalSupplyBefore = await bammToken.totalSupply(); + const lusdBalanceBefore = await stabilityPool.getCompoundedLUSDDeposit(BAMM_ADDRESS); + const amount = ethers.utils.parseEther("10000"); + const spells = [ + { + connector: connectorName, + method: "deposit", + args: [amount, 0, 0, 0] + } + ] + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address) + const receipt = await tx.wait() + + const expectedBalance = totalSupplyBefore.mul(amount).div(lusdBalanceBefore) + expect(veryClose(expectedBalance, await bammToken.balanceOf(dsaWallet0.address))).to.be.true + }); + + it("should deposit all LUSD", async function () { + const totalSupplyBefore = await bammToken.totalSupply(); + const lusdBalanceBefore = await stabilityPool.getCompoundedLUSDDeposit(BAMM_ADDRESS); + const amount = web3.utils.toBN("2").pow(web3.utils.toBN("256")).sub(web3.utils.toBN("1")); + const balanceBefore = await bammToken.balanceOf(dsaWallet0.address) + + const spells = [ + { + connector: connectorName, + method: "deposit", + args: [amount, 0, 0, 0] + } + ] + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address) + const receipt = await tx.wait() + + const expectedBalance = (totalSupplyBefore.mul(ethers.utils.parseEther("90000")).div(lusdBalanceBefore)).add(balanceBefore) + expect(veryClose(expectedBalance, await bammToken.balanceOf(dsaWallet0.address))).to.be.true + }); + + it("should withdraw half of the shares", async function () { + const balanceBefore = await bammToken.balanceOf(dsaWallet0.address) + const halfBalance = balanceBefore.div("2") + + const spells = [ + { + connector: connectorName, + method: "withdraw", + args: [halfBalance, 0, 0, 0] + } + ] + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address) + const receipt = await tx.wait() + + expect(veryClose(halfBalance, await bammToken.balanceOf(dsaWallet0.address))).to.be.true + expect(veryClose(ethers.utils.parseEther("50000"), await lusd.balanceOf(dsaWallet0.address))).to.be.true + }); + + it("should withdraw all the shares", async function () { + const amount = web3.utils.toBN("2").pow(web3.utils.toBN("256")).sub(web3.utils.toBN("1")); + + const spells = [ + { + connector: connectorName, + method: "withdraw", + args: [amount, 0, 0, 0] + } + ] + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.address) + const receipt = await tx.wait() + + expect(veryClose(ethers.utils.parseEther("100000"), await lusd.balanceOf(dsaWallet0.address))).to.be.true + }); + }) +}) + +function veryClose(n1, n2) { + n1 = web3.utils.toBN(n1) + n2 = web3.utils.toBN(n2) + + _10000 = web3.utils.toBN(10000) + _9999 = web3.utils.toBN(9999) + + if(n1.mul(_10000).lt(n2.mul(_9999))) return false + if(n2.mul(_10000).lt(n1.mul(_9999))) return false + + return true +} From b0744e2797684ac0b1a8e44cc6c32ba05c1dbc18 Mon Sep 17 00:00:00 2001 From: Thrilok kumar Date: Thu, 26 Aug 2021 03:00:30 +0530 Subject: [PATCH 09/11] Updated Compound Mapping address --- contracts/mainnet/connectors/b.protocol/compound/helpers.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/mainnet/connectors/b.protocol/compound/helpers.sol b/contracts/mainnet/connectors/b.protocol/compound/helpers.sol index 74e149b1..9cd8ca30 100644 --- a/contracts/mainnet/connectors/b.protocol/compound/helpers.sol +++ b/contracts/mainnet/connectors/b.protocol/compound/helpers.sol @@ -13,7 +13,7 @@ abstract contract Helpers is DSMath, Basic { /** * @dev Compound Mapping */ - CompoundMappingInterface internal constant compMapping = CompoundMappingInterface(0xA8F9D4aA7319C54C04404765117ddBf9448E2082); + CompoundMappingInterface internal constant compMapping = CompoundMappingInterface(0xe7a85d0adDB972A4f0A4e57B698B37f171519e88); /** * @dev B.Compound Mapping From a9897fe3721f39b83644d82806e08d22cbd4f3e4 Mon Sep 17 00:00:00 2001 From: Thrilok kumar Date: Thu, 26 Aug 2021 03:00:41 +0530 Subject: [PATCH 10/11] Minor change --- contracts/mainnet/connectors/b.protocol/compound/main.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/mainnet/connectors/b.protocol/compound/main.sol b/contracts/mainnet/connectors/b.protocol/compound/main.sol index ecc062ad..cfef8513 100644 --- a/contracts/mainnet/connectors/b.protocol/compound/main.sol +++ b/contracts/mainnet/connectors/b.protocol/compound/main.sol @@ -342,6 +342,6 @@ abstract contract BCompoundResolver is Events, Helpers { } } -contract ConnectV1BCompound is BCompoundResolver { +contract ConnectV2BCompound is BCompoundResolver { string public name = "B.Compound-v1.0"; } From fa0a22e49c6a708f48c4a3cd04d3fea4a83c1196 Mon Sep 17 00:00:00 2001 From: Thrilok kumar Date: Thu, 26 Aug 2021 03:00:48 +0530 Subject: [PATCH 11/11] Minor change --- contracts/mainnet/connectors/b.protocol/makerdao/main.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/mainnet/connectors/b.protocol/makerdao/main.sol b/contracts/mainnet/connectors/b.protocol/makerdao/main.sol index a839fb3a..bc4e2deb 100644 --- a/contracts/mainnet/connectors/b.protocol/makerdao/main.sol +++ b/contracts/mainnet/connectors/b.protocol/makerdao/main.sol @@ -516,6 +516,6 @@ abstract contract BMakerResolver is Helpers, Events { } } -contract ConnectV1BMakerDAO is BMakerResolver { +contract ConnectV2BMakerDAO is BMakerResolver { string public constant name = "B.MakerDAO-v1.0"; }