From c4421a60f8a5e75999c2cd17aca936b436becde8 Mon Sep 17 00:00:00 2001 From: Mark Aiken Date: Mon, 18 Oct 2021 19:28:36 -0700 Subject: [PATCH] Functional matic deposit, borrow, payback, and withdraw --- contracts/polygon/connectors/qidao/events.sol | 11 + .../polygon/connectors/qidao/helpers.sol | 8 + .../polygon/connectors/qidao/interface.sol | 21 ++ contracts/polygon/connectors/qidao/main.sol | 202 ++++++++++++++++ scripts/constant/qidao/polygonTokens.js | 9 + scripts/constant/qidao/vaults.js | 5 + test/qidao/qidao.test.js | 221 ++++++++++++++++++ 7 files changed, 477 insertions(+) create mode 100644 contracts/polygon/connectors/qidao/events.sol create mode 100644 contracts/polygon/connectors/qidao/helpers.sol create mode 100644 contracts/polygon/connectors/qidao/interface.sol create mode 100644 contracts/polygon/connectors/qidao/main.sol create mode 100644 scripts/constant/qidao/polygonTokens.js create mode 100644 scripts/constant/qidao/vaults.js create mode 100644 test/qidao/qidao.test.js diff --git a/contracts/polygon/connectors/qidao/events.sol b/contracts/polygon/connectors/qidao/events.sol new file mode 100644 index 00000000..f490ff3e --- /dev/null +++ b/contracts/polygon/connectors/qidao/events.sol @@ -0,0 +1,11 @@ +pragma solidity ^0.7.0; + +contract Events { + + event LogCreateVault(uint256 vaultId, address sender); + event LogDestroyVault(uint256 vaultId, address sender); + event LogDepositCollateral(uint256 vaultID, uint256 amount, uint256 getVaultId, uint256 setVaultId, uint256 getAmtId, uint256 setAmtId); + event LogWithdrawCollateral(uint256 vaultID, uint256 amount, uint256 getVaultId, uint256 setVaultId, uint256 getAmtId, uint256 setAmtId); + event LogBorrow(uint256 vaultID, uint256 amount, uint256 getVaultId, uint256 setVaultId, uint256 getAmtId, uint256 setAmtId); + event LogPayBack(uint256 vaultID, uint256 amount, uint256 getVaultId, uint256 setVaultId, uint256 getAmtId, uint256 setAmtId); +} diff --git a/contracts/polygon/connectors/qidao/helpers.sol b/contracts/polygon/connectors/qidao/helpers.sol new file mode 100644 index 00000000..7f5b6a8a --- /dev/null +++ b/contracts/polygon/connectors/qidao/helpers.sol @@ -0,0 +1,8 @@ +pragma solidity ^0.7.0; + +import { DSMath } from "../../common/math.sol"; +import { Basic } from "../../common/basic.sol"; + +abstract contract Helpers is DSMath, Basic { + +} diff --git a/contracts/polygon/connectors/qidao/interface.sol b/contracts/polygon/connectors/qidao/interface.sol new file mode 100644 index 00000000..9d02ae95 --- /dev/null +++ b/contracts/polygon/connectors/qidao/interface.sol @@ -0,0 +1,21 @@ +pragma solidity ^0.7.0; + +interface erc20StablecoinInterface { + + function createVault() external returns (uint256); + function destroyVault(uint256 vaultID) external; + function depositCollateral(uint256 vaultID, uint256 amount) external; + function withdrawCollateral(uint256 vaultID, uint256 amount) external; + function borrowToken(uint256 vaultID, uint256 amount) external; + function payBackToken(uint256 vaultID, uint256 amount) external; + function transferVault(uint256 vaultID, address to) external; + function vaultOwner(uint256 vaultID) external returns (address); +} + +interface maticStablecoinInterface is erc20StablecoinInterface { + function depositCollateral(uint256 vaultID) external payable; +} + +interface camTokenInterface { + function balanceOf(address _user) external view returns(uint256); +} diff --git a/contracts/polygon/connectors/qidao/main.sol b/contracts/polygon/connectors/qidao/main.sol new file mode 100644 index 00000000..47bb5774 --- /dev/null +++ b/contracts/polygon/connectors/qidao/main.sol @@ -0,0 +1,202 @@ +pragma solidity ^0.7.0; + + +/** + * @title QiDAo. + * @dev Lending & Borrowing. + * TODO Update doc Strings + */ + +import "hardhat/console.sol"; + +import { TokenInterface } from "../../common/interfaces.sol"; +import { Stores } from "../../common/stores.sol"; +import { Helpers } from "./helpers.sol"; +import { Events } from "./events.sol"; +import { erc20StablecoinInterface, maticStablecoinInterface } from "./interface.sol"; + +abstract contract QiDaoResolver is Events, Helpers { + + address constant internal MAI = 0xa3Fa99A148fA48D14Ed51d610c367C61876997F1; + + function createVault(address vaultAddress, uint256 setId) external payable returns (string memory _eventName, bytes memory _eventParam) { + erc20StablecoinInterface vault = erc20StablecoinInterface(vaultAddress); + uint256 vaultId = vault.createVault(); + + setUint(setId, vaultId); + + _eventName = "LogCreateVault(uint256, address)"; + _eventParam = abi.encode(vaultId, address(this)); + } + + function destroyVault(address vaultAddress, uint256 vaultId, uint256 getId) external payable returns (string memory _eventName, bytes memory _eventParam) { + erc20StablecoinInterface vault = erc20StablecoinInterface(vaultAddress); + uint256 _vaultId = getUint(getId, vaultId); + vault.destroyVault(_vaultId); + + _eventName = "LogDestroyVault(uint256, address)"; + _eventParam = abi.encode(_vaultId, address(this)); + } + + + /** + * @dev Deposit ETH/ERC20_Token. + * @notice Deposit a token to Aave v2 for lending / collaterization. + * @param token The address of the token to deposit.(For MATIC: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param amt The amount of the token to deposit. (For max: `uint256(-1)`) + */ + function deposit( + address token, + address vaultAddress, + uint256 vaultId, + uint256 amt, + uint256 getVaultId, + uint256 setVaultId, + uint256 getAmtId, + uint256 setAmtId +) external payable returns (string memory _eventName, bytes memory _eventParam) { + + uint _amt = getUint(getAmtId, amt); + uint _vaultId = getUint(getVaultId, vaultId); + + bool isEth = token == maticAddr; + + if(isEth){ + maticStablecoinInterface vault = maticStablecoinInterface(vaultAddress); + vault.depositCollateral{value: _amt}(_vaultId); + } + else { + erc20StablecoinInterface vault = erc20StablecoinInterface(vaultAddress); + TokenInterface tokenContract = TokenInterface(token); + approve(tokenContract, address(vault), _amt); + vault.depositCollateral(_vaultId, _amt); + } + + setUint(setAmtId, _amt); + setUint(getVaultId, _vaultId); + + _eventName = "LogDepositCollateral(uint256, uint256, uint256, uint256, uint256, uint256)"; + _eventParam = abi.encode(_vaultId, _amt, getVaultId, setVaultId, getAmtId, setAmtId); + } + + /** + * @dev Withdraw ETH/ERC20_Token. + * @notice Withdraw deposited token from Aave v2 + * @param token The address of the token to withdraw.(For MATIC: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param amt The amount of the token to withdraw. (For max: `uint256(-1)`) + * @param getAmtId ID to retrieve amt. + * @param setAmtId ID stores the amount of tokens withdrawn. + */ + function withdraw( + address token, + address vaultAddress, + uint256 vaultId, + uint256 amt, + uint256 getVaultId, + uint256 setVaultId, + uint256 getAmtId, + uint256 setAmtId + ) external payable returns (string memory _eventName, bytes memory _eventParam) { + uint _amt = getUint(getAmtId, amt); + uint _vaultId = getUint(getVaultId, vaultId); + + bool isEth = token == maticAddr; + + uint initialBal; + uint finalBal; + + if(isEth){ + initialBal = address(this).balance; + maticStablecoinInterface vault = maticStablecoinInterface(vaultAddress); + + vault.withdrawCollateral(_vaultId, _amt); + finalBal = address(this).balance; + } + else { + TokenInterface tokenContract = TokenInterface(token); + erc20StablecoinInterface vault = erc20StablecoinInterface(vaultAddress); + + initialBal = tokenContract.balanceOf(address(this)); + + approve(tokenContract, address(vault), _amt); + vault.withdrawCollateral(_vaultId, _amt); + finalBal = tokenContract.balanceOf(address(this)); + } + + + _amt = sub(finalBal, initialBal); + + setUint(setAmtId, _amt); + + _eventName = "LogWithdrawCollateral(uint256, uint256, uint256, uint256, uint256, uint256)"; + _eventParam = abi.encode(_vaultId, _amt, getVaultId, setVaultId, getAmtId, setAmtId); + } + + /** + * @dev Borrow ETH/ERC20_Token. + * @notice Borrow a token using Aave v2 + * @param amt The amount of the token to borrow. + * @param getAmtId ID to retrieve amt. + * @param setAmtId ID stores the amount of tokens borrowed. + */ + function borrow( + address vaultAddress, + uint256 vaultId, + uint256 amt, + uint256 getVaultId, + uint256 setVaultId, + uint256 getAmtId, + uint256 setAmtId + ) external payable returns (string memory _eventName, bytes memory _eventParam) { + uint _amt = getUint(getAmtId, amt); + uint _vaultId = getUint(getVaultId, vaultId); + + erc20StablecoinInterface vault = erc20StablecoinInterface(vaultAddress); + vault.borrowToken(_vaultId, _amt); + vault.transferVault(_vaultId, address(this)); + + setUint(setAmtId, _amt); + setUint(getVaultId, _vaultId); + + _eventName = "LogBorrow(uint256, uint256, uint256, uint256, uint256, uint256);"; + _eventParam = abi.encode(_vaultId, _amt, getVaultId, setVaultId, getAmtId, setAmtId); + } + + /** + * @dev Payback borrowed ETH/ERC20_Token. + * @notice Payback debt owed. + * @param amt The amount of the token to payback. (For max: `uint256(-1)`) + * @param getAmtId ID to retrieve amt. + * @param setAmtId ID stores the amount of tokens paid back. + */ + function payback( + address vaultAddress, + uint256 vaultId, + uint256 amt, + uint256 getVaultId, + uint256 setVaultId, + uint256 getAmtId, + uint256 setAmtId + ) external payable returns (string memory _eventName, bytes memory _eventParam) { + uint _amt = getUint(getAmtId, amt); + uint _vaultId = getUint(getVaultId, vaultId); + + erc20StablecoinInterface vault = erc20StablecoinInterface(vaultAddress); + + TokenInterface tokenContract = TokenInterface(MAI); + + approve(tokenContract, address(vault), _amt); + + vault.payBackToken(_vaultId, _amt); + + setUint(setAmtId, _amt); + setUint(getVaultId, _vaultId); + + _eventName ="LogPayBack(uint256, uint256, uint256, uint256, uint256, uint256)"; + _eventParam = abi.encode(_vaultId, _amt, getVaultId, setVaultId, getAmtId, setAmtId); + } +} + +contract ConnectV2QiDaoPolygon is QiDaoResolver{ + string constant public name = "QiDao-v1"; +} diff --git a/scripts/constant/qidao/polygonTokens.js b/scripts/constant/qidao/polygonTokens.js new file mode 100644 index 00000000..998f21ba --- /dev/null +++ b/scripts/constant/qidao/polygonTokens.js @@ -0,0 +1,9 @@ +module.exports = { + "matic": { + "type": "token", + "symbol": "MATIC", + "name": "Matic", + "address": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", + "decimals": 18 + }, +} diff --git a/scripts/constant/qidao/vaults.js b/scripts/constant/qidao/vaults.js new file mode 100644 index 00000000..c4e7992f --- /dev/null +++ b/scripts/constant/qidao/vaults.js @@ -0,0 +1,5 @@ +module.exports = { + matic: { + address: "0xa3fa99a148fa48d14ed51d610c367c61876997f1" + } +} diff --git a/test/qidao/qidao.test.js b/test/qidao/qidao.test.js new file mode 100644 index 00000000..d7047161 --- /dev/null +++ b/test/qidao/qidao.test.js @@ -0,0 +1,221 @@ +const { expect } = require("chai"); +const hre = require("hardhat"); +const abis = require("../../scripts/constant/abis"); +const addresses = require("../../scripts/constant/addresses"); +const deployAndEnableConnector = require("../../scripts/deployAndEnableConnector"); +const getMasterSigner = require("../../scripts/getMasterSigner"); +const buildDSAv2 = require("../../scripts/buildDSAv2"); +const ConnectV2QiDaoPolygon = require("../../artifacts/contracts/polygon/connectors/qidao/main.sol/ConnectV2QiDaoPolygon.json"); +const { parseEther } = require("@ethersproject/units"); +const encodeSpells = require("../../scripts/encodeSpells"); +const polygonTokens = require("../../scripts/constant/qidao/polygonTokens"); +const vaults = require("../../scripts/constant/qidao/vaults"); +const constants = require("../../scripts/constant/constant"); +const addLiquidity = require("../../scripts/addLiquidity"); +const { ethers } = hre; + +describe("QiDao", function() { + const connectorName = "QIDAO-TEST-A"; + + let wallet0, wallet1; + let dsaWallet0; + let instaConnectorsV2; + let connector; + let masterSigner; + + before(async () => { + await hre.network.provider.request({ + method: "hardhat_reset", + params: [ + { + forking: { + jsonRpcUrl: hre.config.networks.hardhat.forking.url, + }, + }, + ], + }); + [wallet0, wallet1] = await ethers.getSigners(); + masterSigner = await getMasterSigner(); + instaConnectorsV2 = await ethers.getContractAt( + abis.core.connectorsV2, + addresses.core.connectorsV2 + ); + connector = await deployAndEnableConnector({ + connectorName, + contractArtifact: ConnectV2QiDaoPolygon, + signer: masterSigner, + connectors: instaConnectorsV2, + }); + }); + + it("should have contracts deployed", async () => { + expect(!!instaConnectorsV2.address).to.be.true; + expect(!!connector.address).to.be.true; + expect(!!masterSigner.address).to.be.true; + }); + + 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: parseEther("10"), + }); + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte( + parseEther("10") + ); + }); + }); + + describe("Main", function() { + it("should create a MATIC vault in QiDao and deposit MATIC into that vault", async function() { + const amt = parseEther("5"); + const brwAmt = parseEther("1"); + const setVaultId = "13571113"; + const spells = [ + { + connector: connectorName, + method: "createVault", + args: [vaults.matic.address, setVaultId], + }, + { + connector: connectorName, + method: "deposit", + args: [polygonTokens.matic.address, vaults.matic.address, 0, amt, setVaultId, 0, 0, 0], + }, + { + connector: connectorName, + method: "borrow", + args: [vaults.matic.address, 0, brwAmt, setVaultId, 0, 0 , 0] + }, + { + connector: connectorName, + method: "payback", + args: [vaults.matic.address, 0, brwAmt, setVaultId, 0, 0 , 0], + }, + { + connector: connectorName, + method: "withdraw", + args: [polygonTokens.matic.address, vaults.matic.address, 0, amt.mul(995).div(1000), setVaultId, 0, 0, 0], + }, + ]; + + const tx = await dsaWallet0 + .connect(wallet0) + .cast(...encodeSpells(spells), wallet1.address); + + await tx.wait(); + + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.eq( + parseEther("9.975") + ); + }); + + // it("Should borrow and payback half DAI from Aave V2", async function() { + // const amt = parseEther("100"); // 100 DAI + // // const setId = "83478237"; + // await addLiquidity("dai", dsaWallet0.address, parseEther("1")); + // let spells = [ + // { + // connector: connectorName, + // method: "borrow", + // args: [polygonTokens.dai.address, amt, 2, 0, 0], + // }, + // { + // connector: connectorName, + // method: "payback", + // args: [polygonTokens.dai.address, amt.div(2), 2, 0, 0], + // }, + // ]; + // + // let tx = await dsaWallet0 + // .connect(wallet0) + // .cast(...encodeSpells(spells), wallet1.address); + // await tx.wait(); + // expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.lte( + // ethers.utils.parseEther("9") + // ); + // + // spells = [ + // { + // connector: connectorName, + // method: "payback", + // args: [polygonTokens.dai.address, constants.max_value, 2, 0, 0], + // }, + // ]; + // + // tx = await dsaWallet0 + // .connect(wallet0) + // .cast(...encodeSpells(spells), wallet1.address); + // await tx.wait(); + // expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.lte( + // ethers.utils.parseEther("9") + // ); + // }); + // + // it("Should deposit all ETH in Aave V2", async function() { + // const spells = [ + // { + // connector: connectorName, + // method: "deposit", + // args: [polygonTokens.matic.address, constants.max_value, 0, 0], + // }, + // ]; + // + // const tx = await dsaWallet0 + // .connect(wallet0) + // .cast(...encodeSpells(spells), wallet1.address); + // await tx.wait(); + // expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.lte( + // ethers.utils.parseEther("0") + // ); + // }); + // + // it("Should withdraw all ETH from Aave V2", async function() { + // const spells = [ + // { + // connector: connectorName, + // method: "withdraw", + // args: [polygonTokens.eth.address, constants.max_value, 0, 0], + // }, + // ]; + // + // const tx = await dsaWallet0 + // .connect(wallet0) + // .cast(...encodeSpells(spells), wallet1.address); + // await tx.wait(); + // expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte( + // ethers.utils.parseEther("10") + // ); + // }); + // + // it("should deposit and withdraw", async () => { + // const amt = parseEther("1"); // 1 eth + // const setId = "834782373"; + // const spells = [ + // { + // connector: connectorName, + // method: "deposit", + // args: [polygonTokens.eth.address, amt, 0, setId], + // }, + // { + // connector: connectorName, + // method: "withdraw", + // args: [polygonTokens.eth.address, amt, setId, 0], + // }, + // ]; + // + // const tx = await dsaWallet0 + // .connect(wallet0) + // .cast(...encodeSpells(spells), wallet1.address); + // await tx.wait(); + // expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte( + // ethers.utils.parseEther("10") + // ); + // }); + }); +});