Merge pull request #249 from Instadapp/Euler-import-connector

Euler import connector
This commit is contained in:
Thrilok kumar 2022-08-28 00:25:15 +05:30 committed by GitHub
commit 5ede687dff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 606 additions and 0 deletions

View File

@ -0,0 +1,16 @@
//SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
contract Events {
event LogEulerImport(
address user,
uint256 sourceId,
uint256 targetId,
address[] supplyTokens,
uint256[] supplyAmounts,
address[] borrowTokens,
uint256[] borrowAmounts,
bool[] enterMarket
);
}

View File

@ -0,0 +1,111 @@
//SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
import { TokenInterface, AccountInterface } from "../../../common/interfaces.sol";
import { Basic } from "../../../common/basic.sol";
import "./interface.sol";
contract EulerHelpers is Basic {
/**
* @dev Euler's Market Module
*/
IEulerMarkets internal constant markets =
IEulerMarkets(0x3520d5a913427E6F0D6A83E07ccD4A4da316e4d3);
/**
* @dev Euler's Execution Module
*/
IEulerExecute internal constant eulerExec =
IEulerExecute(0x59828FdF7ee634AaaD3f58B19fDBa3b03E2D9d80);
/**
* @dev Compute sub account address.
* @notice Compute sub account address from sub-account id
* @param primary primary address
* @param subAccountId sub-account id whose address needs to be computed
*/
function getSubAccountAddress(address primary, uint256 subAccountId)
public
pure
returns (address)
{
require(subAccountId < 256, "sub-account-id-too-big");
return address(uint160(primary) ^ uint160(subAccountId));
}
struct ImportInputData {
address[] _supplyTokens;
address[] _borrowTokens;
bool[] _enterMarket;
}
struct ImportData {
address[] supplyTokens;
address[] borrowTokens;
EulerTokenInterface[] eTokens;
EulerTokenInterface[] dTokens;
uint256[] supplyAmts;
uint256[] borrowAmts;
}
struct ImportHelper {
uint256 supplylength;
uint256 borrowlength;
uint256 totalExecutions;
address sourceAccount;
address targetAccount;
}
function getSupplyAmounts(
address userAccount, // user's EOA sub-account address
ImportInputData memory inputData,
ImportData memory data
) internal view returns (ImportData memory) {
data.supplyAmts = new uint256[](inputData._supplyTokens.length);
data.supplyTokens = new address[](inputData._supplyTokens.length);
data.eTokens = new EulerTokenInterface[](
inputData._supplyTokens.length
);
uint256 length_ = inputData._supplyTokens.length;
for (uint256 i = 0; i < length_; i++) {
address token_ = inputData._supplyTokens[i] == ethAddr
? wethAddr
: inputData._supplyTokens[i];
data.supplyTokens[i] = token_;
data.eTokens[i] = EulerTokenInterface(
markets.underlyingToEToken(token_)
);
data.supplyAmts[i] = data.eTokens[i].balanceOf(userAccount); //All 18 dec
}
return data;
}
function getBorrowAmounts(
address userAccount, // user's EOA sub-account address
ImportInputData memory inputData,
ImportData memory data
) internal view returns (ImportData memory) {
uint256 borrowTokensLength_ = inputData._borrowTokens.length;
if (borrowTokensLength_ > 0) {
data.borrowTokens = new address[](borrowTokensLength_);
data.dTokens = new EulerTokenInterface[](borrowTokensLength_);
data.borrowAmts = new uint256[](borrowTokensLength_);
for (uint256 i = 0; i < borrowTokensLength_; i++) {
address _token = inputData._borrowTokens[i] == ethAddr
? wethAddr
: inputData._borrowTokens[i];
data.borrowTokens[i] = _token;
data.dTokens[i] = EulerTokenInterface(
markets.underlyingToDToken(_token)
);
data.borrowAmts[i] = data.dTokens[i].balanceOf(userAccount);
}
}
return data;
}
}

View File

@ -0,0 +1,56 @@
//SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
interface EulerTokenInterface {
function balanceOf(address _user) external view returns (uint256);
function transferFrom(
address,
address,
uint256
) external returns (bool);
function allowance(address, address) external returns (uint256);
}
interface IEulerMarkets {
function enterMarket(uint256 subAccountId, address newMarket) external;
function getEnteredMarkets(address account)
external
view
returns (address[] memory);
function exitMarket(uint256 subAccountId, address oldMarket) external;
function underlyingToEToken(address underlying)
external
view
returns (address);
function underlyingToDToken(address underlying)
external
view
returns (address);
}
interface IEulerExecute {
struct EulerBatchItem {
bool allowError;
address proxyAddr;
bytes data;
}
struct EulerBatchItemResponse {
bool success;
bytes result;
}
function batchDispatch(
EulerBatchItem[] calldata items,
address[] calldata deferLiquidityChecks
) external;
function deferLiquidityCheck(address account, bytes memory data) external;
}

View File

@ -0,0 +1,155 @@
//SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
import "./helpers.sol";
import "./interface.sol";
import "./events.sol";
contract EulerImport is EulerHelpers {
/**
* @dev Import Euler position .
* @notice Import EOA's Euler position to DSA's Euler position
* @param userAccount EOA address
* @param sourceId Sub-account id of "EOA" from which the funds will be transferred
* @param targetId Sub-account id of "DSA" to which the funds will be transferred
* @param inputData The struct containing all the neccessary input data
*/
function importEuler(
address userAccount,
uint256 sourceId,
uint256 targetId,
ImportInputData memory inputData
)
external
payable
returns (string memory _eventName, bytes memory _eventParam)
{
require(sourceId < 256 && targetId < 256, "Id should be less than 256");
(_eventName, _eventParam) = _importEuler(
userAccount,
sourceId,
targetId,
inputData
);
}
/**
* @dev Import Euler position .
* @notice Import EOA's Euler position to DSA's Euler position
* @param userAccount EOA address
* @param sourceId Sub-account id of "EOA" from which the funds will be transferred
* @param targetId Sub-account id of "DSA" to which the funds will be transferred
* @param inputData The struct containing all the neccessary input data
*/
function _importEuler(
address userAccount,
uint256 sourceId,
uint256 targetId,
ImportInputData memory inputData
) internal returns (string memory _eventName, bytes memory _eventParam) {
require(inputData._supplyTokens.length > 0, "0-length-not-allowed");
require(
AccountInterface(address(this)).isAuth(userAccount),
"user-account-not-auth"
);
require(
inputData._enterMarket.length == inputData._supplyTokens.length,
"lengths-not-same"
);
ImportData memory data;
ImportHelper memory helper;
helper.sourceAccount = getSubAccountAddress(userAccount, sourceId);
helper.targetAccount = getSubAccountAddress(address(this), targetId);
// BorrowAmts will be in underlying token decimals
data = getBorrowAmounts(helper.sourceAccount, inputData, data);
// SupplyAmts will be in 18 decimals
data = getSupplyAmounts(helper.sourceAccount, inputData, data);
helper.supplylength = data.supplyTokens.length;
helper.borrowlength = data.borrowTokens.length;
uint16 enterMarketsLength = 0;
for (uint16 i = 0; i < inputData._enterMarket.length; i++) {
if (inputData._enterMarket[i]) {
++enterMarketsLength;
}
}
helper.totalExecutions =
helper.supplylength +
enterMarketsLength +
helper.borrowlength;
IEulerExecute.EulerBatchItem[]
memory items = new IEulerExecute.EulerBatchItem[](
helper.totalExecutions
);
uint16 k = 0;
for (uint16 i = 0; i < helper.supplylength; i++) {
items[k++] = IEulerExecute.EulerBatchItem({
allowError: false,
proxyAddr: address(data.eTokens[i]),
data: abi.encodeWithSignature(
"transferFrom(address,address,uint256)",
helper.sourceAccount,
helper.targetAccount,
data.supplyAmts[i]
)
});
if (inputData._enterMarket[i]) {
items[k++] = IEulerExecute.EulerBatchItem({
allowError: false,
proxyAddr: address(markets),
data: abi.encodeWithSignature(
"enterMarket(uint256,address)",
targetId,
data.supplyTokens[i]
)
});
}
}
for (uint16 j = 0; j < helper.borrowlength; j++) {
items[k++] = IEulerExecute.EulerBatchItem({
allowError: false,
proxyAddr: address(data.dTokens[j]),
data: abi.encodeWithSignature(
"transferFrom(address,address,uint256)",
helper.sourceAccount,
helper.targetAccount,
data.borrowAmts[j]
)
});
}
address[] memory deferLiquidityChecks = new address[](2);
deferLiquidityChecks[0] = helper.sourceAccount;
deferLiquidityChecks[1] = helper.targetAccount;
eulerExec.batchDispatch(items, deferLiquidityChecks);
_eventName = "LogEulerImport(address,uint256,uint256,address[],uint256[],address[],uint256[],bool[])";
_eventParam = abi.encode(
userAccount,
sourceId,
targetId,
inputData._supplyTokens,
data.supplyAmts,
inputData._borrowTokens,
data.borrowAmts,
inputData._enterMarket
);
}
}
contract ConnectV2EulerImport is EulerImport {
string public constant name = "Euler-Import-v1.0";
}

View File

@ -0,0 +1,268 @@
import { expect } from "chai";
import hre from "hardhat";
import { abis } from "../../../scripts/constant/abis";
import { addresses } from "../../../scripts/tests/mainnet/addresses";
import { deployAndEnableConnector } from "../../../scripts/tests/deployAndEnableConnector";
import { getMasterSigner } from "../../../scripts/tests/getMasterSigner";
import { buildDSAv2 } from "../../../scripts/tests/buildDSAv2";
import { ConnectV2EulerImport__factory, IERC20__factory } from "../../../typechain";
import { parseEther, parseUnits } from "@ethersproject/units";
import { encodeSpells } from "../../../scripts/tests/encodeSpells";
const { ethers } = hre;
import type { Signer, Contract } from "ethers";
import { BigNumber } from "bignumber.js";
import { Address } from "@project-serum/anchor";
const DAI = '0x6b175474e89094c44da98b954eedeac495271d0f'
const Dai = parseUnits('50', 18)
const WETH = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'
const ACC_WETH = '0x05547D4e1A2191B91510Ea7fA8555a2788C70030'
const Weth = parseUnits('50', 18)
const ETH = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'
const token_weth = new ethers.Contract(
WETH,
[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"guy","type":"address"},{"name":"wad","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"src","type":"address"},{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"wad","type":"uint256"}],"name":"withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"deposit","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":true,"name":"guy","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":true,"name":"dst","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"dst","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Withdrawal","type":"event"}],
// IERC20__factory.abi,
ethers.provider,
)
const token_dai = new ethers.Contract(
DAI,
IERC20__factory.abi,
ethers.provider,
)
const eTokensABI = [
"function approve(address, uint256) public",
"function balanceOf(address account) public view returns (uint256)",
"function allowance(address, address) public returns (uint256)",
"function deposit(uint256,uint256) public",
"function balanceOfUnderlying(address) public view returns (uint256)",
"function mint(uint256,uint256) public",
"function approveSubAccount(uint256, address, uint256) public"
];
const dTokensABI = [
"function balanceOf(address account) public view returns (uint256)",
"function borrow(uint256,uint256) public"
];
const marketsABI = [
"function enterMarket(uint256,address) public",
"function underlyingToEToken(address) public view returns (address)",
"function underlyingToDToken(address) public view returns (address)"
]
const eWethAddress = '0x1b808F49ADD4b8C6b5117d9681cF7312Fcf0dC1D';
const eWethContract = new ethers.Contract(eWethAddress, eTokensABI);
const dWethAddress = '0x62e28f054efc24b26A794F5C1249B6349454352C'
const dWethContract = new ethers.Contract(dWethAddress, dTokensABI);
const dDaiAddress = '0x6085Bc95F506c326DCBCD7A6dd6c79FBc18d4686';
const dDaiContract = new ethers.Contract(dDaiAddress, dTokensABI);
const euler_mainnet = '0x27182842E098f60e3D576794A5bFFb0777E025d3'
const euler_markets = '0x3520d5a913427E6F0D6A83E07ccD4A4da316e4d3'
const marketsContract = new ethers.Contract(euler_markets, marketsABI);
describe("Euler", function () {
const connectorName = "EULER-IMPORT-TEST-A";
let connector: any;
let wallet0: Signer, wallet1:Signer;
let dsaWallet0: any;
let instaConnectorsV2: Contract;
let masterSigner: Signer;
let walletAddr: Address;
let subAcc1: Address;
let subAcc2DSA: Address;
before(async () => {
await hre.network.provider.request({
method: "hardhat_reset",
params: [
{
forking: {
// @ts-ignore
jsonRpcUrl: hre.config.networks.hardhat.forking.url,
blockNumber: 15379000,
},
},
],
});
[wallet0, wallet1] = await ethers.getSigners();
await hre.network.provider.send("hardhat_setBalance", [ACC_WETH, ethers.utils.parseEther("10").toHexString()]);
await hre.network.provider.request({
method: "hardhat_impersonateAccount",
params: [ACC_WETH]
});
const signer_weth = await ethers.getSigner(ACC_WETH)
await token_weth.connect(signer_weth).transfer(wallet0.getAddress(), ethers.utils.parseEther("8"));
console.log("WETH transferred to wallet0");
await hre.network.provider.request({
method: 'hardhat_stopImpersonatingAccount',
params: [ACC_WETH],
})
masterSigner = await getMasterSigner();
instaConnectorsV2 = await ethers.getContractAt(
abis.core.connectorsV2,
addresses.core.connectorsV2
);
connector = await deployAndEnableConnector({
connectorName,
contractArtifact: ConnectV2EulerImport__factory,
signer: masterSigner,
connectors: instaConnectorsV2,
});
console.log("Connector address", connector.address);
walletAddr = (await wallet0.getAddress()).toString()
console.log("walletAddr: ", walletAddr)
subAcc1 = ethers.BigNumber.from(walletAddr).xor(1).toHexString()
console.log("subAcc1: ", subAcc1)
});
it("should have contracts deployed", async () => {
expect(!!instaConnectorsV2.address).to.be.true;
expect(!!connector.address).to.be.true;
expect(!!(await masterSigner.getAddress())).to.be.true;
});
describe("DSA wallet setup", function () {
it("Should build DSA v2", async function () {
dsaWallet0 = await buildDSAv2(wallet0.getAddress());
expect(!!dsaWallet0.address).to.be.true;
subAcc2DSA = ethers.BigNumber.from(dsaWallet0.address).xor(2).toHexString()
console.log("subAcc2DSA: ", subAcc2DSA)
});
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("Create Euler position in SUBACCOUNT 0", async () => {
it("Should create Euler position of WETH(collateral) and DAI(debt)", async () => {
// approve WETH to euler
await token_weth.connect(wallet0).approve(euler_mainnet, Weth);
console.log("Approved WETH");
// deposit WETH in euler
await eWethContract.connect(wallet0).deposit("0", parseEther("2"));
expect(await eWethContract.connect(wallet0).balanceOfUnderlying(walletAddr)).to.be.gte(parseEther("1.9"));
console.log("Supplied WETH on Euler");
// enter WETH market
await marketsContract.connect(wallet0).enterMarket("0", WETH);
console.log("Entered market for WETH");
// borrow DAI from Euler
await dDaiContract.connect(wallet0).borrow("0", Dai);
console.log("Borrowed DAI from Euler");
});
it("Should check created position of user", async () => {
expect(await token_dai.connect(wallet0).balanceOf(walletAddr)).to.be.gte(
parseUnits('50', 18)
);
});
});
describe("Create Euler self-position in SUBACCOUNT 1", async () => {
it("Should create Euler self-position of WETH(collateral) and WETH(debt)", async () => {
// approve WETH to euler
await token_weth.connect(wallet0).approve(euler_mainnet, Weth);
console.log("Approved WETH");
// deposit WETH in euler
await eWethContract.connect(wallet0).deposit("1", parseEther("2"));
expect(await eWethContract.connect(wallet0).balanceOfUnderlying(subAcc1)).to.be.gte(parseEther("1.9"));
console.log("Supplied WETH on Euler");
// enter WETH market
await marketsContract.connect(wallet0).enterMarket("1", WETH);
console.log("Entered market for WETH");
// mint WETH from Euler
await eWethContract.connect(wallet0).mint("1", parseEther("1"));
expect(await eWethContract.connect(wallet0).balanceOfUnderlying(subAcc1)).to.be.gte(parseEther("2.9"));
console.log("Minted WETH from Euler");
});
it("Should check created position of user", async () => {
expect(await eWethContract.connect(wallet0).balanceOfUnderlying(subAcc1)).to.be.gte(parseEther("2.9"));
});
});
describe("Euler position migration", async () => {
it("Approve sub-account0 eTokens for import to DSA sub-account 0", async () => {
let balance = await eWethContract.connect(wallet0).balanceOf(walletAddr)
await eWethContract.connect(wallet0).approve(dsaWallet0.address, balance);
});
it("Approve sub-account1 eTokens for import to DSA sub-account 2", async () => {
let balance = await eWethContract.connect(wallet0).balanceOf(subAcc1)
await eWethContract.connect(wallet0).approveSubAccount("1", dsaWallet0.address, balance);
});
it("Should migrate euler position of sub-account 0 to DSA sub-account 0", async () => {
const spells = [
{
connector: "EULER-IMPORT-TEST-A",
method: "importEuler",
args: [
walletAddr,
"0",
"0",
[[ETH],[DAI],["true"]]
]
},
];
const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet0.getAddress());
const receipt = await tx.wait();
});
it("Should check migration", async () => {
expect(await eWethContract.connect(wallet0).balanceOfUnderlying(dsaWallet0.address)).to.be.gte(parseEther("2"));
expect(await dDaiContract.connect(wallet0).balanceOf(dsaWallet0.address)).to.be.gte(parseEther("50"));
});
it("Should migrate euler position of sub-account 1 to DSA sub-account 2", async () => {
const spells = [
{
connector: "EULER-IMPORT-TEST-A",
method: "importEuler",
args: [
walletAddr,
"1",
"2",
[[ETH],[ETH],["true"]]
]
},
];
const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet0.getAddress());
const receipt = await tx.wait();
});
it("Should check migration", async () => {
expect(await eWethContract.connect(wallet0).balanceOfUnderlying(subAcc2DSA)).to.be.gte(parseEther("3"));
expect(await dWethContract.connect(wallet0).balanceOf(subAcc2DSA)).to.be.gte(parseEther("1"));
});
})
});
})