b.compound

This commit is contained in:
yaron velner 2021-07-05 21:58:51 +03:00
parent a14f4c6e02
commit e892d52c51
5 changed files with 603 additions and 0 deletions

View File

@ -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
);
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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";
}

View File

@ -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"));
});
})
})