From a1203c66aea638099ec2b1e38e9f15196e13c34d Mon Sep 17 00:00:00 2001 From: Edward Mulraney Date: Tue, 1 Jun 2021 15:50:52 +0100 Subject: [PATCH 01/19] add liquity connector --- .../mainnet/connectors/liquity/events.sol | 42 + .../mainnet/connectors/liquity/helpers.sol | 6 + .../mainnet/connectors/liquity/interface.sol | 60 + contracts/mainnet/connectors/liquity/main.sol | 347 +++++ hardhat.config.js | 29 +- scripts/constant/abis.js | 26 +- scripts/constant/addresses.js | 19 +- test/liquity/liquity.abi.js | 84 ++ test/liquity/liquity.helpers.js | 327 +++++ test/liquity/liquity.test.js | 1133 +++++++++++++++++ 10 files changed, 2035 insertions(+), 38 deletions(-) create mode 100644 contracts/mainnet/connectors/liquity/events.sol create mode 100644 contracts/mainnet/connectors/liquity/helpers.sol create mode 100644 contracts/mainnet/connectors/liquity/interface.sol create mode 100644 contracts/mainnet/connectors/liquity/main.sol create mode 100644 test/liquity/liquity.abi.js create mode 100644 test/liquity/liquity.helpers.js create mode 100644 test/liquity/liquity.test.js diff --git a/contracts/mainnet/connectors/liquity/events.sol b/contracts/mainnet/connectors/liquity/events.sol new file mode 100644 index 00000000..c7d1dcc8 --- /dev/null +++ b/contracts/mainnet/connectors/liquity/events.sol @@ -0,0 +1,42 @@ +pragma solidity ^0.7.0; + +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); + + /* Stability Pool */ + event LogStabilityDeposit(address indexed borrower, uint amount, address frontendTag, uint getId); + event LogStabilityWithdraw(address indexed borrower, uint amount, uint setId); + event LogStabilityMoveEthGainToTrove(address indexed borrower); + + /* Staking */ + event LogStake(address indexed borrower, uint amount, uint getId); + event LogUnstake(address indexed borrower, uint amount, uint setId); + event LogClaimGains(address indexed borrower); +} \ No newline at end of file diff --git a/contracts/mainnet/connectors/liquity/helpers.sol b/contracts/mainnet/connectors/liquity/helpers.sol new file mode 100644 index 00000000..1fb15204 --- /dev/null +++ b/contracts/mainnet/connectors/liquity/helpers.sol @@ -0,0 +1,6 @@ +pragma solidity ^0.7.0; + +import { DSMath } from "../../common/math.sol"; +import { Basic } from "../../common/basic.sol"; + +abstract contract Helpers is DSMath, Basic {} \ No newline at end of file diff --git a/contracts/mainnet/connectors/liquity/interface.sol b/contracts/mainnet/connectors/liquity/interface.sol new file mode 100644 index 00000000..80782912 --- /dev/null +++ b/contracts/mainnet/connectors/liquity/interface.sol @@ -0,0 +1,60 @@ +pragma solidity ^0.7.0; + +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; +} + +interface StakingLike { + function stake(uint _LQTYamount) external; + function unstake(uint _LQTYamount) external; +} diff --git a/contracts/mainnet/connectors/liquity/main.sol b/contracts/mainnet/connectors/liquity/main.sol new file mode 100644 index 00000000..948b6450 --- /dev/null +++ b/contracts/mainnet/connectors/liquity/main.sol @@ -0,0 +1,347 @@ +pragma solidity ^0.7.0; + +/** + * @title Liquity. + * @dev Lending & Borrowing. + */ +import "hardhat/console.sol"; + +import { BorrowerOperationsLike, TroveManagerLike, StabilityPoolLike, StakingLike } 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); + + struct AdjustTrove { + uint maxFeePercentage; + uint withdrawAmount; + uint depositAmount; + uint borrowAmount; + uint repayAmount; + bool isBorrow; + } + + constructor() { + console.log("Liquity Connector contract deployed at", address(this)); + } + + /* 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 will now sit in the ordered Trove list + * @param lowerHint Address of the Trove near the lower bound of where the user's Trove will now sit in the ordered Trove list + * @param getId Optional storage slot to retrieve ETH instead of receiving it from msg.value + * @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) { + // User can either send ETH directly or have it collected from a previous spell + 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,uint,uint,uint,uint,uint)"; + _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,uint)"; + _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 will now sit in the ordered Trove list + * @param lowerHint Address of the Trove near the lower bound of where the user's Trove will 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) { + amount = getUint(getId, amount); + borrowerOperations.addColl{value: amount}(upperHint, lowerHint); + _eventName = "LogDeposit(address,uint,uint)"; + _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 will now sit in the ordered Trove list + * @param lowerHint Address of the Trove near the lower bound of where the user's Trove will 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,uint,uint)"; + _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 will now sit in the ordered Trove list + * @param lowerHint Address of the Trove near the lower bound of where the user's Trove will 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); // TODO: apply fee / get exact amount borrowed (with the fee applied) + _eventName = "LogBorrow(address,uint,uint)"; + _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 will now sit in the ordered Trove list + * @param lowerHint Address of the Trove near the lower bound of where the user's Trove will 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) { + amount = getUint(getId, amount); + borrowerOperations.repayLUSD(amount, upperHint, lowerHint); + _eventName = "LogRepay(address,uint,uint)"; + _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 will now sit in the ordered Trove list + * @param lowerHint Address of the Trove near the lower bound of where the user's Trove will 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) { + 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: 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,uint,uint,uint,uint,uint,uint,uint,uint,uint)"; + _eventParam = abi.encode(msg.sender, maxFeePercentage, depositAmount, borrowAmount, getDepositId, setWithdrawId, getRepayId, setBorrowId); + } + + /** + * @dev Withdraw remaining ETH balance from user's redeemed Trove to their DSA + * @notice Claim remaining collateral from Trove + */ + function claimCollateralFromRedemption() external returns(string memory _eventName, bytes memory _eventParam) { + borrowerOperations.claimCollateral(); + _eventName = "LogClaimCollateralFromRedemption(address)"; + _eventParam = abi.encode(msg.sender); + } + /* 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 getId Optional storage slot to retrieve the LUSD from + */ + function stabilityDeposit( + uint amount, + address frontendTag, + uint getId + ) external returns (string memory _eventName, bytes memory _eventParam) { + amount = getUint(getId, amount); + + stabilityPool.provideToSP(amount, frontendTag); + + _eventName = "LogStabilityDeposit(address,uint,address,uint)"; + _eventParam = abi.encode(msg.sender, amount, frontendTag, getId); + } + + /** + * @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 setId Optional storage slot to store the withdrawn LUSD + */ + function stabilityWithdraw( + uint amount, + uint setId + ) external returns (string memory _eventName, bytes memory _eventParam) { + stabilityPool.withdrawFromSP(amount); + setUint(setId, amount); + + _eventName = "LogStabilityWithdraw(address,uint,uint)"; + _eventParam = abi.encode(msg.sender, amount, setId); + } + + /** + * @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 will now sit in the ordered Trove list + * @param lowerHint Address of the Trove near the lower bound of where the user's Trove will now sit in the ordered Trove list + */ + function stabilityMoveEthGainToTrove( + address upperHint, + address lowerHint + ) external returns (string memory _eventName, bytes memory _eventParam) { + stabilityPool.withdrawETHGainToTrove(upperHint, lowerHint); + + _eventName = "LogStabilityMoveEthGainToTrove(address)"; + _eventParam = abi.encode(msg.sender); + } + /* 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 getId Optional storage slot to retrieve the LQTY from + */ + function stake( + uint amount, + uint getId + ) external returns (string memory _eventName, bytes memory _eventParam) { + amount = getUint(getId, amount); + staking.stake(amount); + _eventName = "LogStake(address,uint,uint)"; + _eventParam = abi.encode(msg.sender, amount, getId); + } + + /** + * @dev Sends LQTY tokens from Staking Pool to user + * @notice Unstake LQTY in Staking Pool + * @param amount Amount of LQTY to unstake + * @param setId Optional storage slot to store the unstaked LQTY + */ + function unstake( + uint amount, + uint setId + ) external returns (string memory _eventName, bytes memory _eventParam) { + staking.unstake(amount); + setUint(setId, amount); + _eventName = "LogUnstake(address,uint,uint)"; + _eventParam = abi.encode(msg.sender, amount, setId); + } + + /** + * @dev Sends ETH and LUSD gains from Staking to user + * @notice Claim ETH and LUSD gains from Staking + */ + function claimGains() external returns (string memory _eventName, bytes memory _eventParam) { + // claims are gained when a user's stake is adjusted, so we unstake 0 to trigger the claim + staking.unstake(0); + _eventName = "LogClaimGains(address)"; + _eventParam = abi.encode(msg.sender); + } + /* End: Staking */ + +} + +contract ConnectV2Liquity is LiquityResolver { + string public name = "Liquity-v1"; +} diff --git a/hardhat.config.js b/hardhat.config.js index b48aa2f3..6c1f8bb2 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -1,12 +1,11 @@ - require("@nomiclabs/hardhat-waffle"); require("@nomiclabs/hardhat-ethers"); require("@tenderly/hardhat-tenderly"); require("@nomiclabs/hardhat-etherscan"); -require("@nomiclabs/hardhat-web3") +require("@nomiclabs/hardhat-web3"); require("hardhat-deploy"); require("hardhat-deploy-ethers"); -require('dotenv').config(); +require("dotenv").config(); const { utils } = require("ethers"); @@ -20,30 +19,30 @@ module.exports = { solidity: { compilers: [ { - version: "0.7.6" + version: "0.7.6", }, { - version: "0.6.0" + version: "0.6.0", }, { - version: "0.6.2" + version: "0.6.2", }, { - version: "0.6.5" - } - ] + 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}`, accounts: [`0x${PRIVATE_KEY}`], timeout: 150000, - gasPrice: parseInt(utils.parseUnits("132", "gwei")) + gasPrice: parseInt(utils.parseUnits("132", "gwei")), }, hardhat: { forking: { @@ -56,14 +55,14 @@ module.exports = { url: "https://rpc-mainnet.maticvigil.com/", accounts: [`0x${PRIVATE_KEY}`], timeout: 150000, - gasPrice: parseInt(utils.parseUnits("1", "gwei")) - } + gasPrice: parseInt(utils.parseUnits("1", "gwei")), + }, }, etherscan: { - apiKey: process.env.ETHERSCAN_API_KEY + apiKey: process.env.ETHERSCAN_API_KEY, }, tenderly: { project: process.env.TENDERLY_PROJECT, username: process.env.TENDERLY_USERNAME, - } + }, }; diff --git a/scripts/constant/abis.js b/scripts/constant/abis.js index c25bd350..5f0c4d65 100644 --- a/scripts/constant/abis.js +++ b/scripts/constant/abis.js @@ -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"), - }, - }; - \ No newline at end of file + 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"), + }, +}; diff --git a/scripts/constant/addresses.js b/scripts/constant/addresses.js index ead09c40..cea51b29 100644 --- a/scripts/constant/addresses.js +++ b/scripts/constant/addresses.js @@ -1,11 +1,10 @@ module.exports = { - connectors: { - basic: "0xe5398f279175962E56fE4c5E0b62dc7208EF36c6", - auth: "0xd1aff9f2acf800c876c409100d6f39aea93fc3d9", - }, - core: { - connectorsV2: "0xFE2390DAD597594439f218190fC2De40f9Cf1179", - instaIndex: "0x2971AdFa57b20E5a416aE5a708A8655A9c74f723" - } - }; - \ No newline at end of file + connectors: { + basic: "0xe5398f279175962E56fE4c5E0b62dc7208EF36c6", + auth: "0xd1aff9f2acf800c876c409100d6f39aea93fc3d9", + }, + core: { + connectorsV2: "0x97b0B3A8bDeFE8cB9563a3c610019Ad10DB8aD11", + instaIndex: "0x2971AdFa57b20E5a416aE5a708A8655A9c74f723", + }, +}; diff --git a/test/liquity/liquity.abi.js b/test/liquity/liquity.abi.js new file mode 100644 index 00000000..d1171d1d --- /dev/null +++ b/test/liquity/liquity.abi.js @@ -0,0 +1,84 @@ +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)", +]; + +const STAKING_ADDRESS = "0x4f9Fbb3f1E99B56e0Fe2892e623Ed36A76Fc605d"; +const STAKING_ABI = [ + "function stake(uint _LQTYamount) external", + "function unstake(uint _LQTYamount) external", +]; + +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)", +]; + +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, +}; diff --git a/test/liquity/liquity.helpers.js b/test/liquity/liquity.helpers.js new file mode 100644 index 00000000..c400ded5 --- /dev/null +++ b/test/liquity/liquity.helpers.js @@ -0,0 +1,327 @@ +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"); + +// 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 CONNECTOR_NAME = "LIQUITY-v1-TEST"; +const LUSD_GAS_COMPENSATION = hre.ethers.utils.parseUnits("200", 18); // 200 LUSD gas compensation repaid after loan repayment +const 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: BLOCK_NUMBER +const MAX_GAS = hardhatConfig.networks.hardhat.blockGasLimit; // Maximum gas limit (12000000) + +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: CONNECTOR_NAME, + method: "open", + args: [ + depositAmount, + maxFeePercentage, + borrowAmount, + upperHint, + lowerHint, + 0, + 0, + ], + }; + const openTx = await dsa + .connect(signer) + .cast(...encodeSpells([openTroveSpell]), address, { + value: depositAmount, + }); + return await openTx.wait(); +}; + +const createDsaTrove = async ( + dsa, + signer, + hintHelpers, + sortedTroves, + 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, + hintHelpers, + sortedTroves + ); + 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); +}; + +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(BLOCK_NUMBER); + + const liquity = { + troveManager: null, + borrowerOperations: null, + stabilityPool: null, + lusdToken: null, + lqtyToken: null, + activePool: null, + priceFeed: null, + hintHelpers: null, + sortedTroves: null, + staking: null, + }; + + const masterSigner = await getMasterSigner(); + const instaConnectorsV2 = await ethers.getContractAt( + instadappAbi.core.connectorsV2, + instadappAddresses.core.connectorsV2 + ); + const connector = await deployAndEnableConnector({ + connectorName: CONNECTOR_NAME, + contractArtifact: connectV2LiquityArtifacts, + signer: masterSigner, + connectors: instaConnectorsV2, + }); + isDebug && + console.log(`${CONNECTOR_NAME} 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 + ); + isDebug && + console.log("TroveManager contract address", liquity.troveManager.address); + + liquity.borrowerOperations = new ethers.Contract( + contracts.BORROWER_OPERATIONS_ADDRESS, + contracts.BORROWER_OPERATIONS_ABI, + ethers.provider + ); + isDebug && + console.log( + "BorrowerOperations contract address", + liquity.borrowerOperations.address + ); + + liquity.stabilityPool = new ethers.Contract( + contracts.STABILITY_POOL_ADDRESS, + contracts.STABILITY_POOL_ABI, + ethers.provider + ); + isDebug && + console.log( + "StabilityPool contract address", + liquity.stabilityPool.address + ); + + liquity.lusdToken = new ethers.Contract( + contracts.LUSD_TOKEN_ADDRESS, + contracts.LUSD_TOKEN_ABI, + ethers.provider + ); + isDebug && + console.log("LusdToken contract address", liquity.lusdToken.address); + + liquity.lqtyToken = new ethers.Contract( + contracts.LQTY_TOKEN_ADDRESS, + contracts.LQTY_TOKEN_ABI, + ethers.provider + ); + isDebug && + console.log("LqtyToken contract address", liquity.lqtyToken.address); + + liquity.activePool = new ethers.Contract( + contracts.ACTIVE_POOL_ADDRESS, + contracts.ACTIVE_POOL_ABI, + ethers.provider + ); + isDebug && + console.log("ActivePool contract address", liquity.activePool.address); + + liquity.priceFeed = new ethers.Contract( + contracts.PRICE_FEED_ADDRESS, + contracts.PRICE_FEED_ABI, + ethers.provider + ); + isDebug && + console.log("PriceFeed contract address", liquity.priceFeed.address); + + liquity.hintHelpers = new ethers.Contract( + contracts.HINT_HELPERS_ADDRESS, + contracts.HINT_HELPERS_ABI, + ethers.provider + ); + isDebug && + console.log("HintHelpers contract address", liquity.hintHelpers.address); + + liquity.sortedTroves = new ethers.Contract( + contracts.SORTED_TROVES_ADDRESS, + contracts.SORTED_TROVES_ABI, + ethers.provider + ); + isDebug && + console.log("SortedTroves contract address", liquity.sortedTroves.address); + + liquity.staking = new ethers.Contract( + contracts.STAKING_ADDRESS, + contracts.STAKING_ABI, + ethers.provider + ); + isDebug && console.log("Staking contract address", liquity.staking.address); + + return liquity; +}; + +const getTroveInsertionHints = async ( + depositAmount, + borrowAmount, + hintHelpers, + sortedTroves +) => { + const nominalCR = await hintHelpers.computeNominalCR( + depositAmount, + borrowAmount + ); + + const { hintAddress, latestRandomSeed } = await hintHelpers.getApproxHint( + nominalCR, + 50, + 1298379, + { + gasLimit: MAX_GAS, + } + ); + randomSeed = latestRandomSeed; + + const { 0: upperHint, 1: lowerHint } = await sortedTroves.findInsertPosition( + nominalCR, + hintAddress, + hintAddress, + { + gasLimit: MAX_GAS, + } + ); + + return { + upperHint, + lowerHint, + }; +}; + +let randomSeed = 4223; + +const getRedemptionHints = async ( + amount, + hintHelpers, + sortedTroves, + priceFeed +) => { + const ethPrice = await priceFeed.callStatic.fetchPrice(); + const [ + firstRedemptionHint, + partialRedemptionHintNicr, + ] = await hintHelpers.getRedemptionHints(amount, ethPrice, 0); + + const { hintAddress, latestRandomSeed } = await hintHelpers.getApproxHint( + partialRedemptionHintNicr, + 50, + randomSeed, + { + gasLimit: MAX_GAS, + } + ); + randomSeed = latestRandomSeed; + + const { 0: upperHint, 1: lowerHint } = await sortedTroves.findInsertPosition( + partialRedemptionHintNicr, + hintAddress, + hintAddress, + { + gasLimit: MAX_GAS, + } + ); + + return { + partialRedemptionHintNicr, + firstRedemptionHint, + upperHint, + lowerHint, + }; +}; + +module.exports = { + deployAndConnect, + createDsaTrove, + openTroveSpell, + sendToken, + CONNECTOR_NAME, + LUSD_GAS_COMPENSATION, + BLOCK_NUMBER, + JUSTIN_SUN_ADDRESS, + LIQUIDATABLE_TROVE_ADDRESS, + MAX_GAS, + resetHardhatBlockNumber, + getTroveInsertionHints, + getRedemptionHints, +}; diff --git a/test/liquity/liquity.test.js b/test/liquity/liquity.test.js new file mode 100644 index 00000000..a7993876 --- /dev/null +++ b/test/liquity/liquity.test.js @@ -0,0 +1,1133 @@ +const hre = require("hardhat"); +const { expect } = require("chai"); + +// Instadapp deployment and testing helpers +const buildDSAv2 = require("../../scripts/buildDSAv2"); +const encodeSpells = require("../../scripts/encodeSpells.js"); + +// Liquity smart contracts +const contracts = require("./liquity.abi"); + +// Liquity helpers +const helpers = require("./liquity.helpers"); + +// Instadapp uses a fake address to represent native ETH +const { eth_addr: ETH_ADDRESS } = require("../../scripts/constant/constant"); + +describe.only("Liquity", () => { + const { waffle, ethers } = hre; + const { provider } = waffle; + + // Waffle test account 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 (holds 1000 ETH) + const wallet = provider.getWallets()[0]; + let dsa = null; + let liquity = null; + + before(async () => { + liquity = await helpers.deployAndConnect(contracts, true); + expect(liquity.troveManager.address).to.exist; + expect(liquity.borrowerOperations.address).to.exist; + expect(liquity.stabilityPool.address).to.exist; + expect(liquity.lusdToken.address).to.exist; + expect(liquity.lqtyToken.address).to.exist; + expect(liquity.activePool.address).to.exist; + expect(liquity.priceFeed.address).to.exist; + expect(liquity.hintHelpers.address).to.exist; + expect(liquity.sortedTroves.address).to.exist; + expect(liquity.staking.address).to.exist; + }); + + beforeEach(async () => { + // Build a new DSA before each test so we start each test from the same default state + dsa = await buildDSAv2(wallet.address); + expect(dsa.address).to.exist; + }); + + describe("Main (Connector)", () => { + describe("Trove", () => { + it("opens a Trove", async () => { + const depositAmount = ethers.utils.parseEther("5"); // 5 ETH + const borrowAmount = ethers.utils.parseUnits("2000", 18); // 2000 LUSD + const maxFeePercentage = ethers.utils.parseUnits("0.5", 18); // 0.5% max fee + const upperHint = ethers.constants.AddressZero; + const lowerHint = ethers.constants.AddressZero; + const originalUserBalance = await ethers.provider.getBalance( + wallet.address + ); + const originalDsaBalance = await ethers.provider.getBalance( + dsa.address + ); + + const openTroveSpell = { + connector: helpers.CONNECTOR_NAME, + method: "open", + args: [ + depositAmount, + maxFeePercentage, + borrowAmount, + upperHint, + lowerHint, + 0, + 0, + ], + }; + + const spells = [openTroveSpell]; + const tx = await dsa + .connect(wallet) + .cast(...encodeSpells(spells), wallet.address, { + value: depositAmount, + }); + + await tx.wait(); + + const userBalance = await ethers.provider.getBalance(wallet.address); + const dsaEthBalance = await ethers.provider.getBalance(dsa.address); + const dsaLusdBalance = await liquity.lusdToken.balanceOf(dsa.address); + const troveDebt = await liquity.troveManager.getTroveDebt(dsa.address); + const troveCollateral = await liquity.troveManager.getTroveColl( + dsa.address + ); + + expect(userBalance).lt( + originalUserBalance, + "User should have less Ether after opening Trove" + ); + + expect(dsaEthBalance).to.eq( + originalDsaBalance, + "User's DSA account Ether should not change after borrowing" + ); + + expect( + dsaLusdBalance, + "DSA account should now hold the amount the user tried to borrow" + ).to.eq(borrowAmount); + + expect(troveDebt).to.gt( + borrowAmount, + "Trove debt should equal the borrowed amount plus fee" + ); + + expect(troveCollateral).to.eq( + depositAmount, + "Trove collateral should equal the deposited amount" + ); + }); + + it("opens a Trove using ETH collected from a previous spell", async () => { + const depositAmount = ethers.utils.parseEther("5"); // 5 ETH + const borrowAmount = ethers.utils.parseUnits("2000", 18); // 2000 LUSD + const maxFeePercentage = ethers.utils.parseUnits("0.5", 18); // 0.5% max fee + const upperHint = ethers.constants.AddressZero; + const lowerHint = ethers.constants.AddressZero; + const originalUserBalance = await ethers.provider.getBalance( + wallet.address + ); + const originalDsaBalance = await ethers.provider.getBalance( + dsa.address + ); + const depositId = 1; // Choose an ID to store and retrieve the deopsited ETH + + const depositEthSpell = { + connector: "Basic-v1", + method: "deposit", + args: [ETH_ADDRESS, depositAmount, 0, depositId], + }; + + const openTroveSpell = { + connector: helpers.CONNECTOR_NAME, + method: "open", + args: [ + 0, // When pulling ETH from a previous spell it doesn't matter what deposit value we put in this param + maxFeePercentage, + borrowAmount, + upperHint, + lowerHint, + depositId, + 0, + ], + }; + + const spells = [depositEthSpell, openTroveSpell]; + const tx = await dsa + .connect(wallet) + .cast(...encodeSpells(spells), wallet.address, { + value: depositAmount, + }); + + await tx.wait(); + const userBalance = await ethers.provider.getBalance(wallet.address); + const dsaEthBalance = await ethers.provider.getBalance(dsa.address); + const dsaLusdBalance = await liquity.lusdToken.balanceOf(dsa.address); + const troveDebt = await liquity.troveManager.getTroveDebt(dsa.address); + const troveCollateral = await liquity.troveManager.getTroveColl( + dsa.address + ); + + expect(userBalance).lt( + originalUserBalance, + "User should have less Ether" + ); + + expect(dsaEthBalance).to.eq( + originalDsaBalance, + "DSA balance should not change" + ); + + expect( + dsaLusdBalance, + "DSA account should now hold the amount the user tried to borrow" + ).to.eq(borrowAmount); + + expect(troveDebt).to.gt( + borrowAmount, + "Trove debt should equal the borrowed amount plus fee" + ); + + expect(troveCollateral).to.eq( + depositAmount, + "Trove collateral should equal the deposited amount" + ); + }); + + it("opens a Trove and stores the debt for other spells to use", async () => { + const depositAmount = ethers.utils.parseEther("5"); // 5 ETH + const borrowAmount = ethers.utils.parseUnits("2000", 18); // 2000 LUSD + const maxFeePercentage = ethers.utils.parseUnits("0.5", 18); // 0.5% max fee + const upperHint = ethers.constants.AddressZero; + const lowerHint = ethers.constants.AddressZero; + const originalUserBalance = await ethers.provider.getBalance( + wallet.address + ); + const originalDsaBalance = await ethers.provider.getBalance( + dsa.address + ); + const borrowId = 1; + + const openTroveSpell = { + connector: helpers.CONNECTOR_NAME, + method: "open", + args: [ + depositAmount, + maxFeePercentage, + borrowAmount, + upperHint, + lowerHint, + 0, + borrowId, + ], + }; + + const withdrawLusdSpell = { + connector: "Basic-v1", + method: "withdraw", + args: [ + contracts.LUSD_TOKEN_ADDRESS, + 0, // amount comes from the previous spell's setId + dsa.address, + borrowId, + 0, + ], + }; + + const spells = [openTroveSpell, withdrawLusdSpell]; + const tx = await dsa + .connect(wallet) + .cast(...encodeSpells(spells), wallet.address, { + value: depositAmount, + }); + + await tx.wait(); + + const userBalance = await ethers.provider.getBalance(wallet.address); + + expect(userBalance).lt( + originalUserBalance, + "User should have less Ether after opening Trove" + ); + + const dsaEthBalance = await ethers.provider.getBalance(dsa.address); + const dsaLusdBalance = await liquity.lusdToken.balanceOf(dsa.address); + const troveDebt = await liquity.troveManager.getTroveDebt(dsa.address); + const troveCollateral = await liquity.troveManager.getTroveColl( + dsa.address + ); + + expect(dsaEthBalance).to.eq( + originalDsaBalance, + "User's DSA account Ether should not change after borrowing" + ); + + expect( + dsaLusdBalance, + "DSA account should now hold the amount the user tried to borrow" + ).to.eq(borrowAmount); + + expect(troveDebt).to.gt( + borrowAmount, + "Trove debt should equal the borrowed amount plus fee" + ); + + expect(troveCollateral).to.eq( + depositAmount, + "Trove collateral should equal the deposited amount" + ); + }); + + it("closes a Trove", async () => { + const depositAmount = ethers.utils.parseEther("5"); + const borrowAmount = ethers.utils.parseUnits("2000", 18); + await helpers.createDsaTrove( + dsa, + wallet, + liquity.hintHelpers, + liquity.sortedTroves, + depositAmount, + borrowAmount + ); + + const originalTroveDebt = await liquity.troveManager.getTroveDebt( + dsa.address + ); + + const originalTroveCollateral = await liquity.troveManager.getTroveColl( + dsa.address + ); + + // Send DSA account enough LUSD (from Stability Pool) to close their Trove + const extraLusdRequiredToCloseTrove = originalTroveDebt.sub( + borrowAmount + ); + + await helpers.sendToken( + liquity.lusdToken, + extraLusdRequiredToCloseTrove, + contracts.STABILITY_POOL_ADDRESS, + dsa.address + ); + + const originalDsaLusdBalance = await liquity.lusdToken.balanceOf( + dsa.address + ); + + expect( + originalDsaLusdBalance, + "DSA account should now hold the LUSD amount required to pay off the Trove debt" + ).to.eq(originalTroveDebt); + + const closeTroveSpell = { + connector: helpers.CONNECTOR_NAME, + method: "close", + args: [0], + }; + + const closeTx = await dsa + .connect(wallet) + .cast(...encodeSpells([closeTroveSpell]), wallet.address); + + await closeTx.wait(); + const dsaEthBalance = await ethers.provider.getBalance(dsa.address); + const dsaLusdBalance = await liquity.lusdToken.balanceOf(dsa.address); + const troveDebt = await liquity.troveManager.getTroveDebt(dsa.address); + const troveCollateral = await liquity.troveManager.getTroveColl( + dsa.address + ); + + expect(troveDebt, "Trove debt should equal 0 after close").to.eq(0); + + expect( + troveCollateral, + "Trove collateral should equal 0 after close" + ).to.eq(0); + + expect( + dsaEthBalance, + "DSA account should now hold the Trove's ETH collateral" + ).to.eq(originalTroveCollateral); + + expect( + dsaLusdBalance, + "DSA account should now hold the gas compensation amount of LUSD as it paid off the Trove debt" + ).to.eq(helpers.LUSD_GAS_COMPENSATION); + }); + + it("closes a Trove using LUSD obtained from a previous spell", async () => { + await helpers.createDsaTrove( + dsa, + wallet, + liquity.hintHelpers, + liquity.sortedTroves + ); + + const originalTroveDebt = await liquity.troveManager.getTroveDebt( + dsa.address + ); + const originalTroveCollateral = await liquity.troveManager.getTroveColl( + dsa.address + ); + + // Send user enough LUSD to repay the loan, we'll use a deposit and withdraw spell to obtain it + await helpers.sendToken( + liquity.lusdToken, + originalTroveDebt, + contracts.STABILITY_POOL_ADDRESS, + wallet.address + ); + + // Allow DSA to spend user's LUSD + await liquity.lusdToken + .connect(wallet) + .approve(dsa.address, originalTroveDebt); + + const lusdDepositId = 1; + + // Simulate a spell which would have pulled LUSD from somewhere (e.g. AAVE) into InstaMemory + // In this case we're simply running a deposit spell from the user's EOA + const depositLusdSpell = { + connector: "Basic-v1", + method: "deposit", + args: [ + contracts.LUSD_TOKEN_ADDRESS, + originalTroveDebt, + 0, + lusdDepositId, + ], + }; + // Withdraw the obtained LUSD into DSA account + const withdrawLusdSpell = { + connector: "Basic-v1", + method: "withdraw", + args: [ + contracts.LUSD_TOKEN_ADDRESS, + 0, // amount comes from the previous spell's setId + dsa.address, + lusdDepositId, + 0, + ], + }; + + const closeTroveSpell = { + connector: helpers.CONNECTOR_NAME, + method: "close", + args: [0], + }; + + const closeTx = await dsa + .connect(wallet) + .cast( + ...encodeSpells([ + depositLusdSpell, + withdrawLusdSpell, + closeTroveSpell, + ]), + wallet.address + ); + + await closeTx.wait(); + const dsaEthBalance = await ethers.provider.getBalance(dsa.address); + const troveDebt = await liquity.troveManager.getTroveDebt(dsa.address); + const troveCollateral = await liquity.troveManager.getTroveColl( + dsa.address + ); + + expect(troveDebt, "Trove debt should equal 0 after close").to.eq(0); + + expect( + troveCollateral, + "Trove collateral should equal 0 after close" + ).to.eq(0); + + expect( + dsaEthBalance, + "DSA account should now hold the Trove's ETH collateral" + ).to.eq(originalTroveCollateral); + }); + + it("closes a Trove and stores the released collateral for other spells to use", async () => { + const depositAmount = ethers.utils.parseEther("5"); + const borrowAmount = ethers.utils.parseUnits("2000", 18); + await helpers.createDsaTrove( + dsa, + wallet, + liquity.hintHelpers, + liquity.sortedTroves, + depositAmount, + borrowAmount + ); + + const originalTroveDebt = await liquity.troveManager.getTroveDebt( + dsa.address + ); + const originalTroveCollateral = await liquity.troveManager.getTroveColl( + dsa.address + ); + + // Send DSA account enough LUSD (from Stability Pool) to close their Trove + const extraLusdRequiredToCloseTrove = originalTroveDebt.sub( + borrowAmount + ); + await helpers.sendToken( + liquity.lusdToken, + extraLusdRequiredToCloseTrove, + contracts.STABILITY_POOL_ADDRESS, + dsa.address + ); + const originalDsaLusdBalance = await liquity.lusdToken.balanceOf( + dsa.address + ); + + expect( + originalDsaLusdBalance, + "DSA account should now hold the LUSD amount required to pay off the Trove debt" + ).to.eq(originalTroveDebt); + + const collateralWithdrawId = 1; + + const closeTroveSpell = { + connector: helpers.CONNECTOR_NAME, + method: "close", + args: [collateralWithdrawId], + }; + + const withdrawEthSpell = { + connector: "Basic-v1", + method: "withdraw", + args: [ + ETH_ADDRESS, + 0, // amount comes from the previous spell's setId + dsa.address, + collateralWithdrawId, + 0, + ], + }; + + const closeTx = await dsa + .connect(wallet) + .cast( + ...encodeSpells([closeTroveSpell, withdrawEthSpell]), + wallet.address + ); + + await closeTx.wait(); + const dsaEthBalance = await ethers.provider.getBalance(dsa.address); + const dsaLusdBalance = await liquity.lusdToken.balanceOf(dsa.address); + const troveDebt = await liquity.troveManager.getTroveDebt(dsa.address); + const troveCollateral = await liquity.troveManager.getTroveColl( + dsa.address + ); + + expect(troveDebt, "Trove debt should equal 0 after close").to.eq(0); + + expect( + troveCollateral, + "Trove collateral should equal 0 after close" + ).to.eq(0); + + expect( + dsaEthBalance, + "DSA account should now hold the Trove's ETH collateral" + ).to.eq(originalTroveCollateral); + + expect( + dsaLusdBalance, + "DSA account should now hold the gas compensation amount of LUSD as it paid off the Trove debt" + ).to.eq(helpers.LUSD_GAS_COMPENSATION); + }); + + it("deposits ETH into a Trove", async () => { + await helpers.createDsaTrove( + dsa, + wallet, + liquity.hintHelpers, + liquity.sortedTroves + ); + + const originalTroveCollateral = await liquity.troveManager.getTroveColl( + dsa.address + ); + + const topupAmount = ethers.utils.parseEther("1"); + const upperHint = ethers.constants.AddressZero; + const lowerHint = ethers.constants.AddressZero; + const depositEthSpell = { + connector: helpers.CONNECTOR_NAME, + method: "deposit", + args: [topupAmount, upperHint, lowerHint, 0], + }; + + const depositTx = await dsa + .connect(wallet) + .cast(...encodeSpells([depositEthSpell]), wallet.address, { + value: topupAmount, + }); + + await depositTx.wait(); + + const troveCollateral = await liquity.troveManager.getTroveColl( + dsa.address + ); + + const expectedTroveCollateral = originalTroveCollateral.add( + topupAmount + ); + + expect( + troveCollateral, + `Trove collateral should have increased by ${topupAmount} ETH` + ).to.eq(expectedTroveCollateral); + }); + + it("withdraws ETH from a Trove", async () => { + await helpers.createDsaTrove( + dsa, + wallet, + liquity.hintHelpers, + liquity.sortedTroves + ); + + const originalTroveCollateral = await liquity.troveManager.getTroveColl( + dsa.address + ); + const withdrawAmount = ethers.utils.parseEther("1"); + const upperHint = ethers.constants.AddressZero; + const lowerHint = ethers.constants.AddressZero; + const withdrawEthSpell = { + connector: helpers.CONNECTOR_NAME, + method: "withdraw", + args: [withdrawAmount, upperHint, lowerHint, 0], + }; + + const withdrawTx = await dsa + .connect(wallet) + .cast(...encodeSpells([withdrawEthSpell]), wallet.address); + + await withdrawTx.wait(); + const troveCollateral = await liquity.troveManager.getTroveColl( + dsa.address + ); + const expectedTroveCollateral = originalTroveCollateral.sub( + withdrawAmount + ); + + expect( + troveCollateral, + `Trove collateral should have decreased by ${withdrawAmount} ETH` + ).to.eq(expectedTroveCollateral); + }); + + it("borrows LUSD from a Trove", async () => { + await helpers.createDsaTrove( + dsa, + wallet, + liquity.hintHelpers, + liquity.sortedTroves + ); + + const originalTroveDebt = await liquity.troveManager.getTroveDebt( + dsa.address + ); + const borrowAmount = ethers.utils.parseUnits("1000"); // 1000 LUSD + const upperHint = ethers.constants.AddressZero; + const lowerHint = ethers.constants.AddressZero; + const maxFeePercentage = ethers.utils.parseUnits("0.5", 18); // 0.5% max fee + const borrowSpell = { + connector: helpers.CONNECTOR_NAME, + method: "borrow", + args: [maxFeePercentage, borrowAmount, upperHint, lowerHint, 0], + }; + + const borrowTx = await dsa + .connect(wallet) + .cast(...encodeSpells([borrowSpell]), wallet.address); + + await borrowTx.wait(); + const troveDebt = await liquity.troveManager.getTroveDebt(dsa.address); + const expectedTroveDebt = originalTroveDebt.add(borrowAmount); + + expect( + troveDebt, + `Trove debt should have increased by at least ${borrowAmount} ETH` + ).to.gte(expectedTroveDebt); + }); + + it("repays LUSD to a Trove", async () => { + await helpers.createDsaTrove( + dsa, + wallet, + liquity.hintHelpers, + liquity.sortedTroves + ); + + const originalTroveDebt = await liquity.troveManager.getTroveDebt( + dsa.address + ); + const repayAmount = ethers.utils.parseUnits("100"); // 100 LUSD + const upperHint = ethers.constants.AddressZero; + const lowerHint = ethers.constants.AddressZero; + const borrowSpell = { + connector: helpers.CONNECTOR_NAME, + method: "repay", + args: [repayAmount, upperHint, lowerHint, 0], + }; + + const repayTx = await dsa + .connect(wallet) + .cast(...encodeSpells([borrowSpell]), wallet.address, { + value: repayAmount, + }); + + await repayTx.wait(); + const troveDebt = await liquity.troveManager.getTroveDebt(dsa.address); + const expectedTroveDebt = originalTroveDebt.sub(repayAmount); + + expect( + troveDebt, + `Trove debt should have decreased by ${repayAmount} ETH` + ).to.eq(expectedTroveDebt); + }); + + it("adjusts a Trove: deposit ETH and borrow LUSD", async () => { + await helpers.createDsaTrove( + dsa, + wallet, + liquity.hintHelpers, + liquity.sortedTroves + ); + + const originalTroveCollateral = await liquity.troveManager.getTroveColl( + dsa.address + ); + const originalTroveDebt = await liquity.troveManager.getTroveDebt( + dsa.address + ); + const depositAmount = ethers.utils.parseEther("1"); // 1 ETH + const borrowAmount = ethers.utils.parseUnits("500"); // 500 LUSD + const withdrawAmount = 0; + const repayAmount = 0; + const upperHint = ethers.constants.AddressZero; + const lowerHint = ethers.constants.AddressZero; + const maxFeePercentage = ethers.utils.parseUnits("0.5", 18); // 0.5% max fee + + const adjustSpell = { + connector: helpers.CONNECTOR_NAME, + method: "adjust", + args: [ + maxFeePercentage, + withdrawAmount, + depositAmount, + borrowAmount, + repayAmount, + upperHint, + lowerHint, + 0, + 0, + 0, + 0, + ], + }; + + const adjustTx = await dsa + .connect(wallet) + .cast(...encodeSpells([adjustSpell]), wallet.address, { + value: depositAmount, + gasLimit: helpers.MAX_GAS, + }); + + await adjustTx.wait(); + const troveCollateral = await liquity.troveManager.getTroveColl( + dsa.address + ); + const troveDebt = await liquity.troveManager.getTroveDebt(dsa.address); + const expectedTroveColl = originalTroveCollateral.add(depositAmount); + const expectedTroveDebt = originalTroveDebt.add(borrowAmount); + + expect( + troveCollateral, + `Trove collateral should have increased by ${depositAmount} ETH` + ).to.eq(expectedTroveColl); + + expect( + troveDebt, + `Trove debt should have increased by at least ${borrowAmount} ETH` + ).to.gte(expectedTroveDebt); + }); + + it("adjusts a Trove: withdraw ETH and repay LUSD", async () => { + await helpers.createDsaTrove( + dsa, + wallet, + liquity.hintHelpers, + liquity.sortedTroves + ); + + const originalTroveCollateral = await liquity.troveManager.getTroveColl( + dsa.address + ); + const originalTroveDebt = await liquity.troveManager.getTroveDebt( + dsa.address + ); + const depositAmount = 0; + const borrowAmount = 0; + const withdrawAmount = ethers.utils.parseEther("1"); // 1 ETH; + const repayAmount = ethers.utils.parseUnits("500"); // 500 LUSD; + const { upperHint, lowerHint } = await helpers.getTroveInsertionHints( + originalTroveCollateral.sub(withdrawAmount), + originalTroveDebt.sub(repayAmount), + liquity.hintHelpers, + liquity.sortedTroves + ); + const maxFeePercentage = ethers.utils.parseUnits("0.5", 18); // 0.5% max fee + + const adjustSpell = { + connector: helpers.CONNECTOR_NAME, + method: "adjust", + args: [ + maxFeePercentage, + withdrawAmount, + depositAmount, + borrowAmount, + repayAmount, + upperHint, + lowerHint, + 0, + 0, + 0, + 0, + ], + }; + + const adjustTx = await dsa + .connect(wallet) + .cast(...encodeSpells([adjustSpell]), wallet.address, { + value: depositAmount, + gasLimit: helpers.MAX_GAS, + }); + + await adjustTx.wait(); + const troveCollateral = await liquity.troveManager.getTroveColl( + dsa.address + ); + const troveDebt = await liquity.troveManager.getTroveDebt(dsa.address); + const expectedTroveColl = originalTroveCollateral.sub(withdrawAmount); + const expectedTroveDebt = originalTroveDebt.sub(repayAmount); + + expect( + troveCollateral, + `Trove collateral should have increased by ${depositAmount} ETH` + ).to.eq(expectedTroveColl); + + expect( + troveDebt, + `Trove debt should have increased by at least ${borrowAmount} ETH` + ).to.gte(expectedTroveDebt); + }); + + it("claims collateral from a redeemed Trove", async () => { + // Create a low collateralized Trove + const depositAmount = ethers.utils.parseEther("1.5"); + const borrowAmount = ethers.utils.parseUnits("2500", 18); + + await helpers.createDsaTrove( + dsa, + wallet, + liquity.hintHelpers, + liquity.sortedTroves, + depositAmount, + borrowAmount + ); + + // Redeem lots of LUSD to cause the Trove to become redeemed + const redeemAmount = ethers.utils.parseUnits("10000000", 18); + await helpers.sendToken( + liquity.lusdToken, + redeemAmount, + contracts.STABILITY_POOL_ADDRESS, + wallet.address + ); + const { + partialRedemptionHintNicr, + firstRedemptionHint, + upperHint, + lowerHint, + } = await helpers.getRedemptionHints( + redeemAmount, + liquity.hintHelpers, + liquity.sortedTroves, + liquity.priceFeed + ); + const maxFeePercentage = ethers.utils.parseUnits("0.5", 18); // 0.5% max fee + + await liquity.troveManager + .connect(wallet) + .redeemCollateral( + redeemAmount, + firstRedemptionHint, + upperHint, + lowerHint, + partialRedemptionHintNicr, + 0, + maxFeePercentage, + { + gasLimit: helpers.MAX_GAS, // permit max gas + } + ); + + const ethBalanceBefore = await ethers.provider.getBalance(dsa.address); + + // Claim the remaining collateral from the redeemed Trove + const claimCollateralFromRedemptionSpell = { + connector: helpers.CONNECTOR_NAME, + method: "claimCollateralFromRedemption", + args: [], + }; + + const claimTx = await dsa + .connect(wallet) + .cast( + ...encodeSpells([claimCollateralFromRedemptionSpell]), + wallet.address + ); + + await claimTx.wait(); + + const ethBalanceAfter = await ethers.provider.getBalance(dsa.address); + + const expectedRemainingCollateral = "527014573774047160"; // ~0.25 ETH based on this mainnet fork's blockNumber + expect(ethBalanceAfter).to.be.gt(ethBalanceBefore); + expect(ethBalanceAfter).to.eq(expectedRemainingCollateral); + }); + }); + + describe("Stability Pool", () => { + it("deposits into Stability Pool", async () => { + const amount = ethers.utils.parseUnits("100", 18); + const frontendTag = ethers.constants.AddressZero; + + await helpers.sendToken( + liquity.lusdToken, + amount, + contracts.STABILITY_POOL_ADDRESS, + dsa.address + ); + + const stabilityDepositSpell = { + connector: helpers.CONNECTOR_NAME, + method: "stabilityDeposit", + args: [amount, frontendTag, 0], + }; + + const depositTx = await dsa + .connect(wallet) + .cast(...encodeSpells([stabilityDepositSpell]), wallet.address); + + await depositTx.wait(); + const depositedAmount = await liquity.stabilityPool.getCompoundedLUSDDeposit( + dsa.address + ); + expect(depositedAmount).to.eq(amount); + }); + + it("withdraws from Stability Pool", async () => { + // The current block number has liquidatable Troves. + // Remove them otherwise Stability Pool withdrawals are disabled + await liquity.troveManager.connect(wallet).liquidateTroves(90, { + gasLimit: helpers.MAX_GAS, + }); + const amount = ethers.utils.parseUnits("100", 18); + const frontendTag = ethers.constants.AddressZero; + + await helpers.sendToken( + liquity.lusdToken, + amount, + contracts.STABILITY_POOL_ADDRESS, + dsa.address + ); + + const stabilityDepositSpell = { + connector: helpers.CONNECTOR_NAME, + method: "stabilityDeposit", + args: [amount, frontendTag, 0], + }; + + // Withdraw half of the deposit + const stabilitWithdrawSpell = { + connector: helpers.CONNECTOR_NAME, + method: "stabilityWithdraw", + args: [amount.div(2), 0], + }; + const spells = [stabilityDepositSpell, stabilitWithdrawSpell]; + + const spellsTx = await dsa + .connect(wallet) + .cast(...encodeSpells(spells), wallet.address); + + await spellsTx.wait(); + + const depositedAmount = await liquity.stabilityPool.getCompoundedLUSDDeposit( + dsa.address + ); + const dsaLusdBalance = await liquity.lusdToken.balanceOf(dsa.address); + + expect(depositedAmount).to.eq(amount.div(2)); + expect(dsaLusdBalance).to.eq(amount.div(2)); + }); + + it("moves ETH gain from Stability Pool to Trove", async () => { + // Start this test from fresh so that we definitely have a liquidatable Trove within this block + liqiuty = await helpers.deployAndConnect(contracts); + + // Create a DSA owned Trove to capture ETH liquidation gains + await helpers.createDsaTrove( + dsa, + wallet, + liquity.hintHelpers, + liquity.sortedTroves + ); + const troveCollateralBefore = await liquity.troveManager.getTroveColl( + dsa.address + ); + + // Create a Stability Deposit using the Trove's borrowed LUSD + const amount = ethers.utils.parseUnits("100", 18); + const frontendTag = ethers.constants.AddressZero; + const stabilityDepositSpell = { + connector: helpers.CONNECTOR_NAME, + method: "stabilityDeposit", + args: [amount, frontendTag, 0], + }; + + const depositTx = await dsa + .connect(wallet) + .cast(...encodeSpells([stabilityDepositSpell]), wallet.address); + + await depositTx.wait(); + + // Liquidate a Trove to create an ETH gain for the new DSA Trove + await liquity.troveManager + .connect(wallet) + .liquidate(helpers.LIQUIDATABLE_TROVE_ADDRESS, { + gasLimit: helpers.MAX_GAS, // permit max gas + }); + + const ethGainFromLiquidation = await liquity.stabilityPool.getDepositorETHGain( + dsa.address + ); + + // Move ETH gain to Trove + const moveEthGainSpell = { + connector: helpers.CONNECTOR_NAME, + method: "stabilityMoveEthGainToTrove", + args: [ethers.constants.AddressZero, ethers.constants.AddressZero], + }; + + const moveEthGainTx = await dsa + .connect(wallet) + .cast(...encodeSpells([moveEthGainSpell]), wallet.address); + + await moveEthGainTx.wait(); + + const ethGainAfterMove = await liquity.stabilityPool.getDepositorETHGain( + dsa.address + ); + const troveCollateral = await liquity.troveManager.getTroveColl( + dsa.address + ); + const expectedTroveCollateral = troveCollateralBefore.add( + ethGainFromLiquidation + ); + + expect(ethGainAfterMove).to.eq(0); + expect(troveCollateral).to.eq(expectedTroveCollateral); + }); + }); + + describe("Staking", () => { + it("stakes LQTY", async () => { + const totalStakingBalanceBefore = await liquity.lqtyToken.balanceOf( + contracts.STAKING_ADDRESS + ); + + const amount = ethers.utils.parseUnits("1", 18); + await helpers.sendToken( + liquity.lqtyToken, + amount, + helpers.JUSTIN_SUN_ADDRESS, + dsa.address + ); + + const stakeSpell = { + connector: helpers.CONNECTOR_NAME, + method: "stake", + args: [amount, 0], + }; + + const stakeTx = await dsa + .connect(wallet) + .cast(...encodeSpells([stakeSpell]), wallet.address); + + await stakeTx.wait(); + + const lqtyBalance = await liquity.lqtyToken.balanceOf(dsa.address); + expect(lqtyBalance).to.eq(0); + + const totalStakingBalance = await liquity.lqtyToken.balanceOf( + contracts.STAKING_ADDRESS + ); + expect(totalStakingBalance).to.eq( + totalStakingBalanceBefore.add(amount) + ); + }); + + it("unstakes LQTY", async () => { + const amount = ethers.utils.parseUnits("1", 18); + await helpers.sendToken( + liquity.lqtyToken, + amount, + helpers.JUSTIN_SUN_ADDRESS, + dsa.address + ); + + const stakeSpell = { + connector: helpers.CONNECTOR_NAME, + method: "stake", + args: [amount, 0], + }; + + const stakeTx = await dsa + .connect(wallet) + .cast(...encodeSpells([stakeSpell]), wallet.address); + + await stakeTx.wait(); + + const totalStakingBalanceBefore = await liquity.lqtyToken.balanceOf( + contracts.STAKING_ADDRESS + ); + + const unstakeSpell = { + connector: helpers.CONNECTOR_NAME, + method: "unstake", + args: [amount, 0], + }; + + const unstakeTx = await dsa + .connect(wallet) + .cast(...encodeSpells([unstakeSpell]), wallet.address); + + await unstakeTx.wait(); + + const lqtyBalance = await liquity.lqtyToken.balanceOf(dsa.address); + expect(lqtyBalance).to.eq(amount); + + const totalStakingBalance = await liquity.lqtyToken.balanceOf( + contracts.STAKING_ADDRESS + ); + expect(totalStakingBalance).to.eq( + totalStakingBalanceBefore.sub(amount) + ); + }); + }); + }); +}); + +// TODO add set of tests to verify log return values are generated correctly From ec8fd393a158aa602af4e9ba2faef32c94dfc1c4 Mon Sep 17 00:00:00 2001 From: Edward Mulraney Date: Wed, 2 Jun 2021 11:19:05 +0100 Subject: [PATCH 02/19] add recommended solidity version --- .../mainnet/connectors/liquity/events.sol | 4 ++-- .../mainnet/connectors/liquity/helpers.sol | 4 ++-- .../mainnet/connectors/liquity/interface.sol | 2 +- contracts/mainnet/connectors/liquity/main.sol | 2 +- hardhat.config.js | 20 +++++++++---------- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/contracts/mainnet/connectors/liquity/events.sol b/contracts/mainnet/connectors/liquity/events.sol index c7d1dcc8..ce191ddd 100644 --- a/contracts/mainnet/connectors/liquity/events.sol +++ b/contracts/mainnet/connectors/liquity/events.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.7.0; +pragma solidity ^0.7.6; contract Events { @@ -39,4 +39,4 @@ contract Events { event LogStake(address indexed borrower, uint amount, uint getId); event LogUnstake(address indexed borrower, uint amount, uint setId); event LogClaimGains(address indexed borrower); -} \ No newline at end of file +} diff --git a/contracts/mainnet/connectors/liquity/helpers.sol b/contracts/mainnet/connectors/liquity/helpers.sol index 1fb15204..16048baf 100644 --- a/contracts/mainnet/connectors/liquity/helpers.sol +++ b/contracts/mainnet/connectors/liquity/helpers.sol @@ -1,6 +1,6 @@ -pragma solidity ^0.7.0; +pragma solidity ^0.7.6; import { DSMath } from "../../common/math.sol"; import { Basic } from "../../common/basic.sol"; -abstract contract Helpers is DSMath, Basic {} \ No newline at end of file +abstract contract Helpers is DSMath, Basic {} diff --git a/contracts/mainnet/connectors/liquity/interface.sol b/contracts/mainnet/connectors/liquity/interface.sol index 80782912..53dabea3 100644 --- a/contracts/mainnet/connectors/liquity/interface.sol +++ b/contracts/mainnet/connectors/liquity/interface.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.7.0; +pragma solidity ^0.7.6; interface BorrowerOperationsLike { function openTrove( diff --git a/contracts/mainnet/connectors/liquity/main.sol b/contracts/mainnet/connectors/liquity/main.sol index 948b6450..56db5302 100644 --- a/contracts/mainnet/connectors/liquity/main.sol +++ b/contracts/mainnet/connectors/liquity/main.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.7.0; +pragma solidity ^0.7.6; /** * @title Liquity. diff --git a/hardhat.config.js b/hardhat.config.js index 6c1f8bb2..c7b95d33 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -2,7 +2,7 @@ require("@nomiclabs/hardhat-waffle"); require("@nomiclabs/hardhat-ethers"); require("@tenderly/hardhat-tenderly"); require("@nomiclabs/hardhat-etherscan"); -require("@nomiclabs/hardhat-web3"); +require("@nomiclabs/hardhat-web3") require("hardhat-deploy"); require("hardhat-deploy-ethers"); require("dotenv").config(); @@ -19,16 +19,16 @@ module.exports = { solidity: { compilers: [ { - version: "0.7.6", + version: "0.7.6" }, { - version: "0.6.0", + version: "0.6.0" }, { - version: "0.6.2", + version: "0.6.2" }, { - version: "0.6.5", + version: "0.6.5" }, ], }, @@ -42,27 +42,27 @@ module.exports = { url: `https://eth-mainnet.alchemyapi.io/v2/${ALCHEMY_ID}`, accounts: [`0x${PRIVATE_KEY}`], timeout: 150000, - gasPrice: parseInt(utils.parseUnits("132", "gwei")), + gasPrice: parseInt(utils.parseUnits("132", "gwei")) }, hardhat: { forking: { 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")), + gasPrice: parseInt(utils.parseUnits("1", "gwei")) }, }, etherscan: { - apiKey: process.env.ETHERSCAN_API_KEY, + apiKey: process.env.ETHERSCAN_API_KEY }, tenderly: { project: process.env.TENDERLY_PROJECT, - username: process.env.TENDERLY_USERNAME, + username: process.env.TENDERLY_USERNAME }, }; From 97db94fe595330743385f26c30e15ab28cc18063 Mon Sep 17 00:00:00 2001 From: Edward Mulraney Date: Fri, 4 Jun 2021 12:18:16 +0100 Subject: [PATCH 03/19] add test for instadapp event return values. address PR comments: - add setUints to all applicable functions - use uint256 rather than uint for event data --- .../mainnet/connectors/liquity/events.sol | 6 +- .../mainnet/connectors/liquity/interface.sol | 7 + contracts/mainnet/connectors/liquity/main.sol | 103 +- .../{liquity.abi.js => liquity.contracts.js} | 7 + test/liquity/liquity.helpers.js | 67 +- test/liquity/liquity.test.js | 2640 +++++++++++------ 6 files changed, 1844 insertions(+), 986 deletions(-) rename test/liquity/{liquity.abi.js => liquity.contracts.js} (94%) diff --git a/contracts/mainnet/connectors/liquity/events.sol b/contracts/mainnet/connectors/liquity/events.sol index ce191ddd..196df9ee 100644 --- a/contracts/mainnet/connectors/liquity/events.sol +++ b/contracts/mainnet/connectors/liquity/events.sol @@ -28,15 +28,15 @@ contract Events { uint getRepayId, uint setBorrowId ); - event LogClaimCollateralFromRedemption(address indexed borrower); + event LogClaimCollateralFromRedemption(address indexed borrower, uint amount, uint setId); /* Stability Pool */ event LogStabilityDeposit(address indexed borrower, uint amount, address frontendTag, uint getId); event LogStabilityWithdraw(address indexed borrower, uint amount, uint setId); - event LogStabilityMoveEthGainToTrove(address indexed borrower); + event LogStabilityMoveEthGainToTrove(address indexed borrower, uint amount); /* Staking */ event LogStake(address indexed borrower, uint amount, uint getId); event LogUnstake(address indexed borrower, uint amount, uint setId); - event LogClaimGains(address indexed borrower); + event LogClaimStakingGains(address indexed borrower, uint ethAmount, uint lusdAmount); } diff --git a/contracts/mainnet/connectors/liquity/interface.sol b/contracts/mainnet/connectors/liquity/interface.sol index 53dabea3..72e91e98 100644 --- a/contracts/mainnet/connectors/liquity/interface.sol +++ b/contracts/mainnet/connectors/liquity/interface.sol @@ -52,9 +52,16 @@ 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); } 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); } diff --git a/contracts/mainnet/connectors/liquity/main.sol b/contracts/mainnet/connectors/liquity/main.sol index 56db5302..4550bb1b 100644 --- a/contracts/mainnet/connectors/liquity/main.sol +++ b/contracts/mainnet/connectors/liquity/main.sol @@ -4,12 +4,17 @@ pragma solidity ^0.7.6; * @title Liquity. * @dev Lending & Borrowing. */ -import "hardhat/console.sol"; - -import { BorrowerOperationsLike, TroveManagerLike, StabilityPoolLike, StakingLike } from "./interface.sol"; +import { + BorrowerOperationsLike, + TroveManagerLike, + StabilityPoolLike, + StakingLike, + CollateralSurplusLike +} from "./interface.sol"; import { Stores } from "../../common/stores.sol"; import { Helpers } from "./helpers.sol"; import { Events } from "./events.sol"; +import "hardhat/console.sol"; abstract contract LiquityResolver is Events, Helpers { BorrowerOperationsLike internal constant borrowerOperations = @@ -20,7 +25,10 @@ abstract contract LiquityResolver is Events, Helpers { StabilityPoolLike(0x66017D22b0f8556afDd19FC67041899Eb65a21bb); StakingLike internal constant staking = StakingLike(0x4f9Fbb3f1E99B56e0Fe2892e623Ed36A76Fc605d); - + CollateralSurplusLike internal constant collateralSurplus = + CollateralSurplusLike(0x3D32e8b97Ed5881324241Cf03b2DA5E2EBcE5521); + + // Prevents stack-too-deep error struct AdjustTrove { uint maxFeePercentage; uint withdrawAmount; @@ -30,10 +38,6 @@ abstract contract LiquityResolver is Events, Helpers { bool isBorrow; } - constructor() { - console.log("Liquity Connector contract deployed at", address(this)); - } - /* Begin: Trove */ /** @@ -56,7 +60,17 @@ abstract contract LiquityResolver is Events, Helpers { uint getId, uint setId ) external payable returns (string memory _eventName, bytes memory _eventParam) { - // User can either send ETH directly or have it collected from a previous spell + /* + User has three options for depositing ETH collateral to open a Trove: + - Send ETH directly with this function call + - Have ETH collected from a previous spell + - Have ETH collected from the DSA's existing balance + */ + + 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}( @@ -68,7 +82,7 @@ abstract contract LiquityResolver is Events, Helpers { // Allow other spells to use the borrowed amount setUint(setId, borrowAmount); - _eventName = "LogOpen(address,uint,uint,uint,uint,uint)"; + _eventName = "LogOpen(address,uint256,uint256,uint256,uint256,uint256)"; _eventParam = abi.encode(msg.sender, maxFeePercentage, depositAmount, borrowAmount, getId, setId); } @@ -83,7 +97,7 @@ abstract contract LiquityResolver is Events, Helpers { // Allow other spells to use the collateral released from the Trove setUint(setId, collateral); - _eventName = "LogClose(address,uint)"; + _eventName = "LogClose(address,uint256)"; _eventParam = abi.encode(msg.sender, setId); } @@ -101,9 +115,12 @@ abstract contract LiquityResolver is Events, Helpers { 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,uint,uint)"; + _eventName = "LogDeposit(address,uint256,uint256)"; _eventParam = abi.encode(msg.sender, amount, getId); } @@ -124,7 +141,7 @@ abstract contract LiquityResolver is Events, Helpers { borrowerOperations.withdrawColl(amount, upperHint, lowerHint); setUint(setId, amount); - _eventName = "LogWithdraw(address,uint,uint)"; + _eventName = "LogWithdraw(address,uint256,uint256)"; _eventParam = abi.encode(msg.sender, amount, setId); } @@ -147,7 +164,7 @@ abstract contract LiquityResolver is Events, Helpers { borrowerOperations.withdrawLUSD(maxFeePercentage, amount, upperHint, lowerHint); setUint(setId, amount); // TODO: apply fee / get exact amount borrowed (with the fee applied) - _eventName = "LogBorrow(address,uint,uint)"; + _eventName = "LogBorrow(address,uint256,uint256)"; _eventParam = abi.encode(msg.sender, amount, setId); } @@ -165,9 +182,12 @@ abstract contract LiquityResolver is Events, Helpers { 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,uint,uint)"; + _eventName = "LogRepay(address,uint256,uint256)"; _eventParam = abi.encode(msg.sender, amount, getId); } @@ -199,6 +219,13 @@ abstract contract LiquityResolver is Events, Helpers { 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; @@ -223,18 +250,22 @@ abstract contract LiquityResolver is Events, Helpers { // Allow other spells to use the borrowed amount setUint(setBorrowId, borrowAmount); - _eventName = "LogAdjust(address,uint,uint,uint,uint,uint,uint,uint,uint,uint)"; - _eventParam = abi.encode(msg.sender, maxFeePercentage, depositAmount, borrowAmount, getDepositId, setWithdrawId, getRepayId, setBorrowId); + _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() external returns(string memory _eventName, bytes memory _eventParam) { + function claimCollateralFromRedemption(uint setId) external returns(string memory _eventName, bytes memory _eventParam) { + uint amount = collateralSurplus.getCollateral(address(this)); borrowerOperations.claimCollateral(); - _eventName = "LogClaimCollateralFromRedemption(address)"; - _eventParam = abi.encode(msg.sender); + setUint(setId, amount); + + _eventName = "LogClaimCollateralFromRedemption(address,uint256,uint256)"; + _eventParam = abi.encode(msg.sender, amount, setId); } /* End: Trove */ @@ -256,7 +287,7 @@ abstract contract LiquityResolver is Events, Helpers { stabilityPool.provideToSP(amount, frontendTag); - _eventName = "LogStabilityDeposit(address,uint,address,uint)"; + _eventName = "LogStabilityDeposit(address,uint256,address,uint256)"; _eventParam = abi.encode(msg.sender, amount, frontendTag, getId); } @@ -273,7 +304,7 @@ abstract contract LiquityResolver is Events, Helpers { stabilityPool.withdrawFromSP(amount); setUint(setId, amount); - _eventName = "LogStabilityWithdraw(address,uint,uint)"; + _eventName = "LogStabilityWithdraw(address,uint256,uint256)"; _eventParam = abi.encode(msg.sender, amount, setId); } @@ -287,10 +318,10 @@ abstract contract LiquityResolver is Events, Helpers { 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)"; - _eventParam = abi.encode(msg.sender); + _eventName = "LogStabilityMoveEthGainToTrove(address,uint256)"; + _eventParam = abi.encode(msg.sender, amount); } /* End: Stability Pool */ @@ -308,7 +339,7 @@ abstract contract LiquityResolver is Events, Helpers { ) external returns (string memory _eventName, bytes memory _eventParam) { amount = getUint(getId, amount); staking.stake(amount); - _eventName = "LogStake(address,uint,uint)"; + _eventName = "LogStake(address,uint256,uint256)"; _eventParam = abi.encode(msg.sender, amount, getId); } @@ -324,19 +355,27 @@ abstract contract LiquityResolver is Events, Helpers { ) external returns (string memory _eventName, bytes memory _eventParam) { staking.unstake(amount); setUint(setId, amount); - _eventName = "LogUnstake(address,uint,uint)"; + _eventName = "LogUnstake(address,uint256,uint256)"; _eventParam = abi.encode(msg.sender, amount, setId); } /** * @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 claimGains() external returns (string memory _eventName, bytes memory _eventParam) { - // claims are gained when a user's stake is adjusted, so we unstake 0 to trigger the claim - staking.unstake(0); - _eventName = "LogClaimGains(address)"; - _eventParam = abi.encode(msg.sender); + function claimStakingGains(uint setEthGainId, uint setLusdGainId) external returns (string memory _eventName, bytes memory _eventParam) { + uint ethAmount = staking.getPendingETHGain(address(this)); + uint lusdAmount = 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, ethAmount); + setUint(setLusdGainId, lusdAmount); + + _eventName = "LogClaimStakingGains(address,uint256,uint256)"; + _eventParam = abi.encode(msg.sender, ethAmount, lusdAmount); } /* End: Staking */ diff --git a/test/liquity/liquity.abi.js b/test/liquity/liquity.contracts.js similarity index 94% rename from test/liquity/liquity.abi.js rename to test/liquity/liquity.contracts.js index d1171d1d..d73407c5 100644 --- a/test/liquity/liquity.abi.js +++ b/test/liquity/liquity.contracts.js @@ -60,6 +60,11 @@ const LQTY_TOKEN_ABI = [ "function transfer(address _to, uint256 _value) public returns (bool success)", ]; +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, @@ -81,4 +86,6 @@ module.exports = { STAKING_ABI, LQTY_TOKEN_ADDRESS, LQTY_TOKEN_ABI, + COLL_SURPLUS_ADDRESS, + COLL_SURPLUS_ABI, }; diff --git a/test/liquity/liquity.helpers.js b/test/liquity/liquity.helpers.js index c400ded5..07a94ee3 100644 --- a/test/liquity/liquity.helpers.js +++ b/test/liquity/liquity.helpers.js @@ -5,6 +5,7 @@ const hardhatConfig = require("../../hardhat.config"); 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"); @@ -13,12 +14,13 @@ 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"); const CONNECTOR_NAME = "LIQUITY-v1-TEST"; const LUSD_GAS_COMPENSATION = hre.ethers.utils.parseUnits("200", 18); // 200 LUSD gas compensation repaid after loan repayment -const BLOCK_NUMBER = 12478159; // Deterministic block number for tests to run against, if you change this, tests will break. +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: BLOCK_NUMBER +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 openTroveSpell = async ( @@ -48,12 +50,11 @@ const openTroveSpell = async ( 0, ], }; - const openTx = await dsa + return await dsa .connect(signer) .cast(...encodeSpells([openTroveSpell]), address, { value: depositAmount, }); - return await openTx.wait(); }; const createDsaTrove = async ( @@ -92,6 +93,13 @@ const sendToken = async (token, amount, from, to) => { return await token.connect(signer).transfer(to, amount); }; +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", @@ -108,7 +116,7 @@ const resetHardhatBlockNumber = async (blockNumber) => { const deployAndConnect = async (contracts, isDebug = false) => { // Pin Liquity tests to a particular block number to create deterministic state (Ether price etc.) - await resetHardhatBlockNumber(BLOCK_NUMBER); + await resetHardhatBlockNumber(LIQUIDATABLE_TROVES_BLOCK_NUMBER); const liquity = { troveManager: null, @@ -121,6 +129,7 @@ const deployAndConnect = async (contracts, isDebug = false) => { hintHelpers: null, sortedTroves: null, staking: null, + collSurplus: null, }; const masterSigner = await getMasterSigner(); @@ -230,6 +239,14 @@ const deployAndConnect = async (contracts, isDebug = false) => { ); isDebug && console.log("Staking contract address", liquity.staking.address); + liquity.collSurplus = new ethers.Contract( + contracts.COLL_SURPLUS_ADDRESS, + contracts.COLL_SURPLUS_ABI, + ethers.provider + ); + isDebug && + console.log("CollSurplus contract address", liquity.collSurplus.address); + return liquity; }; @@ -310,18 +327,48 @@ const getRedemptionHints = async ( }; }; +const redeem = async (amount, from, to, liquity) => { + await sendToken(liquity.lusdToken, amount, from, to); + const { + partialRedemptionHintNicr, + firstRedemptionHint, + upperHint, + lowerHint, + } = await getRedemptionHints( + amount, + liquity.hintHelpers, + liquity.sortedTroves, + liquity.priceFeed + ); + 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, - openTroveSpell, sendToken, + getTroveInsertionHints, + getRedemptionHints, + redeem, CONNECTOR_NAME, LUSD_GAS_COMPENSATION, - BLOCK_NUMBER, JUSTIN_SUN_ADDRESS, LIQUIDATABLE_TROVE_ADDRESS, MAX_GAS, - resetHardhatBlockNumber, - getTroveInsertionHints, - getRedemptionHints, }; diff --git a/test/liquity/liquity.test.js b/test/liquity/liquity.test.js index a7993876..274492d7 100644 --- a/test/liquity/liquity.test.js +++ b/test/liquity/liquity.test.js @@ -6,7 +6,7 @@ const buildDSAv2 = require("../../scripts/buildDSAv2"); const encodeSpells = require("../../scripts/encodeSpells.js"); // Liquity smart contracts -const contracts = require("./liquity.abi"); +const contracts = require("./liquity.contracts"); // Liquity helpers const helpers = require("./liquity.helpers"); @@ -45,1089 +45,1847 @@ describe.only("Liquity", () => { describe("Main (Connector)", () => { describe("Trove", () => { - it("opens a Trove", async () => { - const depositAmount = ethers.utils.parseEther("5"); // 5 ETH - const borrowAmount = ethers.utils.parseUnits("2000", 18); // 2000 LUSD - const maxFeePercentage = ethers.utils.parseUnits("0.5", 18); // 0.5% max fee - const upperHint = ethers.constants.AddressZero; - const lowerHint = ethers.constants.AddressZero; - const originalUserBalance = await ethers.provider.getBalance( - wallet.address - ); - const originalDsaBalance = await ethers.provider.getBalance( - dsa.address - ); + describe("open()", () => { + it("opens a Trove", async () => { + const depositAmount = ethers.utils.parseEther("5"); // 5 ETH + const borrowAmount = ethers.utils.parseUnits("2000", 18); // 2000 LUSD + const maxFeePercentage = ethers.utils.parseUnits("0.5", 18); // 0.5% max fee + const upperHint = ethers.constants.AddressZero; + const lowerHint = ethers.constants.AddressZero; + const originalUserBalance = await ethers.provider.getBalance( + wallet.address + ); + const originalDsaBalance = await ethers.provider.getBalance( + dsa.address + ); - const openTroveSpell = { - connector: helpers.CONNECTOR_NAME, - method: "open", - args: [ + const openTroveSpell = { + connector: helpers.CONNECTOR_NAME, + method: "open", + args: [ + depositAmount, + maxFeePercentage, + borrowAmount, + upperHint, + lowerHint, + 0, + 0, + ], + }; + + const tx = await dsa + .connect(wallet) + .cast(...encodeSpells([openTroveSpell]), wallet.address, { + value: depositAmount, + }); + + await tx.wait(); + + const userBalance = await ethers.provider.getBalance(wallet.address); + const dsaEthBalance = await ethers.provider.getBalance(dsa.address); + const dsaLusdBalance = await liquity.lusdToken.balanceOf(dsa.address); + const troveDebt = await liquity.troveManager.getTroveDebt( + dsa.address + ); + const troveCollateral = await liquity.troveManager.getTroveColl( + dsa.address + ); + + expect(userBalance).lt( + originalUserBalance, + "User should have less Ether after opening Trove" + ); + + expect(dsaEthBalance).to.eq( + originalDsaBalance, + "User's DSA account Ether should not change after borrowing" + ); + + expect( + dsaLusdBalance, + "DSA account should now hold the amount the user tried to borrow" + ).to.eq(borrowAmount); + + expect(troveDebt).to.gt( + borrowAmount, + "Trove debt should equal the borrowed amount plus fee" + ); + + expect(troveCollateral).to.eq( depositAmount, - maxFeePercentage, + "Trove collateral should equal the deposited amount" + ); + }); + + it("opens a Trove using ETH collected from a previous spell", async () => { + const depositAmount = ethers.utils.parseEther("5"); // 5 ETH + const borrowAmount = ethers.utils.parseUnits("2000", 18); // 2000 LUSD + const maxFeePercentage = ethers.utils.parseUnits("0.5", 18); // 0.5% max fee + const upperHint = ethers.constants.AddressZero; + const lowerHint = ethers.constants.AddressZero; + const originalUserBalance = await ethers.provider.getBalance( + wallet.address + ); + const originalDsaBalance = await ethers.provider.getBalance( + dsa.address + ); + const depositId = 1; // Choose an ID to store and retrieve the deopsited ETH + + const depositEthSpell = { + connector: "Basic-v1", + method: "deposit", + args: [ETH_ADDRESS, depositAmount, 0, depositId], + }; + + const openTroveSpell = { + connector: helpers.CONNECTOR_NAME, + method: "open", + args: [ + 0, // When pulling ETH from a previous spell it doesn't matter what deposit value we put in this param + maxFeePercentage, + borrowAmount, + upperHint, + lowerHint, + depositId, + 0, + ], + }; + + const spells = [depositEthSpell, openTroveSpell]; + const tx = await dsa + .connect(wallet) + .cast(...encodeSpells(spells), wallet.address, { + value: depositAmount, + }); + + await tx.wait(); + const userBalance = await ethers.provider.getBalance(wallet.address); + const dsaEthBalance = await ethers.provider.getBalance(dsa.address); + const dsaLusdBalance = await liquity.lusdToken.balanceOf(dsa.address); + const troveDebt = await liquity.troveManager.getTroveDebt( + dsa.address + ); + const troveCollateral = await liquity.troveManager.getTroveColl( + dsa.address + ); + + expect(userBalance).lt( + originalUserBalance, + "User should have less Ether" + ); + + expect(dsaEthBalance).to.eq( + originalDsaBalance, + "DSA balance should not change" + ); + + expect( + dsaLusdBalance, + "DSA account should now hold the amount the user tried to borrow" + ).to.eq(borrowAmount); + + expect(troveDebt).to.gt( borrowAmount, - upperHint, - lowerHint, - 0, - 0, - ], - }; + "Trove debt should equal the borrowed amount plus fee" + ); - const spells = [openTroveSpell]; - const tx = await dsa - .connect(wallet) - .cast(...encodeSpells(spells), wallet.address, { - value: depositAmount, - }); - - await tx.wait(); - - const userBalance = await ethers.provider.getBalance(wallet.address); - const dsaEthBalance = await ethers.provider.getBalance(dsa.address); - const dsaLusdBalance = await liquity.lusdToken.balanceOf(dsa.address); - const troveDebt = await liquity.troveManager.getTroveDebt(dsa.address); - const troveCollateral = await liquity.troveManager.getTroveColl( - dsa.address - ); - - expect(userBalance).lt( - originalUserBalance, - "User should have less Ether after opening Trove" - ); - - expect(dsaEthBalance).to.eq( - originalDsaBalance, - "User's DSA account Ether should not change after borrowing" - ); - - expect( - dsaLusdBalance, - "DSA account should now hold the amount the user tried to borrow" - ).to.eq(borrowAmount); - - expect(troveDebt).to.gt( - borrowAmount, - "Trove debt should equal the borrowed amount plus fee" - ); - - expect(troveCollateral).to.eq( - depositAmount, - "Trove collateral should equal the deposited amount" - ); - }); - - it("opens a Trove using ETH collected from a previous spell", async () => { - const depositAmount = ethers.utils.parseEther("5"); // 5 ETH - const borrowAmount = ethers.utils.parseUnits("2000", 18); // 2000 LUSD - const maxFeePercentage = ethers.utils.parseUnits("0.5", 18); // 0.5% max fee - const upperHint = ethers.constants.AddressZero; - const lowerHint = ethers.constants.AddressZero; - const originalUserBalance = await ethers.provider.getBalance( - wallet.address - ); - const originalDsaBalance = await ethers.provider.getBalance( - dsa.address - ); - const depositId = 1; // Choose an ID to store and retrieve the deopsited ETH - - const depositEthSpell = { - connector: "Basic-v1", - method: "deposit", - args: [ETH_ADDRESS, depositAmount, 0, depositId], - }; - - const openTroveSpell = { - connector: helpers.CONNECTOR_NAME, - method: "open", - args: [ - 0, // When pulling ETH from a previous spell it doesn't matter what deposit value we put in this param - maxFeePercentage, - borrowAmount, - upperHint, - lowerHint, - depositId, - 0, - ], - }; - - const spells = [depositEthSpell, openTroveSpell]; - const tx = await dsa - .connect(wallet) - .cast(...encodeSpells(spells), wallet.address, { - value: depositAmount, - }); - - await tx.wait(); - const userBalance = await ethers.provider.getBalance(wallet.address); - const dsaEthBalance = await ethers.provider.getBalance(dsa.address); - const dsaLusdBalance = await liquity.lusdToken.balanceOf(dsa.address); - const troveDebt = await liquity.troveManager.getTroveDebt(dsa.address); - const troveCollateral = await liquity.troveManager.getTroveColl( - dsa.address - ); - - expect(userBalance).lt( - originalUserBalance, - "User should have less Ether" - ); - - expect(dsaEthBalance).to.eq( - originalDsaBalance, - "DSA balance should not change" - ); - - expect( - dsaLusdBalance, - "DSA account should now hold the amount the user tried to borrow" - ).to.eq(borrowAmount); - - expect(troveDebt).to.gt( - borrowAmount, - "Trove debt should equal the borrowed amount plus fee" - ); - - expect(troveCollateral).to.eq( - depositAmount, - "Trove collateral should equal the deposited amount" - ); - }); - - it("opens a Trove and stores the debt for other spells to use", async () => { - const depositAmount = ethers.utils.parseEther("5"); // 5 ETH - const borrowAmount = ethers.utils.parseUnits("2000", 18); // 2000 LUSD - const maxFeePercentage = ethers.utils.parseUnits("0.5", 18); // 0.5% max fee - const upperHint = ethers.constants.AddressZero; - const lowerHint = ethers.constants.AddressZero; - const originalUserBalance = await ethers.provider.getBalance( - wallet.address - ); - const originalDsaBalance = await ethers.provider.getBalance( - dsa.address - ); - const borrowId = 1; - - const openTroveSpell = { - connector: helpers.CONNECTOR_NAME, - method: "open", - args: [ + expect(troveCollateral).to.eq( depositAmount, - maxFeePercentage, + "Trove collateral should equal the deposited amount" + ); + }); + + it("opens a Trove and stores the debt for other spells to use", async () => { + const depositAmount = ethers.utils.parseEther("5"); // 5 ETH + const borrowAmount = ethers.utils.parseUnits("2000", 18); // 2000 LUSD + const maxFeePercentage = ethers.utils.parseUnits("0.5", 18); // 0.5% max fee + const upperHint = ethers.constants.AddressZero; + const lowerHint = ethers.constants.AddressZero; + const originalUserBalance = await ethers.provider.getBalance( + wallet.address + ); + const originalDsaBalance = await ethers.provider.getBalance( + dsa.address + ); + const borrowId = 1; + + const openTroveSpell = { + connector: helpers.CONNECTOR_NAME, + method: "open", + args: [ + depositAmount, + maxFeePercentage, + borrowAmount, + upperHint, + lowerHint, + 0, + borrowId, + ], + }; + + const withdrawLusdSpell = { + connector: "Basic-v1", + method: "withdraw", + args: [ + contracts.LUSD_TOKEN_ADDRESS, + 0, // amount comes from the previous spell's setId + dsa.address, + borrowId, + 0, + ], + }; + + const spells = [openTroveSpell, withdrawLusdSpell]; + const tx = await dsa + .connect(wallet) + .cast(...encodeSpells(spells), wallet.address, { + value: depositAmount, + }); + + await tx.wait(); + + const userBalance = await ethers.provider.getBalance(wallet.address); + + expect(userBalance).lt( + originalUserBalance, + "User should have less Ether after opening Trove" + ); + + const dsaEthBalance = await ethers.provider.getBalance(dsa.address); + const dsaLusdBalance = await liquity.lusdToken.balanceOf(dsa.address); + const troveDebt = await liquity.troveManager.getTroveDebt( + dsa.address + ); + const troveCollateral = await liquity.troveManager.getTroveColl( + dsa.address + ); + + expect(dsaEthBalance).to.eq( + originalDsaBalance, + "User's DSA account Ether should not change after borrowing" + ); + + expect( + dsaLusdBalance, + "DSA account should now hold the amount the user tried to borrow" + ).to.eq(borrowAmount); + + expect(troveDebt).to.gt( borrowAmount, - upperHint, - lowerHint, - 0, - borrowId, - ], - }; + "Trove debt should equal the borrowed amount plus fee" + ); - const withdrawLusdSpell = { - connector: "Basic-v1", - method: "withdraw", - args: [ - contracts.LUSD_TOKEN_ADDRESS, - 0, // amount comes from the previous spell's setId - dsa.address, - borrowId, - 0, - ], - }; + expect(troveCollateral).to.eq( + depositAmount, + "Trove collateral should equal the deposited amount" + ); + }); - const spells = [openTroveSpell, withdrawLusdSpell]; - const tx = await dsa - .connect(wallet) - .cast(...encodeSpells(spells), wallet.address, { - value: depositAmount, - }); + it("returns Instadapp event name and data", async () => { + const depositAmount = ethers.utils.parseEther("5"); + const borrowAmount = ethers.utils.parseUnits("2000", 18); + const maxFeePercentage = ethers.utils.parseUnits("0.5", 18); + const upperHint = ethers.constants.AddressZero; + const lowerHint = ethers.constants.AddressZero; - await tx.wait(); + const openTroveSpell = { + connector: helpers.CONNECTOR_NAME, + method: "open", + args: [ + depositAmount, + maxFeePercentage, + borrowAmount, + upperHint, + lowerHint, + 0, + 0, + ], + }; - const userBalance = await ethers.provider.getBalance(wallet.address); - - expect(userBalance).lt( - originalUserBalance, - "User should have less Ether after opening Trove" - ); - - const dsaEthBalance = await ethers.provider.getBalance(dsa.address); - const dsaLusdBalance = await liquity.lusdToken.balanceOf(dsa.address); - const troveDebt = await liquity.troveManager.getTroveDebt(dsa.address); - const troveCollateral = await liquity.troveManager.getTroveColl( - dsa.address - ); - - expect(dsaEthBalance).to.eq( - originalDsaBalance, - "User's DSA account Ether should not change after borrowing" - ); - - expect( - dsaLusdBalance, - "DSA account should now hold the amount the user tried to borrow" - ).to.eq(borrowAmount); - - expect(troveDebt).to.gt( - borrowAmount, - "Trove debt should equal the borrowed amount plus fee" - ); - - expect(troveCollateral).to.eq( - depositAmount, - "Trove collateral should equal the deposited amount" - ); + const openTx = await dsa.cast( + ...encodeSpells([openTroveSpell]), + wallet.address, + { + value: depositAmount, + } + ); + const receipt = await openTx.wait(); + const castLogEvent = receipt.events.find((e) => e.event === "LogCast") + .args; + expect(castLogEvent.eventNames[0]).eq( + "LogOpen(address,uint256,uint256,uint256,uint256,uint256)" + ); + const expectedEventParams = ethers.utils.defaultAbiCoder.encode( + ["address", "uint256", "uint256", "uint256", "uint256", "uint256"], + [ + wallet.address, + maxFeePercentage, + depositAmount, + borrowAmount, + 0, + 0, + ] + ); + expect(castLogEvent.eventParams[0]).eq(expectedEventParams); + }); }); - it("closes a Trove", async () => { - const depositAmount = ethers.utils.parseEther("5"); - const borrowAmount = ethers.utils.parseUnits("2000", 18); - await helpers.createDsaTrove( - dsa, - wallet, - liquity.hintHelpers, - liquity.sortedTroves, - depositAmount, - borrowAmount - ); + describe("close()", () => { + it("closes a Trove", async () => { + const depositAmount = ethers.utils.parseEther("5"); + const borrowAmount = ethers.utils.parseUnits("2000", 18); + await helpers.createDsaTrove( + dsa, + wallet, + liquity.hintHelpers, + liquity.sortedTroves, + depositAmount, + borrowAmount + ); - const originalTroveDebt = await liquity.troveManager.getTroveDebt( - dsa.address - ); + const originalTroveDebt = await liquity.troveManager.getTroveDebt( + dsa.address + ); - const originalTroveCollateral = await liquity.troveManager.getTroveColl( - dsa.address - ); + const originalTroveCollateral = await liquity.troveManager.getTroveColl( + dsa.address + ); - // Send DSA account enough LUSD (from Stability Pool) to close their Trove - const extraLusdRequiredToCloseTrove = originalTroveDebt.sub( - borrowAmount - ); + // Send DSA account enough LUSD (from Stability Pool) to close their Trove + const extraLusdRequiredToCloseTrove = originalTroveDebt.sub( + borrowAmount + ); - await helpers.sendToken( - liquity.lusdToken, - extraLusdRequiredToCloseTrove, - contracts.STABILITY_POOL_ADDRESS, - dsa.address - ); + await helpers.sendToken( + liquity.lusdToken, + extraLusdRequiredToCloseTrove, + contracts.STABILITY_POOL_ADDRESS, + dsa.address + ); - const originalDsaLusdBalance = await liquity.lusdToken.balanceOf( - dsa.address - ); + const originalDsaLusdBalance = await liquity.lusdToken.balanceOf( + dsa.address + ); - expect( - originalDsaLusdBalance, - "DSA account should now hold the LUSD amount required to pay off the Trove debt" - ).to.eq(originalTroveDebt); + expect( + originalDsaLusdBalance, + "DSA account should now hold the LUSD amount required to pay off the Trove debt" + ).to.eq(originalTroveDebt); - const closeTroveSpell = { - connector: helpers.CONNECTOR_NAME, - method: "close", - args: [0], - }; + const closeTroveSpell = { + connector: helpers.CONNECTOR_NAME, + method: "close", + args: [0], + }; - const closeTx = await dsa - .connect(wallet) - .cast(...encodeSpells([closeTroveSpell]), wallet.address); + const closeTx = await dsa + .connect(wallet) + .cast(...encodeSpells([closeTroveSpell]), wallet.address); - await closeTx.wait(); - const dsaEthBalance = await ethers.provider.getBalance(dsa.address); - const dsaLusdBalance = await liquity.lusdToken.balanceOf(dsa.address); - const troveDebt = await liquity.troveManager.getTroveDebt(dsa.address); - const troveCollateral = await liquity.troveManager.getTroveColl( - dsa.address - ); + await closeTx.wait(); + const dsaEthBalance = await ethers.provider.getBalance(dsa.address); + const dsaLusdBalance = await liquity.lusdToken.balanceOf(dsa.address); + const troveDebt = await liquity.troveManager.getTroveDebt( + dsa.address + ); + const troveCollateral = await liquity.troveManager.getTroveColl( + dsa.address + ); - expect(troveDebt, "Trove debt should equal 0 after close").to.eq(0); + expect(troveDebt, "Trove debt should equal 0 after close").to.eq(0); - expect( - troveCollateral, - "Trove collateral should equal 0 after close" - ).to.eq(0); + expect( + troveCollateral, + "Trove collateral should equal 0 after close" + ).to.eq(0); - expect( - dsaEthBalance, - "DSA account should now hold the Trove's ETH collateral" - ).to.eq(originalTroveCollateral); + expect( + dsaEthBalance, + "DSA account should now hold the Trove's ETH collateral" + ).to.eq(originalTroveCollateral); - expect( - dsaLusdBalance, - "DSA account should now hold the gas compensation amount of LUSD as it paid off the Trove debt" - ).to.eq(helpers.LUSD_GAS_COMPENSATION); - }); + expect( + dsaLusdBalance, + "DSA account should now hold the gas compensation amount of LUSD as it paid off the Trove debt" + ).to.eq(helpers.LUSD_GAS_COMPENSATION); + }); - it("closes a Trove using LUSD obtained from a previous spell", async () => { - await helpers.createDsaTrove( - dsa, - wallet, - liquity.hintHelpers, - liquity.sortedTroves - ); + it("closes a Trove using LUSD obtained from a previous spell", async () => { + await helpers.createDsaTrove( + dsa, + wallet, + liquity.hintHelpers, + liquity.sortedTroves + ); - const originalTroveDebt = await liquity.troveManager.getTroveDebt( - dsa.address - ); - const originalTroveCollateral = await liquity.troveManager.getTroveColl( - dsa.address - ); + const originalTroveDebt = await liquity.troveManager.getTroveDebt( + dsa.address + ); + const originalTroveCollateral = await liquity.troveManager.getTroveColl( + dsa.address + ); - // Send user enough LUSD to repay the loan, we'll use a deposit and withdraw spell to obtain it - await helpers.sendToken( - liquity.lusdToken, - originalTroveDebt, - contracts.STABILITY_POOL_ADDRESS, - wallet.address - ); - - // Allow DSA to spend user's LUSD - await liquity.lusdToken - .connect(wallet) - .approve(dsa.address, originalTroveDebt); - - const lusdDepositId = 1; - - // Simulate a spell which would have pulled LUSD from somewhere (e.g. AAVE) into InstaMemory - // In this case we're simply running a deposit spell from the user's EOA - const depositLusdSpell = { - connector: "Basic-v1", - method: "deposit", - args: [ - contracts.LUSD_TOKEN_ADDRESS, + // Send user enough LUSD to repay the loan, we'll use a deposit and withdraw spell to obtain it + await helpers.sendToken( + liquity.lusdToken, originalTroveDebt, - 0, - lusdDepositId, - ], - }; - // Withdraw the obtained LUSD into DSA account - const withdrawLusdSpell = { - connector: "Basic-v1", - method: "withdraw", - args: [ - contracts.LUSD_TOKEN_ADDRESS, - 0, // amount comes from the previous spell's setId - dsa.address, - lusdDepositId, - 0, - ], - }; - - const closeTroveSpell = { - connector: helpers.CONNECTOR_NAME, - method: "close", - args: [0], - }; - - const closeTx = await dsa - .connect(wallet) - .cast( - ...encodeSpells([ - depositLusdSpell, - withdrawLusdSpell, - closeTroveSpell, - ]), + contracts.STABILITY_POOL_ADDRESS, wallet.address ); - await closeTx.wait(); - const dsaEthBalance = await ethers.provider.getBalance(dsa.address); - const troveDebt = await liquity.troveManager.getTroveDebt(dsa.address); - const troveCollateral = await liquity.troveManager.getTroveColl( - dsa.address - ); + // Allow DSA to spend user's LUSD + await liquity.lusdToken + .connect(wallet) + .approve(dsa.address, originalTroveDebt); - expect(troveDebt, "Trove debt should equal 0 after close").to.eq(0); + const lusdDepositId = 1; - expect( - troveCollateral, - "Trove collateral should equal 0 after close" - ).to.eq(0); + // Simulate a spell which would have pulled LUSD from somewhere (e.g. AAVE) into InstaMemory + // In this case we're simply running a deposit spell from the user's EOA + const depositLusdSpell = { + connector: "Basic-v1", + method: "deposit", + args: [ + contracts.LUSD_TOKEN_ADDRESS, + originalTroveDebt, + 0, + lusdDepositId, + ], + }; + // Withdraw the obtained LUSD into DSA account + const withdrawLusdSpell = { + connector: "Basic-v1", + method: "withdraw", + args: [ + contracts.LUSD_TOKEN_ADDRESS, + 0, // amount comes from the previous spell's setId + dsa.address, + lusdDepositId, + 0, + ], + }; - expect( - dsaEthBalance, - "DSA account should now hold the Trove's ETH collateral" - ).to.eq(originalTroveCollateral); - }); + const closeTroveSpell = { + connector: helpers.CONNECTOR_NAME, + method: "close", + args: [0], + }; - it("closes a Trove and stores the released collateral for other spells to use", async () => { - const depositAmount = ethers.utils.parseEther("5"); - const borrowAmount = ethers.utils.parseUnits("2000", 18); - await helpers.createDsaTrove( - dsa, - wallet, - liquity.hintHelpers, - liquity.sortedTroves, - depositAmount, - borrowAmount - ); + const closeTx = await dsa + .connect(wallet) + .cast( + ...encodeSpells([ + depositLusdSpell, + withdrawLusdSpell, + closeTroveSpell, + ]), + wallet.address + ); - const originalTroveDebt = await liquity.troveManager.getTroveDebt( - dsa.address - ); - const originalTroveCollateral = await liquity.troveManager.getTroveColl( - dsa.address - ); - - // Send DSA account enough LUSD (from Stability Pool) to close their Trove - const extraLusdRequiredToCloseTrove = originalTroveDebt.sub( - borrowAmount - ); - await helpers.sendToken( - liquity.lusdToken, - extraLusdRequiredToCloseTrove, - contracts.STABILITY_POOL_ADDRESS, - dsa.address - ); - const originalDsaLusdBalance = await liquity.lusdToken.balanceOf( - dsa.address - ); - - expect( - originalDsaLusdBalance, - "DSA account should now hold the LUSD amount required to pay off the Trove debt" - ).to.eq(originalTroveDebt); - - const collateralWithdrawId = 1; - - const closeTroveSpell = { - connector: helpers.CONNECTOR_NAME, - method: "close", - args: [collateralWithdrawId], - }; - - const withdrawEthSpell = { - connector: "Basic-v1", - method: "withdraw", - args: [ - ETH_ADDRESS, - 0, // amount comes from the previous spell's setId - dsa.address, - collateralWithdrawId, - 0, - ], - }; - - const closeTx = await dsa - .connect(wallet) - .cast( - ...encodeSpells([closeTroveSpell, withdrawEthSpell]), - wallet.address + await closeTx.wait(); + const dsaEthBalance = await ethers.provider.getBalance(dsa.address); + const troveDebt = await liquity.troveManager.getTroveDebt( + dsa.address + ); + const troveCollateral = await liquity.troveManager.getTroveColl( + dsa.address ); - await closeTx.wait(); - const dsaEthBalance = await ethers.provider.getBalance(dsa.address); - const dsaLusdBalance = await liquity.lusdToken.balanceOf(dsa.address); - const troveDebt = await liquity.troveManager.getTroveDebt(dsa.address); - const troveCollateral = await liquity.troveManager.getTroveColl( - dsa.address - ); + expect(troveDebt, "Trove debt should equal 0 after close").to.eq(0); - expect(troveDebt, "Trove debt should equal 0 after close").to.eq(0); + expect( + troveCollateral, + "Trove collateral should equal 0 after close" + ).to.eq(0); - expect( - troveCollateral, - "Trove collateral should equal 0 after close" - ).to.eq(0); + expect( + dsaEthBalance, + "DSA account should now hold the Trove's ETH collateral" + ).to.eq(originalTroveCollateral); + }); - expect( - dsaEthBalance, - "DSA account should now hold the Trove's ETH collateral" - ).to.eq(originalTroveCollateral); + it("closes a Trove and stores the released collateral for other spells to use", async () => { + const depositAmount = ethers.utils.parseEther("5"); + const borrowAmount = ethers.utils.parseUnits("2000", 18); + await helpers.createDsaTrove( + dsa, + wallet, + liquity.hintHelpers, + liquity.sortedTroves, + depositAmount, + borrowAmount + ); - expect( - dsaLusdBalance, - "DSA account should now hold the gas compensation amount of LUSD as it paid off the Trove debt" - ).to.eq(helpers.LUSD_GAS_COMPENSATION); + const originalTroveDebt = await liquity.troveManager.getTroveDebt( + dsa.address + ); + const originalTroveCollateral = await liquity.troveManager.getTroveColl( + dsa.address + ); + + // Send DSA account enough LUSD (from Stability Pool) to close their Trove + const extraLusdRequiredToCloseTrove = originalTroveDebt.sub( + borrowAmount + ); + await helpers.sendToken( + liquity.lusdToken, + extraLusdRequiredToCloseTrove, + contracts.STABILITY_POOL_ADDRESS, + dsa.address + ); + const originalDsaLusdBalance = await liquity.lusdToken.balanceOf( + dsa.address + ); + + expect( + originalDsaLusdBalance, + "DSA account should now hold the LUSD amount required to pay off the Trove debt" + ).to.eq(originalTroveDebt); + + const collateralWithdrawId = 1; + + const closeTroveSpell = { + connector: helpers.CONNECTOR_NAME, + method: "close", + args: [collateralWithdrawId], + }; + + const withdrawEthSpell = { + connector: "Basic-v1", + method: "withdraw", + args: [ + ETH_ADDRESS, + 0, // amount comes from the previous spell's setId + dsa.address, + collateralWithdrawId, + 0, + ], + }; + + const closeTx = await dsa + .connect(wallet) + .cast( + ...encodeSpells([closeTroveSpell, withdrawEthSpell]), + wallet.address + ); + + await closeTx.wait(); + const dsaEthBalance = await ethers.provider.getBalance(dsa.address); + const dsaLusdBalance = await liquity.lusdToken.balanceOf(dsa.address); + const troveDebt = await liquity.troveManager.getTroveDebt( + dsa.address + ); + const troveCollateral = await liquity.troveManager.getTroveColl( + dsa.address + ); + + expect(troveDebt, "Trove debt should equal 0 after close").to.eq(0); + + expect( + troveCollateral, + "Trove collateral should equal 0 after close" + ).to.eq(0); + + expect( + dsaEthBalance, + "DSA account should now hold the Trove's ETH collateral" + ).to.eq(originalTroveCollateral); + + expect( + dsaLusdBalance, + "DSA account should now hold the gas compensation amount of LUSD as it paid off the Trove debt" + ).to.eq(helpers.LUSD_GAS_COMPENSATION); + }); + + it("returns Instadapp event name and data", async () => { + const depositAmount = ethers.utils.parseEther("5"); + const borrowAmount = ethers.utils.parseUnits("2000", 18); + await helpers.createDsaTrove( + dsa, + wallet, + liquity.hintHelpers, + liquity.sortedTroves, + depositAmount, + borrowAmount + ); + await helpers.sendToken( + liquity.lusdToken, + ethers.utils.parseUnits("2500", 18), + contracts.STABILITY_POOL_ADDRESS, + dsa.address + ); + + const closeTroveSpell = { + connector: helpers.CONNECTOR_NAME, + method: "close", + args: [0], + }; + + const closeTx = await dsa + .connect(wallet) + .cast(...encodeSpells([closeTroveSpell]), wallet.address); + + const receipt = await closeTx.wait(); + const castLogEvent = receipt.events.find((e) => e.event === "LogCast") + .args; + const expectedEventParams = ethers.utils.defaultAbiCoder.encode( + ["address", "uint256"], + [wallet.address, 0] + ); + expect(castLogEvent.eventNames[0]).eq("LogClose(address,uint256)"); + expect(castLogEvent.eventParams[0]).eq(expectedEventParams); + }); }); - it("deposits ETH into a Trove", async () => { - await helpers.createDsaTrove( - dsa, - wallet, - liquity.hintHelpers, - liquity.sortedTroves - ); + describe("deposit()", () => { + it("deposits ETH into a Trove", async () => { + await helpers.createDsaTrove( + dsa, + wallet, + liquity.hintHelpers, + liquity.sortedTroves + ); - const originalTroveCollateral = await liquity.troveManager.getTroveColl( - dsa.address - ); + const originalTroveCollateral = await liquity.troveManager.getTroveColl( + dsa.address + ); - const topupAmount = ethers.utils.parseEther("1"); - const upperHint = ethers.constants.AddressZero; - const lowerHint = ethers.constants.AddressZero; - const depositEthSpell = { - connector: helpers.CONNECTOR_NAME, - method: "deposit", - args: [topupAmount, upperHint, lowerHint, 0], - }; + const topupAmount = ethers.utils.parseEther("1"); + const upperHint = ethers.constants.AddressZero; + const lowerHint = ethers.constants.AddressZero; + const depositEthSpell = { + connector: helpers.CONNECTOR_NAME, + method: "deposit", + args: [topupAmount, upperHint, lowerHint, 0], + }; - const depositTx = await dsa - .connect(wallet) - .cast(...encodeSpells([depositEthSpell]), wallet.address, { - value: topupAmount, - }); + const depositTx = await dsa + .connect(wallet) + .cast(...encodeSpells([depositEthSpell]), wallet.address, { + value: topupAmount, + }); - await depositTx.wait(); + await depositTx.wait(); - const troveCollateral = await liquity.troveManager.getTroveColl( - dsa.address - ); + const troveCollateral = await liquity.troveManager.getTroveColl( + dsa.address + ); - const expectedTroveCollateral = originalTroveCollateral.add( - topupAmount - ); + const expectedTroveCollateral = originalTroveCollateral.add( + topupAmount + ); - expect( - troveCollateral, - `Trove collateral should have increased by ${topupAmount} ETH` - ).to.eq(expectedTroveCollateral); + expect( + troveCollateral, + `Trove collateral should have increased by ${topupAmount} ETH` + ).to.eq(expectedTroveCollateral); + }); + + it("returns Instadapp event name and data", async () => { + await helpers.createDsaTrove( + dsa, + wallet, + liquity.hintHelpers, + liquity.sortedTroves + ); + + const topupAmount = ethers.utils.parseEther("1"); + const upperHint = ethers.constants.AddressZero; + const lowerHint = ethers.constants.AddressZero; + const depositEthSpell = { + connector: helpers.CONNECTOR_NAME, + method: "deposit", + args: [topupAmount, upperHint, lowerHint, 0], + }; + + const depositTx = await dsa + .connect(wallet) + .cast(...encodeSpells([depositEthSpell]), wallet.address, { + value: topupAmount, + }); + + const receipt = await depositTx.wait(); + const castLogEvent = receipt.events.find((e) => e.event === "LogCast") + .args; + const expectedEventParams = ethers.utils.defaultAbiCoder.encode( + ["address", "uint256", "uint256"], + [wallet.address, topupAmount, 0] + ); + expect(castLogEvent.eventNames[0]).eq( + "LogDeposit(address,uint256,uint256)" + ); + expect(castLogEvent.eventParams[0]).eq(expectedEventParams); + }); }); - it("withdraws ETH from a Trove", async () => { - await helpers.createDsaTrove( - dsa, - wallet, - liquity.hintHelpers, - liquity.sortedTroves - ); + describe("withdraw()", () => { + it("withdraws ETH from a Trove", async () => { + await helpers.createDsaTrove( + dsa, + wallet, + liquity.hintHelpers, + liquity.sortedTroves + ); - const originalTroveCollateral = await liquity.troveManager.getTroveColl( - dsa.address - ); - const withdrawAmount = ethers.utils.parseEther("1"); - const upperHint = ethers.constants.AddressZero; - const lowerHint = ethers.constants.AddressZero; - const withdrawEthSpell = { - connector: helpers.CONNECTOR_NAME, - method: "withdraw", - args: [withdrawAmount, upperHint, lowerHint, 0], - }; + const originalTroveCollateral = await liquity.troveManager.getTroveColl( + dsa.address + ); + const withdrawAmount = ethers.utils.parseEther("1"); + const upperHint = ethers.constants.AddressZero; + const lowerHint = ethers.constants.AddressZero; + const withdrawEthSpell = { + connector: helpers.CONNECTOR_NAME, + method: "withdraw", + args: [withdrawAmount, upperHint, lowerHint, 0], + }; - const withdrawTx = await dsa - .connect(wallet) - .cast(...encodeSpells([withdrawEthSpell]), wallet.address); + const withdrawTx = await dsa + .connect(wallet) + .cast(...encodeSpells([withdrawEthSpell]), wallet.address); - await withdrawTx.wait(); - const troveCollateral = await liquity.troveManager.getTroveColl( - dsa.address - ); - const expectedTroveCollateral = originalTroveCollateral.sub( - withdrawAmount - ); + await withdrawTx.wait(); + const troveCollateral = await liquity.troveManager.getTroveColl( + dsa.address + ); + const expectedTroveCollateral = originalTroveCollateral.sub( + withdrawAmount + ); - expect( - troveCollateral, - `Trove collateral should have decreased by ${withdrawAmount} ETH` - ).to.eq(expectedTroveCollateral); + expect( + troveCollateral, + `Trove collateral should have decreased by ${withdrawAmount} ETH` + ).to.eq(expectedTroveCollateral); + }); + + it("returns Instadapp event name and data", async () => { + await helpers.createDsaTrove( + dsa, + wallet, + liquity.hintHelpers, + liquity.sortedTroves + ); + + const withdrawAmount = ethers.utils.parseEther("1"); + const upperHint = ethers.constants.AddressZero; + const lowerHint = ethers.constants.AddressZero; + const setId = 0; + const withdrawEthSpell = { + connector: helpers.CONNECTOR_NAME, + method: "withdraw", + args: [withdrawAmount, upperHint, lowerHint, setId], + }; + + const withdrawTx = await dsa + .connect(wallet) + .cast(...encodeSpells([withdrawEthSpell]), wallet.address); + + const receipt = await withdrawTx.wait(); + const castLogEvent = receipt.events.find((e) => e.event === "LogCast") + .args; + const expectedEventParams = ethers.utils.defaultAbiCoder.encode( + ["address", "uint256", "uint256"], + [wallet.address, withdrawAmount, setId] + ); + expect(castLogEvent.eventNames[0]).eq( + "LogWithdraw(address,uint256,uint256)" + ); + expect(castLogEvent.eventParams[0]).eq(expectedEventParams); + }); }); - it("borrows LUSD from a Trove", async () => { - await helpers.createDsaTrove( - dsa, - wallet, - liquity.hintHelpers, - liquity.sortedTroves - ); + describe("borrow()", () => { + it("borrows LUSD from a Trove", async () => { + await helpers.createDsaTrove( + dsa, + wallet, + liquity.hintHelpers, + liquity.sortedTroves + ); - const originalTroveDebt = await liquity.troveManager.getTroveDebt( - dsa.address - ); - const borrowAmount = ethers.utils.parseUnits("1000"); // 1000 LUSD - const upperHint = ethers.constants.AddressZero; - const lowerHint = ethers.constants.AddressZero; - const maxFeePercentage = ethers.utils.parseUnits("0.5", 18); // 0.5% max fee - const borrowSpell = { - connector: helpers.CONNECTOR_NAME, - method: "borrow", - args: [maxFeePercentage, borrowAmount, upperHint, lowerHint, 0], - }; + const originalTroveDebt = await liquity.troveManager.getTroveDebt( + dsa.address + ); + const borrowAmount = ethers.utils.parseUnits("1000", 18); // 1000 LUSD + const upperHint = ethers.constants.AddressZero; + const lowerHint = ethers.constants.AddressZero; + const maxFeePercentage = ethers.utils.parseUnits("0.5", 18); // 0.5% max fee + const borrowSpell = { + connector: helpers.CONNECTOR_NAME, + method: "borrow", + args: [maxFeePercentage, borrowAmount, upperHint, lowerHint, 0], + }; - const borrowTx = await dsa - .connect(wallet) - .cast(...encodeSpells([borrowSpell]), wallet.address); + const borrowTx = await dsa + .connect(wallet) + .cast(...encodeSpells([borrowSpell]), wallet.address); - await borrowTx.wait(); - const troveDebt = await liquity.troveManager.getTroveDebt(dsa.address); - const expectedTroveDebt = originalTroveDebt.add(borrowAmount); + await borrowTx.wait(); + const troveDebt = await liquity.troveManager.getTroveDebt( + dsa.address + ); + const expectedTroveDebt = originalTroveDebt.add(borrowAmount); - expect( - troveDebt, - `Trove debt should have increased by at least ${borrowAmount} ETH` - ).to.gte(expectedTroveDebt); + expect( + troveDebt, + `Trove debt should have increased by at least ${borrowAmount} ETH` + ).to.gte(expectedTroveDebt); + }); + + it("returns Instadapp event name and data", async () => { + await helpers.createDsaTrove( + dsa, + wallet, + liquity.hintHelpers, + liquity.sortedTroves + ); + + const borrowAmount = ethers.utils.parseUnits("1000", 18); // 1000 LUSD + const upperHint = ethers.constants.AddressZero; + const lowerHint = ethers.constants.AddressZero; + const maxFeePercentage = ethers.utils.parseUnits("0.5", 18); // 0.5% max fee + const setId = 0; + const borrowSpell = { + connector: helpers.CONNECTOR_NAME, + method: "borrow", + args: [maxFeePercentage, borrowAmount, upperHint, lowerHint, setId], + }; + + const borrowTx = await dsa + .connect(wallet) + .cast(...encodeSpells([borrowSpell]), wallet.address); + + const receipt = await borrowTx.wait(); + const castLogEvent = receipt.events.find((e) => e.event === "LogCast") + .args; + const expectedEventParams = ethers.utils.defaultAbiCoder.encode( + ["address", "uint256", "uint256"], + [wallet.address, borrowAmount, setId] + ); + expect(castLogEvent.eventNames[0]).eq( + "LogBorrow(address,uint256,uint256)" + ); + expect(castLogEvent.eventParams[0]).eq(expectedEventParams); + }); }); - it("repays LUSD to a Trove", async () => { - await helpers.createDsaTrove( - dsa, - wallet, - liquity.hintHelpers, - liquity.sortedTroves - ); + describe("repay()", () => { + it("repays LUSD to a Trove", async () => { + const depositAmount = ethers.utils.parseEther("5"); + const borrowAmount = ethers.utils.parseUnits("2500", 18); - const originalTroveDebt = await liquity.troveManager.getTroveDebt( - dsa.address - ); - const repayAmount = ethers.utils.parseUnits("100"); // 100 LUSD - const upperHint = ethers.constants.AddressZero; - const lowerHint = ethers.constants.AddressZero; - const borrowSpell = { - connector: helpers.CONNECTOR_NAME, - method: "repay", - args: [repayAmount, upperHint, lowerHint, 0], - }; + await helpers.createDsaTrove( + dsa, + wallet, + liquity.hintHelpers, + liquity.sortedTroves, + depositAmount, + borrowAmount + ); - const repayTx = await dsa - .connect(wallet) - .cast(...encodeSpells([borrowSpell]), wallet.address, { - value: repayAmount, - }); - - await repayTx.wait(); - const troveDebt = await liquity.troveManager.getTroveDebt(dsa.address); - const expectedTroveDebt = originalTroveDebt.sub(repayAmount); - - expect( - troveDebt, - `Trove debt should have decreased by ${repayAmount} ETH` - ).to.eq(expectedTroveDebt); - }); - - it("adjusts a Trove: deposit ETH and borrow LUSD", async () => { - await helpers.createDsaTrove( - dsa, - wallet, - liquity.hintHelpers, - liquity.sortedTroves - ); - - const originalTroveCollateral = await liquity.troveManager.getTroveColl( - dsa.address - ); - const originalTroveDebt = await liquity.troveManager.getTroveDebt( - dsa.address - ); - const depositAmount = ethers.utils.parseEther("1"); // 1 ETH - const borrowAmount = ethers.utils.parseUnits("500"); // 500 LUSD - const withdrawAmount = 0; - const repayAmount = 0; - const upperHint = ethers.constants.AddressZero; - const lowerHint = ethers.constants.AddressZero; - const maxFeePercentage = ethers.utils.parseUnits("0.5", 18); // 0.5% max fee - - const adjustSpell = { - connector: helpers.CONNECTOR_NAME, - method: "adjust", - args: [ - maxFeePercentage, - withdrawAmount, + const originalTroveDebt = await liquity.troveManager.getTroveDebt( + dsa.address + ); + const repayAmount = ethers.utils.parseUnits("100", 18); // 100 LUSD + const { upperHint, lowerHint } = await helpers.getTroveInsertionHints( depositAmount, borrowAmount, - repayAmount, - upperHint, - lowerHint, - 0, - 0, - 0, - 0, - ], - }; + liquity.hintHelpers, + liquity.sortedTroves + ); + const borrowSpell = { + connector: helpers.CONNECTOR_NAME, + method: "repay", + args: [repayAmount, upperHint, lowerHint, 0], + }; - const adjustTx = await dsa - .connect(wallet) - .cast(...encodeSpells([adjustSpell]), wallet.address, { - value: depositAmount, - gasLimit: helpers.MAX_GAS, - }); + await dsa + .connect(wallet) + .cast(...encodeSpells([borrowSpell]), wallet.address, { + value: repayAmount, + }); - await adjustTx.wait(); - const troveCollateral = await liquity.troveManager.getTroveColl( - dsa.address - ); - const troveDebt = await liquity.troveManager.getTroveDebt(dsa.address); - const expectedTroveColl = originalTroveCollateral.add(depositAmount); - const expectedTroveDebt = originalTroveDebt.add(borrowAmount); + const troveDebt = await liquity.troveManager.getTroveDebt( + dsa.address + ); + const expectedTroveDebt = originalTroveDebt.sub(repayAmount); - expect( - troveCollateral, - `Trove collateral should have increased by ${depositAmount} ETH` - ).to.eq(expectedTroveColl); + expect( + troveDebt, + `Trove debt should have decreased by ${repayAmount} ETH` + ).to.eq(expectedTroveDebt); + }); - expect( - troveDebt, - `Trove debt should have increased by at least ${borrowAmount} ETH` - ).to.gte(expectedTroveDebt); - }); + it("returns Instadapp event name and data", async () => { + const depositAmount = ethers.utils.parseEther("5"); + const borrowAmount = ethers.utils.parseUnits("2500", 18); + await helpers.createDsaTrove( + dsa, + wallet, + liquity.hintHelpers, + liquity.sortedTroves, + depositAmount, + borrowAmount + ); - it("adjusts a Trove: withdraw ETH and repay LUSD", async () => { - await helpers.createDsaTrove( - dsa, - wallet, - liquity.hintHelpers, - liquity.sortedTroves - ); - - const originalTroveCollateral = await liquity.troveManager.getTroveColl( - dsa.address - ); - const originalTroveDebt = await liquity.troveManager.getTroveDebt( - dsa.address - ); - const depositAmount = 0; - const borrowAmount = 0; - const withdrawAmount = ethers.utils.parseEther("1"); // 1 ETH; - const repayAmount = ethers.utils.parseUnits("500"); // 500 LUSD; - const { upperHint, lowerHint } = await helpers.getTroveInsertionHints( - originalTroveCollateral.sub(withdrawAmount), - originalTroveDebt.sub(repayAmount), - liquity.hintHelpers, - liquity.sortedTroves - ); - const maxFeePercentage = ethers.utils.parseUnits("0.5", 18); // 0.5% max fee - - const adjustSpell = { - connector: helpers.CONNECTOR_NAME, - method: "adjust", - args: [ - maxFeePercentage, - withdrawAmount, + const repayAmount = ethers.utils.parseUnits("100", 18); // 100 LUSD + const { upperHint, lowerHint } = await helpers.getTroveInsertionHints( depositAmount, borrowAmount, - repayAmount, - upperHint, - lowerHint, - 0, - 0, - 0, - 0, - ], - }; + liquity.hintHelpers, + liquity.sortedTroves + ); + const getId = 0; - const adjustTx = await dsa - .connect(wallet) - .cast(...encodeSpells([adjustSpell]), wallet.address, { - value: depositAmount, - gasLimit: helpers.MAX_GAS, - }); + const borrowSpell = { + connector: helpers.CONNECTOR_NAME, + method: "repay", + args: [repayAmount, upperHint, lowerHint, getId], + }; - await adjustTx.wait(); - const troveCollateral = await liquity.troveManager.getTroveColl( - dsa.address - ); - const troveDebt = await liquity.troveManager.getTroveDebt(dsa.address); - const expectedTroveColl = originalTroveCollateral.sub(withdrawAmount); - const expectedTroveDebt = originalTroveDebt.sub(repayAmount); + const repayTx = await dsa + .connect(wallet) + .cast(...encodeSpells([borrowSpell]), wallet.address, { + value: repayAmount, + }); - expect( - troveCollateral, - `Trove collateral should have increased by ${depositAmount} ETH` - ).to.eq(expectedTroveColl); - - expect( - troveDebt, - `Trove debt should have increased by at least ${borrowAmount} ETH` - ).to.gte(expectedTroveDebt); + const receipt = await repayTx.wait(); + const castLogEvent = receipt.events.find((e) => e.event === "LogCast") + .args; + const expectedEventParams = ethers.utils.defaultAbiCoder.encode( + ["address", "uint256", "uint256"], + [wallet.address, repayAmount, getId] + ); + expect(castLogEvent.eventNames[0]).eq( + "LogRepay(address,uint256,uint256)" + ); + expect(castLogEvent.eventParams[0]).eq(expectedEventParams); + }); }); - it("claims collateral from a redeemed Trove", async () => { - // Create a low collateralized Trove - const depositAmount = ethers.utils.parseEther("1.5"); - const borrowAmount = ethers.utils.parseUnits("2500", 18); + describe("adjust()", () => { + it("adjusts a Trove: deposit ETH and borrow LUSD", async () => { + await helpers.createDsaTrove( + dsa, + wallet, + liquity.hintHelpers, + liquity.sortedTroves + ); - await helpers.createDsaTrove( - dsa, - wallet, - liquity.hintHelpers, - liquity.sortedTroves, - depositAmount, - borrowAmount - ); + const originalTroveCollateral = await liquity.troveManager.getTroveColl( + dsa.address + ); + const originalTroveDebt = await liquity.troveManager.getTroveDebt( + dsa.address + ); + const depositAmount = ethers.utils.parseEther("1"); // 1 ETH + const borrowAmount = ethers.utils.parseUnits("500", 18); // 500 LUSD + const withdrawAmount = 0; + const repayAmount = 0; + const upperHint = ethers.constants.AddressZero; + const lowerHint = ethers.constants.AddressZero; + const maxFeePercentage = ethers.utils.parseUnits("0.5", 18); // 0.5% max fee - // Redeem lots of LUSD to cause the Trove to become redeemed - const redeemAmount = ethers.utils.parseUnits("10000000", 18); - await helpers.sendToken( - liquity.lusdToken, - redeemAmount, - contracts.STABILITY_POOL_ADDRESS, - wallet.address - ); - const { - partialRedemptionHintNicr, - firstRedemptionHint, - upperHint, - lowerHint, - } = await helpers.getRedemptionHints( - redeemAmount, - liquity.hintHelpers, - liquity.sortedTroves, - liquity.priceFeed - ); - const maxFeePercentage = ethers.utils.parseUnits("0.5", 18); // 0.5% max fee + const adjustSpell = { + connector: helpers.CONNECTOR_NAME, + method: "adjust", + args: [ + maxFeePercentage, + withdrawAmount, + depositAmount, + borrowAmount, + repayAmount, + upperHint, + lowerHint, + 0, + 0, + 0, + 0, + ], + }; - await liquity.troveManager - .connect(wallet) - .redeemCollateral( + const adjustTx = await dsa + .connect(wallet) + .cast(...encodeSpells([adjustSpell]), wallet.address, { + value: depositAmount, + gasLimit: helpers.MAX_GAS, + }); + + await adjustTx.wait(); + const troveCollateral = await liquity.troveManager.getTroveColl( + dsa.address + ); + const troveDebt = await liquity.troveManager.getTroveDebt( + dsa.address + ); + const expectedTroveColl = originalTroveCollateral.add(depositAmount); + const expectedTroveDebt = originalTroveDebt.add(borrowAmount); + + expect( + troveCollateral, + `Trove collateral should have increased by ${depositAmount} ETH` + ).to.eq(expectedTroveColl); + + expect( + troveDebt, + `Trove debt should have increased by at least ${borrowAmount} ETH` + ).to.gte(expectedTroveDebt); + }); + + it("adjusts a Trove: withdraw ETH and repay LUSD", async () => { + await helpers.createDsaTrove( + dsa, + wallet, + liquity.hintHelpers, + liquity.sortedTroves + ); + + const originalTroveCollateral = await liquity.troveManager.getTroveColl( + dsa.address + ); + const originalTroveDebt = await liquity.troveManager.getTroveDebt( + dsa.address + ); + const depositAmount = 0; + const borrowAmount = 0; + const withdrawAmount = ethers.utils.parseEther("1"); // 1 ETH; + const repayAmount = ethers.utils.parseUnits("500", 18); // 500 LUSD; + const { upperHint, lowerHint } = await helpers.getTroveInsertionHints( + originalTroveCollateral.sub(withdrawAmount), + originalTroveDebt.sub(repayAmount), + liquity.hintHelpers, + liquity.sortedTroves + ); + const maxFeePercentage = ethers.utils.parseUnits("0.5", 18); // 0.5% max fee + + const adjustSpell = { + connector: helpers.CONNECTOR_NAME, + method: "adjust", + args: [ + maxFeePercentage, + withdrawAmount, + depositAmount, + borrowAmount, + repayAmount, + upperHint, + lowerHint, + 0, + 0, + 0, + 0, + ], + }; + + const adjustTx = await dsa + .connect(wallet) + .cast(...encodeSpells([adjustSpell]), wallet.address, { + value: depositAmount, + gasLimit: helpers.MAX_GAS, + }); + + await adjustTx.wait(); + const troveCollateral = await liquity.troveManager.getTroveColl( + dsa.address + ); + const troveDebt = await liquity.troveManager.getTroveDebt( + dsa.address + ); + const expectedTroveColl = originalTroveCollateral.sub(withdrawAmount); + const expectedTroveDebt = originalTroveDebt.sub(repayAmount); + + expect( + troveCollateral, + `Trove collateral should have increased by ${depositAmount} ETH` + ).to.eq(expectedTroveColl); + + expect( + troveDebt, + `Trove debt should have increased by at least ${borrowAmount} ETH` + ).to.gte(expectedTroveDebt); + }); + + it("returns Instadapp event name and data", async () => { + await helpers.createDsaTrove( + dsa, + wallet, + liquity.hintHelpers, + liquity.sortedTroves + ); + + const depositAmount = ethers.utils.parseEther("1"); // 1 ETH + const borrowAmount = ethers.utils.parseUnits("500", 18); // 500 LUSD + const withdrawAmount = 0; + const repayAmount = 0; + const upperHint = ethers.constants.AddressZero; + const lowerHint = ethers.constants.AddressZero; + const maxFeePercentage = ethers.utils.parseUnits("0.5", 18); // 0.5% max fee + const getDepositId = 0; + const setWithdrawId = 0; + const getRepayId = 0; + const setBorrowId = 0; + + const adjustSpell = { + connector: helpers.CONNECTOR_NAME, + method: "adjust", + args: [ + maxFeePercentage, + withdrawAmount, + depositAmount, + borrowAmount, + repayAmount, + upperHint, + lowerHint, + getDepositId, + setWithdrawId, + getRepayId, + setBorrowId, + ], + }; + + const adjustTx = await dsa + .connect(wallet) + .cast(...encodeSpells([adjustSpell]), wallet.address, { + value: depositAmount, + gasLimit: helpers.MAX_GAS, + }); + + const receipt = await adjustTx.wait(); + const castLogEvent = receipt.events.find((e) => e.event === "LogCast") + .args; + const expectedEventParams = ethers.utils.defaultAbiCoder.encode( + [ + "address", + "uint256", + "uint256", + "uint256", + "uint256", + "uint256", + "uint256", + "uint256", + "uint256", + "uint256", + ], + [ + wallet.address, + maxFeePercentage, + depositAmount, + withdrawAmount, + borrowAmount, + repayAmount, + getDepositId, + setWithdrawId, + getRepayId, + setBorrowId, + ] + ); + expect(castLogEvent.eventNames[0]).eq( + "LogAdjust(address,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256)" + ); + expect(castLogEvent.eventParams[0]).eq(expectedEventParams); + }); + }); + + describe("claim()", () => { + it("claims collateral from a redeemed Trove", async () => { + // Create a low collateralized Trove + const depositAmount = ethers.utils.parseEther("1.5"); + const borrowAmount = ethers.utils.parseUnits("2500", 18); + + await helpers.createDsaTrove( + dsa, + wallet, + liquity.hintHelpers, + liquity.sortedTroves, + depositAmount, + borrowAmount + ); + + // Redeem lots of LUSD to cause the Trove to become redeemed + const redeemAmount = ethers.utils.parseUnits("10000000", 18); + await helpers.sendToken( + liquity.lusdToken, redeemAmount, + contracts.STABILITY_POOL_ADDRESS, + wallet.address + ); + const { + partialRedemptionHintNicr, firstRedemptionHint, upperHint, lowerHint, - partialRedemptionHintNicr, - 0, - maxFeePercentage, - { - gasLimit: helpers.MAX_GAS, // permit max gas - } + } = await helpers.getRedemptionHints( + redeemAmount, + liquity.hintHelpers, + liquity.sortedTroves, + liquity.priceFeed + ); + const maxFeePercentage = ethers.utils.parseUnits("0.5", 18); // 0.5% max fee + + await liquity.troveManager + .connect(wallet) + .redeemCollateral( + redeemAmount, + firstRedemptionHint, + upperHint, + lowerHint, + partialRedemptionHintNicr, + 0, + maxFeePercentage, + { + gasLimit: helpers.MAX_GAS, // permit max gas + } + ); + + const ethBalanceBefore = await ethers.provider.getBalance( + dsa.address ); - const ethBalanceBefore = await ethers.provider.getBalance(dsa.address); + // Claim the remaining collateral from the redeemed Trove + const claimCollateralFromRedemptionSpell = { + connector: helpers.CONNECTOR_NAME, + method: "claimCollateralFromRedemption", + args: [0], + }; - // Claim the remaining collateral from the redeemed Trove - const claimCollateralFromRedemptionSpell = { - connector: helpers.CONNECTOR_NAME, - method: "claimCollateralFromRedemption", - args: [], - }; + const claimTx = await dsa + .connect(wallet) + .cast( + ...encodeSpells([claimCollateralFromRedemptionSpell]), + wallet.address + ); - const claimTx = await dsa - .connect(wallet) - .cast( - ...encodeSpells([claimCollateralFromRedemptionSpell]), + await claimTx.wait(); + + const ethBalanceAfter = await ethers.provider.getBalance(dsa.address); + + const expectedRemainingCollateral = "527014573774047160"; // ~0.52 ETH based on this mainnet fork's blockNumber + expect(ethBalanceAfter).to.be.gt(ethBalanceBefore); + expect(ethBalanceAfter).to.eq(expectedRemainingCollateral); + }); + + it("returns Instadapp event name and data", async () => { + // Create a low collateralized Trove + const depositAmount = ethers.utils.parseEther("1.5"); + const borrowAmount = ethers.utils.parseUnits("2500", 18); + + await helpers.createDsaTrove( + dsa, + wallet, + liquity.hintHelpers, + liquity.sortedTroves, + depositAmount, + borrowAmount + ); + + // Redeem lots of LUSD to cause the Trove to become redeemed + const redeemAmount = ethers.utils.parseUnits("10000000", 18); + const setId = 0; + await helpers.sendToken( + liquity.lusdToken, + redeemAmount, + contracts.STABILITY_POOL_ADDRESS, wallet.address ); + const { + partialRedemptionHintNicr, + firstRedemptionHint, + upperHint, + lowerHint, + } = await helpers.getRedemptionHints( + redeemAmount, + liquity.hintHelpers, + liquity.sortedTroves, + liquity.priceFeed + ); + const maxFeePercentage = ethers.utils.parseUnits("0.5", 18); // 0.5% max fee - await claimTx.wait(); + await liquity.troveManager + .connect(wallet) + .redeemCollateral( + redeemAmount, + firstRedemptionHint, + upperHint, + lowerHint, + partialRedemptionHintNicr, + 0, + maxFeePercentage, + { + gasLimit: helpers.MAX_GAS, // permit max gas + } + ); + const claimAmount = await liquity.collSurplus.getCollateral( + dsa.address + ); - const ethBalanceAfter = await ethers.provider.getBalance(dsa.address); + const claimCollateralFromRedemptionSpell = { + connector: helpers.CONNECTOR_NAME, + method: "claimCollateralFromRedemption", + args: [setId], + }; - const expectedRemainingCollateral = "527014573774047160"; // ~0.25 ETH based on this mainnet fork's blockNumber - expect(ethBalanceAfter).to.be.gt(ethBalanceBefore); - expect(ethBalanceAfter).to.eq(expectedRemainingCollateral); + const claimTx = await dsa + .connect(wallet) + .cast( + ...encodeSpells([claimCollateralFromRedemptionSpell]), + wallet.address + ); + + const receipt = await claimTx.wait(); + const castLogEvent = receipt.events.find((e) => e.event === "LogCast") + .args; + const expectedEventParams = ethers.utils.defaultAbiCoder.encode( + ["address", "uint256", "uint256"], + [wallet.address, claimAmount, setId] + ); + expect(castLogEvent.eventNames[0]).eq( + "LogClaimCollateralFromRedemption(address,uint256,uint256)" + ); + expect(castLogEvent.eventParams[0]).eq(expectedEventParams); + }); }); }); describe("Stability Pool", () => { - it("deposits into Stability Pool", async () => { - const amount = ethers.utils.parseUnits("100", 18); - const frontendTag = ethers.constants.AddressZero; + describe("stabilityDeposit()", () => { + it("deposits into Stability Pool", async () => { + const amount = ethers.utils.parseUnits("100", 18); + const frontendTag = ethers.constants.AddressZero; - await helpers.sendToken( - liquity.lusdToken, - amount, - contracts.STABILITY_POOL_ADDRESS, - dsa.address - ); + await helpers.sendToken( + liquity.lusdToken, + amount, + contracts.STABILITY_POOL_ADDRESS, + dsa.address + ); - const stabilityDepositSpell = { - connector: helpers.CONNECTOR_NAME, - method: "stabilityDeposit", - args: [amount, frontendTag, 0], - }; + const stabilityDepositSpell = { + connector: helpers.CONNECTOR_NAME, + method: "stabilityDeposit", + args: [amount, frontendTag, 0], + }; - const depositTx = await dsa - .connect(wallet) - .cast(...encodeSpells([stabilityDepositSpell]), wallet.address); + const depositTx = await dsa + .connect(wallet) + .cast(...encodeSpells([stabilityDepositSpell]), wallet.address); - await depositTx.wait(); - const depositedAmount = await liquity.stabilityPool.getCompoundedLUSDDeposit( - dsa.address - ); - expect(depositedAmount).to.eq(amount); - }); - - it("withdraws from Stability Pool", async () => { - // The current block number has liquidatable Troves. - // Remove them otherwise Stability Pool withdrawals are disabled - await liquity.troveManager.connect(wallet).liquidateTroves(90, { - gasLimit: helpers.MAX_GAS, + await depositTx.wait(); + const depositedAmount = await liquity.stabilityPool.getCompoundedLUSDDeposit( + dsa.address + ); + expect(depositedAmount).to.eq(amount); }); - const amount = ethers.utils.parseUnits("100", 18); - const frontendTag = ethers.constants.AddressZero; - await helpers.sendToken( - liquity.lusdToken, - amount, - contracts.STABILITY_POOL_ADDRESS, - dsa.address - ); + it("returns Instadapp event name and data", async () => { + const amount = ethers.utils.parseUnits("100", 18); + const frontendTag = ethers.constants.AddressZero; + const getId = 0; + await helpers.sendToken( + liquity.lusdToken, + amount, + contracts.STABILITY_POOL_ADDRESS, + dsa.address + ); - const stabilityDepositSpell = { - connector: helpers.CONNECTOR_NAME, - method: "stabilityDeposit", - args: [amount, frontendTag, 0], - }; + const stabilityDepositSpell = { + connector: helpers.CONNECTOR_NAME, + method: "stabilityDeposit", + args: [amount, frontendTag, getId], + }; - // Withdraw half of the deposit - const stabilitWithdrawSpell = { - connector: helpers.CONNECTOR_NAME, - method: "stabilityWithdraw", - args: [amount.div(2), 0], - }; - const spells = [stabilityDepositSpell, stabilitWithdrawSpell]; + const depositTx = await dsa + .connect(wallet) + .cast(...encodeSpells([stabilityDepositSpell]), wallet.address); - const spellsTx = await dsa - .connect(wallet) - .cast(...encodeSpells(spells), wallet.address); - - await spellsTx.wait(); - - const depositedAmount = await liquity.stabilityPool.getCompoundedLUSDDeposit( - dsa.address - ); - const dsaLusdBalance = await liquity.lusdToken.balanceOf(dsa.address); - - expect(depositedAmount).to.eq(amount.div(2)); - expect(dsaLusdBalance).to.eq(amount.div(2)); + const receipt = await depositTx.wait(); + const castLogEvent = receipt.events.find((e) => e.event === "LogCast") + .args; + const expectedEventParams = ethers.utils.defaultAbiCoder.encode( + ["address", "uint256", "address", "uint256"], + [wallet.address, amount, frontendTag, getId] + ); + expect(castLogEvent.eventNames[0]).eq( + "LogStabilityDeposit(address,uint256,address,uint256)" + ); + expect(castLogEvent.eventParams[0]).eq(expectedEventParams); + }); }); - it("moves ETH gain from Stability Pool to Trove", async () => { - // Start this test from fresh so that we definitely have a liquidatable Trove within this block - liqiuty = await helpers.deployAndConnect(contracts); + describe("stabilityWithdraw()", () => { + it("withdraws from Stability Pool", async () => { + // Start this test from scratch since we don't want to rely on test order for this to pass. + [liquity, dsa] = await helpers.resetInitialState( + wallet.address, + contracts + ); - // Create a DSA owned Trove to capture ETH liquidation gains - await helpers.createDsaTrove( - dsa, - wallet, - liquity.hintHelpers, - liquity.sortedTroves - ); - const troveCollateralBefore = await liquity.troveManager.getTroveColl( - dsa.address - ); - - // Create a Stability Deposit using the Trove's borrowed LUSD - const amount = ethers.utils.parseUnits("100", 18); - const frontendTag = ethers.constants.AddressZero; - const stabilityDepositSpell = { - connector: helpers.CONNECTOR_NAME, - method: "stabilityDeposit", - args: [amount, frontendTag, 0], - }; - - const depositTx = await dsa - .connect(wallet) - .cast(...encodeSpells([stabilityDepositSpell]), wallet.address); - - await depositTx.wait(); - - // Liquidate a Trove to create an ETH gain for the new DSA Trove - await liquity.troveManager - .connect(wallet) - .liquidate(helpers.LIQUIDATABLE_TROVE_ADDRESS, { - gasLimit: helpers.MAX_GAS, // permit max gas + // The current block number has liquidatable Troves. + // Remove them otherwise Stability Pool withdrawals are disabled + await liquity.troveManager.connect(wallet).liquidateTroves(90, { + gasLimit: helpers.MAX_GAS, }); + const amount = ethers.utils.parseUnits("100", 18); + const frontendTag = ethers.constants.AddressZero; - const ethGainFromLiquidation = await liquity.stabilityPool.getDepositorETHGain( - dsa.address - ); + await helpers.sendToken( + liquity.lusdToken, + amount, + contracts.STABILITY_POOL_ADDRESS, + dsa.address + ); - // Move ETH gain to Trove - const moveEthGainSpell = { - connector: helpers.CONNECTOR_NAME, - method: "stabilityMoveEthGainToTrove", - args: [ethers.constants.AddressZero, ethers.constants.AddressZero], - }; + const stabilityDepositSpell = { + connector: helpers.CONNECTOR_NAME, + method: "stabilityDeposit", + args: [amount, frontendTag, 0], + }; - const moveEthGainTx = await dsa - .connect(wallet) - .cast(...encodeSpells([moveEthGainSpell]), wallet.address); + // Withdraw half of the deposit + const stabilitWithdrawSpell = { + connector: helpers.CONNECTOR_NAME, + method: "stabilityWithdraw", + args: [amount.div(2), 0], + }; + const spells = [stabilityDepositSpell, stabilitWithdrawSpell]; - await moveEthGainTx.wait(); + const castTx = await dsa + .connect(wallet) + .cast(...encodeSpells(spells), wallet.address); - const ethGainAfterMove = await liquity.stabilityPool.getDepositorETHGain( - dsa.address - ); - const troveCollateral = await liquity.troveManager.getTroveColl( - dsa.address - ); - const expectedTroveCollateral = troveCollateralBefore.add( - ethGainFromLiquidation - ); + await castTx.wait(); - expect(ethGainAfterMove).to.eq(0); - expect(troveCollateral).to.eq(expectedTroveCollateral); + const depositedAmount = await liquity.stabilityPool.getCompoundedLUSDDeposit( + dsa.address + ); + const dsaLusdBalance = await liquity.lusdToken.balanceOf(dsa.address); + + expect(depositedAmount).to.eq(amount.div(2)); + expect(dsaLusdBalance).to.eq(amount.div(2)); + }); + + it("returns Instadapp event name and data", async () => { + // Start this test from scratch since we don't want to rely on test order for this to pass. + [liquity, dsa] = await helpers.resetInitialState( + wallet.address, + contracts + ); + + // The current block number has liquidatable Troves. + // Remove them otherwise Stability Pool withdrawals are disabled + await liquity.troveManager.connect(wallet).liquidateTroves(90, { + gasLimit: helpers.MAX_GAS, + }); + const amount = ethers.utils.parseUnits("100", 18); + const frontendTag = ethers.constants.AddressZero; + const setId = 0; + + await helpers.sendToken( + liquity.lusdToken, + amount, + contracts.STABILITY_POOL_ADDRESS, + dsa.address + ); + + const stabilityDepositSpell = { + connector: helpers.CONNECTOR_NAME, + method: "stabilityDeposit", + args: [amount, frontendTag, setId], + }; + + // Withdraw half of the deposit + const withdrawAmount = amount.div(2); + const stabilitWithdrawSpell = { + connector: helpers.CONNECTOR_NAME, + method: "stabilityWithdraw", + args: [withdrawAmount, 0], + }; + const spells = [stabilityDepositSpell, stabilitWithdrawSpell]; + + const castTx = await dsa + .connect(wallet) + .cast(...encodeSpells(spells), wallet.address); + + const receipt = await castTx.wait(); + const castLogEvent = receipt.events.find((e) => e.event === "LogCast") + .args; + const expectedEventParams = ethers.utils.defaultAbiCoder.encode( + ["address", "uint256", "uint256"], + [wallet.address, withdrawAmount, setId] + ); + expect(castLogEvent.eventNames[1]).eq( + "LogStabilityWithdraw(address,uint256,uint256)" + ); + expect(castLogEvent.eventParams[1]).eq(expectedEventParams); + }); + }); + + describe("stabilityMoveEthGainToTrove()", () => { + beforeEach(async () => { + // Start these test from fresh so that we definitely have a liquidatable Trove within this block + [liquity, dsa] = await helpers.resetInitialState( + wallet.address, + contracts + ); + }); + + it("moves ETH gain from Stability Pool to Trove", async () => { + // Create a DSA owned Trove to capture ETH liquidation gains + await helpers.createDsaTrove( + dsa, + wallet, + liquity.hintHelpers, + liquity.sortedTroves + ); + const troveCollateralBefore = await liquity.troveManager.getTroveColl( + dsa.address + ); + + // Create a Stability Deposit using the Trove's borrowed LUSD + const amount = ethers.utils.parseUnits("100", 18); + const frontendTag = ethers.constants.AddressZero; + const stabilityDepositSpell = { + connector: helpers.CONNECTOR_NAME, + method: "stabilityDeposit", + args: [amount, frontendTag, 0], + }; + + const depositTx = await dsa + .connect(wallet) + .cast(...encodeSpells([stabilityDepositSpell]), wallet.address); + + await depositTx.wait(); + + // Liquidate a Trove to create an ETH gain for the new DSA Trove + await liquity.troveManager + .connect(wallet) + .liquidate(helpers.LIQUIDATABLE_TROVE_ADDRESS, { + gasLimit: helpers.MAX_GAS, // permit max gas + }); + + const ethGainFromLiquidation = await liquity.stabilityPool.getDepositorETHGain( + dsa.address + ); + + // Move ETH gain to Trove + const moveEthGainSpell = { + connector: helpers.CONNECTOR_NAME, + method: "stabilityMoveEthGainToTrove", + args: [ethers.constants.AddressZero, ethers.constants.AddressZero], + }; + + const moveEthGainTx = await dsa + .connect(wallet) + .cast(...encodeSpells([moveEthGainSpell]), wallet.address); + + await moveEthGainTx.wait(); + + const ethGainAfterMove = await liquity.stabilityPool.getDepositorETHGain( + dsa.address + ); + const troveCollateral = await liquity.troveManager.getTroveColl( + dsa.address + ); + const expectedTroveCollateral = troveCollateralBefore.add( + ethGainFromLiquidation + ); + expect(ethGainAfterMove).to.eq(0); + expect(troveCollateral).to.eq(expectedTroveCollateral); + }); + + it("returns Instadapp event name and data", async () => { + // Create a DSA owned Trove to capture ETH liquidation gains + await helpers.createDsaTrove( + dsa, + wallet, + liquity.hintHelpers, + liquity.sortedTroves + ); + + // Create a Stability Deposit using the Trove's borrowed LUSD + const amount = ethers.utils.parseUnits("100", 18); + const frontendTag = ethers.constants.AddressZero; + const stabilityDepositSpell = { + connector: helpers.CONNECTOR_NAME, + method: "stabilityDeposit", + args: [amount, frontendTag, 0], + }; + + const depositTx = await dsa + .connect(wallet) + .cast(...encodeSpells([stabilityDepositSpell]), wallet.address); + + await depositTx.wait(); + + // Liquidate a Trove to create an ETH gain for the new DSA Trove + await liquity.troveManager + .connect(wallet) + .liquidate(helpers.LIQUIDATABLE_TROVE_ADDRESS, { + gasLimit: helpers.MAX_GAS, // permit max gas + }); + + const ethGainFromLiquidation = await liquity.stabilityPool.getDepositorETHGain( + dsa.address + ); + + // Move ETH gain to Trove + const moveEthGainSpell = { + connector: helpers.CONNECTOR_NAME, + method: "stabilityMoveEthGainToTrove", + args: [ethers.constants.AddressZero, ethers.constants.AddressZero], + }; + + const moveEthGainTx = await dsa + .connect(wallet) + .cast(...encodeSpells([moveEthGainSpell]), wallet.address); + + const receipt = await moveEthGainTx.wait(); + + const castLogEvent = receipt.events.find((e) => e.event === "LogCast") + .args; + const expectedEventParams = ethers.utils.defaultAbiCoder.encode( + ["address", "uint256"], + [wallet.address, ethGainFromLiquidation] + ); + expect(castLogEvent.eventNames[0]).eq( + "LogStabilityMoveEthGainToTrove(address,uint256)" + ); + expect(castLogEvent.eventParams[0]).eq(expectedEventParams); + }); }); }); describe("Staking", () => { - it("stakes LQTY", async () => { - const totalStakingBalanceBefore = await liquity.lqtyToken.balanceOf( - contracts.STAKING_ADDRESS - ); + describe("stake()", () => { + it("stakes LQTY", async () => { + const totalStakingBalanceBefore = await liquity.lqtyToken.balanceOf( + contracts.STAKING_ADDRESS + ); - const amount = ethers.utils.parseUnits("1", 18); - await helpers.sendToken( - liquity.lqtyToken, - amount, - helpers.JUSTIN_SUN_ADDRESS, - dsa.address - ); + const amount = ethers.utils.parseUnits("1", 18); + await helpers.sendToken( + liquity.lqtyToken, + amount, + helpers.JUSTIN_SUN_ADDRESS, + dsa.address + ); - const stakeSpell = { - connector: helpers.CONNECTOR_NAME, - method: "stake", - args: [amount, 0], - }; + const stakeSpell = { + connector: helpers.CONNECTOR_NAME, + method: "stake", + args: [amount, 0], + }; - const stakeTx = await dsa - .connect(wallet) - .cast(...encodeSpells([stakeSpell]), wallet.address); + const stakeTx = await dsa + .connect(wallet) + .cast(...encodeSpells([stakeSpell]), wallet.address); - await stakeTx.wait(); + await stakeTx.wait(); - const lqtyBalance = await liquity.lqtyToken.balanceOf(dsa.address); - expect(lqtyBalance).to.eq(0); + const lqtyBalance = await liquity.lqtyToken.balanceOf(dsa.address); + expect(lqtyBalance).to.eq(0); - const totalStakingBalance = await liquity.lqtyToken.balanceOf( - contracts.STAKING_ADDRESS - ); - expect(totalStakingBalance).to.eq( - totalStakingBalanceBefore.add(amount) - ); + const totalStakingBalance = await liquity.lqtyToken.balanceOf( + contracts.STAKING_ADDRESS + ); + expect(totalStakingBalance).to.eq( + totalStakingBalanceBefore.add(amount) + ); + }); + + it("returns Instadapp event name and data", async () => { + const amount = ethers.utils.parseUnits("1", 18); + await helpers.sendToken( + liquity.lqtyToken, + amount, + helpers.JUSTIN_SUN_ADDRESS, + dsa.address + ); + const getId = 0; + + const stakeSpell = { + connector: helpers.CONNECTOR_NAME, + method: "stake", + args: [amount, getId], + }; + + const stakeTx = await dsa + .connect(wallet) + .cast(...encodeSpells([stakeSpell]), wallet.address); + + const receipt = await stakeTx.wait(); + + const castLogEvent = receipt.events.find((e) => e.event === "LogCast") + .args; + const expectedEventParams = ethers.utils.defaultAbiCoder.encode( + ["address", "uint256", "uint256"], + [wallet.address, amount, getId] + ); + expect(castLogEvent.eventNames[0]).eq( + "LogStake(address,uint256,uint256)" + ); + expect(castLogEvent.eventParams[0]).eq(expectedEventParams); + }); }); - it("unstakes LQTY", async () => { - const amount = ethers.utils.parseUnits("1", 18); - await helpers.sendToken( - liquity.lqtyToken, - amount, - helpers.JUSTIN_SUN_ADDRESS, - dsa.address - ); + describe("unstake()", () => { + it("unstakes LQTY", async () => { + const amount = ethers.utils.parseUnits("1", 18); + await helpers.sendToken( + liquity.lqtyToken, + amount, + helpers.JUSTIN_SUN_ADDRESS, + dsa.address + ); - const stakeSpell = { - connector: helpers.CONNECTOR_NAME, - method: "stake", - args: [amount, 0], - }; + const stakeSpell = { + connector: helpers.CONNECTOR_NAME, + method: "stake", + args: [amount, 0], + }; - const stakeTx = await dsa - .connect(wallet) - .cast(...encodeSpells([stakeSpell]), wallet.address); + const stakeTx = await dsa + .connect(wallet) + .cast(...encodeSpells([stakeSpell]), wallet.address); - await stakeTx.wait(); + await stakeTx.wait(); - const totalStakingBalanceBefore = await liquity.lqtyToken.balanceOf( - contracts.STAKING_ADDRESS - ); + const totalStakingBalanceBefore = await liquity.lqtyToken.balanceOf( + contracts.STAKING_ADDRESS + ); - const unstakeSpell = { - connector: helpers.CONNECTOR_NAME, - method: "unstake", - args: [amount, 0], - }; + const unstakeSpell = { + connector: helpers.CONNECTOR_NAME, + method: "unstake", + args: [amount, 0], + }; - const unstakeTx = await dsa - .connect(wallet) - .cast(...encodeSpells([unstakeSpell]), wallet.address); + const unstakeTx = await dsa + .connect(wallet) + .cast(...encodeSpells([unstakeSpell]), wallet.address); - await unstakeTx.wait(); + await unstakeTx.wait(); - const lqtyBalance = await liquity.lqtyToken.balanceOf(dsa.address); - expect(lqtyBalance).to.eq(amount); + const lqtyBalance = await liquity.lqtyToken.balanceOf(dsa.address); + expect(lqtyBalance).to.eq(amount); - const totalStakingBalance = await liquity.lqtyToken.balanceOf( - contracts.STAKING_ADDRESS - ); - expect(totalStakingBalance).to.eq( - totalStakingBalanceBefore.sub(amount) - ); + const totalStakingBalance = await liquity.lqtyToken.balanceOf( + contracts.STAKING_ADDRESS + ); + expect(totalStakingBalance).to.eq( + totalStakingBalanceBefore.sub(amount) + ); + }); + + it("returns Instadapp event name and data", async () => { + const amount = ethers.utils.parseUnits("1", 18); + await helpers.sendToken( + liquity.lqtyToken, + amount, + helpers.JUSTIN_SUN_ADDRESS, + dsa.address + ); + + const stakeSpell = { + connector: helpers.CONNECTOR_NAME, + method: "stake", + args: [amount, 0], + }; + + await dsa + .connect(wallet) + .cast(...encodeSpells([stakeSpell]), wallet.address); + + const setId = 0; + const unstakeSpell = { + connector: helpers.CONNECTOR_NAME, + method: "unstake", + args: [amount, setId], + }; + + const unstakeTx = await dsa + .connect(wallet) + .cast(...encodeSpells([unstakeSpell]), wallet.address); + + const receipt = await unstakeTx.wait(); + + const castLogEvent = receipt.events.find((e) => e.event === "LogCast") + .args; + const expectedEventParams = ethers.utils.defaultAbiCoder.encode( + ["address", "uint256", "uint256"], + [wallet.address, amount, setId] + ); + expect(castLogEvent.eventNames[0]).eq( + "LogUnstake(address,uint256,uint256)" + ); + expect(castLogEvent.eventParams[0]).eq(expectedEventParams); + }); + }); + + describe.skip("claimStakingGains()", () => { + it("Claims gains from staking", async () => {}); + + it("returns Instadapp event name and data", async () => { + const stakerDsa = await buildDSAv2(wallet.address); + const whaleLqtyBalance = await liquity.lqtyToken.balanceOf( + helpers.JUSTIN_SUN_ADDRESS + ); + console.log("BALANCE", whaleLqtyBalance.toString()); + + // Stake lots of LQTY + await helpers.sendToken( + liquity.lqtyToken, + whaleLqtyBalance, + helpers.JUSTIN_SUN_ADDRESS, + stakerDsa.address + ); + const dsaBalance = await liquity.lqtyToken.balanceOf( + stakerDsa.address + ); + console.log("dsaBalance", dsaBalance.toString()); + await liquity.staking + .connect(stakerDsa.signer) + .stake(ethers.utils.parseUnits("1", 18)); + + // Open a Trove to cause an ETH issuance gain for stakers + await helpers.createDsaTrove( + dsa, + wallet, + liqiuty.hintHelpers, + liquity.sortedTroves + ); + + // Redeem some ETH to cause an LUSD redemption gain for stakers + await helpers.redeem( + ethers.utils.parseUnits("1000", 18), + contracts.STABILITY_POOL_ADDRESS, + wallet.address, + liquity + ); + + const setEthGainId = 0; + const setLusdGainId = 0; + const claimStakingGainsSpell = { + connector: helpers.CONNECTOR_NAME, + method: "claimStakingGains", + args: [setEthGainId, setLusdGainId], + }; + + const claimGainsTx = await stakerDsa + .connect(wallet) + .cast(...encodeSpells([claimStakingGainsSpell]), wallet.address); + + const receipt = await claimGainsTx.wait(); + + const castLogEvent = receipt.events.find((e) => e.event === "LogCast") + .args; + const expectedEventParams = ethers.utils.defaultAbiCoder.encode( + ["address", "uint256", "uint256"], + [helpers.JUSTIN_SUN_ADDRESS, setEthGainId, setLusdGainId] + ); + expect(castLogEvent.eventNames[0]).eq( + "LogClaimStakingGains(address,uint256,uint256)" + ); + expect(castLogEvent.eventParams[0]).eq(expectedEventParams); + }); }); }); }); }); - -// TODO add set of tests to verify log return values are generated correctly From 5be5cb68668172047e29323092c574d4155f80f8 Mon Sep 17 00:00:00 2001 From: Edward Mulraney Date: Fri, 4 Jun 2021 15:55:04 +0100 Subject: [PATCH 04/19] include all gains from staking/stability-pool when performing operations on them --- .../mainnet/connectors/liquity/events.sol | 22 +- .../mainnet/connectors/liquity/interface.sol | 1 + contracts/mainnet/connectors/liquity/main.sol | 98 ++++++--- test/liquity/liquity.contracts.js | 2 + test/liquity/liquity.helpers.js | 4 +- test/liquity/liquity.test.js | 202 +++++++++++++----- 6 files changed, 240 insertions(+), 89 deletions(-) diff --git a/contracts/mainnet/connectors/liquity/events.sol b/contracts/mainnet/connectors/liquity/events.sol index 196df9ee..e16c771b 100644 --- a/contracts/mainnet/connectors/liquity/events.sol +++ b/contracts/mainnet/connectors/liquity/events.sol @@ -31,12 +31,24 @@ contract Events { event LogClaimCollateralFromRedemption(address indexed borrower, uint amount, uint setId); /* Stability Pool */ - event LogStabilityDeposit(address indexed borrower, uint amount, address frontendTag, uint getId); - event LogStabilityWithdraw(address indexed borrower, uint amount, uint setId); + event LogStabilityDeposit( + address indexed borrower, + uint amount, + address frontendTag, + uint getDepositId, + uint setEthGainId, + uint setLqtyGainId + ); + event LogStabilityWithdraw(address indexed borrower, + uint amount, + uint setWithdrawId, + uint setEthGainId, + uint setLqtyGainId + ); event LogStabilityMoveEthGainToTrove(address indexed borrower, uint amount); /* Staking */ - event LogStake(address indexed borrower, uint amount, uint getId); - event LogUnstake(address indexed borrower, uint amount, uint setId); - event LogClaimStakingGains(address indexed borrower, uint ethAmount, uint lusdAmount); + 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); } diff --git a/contracts/mainnet/connectors/liquity/interface.sol b/contracts/mainnet/connectors/liquity/interface.sol index 72e91e98..7ad22f18 100644 --- a/contracts/mainnet/connectors/liquity/interface.sol +++ b/contracts/mainnet/connectors/liquity/interface.sol @@ -53,6 +53,7 @@ interface StabilityPoolLike { 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 { diff --git a/contracts/mainnet/connectors/liquity/main.sol b/contracts/mainnet/connectors/liquity/main.sol index 4550bb1b..fd670209 100644 --- a/contracts/mainnet/connectors/liquity/main.sol +++ b/contracts/mainnet/connectors/liquity/main.sol @@ -276,36 +276,53 @@ abstract contract LiquityResolver is Events, Helpers { * @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 getId Optional storage slot to retrieve the LUSD from + * @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 ETH gains in */ function stabilityDeposit( uint amount, address frontendTag, - uint getId + uint getDepositId, + uint setEthGainId, + uint setLqtyGainId ) external returns (string memory _eventName, bytes memory _eventParam) { - amount = getUint(getId, amount); - - stabilityPool.provideToSP(amount, frontendTag); + amount = getUint(getDepositId, amount); + uint ethGain = stabilityPool.getDepositorETHGain(address(this)); + uint lqtyGain = stabilityPool.getDepositorLQTYGain(address(this)); - _eventName = "LogStabilityDeposit(address,uint256,address,uint256)"; - _eventParam = abi.encode(msg.sender, amount, frontendTag, getId); + stabilityPool.provideToSP(amount, frontendTag); + setUint(setEthGainId, ethGain); + setUint(setLqtyGainId, lqtyGain); + + _eventName = "LogStabilityDeposit(address,uint256,address,uint256,uint256,uint256)"; + _eventParam = abi.encode(msg.sender, amount, 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 setId Optional storage slot to store the withdrawn LUSD + * @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 ETH gains in */ function stabilityWithdraw( uint amount, - uint setId + uint setWithdrawId, + uint setEthGainId, + uint setLqtyGainId ) external returns (string memory _eventName, bytes memory _eventParam) { stabilityPool.withdrawFromSP(amount); - setUint(setId, amount); + uint ethGain = stabilityPool.getDepositorETHGain(address(this)); + uint lqtyGain = stabilityPool.getDepositorLQTYGain(address(this)); + + setUint(setWithdrawId, amount); + setUint(setEthGainId, ethGain); + setUint(setLqtyGainId, lqtyGain); - _eventName = "LogStabilityWithdraw(address,uint256,uint256)"; - _eventParam = abi.encode(msg.sender, amount, setId); + _eventName = "LogStabilityWithdraw(address,uint256,uint256,uint256,uint256)"; + _eventParam = abi.encode(msg.sender, amount, setWithdrawId, setEthGainId, setLqtyGainId); } /** @@ -331,32 +348,52 @@ abstract contract LiquityResolver is Events, Helpers { * @dev Sends LQTY tokens from user to Staking Pool * @notice Stake LQTY in Staking Pool * @param amount Amount of LQTY to stake - * @param getId Optional storage slot to retrieve the LQTY from + * @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 getId + uint getStakeId, + uint setEthGainId, + uint setLusdGainId ) external returns (string memory _eventName, bytes memory _eventParam) { - amount = getUint(getId, amount); + uint ethGain = staking.getPendingETHGain(address(this)); + uint lusdGain = staking.getPendingLUSDGain(address(this)); + + amount = getUint(getStakeId, amount); staking.stake(amount); - _eventName = "LogStake(address,uint256,uint256)"; - _eventParam = abi.encode(msg.sender, amount, getId); + 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 setId Optional storage slot to store the unstaked LQTY + * @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 setId + 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(setId, amount); - _eventName = "LogUnstake(address,uint256,uint256)"; - _eventParam = abi.encode(msg.sender, amount, setId); + 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); } /** @@ -365,17 +402,20 @@ abstract contract LiquityResolver is Events, Helpers { * @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 ethAmount = staking.getPendingETHGain(address(this)); - uint lusdAmount = staking.getPendingLUSDGain(address(this)); + 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, ethAmount); - setUint(setLusdGainId, lusdAmount); + setUint(setEthGainId, ethGain); + setUint(setLusdGainId, lusdGain); - _eventName = "LogClaimStakingGains(address,uint256,uint256)"; - _eventParam = abi.encode(msg.sender, ethAmount, lusdAmount); + _eventName = "LogClaimStakingGains(address,uint256,uint256,uint256,uint256)"; + _eventParam = abi.encode(msg.sender, ethGain, lusdGain, setEthGainId, setLusdGainId); } /* End: Staking */ diff --git a/test/liquity/liquity.contracts.js b/test/liquity/liquity.contracts.js index d73407c5..9c978e1f 100644 --- a/test/liquity/liquity.contracts.js +++ b/test/liquity/liquity.contracts.js @@ -52,6 +52,8 @@ 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"; diff --git a/test/liquity/liquity.helpers.js b/test/liquity/liquity.helpers.js index 07a94ee3..c2c6799d 100644 --- a/test/liquity/liquity.helpers.js +++ b/test/liquity/liquity.helpers.js @@ -327,8 +327,8 @@ const getRedemptionHints = async ( }; }; -const redeem = async (amount, from, to, liquity) => { - await sendToken(liquity.lusdToken, amount, from, to); +const redeem = async (amount, from, wallet, liquity) => { + await sendToken(liquity.lusdToken, amount, from, wallet.address); const { partialRedemptionHintNicr, firstRedemptionHint, diff --git a/test/liquity/liquity.test.js b/test/liquity/liquity.test.js index 274492d7..14da5db0 100644 --- a/test/liquity/liquity.test.js +++ b/test/liquity/liquity.test.js @@ -1184,7 +1184,7 @@ describe.only("Liquity", () => { }); }); - describe("claim()", () => { + describe("claimCollateralFromRedemption()", () => { it("claims collateral from a redeemed Trove", async () => { // Create a low collateralized Trove const depositAmount = ethers.utils.parseEther("1.5"); @@ -1360,7 +1360,7 @@ describe.only("Liquity", () => { const stabilityDepositSpell = { connector: helpers.CONNECTOR_NAME, method: "stabilityDeposit", - args: [amount, frontendTag, 0], + args: [amount, frontendTag, 0, 0, 0], }; const depositTx = await dsa @@ -1377,7 +1377,10 @@ describe.only("Liquity", () => { it("returns Instadapp event name and data", async () => { const amount = ethers.utils.parseUnits("100", 18); const frontendTag = ethers.constants.AddressZero; - const getId = 0; + const getDepositId = 0; + const setEthGainId = 0; + const setLqtyGainId = 0; + await helpers.sendToken( liquity.lusdToken, amount, @@ -1388,7 +1391,7 @@ describe.only("Liquity", () => { const stabilityDepositSpell = { connector: helpers.CONNECTOR_NAME, method: "stabilityDeposit", - args: [amount, frontendTag, getId], + args: [amount, frontendTag, getDepositId, 0, 0], }; const depositTx = await dsa @@ -1399,11 +1402,18 @@ describe.only("Liquity", () => { const castLogEvent = receipt.events.find((e) => e.event === "LogCast") .args; const expectedEventParams = ethers.utils.defaultAbiCoder.encode( - ["address", "uint256", "address", "uint256"], - [wallet.address, amount, frontendTag, getId] + ["address", "uint256", "address", "uint256", "uint256", "uint256"], + [ + wallet.address, + amount, + frontendTag, + getDepositId, + setEthGainId, + setLqtyGainId, + ] ); expect(castLogEvent.eventNames[0]).eq( - "LogStabilityDeposit(address,uint256,address,uint256)" + "LogStabilityDeposit(address,uint256,address,uint256,uint256,uint256)" ); expect(castLogEvent.eventParams[0]).eq(expectedEventParams); }); @@ -1435,14 +1445,14 @@ describe.only("Liquity", () => { const stabilityDepositSpell = { connector: helpers.CONNECTOR_NAME, method: "stabilityDeposit", - args: [amount, frontendTag, 0], + args: [amount, frontendTag, 0, 0, 0], }; // Withdraw half of the deposit const stabilitWithdrawSpell = { connector: helpers.CONNECTOR_NAME, method: "stabilityWithdraw", - args: [amount.div(2), 0], + args: [amount.div(2), 0, 0, 0], }; const spells = [stabilityDepositSpell, stabilitWithdrawSpell]; @@ -1475,7 +1485,6 @@ describe.only("Liquity", () => { }); const amount = ethers.utils.parseUnits("100", 18); const frontendTag = ethers.constants.AddressZero; - const setId = 0; await helpers.sendToken( liquity.lusdToken, @@ -1487,15 +1496,19 @@ describe.only("Liquity", () => { const stabilityDepositSpell = { connector: helpers.CONNECTOR_NAME, method: "stabilityDeposit", - args: [amount, frontendTag, setId], + args: [amount, frontendTag, 0, 0, 0], }; // Withdraw half of the deposit const withdrawAmount = amount.div(2); + const setWithdrawId = 0; + const setEthGainId = 0; + const setLqtyGainId = 0; + const stabilitWithdrawSpell = { connector: helpers.CONNECTOR_NAME, method: "stabilityWithdraw", - args: [withdrawAmount, 0], + args: [withdrawAmount, setWithdrawId, setEthGainId, setLqtyGainId], }; const spells = [stabilityDepositSpell, stabilitWithdrawSpell]; @@ -1507,11 +1520,17 @@ describe.only("Liquity", () => { const castLogEvent = receipt.events.find((e) => e.event === "LogCast") .args; const expectedEventParams = ethers.utils.defaultAbiCoder.encode( - ["address", "uint256", "uint256"], - [wallet.address, withdrawAmount, setId] + ["address", "uint256", "uint256", "uint256", "uint256"], + [ + wallet.address, + withdrawAmount, + setWithdrawId, + setEthGainId, + setLqtyGainId, + ] ); expect(castLogEvent.eventNames[1]).eq( - "LogStabilityWithdraw(address,uint256,uint256)" + "LogStabilityWithdraw(address,uint256,uint256,uint256,uint256)" ); expect(castLogEvent.eventParams[1]).eq(expectedEventParams); }); @@ -1544,7 +1563,7 @@ describe.only("Liquity", () => { const stabilityDepositSpell = { connector: helpers.CONNECTOR_NAME, method: "stabilityDeposit", - args: [amount, frontendTag, 0], + args: [amount, frontendTag, 0, 0, 0], }; const depositTx = await dsa @@ -1605,7 +1624,7 @@ describe.only("Liquity", () => { const stabilityDepositSpell = { connector: helpers.CONNECTOR_NAME, method: "stabilityDeposit", - args: [amount, frontendTag, 0], + args: [amount, frontendTag, 0, 0, 0], }; const depositTx = await dsa @@ -1670,7 +1689,7 @@ describe.only("Liquity", () => { const stakeSpell = { connector: helpers.CONNECTOR_NAME, method: "stake", - args: [amount, 0], + args: [amount, 0, 0, 0], }; const stakeTx = await dsa @@ -1698,12 +1717,14 @@ describe.only("Liquity", () => { helpers.JUSTIN_SUN_ADDRESS, dsa.address ); - const getId = 0; + const getStakeId = 0; + const setEthGainId = 0; + const setLusdGainId = 0; const stakeSpell = { connector: helpers.CONNECTOR_NAME, method: "stake", - args: [amount, getId], + args: [amount, getStakeId, setEthGainId, setLusdGainId], }; const stakeTx = await dsa @@ -1715,11 +1736,11 @@ describe.only("Liquity", () => { const castLogEvent = receipt.events.find((e) => e.event === "LogCast") .args; const expectedEventParams = ethers.utils.defaultAbiCoder.encode( - ["address", "uint256", "uint256"], - [wallet.address, amount, getId] + ["address", "uint256", "uint256", "uint256", "uint256"], + [wallet.address, amount, getStakeId, setEthGainId, setLusdGainId] ); expect(castLogEvent.eventNames[0]).eq( - "LogStake(address,uint256,uint256)" + "LogStake(address,uint256,uint256,uint256,uint256)" ); expect(castLogEvent.eventParams[0]).eq(expectedEventParams); }); @@ -1738,7 +1759,7 @@ describe.only("Liquity", () => { const stakeSpell = { connector: helpers.CONNECTOR_NAME, method: "stake", - args: [amount, 0], + args: [amount, 0, 0, 0], }; const stakeTx = await dsa @@ -1750,11 +1771,10 @@ describe.only("Liquity", () => { const totalStakingBalanceBefore = await liquity.lqtyToken.balanceOf( contracts.STAKING_ADDRESS ); - const unstakeSpell = { connector: helpers.CONNECTOR_NAME, method: "unstake", - args: [amount, 0], + args: [amount, 0, 0, 0], }; const unstakeTx = await dsa @@ -1786,18 +1806,20 @@ describe.only("Liquity", () => { const stakeSpell = { connector: helpers.CONNECTOR_NAME, method: "stake", - args: [amount, 0], + args: [amount, 0, 0, 0], }; await dsa .connect(wallet) .cast(...encodeSpells([stakeSpell]), wallet.address); - const setId = 0; + const setUnstakeId = 0; + const setEthGainId = 0; + const setLusdGainId = 0; const unstakeSpell = { connector: helpers.CONNECTOR_NAME, method: "unstake", - args: [amount, setId], + args: [amount, setUnstakeId, setEthGainId, setLusdGainId], }; const unstakeTx = await dsa @@ -1809,46 +1831,42 @@ describe.only("Liquity", () => { const castLogEvent = receipt.events.find((e) => e.event === "LogCast") .args; const expectedEventParams = ethers.utils.defaultAbiCoder.encode( - ["address", "uint256", "uint256"], - [wallet.address, amount, setId] + ["address", "uint256", "uint256", "uint256", "uint256"], + [wallet.address, amount, setUnstakeId, setEthGainId, setLusdGainId] ); expect(castLogEvent.eventNames[0]).eq( - "LogUnstake(address,uint256,uint256)" + "LogUnstake(address,uint256,uint256,uint256,uint256)" ); expect(castLogEvent.eventParams[0]).eq(expectedEventParams); }); }); - describe.skip("claimStakingGains()", () => { - it("Claims gains from staking", async () => {}); - - it("returns Instadapp event name and data", async () => { + describe("claimStakingGains()", () => { + it("Claims gains from staking", async () => { const stakerDsa = await buildDSAv2(wallet.address); - const whaleLqtyBalance = await liquity.lqtyToken.balanceOf( - helpers.JUSTIN_SUN_ADDRESS - ); - console.log("BALANCE", whaleLqtyBalance.toString()); + const amount = ethers.utils.parseUnits("1000", 18); // 1000 LQTY // Stake lots of LQTY await helpers.sendToken( liquity.lqtyToken, - whaleLqtyBalance, + amount, helpers.JUSTIN_SUN_ADDRESS, stakerDsa.address ); - const dsaBalance = await liquity.lqtyToken.balanceOf( - stakerDsa.address - ); - console.log("dsaBalance", dsaBalance.toString()); - await liquity.staking - .connect(stakerDsa.signer) - .stake(ethers.utils.parseUnits("1", 18)); + const stakeSpell = { + connector: helpers.CONNECTOR_NAME, + method: "stake", + args: [amount, 0, 0, 0], + }; + await stakerDsa + .connect(wallet) + .cast(...encodeSpells([stakeSpell]), wallet.address); // Open a Trove to cause an ETH issuance gain for stakers await helpers.createDsaTrove( dsa, wallet, - liqiuty.hintHelpers, + liquity.hintHelpers, liquity.sortedTroves ); @@ -1856,18 +1874,96 @@ describe.only("Liquity", () => { await helpers.redeem( ethers.utils.parseUnits("1000", 18), contracts.STABILITY_POOL_ADDRESS, - wallet.address, + wallet, liquity ); const setEthGainId = 0; const setLusdGainId = 0; + const ethGain = await liquity.staking.getPendingETHGain( + stakerDsa.address + ); + const lusdGain = await liquity.staking.getPendingLUSDGain( + stakerDsa.address + ); + const claimStakingGainsSpell = { connector: helpers.CONNECTOR_NAME, method: "claimStakingGains", args: [setEthGainId, setLusdGainId], }; + const ethBalanceBefore = await ethers.provider.getBalance( + stakerDsa.address + ); + + // Claim gains + await stakerDsa + .connect(wallet) + .cast(...encodeSpells([claimStakingGainsSpell]), wallet.address); + + const ethBalanceAfter = await ethers.provider.getBalance( + stakerDsa.address + ); + const lusdBalanceAfter = await liquity.lusdToken.balanceOf( + stakerDsa.address + ); + expect(ethBalanceAfter).to.eq(ethBalanceBefore.add(ethGain)); + expect(lusdBalanceAfter).to.eq(lusdGain); + }); + + it("returns Instadapp event name and data", async () => { + const stakerDsa = await buildDSAv2(wallet.address); + const amount = ethers.utils.parseUnits("1000", 18); // 1000 LQTY + + // Stake lots of LQTY + await helpers.sendToken( + liquity.lqtyToken, + amount, + helpers.JUSTIN_SUN_ADDRESS, + stakerDsa.address + ); + const stakeSpell = { + connector: helpers.CONNECTOR_NAME, + method: "stake", + args: [amount, 0, 0, 0], + }; + await stakerDsa + .connect(wallet) + .cast(...encodeSpells([stakeSpell]), wallet.address); + + // Open a Trove to cause an ETH issuance gain for stakers + await helpers.createDsaTrove( + dsa, + wallet, + liquity.hintHelpers, + liquity.sortedTroves + ); + + // Redeem some ETH to cause an LUSD redemption gain for stakers + await helpers.redeem( + ethers.utils.parseUnits("1000", 18), + contracts.STABILITY_POOL_ADDRESS, + wallet, + liquity + ); + + const setEthGainId = 0; + const setLusdGainId = 0; + const ethGain = await liquity.staking.getPendingETHGain( + stakerDsa.address + ); + const lusdGain = await liquity.staking.getPendingLUSDGain( + stakerDsa.address + ); + + const claimStakingGainsSpell = { + connector: helpers.CONNECTOR_NAME, + method: "claimStakingGains", + args: [setEthGainId, setLusdGainId], + }; + + // Claim gains const claimGainsTx = await stakerDsa .connect(wallet) .cast(...encodeSpells([claimStakingGainsSpell]), wallet.address); @@ -1877,11 +1973,11 @@ describe.only("Liquity", () => { const castLogEvent = receipt.events.find((e) => e.event === "LogCast") .args; const expectedEventParams = ethers.utils.defaultAbiCoder.encode( - ["address", "uint256", "uint256"], - [helpers.JUSTIN_SUN_ADDRESS, setEthGainId, setLusdGainId] + ["address", "uint256", "uint256", "uint256", "uint256"], + [wallet.address, ethGain, lusdGain, setEthGainId, setLusdGainId] ); expect(castLogEvent.eventNames[0]).eq( - "LogClaimStakingGains(address,uint256,uint256)" + "LogClaimStakingGains(address,uint256,uint256,uint256,uint256)" ); expect(castLogEvent.eventParams[0]).eq(expectedEventParams); }); From 7588733d98ea5ba468ea648fd72997d6641e4052 Mon Sep 17 00:00:00 2001 From: Edward Mulraney Date: Tue, 8 Jun 2021 16:57:36 +0100 Subject: [PATCH 05/19] add tests to ensure getId/setIds all work --- contracts/mainnet/connectors/liquity/main.sol | 14 +- test/liquity/liquity.contracts.js | 1 + test/liquity/liquity.helpers.js | 82 +- test/liquity/liquity.test.js | 1468 ++++++++++++----- 4 files changed, 1086 insertions(+), 479 deletions(-) diff --git a/contracts/mainnet/connectors/liquity/main.sol b/contracts/mainnet/connectors/liquity/main.sol index fd670209..caea1f89 100644 --- a/contracts/mainnet/connectors/liquity/main.sol +++ b/contracts/mainnet/connectors/liquity/main.sol @@ -14,7 +14,6 @@ import { import { Stores } from "../../common/stores.sol"; import { Helpers } from "./helpers.sol"; import { Events } from "./events.sol"; -import "hardhat/console.sol"; abstract contract LiquityResolver is Events, Helpers { BorrowerOperationsLike internal constant borrowerOperations = @@ -48,7 +47,7 @@ abstract contract LiquityResolver is Events, Helpers { * @param borrowAmount The amount of LUSD to borrow * @param upperHint Address of the Trove near the upper bound of where the user's Trove will now sit in the ordered Trove list * @param lowerHint Address of the Trove near the lower bound of where the user's Trove will now sit in the ordered Trove list - * @param getId Optional storage slot to retrieve ETH instead of receiving it from msg.value + * @param getId Optional storage slot to retrieve ETH from * @param setId Optional storage slot to store the LUSD borrowed against */ function open( @@ -60,13 +59,6 @@ abstract contract LiquityResolver is Events, Helpers { uint getId, uint setId ) external payable returns (string memory _eventName, bytes memory _eventParam) { - /* - User has three options for depositing ETH collateral to open a Trove: - - Send ETH directly with this function call - - Have ETH collected from a previous spell - - Have ETH collected from the DSA's existing balance - */ - if (getId != 0 && depositAmount != 0) { revert("open(): Cannot supply a depositAmount if a non-zero getId is supplied"); } @@ -225,7 +217,6 @@ abstract contract LiquityResolver is Events, Helpers { if (getRepayId != 0 && repayAmount != 0) { revert("adjust(): Cannot supply a repayAmount if a non-zero getRepayId is supplied"); } - AdjustTrove memory adjustTrove; adjustTrove.maxFeePercentage = maxFeePercentage; @@ -235,7 +226,7 @@ abstract contract LiquityResolver is Events, Helpers { adjustTrove.repayAmount = getUint(getRepayId, repayAmount); adjustTrove.isBorrow = borrowAmount > 0; - borrowerOperations.adjustTrove{value: depositAmount}( + borrowerOperations.adjustTrove{value: adjustTrove.depositAmount}( adjustTrove.maxFeePercentage, adjustTrove.withdrawAmount, adjustTrove.borrowAmount, @@ -288,6 +279,7 @@ abstract contract LiquityResolver is Events, Helpers { uint setLqtyGainId ) external returns (string memory _eventName, bytes memory _eventParam) { amount = getUint(getDepositId, amount); + uint ethGain = stabilityPool.getDepositorETHGain(address(this)); uint lqtyGain = stabilityPool.getDepositorLQTYGain(address(this)); diff --git a/test/liquity/liquity.contracts.js b/test/liquity/liquity.contracts.js index 9c978e1f..43863525 100644 --- a/test/liquity/liquity.contracts.js +++ b/test/liquity/liquity.contracts.js @@ -60,6 +60,7 @@ 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"; diff --git a/test/liquity/liquity.helpers.js b/test/liquity/liquity.helpers.js index c2c6799d..cc411312 100644 --- a/test/liquity/liquity.helpers.js +++ b/test/liquity/liquity.helpers.js @@ -16,12 +16,16 @@ const connectV2LiquityArtifacts = require("../../artifacts/contracts/mainnet/con const connectV2BasicV1Artifacts = require("../../artifacts/contracts/mainnet/connectors/basic/main.sol/ConnectV2Basic.json"); const { ethers } = require("hardhat"); -const CONNECTOR_NAME = "LIQUITY-v1-TEST"; +// 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, @@ -38,7 +42,7 @@ const openTroveSpell = async ( } const openTroveSpell = { - connector: CONNECTOR_NAME, + connector: LIQUITY_CONNECTOR, method: "open", args: [ depositAmount, @@ -60,8 +64,7 @@ const openTroveSpell = async ( const createDsaTrove = async ( dsa, signer, - hintHelpers, - sortedTroves, + liquity, depositAmount = hre.ethers.utils.parseEther("5"), borrowAmount = hre.ethers.utils.parseUnits("2000", 18) ) => { @@ -69,8 +72,7 @@ const createDsaTrove = async ( const { upperHint, lowerHint } = await getTroveInsertionHints( depositAmount, borrowAmount, - hintHelpers, - sortedTroves + liquity ); return await openTroveSpell( dsa, @@ -90,7 +92,9 @@ const sendToken = async (token, amount, from, to) => { }); const signer = await hre.ethers.provider.getSigner(from); - return await token.connect(signer).transfer(to, amount); + return await token.connect(signer).transfer(to, amount, { + gasPrice: 0, + }); }; const resetInitialState = async (walletAddress, contracts, isDebug = false) => { @@ -138,13 +142,13 @@ const deployAndConnect = async (contracts, isDebug = false) => { instadappAddresses.core.connectorsV2 ); const connector = await deployAndEnableConnector({ - connectorName: CONNECTOR_NAME, + connectorName: LIQUITY_CONNECTOR, contractArtifact: connectV2LiquityArtifacts, signer: masterSigner, connectors: instaConnectorsV2, }); isDebug && - console.log(`${CONNECTOR_NAME} Connector address`, connector.address); + console.log(`${LIQUITY_CONNECTOR} Connector address`, connector.address); const basicConnector = await deployAndEnableConnector({ connectorName: "Basic-v1", @@ -250,28 +254,24 @@ const deployAndConnect = async (contracts, isDebug = false) => { return liquity; }; -const getTroveInsertionHints = async ( - depositAmount, - borrowAmount, - hintHelpers, - sortedTroves -) => { - const nominalCR = await hintHelpers.computeNominalCR( +const getTroveInsertionHints = async (depositAmount, borrowAmount, liquity) => { + const nominalCR = await liquity.hintHelpers.computeNominalCR( depositAmount, borrowAmount ); - const { hintAddress, latestRandomSeed } = await hintHelpers.getApproxHint( - nominalCR, - 50, - 1298379, - { - gasLimit: MAX_GAS, - } - ); + const { + hintAddress, + latestRandomSeed, + } = await liquity.hintHelpers.getApproxHint(nominalCR, 50, 1298379, { + gasLimit: MAX_GAS, + }); randomSeed = latestRandomSeed; - const { 0: upperHint, 1: lowerHint } = await sortedTroves.findInsertPosition( + const { + 0: upperHint, + 1: lowerHint, + } = await liquity.sortedTroves.findInsertPosition( nominalCR, hintAddress, hintAddress, @@ -288,19 +288,17 @@ const getTroveInsertionHints = async ( let randomSeed = 4223; -const getRedemptionHints = async ( - amount, - hintHelpers, - sortedTroves, - priceFeed -) => { - const ethPrice = await priceFeed.callStatic.fetchPrice(); +const getRedemptionHints = async (amount, liquity) => { + const ethPrice = await liquity.priceFeed.callStatic.fetchPrice(); const [ firstRedemptionHint, partialRedemptionHintNicr, - ] = await hintHelpers.getRedemptionHints(amount, ethPrice, 0); + ] = await liquity.hintHelpers.getRedemptionHints(amount, ethPrice, 0); - const { hintAddress, latestRandomSeed } = await hintHelpers.getApproxHint( + const { + hintAddress, + latestRandomSeed, + } = await liquity.hintHelpers.getApproxHint( partialRedemptionHintNicr, 50, randomSeed, @@ -310,7 +308,10 @@ const getRedemptionHints = async ( ); randomSeed = latestRandomSeed; - const { 0: upperHint, 1: lowerHint } = await sortedTroves.findInsertPosition( + const { + 0: upperHint, + 1: lowerHint, + } = await liquity.sortedTroves.findInsertPosition( partialRedemptionHintNicr, hintAddress, hintAddress, @@ -334,12 +335,7 @@ const redeem = async (amount, from, wallet, liquity) => { firstRedemptionHint, upperHint, lowerHint, - } = await getRedemptionHints( - amount, - liquity.hintHelpers, - liquity.sortedTroves, - liquity.priceFeed - ); + } = await getRedemptionHints(amount, liquity); const maxFeePercentage = ethers.utils.parseUnits("0.5", 18); // 0.5% max fee return await liquity.troveManager @@ -366,9 +362,11 @@ module.exports = { getTroveInsertionHints, getRedemptionHints, redeem, - CONNECTOR_NAME, + LIQUITY_CONNECTOR, LUSD_GAS_COMPENSATION, JUSTIN_SUN_ADDRESS, LIQUIDATABLE_TROVE_ADDRESS, MAX_GAS, + INSTADAPP_BASIC_V1_CONNECTOR, + ETH_ADDRESS, }; diff --git a/test/liquity/liquity.test.js b/test/liquity/liquity.test.js index 14da5db0..7eeafe53 100644 --- a/test/liquity/liquity.test.js +++ b/test/liquity/liquity.test.js @@ -11,15 +11,12 @@ const contracts = require("./liquity.contracts"); // Liquity helpers const helpers = require("./liquity.helpers"); -// Instadapp uses a fake address to represent native ETH -const { eth_addr: ETH_ADDRESS } = require("../../scripts/constant/constant"); - describe.only("Liquity", () => { const { waffle, ethers } = hre; const { provider } = waffle; // Waffle test account 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 (holds 1000 ETH) - const wallet = provider.getWallets()[0]; + const userWallet = provider.getWallets()[0]; let dsa = null; let liquity = null; @@ -39,7 +36,7 @@ describe.only("Liquity", () => { beforeEach(async () => { // Build a new DSA before each test so we start each test from the same default state - dsa = await buildDSAv2(wallet.address); + dsa = await buildDSAv2(userWallet.address); expect(dsa.address).to.exist; }); @@ -53,14 +50,14 @@ describe.only("Liquity", () => { const upperHint = ethers.constants.AddressZero; const lowerHint = ethers.constants.AddressZero; const originalUserBalance = await ethers.provider.getBalance( - wallet.address + userWallet.address ); const originalDsaBalance = await ethers.provider.getBalance( dsa.address ); const openTroveSpell = { - connector: helpers.CONNECTOR_NAME, + connector: helpers.LIQUITY_CONNECTOR, method: "open", args: [ depositAmount, @@ -73,15 +70,16 @@ describe.only("Liquity", () => { ], }; - const tx = await dsa - .connect(wallet) - .cast(...encodeSpells([openTroveSpell]), wallet.address, { + await dsa + .connect(userWallet) + .cast(...encodeSpells([openTroveSpell]), userWallet.address, { value: depositAmount, + gasPrice: 0, }); - await tx.wait(); - - const userBalance = await ethers.provider.getBalance(wallet.address); + const userBalance = await ethers.provider.getBalance( + userWallet.address + ); const dsaEthBalance = await ethers.provider.getBalance(dsa.address); const dsaLusdBalance = await liquity.lusdToken.balanceOf(dsa.address); const troveDebt = await liquity.troveManager.getTroveDebt( @@ -91,9 +89,9 @@ describe.only("Liquity", () => { dsa.address ); - expect(userBalance).lt( - originalUserBalance, - "User should have less Ether after opening Trove" + expect(userBalance).eq( + originalUserBalance.sub(depositAmount), + "User's Ether balance should decrease by the amount they deposited" ); expect(dsaEthBalance).to.eq( @@ -103,7 +101,7 @@ describe.only("Liquity", () => { expect( dsaLusdBalance, - "DSA account should now hold the amount the user tried to borrow" + "DSA account should now hold the amount the user borrowed" ).to.eq(borrowAmount); expect(troveDebt).to.gt( @@ -124,21 +122,21 @@ describe.only("Liquity", () => { const upperHint = ethers.constants.AddressZero; const lowerHint = ethers.constants.AddressZero; const originalUserBalance = await ethers.provider.getBalance( - wallet.address + userWallet.address ); const originalDsaBalance = await ethers.provider.getBalance( dsa.address ); - const depositId = 1; // Choose an ID to store and retrieve the deopsited ETH + const depositId = 1; // Choose an ID to store and retrieve the deposited ETH const depositEthSpell = { - connector: "Basic-v1", + connector: helpers.INSTADAPP_BASIC_V1_CONNECTOR, method: "deposit", - args: [ETH_ADDRESS, depositAmount, 0, depositId], + args: [helpers.ETH_ADDRESS, depositAmount, 0, depositId], }; const openTroveSpell = { - connector: helpers.CONNECTOR_NAME, + connector: helpers.LIQUITY_CONNECTOR, method: "open", args: [ 0, // When pulling ETH from a previous spell it doesn't matter what deposit value we put in this param @@ -152,14 +150,16 @@ describe.only("Liquity", () => { }; const spells = [depositEthSpell, openTroveSpell]; - const tx = await dsa - .connect(wallet) - .cast(...encodeSpells(spells), wallet.address, { + await dsa + .connect(userWallet) + .cast(...encodeSpells(spells), userWallet.address, { value: depositAmount, + gasPrice: 0, }); - await tx.wait(); - const userBalance = await ethers.provider.getBalance(wallet.address); + const userBalance = await ethers.provider.getBalance( + userWallet.address + ); const dsaEthBalance = await ethers.provider.getBalance(dsa.address); const dsaLusdBalance = await liquity.lusdToken.balanceOf(dsa.address); const troveDebt = await liquity.troveManager.getTroveDebt( @@ -169,9 +169,9 @@ describe.only("Liquity", () => { dsa.address ); - expect(userBalance).lt( - originalUserBalance, - "User should have less Ether" + expect(userBalance).eq( + originalUserBalance.sub(depositAmount), + "User's Ether balance should decrease by the amount they deposited" ); expect(dsaEthBalance).to.eq( @@ -181,7 +181,7 @@ describe.only("Liquity", () => { expect( dsaLusdBalance, - "DSA account should now hold the amount the user tried to borrow" + "DSA account should now hold the amount the user borrowed" ).to.eq(borrowAmount); expect(troveDebt).to.gt( @@ -202,7 +202,7 @@ describe.only("Liquity", () => { const upperHint = ethers.constants.AddressZero; const lowerHint = ethers.constants.AddressZero; const originalUserBalance = await ethers.provider.getBalance( - wallet.address + userWallet.address ); const originalDsaBalance = await ethers.provider.getBalance( dsa.address @@ -210,7 +210,7 @@ describe.only("Liquity", () => { const borrowId = 1; const openTroveSpell = { - connector: helpers.CONNECTOR_NAME, + connector: helpers.LIQUITY_CONNECTOR, method: "open", args: [ depositAmount, @@ -224,11 +224,11 @@ describe.only("Liquity", () => { }; const withdrawLusdSpell = { - connector: "Basic-v1", + connector: helpers.INSTADAPP_BASIC_V1_CONNECTOR, method: "withdraw", args: [ contracts.LUSD_TOKEN_ADDRESS, - 0, // amount comes from the previous spell's setId + 0, // Amount comes from the previous spell's setId dsa.address, borrowId, 0, @@ -236,21 +236,16 @@ describe.only("Liquity", () => { }; const spells = [openTroveSpell, withdrawLusdSpell]; - const tx = await dsa - .connect(wallet) - .cast(...encodeSpells(spells), wallet.address, { + await dsa + .connect(userWallet) + .cast(...encodeSpells(spells), userWallet.address, { value: depositAmount, + gasPrice: 0, }); - await tx.wait(); - - const userBalance = await ethers.provider.getBalance(wallet.address); - - expect(userBalance).lt( - originalUserBalance, - "User should have less Ether after opening Trove" + const userBalance = await ethers.provider.getBalance( + userWallet.address ); - const dsaEthBalance = await ethers.provider.getBalance(dsa.address); const dsaLusdBalance = await liquity.lusdToken.balanceOf(dsa.address); const troveDebt = await liquity.troveManager.getTroveDebt( @@ -260,6 +255,11 @@ describe.only("Liquity", () => { dsa.address ); + expect(userBalance).eq( + originalUserBalance.sub(depositAmount), + "User's Ether balance should decrease by the amount they deposited" + ); + expect(dsaEthBalance).to.eq( originalDsaBalance, "User's DSA account Ether should not change after borrowing" @@ -267,7 +267,7 @@ describe.only("Liquity", () => { expect( dsaLusdBalance, - "DSA account should now hold the amount the user tried to borrow" + "DSA account should now hold the amount the user borrowed" ).to.eq(borrowAmount); expect(troveDebt).to.gt( @@ -289,7 +289,7 @@ describe.only("Liquity", () => { const lowerHint = ethers.constants.AddressZero; const openTroveSpell = { - connector: helpers.CONNECTOR_NAME, + connector: helpers.LIQUITY_CONNECTOR, method: "open", args: [ depositAmount, @@ -304,7 +304,7 @@ describe.only("Liquity", () => { const openTx = await dsa.cast( ...encodeSpells([openTroveSpell]), - wallet.address, + userWallet.address, { value: depositAmount, } @@ -318,7 +318,7 @@ describe.only("Liquity", () => { const expectedEventParams = ethers.utils.defaultAbiCoder.encode( ["address", "uint256", "uint256", "uint256", "uint256", "uint256"], [ - wallet.address, + userWallet.address, maxFeePercentage, depositAmount, borrowAmount, @@ -334,25 +334,25 @@ describe.only("Liquity", () => { it("closes a Trove", async () => { const depositAmount = ethers.utils.parseEther("5"); const borrowAmount = ethers.utils.parseUnits("2000", 18); + // Create a dummy Trove await helpers.createDsaTrove( dsa, - wallet, - liquity.hintHelpers, - liquity.sortedTroves, + userWallet, + liquity, depositAmount, borrowAmount ); - const originalTroveDebt = await liquity.troveManager.getTroveDebt( + const troveDebtBefore = await liquity.troveManager.getTroveDebt( dsa.address ); - const originalTroveCollateral = await liquity.troveManager.getTroveColl( + const troveCollateralBefore = await liquity.troveManager.getTroveColl( dsa.address ); // Send DSA account enough LUSD (from Stability Pool) to close their Trove - const extraLusdRequiredToCloseTrove = originalTroveDebt.sub( + const extraLusdRequiredToCloseTrove = troveDebtBefore.sub( borrowAmount ); @@ -370,19 +370,18 @@ describe.only("Liquity", () => { expect( originalDsaLusdBalance, "DSA account should now hold the LUSD amount required to pay off the Trove debt" - ).to.eq(originalTroveDebt); + ).to.eq(troveDebtBefore); const closeTroveSpell = { - connector: helpers.CONNECTOR_NAME, + connector: helpers.LIQUITY_CONNECTOR, method: "close", args: [0], }; - const closeTx = await dsa - .connect(wallet) - .cast(...encodeSpells([closeTroveSpell]), wallet.address); + await dsa + .connect(userWallet) + .cast(...encodeSpells([closeTroveSpell]), userWallet.address); - await closeTx.wait(); const dsaEthBalance = await ethers.provider.getBalance(dsa.address); const dsaLusdBalance = await liquity.lusdToken.balanceOf(dsa.address); const troveDebt = await liquity.troveManager.getTroveDebt( @@ -402,7 +401,7 @@ describe.only("Liquity", () => { expect( dsaEthBalance, "DSA account should now hold the Trove's ETH collateral" - ).to.eq(originalTroveCollateral); + ).to.eq(troveCollateralBefore); expect( dsaLusdBalance, @@ -411,78 +410,48 @@ describe.only("Liquity", () => { }); it("closes a Trove using LUSD obtained from a previous spell", async () => { - await helpers.createDsaTrove( - dsa, - wallet, - liquity.hintHelpers, - liquity.sortedTroves - ); + // Create a dummy Trove + await helpers.createDsaTrove(dsa, userWallet, liquity); - const originalTroveDebt = await liquity.troveManager.getTroveDebt( + const troveDebtBefore = await liquity.troveManager.getTroveDebt( dsa.address ); - const originalTroveCollateral = await liquity.troveManager.getTroveColl( + const troveCollateralBefore = await liquity.troveManager.getTroveColl( dsa.address ); // Send user enough LUSD to repay the loan, we'll use a deposit and withdraw spell to obtain it await helpers.sendToken( liquity.lusdToken, - originalTroveDebt, + troveDebtBefore, contracts.STABILITY_POOL_ADDRESS, - wallet.address + userWallet.address ); // Allow DSA to spend user's LUSD await liquity.lusdToken - .connect(wallet) - .approve(dsa.address, originalTroveDebt); + .connect(userWallet) + .approve(dsa.address, troveDebtBefore); - const lusdDepositId = 1; - - // Simulate a spell which would have pulled LUSD from somewhere (e.g. AAVE) into InstaMemory + // Simulate a spell which would have pulled LUSD from somewhere (e.g. Uniswap) into InstaMemory // In this case we're simply running a deposit spell from the user's EOA const depositLusdSpell = { - connector: "Basic-v1", + connector: helpers.INSTADAPP_BASIC_V1_CONNECTOR, method: "deposit", - args: [ - contracts.LUSD_TOKEN_ADDRESS, - originalTroveDebt, - 0, - lusdDepositId, - ], - }; - // Withdraw the obtained LUSD into DSA account - const withdrawLusdSpell = { - connector: "Basic-v1", - method: "withdraw", - args: [ - contracts.LUSD_TOKEN_ADDRESS, - 0, // amount comes from the previous spell's setId - dsa.address, - lusdDepositId, - 0, - ], + args: [contracts.LUSD_TOKEN_ADDRESS, troveDebtBefore, 0, 0], }; const closeTroveSpell = { - connector: helpers.CONNECTOR_NAME, + connector: helpers.LIQUITY_CONNECTOR, method: "close", args: [0], }; + const spells = [depositLusdSpell, closeTroveSpell]; - const closeTx = await dsa - .connect(wallet) - .cast( - ...encodeSpells([ - depositLusdSpell, - withdrawLusdSpell, - closeTroveSpell, - ]), - wallet.address - ); + await dsa + .connect(userWallet) + .cast(...encodeSpells(spells), userWallet.address); - await closeTx.wait(); const dsaEthBalance = await ethers.provider.getBalance(dsa.address); const troveDebt = await liquity.troveManager.getTroveDebt( dsa.address @@ -501,30 +470,30 @@ describe.only("Liquity", () => { expect( dsaEthBalance, "DSA account should now hold the Trove's ETH collateral" - ).to.eq(originalTroveCollateral); + ).to.eq(troveCollateralBefore); }); it("closes a Trove and stores the released collateral for other spells to use", async () => { const depositAmount = ethers.utils.parseEther("5"); const borrowAmount = ethers.utils.parseUnits("2000", 18); + // Create a dummy Trove await helpers.createDsaTrove( dsa, - wallet, - liquity.hintHelpers, - liquity.sortedTroves, + userWallet, + liquity, depositAmount, borrowAmount ); - const originalTroveDebt = await liquity.troveManager.getTroveDebt( + const troveDebtBefore = await liquity.troveManager.getTroveDebt( dsa.address ); - const originalTroveCollateral = await liquity.troveManager.getTroveColl( + const troveCollateralBefore = await liquity.troveManager.getTroveColl( dsa.address ); // Send DSA account enough LUSD (from Stability Pool) to close their Trove - const extraLusdRequiredToCloseTrove = originalTroveDebt.sub( + const extraLusdRequiredToCloseTrove = troveDebtBefore.sub( borrowAmount ); await helpers.sendToken( @@ -540,21 +509,21 @@ describe.only("Liquity", () => { expect( originalDsaLusdBalance, "DSA account should now hold the LUSD amount required to pay off the Trove debt" - ).to.eq(originalTroveDebt); + ).to.eq(troveDebtBefore); const collateralWithdrawId = 1; const closeTroveSpell = { - connector: helpers.CONNECTOR_NAME, + connector: helpers.LIQUITY_CONNECTOR, method: "close", args: [collateralWithdrawId], }; const withdrawEthSpell = { - connector: "Basic-v1", + connector: helpers.INSTADAPP_BASIC_V1_CONNECTOR, method: "withdraw", args: [ - ETH_ADDRESS, + helpers.ETH_ADDRESS, 0, // amount comes from the previous spell's setId dsa.address, collateralWithdrawId, @@ -562,14 +531,13 @@ describe.only("Liquity", () => { ], }; - const closeTx = await dsa - .connect(wallet) + await dsa + .connect(userWallet) .cast( ...encodeSpells([closeTroveSpell, withdrawEthSpell]), - wallet.address + userWallet.address ); - await closeTx.wait(); const dsaEthBalance = await ethers.provider.getBalance(dsa.address); const dsaLusdBalance = await liquity.lusdToken.balanceOf(dsa.address); const troveDebt = await liquity.troveManager.getTroveDebt( @@ -589,7 +557,7 @@ describe.only("Liquity", () => { expect( dsaEthBalance, "DSA account should now hold the Trove's ETH collateral" - ).to.eq(originalTroveCollateral); + ).to.eq(troveCollateralBefore); expect( dsaLusdBalance, @@ -600,11 +568,11 @@ describe.only("Liquity", () => { it("returns Instadapp event name and data", async () => { const depositAmount = ethers.utils.parseEther("5"); const borrowAmount = ethers.utils.parseUnits("2000", 18); + // Create a dummy Trove await helpers.createDsaTrove( dsa, - wallet, - liquity.hintHelpers, - liquity.sortedTroves, + userWallet, + liquity, depositAmount, borrowAmount ); @@ -616,21 +584,21 @@ describe.only("Liquity", () => { ); const closeTroveSpell = { - connector: helpers.CONNECTOR_NAME, + connector: helpers.LIQUITY_CONNECTOR, method: "close", args: [0], }; const closeTx = await dsa - .connect(wallet) - .cast(...encodeSpells([closeTroveSpell]), wallet.address); + .connect(userWallet) + .cast(...encodeSpells([closeTroveSpell]), userWallet.address); const receipt = await closeTx.wait(); const castLogEvent = receipt.events.find((e) => e.event === "LogCast") .args; const expectedEventParams = ethers.utils.defaultAbiCoder.encode( ["address", "uint256"], - [wallet.address, 0] + [userWallet.address, 0] ); expect(castLogEvent.eventNames[0]).eq("LogClose(address,uint256)"); expect(castLogEvent.eventParams[0]).eq(expectedEventParams); @@ -639,14 +607,10 @@ describe.only("Liquity", () => { describe("deposit()", () => { it("deposits ETH into a Trove", async () => { - await helpers.createDsaTrove( - dsa, - wallet, - liquity.hintHelpers, - liquity.sortedTroves - ); + // Create a dummy Trove + await helpers.createDsaTrove(dsa, userWallet, liquity); - const originalTroveCollateral = await liquity.troveManager.getTroveColl( + const troveCollateralBefore = await liquity.troveManager.getTroveColl( dsa.address ); @@ -654,24 +618,66 @@ describe.only("Liquity", () => { const upperHint = ethers.constants.AddressZero; const lowerHint = ethers.constants.AddressZero; const depositEthSpell = { - connector: helpers.CONNECTOR_NAME, + connector: helpers.LIQUITY_CONNECTOR, method: "deposit", args: [topupAmount, upperHint, lowerHint, 0], }; - const depositTx = await dsa - .connect(wallet) - .cast(...encodeSpells([depositEthSpell]), wallet.address, { + await dsa + .connect(userWallet) + .cast(...encodeSpells([depositEthSpell]), userWallet.address, { value: topupAmount, }); - await depositTx.wait(); - const troveCollateral = await liquity.troveManager.getTroveColl( dsa.address ); - const expectedTroveCollateral = originalTroveCollateral.add( + const expectedTroveCollateral = troveCollateralBefore.add( + topupAmount + ); + + expect( + troveCollateral, + `Trove collateral should have increased by ${topupAmount} ETH` + ).to.eq(expectedTroveCollateral); + }); + + it("deposits using ETH gained from a previous spell", async () => { + // Create a dummy Trove + await helpers.createDsaTrove(dsa, userWallet, liquity); + const troveCollateralBefore = await liquity.troveManager.getTroveColl( + dsa.address + ); + + const topupAmount = ethers.utils.parseEther("1"); + const depositId = 1; + const depositEthSpell = { + connector: helpers.INSTADAPP_BASIC_V1_CONNECTOR, + method: "deposit", + args: [helpers.ETH_ADDRESS, topupAmount, 0, depositId], + }; + + const upperHint = ethers.constants.AddressZero; + const lowerHint = ethers.constants.AddressZero; + const depositEthToTroveSpell = { + connector: helpers.LIQUITY_CONNECTOR, + method: "deposit", + args: [0, upperHint, lowerHint, depositId], + }; + const spells = [depositEthSpell, depositEthToTroveSpell]; + + await dsa + .connect(userWallet) + .cast(...encodeSpells(spells), userWallet.address, { + value: topupAmount, + }); + + const troveCollateral = await liquity.troveManager.getTroveColl( + dsa.address + ); + + const expectedTroveCollateral = troveCollateralBefore.add( topupAmount ); @@ -682,25 +688,21 @@ describe.only("Liquity", () => { }); it("returns Instadapp event name and data", async () => { - await helpers.createDsaTrove( - dsa, - wallet, - liquity.hintHelpers, - liquity.sortedTroves - ); + // Create a dummy Trove + await helpers.createDsaTrove(dsa, userWallet, liquity); const topupAmount = ethers.utils.parseEther("1"); const upperHint = ethers.constants.AddressZero; const lowerHint = ethers.constants.AddressZero; const depositEthSpell = { - connector: helpers.CONNECTOR_NAME, + connector: helpers.LIQUITY_CONNECTOR, method: "deposit", args: [topupAmount, upperHint, lowerHint, 0], }; const depositTx = await dsa - .connect(wallet) - .cast(...encodeSpells([depositEthSpell]), wallet.address, { + .connect(userWallet) + .cast(...encodeSpells([depositEthSpell]), userWallet.address, { value: topupAmount, }); @@ -709,7 +711,7 @@ describe.only("Liquity", () => { .args; const expectedEventParams = ethers.utils.defaultAbiCoder.encode( ["address", "uint256", "uint256"], - [wallet.address, topupAmount, 0] + [userWallet.address, topupAmount, 0] ); expect(castLogEvent.eventNames[0]).eq( "LogDeposit(address,uint256,uint256)" @@ -720,34 +722,29 @@ describe.only("Liquity", () => { describe("withdraw()", () => { it("withdraws ETH from a Trove", async () => { - await helpers.createDsaTrove( - dsa, - wallet, - liquity.hintHelpers, - liquity.sortedTroves - ); + // Create a dummy Trove + await helpers.createDsaTrove(dsa, userWallet, liquity); - const originalTroveCollateral = await liquity.troveManager.getTroveColl( + const troveCollateralBefore = await liquity.troveManager.getTroveColl( dsa.address ); const withdrawAmount = ethers.utils.parseEther("1"); const upperHint = ethers.constants.AddressZero; const lowerHint = ethers.constants.AddressZero; const withdrawEthSpell = { - connector: helpers.CONNECTOR_NAME, + connector: helpers.LIQUITY_CONNECTOR, method: "withdraw", args: [withdrawAmount, upperHint, lowerHint, 0], }; - const withdrawTx = await dsa - .connect(wallet) - .cast(...encodeSpells([withdrawEthSpell]), wallet.address); + await dsa + .connect(userWallet) + .cast(...encodeSpells([withdrawEthSpell]), userWallet.address); - await withdrawTx.wait(); const troveCollateral = await liquity.troveManager.getTroveColl( dsa.address ); - const expectedTroveCollateral = originalTroveCollateral.sub( + const expectedTroveCollateral = troveCollateralBefore.sub( withdrawAmount ); @@ -757,34 +754,84 @@ describe.only("Liquity", () => { ).to.eq(expectedTroveCollateral); }); - it("returns Instadapp event name and data", async () => { - await helpers.createDsaTrove( - dsa, - wallet, - liquity.hintHelpers, - liquity.sortedTroves + it("withdraws ETH from a Trove and stores the ETH for other spells to use", async () => { + // Create a dummy Trove + await helpers.createDsaTrove(dsa, userWallet, liquity); + + const troveCollateralBefore = await liquity.troveManager.getTroveColl( + dsa.address ); + const originalUserEthBalance = await ethers.provider.getBalance( + userWallet.address + ); + + const withdrawAmount = ethers.utils.parseEther("1"); + const upperHint = ethers.constants.AddressZero; + const lowerHint = ethers.constants.AddressZero; + const withdrawId = 1; + const withdrawEthFromTroveSpell = { + connector: helpers.LIQUITY_CONNECTOR, + method: "withdraw", + args: [withdrawAmount, upperHint, lowerHint, withdrawId], + }; + + const withdrawEthSpell = { + connector: helpers.INSTADAPP_BASIC_V1_CONNECTOR, + method: "withdraw", + args: [helpers.ETH_ADDRESS, 0, userWallet.address, withdrawId, 0], + }; + const spells = [withdrawEthFromTroveSpell, withdrawEthSpell]; + await dsa + .connect(userWallet) + .cast(...encodeSpells(spells), userWallet.address, { + gasPrice: 0, // Remove gas costs so we can check balances have changed correctly + }); + + const troveCollateral = await liquity.troveManager.getTroveColl( + dsa.address + ); + const expectedTroveCollateral = troveCollateralBefore.sub( + withdrawAmount + ); + const userEthBalance = await ethers.provider.getBalance( + userWallet.address + ); + + expect( + troveCollateral, + `Trove collateral should have decreased by ${withdrawAmount} ETH` + ).to.eq(expectedTroveCollateral); + + expect( + userEthBalance, + `User ETH balance should have increased by ${withdrawAmount} ETH` + ).to.eq(originalUserEthBalance.add(withdrawAmount)); + }); + + it("returns Instadapp event name and data", async () => { + // Create a dummy Trove + await helpers.createDsaTrove(dsa, userWallet, liquity); const withdrawAmount = ethers.utils.parseEther("1"); const upperHint = ethers.constants.AddressZero; const lowerHint = ethers.constants.AddressZero; const setId = 0; const withdrawEthSpell = { - connector: helpers.CONNECTOR_NAME, + connector: helpers.LIQUITY_CONNECTOR, method: "withdraw", args: [withdrawAmount, upperHint, lowerHint, setId], }; const withdrawTx = await dsa - .connect(wallet) - .cast(...encodeSpells([withdrawEthSpell]), wallet.address); + .connect(userWallet) + .cast(...encodeSpells([withdrawEthSpell]), userWallet.address); const receipt = await withdrawTx.wait(); const castLogEvent = receipt.events.find((e) => e.event === "LogCast") .args; const expectedEventParams = ethers.utils.defaultAbiCoder.encode( ["address", "uint256", "uint256"], - [wallet.address, withdrawAmount, setId] + [userWallet.address, withdrawAmount, setId] ); expect(castLogEvent.eventNames[0]).eq( "LogWithdraw(address,uint256,uint256)" @@ -795,35 +842,32 @@ describe.only("Liquity", () => { describe("borrow()", () => { it("borrows LUSD from a Trove", async () => { - await helpers.createDsaTrove( - dsa, - wallet, - liquity.hintHelpers, - liquity.sortedTroves - ); + // Create a dummy Trove + await helpers.createDsaTrove(dsa, userWallet, liquity); - const originalTroveDebt = await liquity.troveManager.getTroveDebt( + const troveDebtBefore = await liquity.troveManager.getTroveDebt( dsa.address ); + const borrowAmount = ethers.utils.parseUnits("1000", 18); // 1000 LUSD const upperHint = ethers.constants.AddressZero; const lowerHint = ethers.constants.AddressZero; const maxFeePercentage = ethers.utils.parseUnits("0.5", 18); // 0.5% max fee const borrowSpell = { - connector: helpers.CONNECTOR_NAME, + connector: helpers.LIQUITY_CONNECTOR, method: "borrow", args: [maxFeePercentage, borrowAmount, upperHint, lowerHint, 0], }; - const borrowTx = await dsa - .connect(wallet) - .cast(...encodeSpells([borrowSpell]), wallet.address); + // Borrow more LUSD from the Trove + await dsa + .connect(userWallet) + .cast(...encodeSpells([borrowSpell]), userWallet.address); - await borrowTx.wait(); const troveDebt = await liquity.troveManager.getTroveDebt( dsa.address ); - const expectedTroveDebt = originalTroveDebt.add(borrowAmount); + const expectedTroveDebt = troveDebtBefore.add(borrowAmount); expect( troveDebt, @@ -831,35 +875,92 @@ describe.only("Liquity", () => { ).to.gte(expectedTroveDebt); }); - it("returns Instadapp event name and data", async () => { - await helpers.createDsaTrove( - dsa, - wallet, - liquity.hintHelpers, - liquity.sortedTroves + it("borrows LUSD from a Trove and stores the LUSD for other spells to use", async () => { + // Create a dummy Trove + await helpers.createDsaTrove(dsa, userWallet, liquity); + + const troveDebtBefore = await liquity.troveManager.getTroveDebt( + dsa.address ); + const borrowAmount = ethers.utils.parseUnits("1000", 18); // 1000 LUSD + const upperHint = ethers.constants.AddressZero; + const lowerHint = ethers.constants.AddressZero; + const maxFeePercentage = ethers.utils.parseUnits("0.5", 18); // 0.5% max fee + const borrowId = 1; + const borrowSpell = { + connector: helpers.LIQUITY_CONNECTOR, + method: "borrow", + args: [ + maxFeePercentage, + borrowAmount, + upperHint, + lowerHint, + borrowId, + ], + }; + const withdrawSpell = { + connector: helpers.INSTADAPP_BASIC_V1_CONNECTOR, + method: "withdraw", + args: [ + liquity.lusdToken.address, + 0, + userWallet.address, + borrowId, + 0, + ], + }; + const spells = [borrowSpell, withdrawSpell]; + + // Borrow more LUSD from the Trove + await dsa + .connect(userWallet) + .cast(...encodeSpells(spells), userWallet.address); + + const troveDebt = await liquity.troveManager.getTroveDebt( + dsa.address + ); + const expectedTroveDebt = troveDebtBefore.add(borrowAmount); + const userLusdBalance = await liquity.lusdToken.balanceOf( + userWallet.address + ); + + expect( + troveDebt, + `Trove debt should have increased by at least ${borrowAmount} ETH` + ).to.gte(expectedTroveDebt); + + expect( + userLusdBalance, + `User LUSD balance should equal the borrowed LUSD due to the second withdraw spell` + ).eq(borrowAmount); + }); + + it("returns Instadapp event name and data", async () => { + // Create a dummy Trove + await helpers.createDsaTrove(dsa, userWallet, liquity); + const borrowAmount = ethers.utils.parseUnits("1000", 18); // 1000 LUSD const upperHint = ethers.constants.AddressZero; const lowerHint = ethers.constants.AddressZero; const maxFeePercentage = ethers.utils.parseUnits("0.5", 18); // 0.5% max fee const setId = 0; const borrowSpell = { - connector: helpers.CONNECTOR_NAME, + connector: helpers.LIQUITY_CONNECTOR, method: "borrow", args: [maxFeePercentage, borrowAmount, upperHint, lowerHint, setId], }; const borrowTx = await dsa - .connect(wallet) - .cast(...encodeSpells([borrowSpell]), wallet.address); + .connect(userWallet) + .cast(...encodeSpells([borrowSpell]), userWallet.address); const receipt = await borrowTx.wait(); const castLogEvent = receipt.events.find((e) => e.event === "LogCast") .args; const expectedEventParams = ethers.utils.defaultAbiCoder.encode( ["address", "uint256", "uint256"], - [wallet.address, borrowAmount, setId] + [userWallet.address, borrowAmount, setId] ); expect(castLogEvent.eventNames[0]).eq( "LogBorrow(address,uint256,uint256)" @@ -873,41 +974,106 @@ describe.only("Liquity", () => { const depositAmount = ethers.utils.parseEther("5"); const borrowAmount = ethers.utils.parseUnits("2500", 18); + // Create a dummy Trove await helpers.createDsaTrove( dsa, - wallet, - liquity.hintHelpers, - liquity.sortedTroves, + userWallet, + liquity, depositAmount, borrowAmount ); - const originalTroveDebt = await liquity.troveManager.getTroveDebt( + const troveDebtBefore = await liquity.troveManager.getTroveDebt( dsa.address ); + // DSA account is holding 2500 LUSD from opening a Trove, so we use some of that to repay const repayAmount = ethers.utils.parseUnits("100", 18); // 100 LUSD + const { upperHint, lowerHint } = await helpers.getTroveInsertionHints( depositAmount, borrowAmount, - liquity.hintHelpers, - liquity.sortedTroves + liquity ); - const borrowSpell = { - connector: helpers.CONNECTOR_NAME, + const repaySpell = { + connector: helpers.LIQUITY_CONNECTOR, method: "repay", args: [repayAmount, upperHint, lowerHint, 0], }; await dsa - .connect(wallet) - .cast(...encodeSpells([borrowSpell]), wallet.address, { - value: repayAmount, - }); + .connect(userWallet) + .cast(...encodeSpells([repaySpell]), userWallet.address); const troveDebt = await liquity.troveManager.getTroveDebt( dsa.address ); - const expectedTroveDebt = originalTroveDebt.sub(repayAmount); + const expectedTroveDebt = troveDebtBefore.sub(repayAmount); + + expect( + troveDebt, + `Trove debt should have decreased by ${repayAmount} ETH` + ).to.eq(expectedTroveDebt); + }); + + it("repays LUSD to a Trove using LUSD collected from a previous spell", async () => { + const depositAmount = ethers.utils.parseEther("5"); + const borrowAmount = ethers.utils.parseUnits("2500", 18); + + // Create a dummy Trove + await helpers.createDsaTrove( + dsa, + userWallet, + liquity, + depositAmount, + borrowAmount + ); + + const troveDebtBefore = await liquity.troveManager.getTroveDebt( + dsa.address + ); + + const repayAmount = ethers.utils.parseUnits("100", 18); // 100 LUSD + const { upperHint, lowerHint } = await helpers.getTroveInsertionHints( + depositAmount, + borrowAmount, + liquity + ); + + // Drain the DSA's LUSD balance so that we ensure we are repaying using LUSD from a previous spell + await helpers.sendToken( + liquity.lusdToken, + borrowAmount, + dsa.address, + userWallet.address + ); + + // Allow DSA to spend user's LUSD + await liquity.lusdToken + .connect(userWallet) + .approve(dsa.address, repayAmount); + + const lusdDepositId = 1; + const depositSpell = { + connector: helpers.INSTADAPP_BASIC_V1_CONNECTOR, + method: "deposit", + args: [liquity.lusdToken.address, repayAmount, 0, lusdDepositId], + }; + const borrowSpell = { + connector: helpers.LIQUITY_CONNECTOR, + method: "repay", + args: [0, upperHint, lowerHint, lusdDepositId], + }; + + const spells = [depositSpell, borrowSpell]; + + await dsa + .connect(userWallet) + .cast(...encodeSpells(spells), userWallet.address); + + const troveDebt = await liquity.troveManager.getTroveDebt( + dsa.address + ); + const expectedTroveDebt = troveDebtBefore.sub(repayAmount); expect( troveDebt, @@ -916,13 +1082,13 @@ describe.only("Liquity", () => { }); it("returns Instadapp event name and data", async () => { + // Create a dummy Trove const depositAmount = ethers.utils.parseEther("5"); const borrowAmount = ethers.utils.parseUnits("2500", 18); await helpers.createDsaTrove( dsa, - wallet, - liquity.hintHelpers, - liquity.sortedTroves, + userWallet, + liquity, depositAmount, borrowAmount ); @@ -931,20 +1097,19 @@ describe.only("Liquity", () => { const { upperHint, lowerHint } = await helpers.getTroveInsertionHints( depositAmount, borrowAmount, - liquity.hintHelpers, - liquity.sortedTroves + liquity ); const getId = 0; const borrowSpell = { - connector: helpers.CONNECTOR_NAME, + connector: helpers.LIQUITY_CONNECTOR, method: "repay", args: [repayAmount, upperHint, lowerHint, getId], }; const repayTx = await dsa - .connect(wallet) - .cast(...encodeSpells([borrowSpell]), wallet.address, { + .connect(userWallet) + .cast(...encodeSpells([borrowSpell]), userWallet.address, { value: repayAmount, }); @@ -953,7 +1118,7 @@ describe.only("Liquity", () => { .args; const expectedEventParams = ethers.utils.defaultAbiCoder.encode( ["address", "uint256", "uint256"], - [wallet.address, repayAmount, getId] + [userWallet.address, repayAmount, getId] ); expect(castLogEvent.eventNames[0]).eq( "LogRepay(address,uint256,uint256)" @@ -964,17 +1129,13 @@ describe.only("Liquity", () => { describe("adjust()", () => { it("adjusts a Trove: deposit ETH and borrow LUSD", async () => { - await helpers.createDsaTrove( - dsa, - wallet, - liquity.hintHelpers, - liquity.sortedTroves - ); + // Create a dummy Trove + await helpers.createDsaTrove(dsa, userWallet, liquity); - const originalTroveCollateral = await liquity.troveManager.getTroveColl( + const troveCollateralBefore = await liquity.troveManager.getTroveColl( dsa.address ); - const originalTroveDebt = await liquity.troveManager.getTroveDebt( + const troveDebtBefore = await liquity.troveManager.getTroveDebt( dsa.address ); const depositAmount = ethers.utils.parseEther("1"); // 1 ETH @@ -986,7 +1147,7 @@ describe.only("Liquity", () => { const maxFeePercentage = ethers.utils.parseUnits("0.5", 18); // 0.5% max fee const adjustSpell = { - connector: helpers.CONNECTOR_NAME, + connector: helpers.LIQUITY_CONNECTOR, method: "adjust", args: [ maxFeePercentage, @@ -1003,22 +1164,22 @@ describe.only("Liquity", () => { ], }; - const adjustTx = await dsa - .connect(wallet) - .cast(...encodeSpells([adjustSpell]), wallet.address, { + // Adjust Trove by depositing ETH and borrowing LUSD + await dsa + .connect(userWallet) + .cast(...encodeSpells([adjustSpell]), userWallet.address, { value: depositAmount, gasLimit: helpers.MAX_GAS, }); - await adjustTx.wait(); const troveCollateral = await liquity.troveManager.getTroveColl( dsa.address ); const troveDebt = await liquity.troveManager.getTroveDebt( dsa.address ); - const expectedTroveColl = originalTroveCollateral.add(depositAmount); - const expectedTroveDebt = originalTroveDebt.add(borrowAmount); + const expectedTroveColl = troveCollateralBefore.add(depositAmount); + const expectedTroveDebt = troveDebtBefore.add(borrowAmount); expect( troveCollateral, @@ -1032,17 +1193,13 @@ describe.only("Liquity", () => { }); it("adjusts a Trove: withdraw ETH and repay LUSD", async () => { - await helpers.createDsaTrove( - dsa, - wallet, - liquity.hintHelpers, - liquity.sortedTroves - ); + // Create a dummy Trove + await helpers.createDsaTrove(dsa, userWallet, liquity); - const originalTroveCollateral = await liquity.troveManager.getTroveColl( + const troveCollateralBefore = await liquity.troveManager.getTroveColl( dsa.address ); - const originalTroveDebt = await liquity.troveManager.getTroveDebt( + const troveDebtBefore = await liquity.troveManager.getTroveDebt( dsa.address ); const depositAmount = 0; @@ -1050,15 +1207,14 @@ describe.only("Liquity", () => { const withdrawAmount = ethers.utils.parseEther("1"); // 1 ETH; const repayAmount = ethers.utils.parseUnits("500", 18); // 500 LUSD; const { upperHint, lowerHint } = await helpers.getTroveInsertionHints( - originalTroveCollateral.sub(withdrawAmount), - originalTroveDebt.sub(repayAmount), - liquity.hintHelpers, - liquity.sortedTroves + troveCollateralBefore.sub(withdrawAmount), + troveDebtBefore.sub(repayAmount), + liquity ); const maxFeePercentage = ethers.utils.parseUnits("0.5", 18); // 0.5% max fee const adjustSpell = { - connector: helpers.CONNECTOR_NAME, + connector: helpers.LIQUITY_CONNECTOR, method: "adjust", args: [ maxFeePercentage, @@ -1075,22 +1231,22 @@ describe.only("Liquity", () => { ], }; - const adjustTx = await dsa - .connect(wallet) - .cast(...encodeSpells([adjustSpell]), wallet.address, { + // Adjust Trove by withdrawing ETH and repaying LUSD + await dsa + .connect(userWallet) + .cast(...encodeSpells([adjustSpell]), userWallet.address, { value: depositAmount, gasLimit: helpers.MAX_GAS, }); - await adjustTx.wait(); const troveCollateral = await liquity.troveManager.getTroveColl( dsa.address ); const troveDebt = await liquity.troveManager.getTroveDebt( dsa.address ); - const expectedTroveColl = originalTroveCollateral.sub(withdrawAmount); - const expectedTroveDebt = originalTroveDebt.sub(repayAmount); + const expectedTroveColl = troveCollateralBefore.sub(withdrawAmount); + const expectedTroveDebt = troveDebtBefore.sub(repayAmount); expect( troveCollateral, @@ -1103,13 +1259,191 @@ describe.only("Liquity", () => { ).to.gte(expectedTroveDebt); }); - it("returns Instadapp event name and data", async () => { - await helpers.createDsaTrove( - dsa, - wallet, - liquity.hintHelpers, - liquity.sortedTroves + it("adjusts a Trove: deposit ETH and repay LUSD using previous spells", async () => { + // Create a dummy Trove + await helpers.createDsaTrove(dsa, userWallet, liquity); + + const troveCollateralBefore = await liquity.troveManager.getTroveColl( + dsa.address ); + const troveDebtBefore = await liquity.troveManager.getTroveDebt( + dsa.address + ); + const depositAmount = ethers.utils.parseEther("1"); // 1 ETH + const borrowAmount = 0; + const withdrawAmount = 0; + const repayAmount = ethers.utils.parseUnits("100", 18); // 100 lUSD + const upperHint = ethers.constants.AddressZero; + const lowerHint = ethers.constants.AddressZero; + const maxFeePercentage = ethers.utils.parseUnits("0.5", 18); // 0.5% max fee + + const ethDepositId = 1; + const lusdRepayId = 2; + + const depositEthSpell = { + connector: helpers.INSTADAPP_BASIC_V1_CONNECTOR, + method: "deposit", + args: [helpers.ETH_ADDRESS, depositAmount, 0, ethDepositId], + }; + + const depositLusdSpell = { + connector: helpers.INSTADAPP_BASIC_V1_CONNECTOR, + method: "deposit", + args: [liquity.lusdToken.address, repayAmount, 0, lusdRepayId], + }; + + const adjustSpell = { + connector: helpers.LIQUITY_CONNECTOR, + method: "adjust", + args: [ + maxFeePercentage, + withdrawAmount, + 0, // Deposit amount comes from a previous spell's storage slot + borrowAmount, + 0, // Repay amount comes from a previous spell's storage slot + upperHint, + lowerHint, + ethDepositId, + 0, + lusdRepayId, + 0, + ], + }; + const spells = [depositEthSpell, depositLusdSpell, adjustSpell]; + + // Send user some LUSD so they can repay + await helpers.sendToken( + liquity.lusdToken, + repayAmount, + helpers.JUSTIN_SUN_ADDRESS, + userWallet.address + ); + + // Allow DSA to spend user's LUSD + await liquity.lusdToken + .connect(userWallet) + .approve(dsa.address, repayAmount); + + // Adjust Trove by depositing ETH and borrowing LUSD + await dsa + .connect(userWallet) + .cast(...encodeSpells(spells), userWallet.address, { + value: depositAmount, + gasLimit: helpers.MAX_GAS, + }); + + const troveCollateral = await liquity.troveManager.getTroveColl( + dsa.address + ); + const troveDebt = await liquity.troveManager.getTroveDebt( + dsa.address + ); + const expectedTroveColl = troveCollateralBefore.add(depositAmount); + const expectedTroveDebt = troveDebtBefore.add(borrowAmount); + + expect( + troveCollateral, + `Trove collateral should have increased by ${depositAmount} ETH` + ).to.eq(expectedTroveColl); + + expect( + troveDebt, + `Trove debt should have increased by at least ${borrowAmount} ETH` + ).to.eq(expectedTroveDebt); + }); + + it("adjusts a Trove: withdraw ETH, borrow LUSD, and store the amounts for other spells", async () => { + // Create a dummy Trove + await helpers.createDsaTrove(dsa, userWallet, liquity); + + const userEthBalanceBefore = await ethers.provider.getBalance( + userWallet.address + ); + const userLusdBalanceBefore = await liquity.lusdToken.balanceOf( + userWallet.address + ); + + const depositAmount = 0; + const borrowAmount = ethers.utils.parseUnits("100", 18); // 100 LUSD + const withdrawAmount = ethers.utils.parseEther("1"); // 1 ETH + const repayAmount = 0; + const upperHint = ethers.constants.AddressZero; + const lowerHint = ethers.constants.AddressZero; + const maxFeePercentage = ethers.utils.parseUnits("0.5", 18); // 0.5% max fee + + const ethWithdrawId = 1; + const lusdBorrowId = 2; + + const adjustSpell = { + connector: helpers.LIQUITY_CONNECTOR, + method: "adjust", + args: [ + maxFeePercentage, + withdrawAmount, + depositAmount, + borrowAmount, + repayAmount, + upperHint, + lowerHint, + 0, + ethWithdrawId, + 0, + lusdBorrowId, + ], + }; + + const withdrawEthSpell = { + connector: helpers.INSTADAPP_BASIC_V1_CONNECTOR, + method: "withdraw", + args: [ + helpers.ETH_ADDRESS, + 0, + userWallet.address, + ethWithdrawId, + 0, + ], + }; + + const withdrawLusdSpell = { + connector: helpers.INSTADAPP_BASIC_V1_CONNECTOR, + method: "withdraw", + args: [ + liquity.lusdToken.address, + 0, + userWallet.address, + lusdBorrowId, + 0, + ], + }; + + const spells = [adjustSpell, withdrawEthSpell, withdrawLusdSpell]; + + // Adjust Trove by withdrawing ETH and borrowing LUSD + await dsa + .connect(userWallet) + .cast(...encodeSpells(spells), userWallet.address, { + gasLimit: helpers.MAX_GAS, + gasPrice: 0, + }); + + const userEthBalanceAfter = await ethers.provider.getBalance( + userWallet.address + ); + const userLusdBalanceAfter = await liquity.lusdToken.balanceOf( + userWallet.address + ); + + expect(userEthBalanceAfter).eq( + userEthBalanceBefore.add(withdrawAmount) + ); + expect(userLusdBalanceAfter).eq( + userLusdBalanceBefore.add(borrowAmount) + ); + }); + + it("returns Instadapp event name and data", async () => { + // Create a dummy Trove + await helpers.createDsaTrove(dsa, userWallet, liquity); const depositAmount = ethers.utils.parseEther("1"); // 1 ETH const borrowAmount = ethers.utils.parseUnits("500", 18); // 500 LUSD @@ -1124,7 +1458,7 @@ describe.only("Liquity", () => { const setBorrowId = 0; const adjustSpell = { - connector: helpers.CONNECTOR_NAME, + connector: helpers.LIQUITY_CONNECTOR, method: "adjust", args: [ maxFeePercentage, @@ -1142,8 +1476,8 @@ describe.only("Liquity", () => { }; const adjustTx = await dsa - .connect(wallet) - .cast(...encodeSpells([adjustSpell]), wallet.address, { + .connect(userWallet) + .cast(...encodeSpells([adjustSpell]), userWallet.address, { value: depositAmount, gasLimit: helpers.MAX_GAS, }); @@ -1165,7 +1499,7 @@ describe.only("Liquity", () => { "uint256", ], [ - wallet.address, + userWallet.address, maxFeePercentage, depositAmount, withdrawAmount, @@ -1192,9 +1526,8 @@ describe.only("Liquity", () => { await helpers.createDsaTrove( dsa, - wallet, - liquity.hintHelpers, - liquity.sortedTroves, + userWallet, + liquity, depositAmount, borrowAmount ); @@ -1205,23 +1538,18 @@ describe.only("Liquity", () => { liquity.lusdToken, redeemAmount, contracts.STABILITY_POOL_ADDRESS, - wallet.address + userWallet.address ); const { partialRedemptionHintNicr, firstRedemptionHint, upperHint, lowerHint, - } = await helpers.getRedemptionHints( - redeemAmount, - liquity.hintHelpers, - liquity.sortedTroves, - liquity.priceFeed - ); + } = await helpers.getRedemptionHints(redeemAmount, liquity); const maxFeePercentage = ethers.utils.parseUnits("0.5", 18); // 0.5% max fee await liquity.troveManager - .connect(wallet) + .connect(userWallet) .redeemCollateral( redeemAmount, firstRedemptionHint, @@ -1235,31 +1563,27 @@ describe.only("Liquity", () => { } ); - const ethBalanceBefore = await ethers.provider.getBalance( + const remainingEthCollateral = await liquity.collSurplus.getCollateral( dsa.address ); // Claim the remaining collateral from the redeemed Trove const claimCollateralFromRedemptionSpell = { - connector: helpers.CONNECTOR_NAME, + connector: helpers.LIQUITY_CONNECTOR, method: "claimCollateralFromRedemption", args: [0], }; - const claimTx = await dsa - .connect(wallet) + await dsa + .connect(userWallet) .cast( ...encodeSpells([claimCollateralFromRedemptionSpell]), - wallet.address + userWallet.address ); - await claimTx.wait(); + const ethBalance = await ethers.provider.getBalance(dsa.address); - const ethBalanceAfter = await ethers.provider.getBalance(dsa.address); - - const expectedRemainingCollateral = "527014573774047160"; // ~0.52 ETH based on this mainnet fork's blockNumber - expect(ethBalanceAfter).to.be.gt(ethBalanceBefore); - expect(ethBalanceAfter).to.eq(expectedRemainingCollateral); + expect(ethBalance).to.eq(remainingEthCollateral); }); it("returns Instadapp event name and data", async () => { @@ -1269,9 +1593,8 @@ describe.only("Liquity", () => { await helpers.createDsaTrove( dsa, - wallet, - liquity.hintHelpers, - liquity.sortedTroves, + userWallet, + liquity, depositAmount, borrowAmount ); @@ -1283,23 +1606,18 @@ describe.only("Liquity", () => { liquity.lusdToken, redeemAmount, contracts.STABILITY_POOL_ADDRESS, - wallet.address + userWallet.address ); const { partialRedemptionHintNicr, firstRedemptionHint, upperHint, lowerHint, - } = await helpers.getRedemptionHints( - redeemAmount, - liquity.hintHelpers, - liquity.sortedTroves, - liquity.priceFeed - ); + } = await helpers.getRedemptionHints(redeemAmount, liquity); const maxFeePercentage = ethers.utils.parseUnits("0.5", 18); // 0.5% max fee await liquity.troveManager - .connect(wallet) + .connect(userWallet) .redeemCollateral( redeemAmount, firstRedemptionHint, @@ -1317,16 +1635,16 @@ describe.only("Liquity", () => { ); const claimCollateralFromRedemptionSpell = { - connector: helpers.CONNECTOR_NAME, + connector: helpers.LIQUITY_CONNECTOR, method: "claimCollateralFromRedemption", args: [setId], }; const claimTx = await dsa - .connect(wallet) + .connect(userWallet) .cast( ...encodeSpells([claimCollateralFromRedemptionSpell]), - wallet.address + userWallet.address ); const receipt = await claimTx.wait(); @@ -1334,7 +1652,7 @@ describe.only("Liquity", () => { .args; const expectedEventParams = ethers.utils.defaultAbiCoder.encode( ["address", "uint256", "uint256"], - [wallet.address, claimAmount, setId] + [userWallet.address, claimAmount, setId] ); expect(castLogEvent.eventNames[0]).eq( "LogClaimCollateralFromRedemption(address,uint256,uint256)" @@ -1358,16 +1676,54 @@ describe.only("Liquity", () => { ); const stabilityDepositSpell = { - connector: helpers.CONNECTOR_NAME, + connector: helpers.LIQUITY_CONNECTOR, method: "stabilityDeposit", args: [amount, frontendTag, 0, 0, 0], }; - const depositTx = await dsa - .connect(wallet) - .cast(...encodeSpells([stabilityDepositSpell]), wallet.address); + await dsa + .connect(userWallet) + .cast(...encodeSpells([stabilityDepositSpell]), userWallet.address); + + const depositedAmount = await liquity.stabilityPool.getCompoundedLUSDDeposit( + dsa.address + ); + expect(depositedAmount).to.eq(amount); + }); + + it("deposits into Stability Pool using LUSD collected from a previous spell", async () => { + const amount = ethers.utils.parseUnits("100", 18); + const frontendTag = ethers.constants.AddressZero; + + await helpers.sendToken( + liquity.lusdToken, + amount, + contracts.STABILITY_POOL_ADDRESS, + userWallet.address + ); + const lusdDepositId = 1; + + const depositLusdSpell = { + connector: helpers.INSTADAPP_BASIC_V1_CONNECTOR, + method: "deposit", + args: [liquity.lusdToken.address, amount, 0, lusdDepositId], + }; + const stabilityDepositSpell = { + connector: helpers.LIQUITY_CONNECTOR, + method: "stabilityDeposit", + args: [0, frontendTag, lusdDepositId, 0, 0], + }; + const spells = [depositLusdSpell, stabilityDepositSpell]; + + // Allow DSA to spend user's LUSD + await liquity.lusdToken + .connect(userWallet) + .approve(dsa.address, amount); + + await dsa + .connect(userWallet) + .cast(...encodeSpells(spells), userWallet.address); - await depositTx.wait(); const depositedAmount = await liquity.stabilityPool.getCompoundedLUSDDeposit( dsa.address ); @@ -1389,14 +1745,14 @@ describe.only("Liquity", () => { ); const stabilityDepositSpell = { - connector: helpers.CONNECTOR_NAME, + connector: helpers.LIQUITY_CONNECTOR, method: "stabilityDeposit", args: [amount, frontendTag, getDepositId, 0, 0], }; const depositTx = await dsa - .connect(wallet) - .cast(...encodeSpells([stabilityDepositSpell]), wallet.address); + .connect(userWallet) + .cast(...encodeSpells([stabilityDepositSpell]), userWallet.address); const receipt = await depositTx.wait(); const castLogEvent = receipt.events.find((e) => e.event === "LogCast") @@ -1404,7 +1760,7 @@ describe.only("Liquity", () => { const expectedEventParams = ethers.utils.defaultAbiCoder.encode( ["address", "uint256", "address", "uint256", "uint256", "uint256"], [ - wallet.address, + userWallet.address, amount, frontendTag, getDepositId, @@ -1423,13 +1779,13 @@ describe.only("Liquity", () => { it("withdraws from Stability Pool", async () => { // Start this test from scratch since we don't want to rely on test order for this to pass. [liquity, dsa] = await helpers.resetInitialState( - wallet.address, + userWallet.address, contracts ); // The current block number has liquidatable Troves. // Remove them otherwise Stability Pool withdrawals are disabled - await liquity.troveManager.connect(wallet).liquidateTroves(90, { + await liquity.troveManager.connect(userWallet).liquidateTroves(90, { gasLimit: helpers.MAX_GAS, }); const amount = ethers.utils.parseUnits("100", 18); @@ -1443,24 +1799,22 @@ describe.only("Liquity", () => { ); const stabilityDepositSpell = { - connector: helpers.CONNECTOR_NAME, + connector: helpers.LIQUITY_CONNECTOR, method: "stabilityDeposit", args: [amount, frontendTag, 0, 0, 0], }; // Withdraw half of the deposit const stabilitWithdrawSpell = { - connector: helpers.CONNECTOR_NAME, + connector: helpers.LIQUITY_CONNECTOR, method: "stabilityWithdraw", args: [amount.div(2), 0, 0, 0], }; const spells = [stabilityDepositSpell, stabilitWithdrawSpell]; - const castTx = await dsa - .connect(wallet) - .cast(...encodeSpells(spells), wallet.address); - - await castTx.wait(); + await dsa + .connect(userWallet) + .cast(...encodeSpells(spells), userWallet.address); const depositedAmount = await liquity.stabilityPool.getCompoundedLUSDDeposit( dsa.address @@ -1471,16 +1825,85 @@ describe.only("Liquity", () => { expect(dsaLusdBalance).to.eq(amount.div(2)); }); - it("returns Instadapp event name and data", async () => { + it("withdraws from Stability Pool and stores the LUSD for other spells", async () => { // Start this test from scratch since we don't want to rely on test order for this to pass. [liquity, dsa] = await helpers.resetInitialState( - wallet.address, + userWallet.address, contracts ); // The current block number has liquidatable Troves. // Remove them otherwise Stability Pool withdrawals are disabled - await liquity.troveManager.connect(wallet).liquidateTroves(90, { + await liquity.troveManager.connect(userWallet).liquidateTroves(90, { + gasLimit: helpers.MAX_GAS, + }); + const amount = ethers.utils.parseUnits("100", 18); + const frontendTag = ethers.constants.AddressZero; + const withdrawId = 1; + + await helpers.sendToken( + liquity.lusdToken, + amount, + contracts.STABILITY_POOL_ADDRESS, + dsa.address + ); + + const stabilityDepositSpell = { + connector: helpers.LIQUITY_CONNECTOR, + method: "stabilityDeposit", + args: [amount, frontendTag, 0, 0, 0], + }; + + // Withdraw half of the deposit + const stabilitWithdrawSpell = { + connector: helpers.LIQUITY_CONNECTOR, + method: "stabilityWithdraw", + args: [amount.div(2), 0, 0, withdrawId], + }; + + const withdrawLusdSpell = { + connector: helpers.INSTADAPP_BASIC_V1_CONNECTOR, + method: "withdraw", + args: [ + liquity.lusdToken.address, + 0, + userWallet.address, + withdrawId, + 0, + ], + }; + + const spells = [ + stabilityDepositSpell, + stabilitWithdrawSpell, + withdrawLusdSpell, + ]; + + await dsa + .connect(userWallet) + .cast(...encodeSpells(spells), userWallet.address); + + const depositedAmount = await liquity.stabilityPool.getCompoundedLUSDDeposit( + dsa.address + ); + const walletLusdBalance = await liquity.lusdToken.balanceOf( + dsa.address + ); + + expect(depositedAmount).to.eq(amount.div(2)); + expect(walletLusdBalance).to.eq(amount.div(2)); + }); + + it("returns Instadapp event name and data", async () => { + // Start this test from scratch since we don't want to rely on test order for this to pass. + [liquity, dsa] = await helpers.resetInitialState( + userWallet.address, + contracts + ); + + // The current block number has liquidatable Troves. + // Remove them otherwise Stability Pool withdrawals are disabled + await liquity.troveManager.connect(userWallet).liquidateTroves(90, { gasLimit: helpers.MAX_GAS, }); const amount = ethers.utils.parseUnits("100", 18); @@ -1494,7 +1917,7 @@ describe.only("Liquity", () => { ); const stabilityDepositSpell = { - connector: helpers.CONNECTOR_NAME, + connector: helpers.LIQUITY_CONNECTOR, method: "stabilityDeposit", args: [amount, frontendTag, 0, 0, 0], }; @@ -1506,15 +1929,15 @@ describe.only("Liquity", () => { const setLqtyGainId = 0; const stabilitWithdrawSpell = { - connector: helpers.CONNECTOR_NAME, + connector: helpers.LIQUITY_CONNECTOR, method: "stabilityWithdraw", args: [withdrawAmount, setWithdrawId, setEthGainId, setLqtyGainId], }; const spells = [stabilityDepositSpell, stabilitWithdrawSpell]; const castTx = await dsa - .connect(wallet) - .cast(...encodeSpells(spells), wallet.address); + .connect(userWallet) + .cast(...encodeSpells(spells), userWallet.address); const receipt = await castTx.wait(); const castLogEvent = receipt.events.find((e) => e.event === "LogCast") @@ -1522,7 +1945,7 @@ describe.only("Liquity", () => { const expectedEventParams = ethers.utils.defaultAbiCoder.encode( ["address", "uint256", "uint256", "uint256", "uint256"], [ - wallet.address, + userWallet.address, withdrawAmount, setWithdrawId, setEthGainId, @@ -1540,19 +1963,15 @@ describe.only("Liquity", () => { beforeEach(async () => { // Start these test from fresh so that we definitely have a liquidatable Trove within this block [liquity, dsa] = await helpers.resetInitialState( - wallet.address, + userWallet.address, contracts ); }); it("moves ETH gain from Stability Pool to Trove", async () => { // Create a DSA owned Trove to capture ETH liquidation gains - await helpers.createDsaTrove( - dsa, - wallet, - liquity.hintHelpers, - liquity.sortedTroves - ); + // Create a dummy Trove + await helpers.createDsaTrove(dsa, userWallet, liquity); const troveCollateralBefore = await liquity.troveManager.getTroveColl( dsa.address ); @@ -1561,20 +1980,18 @@ describe.only("Liquity", () => { const amount = ethers.utils.parseUnits("100", 18); const frontendTag = ethers.constants.AddressZero; const stabilityDepositSpell = { - connector: helpers.CONNECTOR_NAME, + connector: helpers.LIQUITY_CONNECTOR, method: "stabilityDeposit", args: [amount, frontendTag, 0, 0, 0], }; - const depositTx = await dsa - .connect(wallet) - .cast(...encodeSpells([stabilityDepositSpell]), wallet.address); - - await depositTx.wait(); + await dsa + .connect(userWallet) + .cast(...encodeSpells([stabilityDepositSpell]), userWallet.address); // Liquidate a Trove to create an ETH gain for the new DSA Trove await liquity.troveManager - .connect(wallet) + .connect(userWallet) .liquidate(helpers.LIQUIDATABLE_TROVE_ADDRESS, { gasLimit: helpers.MAX_GAS, // permit max gas }); @@ -1585,16 +2002,14 @@ describe.only("Liquity", () => { // Move ETH gain to Trove const moveEthGainSpell = { - connector: helpers.CONNECTOR_NAME, + connector: helpers.LIQUITY_CONNECTOR, method: "stabilityMoveEthGainToTrove", args: [ethers.constants.AddressZero, ethers.constants.AddressZero], }; - const moveEthGainTx = await dsa - .connect(wallet) - .cast(...encodeSpells([moveEthGainSpell]), wallet.address); - - await moveEthGainTx.wait(); + await dsa + .connect(userWallet) + .cast(...encodeSpells([moveEthGainSpell]), userWallet.address); const ethGainAfterMove = await liquity.stabilityPool.getDepositorETHGain( dsa.address @@ -1611,31 +2026,25 @@ describe.only("Liquity", () => { it("returns Instadapp event name and data", async () => { // Create a DSA owned Trove to capture ETH liquidation gains - await helpers.createDsaTrove( - dsa, - wallet, - liquity.hintHelpers, - liquity.sortedTroves - ); + // Create a dummy Trove + await helpers.createDsaTrove(dsa, userWallet, liquity); // Create a Stability Deposit using the Trove's borrowed LUSD const amount = ethers.utils.parseUnits("100", 18); const frontendTag = ethers.constants.AddressZero; const stabilityDepositSpell = { - connector: helpers.CONNECTOR_NAME, + connector: helpers.LIQUITY_CONNECTOR, method: "stabilityDeposit", args: [amount, frontendTag, 0, 0, 0], }; - const depositTx = await dsa - .connect(wallet) - .cast(...encodeSpells([stabilityDepositSpell]), wallet.address); - - await depositTx.wait(); + await dsa + .connect(userWallet) + .cast(...encodeSpells([stabilityDepositSpell]), userWallet.address); // Liquidate a Trove to create an ETH gain for the new DSA Trove await liquity.troveManager - .connect(wallet) + .connect(userWallet) .liquidate(helpers.LIQUIDATABLE_TROVE_ADDRESS, { gasLimit: helpers.MAX_GAS, // permit max gas }); @@ -1646,14 +2055,14 @@ describe.only("Liquity", () => { // Move ETH gain to Trove const moveEthGainSpell = { - connector: helpers.CONNECTOR_NAME, + connector: helpers.LIQUITY_CONNECTOR, method: "stabilityMoveEthGainToTrove", args: [ethers.constants.AddressZero, ethers.constants.AddressZero], }; const moveEthGainTx = await dsa - .connect(wallet) - .cast(...encodeSpells([moveEthGainSpell]), wallet.address); + .connect(userWallet) + .cast(...encodeSpells([moveEthGainSpell]), userWallet.address); const receipt = await moveEthGainTx.wait(); @@ -1661,7 +2070,7 @@ describe.only("Liquity", () => { .args; const expectedEventParams = ethers.utils.defaultAbiCoder.encode( ["address", "uint256"], - [wallet.address, ethGainFromLiquidation] + [userWallet.address, ethGainFromLiquidation] ); expect(castLogEvent.eventNames[0]).eq( "LogStabilityMoveEthGainToTrove(address,uint256)" @@ -1687,16 +2096,60 @@ describe.only("Liquity", () => { ); const stakeSpell = { - connector: helpers.CONNECTOR_NAME, + connector: helpers.LIQUITY_CONNECTOR, method: "stake", args: [amount, 0, 0, 0], }; - const stakeTx = await dsa - .connect(wallet) - .cast(...encodeSpells([stakeSpell]), wallet.address); + await dsa + .connect(userWallet) + .cast(...encodeSpells([stakeSpell]), userWallet.address); - await stakeTx.wait(); + const lqtyBalance = await liquity.lqtyToken.balanceOf(dsa.address); + expect(lqtyBalance).to.eq(0); + + const totalStakingBalance = await liquity.lqtyToken.balanceOf( + contracts.STAKING_ADDRESS + ); + expect(totalStakingBalance).to.eq( + totalStakingBalanceBefore.add(amount) + ); + }); + + it("stakes LQTY using LQTY obtained from a previous spell", async () => { + const totalStakingBalanceBefore = await liquity.lqtyToken.balanceOf( + contracts.STAKING_ADDRESS + ); + + const amount = ethers.utils.parseUnits("1", 18); + await helpers.sendToken( + liquity.lqtyToken, + amount, + helpers.JUSTIN_SUN_ADDRESS, + userWallet.address + ); + + const lqtyDepositId = 1; + const depositSpell = { + connector: helpers.INSTADAPP_BASIC_V1_CONNECTOR, + method: "deposit", + args: [liquity.lqtyToken.address, amount, 0, lqtyDepositId], + }; + const stakeSpell = { + connector: helpers.LIQUITY_CONNECTOR, + method: "stake", + args: [0, lqtyDepositId, lqtyDepositId, 0], + }; + const spells = [depositSpell, stakeSpell]; + + // Allow DSA to spend user's LQTY + await liquity.lqtyToken + .connect(userWallet) + .approve(dsa.address, amount); + + await dsa + .connect(userWallet) + .cast(...encodeSpells(spells), userWallet.address); const lqtyBalance = await liquity.lqtyToken.balanceOf(dsa.address); expect(lqtyBalance).to.eq(0); @@ -1722,14 +2175,14 @@ describe.only("Liquity", () => { const setEthGainId = 0; const setLusdGainId = 0; const stakeSpell = { - connector: helpers.CONNECTOR_NAME, + connector: helpers.LIQUITY_CONNECTOR, method: "stake", args: [amount, getStakeId, setEthGainId, setLusdGainId], }; const stakeTx = await dsa - .connect(wallet) - .cast(...encodeSpells([stakeSpell]), wallet.address); + .connect(userWallet) + .cast(...encodeSpells([stakeSpell]), userWallet.address); const receipt = await stakeTx.wait(); @@ -1737,7 +2190,13 @@ describe.only("Liquity", () => { .args; const expectedEventParams = ethers.utils.defaultAbiCoder.encode( ["address", "uint256", "uint256", "uint256", "uint256"], - [wallet.address, amount, getStakeId, setEthGainId, setLusdGainId] + [ + userWallet.address, + amount, + getStakeId, + setEthGainId, + setLusdGainId, + ] ); expect(castLogEvent.eventNames[0]).eq( "LogStake(address,uint256,uint256,uint256,uint256)" @@ -1757,31 +2216,27 @@ describe.only("Liquity", () => { ); const stakeSpell = { - connector: helpers.CONNECTOR_NAME, + connector: helpers.LIQUITY_CONNECTOR, method: "stake", args: [amount, 0, 0, 0], }; - const stakeTx = await dsa - .connect(wallet) - .cast(...encodeSpells([stakeSpell]), wallet.address); - - await stakeTx.wait(); + await dsa + .connect(userWallet) + .cast(...encodeSpells([stakeSpell]), userWallet.address); const totalStakingBalanceBefore = await liquity.lqtyToken.balanceOf( contracts.STAKING_ADDRESS ); const unstakeSpell = { - connector: helpers.CONNECTOR_NAME, + connector: helpers.LIQUITY_CONNECTOR, method: "unstake", args: [amount, 0, 0, 0], }; - const unstakeTx = await dsa - .connect(wallet) - .cast(...encodeSpells([unstakeSpell]), wallet.address); - - await unstakeTx.wait(); + await dsa + .connect(userWallet) + .cast(...encodeSpells([unstakeSpell]), userWallet.address); const lqtyBalance = await liquity.lqtyToken.balanceOf(dsa.address); expect(lqtyBalance).to.eq(amount); @@ -1794,6 +2249,65 @@ describe.only("Liquity", () => { ); }); + it("unstakes LQTY and stores the LQTY for other spells", async () => { + const amount = ethers.utils.parseUnits("1", 18); + await helpers.sendToken( + liquity.lqtyToken, + amount, + helpers.JUSTIN_SUN_ADDRESS, + dsa.address + ); + + const stakeSpell = { + connector: helpers.LIQUITY_CONNECTOR, + method: "stake", + args: [amount, 0, 0, 0], + }; + + await dsa + .connect(userWallet) + .cast(...encodeSpells([stakeSpell]), userWallet.address); + + const totalStakingBalanceBefore = await liquity.lqtyToken.balanceOf( + contracts.STAKING_ADDRESS + ); + const withdrawId = 1; + const unstakeSpell = { + connector: helpers.LIQUITY_CONNECTOR, + method: "unstake", + args: [amount, withdrawId, 0, 0], + }; + + const withdrawLqtySpell = { + connector: helpers.INSTADAPP_BASIC_V1_CONNECTOR, + method: "withdraw", + args: [ + liquity.lqtyToken.address, + 0, + userWallet.address, + withdrawId, + 0, + ], + }; + const spells = [unstakeSpell, withdrawLqtySpell]; + await dsa + .connect(userWallet) + .cast(...encodeSpells(spells), userWallet.address); + + const lqtyBalance = await liquity.lqtyToken.balanceOf(dsa.address); + const totalStakingBalance = await liquity.lqtyToken.balanceOf( + contracts.STAKING_ADDRESS + ); + const userLqtyBalance = await liquity.lqtyToken.balanceOf( + userWallet.address + ); + expect(lqtyBalance).to.eq(0); + expect(totalStakingBalance).to.eq( + totalStakingBalanceBefore.sub(amount) + ); + expect(userLqtyBalance).to.eq(amount); + }); + it("returns Instadapp event name and data", async () => { const amount = ethers.utils.parseUnits("1", 18); await helpers.sendToken( @@ -1804,27 +2318,27 @@ describe.only("Liquity", () => { ); const stakeSpell = { - connector: helpers.CONNECTOR_NAME, + connector: helpers.LIQUITY_CONNECTOR, method: "stake", args: [amount, 0, 0, 0], }; await dsa - .connect(wallet) - .cast(...encodeSpells([stakeSpell]), wallet.address); + .connect(userWallet) + .cast(...encodeSpells([stakeSpell]), userWallet.address); const setUnstakeId = 0; const setEthGainId = 0; const setLusdGainId = 0; const unstakeSpell = { - connector: helpers.CONNECTOR_NAME, + connector: helpers.LIQUITY_CONNECTOR, method: "unstake", args: [amount, setUnstakeId, setEthGainId, setLusdGainId], }; const unstakeTx = await dsa - .connect(wallet) - .cast(...encodeSpells([unstakeSpell]), wallet.address); + .connect(userWallet) + .cast(...encodeSpells([unstakeSpell]), userWallet.address); const receipt = await unstakeTx.wait(); @@ -1832,7 +2346,13 @@ describe.only("Liquity", () => { .args; const expectedEventParams = ethers.utils.defaultAbiCoder.encode( ["address", "uint256", "uint256", "uint256", "uint256"], - [wallet.address, amount, setUnstakeId, setEthGainId, setLusdGainId] + [ + userWallet.address, + amount, + setUnstakeId, + setEthGainId, + setLusdGainId, + ] ); expect(castLogEvent.eventNames[0]).eq( "LogUnstake(address,uint256,uint256,uint256,uint256)" @@ -1843,7 +2363,7 @@ describe.only("Liquity", () => { describe("claimStakingGains()", () => { it("Claims gains from staking", async () => { - const stakerDsa = await buildDSAv2(wallet.address); + const stakerDsa = await buildDSAv2(userWallet.address); const amount = ethers.utils.parseUnits("1000", 18); // 1000 LQTY // Stake lots of LQTY @@ -1854,27 +2374,22 @@ describe.only("Liquity", () => { stakerDsa.address ); const stakeSpell = { - connector: helpers.CONNECTOR_NAME, + connector: helpers.LIQUITY_CONNECTOR, method: "stake", args: [amount, 0, 0, 0], }; await stakerDsa - .connect(wallet) - .cast(...encodeSpells([stakeSpell]), wallet.address); + .connect(userWallet) + .cast(...encodeSpells([stakeSpell]), userWallet.address); // Open a Trove to cause an ETH issuance gain for stakers - await helpers.createDsaTrove( - dsa, - wallet, - liquity.hintHelpers, - liquity.sortedTroves - ); + await helpers.createDsaTrove(dsa, userWallet, liquity); // Redeem some ETH to cause an LUSD redemption gain for stakers await helpers.redeem( ethers.utils.parseUnits("1000", 18), contracts.STABILITY_POOL_ADDRESS, - wallet, + userWallet, liquity ); @@ -1888,7 +2403,7 @@ describe.only("Liquity", () => { ); const claimStakingGainsSpell = { - connector: helpers.CONNECTOR_NAME, + connector: helpers.LIQUITY_CONNECTOR, method: "claimStakingGains", args: [setEthGainId, setLusdGainId], }; @@ -1899,8 +2414,11 @@ describe.only("Liquity", () => { // Claim gains await stakerDsa - .connect(wallet) - .cast(...encodeSpells([claimStakingGainsSpell]), wallet.address); + .connect(userWallet) + .cast( + ...encodeSpells([claimStakingGainsSpell]), + userWallet.address + ); const ethBalanceAfter = await ethers.provider.getBalance( stakerDsa.address @@ -1912,8 +2430,8 @@ describe.only("Liquity", () => { expect(lusdBalanceAfter).to.eq(lusdGain); }); - it("returns Instadapp event name and data", async () => { - const stakerDsa = await buildDSAv2(wallet.address); + it("Claims gains from staking and stores them for other spells", async () => { + const stakerDsa = await buildDSAv2(userWallet.address); const amount = ethers.utils.parseUnits("1000", 18); // 1000 LQTY // Stake lots of LQTY @@ -1924,27 +2442,122 @@ describe.only("Liquity", () => { stakerDsa.address ); const stakeSpell = { - connector: helpers.CONNECTOR_NAME, + connector: helpers.LIQUITY_CONNECTOR, method: "stake", args: [amount, 0, 0, 0], }; await stakerDsa - .connect(wallet) - .cast(...encodeSpells([stakeSpell]), wallet.address); + .connect(userWallet) + .cast(...encodeSpells([stakeSpell]), userWallet.address); // Open a Trove to cause an ETH issuance gain for stakers - await helpers.createDsaTrove( - dsa, - wallet, - liquity.hintHelpers, - liquity.sortedTroves - ); + await helpers.createDsaTrove(dsa, userWallet, liquity); // Redeem some ETH to cause an LUSD redemption gain for stakers await helpers.redeem( ethers.utils.parseUnits("1000", 18), contracts.STABILITY_POOL_ADDRESS, - wallet, + userWallet, + liquity + ); + + const ethGain = await liquity.staking.getPendingETHGain( + stakerDsa.address + ); + const lusdGain = await liquity.staking.getPendingLUSDGain( + stakerDsa.address + ); + const lusdBalanceBefore = await liquity.lusdToken.balanceOf( + userWallet.address + ); + const ethBalanceBefore = await ethers.provider.getBalance( + userWallet.address + ); + const ethGainId = 111; + const lusdGainId = 222; + + const claimStakingGainsSpell = { + connector: helpers.LIQUITY_CONNECTOR, + method: "claimStakingGains", + args: [ethGainId, lusdGainId], + }; + + const withdrawEthSpell = { + connector: helpers.INSTADAPP_BASIC_V1_CONNECTOR, + method: "withdraw", + args: [helpers.ETH_ADDRESS, 0, userWallet.address, ethGainId, 0], + }; + + const withdrawLusdSpell = { + connector: helpers.INSTADAPP_BASIC_V1_CONNECTOR, + method: "withdraw", + args: [ + liquity.lusdToken.address, + 0, + userWallet.address, + lusdGainId, + 0, + ], + }; + + const spells = [ + claimStakingGainsSpell, + withdrawEthSpell, + withdrawLusdSpell, + ]; + + // Claim gains + await stakerDsa + .connect(userWallet) + .cast(...encodeSpells(spells), userWallet.address, { + gasPrice: 0, + }); + + const ethBalanceAfter = await ethers.provider.getBalance( + userWallet.address + ); + const lusdBalanceAfter = await liquity.lusdToken.balanceOf( + userWallet.address + ); + + expect( + ethBalanceAfter, + "User's ETH balance should have increased by the issuance gain from staking" + ).to.eq(ethBalanceBefore.add(ethGain)); + expect( + lusdBalanceAfter, + "User's LUSD balance should have increased by the redemption gain from staking" + ).to.eq(lusdBalanceBefore.add(lusdGain)); + }); + + it("returns Instadapp event name and data", async () => { + const stakerDsa = await buildDSAv2(userWallet.address); + const amount = ethers.utils.parseUnits("1000", 18); // 1000 LQTY + + // Stake lots of LQTY + await helpers.sendToken( + liquity.lqtyToken, + amount, + helpers.JUSTIN_SUN_ADDRESS, + stakerDsa.address + ); + const stakeSpell = { + connector: helpers.LIQUITY_CONNECTOR, + method: "stake", + args: [amount, 0, 0, 0], + }; + await stakerDsa + .connect(userWallet) + .cast(...encodeSpells([stakeSpell]), userWallet.address); + + // Open a Trove to cause an ETH issuance gain for stakers + await helpers.createDsaTrove(dsa, userWallet, liquity); + + // Redeem some ETH to cause an LUSD redemption gain for stakers + await helpers.redeem( + ethers.utils.parseUnits("1000", 18), + contracts.STABILITY_POOL_ADDRESS, + userWallet, liquity ); @@ -1958,15 +2571,18 @@ describe.only("Liquity", () => { ); const claimStakingGainsSpell = { - connector: helpers.CONNECTOR_NAME, + connector: helpers.LIQUITY_CONNECTOR, method: "claimStakingGains", args: [setEthGainId, setLusdGainId], }; // Claim gains const claimGainsTx = await stakerDsa - .connect(wallet) - .cast(...encodeSpells([claimStakingGainsSpell]), wallet.address); + .connect(userWallet) + .cast( + ...encodeSpells([claimStakingGainsSpell]), + userWallet.address + ); const receipt = await claimGainsTx.wait(); @@ -1974,7 +2590,7 @@ describe.only("Liquity", () => { .args; const expectedEventParams = ethers.utils.defaultAbiCoder.encode( ["address", "uint256", "uint256", "uint256", "uint256"], - [wallet.address, ethGain, lusdGain, setEthGainId, setLusdGainId] + [userWallet.address, ethGain, lusdGain, setEthGainId, setLusdGainId] ); expect(castLogEvent.eventNames[0]).eq( "LogClaimStakingGains(address,uint256,uint256,uint256,uint256)" From 4ea58ba50d58dd6aad8c2f5d4cf7bb11171f8ce4 Mon Sep 17 00:00:00 2001 From: Edward Mulraney Date: Wed, 9 Jun 2021 21:02:06 +0100 Subject: [PATCH 06/19] log the eth and lqty gains from stability pool operations. general tidy up --- .../mainnet/connectors/liquity/events.sol | 4 + .../mainnet/connectors/liquity/interface.sol | 4 + contracts/mainnet/connectors/liquity/main.sol | 61 +++++--- test/liquity/liquity.contracts.js | 1 + test/liquity/liquity.helpers.js | 29 ---- test/liquity/liquity.test.js | 146 ++++++++++++++---- 6 files changed, 163 insertions(+), 82 deletions(-) diff --git a/contracts/mainnet/connectors/liquity/events.sol b/contracts/mainnet/connectors/liquity/events.sol index e16c771b..2457b8d3 100644 --- a/contracts/mainnet/connectors/liquity/events.sol +++ b/contracts/mainnet/connectors/liquity/events.sol @@ -34,6 +34,8 @@ contract Events { event LogStabilityDeposit( address indexed borrower, uint amount, + uint ethGain, + uint lqtyGain, address frontendTag, uint getDepositId, uint setEthGainId, @@ -41,6 +43,8 @@ contract Events { ); event LogStabilityWithdraw(address indexed borrower, uint amount, + uint ethGain, + uint lqtyGain, uint setWithdrawId, uint setEthGainId, uint setLqtyGainId diff --git a/contracts/mainnet/connectors/liquity/interface.sol b/contracts/mainnet/connectors/liquity/interface.sol index 7ad22f18..b7318356 100644 --- a/contracts/mainnet/connectors/liquity/interface.sol +++ b/contracts/mainnet/connectors/liquity/interface.sol @@ -66,3 +66,7 @@ interface StakingLike { interface CollateralSurplusLike { function getCollateral(address _account) external view returns (uint); } + +interface LqtyTokenLike { + function balanceOf(address account) external view returns (uint256); +} diff --git a/contracts/mainnet/connectors/liquity/main.sol b/contracts/mainnet/connectors/liquity/main.sol index caea1f89..4e42c216 100644 --- a/contracts/mainnet/connectors/liquity/main.sol +++ b/contracts/mainnet/connectors/liquity/main.sol @@ -9,7 +9,8 @@ import { TroveManagerLike, StabilityPoolLike, StakingLike, - CollateralSurplusLike + CollateralSurplusLike, + LqtyTokenLike } from "./interface.sol"; import { Stores } from "../../common/stores.sol"; import { Helpers } from "./helpers.sol"; @@ -26,6 +27,8 @@ abstract contract LiquityResolver is Events, Helpers { StakingLike(0x4f9Fbb3f1E99B56e0Fe2892e623Ed36A76Fc605d); CollateralSurplusLike internal constant collateralSurplus = CollateralSurplusLike(0x3D32e8b97Ed5881324241Cf03b2DA5E2EBcE5521); + LqtyTokenLike internal constant lqtyToken = + LqtyTokenLike(0x6DEA81C8171D0bA574754EF6F8b412F2Ed88c54D); // Prevents stack-too-deep error struct AdjustTrove { @@ -45,8 +48,8 @@ abstract contract LiquityResolver is Events, Helpers { * @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 will now sit in the ordered Trove list - * @param lowerHint Address of the Trove near the lower bound of where the user's Trove will now sit in the ordered Trove list + * @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 */ @@ -97,8 +100,8 @@ abstract contract LiquityResolver is Events, Helpers { * @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 will now sit in the ordered Trove list - * @param lowerHint Address of the Trove near the lower bound of where the user's Trove will now sit in the ordered Trove list + * @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( @@ -120,8 +123,8 @@ abstract contract LiquityResolver is Events, Helpers { * @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 will now sit in the ordered Trove list - * @param lowerHint Address of the Trove near the lower bound of where the user's Trove will now sit in the ordered Trove list + * @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( @@ -142,8 +145,8 @@ abstract contract LiquityResolver is Events, Helpers { * @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 will now sit in the ordered Trove list - * @param lowerHint Address of the Trove near the lower bound of where the user's Trove will now sit in the ordered Trove list + * @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( @@ -155,7 +158,7 @@ abstract contract LiquityResolver is Events, Helpers { ) external payable returns (string memory _eventName, bytes memory _eventParam) { borrowerOperations.withdrawLUSD(maxFeePercentage, amount, upperHint, lowerHint); - setUint(setId, amount); // TODO: apply fee / get exact amount borrowed (with the fee applied) + setUint(setId, amount); _eventName = "LogBorrow(address,uint256,uint256)"; _eventParam = abi.encode(msg.sender, amount, setId); } @@ -164,8 +167,8 @@ abstract contract LiquityResolver is Events, Helpers { * @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 will now sit in the ordered Trove list - * @param lowerHint Address of the Trove near the lower bound of where the user's Trove will now sit in the ordered Trove list + * @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( @@ -191,8 +194,8 @@ abstract contract LiquityResolver is Events, Helpers { * @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 will now sit in the ordered Trove list - * @param lowerHint Address of the Trove near the lower bound of where the user's Trove will now sit in the ordered Trove list + * @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 @@ -269,7 +272,7 @@ abstract contract LiquityResolver is Events, Helpers { * @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 ETH gains in + * @param setLqtyGainId Optional storage slot to store any LQTY gains in */ function stabilityDeposit( uint amount, @@ -281,14 +284,18 @@ abstract contract LiquityResolver is Events, Helpers { amount = getUint(getDepositId, amount); uint ethGain = stabilityPool.getDepositorETHGain(address(this)); - uint lqtyGain = stabilityPool.getDepositorLQTYGain(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,address,uint256,uint256,uint256)"; - _eventParam = abi.encode(msg.sender, amount, frontendTag, getDepositId, setEthGainId, setLqtyGainId); + _eventName = "LogStabilityDeposit(address,uint256,uint256,uint256,address,uint256,uint256,uint256)"; + _eventParam = abi.encode(msg.sender, amount, ethGain, lqtyGain, frontendTag, getDepositId, setEthGainId, setLqtyGainId); } /** @@ -297,7 +304,7 @@ abstract contract LiquityResolver is Events, Helpers { * @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 ETH gains in + * @param setLqtyGainId Optional storage slot to store any LQTY gains in */ function stabilityWithdraw( uint amount, @@ -305,23 +312,27 @@ abstract contract LiquityResolver is Events, Helpers { uint setEthGainId, uint setLqtyGainId ) external returns (string memory _eventName, bytes memory _eventParam) { - stabilityPool.withdrawFromSP(amount); uint ethGain = stabilityPool.getDepositorETHGain(address(this)); - uint lqtyGain = stabilityPool.getDepositorLQTYGain(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)"; - _eventParam = abi.encode(msg.sender, amount, setWithdrawId, setEthGainId, setLqtyGainId); + _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 will now sit in the ordered Trove list - * @param lowerHint Address of the Trove near the lower bound of where the user's Trove will now sit in the ordered Trove list + * @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, diff --git a/test/liquity/liquity.contracts.js b/test/liquity/liquity.contracts.js index 43863525..f6a6df74 100644 --- a/test/liquity/liquity.contracts.js +++ b/test/liquity/liquity.contracts.js @@ -46,6 +46,7 @@ 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"; diff --git a/test/liquity/liquity.helpers.js b/test/liquity/liquity.helpers.js index cc411312..009a114a 100644 --- a/test/liquity/liquity.helpers.js +++ b/test/liquity/liquity.helpers.js @@ -121,7 +121,6 @@ const resetHardhatBlockNumber = async (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, @@ -163,93 +162,65 @@ const deployAndConnect = async (contracts, isDebug = false) => { contracts.TROVE_MANAGER_ABI, ethers.provider ); - isDebug && - console.log("TroveManager contract address", liquity.troveManager.address); liquity.borrowerOperations = new ethers.Contract( contracts.BORROWER_OPERATIONS_ADDRESS, contracts.BORROWER_OPERATIONS_ABI, ethers.provider ); - isDebug && - console.log( - "BorrowerOperations contract address", - liquity.borrowerOperations.address - ); liquity.stabilityPool = new ethers.Contract( contracts.STABILITY_POOL_ADDRESS, contracts.STABILITY_POOL_ABI, ethers.provider ); - isDebug && - console.log( - "StabilityPool contract address", - liquity.stabilityPool.address - ); liquity.lusdToken = new ethers.Contract( contracts.LUSD_TOKEN_ADDRESS, contracts.LUSD_TOKEN_ABI, ethers.provider ); - isDebug && - console.log("LusdToken contract address", liquity.lusdToken.address); liquity.lqtyToken = new ethers.Contract( contracts.LQTY_TOKEN_ADDRESS, contracts.LQTY_TOKEN_ABI, ethers.provider ); - isDebug && - console.log("LqtyToken contract address", liquity.lqtyToken.address); liquity.activePool = new ethers.Contract( contracts.ACTIVE_POOL_ADDRESS, contracts.ACTIVE_POOL_ABI, ethers.provider ); - isDebug && - console.log("ActivePool contract address", liquity.activePool.address); liquity.priceFeed = new ethers.Contract( contracts.PRICE_FEED_ADDRESS, contracts.PRICE_FEED_ABI, ethers.provider ); - isDebug && - console.log("PriceFeed contract address", liquity.priceFeed.address); liquity.hintHelpers = new ethers.Contract( contracts.HINT_HELPERS_ADDRESS, contracts.HINT_HELPERS_ABI, ethers.provider ); - isDebug && - console.log("HintHelpers contract address", liquity.hintHelpers.address); liquity.sortedTroves = new ethers.Contract( contracts.SORTED_TROVES_ADDRESS, contracts.SORTED_TROVES_ABI, ethers.provider ); - isDebug && - console.log("SortedTroves contract address", liquity.sortedTroves.address); liquity.staking = new ethers.Contract( contracts.STAKING_ADDRESS, contracts.STAKING_ABI, ethers.provider ); - isDebug && console.log("Staking contract address", liquity.staking.address); - liquity.collSurplus = new ethers.Contract( contracts.COLL_SURPLUS_ADDRESS, contracts.COLL_SURPLUS_ABI, ethers.provider ); - isDebug && - console.log("CollSurplus contract address", liquity.collSurplus.address); return liquity; }; diff --git a/test/liquity/liquity.test.js b/test/liquity/liquity.test.js index 7eeafe53..52cd373c 100644 --- a/test/liquity/liquity.test.js +++ b/test/liquity/liquity.test.js @@ -11,7 +11,7 @@ const contracts = require("./liquity.contracts"); // Liquity helpers const helpers = require("./liquity.helpers"); -describe.only("Liquity", () => { +describe("Liquity", () => { const { waffle, ethers } = hre; const { provider } = waffle; @@ -1732,6 +1732,7 @@ describe.only("Liquity", () => { it("returns Instadapp event name and data", async () => { const amount = ethers.utils.parseUnits("100", 18); + const halfAmount = amount.div(2); const frontendTag = ethers.constants.AddressZero; const getDepositId = 0; const setEthGainId = 0; @@ -1747,21 +1748,67 @@ describe.only("Liquity", () => { const stabilityDepositSpell = { connector: helpers.LIQUITY_CONNECTOR, method: "stabilityDeposit", - args: [amount, frontendTag, getDepositId, 0, 0], + args: [halfAmount, frontendTag, getDepositId, 0, 0], }; - const depositTx = await dsa + // Create a Stability deposit for this DSA + await dsa .connect(userWallet) .cast(...encodeSpells([stabilityDepositSpell]), userWallet.address); - const receipt = await depositTx.wait(); + // Liquidate a Trove to cause an ETH gain + await liquity.troveManager.connect(userWallet).liquidateTroves(1, { + gasLimit: helpers.MAX_GAS, + }); + + // Fast forward in time so we have an LQTY gain + await provider.send("evm_increaseTime", [600]); + await provider.send("evm_mine"); + + // Create a Stability Pool deposit with a differen DSA so that LQTY gains can be calculated + // See: https://github.com/liquity/dev/#lqty-reward-events-and-payouts + const tempDsa = await buildDSAv2(userWallet.address); + await helpers.sendToken( + liquity.lusdToken, + amount, + contracts.STABILITY_POOL_ADDRESS, + tempDsa.address + ); + await tempDsa + .connect(userWallet) + .cast(...encodeSpells([stabilityDepositSpell]), userWallet.address); + + const ethGain = await liquity.stabilityPool.getDepositorETHGain( + dsa.address + ); + const lqtyGain = await liquity.stabilityPool.getDepositorLQTYGain( + dsa.address + ); + + // Top up the user's deposit so that we can track their ETH and LQTY gain + const depositAgainTx = await dsa + .connect(userWallet) + .cast(...encodeSpells([stabilityDepositSpell]), userWallet.address); + + const receipt = await depositAgainTx.wait(); const castLogEvent = receipt.events.find((e) => e.event === "LogCast") .args; const expectedEventParams = ethers.utils.defaultAbiCoder.encode( - ["address", "uint256", "address", "uint256", "uint256", "uint256"], + [ + "address", + "uint256", + "uint256", + "uint256", + "address", + "uint256", + "uint256", + "uint256", + ], [ userWallet.address, - amount, + halfAmount, + ethGain, + lqtyGain, frontendTag, getDepositId, setEthGainId, @@ -1769,7 +1816,7 @@ describe.only("Liquity", () => { ] ); expect(castLogEvent.eventNames[0]).eq( - "LogStabilityDeposit(address,uint256,address,uint256,uint256,uint256)" + "LogStabilityDeposit(address,uint256,uint256,uint256,address,uint256,uint256,uint256)" ); expect(castLogEvent.eventParams[0]).eq(expectedEventParams); }); @@ -1777,7 +1824,7 @@ describe.only("Liquity", () => { describe("stabilityWithdraw()", () => { it("withdraws from Stability Pool", async () => { - // Start this test from scratch since we don't want to rely on test order for this to pass. + // Start this test from scratch since we need to remove any liquidatable Troves withdrawing from Stability Pool [liquity, dsa] = await helpers.resetInitialState( userWallet.address, contracts @@ -1788,6 +1835,7 @@ describe.only("Liquity", () => { await liquity.troveManager.connect(userWallet).liquidateTroves(90, { gasLimit: helpers.MAX_GAS, }); + const amount = ethers.utils.parseUnits("100", 18); const frontendTag = ethers.constants.AddressZero; @@ -1805,12 +1853,12 @@ describe.only("Liquity", () => { }; // Withdraw half of the deposit - const stabilitWithdrawSpell = { + const stabilityWithdrawSpell = { connector: helpers.LIQUITY_CONNECTOR, method: "stabilityWithdraw", args: [amount.div(2), 0, 0, 0], }; - const spells = [stabilityDepositSpell, stabilitWithdrawSpell]; + const spells = [stabilityDepositSpell, stabilityWithdrawSpell]; await dsa .connect(userWallet) @@ -1826,7 +1874,7 @@ describe.only("Liquity", () => { }); it("withdraws from Stability Pool and stores the LUSD for other spells", async () => { - // Start this test from scratch since we don't want to rely on test order for this to pass. + // Start this test from scratch since we need to remove any liquidatable Troves withdrawing from Stability Pool [liquity, dsa] = await helpers.resetInitialState( userWallet.address, contracts @@ -1855,7 +1903,7 @@ describe.only("Liquity", () => { }; // Withdraw half of the deposit - const stabilitWithdrawSpell = { + const stabilityWithdrawSpell = { connector: helpers.LIQUITY_CONNECTOR, method: "stabilityWithdraw", args: [amount.div(2), 0, 0, withdrawId], @@ -1875,7 +1923,7 @@ describe.only("Liquity", () => { const spells = [ stabilityDepositSpell, - stabilitWithdrawSpell, + stabilityWithdrawSpell, withdrawLusdSpell, ]; @@ -1895,17 +1943,12 @@ describe.only("Liquity", () => { }); it("returns Instadapp event name and data", async () => { - // Start this test from scratch since we don't want to rely on test order for this to pass. + // Start this test from scratch since we need to remove any liquidatable Troves withdrawing from Stability Pool [liquity, dsa] = await helpers.resetInitialState( userWallet.address, contracts ); - // The current block number has liquidatable Troves. - // Remove them otherwise Stability Pool withdrawals are disabled - await liquity.troveManager.connect(userWallet).liquidateTroves(90, { - gasLimit: helpers.MAX_GAS, - }); const amount = ethers.utils.parseUnits("100", 18); const frontendTag = ethers.constants.AddressZero; @@ -1928,34 +1971,81 @@ describe.only("Liquity", () => { const setEthGainId = 0; const setLqtyGainId = 0; - const stabilitWithdrawSpell = { + // Create a Stability Pool deposit + await dsa + .connect(userWallet) + .cast(...encodeSpells([stabilityDepositSpell]), userWallet.address); + + // The current block number has liquidatable Troves. + // Remove them otherwise Stability Pool withdrawals are disabled + await liquity.troveManager.connect(userWallet).liquidateTroves(90, { + gasLimit: helpers.MAX_GAS, + }); + + // Fast forward in time so we have an LQTY gain + await provider.send("evm_increaseTime", [600]); + await provider.send("evm_mine"); + + // Create another Stability Pool deposit so that LQTY gains are realized + // See: https://github.com/liquity/dev/#lqty-reward-events-and-payouts + const tempDsa = await buildDSAv2(userWallet.address); + await helpers.sendToken( + liquity.lusdToken, + amount, + contracts.STABILITY_POOL_ADDRESS, + tempDsa.address + ); + await tempDsa + .connect(userWallet) + .cast(...encodeSpells([stabilityDepositSpell]), userWallet.address); + + const ethGain = await liquity.stabilityPool.getDepositorETHGain( + dsa.address + ); + const lqtyGain = await liquity.stabilityPool.getDepositorLQTYGain( + dsa.address + ); + + const stabilityWithdrawSpell = { connector: helpers.LIQUITY_CONNECTOR, method: "stabilityWithdraw", args: [withdrawAmount, setWithdrawId, setEthGainId, setLqtyGainId], }; - const spells = [stabilityDepositSpell, stabilitWithdrawSpell]; - const castTx = await dsa + const withdrawTx = await dsa .connect(userWallet) - .cast(...encodeSpells(spells), userWallet.address); + .cast( + ...encodeSpells([stabilityWithdrawSpell]), + userWallet.address + ); - const receipt = await castTx.wait(); + const receipt = await withdrawTx.wait(); const castLogEvent = receipt.events.find((e) => e.event === "LogCast") .args; const expectedEventParams = ethers.utils.defaultAbiCoder.encode( - ["address", "uint256", "uint256", "uint256", "uint256"], + [ + "address", + "uint256", + "uint256", + "uint256", + "uint256", + "uint256", + "uint256", + ], [ userWallet.address, withdrawAmount, + ethGain, + lqtyGain, setWithdrawId, setEthGainId, setLqtyGainId, ] ); - expect(castLogEvent.eventNames[1]).eq( - "LogStabilityWithdraw(address,uint256,uint256,uint256,uint256)" + expect(castLogEvent.eventNames[0]).eq( + "LogStabilityWithdraw(address,uint256,uint256,uint256,uint256,uint256,uint256)" ); - expect(castLogEvent.eventParams[1]).eq(expectedEventParams); + expect(castLogEvent.eventParams[0]).eq(expectedEventParams); }); }); From 0a2735cad259e271544bbdd2932df9a876f17107 Mon Sep 17 00:00:00 2001 From: Samyak Jain <34437877+KaymasJain@users.noreply.github.com> Date: Sat, 26 Jun 2021 01:40:45 +0530 Subject: [PATCH 07/19] restructuring & updated function till repay --- .../mainnet/connectors/liquity/events.sol | 12 +- .../mainnet/connectors/liquity/helpers.sol | 33 +++- contracts/mainnet/connectors/liquity/main.sol | 146 +++++++++--------- 3 files changed, 113 insertions(+), 78 deletions(-) diff --git a/contracts/mainnet/connectors/liquity/events.sol b/contracts/mainnet/connectors/liquity/events.sol index 2457b8d3..8dc24320 100644 --- a/contracts/mainnet/connectors/liquity/events.sol +++ b/contracts/mainnet/connectors/liquity/events.sol @@ -8,14 +8,14 @@ contract Events { uint maxFeePercentage, uint depositAmount, uint borrowAmount, - uint getId, - uint setId + uint[] getIds, + uint[] setIds ); 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 LogDeposit(address indexed borrower, uint amount, uint getId, uint setId); + event LogWithdraw(address indexed borrower, uint amount, uint getId, uint setId); + event LogBorrow(address indexed borrower, uint amount, uint getId, uint setId); + event LogRepay(address indexed borrower, uint amount, uint getId, uint setId); event LogAdjust( address indexed borrower, uint maxFeePercentage, diff --git a/contracts/mainnet/connectors/liquity/helpers.sol b/contracts/mainnet/connectors/liquity/helpers.sol index 16048baf..0fc9ef1c 100644 --- a/contracts/mainnet/connectors/liquity/helpers.sol +++ b/contracts/mainnet/connectors/liquity/helpers.sol @@ -3,4 +3,35 @@ pragma solidity ^0.7.6; import { DSMath } from "../../common/math.sol"; import { Basic } from "../../common/basic.sol"; -abstract contract Helpers is DSMath, Basic {} +import { TokenInterface } from "../../common/interfaces.sol"; + +import { + BorrowerOperationsLike, + TroveManagerLike, + StabilityPoolLike, + StakingLike, + CollateralSurplusLike, + LqtyTokenLike +} from "./interface.sol"; + +abstract contract Helpers is DSMath, Basic { + + 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); + TokenInterface internal constant lusdToken = TokenInterface(0x5f98805A4E8be255a32880FDeC7F6728C6568bA0); + + // Prevents stack-too-deep error + struct AdjustTrove { + uint maxFeePercentage; + uint withdrawAmount; + uint depositAmount; + uint borrowAmount; + uint repayAmount; + bool isBorrow; + } + +} diff --git a/contracts/mainnet/connectors/liquity/main.sol b/contracts/mainnet/connectors/liquity/main.sol index 4e42c216..643994fa 100644 --- a/contracts/mainnet/connectors/liquity/main.sol +++ b/contracts/mainnet/connectors/liquity/main.sol @@ -17,28 +17,7 @@ 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 */ @@ -50,8 +29,8 @@ abstract contract LiquityResolver is Events, Helpers { * @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 + * @param getIds Optional (default: 0) Optional storage slot to get deposit & borrow amounts stored using other spells + * @param setIds Optional (default: 0) Optional storage slot to set deposit & borrow amounts to be used in future spells */ function open( uint depositAmount, @@ -59,26 +38,27 @@ abstract contract LiquityResolver is Events, Helpers { uint borrowAmount, address upperHint, address lowerHint, - uint getId, - uint setId + uint[] getIds, + uint[] setIds ) 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); + uint _depositAmount = getUint(getIds[0], depositAmount); + uint _borrowAmount = getUint(getIds[1], borrowAmount); - borrowerOperations.openTrove{value: depositAmount}( + _depositAmount = _depositAmount == uint(-1) ? address(this).balance : _depositAmount; + + borrowerOperations.openTrove{value: _depositAmount}( maxFeePercentage, - borrowAmount, + _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); + setUint(setIds[0], _depositAmount); + setUint(setIds[1], _borrowAmount); + + _eventName = "LogOpen(address,uint256,uint256,uint256,uint256[],uint256[])"; + _eventParam = abi.encode(address(this), maxFeePercentage, _depositAmount, _borrowAmount, getIds, setIds); } /** @@ -86,14 +66,14 @@ abstract contract LiquityResolver is Events, Helpers { * @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) { + function close(uint setId) external payable 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); + _eventParam = abi.encode(address(this), setId); } /** @@ -103,20 +83,26 @@ abstract contract LiquityResolver is Events, Helpers { * @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 + * @param setId Optional storage slot to set the ETH deposited */ function deposit( uint amount, address upperHint, address lowerHint, - uint getId + uint getId, + uint setId ) 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); + + uint _amount = getUint(getId, amount); + + _amount = _amount == uint(-1) ? address(this).balance : _amount; + + borrowerOperations.addColl{value: _amount}(upperHint, lowerHint); + + setUint(setId, _amount); + + _eventName = "LogDeposit(address,uint256,uint256,uint256)"; + _eventParam = abi.encode(address(this), _amount, getId, setId); } /** @@ -125,19 +111,25 @@ abstract contract LiquityResolver is Events, Helpers { * @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 getId Optional storage slot to get the amount of ETH to withdraw * @param setId Optional storage slot to store the withdrawn ETH in */ function withdraw( uint amount, address upperHint, address lowerHint, + uint getId, uint setId ) external payable returns (string memory _eventName, bytes memory _eventParam) { - borrowerOperations.withdrawColl(amount, upperHint, lowerHint); + uint _amount = getUint(getId, amount); - setUint(setId, amount); - _eventName = "LogWithdraw(address,uint256,uint256)"; - _eventParam = abi.encode(msg.sender, amount, setId); + _amount = _amount == uint(-1) ? troveManager.getTroveColl(address(this)) : _amount; + + borrowerOperations.withdrawColl(_amount, upperHint, lowerHint); + + setUint(setId, _amount); + _eventName = "LogWithdraw(address,uint256,uint256,uint256)"; + _eventParam = abi.encode(address(this), _amount, getId, setId); } /** @@ -154,13 +146,17 @@ abstract contract LiquityResolver is Events, Helpers { uint amount, address upperHint, address lowerHint, + uint getId, uint setId ) external payable returns (string memory _eventName, bytes memory _eventParam) { - borrowerOperations.withdrawLUSD(maxFeePercentage, amount, upperHint, lowerHint); + uint _amount = getUint(getId, amount); - setUint(setId, amount); - _eventName = "LogBorrow(address,uint256,uint256)"; - _eventParam = abi.encode(msg.sender, amount, setId); + borrowerOperations.withdrawLUSD(maxFeePercentage, _amount, upperHint, lowerHint); + + setUint(setId, _amount); + + _eventName = "LogBorrow(address,uint256,uint256,uint256)"; + _eventParam = abi.encode(address(this), _amount, getId, setId); } /** @@ -175,15 +171,23 @@ abstract contract LiquityResolver is Events, Helpers { uint amount, address upperHint, address lowerHint, - uint getId + uint getId, + uint setId ) 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"); + uint _amount = getUint(getId, amount); + + if (_amount == uint(-1)) { + uint _lusdBal = lusdToken.balanceOf(address(this)); + uint _totalDebt = troveManager.getTroveDebt(address(this)); + _amount = _lusdBal > _totalDebt ? _totalDebt : _lusdBal; } - amount = getUint(getId, amount); - borrowerOperations.repayLUSD(amount, upperHint, lowerHint); - _eventName = "LogRepay(address,uint256,uint256)"; - _eventParam = abi.encode(msg.sender, amount, getId); + + borrowerOperations.repayLUSD(_amount, upperHint, lowerHint); + + setUint(setId, _amount); + + _eventName = "LogRepay(address,uint256,uint256,uint256)"; + _eventParam = abi.encode(address(this), _amount, getId, setId); } /** @@ -245,7 +249,7 @@ abstract contract LiquityResolver is Events, Helpers { 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); + _eventParam = abi.encode(address(this), maxFeePercentage, depositAmount, withdrawAmount, borrowAmount, repayAmount, getDepositId, setWithdrawId, getRepayId, setBorrowId); } /** @@ -253,13 +257,13 @@ abstract contract LiquityResolver is Events, Helpers { * @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) { + function claimCollateralFromRedemption(uint setId) external payable 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); + _eventParam = abi.encode(address(this), amount, setId); } /* End: Trove */ @@ -280,7 +284,7 @@ abstract contract LiquityResolver is Events, Helpers { uint getDepositId, uint setEthGainId, uint setLqtyGainId - ) external returns (string memory _eventName, bytes memory _eventParam) { + ) external payable returns (string memory _eventName, bytes memory _eventParam) { amount = getUint(getDepositId, amount); uint ethGain = stabilityPool.getDepositorETHGain(address(this)); @@ -295,7 +299,7 @@ abstract contract LiquityResolver is Events, Helpers { 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); + _eventParam = abi.encode(address(this), amount, ethGain, lqtyGain, frontendTag, getDepositId, setEthGainId, setLqtyGainId); } /** @@ -325,7 +329,7 @@ abstract contract LiquityResolver is Events, Helpers { setUint(setLqtyGainId, lqtyGain); _eventName = "LogStabilityWithdraw(address,uint256,uint256,uint256,uint256,uint256,uint256)"; - _eventParam = abi.encode(msg.sender, amount, ethGain, lqtyGain, setWithdrawId, setEthGainId, setLqtyGainId); + _eventParam = abi.encode(address(this), amount, ethGain, lqtyGain, setWithdrawId, setEthGainId, setLqtyGainId); } /** @@ -341,7 +345,7 @@ abstract contract LiquityResolver is Events, Helpers { uint amount = stabilityPool.getDepositorETHGain(address(this)); stabilityPool.withdrawETHGainToTrove(upperHint, lowerHint); _eventName = "LogStabilityMoveEthGainToTrove(address,uint256)"; - _eventParam = abi.encode(msg.sender, amount); + _eventParam = abi.encode(address(this), amount); } /* End: Stability Pool */ @@ -370,7 +374,7 @@ abstract contract LiquityResolver is Events, Helpers { setUint(setLusdGainId, lusdGain); _eventName = "LogStake(address,uint256,uint256,uint256,uint256)"; - _eventParam = abi.encode(msg.sender, amount, getStakeId, setEthGainId, setLusdGainId); + _eventParam = abi.encode(address(this), amount, getStakeId, setEthGainId, setLusdGainId); } /** @@ -396,7 +400,7 @@ abstract contract LiquityResolver is Events, Helpers { setUint(setLusdGainId, lusdGain); _eventName = "LogUnstake(address,uint256,uint256,uint256,uint256)"; - _eventParam = abi.encode(msg.sender, amount, setStakeId, setEthGainId, setLusdGainId); + _eventParam = abi.encode(address(this), amount, setStakeId, setEthGainId, setLusdGainId); } /** @@ -418,7 +422,7 @@ abstract contract LiquityResolver is Events, Helpers { setUint(setLusdGainId, lusdGain); _eventName = "LogClaimStakingGains(address,uint256,uint256,uint256,uint256)"; - _eventParam = abi.encode(msg.sender, ethGain, lusdGain, setEthGainId, setLusdGainId); + _eventParam = abi.encode(address(this), ethGain, lusdGain, setEthGainId, setLusdGainId); } /* End: Staking */ From 6211c526d8912a14c79ba8806063acfcbfc6fad9 Mon Sep 17 00:00:00 2001 From: Samyak Jain <34437877+KaymasJain@users.noreply.github.com> Date: Sat, 26 Jun 2021 18:30:03 +0530 Subject: [PATCH 08/19] updated all the functions other adjust --- .../mainnet/connectors/liquity/events.sol | 6 ++- .../mainnet/connectors/liquity/interface.sol | 2 + contracts/mainnet/connectors/liquity/main.sol | 39 +++++++++++++------ 3 files changed, 34 insertions(+), 13 deletions(-) diff --git a/contracts/mainnet/connectors/liquity/events.sol b/contracts/mainnet/connectors/liquity/events.sol index 8dc24320..47105414 100644 --- a/contracts/mainnet/connectors/liquity/events.sol +++ b/contracts/mainnet/connectors/liquity/events.sol @@ -38,6 +38,7 @@ contract Events { uint lqtyGain, address frontendTag, uint getDepositId, + uint setDepositId, uint setEthGainId, uint setLqtyGainId ); @@ -45,6 +46,7 @@ contract Events { uint amount, uint ethGain, uint lqtyGain, + uint getWithdrawId, uint setWithdrawId, uint setEthGainId, uint setLqtyGainId @@ -52,7 +54,7 @@ contract Events { 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 LogStake(address indexed borrower, uint amount, uint setStakeId, uint getStakeId, uint setEthGainId, uint setLusdGainId); + event LogUnstake(address indexed borrower, uint amount, uint getUnstakeId, uint setUnstakeId, uint setEthGainId, uint setLusdGainId); event LogClaimStakingGains(address indexed borrower, uint ethGain, uint lusdGain, uint setEthGainId, uint setLusdGainId); } diff --git a/contracts/mainnet/connectors/liquity/interface.sol b/contracts/mainnet/connectors/liquity/interface.sol index b7318356..8ffd65d1 100644 --- a/contracts/mainnet/connectors/liquity/interface.sol +++ b/contracts/mainnet/connectors/liquity/interface.sol @@ -54,6 +54,7 @@ interface StabilityPoolLike { function withdrawETHGainToTrove(address _upperHint, address _lowerHint) external; function getDepositorETHGain(address _depositor) external view returns (uint); function getDepositorLQTYGain(address _depositor) external view returns (uint); + function getCompoundedLUSDDeposit(address _depositor) external view returns (uint); } interface StakingLike { @@ -61,6 +62,7 @@ interface StakingLike { function unstake(uint _LQTYamount) external; function getPendingETHGain(address _user) external view returns (uint); function getPendingLUSDGain(address _user) external view returns (uint); + function stakes(address owner) external view returns (uint); } interface CollateralSurplusLike { diff --git a/contracts/mainnet/connectors/liquity/main.sol b/contracts/mainnet/connectors/liquity/main.sol index 643994fa..efe05d73 100644 --- a/contracts/mainnet/connectors/liquity/main.sol +++ b/contracts/mainnet/connectors/liquity/main.sol @@ -282,11 +282,14 @@ abstract contract LiquityResolver is Events, Helpers { uint amount, address frontendTag, uint getDepositId, + uint setDepositId, uint setEthGainId, uint setLqtyGainId ) external payable returns (string memory _eventName, bytes memory _eventParam) { amount = getUint(getDepositId, amount); + amount = amount == uint(-1) ? lusdToken.balanceOf(address(this)) : amount; + uint ethGain = stabilityPool.getDepositorETHGain(address(this)); uint lqtyBalanceBefore = lqtyToken.balanceOf(address(this)); @@ -295,11 +298,12 @@ abstract contract LiquityResolver is Events, Helpers { uint lqtyBalanceAfter = lqtyToken.balanceOf(address(this)); uint lqtyGain = sub(lqtyBalanceAfter, lqtyBalanceBefore); + setUint(setDepositId, amount); setUint(setEthGainId, ethGain); setUint(setLqtyGainId, lqtyGain); - _eventName = "LogStabilityDeposit(address,uint256,uint256,uint256,address,uint256,uint256,uint256)"; - _eventParam = abi.encode(address(this), amount, ethGain, lqtyGain, frontendTag, getDepositId, setEthGainId, setLqtyGainId); + _eventName = "LogStabilityDeposit(address,uint256,uint256,uint256,address,uint256,uint256,uint256,uint256)"; + _eventParam = abi.encode(address(this), amount, ethGain, lqtyGain, frontendTag, getDepositId, setDepositId, setEthGainId, setLqtyGainId); } /** @@ -312,10 +316,15 @@ abstract contract LiquityResolver is Events, Helpers { */ function stabilityWithdraw( uint amount, + uint getWithdrawId, uint setWithdrawId, uint setEthGainId, uint setLqtyGainId ) external returns (string memory _eventName, bytes memory _eventParam) { + amount = getUint(getWithdrawId, amount); + + amount = amount == uint(-1) ? StabilityPoolLike.getCompoundedLUSDDeposit(address(this)) : amount; + uint ethGain = stabilityPool.getDepositorETHGain(address(this)); uint lqtyBalanceBefore = lqtyToken.balanceOf(address(this)); @@ -328,8 +337,8 @@ abstract contract LiquityResolver is Events, Helpers { setUint(setEthGainId, ethGain); setUint(setLqtyGainId, lqtyGain); - _eventName = "LogStabilityWithdraw(address,uint256,uint256,uint256,uint256,uint256,uint256)"; - _eventParam = abi.encode(address(this), amount, ethGain, lqtyGain, setWithdrawId, setEthGainId, setLqtyGainId); + _eventName = "LogStabilityWithdraw(address,uint256,uint256,uint256,uint256,uint256,uint256,uint256)"; + _eventParam = abi.encode(address(this), amount, ethGain, lqtyGain, getWithdrawId, setWithdrawId, setEthGainId, setLqtyGainId); } /** @@ -362,19 +371,23 @@ abstract contract LiquityResolver is Events, Helpers { function stake( uint amount, uint getStakeId, + uint setStakeId, uint setEthGainId, uint setLusdGainId ) external returns (string memory _eventName, bytes memory _eventParam) { + amount = getUint(getStakeId, amount); + amount = amount == uint(-1) ? lqtyToken.balanceOf(address(this)) : amount; + uint ethGain = staking.getPendingETHGain(address(this)); uint lusdGain = staking.getPendingLUSDGain(address(this)); - amount = getUint(getStakeId, amount); staking.stake(amount); + setUint(setStakeId, amount); setUint(setEthGainId, ethGain); setUint(setLusdGainId, lusdGain); - _eventName = "LogStake(address,uint256,uint256,uint256,uint256)"; - _eventParam = abi.encode(address(this), amount, getStakeId, setEthGainId, setLusdGainId); + _eventName = "LogStake(address,uint256,uint256,uint256,uint256,uint256)"; + _eventParam = abi.encode(address(this), amount, getStakeId, setStakeId, setEthGainId, setLusdGainId); } /** @@ -387,20 +400,24 @@ abstract contract LiquityResolver is Events, Helpers { */ function unstake( uint amount, - uint setStakeId, + uint getUnstakeId, + uint setUnstakeId, uint setEthGainId, uint setLusdGainId ) external returns (string memory _eventName, bytes memory _eventParam) { + amount = getUint(getUnstakeId, amount); + amount = amount == uint(-1) ? staking.stakes(address(this)) : amount; + uint ethGain = staking.getPendingETHGain(address(this)); uint lusdGain = staking.getPendingLUSDGain(address(this)); staking.unstake(amount); - setUint(setStakeId, amount); + setUint(setUnstakeId, amount); setUint(setEthGainId, ethGain); setUint(setLusdGainId, lusdGain); - _eventName = "LogUnstake(address,uint256,uint256,uint256,uint256)"; - _eventParam = abi.encode(address(this), amount, setStakeId, setEthGainId, setLusdGainId); + _eventName = "LogUnstake(address,uint256,uint256,uint256,uint256,uint256)"; + _eventParam = abi.encode(address(this), amount, getUnstakeId, setUnstakeId, setEthGainId, setLusdGainId); } /** From 7b17bc9d40fc368bf5390cea106570b8d5e732d6 Mon Sep 17 00:00:00 2001 From: Samyak Jain <34437877+KaymasJain@users.noreply.github.com> Date: Mon, 28 Jun 2021 23:55:24 +0530 Subject: [PATCH 09/19] liquity updates done --- .../mainnet/connectors/liquity/events.sol | 6 +- contracts/mainnet/connectors/liquity/main.sol | 70 ++++++++++--------- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/contracts/mainnet/connectors/liquity/events.sol b/contracts/mainnet/connectors/liquity/events.sol index 47105414..b8b9bd50 100644 --- a/contracts/mainnet/connectors/liquity/events.sol +++ b/contracts/mainnet/connectors/liquity/events.sol @@ -23,10 +23,8 @@ contract Events { uint withdrawAmount, uint borrowAmount, uint repayAmount, - uint getDepositId, - uint setWithdrawId, - uint getRepayId, - uint setBorrowId + uint[] getIds, + uint[] setIds ); event LogClaimCollateralFromRedemption(address indexed borrower, uint amount, uint setId); diff --git a/contracts/mainnet/connectors/liquity/main.sol b/contracts/mainnet/connectors/liquity/main.sol index efe05d73..0e10f60c 100644 --- a/contracts/mainnet/connectors/liquity/main.sol +++ b/contracts/mainnet/connectors/liquity/main.sol @@ -42,23 +42,23 @@ abstract contract LiquityResolver is Events, Helpers { uint[] setIds ) external payable returns (string memory _eventName, bytes memory _eventParam) { - uint _depositAmount = getUint(getIds[0], depositAmount); - uint _borrowAmount = getUint(getIds[1], borrowAmount); + depositAmount = getUint(getIds[0], depositAmount); + borrowAmount = getUint(getIds[1], borrowAmount); - _depositAmount = _depositAmount == uint(-1) ? address(this).balance : _depositAmount; + depositAmount = depositAmount == uint(-1) ? address(this).balance : depositAmount; - borrowerOperations.openTrove{value: _depositAmount}( + borrowerOperations.openTrove{value: depositAmount}( maxFeePercentage, - _borrowAmount, + borrowAmount, upperHint, lowerHint ); - setUint(setIds[0], _depositAmount); - setUint(setIds[1], _borrowAmount); + setUint(setIds[0], depositAmount); + setUint(setIds[1], borrowAmount); _eventName = "LogOpen(address,uint256,uint256,uint256,uint256[],uint256[])"; - _eventParam = abi.encode(address(this), maxFeePercentage, _depositAmount, _borrowAmount, getIds, setIds); + _eventParam = abi.encode(address(this), maxFeePercentage, depositAmount, borrowAmount, getIds, setIds); } /** @@ -200,37 +200,40 @@ abstract contract LiquityResolver is Events, Helpers { * @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 + * @param getIds Optional Get Ids for deposit, withdraw, borrow & repay + * @param setIds Optional Set Ids for deposit, withdraw, borrow & repay */ function adjust( uint maxFeePercentage, - uint withdrawAmount, uint depositAmount, + uint withdrawAmount, uint borrowAmount, uint repayAmount, address upperHint, address lowerHint, - uint getDepositId, - uint setWithdrawId, - uint getRepayId, - uint setBorrowId + uint[] getIds, + uint[] setIds ) 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); + + depositAmount = getUint(getIds[0], depositAmount); + adjustTrove.depositAmount = depositAmount == uint(-1) ? address(this).balance : depositAmount; + + withdrawAmount = getUint(getIds[1], withdrawAmount); + adjustTrove.withdrawAmount = withdrawAmount == uint(-1) ? troveManager.getTroveColl(address(this)) : withdrawAmount; + + adjustTrove.borrowAmount = getUint(getIds[2], borrowAmount); + + repayAmount = getUint(getIds[3], repayAmount); + if (repayAmount == uint(-1)) { + uint _lusdBal = lusdToken.balanceOf(address(this)); + uint _totalDebt = troveManager.getTroveDebt(address(this)); + repayAmount = _lusdBal > _totalDebt ? _totalDebt : _lusdBal; + } + adjustTrove.repayAmount = repayAmount; + adjustTrove.isBorrow = borrowAmount > 0; borrowerOperations.adjustTrove{value: adjustTrove.depositAmount}( @@ -242,14 +245,13 @@ abstract contract LiquityResolver is Events, Helpers { lowerHint ); - // Allow other spells to use the withdrawn collateral - setUint(setWithdrawId, withdrawAmount); + setUint(setIds[0], depositAmount); + setUint(setIds[1], withdrawAmount); + setUint(setIds[2], borrowAmount); + setUint(setIds[3], repayAmount); - // 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(address(this), maxFeePercentage, depositAmount, withdrawAmount, borrowAmount, repayAmount, getDepositId, setWithdrawId, getRepayId, setBorrowId); + _eventName = "LogAdjust(address,uint256,uint256,uint256,uint256,uint256,uint256[],uint256[])"; + _eventParam = abi.encode(address(this), maxFeePercentage, depositAmount, withdrawAmount, borrowAmount, repayAmount, getIds, setIds); } /** From 027095603ce7b8e37e0d31f0660834bfd0a91c86 Mon Sep 17 00:00:00 2001 From: Samyak Jain <34437877+KaymasJain@users.noreply.github.com> Date: Tue, 29 Jun 2021 04:22:04 +0530 Subject: [PATCH 10/19] added struct on adjust setIds --- contracts/mainnet/connectors/liquity/main.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/mainnet/connectors/liquity/main.sol b/contracts/mainnet/connectors/liquity/main.sol index 0e10f60c..f077544f 100644 --- a/contracts/mainnet/connectors/liquity/main.sol +++ b/contracts/mainnet/connectors/liquity/main.sol @@ -245,10 +245,10 @@ abstract contract LiquityResolver is Events, Helpers { lowerHint ); - setUint(setIds[0], depositAmount); - setUint(setIds[1], withdrawAmount); - setUint(setIds[2], borrowAmount); - setUint(setIds[3], repayAmount); + setUint(setIds[0], adjustTrove.depositAmount); + setUint(setIds[1], adjustTrove.withdrawAmount); + setUint(setIds[2], adjustTrove.borrowAmount); + setUint(setIds[3], adjustTrove.repayAmount); _eventName = "LogAdjust(address,uint256,uint256,uint256,uint256,uint256,uint256[],uint256[])"; _eventParam = abi.encode(address(this), maxFeePercentage, depositAmount, withdrawAmount, borrowAmount, repayAmount, getIds, setIds); From d45f08b7430350a24555b2cd96b5834c72fd84c0 Mon Sep 17 00:00:00 2001 From: Samyak Jain <34437877+KaymasJain@users.noreply.github.com> Date: Tue, 29 Jun 2021 04:23:30 +0530 Subject: [PATCH 11/19] updated the event with data from struct --- contracts/mainnet/connectors/liquity/main.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/mainnet/connectors/liquity/main.sol b/contracts/mainnet/connectors/liquity/main.sol index f077544f..13113bb3 100644 --- a/contracts/mainnet/connectors/liquity/main.sol +++ b/contracts/mainnet/connectors/liquity/main.sol @@ -251,7 +251,7 @@ abstract contract LiquityResolver is Events, Helpers { setUint(setIds[3], adjustTrove.repayAmount); _eventName = "LogAdjust(address,uint256,uint256,uint256,uint256,uint256,uint256[],uint256[])"; - _eventParam = abi.encode(address(this), maxFeePercentage, depositAmount, withdrawAmount, borrowAmount, repayAmount, getIds, setIds); + _eventParam = abi.encode(address(this), maxFeePercentage, adjustTrove.depositAmount, adjustTrove.withdrawAmount, adjustTrove.borrowAmount, adjustTrove.repayAmount, getIds, setIds); } /** From 7ef923929c0c350bf182b81477942dfa2b3209f7 Mon Sep 17 00:00:00 2001 From: Edward Mulraney Date: Wed, 30 Jun 2021 17:16:05 +0100 Subject: [PATCH 12/19] fix broken tests from instadapp PR --- .../mainnet/connectors/liquity/events.sol | 10 +- contracts/mainnet/connectors/liquity/main.sol | 26 +- test/liquity/liquity.helpers.js | 5 +- test/liquity/liquity.test.js | 251 ++++++++++-------- 4 files changed, 157 insertions(+), 135 deletions(-) diff --git a/contracts/mainnet/connectors/liquity/events.sol b/contracts/mainnet/connectors/liquity/events.sol index b8b9bd50..cb48bc99 100644 --- a/contracts/mainnet/connectors/liquity/events.sol +++ b/contracts/mainnet/connectors/liquity/events.sol @@ -8,8 +8,8 @@ contract Events { uint maxFeePercentage, uint depositAmount, uint borrowAmount, - uint[] getIds, - uint[] setIds + uint256[] getIds, + uint256[] setIds ); event LogClose(address indexed borrower, uint setId); event LogDeposit(address indexed borrower, uint amount, uint getId, uint setId); @@ -23,8 +23,8 @@ contract Events { uint withdrawAmount, uint borrowAmount, uint repayAmount, - uint[] getIds, - uint[] setIds + uint256[] getIds, + uint256[] setIds ); event LogClaimCollateralFromRedemption(address indexed borrower, uint amount, uint setId); @@ -52,7 +52,7 @@ contract Events { event LogStabilityMoveEthGainToTrove(address indexed borrower, uint amount); /* Staking */ - event LogStake(address indexed borrower, uint amount, uint setStakeId, uint getStakeId, uint setEthGainId, uint setLusdGainId); + event LogStake(address indexed borrower, uint amount, uint getStakeId, uint setStakeId, uint setEthGainId, uint setLusdGainId); event LogUnstake(address indexed borrower, uint amount, uint getUnstakeId, uint setUnstakeId, uint setEthGainId, uint setLusdGainId); event LogClaimStakingGains(address indexed borrower, uint ethGain, uint lusdGain, uint setEthGainId, uint setLusdGainId); } diff --git a/contracts/mainnet/connectors/liquity/main.sol b/contracts/mainnet/connectors/liquity/main.sol index 13113bb3..875c36b9 100644 --- a/contracts/mainnet/connectors/liquity/main.sol +++ b/contracts/mainnet/connectors/liquity/main.sol @@ -38,8 +38,8 @@ abstract contract LiquityResolver is Events, Helpers { uint borrowAmount, address upperHint, address lowerHint, - uint[] getIds, - uint[] setIds + uint[] memory getIds, + uint[] memory setIds ) external payable returns (string memory _eventName, bytes memory _eventParam) { depositAmount = getUint(getIds[0], depositAmount); @@ -139,7 +139,8 @@ abstract contract LiquityResolver is Events, Helpers { * @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 + * @param getId Optional storage slot to retrieve the amount of LUSD to borrow + * @param setId Optional storage slot to store the final amount of LUSD borrowed */ function borrow( uint maxFeePercentage, @@ -165,7 +166,8 @@ abstract contract LiquityResolver is Events, Helpers { * @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 + * @param getId Optional storage slot to retrieve the amount of LUSD from + * @param setId Optional storage slot to store the final amount of LUSD repaid */ function repay( uint amount, @@ -211,8 +213,8 @@ abstract contract LiquityResolver is Events, Helpers { uint repayAmount, address upperHint, address lowerHint, - uint[] getIds, - uint[] setIds + uint[] memory getIds, + uint[] memory setIds ) external payable returns (string memory _eventName, bytes memory _eventParam) { AdjustTrove memory adjustTrove; @@ -276,7 +278,8 @@ abstract contract LiquityResolver is Events, Helpers { * @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 getDepositId Optional storage slot to retrieve the amount of LUSD from + * @param setDepositId Optional storage slot to store the final amount of LUSD deposited * @param setEthGainId Optional storage slot to store any ETH gains in * @param setLqtyGainId Optional storage slot to store any LQTY gains in */ @@ -312,6 +315,7 @@ abstract contract LiquityResolver is Events, Helpers { * @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 getWithdrawId Optional storage slot to retrieve the amount of LUSD to withdraw from * @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 @@ -325,7 +329,7 @@ abstract contract LiquityResolver is Events, Helpers { ) external returns (string memory _eventName, bytes memory _eventParam) { amount = getUint(getWithdrawId, amount); - amount = amount == uint(-1) ? StabilityPoolLike.getCompoundedLUSDDeposit(address(this)) : amount; + amount = amount == uint(-1) ? stabilityPool.getCompoundedLUSDDeposit(address(this)) : amount; uint ethGain = stabilityPool.getDepositorETHGain(address(this)); uint lqtyBalanceBefore = lqtyToken.balanceOf(address(this)); @@ -366,7 +370,8 @@ abstract contract LiquityResolver is Events, Helpers { * @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 getStakeId Optional storage slot to retrieve the amount of LQTY to stake + * @param setStakeId Optional storage slot to store the final staked amount (can differ if requested with max balance: uint(-1)) * @param setEthGainId Optional storage slot to store any ETH gains * @param setLusdGainId Optional storage slot to store any LUSD gains */ @@ -396,7 +401,8 @@ abstract contract LiquityResolver is Events, Helpers { * @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 getUnstakeId Optional storage slot to retrieve the amount of LQTY to unstake + * @param setUnstakeId 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 */ diff --git a/test/liquity/liquity.helpers.js b/test/liquity/liquity.helpers.js index 009a114a..f6aa3e34 100644 --- a/test/liquity/liquity.helpers.js +++ b/test/liquity/liquity.helpers.js @@ -50,10 +50,11 @@ const openTroveSpell = async ( borrowAmount, upperHint, lowerHint, - 0, - 0, + [0, 0], + [0, 0], ], }; + return await dsa .connect(signer) .cast(...encodeSpells([openTroveSpell]), address, { diff --git a/test/liquity/liquity.test.js b/test/liquity/liquity.test.js index 52cd373c..6b4ae743 100644 --- a/test/liquity/liquity.test.js +++ b/test/liquity/liquity.test.js @@ -65,8 +65,8 @@ describe("Liquity", () => { borrowAmount, upperHint, lowerHint, - 0, - 0, + [0, 0], + [0, 0], ], }; @@ -144,8 +144,8 @@ describe("Liquity", () => { borrowAmount, upperHint, lowerHint, - depositId, - 0, + [depositId, 0], + [0, 0], ], }; @@ -218,8 +218,8 @@ describe("Liquity", () => { borrowAmount, upperHint, lowerHint, - 0, - borrowId, + [0, 0], + [borrowId, 0], ], }; @@ -297,8 +297,8 @@ describe("Liquity", () => { borrowAmount, upperHint, lowerHint, - 0, - 0, + [0, 0], + [0, 0], ], }; @@ -313,17 +313,24 @@ describe("Liquity", () => { const castLogEvent = receipt.events.find((e) => e.event === "LogCast") .args; expect(castLogEvent.eventNames[0]).eq( - "LogOpen(address,uint256,uint256,uint256,uint256,uint256)" + "LogOpen(address,uint256,uint256,uint256,uint256[],uint256[])" ); const expectedEventParams = ethers.utils.defaultAbiCoder.encode( - ["address", "uint256", "uint256", "uint256", "uint256", "uint256"], [ - userWallet.address, + "address", + "uint256", + "uint256", + "uint256", + "uint256[]", + "uint256[]", + ], + [ + dsa.address, maxFeePercentage, depositAmount, borrowAmount, - 0, - 0, + [0, 0], + [0, 0], ] ); expect(castLogEvent.eventParams[0]).eq(expectedEventParams); @@ -598,7 +605,7 @@ describe("Liquity", () => { .args; const expectedEventParams = ethers.utils.defaultAbiCoder.encode( ["address", "uint256"], - [userWallet.address, 0] + [dsa.address, 0] ); expect(castLogEvent.eventNames[0]).eq("LogClose(address,uint256)"); expect(castLogEvent.eventParams[0]).eq(expectedEventParams); @@ -620,7 +627,7 @@ describe("Liquity", () => { const depositEthSpell = { connector: helpers.LIQUITY_CONNECTOR, method: "deposit", - args: [topupAmount, upperHint, lowerHint, 0], + args: [topupAmount, upperHint, lowerHint, 0, 0], }; await dsa @@ -663,7 +670,7 @@ describe("Liquity", () => { const depositEthToTroveSpell = { connector: helpers.LIQUITY_CONNECTOR, method: "deposit", - args: [0, upperHint, lowerHint, depositId], + args: [0, upperHint, lowerHint, depositId, 0], }; const spells = [depositEthSpell, depositEthToTroveSpell]; @@ -697,7 +704,7 @@ describe("Liquity", () => { const depositEthSpell = { connector: helpers.LIQUITY_CONNECTOR, method: "deposit", - args: [topupAmount, upperHint, lowerHint, 0], + args: [topupAmount, upperHint, lowerHint, 0, 0], }; const depositTx = await dsa @@ -710,11 +717,11 @@ describe("Liquity", () => { const castLogEvent = receipt.events.find((e) => e.event === "LogCast") .args; const expectedEventParams = ethers.utils.defaultAbiCoder.encode( - ["address", "uint256", "uint256"], - [userWallet.address, topupAmount, 0] + ["address", "uint256", "uint256", "uint256"], + [dsa.address, topupAmount, 0, 0] ); expect(castLogEvent.eventNames[0]).eq( - "LogDeposit(address,uint256,uint256)" + "LogDeposit(address,uint256,uint256,uint256)" ); expect(castLogEvent.eventParams[0]).eq(expectedEventParams); }); @@ -734,7 +741,7 @@ describe("Liquity", () => { const withdrawEthSpell = { connector: helpers.LIQUITY_CONNECTOR, method: "withdraw", - args: [withdrawAmount, upperHint, lowerHint, 0], + args: [withdrawAmount, upperHint, lowerHint, 0, 0], }; await dsa @@ -772,7 +779,7 @@ describe("Liquity", () => { const withdrawEthFromTroveSpell = { connector: helpers.LIQUITY_CONNECTOR, method: "withdraw", - args: [withdrawAmount, upperHint, lowerHint, withdrawId], + args: [withdrawAmount, upperHint, lowerHint, 0, withdrawId], }; const withdrawEthSpell = { @@ -815,11 +822,10 @@ describe("Liquity", () => { const withdrawAmount = ethers.utils.parseEther("1"); const upperHint = ethers.constants.AddressZero; const lowerHint = ethers.constants.AddressZero; - const setId = 0; const withdrawEthSpell = { connector: helpers.LIQUITY_CONNECTOR, method: "withdraw", - args: [withdrawAmount, upperHint, lowerHint, setId], + args: [withdrawAmount, upperHint, lowerHint, 0, 0], }; const withdrawTx = await dsa @@ -830,11 +836,11 @@ describe("Liquity", () => { const castLogEvent = receipt.events.find((e) => e.event === "LogCast") .args; const expectedEventParams = ethers.utils.defaultAbiCoder.encode( - ["address", "uint256", "uint256"], - [userWallet.address, withdrawAmount, setId] + ["address", "uint256", "uint256", "uint256"], + [dsa.address, withdrawAmount, 0, 0] ); expect(castLogEvent.eventNames[0]).eq( - "LogWithdraw(address,uint256,uint256)" + "LogWithdraw(address,uint256,uint256,uint256)" ); expect(castLogEvent.eventParams[0]).eq(expectedEventParams); }); @@ -856,7 +862,7 @@ describe("Liquity", () => { const borrowSpell = { connector: helpers.LIQUITY_CONNECTOR, method: "borrow", - args: [maxFeePercentage, borrowAmount, upperHint, lowerHint, 0], + args: [maxFeePercentage, borrowAmount, upperHint, lowerHint, 0, 0], }; // Borrow more LUSD from the Trove @@ -896,6 +902,7 @@ describe("Liquity", () => { borrowAmount, upperHint, lowerHint, + 0, borrowId, ], }; @@ -944,11 +951,10 @@ describe("Liquity", () => { const upperHint = ethers.constants.AddressZero; const lowerHint = ethers.constants.AddressZero; const maxFeePercentage = ethers.utils.parseUnits("0.5", 18); // 0.5% max fee - const setId = 0; const borrowSpell = { connector: helpers.LIQUITY_CONNECTOR, method: "borrow", - args: [maxFeePercentage, borrowAmount, upperHint, lowerHint, setId], + args: [maxFeePercentage, borrowAmount, upperHint, lowerHint, 0, 0], }; const borrowTx = await dsa @@ -959,11 +965,11 @@ describe("Liquity", () => { const castLogEvent = receipt.events.find((e) => e.event === "LogCast") .args; const expectedEventParams = ethers.utils.defaultAbiCoder.encode( - ["address", "uint256", "uint256"], - [userWallet.address, borrowAmount, setId] + ["address", "uint256", "uint256", "uint256"], + [dsa.address, borrowAmount, 0, 0] ); expect(castLogEvent.eventNames[0]).eq( - "LogBorrow(address,uint256,uint256)" + "LogBorrow(address,uint256,uint256,uint256)" ); expect(castLogEvent.eventParams[0]).eq(expectedEventParams); }); @@ -997,7 +1003,7 @@ describe("Liquity", () => { const repaySpell = { connector: helpers.LIQUITY_CONNECTOR, method: "repay", - args: [repayAmount, upperHint, lowerHint, 0], + args: [repayAmount, upperHint, lowerHint, 0, 0], }; await dsa @@ -1061,7 +1067,7 @@ describe("Liquity", () => { const borrowSpell = { connector: helpers.LIQUITY_CONNECTOR, method: "repay", - args: [0, upperHint, lowerHint, lusdDepositId], + args: [0, upperHint, lowerHint, lusdDepositId, 0], }; const spells = [depositSpell, borrowSpell]; @@ -1099,12 +1105,11 @@ describe("Liquity", () => { borrowAmount, liquity ); - const getId = 0; const borrowSpell = { connector: helpers.LIQUITY_CONNECTOR, method: "repay", - args: [repayAmount, upperHint, lowerHint, getId], + args: [repayAmount, upperHint, lowerHint, 0, 0], }; const repayTx = await dsa @@ -1117,11 +1122,11 @@ describe("Liquity", () => { const castLogEvent = receipt.events.find((e) => e.event === "LogCast") .args; const expectedEventParams = ethers.utils.defaultAbiCoder.encode( - ["address", "uint256", "uint256"], - [userWallet.address, repayAmount, getId] + ["address", "uint256", "uint256", "uint256"], + [dsa.address, repayAmount, 0, 0] ); expect(castLogEvent.eventNames[0]).eq( - "LogRepay(address,uint256,uint256)" + "LogRepay(address,uint256,uint256,uint256)" ); expect(castLogEvent.eventParams[0]).eq(expectedEventParams); }); @@ -1151,16 +1156,14 @@ describe("Liquity", () => { method: "adjust", args: [ maxFeePercentage, - withdrawAmount, depositAmount, + withdrawAmount, borrowAmount, repayAmount, upperHint, lowerHint, - 0, - 0, - 0, - 0, + [0, 0, 0, 0], + [0, 0, 0, 0], ], }; @@ -1218,16 +1221,14 @@ describe("Liquity", () => { method: "adjust", args: [ maxFeePercentage, - withdrawAmount, depositAmount, + withdrawAmount, borrowAmount, repayAmount, upperHint, lowerHint, - 0, - 0, - 0, - 0, + [0, 0, 0, 0], + [0, 0, 0, 0], ], }; @@ -1297,16 +1298,14 @@ describe("Liquity", () => { method: "adjust", args: [ maxFeePercentage, - withdrawAmount, 0, // Deposit amount comes from a previous spell's storage slot + withdrawAmount, borrowAmount, 0, // Repay amount comes from a previous spell's storage slot upperHint, lowerHint, - ethDepositId, - 0, - lusdRepayId, - 0, + [ethDepositId, 0, 0, 0], + [0, 0, 0, 0], ], }; const spells = [depositEthSpell, depositLusdSpell, adjustSpell]; @@ -1379,16 +1378,14 @@ describe("Liquity", () => { method: "adjust", args: [ maxFeePercentage, - withdrawAmount, depositAmount, + withdrawAmount, borrowAmount, repayAmount, upperHint, lowerHint, - 0, - ethWithdrawId, - 0, - lusdBorrowId, + [0, 0, 0, 0], + [0, ethWithdrawId, lusdBorrowId, 0], ], }; @@ -1432,7 +1429,6 @@ describe("Liquity", () => { const userLusdBalanceAfter = await liquity.lusdToken.balanceOf( userWallet.address ); - expect(userEthBalanceAfter).eq( userEthBalanceBefore.add(withdrawAmount) ); @@ -1452,26 +1448,20 @@ describe("Liquity", () => { const upperHint = ethers.constants.AddressZero; const lowerHint = ethers.constants.AddressZero; const maxFeePercentage = ethers.utils.parseUnits("0.5", 18); // 0.5% max fee - const getDepositId = 0; - const setWithdrawId = 0; - const getRepayId = 0; - const setBorrowId = 0; const adjustSpell = { connector: helpers.LIQUITY_CONNECTOR, method: "adjust", args: [ maxFeePercentage, - withdrawAmount, depositAmount, + withdrawAmount, borrowAmount, repayAmount, upperHint, lowerHint, - getDepositId, - setWithdrawId, - getRepayId, - setBorrowId, + [0, 0, 0, 0], + [0, 0, 0, 0], ], }; @@ -1493,26 +1483,22 @@ describe("Liquity", () => { "uint256", "uint256", "uint256", - "uint256", - "uint256", - "uint256", - "uint256", + "uint256[]", + "uint256[]", ], [ - userWallet.address, + dsa.address, maxFeePercentage, depositAmount, withdrawAmount, borrowAmount, repayAmount, - getDepositId, - setWithdrawId, - getRepayId, - setBorrowId, + [0, 0, 0, 0], + [0, 0, 0, 0], ] ); expect(castLogEvent.eventNames[0]).eq( - "LogAdjust(address,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256)" + "LogAdjust(address,uint256,uint256,uint256,uint256,uint256,uint256[],uint256[])" ); expect(castLogEvent.eventParams[0]).eq(expectedEventParams); }); @@ -1652,7 +1638,7 @@ describe("Liquity", () => { .args; const expectedEventParams = ethers.utils.defaultAbiCoder.encode( ["address", "uint256", "uint256"], - [userWallet.address, claimAmount, setId] + [dsa.address, claimAmount, setId] ); expect(castLogEvent.eventNames[0]).eq( "LogClaimCollateralFromRedemption(address,uint256,uint256)" @@ -1678,7 +1664,7 @@ describe("Liquity", () => { const stabilityDepositSpell = { connector: helpers.LIQUITY_CONNECTOR, method: "stabilityDeposit", - args: [amount, frontendTag, 0, 0, 0], + args: [amount, frontendTag, 0, 0, 0, 0], }; await dsa @@ -1711,7 +1697,7 @@ describe("Liquity", () => { const stabilityDepositSpell = { connector: helpers.LIQUITY_CONNECTOR, method: "stabilityDeposit", - args: [0, frontendTag, lusdDepositId, 0, 0], + args: [0, frontendTag, lusdDepositId, 0, 0, 0], }; const spells = [depositLusdSpell, stabilityDepositSpell]; @@ -1735,6 +1721,7 @@ describe("Liquity", () => { const halfAmount = amount.div(2); const frontendTag = ethers.constants.AddressZero; const getDepositId = 0; + const setDepositId = 0; const setEthGainId = 0; const setLqtyGainId = 0; @@ -1748,7 +1735,14 @@ describe("Liquity", () => { const stabilityDepositSpell = { connector: helpers.LIQUITY_CONNECTOR, method: "stabilityDeposit", - args: [halfAmount, frontendTag, getDepositId, 0, 0], + args: [ + halfAmount, + frontendTag, + getDepositId, + setDepositId, + setEthGainId, + setLqtyGainId, + ], }; // Create a Stability deposit for this DSA @@ -1803,20 +1797,22 @@ describe("Liquity", () => { "uint256", "uint256", "uint256", + "uint256", ], [ - userWallet.address, + dsa.address, halfAmount, ethGain, lqtyGain, frontendTag, getDepositId, + setDepositId, setEthGainId, setLqtyGainId, ] ); expect(castLogEvent.eventNames[0]).eq( - "LogStabilityDeposit(address,uint256,uint256,uint256,address,uint256,uint256,uint256)" + "LogStabilityDeposit(address,uint256,uint256,uint256,address,uint256,uint256,uint256,uint256)" ); expect(castLogEvent.eventParams[0]).eq(expectedEventParams); }); @@ -1849,14 +1845,14 @@ describe("Liquity", () => { const stabilityDepositSpell = { connector: helpers.LIQUITY_CONNECTOR, method: "stabilityDeposit", - args: [amount, frontendTag, 0, 0, 0], + args: [amount, frontendTag, 0, 0, 0, 0], }; // Withdraw half of the deposit const stabilityWithdrawSpell = { connector: helpers.LIQUITY_CONNECTOR, method: "stabilityWithdraw", - args: [amount.div(2), 0, 0, 0], + args: [amount.div(2), 0, 0, 0, 0], }; const spells = [stabilityDepositSpell, stabilityWithdrawSpell]; @@ -1899,14 +1895,14 @@ describe("Liquity", () => { const stabilityDepositSpell = { connector: helpers.LIQUITY_CONNECTOR, method: "stabilityDeposit", - args: [amount, frontendTag, 0, 0, 0], + args: [amount, frontendTag, 0, 0, 0, 0], }; // Withdraw half of the deposit const stabilityWithdrawSpell = { connector: helpers.LIQUITY_CONNECTOR, method: "stabilityWithdraw", - args: [amount.div(2), 0, 0, withdrawId], + args: [amount.div(2), 0, 0, 0, withdrawId], }; const withdrawLusdSpell = { @@ -1962,11 +1958,12 @@ describe("Liquity", () => { const stabilityDepositSpell = { connector: helpers.LIQUITY_CONNECTOR, method: "stabilityDeposit", - args: [amount, frontendTag, 0, 0, 0], + args: [amount, frontendTag, 0, 0, 0, 0], }; // Withdraw half of the deposit const withdrawAmount = amount.div(2); + const getWithdrawId = 0; const setWithdrawId = 0; const setEthGainId = 0; const setLqtyGainId = 0; @@ -2009,7 +2006,13 @@ describe("Liquity", () => { const stabilityWithdrawSpell = { connector: helpers.LIQUITY_CONNECTOR, method: "stabilityWithdraw", - args: [withdrawAmount, setWithdrawId, setEthGainId, setLqtyGainId], + args: [ + withdrawAmount, + getWithdrawId, + setWithdrawId, + setEthGainId, + setLqtyGainId, + ], }; const withdrawTx = await dsa @@ -2031,19 +2034,21 @@ describe("Liquity", () => { "uint256", "uint256", "uint256", + "uint256", ], [ - userWallet.address, + dsa.address, withdrawAmount, ethGain, lqtyGain, + getWithdrawId, setWithdrawId, setEthGainId, setLqtyGainId, ] ); expect(castLogEvent.eventNames[0]).eq( - "LogStabilityWithdraw(address,uint256,uint256,uint256,uint256,uint256,uint256)" + "LogStabilityWithdraw(address,uint256,uint256,uint256,uint256,uint256,uint256,uint256)" ); expect(castLogEvent.eventParams[0]).eq(expectedEventParams); }); @@ -2072,7 +2077,7 @@ describe("Liquity", () => { const stabilityDepositSpell = { connector: helpers.LIQUITY_CONNECTOR, method: "stabilityDeposit", - args: [amount, frontendTag, 0, 0, 0], + args: [amount, frontendTag, 0, 0, 0, 0], }; await dsa @@ -2125,7 +2130,7 @@ describe("Liquity", () => { const stabilityDepositSpell = { connector: helpers.LIQUITY_CONNECTOR, method: "stabilityDeposit", - args: [amount, frontendTag, 0, 0, 0], + args: [amount, frontendTag, 0, 0, 0, 0], }; await dsa @@ -2160,7 +2165,7 @@ describe("Liquity", () => { .args; const expectedEventParams = ethers.utils.defaultAbiCoder.encode( ["address", "uint256"], - [userWallet.address, ethGainFromLiquidation] + [dsa.address, ethGainFromLiquidation] ); expect(castLogEvent.eventNames[0]).eq( "LogStabilityMoveEthGainToTrove(address,uint256)" @@ -2188,7 +2193,7 @@ describe("Liquity", () => { const stakeSpell = { connector: helpers.LIQUITY_CONNECTOR, method: "stake", - args: [amount, 0, 0, 0], + args: [amount, 0, 0, 0, 0], }; await dsa @@ -2228,7 +2233,7 @@ describe("Liquity", () => { const stakeSpell = { connector: helpers.LIQUITY_CONNECTOR, method: "stake", - args: [0, lqtyDepositId, lqtyDepositId, 0], + args: [0, lqtyDepositId, 0, 0, 0], }; const spells = [depositSpell, stakeSpell]; @@ -2262,12 +2267,13 @@ describe("Liquity", () => { ); const getStakeId = 0; + const setStakeId = 0; const setEthGainId = 0; const setLusdGainId = 0; const stakeSpell = { connector: helpers.LIQUITY_CONNECTOR, method: "stake", - args: [amount, getStakeId, setEthGainId, setLusdGainId], + args: [amount, getStakeId, setStakeId, setEthGainId, setLusdGainId], }; const stakeTx = await dsa @@ -2279,17 +2285,18 @@ describe("Liquity", () => { const castLogEvent = receipt.events.find((e) => e.event === "LogCast") .args; const expectedEventParams = ethers.utils.defaultAbiCoder.encode( - ["address", "uint256", "uint256", "uint256", "uint256"], + ["address", "uint256", "uint256", "uint256", "uint256", "uint256"], [ - userWallet.address, + dsa.address, amount, getStakeId, + setStakeId, setEthGainId, setLusdGainId, ] ); expect(castLogEvent.eventNames[0]).eq( - "LogStake(address,uint256,uint256,uint256,uint256)" + "LogStake(address,uint256,uint256,uint256,uint256,uint256)" ); expect(castLogEvent.eventParams[0]).eq(expectedEventParams); }); @@ -2308,7 +2315,7 @@ describe("Liquity", () => { const stakeSpell = { connector: helpers.LIQUITY_CONNECTOR, method: "stake", - args: [amount, 0, 0, 0], + args: [amount, 0, 0, 0, 0], }; await dsa @@ -2321,7 +2328,7 @@ describe("Liquity", () => { const unstakeSpell = { connector: helpers.LIQUITY_CONNECTOR, method: "unstake", - args: [amount, 0, 0, 0], + args: [amount, 0, 0, 0, 0], }; await dsa @@ -2351,7 +2358,7 @@ describe("Liquity", () => { const stakeSpell = { connector: helpers.LIQUITY_CONNECTOR, method: "stake", - args: [amount, 0, 0, 0], + args: [amount, 0, 0, 0, 0], }; await dsa @@ -2365,7 +2372,7 @@ describe("Liquity", () => { const unstakeSpell = { connector: helpers.LIQUITY_CONNECTOR, method: "unstake", - args: [amount, withdrawId, 0, 0], + args: [amount, 0, withdrawId, 0, 0], }; const withdrawLqtySpell = { @@ -2410,20 +2417,27 @@ describe("Liquity", () => { const stakeSpell = { connector: helpers.LIQUITY_CONNECTOR, method: "stake", - args: [amount, 0, 0, 0], + args: [amount, 0, 0, 0, 0], }; await dsa .connect(userWallet) .cast(...encodeSpells([stakeSpell]), userWallet.address); + const getUnstakeId = 0; const setUnstakeId = 0; const setEthGainId = 0; const setLusdGainId = 0; const unstakeSpell = { connector: helpers.LIQUITY_CONNECTOR, method: "unstake", - args: [amount, setUnstakeId, setEthGainId, setLusdGainId], + args: [ + amount, + getUnstakeId, + setUnstakeId, + setEthGainId, + setLusdGainId, + ], }; const unstakeTx = await dsa @@ -2435,24 +2449,25 @@ describe("Liquity", () => { const castLogEvent = receipt.events.find((e) => e.event === "LogCast") .args; const expectedEventParams = ethers.utils.defaultAbiCoder.encode( - ["address", "uint256", "uint256", "uint256", "uint256"], + ["address", "uint256", "uint256", "uint256", "uint256", "uint256"], [ - userWallet.address, + dsa.address, amount, + getUnstakeId, setUnstakeId, setEthGainId, setLusdGainId, ] ); expect(castLogEvent.eventNames[0]).eq( - "LogUnstake(address,uint256,uint256,uint256,uint256)" + "LogUnstake(address,uint256,uint256,uint256,uint256,uint256)" ); expect(castLogEvent.eventParams[0]).eq(expectedEventParams); }); }); describe("claimStakingGains()", () => { - it("Claims gains from staking", async () => { + it("claims gains from staking", async () => { const stakerDsa = await buildDSAv2(userWallet.address); const amount = ethers.utils.parseUnits("1000", 18); // 1000 LQTY @@ -2466,7 +2481,7 @@ describe("Liquity", () => { const stakeSpell = { connector: helpers.LIQUITY_CONNECTOR, method: "stake", - args: [amount, 0, 0, 0], + args: [amount, 0, 0, 0, 0], }; await stakerDsa .connect(userWallet) @@ -2520,7 +2535,7 @@ describe("Liquity", () => { expect(lusdBalanceAfter).to.eq(lusdGain); }); - it("Claims gains from staking and stores them for other spells", async () => { + it("claims gains from staking and stores them for other spells", async () => { const stakerDsa = await buildDSAv2(userWallet.address); const amount = ethers.utils.parseUnits("1000", 18); // 1000 LQTY @@ -2534,7 +2549,7 @@ describe("Liquity", () => { const stakeSpell = { connector: helpers.LIQUITY_CONNECTOR, method: "stake", - args: [amount, 0, 0, 0], + args: [amount, 0, 0, 0, 0], }; await stakerDsa .connect(userWallet) @@ -2634,7 +2649,7 @@ describe("Liquity", () => { const stakeSpell = { connector: helpers.LIQUITY_CONNECTOR, method: "stake", - args: [amount, 0, 0, 0], + args: [amount, 0, 0, 0, 0], }; await stakerDsa .connect(userWallet) @@ -2680,7 +2695,7 @@ describe("Liquity", () => { .args; const expectedEventParams = ethers.utils.defaultAbiCoder.encode( ["address", "uint256", "uint256", "uint256", "uint256"], - [userWallet.address, ethGain, lusdGain, setEthGainId, setLusdGainId] + [stakerDsa.address, ethGain, lusdGain, setEthGainId, setLusdGainId] ); expect(castLogEvent.eventNames[0]).eq( "LogClaimStakingGains(address,uint256,uint256,uint256,uint256)" From 67e07913edc78c00e6ef8ad333471e5fbec5ade3 Mon Sep 17 00:00:00 2001 From: Edward Mulraney Date: Mon, 26 Jul 2021 08:41:32 -0400 Subject: [PATCH 13/19] Allow non-payable functions to be composable with payable functions Make all functions payable so they can be composed in a larger spell which might use msg.value (normally non-payable is rejected) Co-authored-by: Samyak Jain <34437877+KaymasJain@users.noreply.github.com> --- contracts/mainnet/connectors/liquity/main.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/mainnet/connectors/liquity/main.sol b/contracts/mainnet/connectors/liquity/main.sol index 875c36b9..01d46958 100644 --- a/contracts/mainnet/connectors/liquity/main.sol +++ b/contracts/mainnet/connectors/liquity/main.sol @@ -356,7 +356,7 @@ abstract contract LiquityResolver is Events, Helpers { function stabilityMoveEthGainToTrove( address upperHint, address lowerHint - ) external returns (string memory _eventName, bytes memory _eventParam) { + ) external payable returns (string memory _eventName, bytes memory _eventParam) { uint amount = stabilityPool.getDepositorETHGain(address(this)); stabilityPool.withdrawETHGainToTrove(upperHint, lowerHint); _eventName = "LogStabilityMoveEthGainToTrove(address,uint256)"; From 24a038acedfff108083daa71a9d41da343b2474a Mon Sep 17 00:00:00 2001 From: Edward Mulraney Date: Mon, 26 Jul 2021 08:42:20 -0400 Subject: [PATCH 14/19] Allow non payable functions to be composable with payable functions Make all functions payable so they can be composed in a larger spell which might use msg.value (normally non-payable is rejected) Co-authored-by: Samyak Jain <34437877+KaymasJain@users.noreply.github.com> --- contracts/mainnet/connectors/liquity/main.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/mainnet/connectors/liquity/main.sol b/contracts/mainnet/connectors/liquity/main.sol index 01d46958..51761b4c 100644 --- a/contracts/mainnet/connectors/liquity/main.sol +++ b/contracts/mainnet/connectors/liquity/main.sol @@ -381,7 +381,7 @@ abstract contract LiquityResolver is Events, Helpers { uint setStakeId, uint setEthGainId, uint setLusdGainId - ) external returns (string memory _eventName, bytes memory _eventParam) { + ) external payable returns (string memory _eventName, bytes memory _eventParam) { amount = getUint(getStakeId, amount); amount = amount == uint(-1) ? lqtyToken.balanceOf(address(this)) : amount; From a126642e30dd8e548d29cb8aba9ff734a1793eb5 Mon Sep 17 00:00:00 2001 From: Edward Mulraney Date: Mon, 26 Jul 2021 08:43:00 -0400 Subject: [PATCH 15/19] Allow non-payable functions to be composable with payable functions Make all functions payable so they can be composed in a larger spell which might use msg.value (normally non-payable is rejected) Co-authored-by: Samyak Jain <34437877+KaymasJain@users.noreply.github.com> --- contracts/mainnet/connectors/liquity/main.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/mainnet/connectors/liquity/main.sol b/contracts/mainnet/connectors/liquity/main.sol index 51761b4c..d6e5aaf9 100644 --- a/contracts/mainnet/connectors/liquity/main.sol +++ b/contracts/mainnet/connectors/liquity/main.sol @@ -412,7 +412,7 @@ abstract contract LiquityResolver is Events, Helpers { uint setUnstakeId, uint setEthGainId, uint setLusdGainId - ) external returns (string memory _eventName, bytes memory _eventParam) { + ) external payable returns (string memory _eventName, bytes memory _eventParam) { amount = getUint(getUnstakeId, amount); amount = amount == uint(-1) ? staking.stakes(address(this)) : amount; From f37b863815ce4cddd18a01343e98946f9492c7d3 Mon Sep 17 00:00:00 2001 From: Edward Mulraney Date: Mon, 26 Jul 2021 08:43:12 -0400 Subject: [PATCH 16/19] Allow non-payable functions to be composable with payable functions Make all functions payable so they can be composed in a larger spell which might use msg.value (normally non-payable is rejected) Co-authored-by: Samyak Jain <34437877+KaymasJain@users.noreply.github.com> --- contracts/mainnet/connectors/liquity/main.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/mainnet/connectors/liquity/main.sol b/contracts/mainnet/connectors/liquity/main.sol index d6e5aaf9..5628499c 100644 --- a/contracts/mainnet/connectors/liquity/main.sol +++ b/contracts/mainnet/connectors/liquity/main.sol @@ -437,7 +437,7 @@ abstract contract LiquityResolver is Events, Helpers { function claimStakingGains( uint setEthGainId, uint setLusdGainId - ) external returns (string memory _eventName, bytes memory _eventParam) { + ) external payable returns (string memory _eventName, bytes memory _eventParam) { uint ethGain = staking.getPendingETHGain(address(this)); uint lusdGain = staking.getPendingLUSDGain(address(this)); From f81af1743ea00e6a45719ea73785a2f6d14976eb Mon Sep 17 00:00:00 2001 From: Edward Mulraney Date: Fri, 30 Jul 2021 10:31:46 +0100 Subject: [PATCH 17/19] make all functions payable so they can be composed with payable functions --- contracts/mainnet/connectors/liquity/main.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/mainnet/connectors/liquity/main.sol b/contracts/mainnet/connectors/liquity/main.sol index 5628499c..6bec9af8 100644 --- a/contracts/mainnet/connectors/liquity/main.sol +++ b/contracts/mainnet/connectors/liquity/main.sol @@ -326,7 +326,7 @@ abstract contract LiquityResolver is Events, Helpers { uint setWithdrawId, uint setEthGainId, uint setLqtyGainId - ) external returns (string memory _eventName, bytes memory _eventParam) { + ) external payable returns (string memory _eventName, bytes memory _eventParam) { amount = getUint(getWithdrawId, amount); amount = amount == uint(-1) ? stabilityPool.getCompoundedLUSDDeposit(address(this)) : amount; From 9d60daa894d1e6022d9d5fe8c68c56f6aa228be3 Mon Sep 17 00:00:00 2001 From: Thrilok Kumar Date: Tue, 3 Aug 2021 21:45:03 +0530 Subject: [PATCH 18/19] Deployed reflexer mapping contract --- contracts/mainnet/mapping/reflexer.sol | 7 +++---- docs/connectors.json | 3 ++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/contracts/mainnet/mapping/reflexer.sol b/contracts/mainnet/mapping/reflexer.sol index c9092d3e..ef5728a2 100644 --- a/contracts/mainnet/mapping/reflexer.sol +++ b/contracts/mainnet/mapping/reflexer.sol @@ -20,8 +20,7 @@ contract Helpers { ConnectorsInterface public constant connectors = ConnectorsInterface(0x97b0B3A8bDeFE8cB9563a3c610019Ad10DB8aD11); // InstaConnectorsV2 IndexInterface public constant instaIndex = IndexInterface(0x2971AdFa57b20E5a416aE5a708A8655A9c74f723); - // TODO: add address for MappingController - MappingControllerInterface public constant mappingController = MappingControllerInterface(address(0)); + MappingControllerInterface public constant mappingController = MappingControllerInterface(0xDdd075D5e1024901E4038461e1e4BbC3A48a08d4); uint public version = 1; mapping (bytes32 => address) public collateralJoinMapping; @@ -55,8 +54,8 @@ contract Helpers { } -contract GebMapping is Helpers { - string constant public name = "Reflexer-Mapping-v1"; +contract InstaReflexerGebMapping is Helpers { + string constant public name = "Reflexer-Geb-Mapping-v1"; constructor() public { address[] memory collateralJoins = new address[](1); diff --git a/docs/connectors.json b/docs/connectors.json index 52b4877d..bae7208b 100644 --- a/docs/connectors.json +++ b/docs/connectors.json @@ -36,6 +36,7 @@ } }, "mappings": { - "InstaMappingController": "0xDdd075D5e1024901E4038461e1e4BbC3A48a08d4" + "InstaMappingController": "0xDdd075D5e1024901E4038461e1e4BbC3A48a08d4", + "InstaReflexerGebMapping": "0x573e5132693C046D1A9F75Bac683889164bA41b4" } } From cf9232288cf19dd80de84476ae88e82a638743c9 Mon Sep 17 00:00:00 2001 From: Thrilok Kumar Date: Thu, 5 Aug 2021 12:57:09 +0530 Subject: [PATCH 19/19] Deployed reflexer connector --- contracts/mainnet/connectors/reflexer/helpers.sol | 3 +-- docs/connectors.json | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/contracts/mainnet/connectors/reflexer/helpers.sol b/contracts/mainnet/connectors/reflexer/helpers.sol index c46776a7..09d2b7a2 100644 --- a/contracts/mainnet/connectors/reflexer/helpers.sol +++ b/contracts/mainnet/connectors/reflexer/helpers.sol @@ -37,8 +37,7 @@ abstract contract Helpers is DSMath, Basic { * @dev Return Reflexer mapping Address. */ function getGebMappingAddress() internal pure returns (address) { - // TODO: Set the real deployed Reflexer mapping address - return 0x0000000000000000000000000000000000000000; + return 0x573e5132693C046D1A9F75Bac683889164bA41b4; } function getCollateralJoinAddress(bytes32 collateralType) internal view returns (address) { diff --git a/docs/connectors.json b/docs/connectors.json index bae7208b..f5146a77 100644 --- a/docs/connectors.json +++ b/docs/connectors.json @@ -1,6 +1,5 @@ { - "connectors": - { + "connectors": { "1" : { "AUTHORITY-A": "0x351Bb32e90C35647Df7a584f3c1a3A0c38F31c68", "BASIC-A": "0x9926955e0Dd681Dc303370C52f4Ad0a4dd061687", @@ -24,7 +23,8 @@ "MAKERDAO-CLAIM-A": "0x2f8cBE650af98602a215b6482F2aD60893C5A4E8", "WETH-A": "0x22075fa719eFb02Ca3cF298AFa9C974B7465E5D3", "REFINANCE-A": "0x9eA34bE6dA51aa9F6408FeA79c946FDCFA424442", - "INST-A": "0x52C2C4a0db049255fF345EB9D3Fb1f555b7a924A" + "INST-A": "0x52C2C4a0db049255fF345EB9D3Fb1f555b7a924A", + "REFLEXER-A": "0xaC6dc28a6251F49Bbe5755E630107Dccde9ae2C8" }, "137" : { "1INCH-A": "0xC0d9210496afE9763F5d8cEb8deFfBa817232A9e",