Merge pull request #1 from edmulraney/liquity-connector

Instadapp integration: add Liquity connector
This commit is contained in:
Edward Mulraney 2021-06-21 09:16:00 +01:00 committed by GitHub
commit 03a1f64398
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 3726 additions and 32 deletions

View File

@ -0,0 +1,58 @@
pragma solidity ^0.7.6;
contract Events {
/* Trove */
event LogOpen(
address indexed borrower,
uint maxFeePercentage,
uint depositAmount,
uint borrowAmount,
uint getId,
uint setId
);
event LogClose(address indexed borrower, uint setId);
event LogDeposit(address indexed borrower, uint amount, uint getId);
event LogWithdraw(address indexed borrower, uint amount, uint setId);
event LogBorrow(address indexed borrower, uint amount, uint setId);
event LogRepay(address indexed borrower, uint amount, uint getId);
event LogAdjust(
address indexed borrower,
uint maxFeePercentage,
uint depositAmount,
uint withdrawAmount,
uint borrowAmount,
uint repayAmount,
uint getDepositId,
uint setWithdrawId,
uint getRepayId,
uint setBorrowId
);
event LogClaimCollateralFromRedemption(address indexed borrower, uint amount, uint setId);
/* Stability Pool */
event LogStabilityDeposit(
address indexed borrower,
uint amount,
uint ethGain,
uint lqtyGain,
address frontendTag,
uint getDepositId,
uint setEthGainId,
uint setLqtyGainId
);
event LogStabilityWithdraw(address indexed borrower,
uint amount,
uint ethGain,
uint lqtyGain,
uint setWithdrawId,
uint setEthGainId,
uint setLqtyGainId
);
event LogStabilityMoveEthGainToTrove(address indexed borrower, uint amount);
/* Staking */
event LogStake(address indexed borrower, uint amount, uint getStakeId, uint setEthGainId, uint setLusdGainId);
event LogUnstake(address indexed borrower, uint amount, uint setUnstakeId, uint setEthGainId, uint setLusdGainId);
event LogClaimStakingGains(address indexed borrower, uint ethGain, uint lusdGain, uint setEthGainId, uint setLusdGainId);
}

View File

@ -0,0 +1,6 @@
pragma solidity ^0.7.6;
import { DSMath } from "../../common/math.sol";
import { Basic } from "../../common/basic.sol";
abstract contract Helpers is DSMath, Basic {}

View File

@ -0,0 +1,72 @@
pragma solidity ^0.7.6;
interface BorrowerOperationsLike {
function openTrove(
uint256 _maxFee,
uint256 _LUSDAmount,
address _upperHint,
address _lowerHint
) external payable;
function addColl(address _upperHint, address _lowerHint) external payable;
function withdrawColl(
uint256 _amount,
address _upperHint,
address _lowerHint
) external;
function withdrawLUSD(
uint256 _maxFee,
uint256 _amount,
address _upperHint,
address _lowerHint
) external;
function repayLUSD(
uint256 _amount,
address _upperHint,
address _lowerHint
) external;
function closeTrove() external;
function adjustTrove(
uint256 _maxFee,
uint256 _collWithdrawal,
uint256 _debtChange,
bool isDebtIncrease,
address _upperHint,
address _lowerHint
) external payable;
function claimCollateral() external;
}
interface TroveManagerLike {
function getTroveColl(address _borrower) external view returns (uint);
function getTroveDebt(address _borrower) external view returns (uint);
}
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);
}
interface StakingLike {
function stake(uint _LQTYamount) external;
function unstake(uint _LQTYamount) external;
function getPendingETHGain(address _user) external view returns (uint);
function getPendingLUSDGain(address _user) external view returns (uint);
}
interface CollateralSurplusLike {
function getCollateral(address _account) external view returns (uint);
}
interface LqtyTokenLike {
function balanceOf(address account) external view returns (uint256);
}

View File

@ -0,0 +1,429 @@
pragma solidity ^0.7.6;
/**
* @title Liquity.
* @dev Lending & Borrowing.
*/
import {
BorrowerOperationsLike,
TroveManagerLike,
StabilityPoolLike,
StakingLike,
CollateralSurplusLike,
LqtyTokenLike
} from "./interface.sol";
import { Stores } from "../../common/stores.sol";
import { Helpers } from "./helpers.sol";
import { Events } from "./events.sol";
abstract contract LiquityResolver is Events, Helpers {
BorrowerOperationsLike internal constant borrowerOperations =
BorrowerOperationsLike(0x24179CD81c9e782A4096035f7eC97fB8B783e007);
TroveManagerLike internal constant troveManager =
TroveManagerLike(0xA39739EF8b0231DbFA0DcdA07d7e29faAbCf4bb2);
StabilityPoolLike internal constant stabilityPool =
StabilityPoolLike(0x66017D22b0f8556afDd19FC67041899Eb65a21bb);
StakingLike internal constant staking =
StakingLike(0x4f9Fbb3f1E99B56e0Fe2892e623Ed36A76Fc605d);
CollateralSurplusLike internal constant collateralSurplus =
CollateralSurplusLike(0x3D32e8b97Ed5881324241Cf03b2DA5E2EBcE5521);
LqtyTokenLike internal constant lqtyToken =
LqtyTokenLike(0x6DEA81C8171D0bA574754EF6F8b412F2Ed88c54D);
// Prevents stack-too-deep error
struct AdjustTrove {
uint maxFeePercentage;
uint withdrawAmount;
uint depositAmount;
uint borrowAmount;
uint repayAmount;
bool isBorrow;
}
/* Begin: Trove */
/**
* @dev Deposit native ETH and borrow LUSD
* @notice Opens a Trove by depositing ETH and borrowing LUSD
* @param depositAmount The amount of ETH to deposit
* @param maxFeePercentage The maximum borrow fee that this transaction should permit
* @param borrowAmount The amount of LUSD to borrow
* @param upperHint Address of the Trove near the upper bound of where the user's Trove should now sit in the ordered Trove list
* @param lowerHint Address of the Trove near the lower bound of where the user's Trove should now sit in the ordered Trove list
* @param getId Optional storage slot to retrieve ETH from
* @param setId Optional storage slot to store the LUSD borrowed against
*/
function open(
uint depositAmount,
uint maxFeePercentage,
uint borrowAmount,
address upperHint,
address lowerHint,
uint getId,
uint setId
) external payable returns (string memory _eventName, bytes memory _eventParam) {
if (getId != 0 && depositAmount != 0) {
revert("open(): Cannot supply a depositAmount if a non-zero getId is supplied");
}
depositAmount = getUint(getId, depositAmount);
borrowerOperations.openTrove{value: depositAmount}(
maxFeePercentage,
borrowAmount,
upperHint,
lowerHint
);
// Allow other spells to use the borrowed amount
setUint(setId, borrowAmount);
_eventName = "LogOpen(address,uint256,uint256,uint256,uint256,uint256)";
_eventParam = abi.encode(msg.sender, maxFeePercentage, depositAmount, borrowAmount, getId, setId);
}
/**
* @dev Repay LUSD debt from the DSA account's LUSD balance, and withdraw ETH to DSA
* @notice Closes a Trove by repaying LUSD debt
* @param setId Optional storage slot to store the ETH withdrawn from the Trove
*/
function close(uint setId) external returns (string memory _eventName, bytes memory _eventParam) {
uint collateral = troveManager.getTroveColl(address(this));
borrowerOperations.closeTrove();
// Allow other spells to use the collateral released from the Trove
setUint(setId, collateral);
_eventName = "LogClose(address,uint256)";
_eventParam = abi.encode(msg.sender, setId);
}
/**
* @dev Deposit ETH to Trove
* @notice Increase Trove collateral (collateral Top up)
* @param amount Amount of ETH to deposit into Trove
* @param upperHint Address of the Trove near the upper bound of where the user's Trove should now sit in the ordered Trove list
* @param lowerHint Address of the Trove near the lower bound of where the user's Trove should now sit in the ordered Trove list
* @param getId Optional storage slot to retrieve the ETH from
*/
function deposit(
uint amount,
address upperHint,
address lowerHint,
uint getId
) external payable returns (string memory _eventName, bytes memory _eventParam) {
if (getId != 0 && amount != 0) {
revert("deposit(): Cannot supply an amount if a non-zero getId is supplied");
}
amount = getUint(getId, amount);
borrowerOperations.addColl{value: amount}(upperHint, lowerHint);
_eventName = "LogDeposit(address,uint256,uint256)";
_eventParam = abi.encode(msg.sender, amount, getId);
}
/**
* @dev Withdraw ETH from Trove
* @notice Move Trove collateral from Trove to DSA
* @param amount Amount of ETH to move from Trove to DSA
* @param upperHint Address of the Trove near the upper bound of where the user's Trove should now sit in the ordered Trove list
* @param lowerHint Address of the Trove near the lower bound of where the user's Trove should now sit in the ordered Trove list
* @param setId Optional storage slot to store the withdrawn ETH in
*/
function withdraw(
uint amount,
address upperHint,
address lowerHint,
uint setId
) external payable returns (string memory _eventName, bytes memory _eventParam) {
borrowerOperations.withdrawColl(amount, upperHint, lowerHint);
setUint(setId, amount);
_eventName = "LogWithdraw(address,uint256,uint256)";
_eventParam = abi.encode(msg.sender, amount, setId);
}
/**
* @dev Mints LUSD tokens
* @notice Borrow LUSD via an existing Trove
* @param maxFeePercentage The maximum borrow fee that this transaction should permit
* @param amount Amount of LUSD to borrow
* @param upperHint Address of the Trove near the upper bound of where the user's Trove should now sit in the ordered Trove list
* @param lowerHint Address of the Trove near the lower bound of where the user's Trove should now sit in the ordered Trove list
* @param setId Optional storage slot to store the borrowed LUSD in
*/
function borrow(
uint maxFeePercentage,
uint amount,
address upperHint,
address lowerHint,
uint setId
) external payable returns (string memory _eventName, bytes memory _eventParam) {
borrowerOperations.withdrawLUSD(maxFeePercentage, amount, upperHint, lowerHint);
setUint(setId, amount);
_eventName = "LogBorrow(address,uint256,uint256)";
_eventParam = abi.encode(msg.sender, amount, setId);
}
/**
* @dev Send LUSD to repay debt
* @notice Repay LUSD Trove debt
* @param amount Amount of LUSD to repay
* @param upperHint Address of the Trove near the upper bound of where the user's Trove should now sit in the ordered Trove list
* @param lowerHint Address of the Trove near the lower bound of where the user's Trove should now sit in the ordered Trove list
* @param getId Optional storage slot to retrieve the LUSD from
*/
function repay(
uint amount,
address upperHint,
address lowerHint,
uint getId
) external payable returns (string memory _eventName, bytes memory _eventParam) {
if (getId != 0 && amount != 0) {
revert("repay(): Cannot supply an amount if a non-zero getId is supplied");
}
amount = getUint(getId, amount);
borrowerOperations.repayLUSD(amount, upperHint, lowerHint);
_eventName = "LogRepay(address,uint256,uint256)";
_eventParam = abi.encode(msg.sender, amount, getId);
}
/**
* @dev Increase or decrease Trove ETH collateral and LUSD debt in one transaction
* @notice Adjust Trove debt and/or collateral
* @param maxFeePercentage The maximum borrow fee that this transaction should permit
* @param withdrawAmount Amount of ETH to withdraw
* @param depositAmount Amount of ETH to deposit
* @param borrowAmount Amount of LUSD to borrow
* @param repayAmount Amount of LUSD to repay
* @param upperHint Address of the Trove near the upper bound of where the user's Trove should now sit in the ordered Trove list
* @param lowerHint Address of the Trove near the lower bound of where the user's Trove should now sit in the ordered Trove list
* @param getDepositId Optional storage slot to retrieve the ETH to deposit
* @param setWithdrawId Optional storage slot to store the withdrawn ETH to
* @param getRepayId Optional storage slot to retrieve the LUSD to repay
* @param setBorrowId Optional storage slot to store the LUSD borrowed
*/
function adjust(
uint maxFeePercentage,
uint withdrawAmount,
uint depositAmount,
uint borrowAmount,
uint repayAmount,
address upperHint,
address lowerHint,
uint getDepositId,
uint setWithdrawId,
uint getRepayId,
uint setBorrowId
) external payable returns (string memory _eventName, bytes memory _eventParam) {
if (getDepositId != 0 && depositAmount != 0) {
revert("adjust(): Cannot supply a depositAmount if a non-zero getDepositId is supplied");
}
if (getRepayId != 0 && repayAmount != 0) {
revert("adjust(): Cannot supply a repayAmount if a non-zero getRepayId is supplied");
}
AdjustTrove memory adjustTrove;
adjustTrove.maxFeePercentage = maxFeePercentage;
adjustTrove.withdrawAmount = withdrawAmount;
adjustTrove.depositAmount = getUint(getDepositId, depositAmount);
adjustTrove.borrowAmount = borrowAmount;
adjustTrove.repayAmount = getUint(getRepayId, repayAmount);
adjustTrove.isBorrow = borrowAmount > 0;
borrowerOperations.adjustTrove{value: adjustTrove.depositAmount}(
adjustTrove.maxFeePercentage,
adjustTrove.withdrawAmount,
adjustTrove.borrowAmount,
adjustTrove.isBorrow,
upperHint,
lowerHint
);
// Allow other spells to use the withdrawn collateral
setUint(setWithdrawId, withdrawAmount);
// Allow other spells to use the borrowed amount
setUint(setBorrowId, borrowAmount);
_eventName = "LogAdjust(address,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256)";
_eventParam = abi.encode(msg.sender, maxFeePercentage, depositAmount, withdrawAmount, borrowAmount, repayAmount, getDepositId, setWithdrawId, getRepayId, setBorrowId);
}
/**
* @dev Withdraw remaining ETH balance from user's redeemed Trove to their DSA
* @param setId Optional storage slot to store the ETH claimed
* @notice Claim remaining collateral from Trove
*/
function claimCollateralFromRedemption(uint setId) external returns(string memory _eventName, bytes memory _eventParam) {
uint amount = collateralSurplus.getCollateral(address(this));
borrowerOperations.claimCollateral();
setUint(setId, amount);
_eventName = "LogClaimCollateralFromRedemption(address,uint256,uint256)";
_eventParam = abi.encode(msg.sender, amount, setId);
}
/* End: Trove */
/* 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 frontendTag Address of the frontend to make this deposit against (determines the kickback rate of rewards)
* @param getDepositId Optional storage slot to retrieve the LUSD from
* @param setEthGainId Optional storage slot to store any ETH gains in
* @param setLqtyGainId Optional storage slot to store any LQTY gains in
*/
function stabilityDeposit(
uint amount,
address frontendTag,
uint getDepositId,
uint setEthGainId,
uint setLqtyGainId
) external returns (string memory _eventName, bytes memory _eventParam) {
amount = getUint(getDepositId, amount);
uint ethGain = stabilityPool.getDepositorETHGain(address(this));
uint lqtyBalanceBefore = lqtyToken.balanceOf(address(this));
stabilityPool.provideToSP(amount, frontendTag);
uint lqtyBalanceAfter = lqtyToken.balanceOf(address(this));
uint lqtyGain = sub(lqtyBalanceAfter, lqtyBalanceBefore);
setUint(setEthGainId, ethGain);
setUint(setLqtyGainId, lqtyGain);
_eventName = "LogStabilityDeposit(address,uint256,uint256,uint256,address,uint256,uint256,uint256)";
_eventParam = abi.encode(msg.sender, amount, ethGain, lqtyGain, frontendTag, getDepositId, setEthGainId, setLqtyGainId);
}
/**
* @dev Withdraw user deposited LUSD from Stability Pool
* @notice Withdraw LUSD from Stability Pool
* @param amount Amount of LUSD to withdraw from Stability Pool
* @param setWithdrawId Optional storage slot to store the withdrawn LUSD
* @param setEthGainId Optional storage slot to store any ETH gains in
* @param setLqtyGainId Optional storage slot to store any LQTY gains in
*/
function stabilityWithdraw(
uint amount,
uint setWithdrawId,
uint setEthGainId,
uint setLqtyGainId
) external returns (string memory _eventName, bytes memory _eventParam) {
uint ethGain = stabilityPool.getDepositorETHGain(address(this));
uint lqtyBalanceBefore = lqtyToken.balanceOf(address(this));
stabilityPool.withdrawFromSP(amount);
uint lqtyBalanceAfter = lqtyToken.balanceOf(address(this));
uint lqtyGain = sub(lqtyBalanceAfter, lqtyBalanceBefore);
setUint(setWithdrawId, amount);
setUint(setEthGainId, ethGain);
setUint(setLqtyGainId, lqtyGain);
_eventName = "LogStabilityWithdraw(address,uint256,uint256,uint256,uint256,uint256,uint256)";
_eventParam = abi.encode(msg.sender, amount, ethGain, lqtyGain, setWithdrawId, setEthGainId, setLqtyGainId);
}
/**
* @dev Increase Trove collateral by sending Stability Pool ETH gain to user's Trove
* @notice Moves user's ETH gain from the Stability Pool into their Trove
* @param upperHint Address of the Trove near the upper bound of where the user's Trove should now sit in the ordered Trove list
* @param lowerHint Address of the Trove near the lower bound of where the user's Trove should now sit in the ordered Trove list
*/
function stabilityMoveEthGainToTrove(
address upperHint,
address lowerHint
) external returns (string memory _eventName, bytes memory _eventParam) {
uint amount = stabilityPool.getDepositorETHGain(address(this));
stabilityPool.withdrawETHGainToTrove(upperHint, lowerHint);
_eventName = "LogStabilityMoveEthGainToTrove(address,uint256)";
_eventParam = abi.encode(msg.sender, amount);
}
/* End: Stability Pool */
/* Begin: Staking */
/**
* @dev Sends LQTY tokens from user to Staking Pool
* @notice Stake LQTY in Staking Pool
* @param amount Amount of LQTY to stake
* @param getStakeId Optional storage slot to retrieve the LQTY from
* @param setEthGainId Optional storage slot to store any ETH gains
* @param setLusdGainId Optional storage slot to store any LUSD gains
*/
function stake(
uint amount,
uint getStakeId,
uint setEthGainId,
uint setLusdGainId
) external returns (string memory _eventName, bytes memory _eventParam) {
uint ethGain = staking.getPendingETHGain(address(this));
uint lusdGain = staking.getPendingLUSDGain(address(this));
amount = getUint(getStakeId, amount);
staking.stake(amount);
setUint(setEthGainId, ethGain);
setUint(setLusdGainId, lusdGain);
_eventName = "LogStake(address,uint256,uint256,uint256,uint256)";
_eventParam = abi.encode(msg.sender, amount, getStakeId, setEthGainId, setLusdGainId);
}
/**
* @dev Sends LQTY tokens from Staking Pool to user
* @notice Unstake LQTY in Staking Pool
* @param amount Amount of LQTY to unstake
* @param setStakeId Optional storage slot to store the unstaked LQTY
* @param setEthGainId Optional storage slot to store any ETH gains
* @param setLusdGainId Optional storage slot to store any LUSD gains
*/
function unstake(
uint amount,
uint setStakeId,
uint setEthGainId,
uint setLusdGainId
) external returns (string memory _eventName, bytes memory _eventParam) {
uint ethGain = staking.getPendingETHGain(address(this));
uint lusdGain = staking.getPendingLUSDGain(address(this));
staking.unstake(amount);
setUint(setStakeId, amount);
setUint(setEthGainId, ethGain);
setUint(setLusdGainId, lusdGain);
_eventName = "LogUnstake(address,uint256,uint256,uint256,uint256)";
_eventParam = abi.encode(msg.sender, amount, setStakeId, setEthGainId, setLusdGainId);
}
/**
* @dev Sends ETH and LUSD gains from Staking to user
* @notice Claim ETH and LUSD gains from Staking
* @param setEthGainId Optional storage slot to store the claimed ETH
* @param setLusdGainId Optional storage slot to store the claimed LUSD
*/
function claimStakingGains(
uint setEthGainId,
uint setLusdGainId
) external returns (string memory _eventName, bytes memory _eventParam) {
uint ethGain = staking.getPendingETHGain(address(this));
uint lusdGain = staking.getPendingLUSDGain(address(this));
// Gains are claimed when a user's stake is adjusted, so we unstake 0 to trigger the claim
staking.unstake(0);
setUint(setEthGainId, ethGain);
setUint(setLusdGainId, lusdGain);
_eventName = "LogClaimStakingGains(address,uint256,uint256,uint256,uint256)";
_eventParam = abi.encode(msg.sender, ethGain, lusdGain, setEthGainId, setLusdGainId);
}
/* End: Staking */
}
contract ConnectV2Liquity is LiquityResolver {
string public name = "Liquity-v1";
}

View File

@ -1,4 +1,3 @@
require("@nomiclabs/hardhat-waffle");
require("@nomiclabs/hardhat-ethers");
require("@tenderly/hardhat-tenderly");
@ -6,7 +5,7 @@ require("@nomiclabs/hardhat-etherscan");
require("@nomiclabs/hardhat-web3")
require("hardhat-deploy");
require("hardhat-deploy-ethers");
require('dotenv').config();
require("dotenv").config();
const { utils } = require("ethers");
@ -30,14 +29,14 @@ module.exports = {
},
{
version: "0.6.5"
}
]
},
],
},
networks: {
// defaultNetwork: "hardhat",
kovan: {
url: `https://eth-kovan.alchemyapi.io/v2/${ALCHEMY_ID}`,
accounts: [`0x${PRIVATE_KEY}`]
accounts: [`0x${PRIVATE_KEY}`],
},
mainnet: {
url: `https://eth-mainnet.alchemyapi.io/v2/${ALCHEMY_ID}`,
@ -50,20 +49,20 @@ module.exports = {
url: `https://eth-mainnet.alchemyapi.io/v2/${ALCHEMY_ID}`,
blockNumber: 12433781,
},
blockGasLimit: 12000000,
blockGasLimit: 12000000
},
matic: {
url: "https://rpc-mainnet.maticvigil.com/",
accounts: [`0x${PRIVATE_KEY}`],
timeout: 150000,
gasPrice: parseInt(utils.parseUnits("1", "gwei"))
}
},
},
etherscan: {
apiKey: process.env.ETHERSCAN_API_KEY
},
tenderly: {
project: process.env.TENDERLY_PROJECT,
username: process.env.TENDERLY_USERNAME,
}
username: process.env.TENDERLY_USERNAME
},
};

View File

@ -1,14 +1,14 @@
module.exports = {
core: {
connectorsV2: require("./abi/core/connectorsV2.json"),
instaIndex: require("./abi/core/instaIndex.json"),
},
connectors: {
basic: require("./abi/connectors/basic.json"),
auth: require("./abi/connectors/auth.json"),
},
basic: {
erc20: require("./abi/basics/erc20.json"),
},
};
core: {
connectorsV2: require("./abi/core/connectorsV2.json"),
instaIndex: require("./abi/core/instaIndex.json"),
},
connectors: {
"Basic-v1": require("./abi/connectors/basic.json"),
basic: require("./abi/connectors/basic.json"),
auth: require("./abi/connectors/auth.json"),
},
basic: {
erc20: require("./abi/basics/erc20.json"),
},
};

View File

@ -1,11 +1,10 @@
module.exports = {
connectors: {
basic: "0xe5398f279175962E56fE4c5E0b62dc7208EF36c6",
auth: "0xd1aff9f2acf800c876c409100d6f39aea93fc3d9",
},
core: {
connectorsV2: "0xFE2390DAD597594439f218190fC2De40f9Cf1179",
instaIndex: "0x2971AdFa57b20E5a416aE5a708A8655A9c74f723"
}
};
connectors: {
basic: "0xe5398f279175962E56fE4c5E0b62dc7208EF36c6",
auth: "0xd1aff9f2acf800c876c409100d6f39aea93fc3d9",
},
core: {
connectorsV2: "0x97b0B3A8bDeFE8cB9563a3c610019Ad10DB8aD11",
instaIndex: "0x2971AdFa57b20E5a416aE5a708A8655A9c74f723",
},
};

View File

@ -0,0 +1,95 @@
const TROVE_MANAGER_ADDRESS = "0xA39739EF8b0231DbFA0DcdA07d7e29faAbCf4bb2";
const TROVE_MANAGER_ABI = [
"function getTroveColl(address _borrower) external view returns (uint)",
"function getTroveDebt(address _borrower) external view returns (uint)",
"function getTroveStatus(address _borrower) external view returns (uint)",
"function redeemCollateral(uint _LUSDAmount, address _firstRedemptionHint, address _upperPartialRedemptionHint, address _lowerPartialRedemptionHint, uint _partialRedemptionHintNICR, uint _maxIterations, uint _maxFee) external returns (uint)",
"function getNominalICR(address _borrower) external view returns (uint)",
"function liquidate(address _borrower) external",
"function liquidateTroves(uint _n) external",
];
const BORROWER_OPERATIONS_ADDRESS =
"0x24179CD81c9e782A4096035f7eC97fB8B783e007";
const BORROWER_OPERATIONS_ABI = [
"function openTrove(uint256 _maxFee, uint256 _LUSDAmount, address _upperHint, address _lowerHint) external payable",
"function closeTrove() external",
];
const LUSD_TOKEN_ADDRESS = "0x5f98805A4E8be255a32880FDeC7F6728C6568bA0";
const LUSD_TOKEN_ABI = [
"function transfer(address _to, uint256 _value) public returns (bool success)",
"function balanceOf(address account) external view returns (uint256)",
"function approve(address spender, uint256 amount) external returns (bool)",
];
const ACTIVE_POOL_ADDRESS = "0xDf9Eb223bAFBE5c5271415C75aeCD68C21fE3D7F";
const ACTIVE_POOL_ABI = ["function getLUSDDebt() external view returns (uint)"];
const PRICE_FEED_ADDRESS = "0x4c517D4e2C851CA76d7eC94B805269Df0f2201De";
const PRICE_FEED_ABI = ["function fetchPrice() external returns (uint)"];
const HINT_HELPERS_ADDRESS = "0xE84251b93D9524E0d2e621Ba7dc7cb3579F997C0";
const HINT_HELPERS_ABI = [
"function getRedemptionHints(uint _LUSDamount, uint _price, uint _maxIterations) external view returns (address firstRedemptionHint, uint partialRedemptionHintNICR, uint truncatedLUSDamount)",
"function getApproxHint(uint _CR, uint _numTrials, uint _inputRandomSeed) view returns (address hintAddress, uint diff, uint latestRandomSeed)",
"function computeNominalCR(uint _coll, uint _debt) external pure returns (uint)",
];
const SORTED_TROVES_ADDRESS = "0x8FdD3fbFEb32b28fb73555518f8b361bCeA741A6";
const SORTED_TROVES_ABI = [
"function findInsertPosition(uint256 _ICR, address _prevId, address _nextId) external view returns (address, address)",
"function getLast() external view returns (address)",
];
const STABILITY_POOL_ADDRESS = "0x66017D22b0f8556afDd19FC67041899Eb65a21bb";
const STABILITY_POOL_ABI = [
"function getCompoundedLUSDDeposit(address _depositor) external view returns (uint)",
"function getDepositorETHGain(address _depositor) external view returns (uint)",
"function getDepositorLQTYGain(address _depositor) external view returns (uint)",
];
const STAKING_ADDRESS = "0x4f9Fbb3f1E99B56e0Fe2892e623Ed36A76Fc605d";
const STAKING_ABI = [
"function stake(uint _LQTYamount) external",
"function unstake(uint _LQTYamount) external",
"function getPendingETHGain(address _user) external view returns (uint)",
"function getPendingLUSDGain(address _user) external view returns (uint)",
];
const LQTY_TOKEN_ADDRESS = "0x6DEA81C8171D0bA574754EF6F8b412F2Ed88c54D";
const LQTY_TOKEN_ABI = [
"function balanceOf(address account) external view returns (uint256)",
"function transfer(address _to, uint256 _value) public returns (bool success)",
"function approve(address spender, uint256 amount) external returns (bool)",
];
const COLL_SURPLUS_ADDRESS = "0x3D32e8b97Ed5881324241Cf03b2DA5E2EBcE5521";
const COLL_SURPLUS_ABI = [
"function getCollateral(address _account) external view returns (uint)",
];
module.exports = {
TROVE_MANAGER_ADDRESS,
TROVE_MANAGER_ABI,
BORROWER_OPERATIONS_ADDRESS,
BORROWER_OPERATIONS_ABI,
LUSD_TOKEN_ADDRESS,
LUSD_TOKEN_ABI,
STABILITY_POOL_ADDRESS,
STABILITY_POOL_ABI,
ACTIVE_POOL_ADDRESS,
ACTIVE_POOL_ABI,
PRICE_FEED_ADDRESS,
PRICE_FEED_ABI,
HINT_HELPERS_ADDRESS,
HINT_HELPERS_ABI,
SORTED_TROVES_ADDRESS,
SORTED_TROVES_ABI,
STAKING_ADDRESS,
STAKING_ABI,
LQTY_TOKEN_ADDRESS,
LQTY_TOKEN_ABI,
COLL_SURPLUS_ADDRESS,
COLL_SURPLUS_ABI,
};

View File

@ -0,0 +1,343 @@
const hre = require("hardhat");
const hardhatConfig = require("../../hardhat.config");
// Instadapp deployment and testing helpers
const deployAndEnableConnector = require("../../scripts/deployAndEnableConnector.js");
const encodeSpells = require("../../scripts/encodeSpells.js");
const getMasterSigner = require("../../scripts/getMasterSigner");
const buildDSAv2 = require("../../scripts/buildDSAv2");
// Instadapp instadappAddresses/ABIs
const instadappAddresses = require("../../scripts/constant/addresses");
const instadappAbi = require("../../scripts/constant/abis");
// Instadapp Liquity Connector artifacts
const connectV2LiquityArtifacts = require("../../artifacts/contracts/mainnet/connectors/liquity/main.sol/ConnectV2Liquity.json");
const connectV2BasicV1Artifacts = require("../../artifacts/contracts/mainnet/connectors/basic/main.sol/ConnectV2Basic.json");
const { ethers } = require("hardhat");
// Instadapp uses a fake address to represent native ETH
const { eth_addr: ETH_ADDRESS } = require("../../scripts/constant/constant");
const LIQUITY_CONNECTOR = "LIQUITY-v1-TEST";
const LUSD_GAS_COMPENSATION = hre.ethers.utils.parseUnits("200", 18); // 200 LUSD gas compensation repaid after loan repayment
const LIQUIDATABLE_TROVES_BLOCK_NUMBER = 12478159; // Deterministic block number for tests to run against, if you change this, tests will break.
const JUSTIN_SUN_ADDRESS = "0x903d12bf2c57a29f32365917c706ce0e1a84cce3"; // LQTY whale address
const LIQUIDATABLE_TROVE_ADDRESS = "0xafbeb4cb97f3b08ec2fe07ef0dac15d37013a347"; // Trove which is liquidatable at blockNumber: LIQUIDATABLE_TROVES_BLOCK_NUMBER
const MAX_GAS = hardhatConfig.networks.hardhat.blockGasLimit; // Maximum gas limit (12000000)
const INSTADAPP_BASIC_V1_CONNECTOR = "Basic-v1";
const openTroveSpell = async (
dsa,
signer,
depositAmount,
borrowAmount,
upperHint,
lowerHint,
maxFeePercentage
) => {
let address = signer.address;
if (signer.address === undefined) {
address = await signer.getAddress();
}
const openTroveSpell = {
connector: LIQUITY_CONNECTOR,
method: "open",
args: [
depositAmount,
maxFeePercentage,
borrowAmount,
upperHint,
lowerHint,
0,
0,
],
};
return await dsa
.connect(signer)
.cast(...encodeSpells([openTroveSpell]), address, {
value: depositAmount,
});
};
const createDsaTrove = async (
dsa,
signer,
liquity,
depositAmount = hre.ethers.utils.parseEther("5"),
borrowAmount = hre.ethers.utils.parseUnits("2000", 18)
) => {
const maxFeePercentage = hre.ethers.utils.parseUnits("0.5", 18); // 0.5% max fee
const { upperHint, lowerHint } = await getTroveInsertionHints(
depositAmount,
borrowAmount,
liquity
);
return await openTroveSpell(
dsa,
signer,
depositAmount,
borrowAmount,
upperHint,
lowerHint,
maxFeePercentage
);
};
const sendToken = async (token, amount, from, to) => {
await hre.network.provider.request({
method: "hardhat_impersonateAccount",
params: [from],
});
const signer = await hre.ethers.provider.getSigner(from);
return await token.connect(signer).transfer(to, amount, {
gasPrice: 0,
});
};
const resetInitialState = async (walletAddress, contracts, isDebug = false) => {
const liquity = await deployAndConnect(contracts, isDebug);
const dsa = await buildDSAv2(walletAddress);
return [liquity, dsa];
};
const resetHardhatBlockNumber = async (blockNumber) => {
return await hre.network.provider.request({
method: "hardhat_reset",
params: [
{
forking: {
jsonRpcUrl: hardhatConfig.networks.hardhat.forking.url,
blockNumber,
},
},
],
});
};
const deployAndConnect = async (contracts, isDebug = false) => {
// Pin Liquity tests to a particular block number to create deterministic state (Ether price etc.)
await resetHardhatBlockNumber(LIQUIDATABLE_TROVES_BLOCK_NUMBER);
const liquity = {
troveManager: null,
borrowerOperations: null,
stabilityPool: null,
lusdToken: null,
lqtyToken: null,
activePool: null,
priceFeed: null,
hintHelpers: null,
sortedTroves: null,
staking: null,
collSurplus: null,
};
const masterSigner = await getMasterSigner();
const instaConnectorsV2 = await ethers.getContractAt(
instadappAbi.core.connectorsV2,
instadappAddresses.core.connectorsV2
);
const connector = await deployAndEnableConnector({
connectorName: LIQUITY_CONNECTOR,
contractArtifact: connectV2LiquityArtifacts,
signer: masterSigner,
connectors: instaConnectorsV2,
});
isDebug &&
console.log(`${LIQUITY_CONNECTOR} Connector address`, connector.address);
const basicConnector = await deployAndEnableConnector({
connectorName: "Basic-v1",
contractArtifact: connectV2BasicV1Artifacts,
signer: masterSigner,
connectors: instaConnectorsV2,
});
isDebug && console.log("Basic-v1 Connector address", basicConnector.address);
liquity.troveManager = new ethers.Contract(
contracts.TROVE_MANAGER_ADDRESS,
contracts.TROVE_MANAGER_ABI,
ethers.provider
);
liquity.borrowerOperations = new ethers.Contract(
contracts.BORROWER_OPERATIONS_ADDRESS,
contracts.BORROWER_OPERATIONS_ABI,
ethers.provider
);
liquity.stabilityPool = new ethers.Contract(
contracts.STABILITY_POOL_ADDRESS,
contracts.STABILITY_POOL_ABI,
ethers.provider
);
liquity.lusdToken = new ethers.Contract(
contracts.LUSD_TOKEN_ADDRESS,
contracts.LUSD_TOKEN_ABI,
ethers.provider
);
liquity.lqtyToken = new ethers.Contract(
contracts.LQTY_TOKEN_ADDRESS,
contracts.LQTY_TOKEN_ABI,
ethers.provider
);
liquity.activePool = new ethers.Contract(
contracts.ACTIVE_POOL_ADDRESS,
contracts.ACTIVE_POOL_ABI,
ethers.provider
);
liquity.priceFeed = new ethers.Contract(
contracts.PRICE_FEED_ADDRESS,
contracts.PRICE_FEED_ABI,
ethers.provider
);
liquity.hintHelpers = new ethers.Contract(
contracts.HINT_HELPERS_ADDRESS,
contracts.HINT_HELPERS_ABI,
ethers.provider
);
liquity.sortedTroves = new ethers.Contract(
contracts.SORTED_TROVES_ADDRESS,
contracts.SORTED_TROVES_ABI,
ethers.provider
);
liquity.staking = new ethers.Contract(
contracts.STAKING_ADDRESS,
contracts.STAKING_ABI,
ethers.provider
);
liquity.collSurplus = new ethers.Contract(
contracts.COLL_SURPLUS_ADDRESS,
contracts.COLL_SURPLUS_ABI,
ethers.provider
);
return liquity;
};
const getTroveInsertionHints = async (depositAmount, borrowAmount, liquity) => {
const nominalCR = await liquity.hintHelpers.computeNominalCR(
depositAmount,
borrowAmount
);
const {
hintAddress,
latestRandomSeed,
} = await liquity.hintHelpers.getApproxHint(nominalCR, 50, 1298379, {
gasLimit: MAX_GAS,
});
randomSeed = latestRandomSeed;
const {
0: upperHint,
1: lowerHint,
} = await liquity.sortedTroves.findInsertPosition(
nominalCR,
hintAddress,
hintAddress,
{
gasLimit: MAX_GAS,
}
);
return {
upperHint,
lowerHint,
};
};
let randomSeed = 4223;
const getRedemptionHints = async (amount, liquity) => {
const ethPrice = await liquity.priceFeed.callStatic.fetchPrice();
const [
firstRedemptionHint,
partialRedemptionHintNicr,
] = await liquity.hintHelpers.getRedemptionHints(amount, ethPrice, 0);
const {
hintAddress,
latestRandomSeed,
} = await liquity.hintHelpers.getApproxHint(
partialRedemptionHintNicr,
50,
randomSeed,
{
gasLimit: MAX_GAS,
}
);
randomSeed = latestRandomSeed;
const {
0: upperHint,
1: lowerHint,
} = await liquity.sortedTroves.findInsertPosition(
partialRedemptionHintNicr,
hintAddress,
hintAddress,
{
gasLimit: MAX_GAS,
}
);
return {
partialRedemptionHintNicr,
firstRedemptionHint,
upperHint,
lowerHint,
};
};
const redeem = async (amount, from, wallet, liquity) => {
await sendToken(liquity.lusdToken, amount, from, wallet.address);
const {
partialRedemptionHintNicr,
firstRedemptionHint,
upperHint,
lowerHint,
} = await getRedemptionHints(amount, liquity);
const maxFeePercentage = ethers.utils.parseUnits("0.5", 18); // 0.5% max fee
return await liquity.troveManager
.connect(wallet)
.redeemCollateral(
amount,
firstRedemptionHint,
upperHint,
lowerHint,
partialRedemptionHintNicr,
0,
maxFeePercentage,
{
gasLimit: MAX_GAS, // permit max gas
}
);
};
module.exports = {
deployAndConnect,
resetInitialState,
createDsaTrove,
sendToken,
getTroveInsertionHints,
getRedemptionHints,
redeem,
LIQUITY_CONNECTOR,
LUSD_GAS_COMPENSATION,
JUSTIN_SUN_ADDRESS,
LIQUIDATABLE_TROVE_ADDRESS,
MAX_GAS,
INSTADAPP_BASIC_V1_CONNECTOR,
ETH_ADDRESS,
};

2693
test/liquity/liquity.test.js Normal file

File diff suppressed because it is too large Load Diff