From de1472e98fdfe81e0b15718b7e90a1429aa8eee5 Mon Sep 17 00:00:00 2001 From: Thrilok Kumar Date: Wed, 26 May 2021 07:03:45 +0530 Subject: [PATCH 01/45] Updated 1inch connector --- .../mainnet/connectors/1inch/helpers.sol | 9 +++++-- contracts/mainnet/connectors/1inch/main.sol | 4 ++-- contracts/mainnet/connectors_old/1inch.sol | 24 +++++++++++++++---- 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/contracts/mainnet/connectors/1inch/helpers.sol b/contracts/mainnet/connectors/1inch/helpers.sol index 1aa7b6a1..41cc4b0d 100644 --- a/contracts/mainnet/connectors/1inch/helpers.sol +++ b/contracts/mainnet/connectors/1inch/helpers.sol @@ -9,10 +9,15 @@ abstract contract Helpers is DSMath, Basic { /** * @dev 1Inch Address */ - address internal constant oneInchAddr = 0x111111125434b319222CdBf8C261674aDB56F3ae; + address internal constant oneInchAddr = 0x11111112542D85B3EF69AE05771c2dCCff4fAa26; /** * @dev 1inch swap function sig */ - bytes4 internal constant oneInchSig = 0x90411a32; + bytes4 internal constant oneInchSwapSig = 0x7c025200; + + /** + * @dev 1inch swap function sig + */ + bytes4 internal constant oneInchUnoswapSig = 0x2e95b6c8; } \ No newline at end of file diff --git a/contracts/mainnet/connectors/1inch/main.sol b/contracts/mainnet/connectors/1inch/main.sol index 31901334..2d892cbd 100644 --- a/contracts/mainnet/connectors/1inch/main.sol +++ b/contracts/mainnet/connectors/1inch/main.sol @@ -25,7 +25,7 @@ abstract contract OneInchResolver is Helpers, Events { assembly { sig := mload(add(_data, 32)) } - isOk = sig == oneInchSig; + isOk = sig == oneInchSwapSig || sig == oneInchUnoswapSig; } /** @@ -124,5 +124,5 @@ abstract contract OneInch is OneInchResolverHelpers { } contract ConnectV2OneInch is OneInch { - string public name = "1Inch-v1"; + string public name = "1Inch-v1.1"; } diff --git a/contracts/mainnet/connectors_old/1inch.sol b/contracts/mainnet/connectors_old/1inch.sol index fc4b728d..b71b2771 100644 --- a/contracts/mainnet/connectors_old/1inch.sol +++ b/contracts/mainnet/connectors_old/1inch.sol @@ -82,14 +82,21 @@ abstract contract OneHelpers is Stores, DSMath { * @dev Return 1Inch Address */ function getOneInchAddress() internal pure returns (address) { - return 0x111111125434b319222CdBf8C261674aDB56F3ae; + return 0x11111112542D85B3EF69AE05771c2dCCff4fAa26; } /** * @dev Return 1inch swap function sig */ - function getOneInchSig() internal pure returns (bytes4) { - return 0x90411a32; + function getOneInchSwapSig() internal pure returns (bytes4) { + return 0x7c025200; + } + + /** + * @dev Return 1inch swap function sig + */ + function getOneInchUnoswapSig() internal pure returns (bytes4) { + return 0x2e95b6c8; } function convert18ToDec(uint _dec, uint256 _amt) internal pure returns (uint256 amt) { @@ -224,7 +231,7 @@ abstract contract OneInchResolver is OneProtoResolver { assembly { sig := mload(add(_data, 32)) } - isOk = sig == getOneInchSig(); + isOk = sig == getOneInchSwapSig() || sig == getOneInchUnoswapSig(); } struct OneInchData { @@ -585,5 +592,12 @@ abstract contract OneInch is OneProto { } contract ConnectOne is OneInch { - string public name = "1inch-1proto-v1"; + string public name = "1inch-1proto-v1.2"; + + /** + * @dev Connector Details + */ + function connectorID() public pure returns(uint _type, uint _id) { + (_type, _id) = (1, 98); + } } From a1203c66aea638099ec2b1e38e9f15196e13c34d Mon Sep 17 00:00:00 2001 From: Edward Mulraney Date: Tue, 1 Jun 2021 15:50:52 +0100 Subject: [PATCH 02/45] 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 03/45] 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 04/45] 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 05/45] 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 06/45] 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 07/45] 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 2126be64e2385514932ae59eb5613172d4081fff Mon Sep 17 00:00:00 2001 From: Thrilok Kumar Date: Wed, 23 Jun 2021 02:18:01 +0530 Subject: [PATCH 08/45] Added refinanace connector --- .../mainnet/connectors/refinance/events.sol | 0 .../mainnet/connectors/refinance/helpers.sol | 155 +++++++++++ .../connectors/refinance/helpers/aaveV1.sol | 202 ++++++++++++++ .../connectors/refinance/helpers/aaveV2.sol | 200 ++++++++++++++ .../connectors/refinance/helpers/compound.sol | 160 +++++++++++ .../connectors/refinance/interfaces.sol | 113 ++++++++ .../mainnet/connectors/refinance/main.sol | 251 ++++++++++++++++++ 7 files changed, 1081 insertions(+) create mode 100644 contracts/mainnet/connectors/refinance/events.sol create mode 100644 contracts/mainnet/connectors/refinance/helpers.sol create mode 100644 contracts/mainnet/connectors/refinance/helpers/aaveV1.sol create mode 100644 contracts/mainnet/connectors/refinance/helpers/aaveV2.sol create mode 100644 contracts/mainnet/connectors/refinance/helpers/compound.sol create mode 100644 contracts/mainnet/connectors/refinance/interfaces.sol create mode 100644 contracts/mainnet/connectors/refinance/main.sol diff --git a/contracts/mainnet/connectors/refinance/events.sol b/contracts/mainnet/connectors/refinance/events.sol new file mode 100644 index 00000000..e69de29b diff --git a/contracts/mainnet/connectors/refinance/helpers.sol b/contracts/mainnet/connectors/refinance/helpers.sol new file mode 100644 index 00000000..c3593d50 --- /dev/null +++ b/contracts/mainnet/connectors/refinance/helpers.sol @@ -0,0 +1,155 @@ +pragma solidity ^0.7.0; +pragma experimental ABIEncoderV2; + +import { Basic } from "../../common/basic.sol"; + + +import { + AaveV1ProviderInterface, + AaveV1Interface, + AaveV2LendingPoolProviderInterface, + AaveV2DataProviderInterface, + AaveV2Interface, + ComptrollerInterface, + CTokenInterface, + CompoundMappingInterface +} from "./interfaces.sol"; + + +import { TokenInterface } from "../../common/interfaces.sol"; + +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +contract Helpers is Basic { + using SafeERC20 for IERC20; + + enum Protocol { + Aave, + AaveV2, + Compound + } + + address payable constant feeCollector = 0xb1DC62EC38E6E3857a887210C38418E4A17Da5B2; + + /** + * @dev Return InstaDApp Mapping Address + */ + address constant internal getMappingAddr = 0xA8F9D4aA7319C54C04404765117ddBf9448E2082; // CompoundMapping Address + + /** + * @dev Return Compound Comptroller Address + */ + address constant internal getComptrollerAddress = 0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B; // CompoundMapping Address + + /** + * @dev get Aave Provider + */ + AaveV1ProviderInterface constant internal getAaveProvider = + AaveV1ProviderInterface(0x24a42fD28C976A61Df5D00D0599C34c4f90748c8); + + /** + * @dev get Aave Lending Pool Provider + */ + AaveV2LendingPoolProviderInterface constant internal getAaveV2Provider = + AaveV2LendingPoolProviderInterface(0xB53C1a33016B2DC2fF3653530bfF1848a515c8c5); + + /** + * @dev get Aave Protocol Data Provider + */ + AaveV2DataProviderInterface constant internal getAaveV2DataProvider = + AaveV2DataProviderInterface(0x057835Ad21a177dbdd3090bB1CAE03EaCF78Fc6d); + + /** + * @dev get Referral Code + */ + uint16 constant internal getReferralCode = 3228; +} + +contract protocolHelpers is Helpers { + using SafeERC20 for IERC20; + + function getWithdrawBalance(AaveV1Interface aave, address token) internal view returns (uint bal) { + (bal, , , , , , , , , ) = aave.getUserReserveData(token, address(this)); + } + + function getPaybackBalance(AaveV1Interface aave, address token) internal view returns (uint bal, uint fee) { + (, bal, , , , , fee, , , ) = aave.getUserReserveData(token, address(this)); + } + + function getTotalBorrowBalance(AaveV1Interface aave, address token) internal view returns (uint amt) { + (, uint bal, , , , , uint fee, , , ) = aave.getUserReserveData(token, address(this)); + amt = add(bal, fee); + } + + function getWithdrawBalanceV2(AaveV2DataProviderInterface aaveData, address token) internal view returns (uint bal) { + (bal, , , , , , , , ) = aaveData.getUserReserveData(token, address(this)); + } + + function getPaybackBalanceV2(AaveV2DataProviderInterface aaveData, address token, uint rateMode) internal view returns (uint bal) { + if (rateMode == 1) { + (, bal, , , , , , , ) = aaveData.getUserReserveData(token, address(this)); + } else { + (, , bal, , , , , , ) = aaveData.getUserReserveData(token, address(this)); + } + } + + function getIsColl(AaveV1Interface aave, address token) internal view returns (bool isCol) { + (, , , , , , , , , isCol) = aave.getUserReserveData(token, address(this)); + } + + function getIsCollV2(AaveV2DataProviderInterface aaveData, address token) internal view returns (bool isCol) { + (, , , , , , , , isCol) = aaveData.getUserReserveData(token, address(this)); + } + + function getMaxBorrow(Protocol target, address token, CTokenInterface ctoken, uint rateMode) internal returns (uint amt) { + AaveV1Interface aaveV1 = AaveV1Interface(getAaveProvider.getLendingPool()); + AaveV2DataProviderInterface aaveData = getAaveV2DataProvider; + + if (target == Protocol.Aave) { + (uint _amt, uint _fee) = getPaybackBalance(aaveV1, token); + amt = _amt + _fee; + } else if (target == Protocol.AaveV2) { + amt = getPaybackBalanceV2(aaveData, token, rateMode); + } else if (target == Protocol.Compound) { + amt = ctoken.borrowBalanceCurrent(address(this)); + } + } + + function transferFees(address token, uint feeAmt) internal { + if (feeAmt > 0) { + if (token == ethAddr) { + feeCollector.transfer(feeAmt); + } else { + IERC20(token).safeTransfer(feeCollector, feeAmt); + } + } + } + + function calculateFee(uint256 amount, uint256 fee, bool toAdd) internal pure returns(uint feeAmount, uint _amount){ + feeAmount = wmul(amount, fee); + _amount = toAdd ? add(amount, feeAmount) : sub(amount, feeAmount); + } + + function getTokenInterfaces(uint length, address[] memory tokens) internal pure returns (TokenInterface[] memory) { + TokenInterface[] memory _tokens = new TokenInterface[](length); + for (uint i = 0; i < length; i++) { + if (tokens[i] == ethAddr) { + _tokens[i] = TokenInterface(wethAddr); + } else { + _tokens[i] = TokenInterface(tokens[i]); + } + } + return _tokens; + } + + function getCtokenInterfaces(uint length, string[] memory tokenIds) internal view returns (CTokenInterface[] memory) { + CTokenInterface[] memory _ctokens = new CTokenInterface[](length); + for (uint i = 0; i < length; i++) { + (address token, address cToken) = CompoundMappingInterface(getMappingAddr).getMapping(tokenIds[i]); + require(token != address(0) && cToken != address(0), "invalid token/ctoken address"); + _ctokens[i] = CTokenInterface(cToken); + } + return _ctokens; + } +} \ No newline at end of file diff --git a/contracts/mainnet/connectors/refinance/helpers/aaveV1.sol b/contracts/mainnet/connectors/refinance/helpers/aaveV1.sol new file mode 100644 index 00000000..bc33c624 --- /dev/null +++ b/contracts/mainnet/connectors/refinance/helpers/aaveV1.sol @@ -0,0 +1,202 @@ +pragma solidity ^0.7.0; +pragma experimental ABIEncoderV2; + +import { protocolHelpers } from "../helpers.sol"; + +import { + AaveV1ProviderInterface, + AaveV1Interface, + AaveV1CoreInterface, + ATokenV1Interface, + CTokenInterface + // AaveV2LendingPoolProviderInterface, + // AaveV2DataProviderInterface, + // AaveV2Interface, +} from "../interfaces.sol"; + +import { TokenInterface } from "../../../common/interfaces.sol"; + +contract AaveV1Helpers is protocolHelpers { + + struct AaveV1BorrowData { + AaveV1Interface aave; + uint length; + uint fee; + Protocol target; + TokenInterface[] tokens; + CTokenInterface[] ctokens; + uint[] amts; + uint[] borrowRateModes; + uint[] paybackRateModes; + } + + struct AaveV1DepositData { + AaveV1Interface aave; + AaveV1CoreInterface aaveCore; + uint length; + uint fee; + TokenInterface[] tokens; + uint[] amts; + } + + function _aaveV1BorrowOne( + AaveV1Interface aave, + uint fee, + Protocol target, + TokenInterface token, + CTokenInterface ctoken, + uint amt, + uint borrowRateMode, + uint paybackRateMode + ) internal returns (uint) { + if (amt > 0) { + + address _token = address(token) == wethAddr ? ethAddr : address(token); + + if (amt == uint(-1)) { + amt = getMaxBorrow(target, address(token), ctoken, paybackRateMode); + } + + (uint feeAmt, uint _amt) = calculateFee(amt, fee, true); + + aave.borrow(_token, _amt, borrowRateMode, getReferralCode); + transferFees(_token, feeAmt); + } + return amt; + } + + function _aaveV1Borrow( + AaveV1BorrowData memory data + ) internal returns (uint[] memory) { + uint[] memory finalAmts = new uint[](data.length); + for (uint i = 0; i < data.length; i++) { + finalAmts[i] = _aaveV1BorrowOne( + data.aave, + data.fee, + data.target, + data.tokens[i], + data.ctokens[i], + data.amts[i], + data.borrowRateModes[i], + data.paybackRateModes[i] + ); + } + return finalAmts; + } + + function _aaveV1DepositOne( + AaveV1Interface aave, + AaveV1CoreInterface aaveCore, + uint fee, + TokenInterface token, + uint amt + ) internal { + if (amt > 0) { + uint ethAmt; + (uint feeAmt, uint _amt) = calculateFee(amt, fee, false); + + bool isEth = address(token) == wethAddr; + + address _token = isEth ? ethAddr : address(token); + + if (isEth) { + ethAmt = _amt; + } else { + token.approve(address(aaveCore), _amt); + } + + transferFees(_token, feeAmt); + + aave.deposit{value:ethAmt}(_token, _amt, getReferralCode); + + if (!getIsColl(aave, _token)) + aave.setUserUseReserveAsCollateral(_token, true); + } + } + + function _aaveV1Deposit( + AaveV1DepositData memory data + ) internal { + for (uint i = 0; i < data.length; i++) { + _aaveV1DepositOne( + data.aave, + data.aaveCore, + data.fee, + data.tokens[i], + data.amts[i] + ); + } + } + + function _aaveV1WithdrawOne( + AaveV1Interface aave, + AaveV1CoreInterface aaveCore, + TokenInterface token, + uint amt + ) internal returns (uint) { + if (amt > 0) { + address _token = address(token) == wethAddr ? ethAddr : address(token); + ATokenV1Interface atoken = ATokenV1Interface(aaveCore.getReserveATokenAddress(_token)); + if (amt == uint(-1)) { + amt = getWithdrawBalance(aave, _token); + } + atoken.redeem(amt); + } + return amt; + } + + function _aaveV1Withdraw( + AaveV1Interface aave, + AaveV1CoreInterface aaveCore, + uint length, + TokenInterface[] memory tokens, + uint[] memory amts + ) internal returns (uint[] memory) { + uint[] memory finalAmts = new uint[](length); + for (uint i = 0; i < length; i++) { + finalAmts[i] = _aaveV1WithdrawOne(aave, aaveCore, tokens[i], amts[i]); + } + return finalAmts; + } + + function _aaveV1PaybackOne( + AaveV1Interface aave, + AaveV1CoreInterface aaveCore, + TokenInterface token, + uint amt + ) internal returns (uint) { + if (amt > 0) { + uint ethAmt; + + bool isEth = address(token) == wethAddr; + + address _token = isEth ? ethAddr : address(token); + + if (amt == uint(-1)) { + (uint _amt, uint _fee) = getPaybackBalance(aave, _token); + amt = _amt + _fee; + } + + if (isEth) { + ethAmt = amt; + } else { + token.approve(address(aaveCore), amt); + } + + aave.repay{value:ethAmt}(_token, amt, payable(address(this))); + } + return amt; + } + + function _aaveV1Payback( + AaveV1Interface aave, + AaveV1CoreInterface aaveCore, + uint length, + TokenInterface[] memory tokens, + uint[] memory amts + ) internal { + for (uint i = 0; i < length; i++) { + _aaveV1PaybackOne(aave, aaveCore, tokens[i], amts[i]); + } + } +} diff --git a/contracts/mainnet/connectors/refinance/helpers/aaveV2.sol b/contracts/mainnet/connectors/refinance/helpers/aaveV2.sol new file mode 100644 index 00000000..488deae3 --- /dev/null +++ b/contracts/mainnet/connectors/refinance/helpers/aaveV2.sol @@ -0,0 +1,200 @@ +pragma solidity ^0.7.0; +pragma experimental ABIEncoderV2; + +import { protocolHelpers } from "../helpers.sol"; + +import { + // AaveV1ProviderInterface, + // AaveV1Interface, + // AaveV1CoreInterface, + AaveV2LendingPoolProviderInterface, + AaveV2DataProviderInterface, + AaveV2Interface, + ATokenV1Interface, + CTokenInterface +} from "../interfaces.sol"; + +import { TokenInterface } from "../../../common/interfaces.sol"; + +contract AaveV2Helpers is protocolHelpers { + + struct AaveV2BorrowData { + AaveV2Interface aave; + uint length; + uint fee; + Protocol target; + TokenInterface[] tokens; + CTokenInterface[] ctokens; + uint[] amts; + uint[] rateModes; + } + + struct AaveV2PaybackData { + AaveV2Interface aave; + AaveV2DataProviderInterface aaveData; + uint length; + TokenInterface[] tokens; + uint[] amts; + uint[] rateModes; + } + + struct AaveV2WithdrawData { + AaveV2Interface aave; + AaveV2DataProviderInterface aaveData; + uint length; + TokenInterface[] tokens; + uint[] amts; + } + + function _aaveV2BorrowOne( + AaveV2Interface aave, + uint fee, + Protocol target, + TokenInterface token, + CTokenInterface ctoken, + uint amt, + uint rateMode + ) internal returns (uint) { + if (amt > 0) { + bool isEth = address(token) == wethAddr; + + address _token = isEth ? ethAddr : address(token); + + if (amt == uint(-1)) { + amt = getMaxBorrow(target, _token, ctoken, rateMode); + } + + (uint feeAmt, uint _amt) = calculateFee(amt, fee, true); + + aave.borrow(address(token), _amt, rateMode, getReferralCode, address(this)); + convertWethToEth(isEth, token, amt); + + transferFees(_token, feeAmt); + } + return amt; + } + + function _aaveV2Borrow( + AaveV2BorrowData memory data + ) internal returns (uint[] memory) { + uint[] memory finalAmts = new uint[](data.length); + for (uint i = 0; i < data.length; i++) { + finalAmts[i] = _aaveV2BorrowOne( + data.aave, + data.fee, + data.target, + data.tokens[i], + data.ctokens[i], + data.amts[i], + data.rateModes[i] + ); + } + return finalAmts; + } + + function _aaveV2DepositOne( + AaveV2Interface aave, + AaveV2DataProviderInterface aaveData, + uint fee, + TokenInterface token, + uint amt + ) internal { + if (amt > 0) { + (uint feeAmt, uint _amt) = calculateFee(amt, fee, false); + + bool isEth = address(token) == wethAddr; + address _token = isEth ? ethAddr : address(token); + + transferFees(_token, feeAmt); + + convertEthToWeth(isEth, token, _amt); + + token.approve(address(aave), _amt); + + aave.deposit(address(token), _amt, address(this), getReferralCode); + + if (!getIsCollV2(aaveData, address(token))) { + aave.setUserUseReserveAsCollateral(address(token), true); + } + } + } + + function _aaveV2Deposit( + AaveV2Interface aave, + AaveV2DataProviderInterface aaveData, + uint length, + uint fee, + TokenInterface[] memory tokens, + uint[] memory amts + ) internal { + for (uint i = 0; i < length; i++) { + _aaveV2DepositOne(aave, aaveData, fee, tokens[i], amts[i]); + } + } + + function _aaveV2WithdrawOne( + AaveV2Interface aave, + AaveV2DataProviderInterface aaveData, + TokenInterface token, + uint amt + ) internal returns (uint _amt) { + if (amt > 0) { + bool isEth = address(token) == wethAddr; + + aave.withdraw(address(token), amt, address(this)); + + _amt = amt == uint(-1) ? getWithdrawBalanceV2(aaveData, address(token)) : amt; + + convertWethToEth(isEth, token, _amt); + } + } + + function _aaveV2Withdraw( + AaveV2WithdrawData memory data + ) internal returns (uint[] memory) { + uint[] memory finalAmts = new uint[](data.length); + for (uint i = 0; i < data.length; i++) { + finalAmts[i] = _aaveV2WithdrawOne( + data.aave, + data.aaveData, + data.tokens[i], + data.amts[i] + ); + } + return finalAmts; + } + + function _aaveV2PaybackOne( + AaveV2Interface aave, + AaveV2DataProviderInterface aaveData, + TokenInterface token, + uint amt, + uint rateMode + ) internal returns (uint _amt) { + if (amt > 0) { + bool isEth = address(token) == wethAddr; + + _amt = amt == uint(-1) ? getPaybackBalanceV2(aaveData, address(token), rateMode) : amt; + + convertEthToWeth(isEth, token, _amt); + + token.approve(address(aave), _amt); + + aave.repay(address(token), _amt, rateMode, address(this)); + } + } + + function _aaveV2Payback( + AaveV2PaybackData memory data + ) internal { + for (uint i = 0; i < data.length; i++) { + _aaveV2PaybackOne( + data.aave, + data.aaveData, + data.tokens[i], + data.amts[i], + data.rateModes[i] + ); + } + } +} diff --git a/contracts/mainnet/connectors/refinance/helpers/compound.sol b/contracts/mainnet/connectors/refinance/helpers/compound.sol new file mode 100644 index 00000000..c6bebc42 --- /dev/null +++ b/contracts/mainnet/connectors/refinance/helpers/compound.sol @@ -0,0 +1,160 @@ +pragma solidity ^0.7.0; +pragma experimental ABIEncoderV2; + +import { protocolHelpers } from "../helpers.sol"; + +import { + ComptrollerInterface, + CTokenInterface, + CompoundMappingInterface, + CETHInterface +} from "../interfaces.sol"; + +import { TokenInterface } from "../../../common/interfaces.sol"; + + + +contract CompoundHelpers is protocolHelpers { + + struct CompoundBorrowData { + uint length; + uint fee; + Protocol target; + CTokenInterface[] ctokens; + TokenInterface[] tokens; + uint[] amts; + uint[] rateModes; + } + + function _compEnterMarkets(uint length, CTokenInterface[] memory ctokens) internal { + ComptrollerInterface troller = ComptrollerInterface(getComptrollerAddress); + address[] memory _cTokens = new address[](length); + + for (uint i = 0; i < length; i++) { + _cTokens[i] = address(ctokens[i]); + } + troller.enterMarkets(_cTokens); + } + + function _compBorrowOne( + uint fee, + CTokenInterface ctoken, + TokenInterface token, + uint amt, + Protocol target, + uint rateMode + ) internal returns (uint) { + if (amt > 0) { + address _token = address(token) == wethAddr ? ethAddr : address(token); + + if (amt == uint(-1)) { + amt = getMaxBorrow(target, address(token), ctoken, rateMode); + } + + (uint feeAmt, uint _amt) = calculateFee(amt, fee, true); + + require(ctoken.borrow(_amt) == 0, "borrow-failed-collateral?"); + transferFees(_token, feeAmt); + } + return amt; + } + + function _compBorrow( + CompoundBorrowData memory data + ) internal returns (uint[] memory) { + uint[] memory finalAmts = new uint[](data.length); + for (uint i = 0; i < data.length; i++) { + finalAmts[i] = _compBorrowOne( + data.fee, + data.ctokens[i], + data.tokens[i], + data.amts[i], + data.target, + data.rateModes[i] + ); + } + return finalAmts; + } + + function _compDepositOne(uint fee, CTokenInterface ctoken, TokenInterface token, uint amt) internal { + if (amt > 0) { + address _token = address(token) == wethAddr ? ethAddr : address(token); + + (uint feeAmt, uint _amt) = calculateFee(amt, fee, false); + + if (_token != ethAddr) { + token.approve(address(ctoken), _amt); + require(ctoken.mint(_amt) == 0, "deposit-failed"); + } else { + CETHInterface(address(ctoken)).mint{value:_amt}(); + } + transferFees(_token, feeAmt); + } + } + + function _compDeposit( + uint length, + uint fee, + CTokenInterface[] memory ctokens, + TokenInterface[] memory tokens, + uint[] memory amts + ) internal { + for (uint i = 0; i < length; i++) { + _compDepositOne(fee, ctokens[i], tokens[i], amts[i]); + } + } + + function _compWithdrawOne(CTokenInterface ctoken, TokenInterface token, uint amt) internal returns (uint) { + if (amt > 0) { + if (amt == uint(-1)) { + bool isEth = address(token) == wethAddr; + uint initalBal = isEth ? address(this).balance : token.balanceOf(address(this)); + require(ctoken.redeem(ctoken.balanceOf(address(this))) == 0, "withdraw-failed"); + uint finalBal = isEth ? address(this).balance : token.balanceOf(address(this)); + amt = sub(finalBal, initalBal); + } else { + require(ctoken.redeemUnderlying(amt) == 0, "withdraw-failed"); + } + } + return amt; + } + + function _compWithdraw( + uint length, + CTokenInterface[] memory ctokens, + TokenInterface[] memory tokens, + uint[] memory amts + ) internal returns(uint[] memory) { + uint[] memory finalAmts = new uint[](length); + for (uint i = 0; i < length; i++) { + finalAmts[i] = _compWithdrawOne(ctokens[i], tokens[i], amts[i]); + } + return finalAmts; + } + + function _compPaybackOne(CTokenInterface ctoken, TokenInterface token, uint amt) internal returns (uint) { + if (amt > 0) { + if (amt == uint(-1)) { + amt = ctoken.borrowBalanceCurrent(address(this)); + } + if (address(token) != wethAddr) { + token.approve(address(ctoken), amt); + require(ctoken.repayBorrow(amt) == 0, "repay-failed."); + } else { + CETHInterface(address(ctoken)).repayBorrow{value:amt}(); + } + } + return amt; + } + + function _compPayback( + uint length, + CTokenInterface[] memory ctokens, + TokenInterface[] memory tokens, + uint[] memory amts + ) internal { + for (uint i = 0; i < length; i++) { + _compPaybackOne(ctokens[i], tokens[i], amts[i]); + } + } +} \ No newline at end of file diff --git a/contracts/mainnet/connectors/refinance/interfaces.sol b/contracts/mainnet/connectors/refinance/interfaces.sol new file mode 100644 index 00000000..a42edc61 --- /dev/null +++ b/contracts/mainnet/connectors/refinance/interfaces.sol @@ -0,0 +1,113 @@ +pragma solidity ^0.7.0; +pragma experimental ABIEncoderV2; + +// Compound Helpers +interface CTokenInterface { + function mint(uint mintAmount) external returns (uint); + function redeem(uint redeemTokens) external returns (uint); + function borrow(uint borrowAmount) external returns (uint); + function repayBorrow(uint repayAmount) external returns (uint); + + function borrowBalanceCurrent(address account) external returns (uint); + function redeemUnderlying(uint redeemAmount) external returns (uint); + + function balanceOf(address owner) external view returns (uint256 balance); +} + +interface CETHInterface { + function mint() external payable; + function repayBorrow() external payable; +} + +interface CompoundMappingInterface { + function cTokenMapping(string calldata tokenId) external view returns (address); + function getMapping(string calldata tokenId) external view returns (address, address); +} + +interface ComptrollerInterface { + function enterMarkets(address[] calldata cTokens) external returns (uint[] memory); +} +// End Compound Helpers + +// Aave v1 Helpers +interface AaveV1Interface { + function deposit(address _reserve, uint256 _amount, uint16 _referralCode) external payable; + function redeemUnderlying( + address _reserve, + address payable _user, + uint256 _amount, + uint256 _aTokenBalanceAfterRedeem + ) external; + + function setUserUseReserveAsCollateral(address _reserve, bool _useAsCollateral) external; + function getUserReserveData(address _reserve, address _user) external view returns ( + uint256 currentATokenBalance, + uint256 currentBorrowBalance, + uint256 principalBorrowBalance, + uint256 borrowRateMode, + uint256 borrowRate, + uint256 liquidityRate, + uint256 originationFee, + uint256 variableBorrowIndex, + uint256 lastUpdateTimestamp, + bool usageAsCollateralEnabled + ); + function borrow(address _reserve, uint256 _amount, uint256 _interestRateMode, uint16 _referralCode) external; + function repay(address _reserve, uint256 _amount, address payable _onBehalfOf) external payable; +} + +interface AaveV1ProviderInterface { + function getLendingPool() external view returns (address); + function getLendingPoolCore() external view returns (address); +} + +interface AaveV1CoreInterface { + function getReserveATokenAddress(address _reserve) external view returns (address); +} + +interface ATokenV1Interface { + function redeem(uint256 _amount) external; + function balanceOf(address _user) external view returns(uint256); + function principalBalanceOf(address _user) external view returns(uint256); + + function allowance(address, address) external view returns (uint); + function approve(address, uint) external; + function transfer(address, uint) external returns (bool); + function transferFrom(address, address, uint) external returns (bool); +} +// End Aave v1 Helpers + +// Aave v2 Helpers +interface AaveV2Interface { + function deposit(address _asset, uint256 _amount, address _onBehalfOf, uint16 _referralCode) external; + function withdraw(address _asset, uint256 _amount, address _to) external; + function borrow( + address _asset, + uint256 _amount, + uint256 _interestRateMode, + uint16 _referralCode, + address _onBehalfOf + ) external; + function repay(address _asset, uint256 _amount, uint256 _rateMode, address _onBehalfOf) external; + function setUserUseReserveAsCollateral(address _asset, bool _useAsCollateral) external; +} + +interface AaveV2LendingPoolProviderInterface { + function getLendingPool() external view returns (address); +} + +// Aave Protocol Data Provider +interface AaveV2DataProviderInterface { + function getUserReserveData(address _asset, address _user) external view returns ( + uint256 currentATokenBalance, + uint256 currentStableDebt, + uint256 currentVariableDebt, + uint256 principalStableDebt, + uint256 scaledVariableDebt, + uint256 stableBorrowRate, + uint256 liquidityRate, + uint40 stableRateLastUpdated, + bool usageAsCollateralEnabled + ); +} +// End Aave v2 Helpers diff --git a/contracts/mainnet/connectors/refinance/main.sol b/contracts/mainnet/connectors/refinance/main.sol new file mode 100644 index 00000000..bafbb69a --- /dev/null +++ b/contracts/mainnet/connectors/refinance/main.sol @@ -0,0 +1,251 @@ +pragma solidity ^0.7.0; +pragma experimental ABIEncoderV2; + +/** + * @title Refinance. + * @dev Refinancing. + */ + +import { TokenInterface } from "../../common/interfaces.sol"; + +import { + AaveV1ProviderInterface, + AaveV1Interface, + AaveV1CoreInterface, + AaveV2LendingPoolProviderInterface, + AaveV2DataProviderInterface, + AaveV2Interface, + ComptrollerInterface, + CTokenInterface, + CompoundMappingInterface +} from "./interfaces.sol"; + + +import { AaveV1Helpers } from "./helpers/aaveV1.sol"; +import { AaveV2Helpers } from "./helpers/aaveV2.sol"; +import { CompoundHelpers } from "./helpers/compound.sol"; + + +contract RefinanceResolver is CompoundHelpers, AaveV1Helpers, AaveV2Helpers { + + struct RefinanceData { + Protocol source; + Protocol target; + uint collateralFee; + uint debtFee; + address[] tokens; + string[] ctokenIds; + uint[] borrowAmts; + uint[] withdrawAmts; + uint[] borrowRateModes; + uint[] paybackRateModes; + } + + /** + * @dev Refinance + * @notice Refinancing between AaveV1, AaveV2 and Compound + * @param data refinance data. + */ + function refinance(RefinanceData calldata data) external payable { + + require(data.source != data.target, "source-and-target-unequal"); + + uint length = data.tokens.length; + + require(data.borrowAmts.length == length, "length-mismatch"); + require(data.withdrawAmts.length == length, "length-mismatch"); + require(data.borrowRateModes.length == length, "length-mismatch"); + require(data.paybackRateModes.length == length, "length-mismatch"); + require(data.ctokenIds.length == length, "length-mismatch"); + + AaveV2Interface aaveV2 = AaveV2Interface(getAaveV2Provider.getLendingPool()); + AaveV1Interface aaveV1 = AaveV1Interface(getAaveProvider.getLendingPool()); + AaveV1CoreInterface aaveCore = AaveV1CoreInterface(getAaveProvider.getLendingPoolCore()); + AaveV2DataProviderInterface aaveData = getAaveV2DataProvider; + + uint[] memory depositAmts; + uint[] memory paybackAmts; + + TokenInterface[] memory tokens = getTokenInterfaces(length, data.tokens); + CTokenInterface[] memory _ctokens = getCtokenInterfaces(length, data.ctokenIds); + + if (data.source == Protocol.Aave && data.target == Protocol.AaveV2) { + AaveV2BorrowData memory _aaveV2BorrowData; + + _aaveV2BorrowData.aave = aaveV2; + _aaveV2BorrowData.length = length; + _aaveV2BorrowData.fee = data.debtFee; + _aaveV2BorrowData.target = data.source; + _aaveV2BorrowData.tokens = tokens; + _aaveV2BorrowData.ctokens = _ctokens; + _aaveV2BorrowData.amts = data.borrowAmts; + _aaveV2BorrowData.rateModes = data.borrowRateModes; + + paybackAmts = _aaveV2Borrow(_aaveV2BorrowData); + _aaveV1Payback(aaveV1, aaveCore, length, tokens, paybackAmts); + depositAmts = _aaveV1Withdraw(aaveV1, aaveCore, length, tokens, data.withdrawAmts); + _aaveV2Deposit(aaveV2, aaveData, length, data.collateralFee, tokens, depositAmts); + } else if (data.source == Protocol.Aave && data.target == Protocol.Compound) { + _compEnterMarkets(length, _ctokens); + + CompoundBorrowData memory _compoundBorrowData; + + _compoundBorrowData.length = length; + _compoundBorrowData.fee = data.debtFee; + _compoundBorrowData.target = data.source; + _compoundBorrowData.ctokens = _ctokens; + _compoundBorrowData.tokens = tokens; + _compoundBorrowData.amts = data.borrowAmts; + _compoundBorrowData.rateModes = data.borrowRateModes; + + paybackAmts = _compBorrow(_compoundBorrowData); + + _aaveV1Payback(aaveV1, aaveCore, length, tokens, paybackAmts); + depositAmts = _aaveV1Withdraw(aaveV1, aaveCore, length, tokens, data.withdrawAmts); + _compDeposit(length, data.collateralFee, _ctokens, tokens, depositAmts); + } else if (data.source == Protocol.AaveV2 && data.target == Protocol.Aave) { + + AaveV1BorrowData memory _aaveV1BorrowData; + AaveV2PaybackData memory _aaveV2PaybackData; + AaveV2WithdrawData memory _aaveV2WithdrawData; + + { + _aaveV1BorrowData.aave = aaveV1; + _aaveV1BorrowData.length = length; + _aaveV1BorrowData.fee = data.debtFee; + _aaveV1BorrowData.target = data.source; + _aaveV1BorrowData.tokens = tokens; + _aaveV1BorrowData.ctokens = _ctokens; + _aaveV1BorrowData.amts = data.borrowAmts; + _aaveV1BorrowData.borrowRateModes = data.borrowRateModes; + _aaveV1BorrowData.paybackRateModes = data.paybackRateModes; + + paybackAmts = _aaveV1Borrow(_aaveV1BorrowData); + } + + { + _aaveV2PaybackData.aave = aaveV2; + _aaveV2PaybackData.aaveData = aaveData; + _aaveV2PaybackData.length = length; + _aaveV2PaybackData.tokens = tokens; + _aaveV2PaybackData.amts = paybackAmts; + _aaveV2PaybackData.rateModes = data.paybackRateModes; + _aaveV2Payback(_aaveV2PaybackData); + } + + { + _aaveV2WithdrawData.aave = aaveV2; + _aaveV2WithdrawData.aaveData = aaveData; + _aaveV2WithdrawData.length = length; + _aaveV2WithdrawData.tokens = tokens; + _aaveV2WithdrawData.amts = data.withdrawAmts; + depositAmts = _aaveV2Withdraw(_aaveV2WithdrawData); + } + { + AaveV1DepositData memory _aaveV1DepositData; + + _aaveV1DepositData.aave = aaveV1; + _aaveV1DepositData.aaveCore = aaveCore; + _aaveV1DepositData.length = length; + _aaveV1DepositData.fee = data.collateralFee; + _aaveV1DepositData.tokens = tokens; + _aaveV1DepositData.amts = depositAmts; + + _aaveV1Deposit(_aaveV1DepositData); + } + } else if (data.source == Protocol.AaveV2 && data.target == Protocol.Compound) { + _compEnterMarkets(length, _ctokens); + + { + CompoundBorrowData memory _compoundBorrowData; + + _compoundBorrowData.length = length; + _compoundBorrowData.fee = data.debtFee; + _compoundBorrowData.target = data.source; + _compoundBorrowData.ctokens = _ctokens; + _compoundBorrowData.tokens = tokens; + _compoundBorrowData.amts = data.borrowAmts; + _compoundBorrowData.rateModes = data.borrowRateModes; + + paybackAmts = _compBorrow(_compoundBorrowData); + } + + AaveV2PaybackData memory _aaveV2PaybackData; + + _aaveV2PaybackData.aave = aaveV2; + _aaveV2PaybackData.aaveData = aaveData; + _aaveV2PaybackData.length = length; + _aaveV2PaybackData.tokens = tokens; + _aaveV2PaybackData.amts = paybackAmts; + _aaveV2PaybackData.rateModes = data.paybackRateModes; + + _aaveV2Payback(_aaveV2PaybackData); + + { + AaveV2WithdrawData memory _aaveV2WithdrawData; + + _aaveV2WithdrawData.aave = aaveV2; + _aaveV2WithdrawData.aaveData = aaveData; + _aaveV2WithdrawData.length = length; + _aaveV2WithdrawData.tokens = tokens; + _aaveV2WithdrawData.amts = data.withdrawAmts; + depositAmts = _aaveV2Withdraw(_aaveV2WithdrawData); + } + _compDeposit(length, data.collateralFee, _ctokens, tokens, depositAmts); + } else if (data.source == Protocol.Compound && data.target == Protocol.Aave) { + + AaveV1BorrowData memory _aaveV1BorrowData; + + _aaveV1BorrowData.aave = aaveV1; + _aaveV1BorrowData.length = length; + _aaveV1BorrowData.fee = data.debtFee; + _aaveV1BorrowData.target = data.source; + _aaveV1BorrowData.tokens = tokens; + _aaveV1BorrowData.ctokens = _ctokens; + _aaveV1BorrowData.amts = data.borrowAmts; + _aaveV1BorrowData.borrowRateModes = data.borrowRateModes; + _aaveV1BorrowData.paybackRateModes = data.paybackRateModes; + + paybackAmts = _aaveV1Borrow(_aaveV1BorrowData); + { + _compPayback(length, _ctokens, tokens, paybackAmts); + depositAmts = _compWithdraw(length, _ctokens, tokens, data.withdrawAmts); + } + + { + AaveV1DepositData memory _aaveV1DepositData; + + _aaveV1DepositData.aave = aaveV1; + _aaveV1DepositData.aaveCore = aaveCore; + _aaveV1DepositData.length = length; + _aaveV1DepositData.fee = data.collateralFee; + _aaveV1DepositData.tokens = tokens; + _aaveV1DepositData.amts = depositAmts; + + _aaveV1Deposit(_aaveV1DepositData); + } + } else if (data.source == Protocol.Compound && data.target == Protocol.AaveV2) { + AaveV2BorrowData memory _aaveV2BorrowData; + + _aaveV2BorrowData.aave = aaveV2; + _aaveV2BorrowData.length = length; + _aaveV2BorrowData.fee = data.debtFee; + _aaveV2BorrowData.target = data.source; + _aaveV2BorrowData.tokens = tokens; + _aaveV2BorrowData.ctokens = _ctokens; + _aaveV2BorrowData.amts = data.borrowAmts; + _aaveV2BorrowData.rateModes = data.borrowRateModes; + + paybackAmts = _aaveV2Borrow(_aaveV2BorrowData); + _compPayback(length, _ctokens, tokens, paybackAmts); + depositAmts = _compWithdraw(length, _ctokens, tokens, data.withdrawAmts); + _aaveV2Deposit(aaveV2, aaveData, length, data.collateralFee, tokens, depositAmts); + } else { + revert("invalid-options"); + } + } +} + +contract ConnectV2Refinance is RefinanceResolver { + string public name = "Refinance-v1.0"; +} \ No newline at end of file From 9fa32fb8881901aa538ae925c297c9b4f322d2e5 Mon Sep 17 00:00:00 2001 From: Thrilok Kumar Date: Wed, 23 Jun 2021 02:33:32 +0530 Subject: [PATCH 09/45] Minor fix --- contracts/mainnet/connectors/refinance/main.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/contracts/mainnet/connectors/refinance/main.sol b/contracts/mainnet/connectors/refinance/main.sol index bafbb69a..536f6162 100644 --- a/contracts/mainnet/connectors/refinance/main.sol +++ b/contracts/mainnet/connectors/refinance/main.sol @@ -46,7 +46,9 @@ contract RefinanceResolver is CompoundHelpers, AaveV1Helpers, AaveV2Helpers { * @notice Refinancing between AaveV1, AaveV2 and Compound * @param data refinance data. */ - function refinance(RefinanceData calldata data) external payable { + function refinance(RefinanceData calldata data) + external payable returns (string memory _eventName, bytes memory _eventParam) + { require(data.source != data.target, "source-and-target-unequal"); From df571ef426602bd66d0b6d6b73e60060ce1d6b55 Mon Sep 17 00:00:00 2001 From: Thrilok Kumar Date: Thu, 24 Jun 2021 07:35:53 +0530 Subject: [PATCH 10/45] Fixed stack too deep error --- .../mainnet/connectors/refinance/main.sol | 238 ++++++++++++------ 1 file changed, 162 insertions(+), 76 deletions(-) diff --git a/contracts/mainnet/connectors/refinance/main.sol b/contracts/mainnet/connectors/refinance/main.sol index 536f6162..2fa0eff6 100644 --- a/contracts/mainnet/connectors/refinance/main.sol +++ b/contracts/mainnet/connectors/refinance/main.sol @@ -41,13 +41,19 @@ contract RefinanceResolver is CompoundHelpers, AaveV1Helpers, AaveV2Helpers { uint[] paybackRateModes; } - /** - * @dev Refinance - * @notice Refinancing between AaveV1, AaveV2 and Compound - * @param data refinance data. - */ - function refinance(RefinanceData calldata data) - external payable returns (string memory _eventName, bytes memory _eventParam) + struct RefinanceInternalData { + AaveV2Interface aaveV2; + AaveV1Interface aaveV1; + AaveV1CoreInterface aaveCore; + AaveV2DataProviderInterface aaveData; + uint[] depositAmts; + uint[] paybackAmts; + TokenInterface[] tokens; + CTokenInterface[] _ctokens; + } + + function _refinance(RefinanceData calldata data) + internal returns (string memory _eventName, bytes memory _eventParam) { require(data.source != data.target, "source-and-target-unequal"); @@ -60,51 +66,91 @@ contract RefinanceResolver is CompoundHelpers, AaveV1Helpers, AaveV2Helpers { require(data.paybackRateModes.length == length, "length-mismatch"); require(data.ctokenIds.length == length, "length-mismatch"); - AaveV2Interface aaveV2 = AaveV2Interface(getAaveV2Provider.getLendingPool()); - AaveV1Interface aaveV1 = AaveV1Interface(getAaveProvider.getLendingPool()); - AaveV1CoreInterface aaveCore = AaveV1CoreInterface(getAaveProvider.getLendingPoolCore()); - AaveV2DataProviderInterface aaveData = getAaveV2DataProvider; + RefinanceInternalData memory refinanceInternalData; - uint[] memory depositAmts; - uint[] memory paybackAmts; + refinanceInternalData.aaveV2 = AaveV2Interface(getAaveV2Provider.getLendingPool()); + refinanceInternalData.aaveV1 = AaveV1Interface(getAaveProvider.getLendingPool()); + refinanceInternalData.aaveCore = AaveV1CoreInterface(getAaveProvider.getLendingPoolCore()); + refinanceInternalData.aaveData = getAaveV2DataProvider; - TokenInterface[] memory tokens = getTokenInterfaces(length, data.tokens); - CTokenInterface[] memory _ctokens = getCtokenInterfaces(length, data.ctokenIds); + refinanceInternalData.depositAmts; + refinanceInternalData.paybackAmts; + + refinanceInternalData.tokens = getTokenInterfaces(length, data.tokens); + refinanceInternalData._ctokens = getCtokenInterfaces(length, data.ctokenIds); if (data.source == Protocol.Aave && data.target == Protocol.AaveV2) { AaveV2BorrowData memory _aaveV2BorrowData; - _aaveV2BorrowData.aave = aaveV2; + _aaveV2BorrowData.aave = refinanceInternalData.aaveV2; _aaveV2BorrowData.length = length; _aaveV2BorrowData.fee = data.debtFee; _aaveV2BorrowData.target = data.source; - _aaveV2BorrowData.tokens = tokens; - _aaveV2BorrowData.ctokens = _ctokens; + _aaveV2BorrowData.tokens = refinanceInternalData.tokens; + _aaveV2BorrowData.ctokens = refinanceInternalData._ctokens; _aaveV2BorrowData.amts = data.borrowAmts; _aaveV2BorrowData.rateModes = data.borrowRateModes; - - paybackAmts = _aaveV2Borrow(_aaveV2BorrowData); - _aaveV1Payback(aaveV1, aaveCore, length, tokens, paybackAmts); - depositAmts = _aaveV1Withdraw(aaveV1, aaveCore, length, tokens, data.withdrawAmts); - _aaveV2Deposit(aaveV2, aaveData, length, data.collateralFee, tokens, depositAmts); + { + refinanceInternalData.paybackAmts = _aaveV2Borrow(_aaveV2BorrowData); + _aaveV1Payback( + refinanceInternalData.aaveV1, + refinanceInternalData.aaveCore, + length, + refinanceInternalData.tokens, + refinanceInternalData.paybackAmts + ); + refinanceInternalData.depositAmts = _aaveV1Withdraw( + refinanceInternalData.aaveV1, + refinanceInternalData.aaveCore, + length, + refinanceInternalData.tokens, + data.withdrawAmts + ); + _aaveV2Deposit( + refinanceInternalData.aaveV2, + refinanceInternalData.aaveData, + length, + data.collateralFee, + refinanceInternalData.tokens, + refinanceInternalData.depositAmts + ); + } } else if (data.source == Protocol.Aave && data.target == Protocol.Compound) { - _compEnterMarkets(length, _ctokens); + _compEnterMarkets(length, refinanceInternalData._ctokens); CompoundBorrowData memory _compoundBorrowData; _compoundBorrowData.length = length; _compoundBorrowData.fee = data.debtFee; _compoundBorrowData.target = data.source; - _compoundBorrowData.ctokens = _ctokens; - _compoundBorrowData.tokens = tokens; + _compoundBorrowData.ctokens = refinanceInternalData._ctokens; + _compoundBorrowData.tokens = refinanceInternalData.tokens; _compoundBorrowData.amts = data.borrowAmts; _compoundBorrowData.rateModes = data.borrowRateModes; - paybackAmts = _compBorrow(_compoundBorrowData); + refinanceInternalData.paybackAmts = _compBorrow(_compoundBorrowData); - _aaveV1Payback(aaveV1, aaveCore, length, tokens, paybackAmts); - depositAmts = _aaveV1Withdraw(aaveV1, aaveCore, length, tokens, data.withdrawAmts); - _compDeposit(length, data.collateralFee, _ctokens, tokens, depositAmts); + _aaveV1Payback( + refinanceInternalData.aaveV1, + refinanceInternalData.aaveCore, + length, + refinanceInternalData.tokens, + refinanceInternalData.paybackAmts + ); + refinanceInternalData.depositAmts = _aaveV1Withdraw( + refinanceInternalData.aaveV1, + refinanceInternalData.aaveCore, + length, + refinanceInternalData.tokens, + data.withdrawAmts + ); + _compDeposit( + length, + data.collateralFee, + refinanceInternalData._ctokens, + refinanceInternalData.tokens, + refinanceInternalData.depositAmts + ); } else if (data.source == Protocol.AaveV2 && data.target == Protocol.Aave) { AaveV1BorrowData memory _aaveV1BorrowData; @@ -112,51 +158,51 @@ contract RefinanceResolver is CompoundHelpers, AaveV1Helpers, AaveV2Helpers { AaveV2WithdrawData memory _aaveV2WithdrawData; { - _aaveV1BorrowData.aave = aaveV1; + _aaveV1BorrowData.aave = refinanceInternalData.aaveV1; _aaveV1BorrowData.length = length; _aaveV1BorrowData.fee = data.debtFee; _aaveV1BorrowData.target = data.source; - _aaveV1BorrowData.tokens = tokens; - _aaveV1BorrowData.ctokens = _ctokens; + _aaveV1BorrowData.tokens = refinanceInternalData.tokens; + _aaveV1BorrowData.ctokens = refinanceInternalData._ctokens; _aaveV1BorrowData.amts = data.borrowAmts; _aaveV1BorrowData.borrowRateModes = data.borrowRateModes; _aaveV1BorrowData.paybackRateModes = data.paybackRateModes; - paybackAmts = _aaveV1Borrow(_aaveV1BorrowData); + refinanceInternalData.paybackAmts = _aaveV1Borrow(_aaveV1BorrowData); } { - _aaveV2PaybackData.aave = aaveV2; - _aaveV2PaybackData.aaveData = aaveData; + _aaveV2PaybackData.aave = refinanceInternalData.aaveV2; + _aaveV2PaybackData.aaveData = refinanceInternalData.aaveData; _aaveV2PaybackData.length = length; - _aaveV2PaybackData.tokens = tokens; - _aaveV2PaybackData.amts = paybackAmts; + _aaveV2PaybackData.tokens = refinanceInternalData.tokens; + _aaveV2PaybackData.amts = refinanceInternalData.paybackAmts; _aaveV2PaybackData.rateModes = data.paybackRateModes; _aaveV2Payback(_aaveV2PaybackData); } { - _aaveV2WithdrawData.aave = aaveV2; - _aaveV2WithdrawData.aaveData = aaveData; + _aaveV2WithdrawData.aave = refinanceInternalData.aaveV2; + _aaveV2WithdrawData.aaveData = refinanceInternalData.aaveData; _aaveV2WithdrawData.length = length; - _aaveV2WithdrawData.tokens = tokens; + _aaveV2WithdrawData.tokens = refinanceInternalData.tokens; _aaveV2WithdrawData.amts = data.withdrawAmts; - depositAmts = _aaveV2Withdraw(_aaveV2WithdrawData); + refinanceInternalData.depositAmts = _aaveV2Withdraw(_aaveV2WithdrawData); } { AaveV1DepositData memory _aaveV1DepositData; - _aaveV1DepositData.aave = aaveV1; - _aaveV1DepositData.aaveCore = aaveCore; + _aaveV1DepositData.aave = refinanceInternalData.aaveV1; + _aaveV1DepositData.aaveCore = refinanceInternalData.aaveCore; _aaveV1DepositData.length = length; _aaveV1DepositData.fee = data.collateralFee; - _aaveV1DepositData.tokens = tokens; - _aaveV1DepositData.amts = depositAmts; + _aaveV1DepositData.tokens = refinanceInternalData.tokens; + _aaveV1DepositData.amts = refinanceInternalData.depositAmts; _aaveV1Deposit(_aaveV1DepositData); } } else if (data.source == Protocol.AaveV2 && data.target == Protocol.Compound) { - _compEnterMarkets(length, _ctokens); + _compEnterMarkets(length, refinanceInternalData._ctokens); { CompoundBorrowData memory _compoundBorrowData; @@ -164,21 +210,21 @@ contract RefinanceResolver is CompoundHelpers, AaveV1Helpers, AaveV2Helpers { _compoundBorrowData.length = length; _compoundBorrowData.fee = data.debtFee; _compoundBorrowData.target = data.source; - _compoundBorrowData.ctokens = _ctokens; - _compoundBorrowData.tokens = tokens; + _compoundBorrowData.ctokens = refinanceInternalData._ctokens; + _compoundBorrowData.tokens = refinanceInternalData.tokens; _compoundBorrowData.amts = data.borrowAmts; _compoundBorrowData.rateModes = data.borrowRateModes; - paybackAmts = _compBorrow(_compoundBorrowData); + refinanceInternalData.paybackAmts = _compBorrow(_compoundBorrowData); } AaveV2PaybackData memory _aaveV2PaybackData; - _aaveV2PaybackData.aave = aaveV2; - _aaveV2PaybackData.aaveData = aaveData; + _aaveV2PaybackData.aave = refinanceInternalData.aaveV2; + _aaveV2PaybackData.aaveData = refinanceInternalData.aaveData; _aaveV2PaybackData.length = length; - _aaveV2PaybackData.tokens = tokens; - _aaveV2PaybackData.amts = paybackAmts; + _aaveV2PaybackData.tokens = refinanceInternalData.tokens; + _aaveV2PaybackData.amts = refinanceInternalData.paybackAmts; _aaveV2PaybackData.rateModes = data.paybackRateModes; _aaveV2Payback(_aaveV2PaybackData); @@ -186,66 +232,106 @@ contract RefinanceResolver is CompoundHelpers, AaveV1Helpers, AaveV2Helpers { { AaveV2WithdrawData memory _aaveV2WithdrawData; - _aaveV2WithdrawData.aave = aaveV2; - _aaveV2WithdrawData.aaveData = aaveData; + _aaveV2WithdrawData.aave = refinanceInternalData.aaveV2; + _aaveV2WithdrawData.aaveData = refinanceInternalData.aaveData; _aaveV2WithdrawData.length = length; - _aaveV2WithdrawData.tokens = tokens; + _aaveV2WithdrawData.tokens = refinanceInternalData.tokens; _aaveV2WithdrawData.amts = data.withdrawAmts; - depositAmts = _aaveV2Withdraw(_aaveV2WithdrawData); + refinanceInternalData.depositAmts = _aaveV2Withdraw(_aaveV2WithdrawData); } - _compDeposit(length, data.collateralFee, _ctokens, tokens, depositAmts); + _compDeposit( + length, + data.collateralFee, + refinanceInternalData._ctokens, + refinanceInternalData.tokens, + refinanceInternalData.depositAmts + ); } else if (data.source == Protocol.Compound && data.target == Protocol.Aave) { AaveV1BorrowData memory _aaveV1BorrowData; - _aaveV1BorrowData.aave = aaveV1; + _aaveV1BorrowData.aave = refinanceInternalData.aaveV1; _aaveV1BorrowData.length = length; _aaveV1BorrowData.fee = data.debtFee; _aaveV1BorrowData.target = data.source; - _aaveV1BorrowData.tokens = tokens; - _aaveV1BorrowData.ctokens = _ctokens; + _aaveV1BorrowData.tokens = refinanceInternalData.tokens; + _aaveV1BorrowData.ctokens = refinanceInternalData._ctokens; _aaveV1BorrowData.amts = data.borrowAmts; _aaveV1BorrowData.borrowRateModes = data.borrowRateModes; _aaveV1BorrowData.paybackRateModes = data.paybackRateModes; - paybackAmts = _aaveV1Borrow(_aaveV1BorrowData); + refinanceInternalData.paybackAmts = _aaveV1Borrow(_aaveV1BorrowData); { - _compPayback(length, _ctokens, tokens, paybackAmts); - depositAmts = _compWithdraw(length, _ctokens, tokens, data.withdrawAmts); + _compPayback( + length, + refinanceInternalData._ctokens, + refinanceInternalData.tokens, + refinanceInternalData.paybackAmts + ); + refinanceInternalData.depositAmts = _compWithdraw( + length, + refinanceInternalData._ctokens, + refinanceInternalData.tokens, + data.withdrawAmts + ); } { AaveV1DepositData memory _aaveV1DepositData; - _aaveV1DepositData.aave = aaveV1; - _aaveV1DepositData.aaveCore = aaveCore; + _aaveV1DepositData.aave = refinanceInternalData.aaveV1; + _aaveV1DepositData.aaveCore = refinanceInternalData.aaveCore; _aaveV1DepositData.length = length; _aaveV1DepositData.fee = data.collateralFee; - _aaveV1DepositData.tokens = tokens; - _aaveV1DepositData.amts = depositAmts; + _aaveV1DepositData.tokens = refinanceInternalData.tokens; + _aaveV1DepositData.amts = refinanceInternalData.depositAmts; _aaveV1Deposit(_aaveV1DepositData); } } else if (data.source == Protocol.Compound && data.target == Protocol.AaveV2) { AaveV2BorrowData memory _aaveV2BorrowData; - _aaveV2BorrowData.aave = aaveV2; + _aaveV2BorrowData.aave = refinanceInternalData.aaveV2; _aaveV2BorrowData.length = length; _aaveV2BorrowData.fee = data.debtFee; _aaveV2BorrowData.target = data.source; - _aaveV2BorrowData.tokens = tokens; - _aaveV2BorrowData.ctokens = _ctokens; + _aaveV2BorrowData.tokens = refinanceInternalData.tokens; + _aaveV2BorrowData.ctokens = refinanceInternalData._ctokens; _aaveV2BorrowData.amts = data.borrowAmts; _aaveV2BorrowData.rateModes = data.borrowRateModes; - paybackAmts = _aaveV2Borrow(_aaveV2BorrowData); - _compPayback(length, _ctokens, tokens, paybackAmts); - depositAmts = _compWithdraw(length, _ctokens, tokens, data.withdrawAmts); - _aaveV2Deposit(aaveV2, aaveData, length, data.collateralFee, tokens, depositAmts); + refinanceInternalData.paybackAmts = _aaveV2Borrow(_aaveV2BorrowData); + _compPayback(length, refinanceInternalData._ctokens, refinanceInternalData.tokens, refinanceInternalData.paybackAmts); + refinanceInternalData.depositAmts = _compWithdraw( + length, + refinanceInternalData._ctokens, + refinanceInternalData.tokens, + data.withdrawAmts + ); + _aaveV2Deposit( + refinanceInternalData.aaveV2, + refinanceInternalData.aaveData, + length, + data.collateralFee, + refinanceInternalData.tokens, + refinanceInternalData.depositAmts + ); } else { revert("invalid-options"); } } + + + + /** + * @dev Refinance + * @notice Refinancing between AaveV1, AaveV2 and Compound + * @param data refinance data. + */ + function refinance(RefinanceData calldata data) + external payable returns (string memory _eventName, bytes memory _eventParam) { + (_eventName, _eventParam) = _refinance(data); + } } contract ConnectV2Refinance is RefinanceResolver { From 4d55758394fbf2d20fb23b572b6294f4b8b9c2be Mon Sep 17 00:00:00 2001 From: Thrilok Kumar Date: Thu, 24 Jun 2021 07:44:59 +0530 Subject: [PATCH 11/45] Updated connector list --- docs/connectors.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/connectors.json b/docs/connectors.json index 1f2634a7..587e5b8e 100644 --- a/docs/connectors.json +++ b/docs/connectors.json @@ -19,7 +19,8 @@ "COMPOUND-IMPORT-B": "0xdA101870ca6136539628F28041E1B55baf4EB6C0", "INSTAPOOL-A": "0x5806Af7AB22E2916fA582Ff05731Bf7C682387B2", "MAKERDAO-CLAIM-A": "0x2f8cBE650af98602a215b6482F2aD60893C5A4E8", - "WETH-A": "0x22075fa719eFb02Ca3cF298AFa9C974B7465E5D3" + "WETH-A": "0x22075fa719eFb02Ca3cF298AFa9C974B7465E5D3", + "REFINANCE-A": "0x9eA34bE6dA51aa9F6408FeA79c946FDCFA424442" }, "137" : { "AAVE-V2-A": "0xE84d8010Afc3663919F44685cB53ED88866da3eE", From 1cce1fffeada8401bb45b59e725f5f9f7fdf3c88 Mon Sep 17 00:00:00 2001 From: Thrilok Kumar Date: Thu, 24 Jun 2021 16:58:01 +0530 Subject: [PATCH 12/45] Added flashloan testcases --- hardhat.config.js | 2 +- .../constant/abi/connectors/instapool.json | 1 + scripts/constant/abis.js | 1 + scripts/constant/addresses.js | 5 +- scripts/encodeFlashcastData.js | 16 +++ test/instapool/instapool.test.js | 109 ++++++++++++++++++ 6 files changed, 131 insertions(+), 3 deletions(-) create mode 100644 scripts/constant/abi/connectors/instapool.json create mode 100644 scripts/encodeFlashcastData.js create mode 100644 test/instapool/instapool.test.js diff --git a/hardhat.config.js b/hardhat.config.js index 45b17f9d..a3d99c31 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -52,7 +52,7 @@ module.exports = { hardhat: { forking: { url: `https://eth-mainnet.alchemyapi.io/v2/${ALCHEMY_ID}`, - blockNumber: 12433781, + blockNumber: 12696000, }, blockGasLimit: 12000000, }, diff --git a/scripts/constant/abi/connectors/instapool.json b/scripts/constant/abi/connectors/instapool.json new file mode 100644 index 00000000..75a04f42 --- /dev/null +++ b/scripts/constant/abi/connectors/instapool.json @@ -0,0 +1 @@ +[{"type":"event","name":"LogFlashBorrow","inputs":[{"type":"address","name":"token","internalType":"address","indexed":false},{"type":"uint256","name":"tokenAmt","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"LogFlashMultiBorrow","inputs":[{"type":"address[]","name":"token","internalType":"address[]","indexed":false},{"type":"uint256[]","name":"tokenAmts","internalType":"uint256[]","indexed":false}],"anonymous":false},{"type":"event","name":"LogFlashMultiPayback","inputs":[{"type":"address[]","name":"token","internalType":"address[]","indexed":false},{"type":"uint256[]","name":"tokenAmts","internalType":"uint256[]","indexed":false}],"anonymous":false},{"type":"event","name":"LogFlashPayback","inputs":[{"type":"address","name":"token","internalType":"address","indexed":false},{"type":"uint256","name":"tokenAmt","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"function","stateMutability":"payable","outputs":[{"type":"string","name":"_eventName","internalType":"string"},{"type":"bytes","name":"_eventParam","internalType":"bytes"}],"name":"flashBorrowAndCast","inputs":[{"type":"address","name":"token","internalType":"address"},{"type":"uint256","name":"amt","internalType":"uint256"},{"type":"uint256","name":"route","internalType":"uint256"},{"type":"bytes","name":"data","internalType":"bytes"}]},{"type":"function","stateMutability":"payable","outputs":[{"type":"string","name":"_eventName","internalType":"string"},{"type":"bytes","name":"_eventParam","internalType":"bytes"}],"name":"flashMultiBorrowAndCast","inputs":[{"type":"address[]","name":"tokens","internalType":"address[]"},{"type":"uint256[]","name":"amts","internalType":"uint256[]"},{"type":"uint256","name":"route","internalType":"uint256"},{"type":"bytes","name":"data","internalType":"bytes"}]},{"type":"function","stateMutability":"payable","outputs":[{"type":"string","name":"_eventName","internalType":"string"},{"type":"bytes","name":"_eventParam","internalType":"bytes"}],"name":"flashMultiPayback","inputs":[{"type":"address[]","name":"tokens","internalType":"address[]"},{"type":"uint256[]","name":"amts","internalType":"uint256[]"},{"type":"uint256[]","name":"getId","internalType":"uint256[]"},{"type":"uint256[]","name":"setId","internalType":"uint256[]"}]},{"type":"function","stateMutability":"payable","outputs":[{"type":"string","name":"_eventName","internalType":"string"},{"type":"bytes","name":"_eventParam","internalType":"bytes"}],"name":"flashPayback","inputs":[{"type":"address","name":"token","internalType":"address"},{"type":"uint256","name":"amt","internalType":"uint256"},{"type":"uint256","name":"getId","internalType":"uint256"},{"type":"uint256","name":"setId","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"contract InstaFlashV2Interface"}],"name":"instaPool","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"string","name":"","internalType":"string"}],"name":"name","inputs":[]}] \ No newline at end of file diff --git a/scripts/constant/abis.js b/scripts/constant/abis.js index c25bd350..3ed03f80 100644 --- a/scripts/constant/abis.js +++ b/scripts/constant/abis.js @@ -6,6 +6,7 @@ module.exports = { connectors: { basic: require("./abi/connectors/basic.json"), auth: require("./abi/connectors/auth.json"), + "INSTAPOOL-A": require("./abi/connectors/instapool.json"), }, basic: { erc20: require("./abi/basics/erc20.json"), diff --git a/scripts/constant/addresses.js b/scripts/constant/addresses.js index ead09c40..a1cb7d45 100644 --- a/scripts/constant/addresses.js +++ b/scripts/constant/addresses.js @@ -2,10 +2,11 @@ module.exports = { connectors: { basic: "0xe5398f279175962E56fE4c5E0b62dc7208EF36c6", auth: "0xd1aff9f2acf800c876c409100d6f39aea93fc3d9", + "INSTAPOOL-A": "0x5806af7ab22e2916fa582ff05731bf7c682387b2" }, core: { - connectorsV2: "0xFE2390DAD597594439f218190fC2De40f9Cf1179", - instaIndex: "0x2971AdFa57b20E5a416aE5a708A8655A9c74f723" + connectorsV2: "0x97b0B3A8bDeFE8cB9563a3c610019Ad10DB8aD11", + instaIndex: "0x2971AdFa57b20E5a416aE5a708A8655A9c74f723", } }; \ No newline at end of file diff --git a/scripts/encodeFlashcastData.js b/scripts/encodeFlashcastData.js new file mode 100644 index 00000000..a674c1af --- /dev/null +++ b/scripts/encodeFlashcastData.js @@ -0,0 +1,16 @@ +const abis = require("./constant/abis"); +const addresses = require("./constant/addresses"); +const { web3 } = hre; + +const encodeSpells = require("./encodeSpells.js") + + +module.exports = function (spells) { + const encodeSpellsData = encodeSpells(spells); + const targetType = "string[]"; + let argTypes = [targetType, "bytes[]"]; + return web3.eth.abi.encodeParameters(argTypes, [ + encodeSpellsData[0], + encodeSpellsData[1], + ]); +}; diff --git a/test/instapool/instapool.test.js b/test/instapool/instapool.test.js new file mode 100644 index 00000000..c0a0fb81 --- /dev/null +++ b/test/instapool/instapool.test.js @@ -0,0 +1,109 @@ +const { expect } = require("chai"); +const hre = require("hardhat"); +const { web3, deployments, waffle, ethers } = hre; +const { provider, deployContract } = waffle + +const deployAndEnableConnector = require("../../scripts/deployAndEnableConnector.js") +const buildDSAv2 = require("../../scripts/buildDSAv2") +const encodeSpells = require("../../scripts/encodeSpells.js") +const encodeFlashcastData = require("../../scripts/encodeFlashcastData.js") +const getMasterSigner = require("../../scripts/getMasterSigner") + +const addresses = require("../../scripts/constant/addresses"); +const abis = require("../../scripts/constant/abis"); +const constants = require("../../scripts/constant/constant"); +const tokens = require("../../scripts/constant/tokens"); + +const connectV2CompoundArtifacts = require("../../artifacts/contracts/mainnet/connectors/compound/main.sol/ConnectV2Compound.json") + +describe("Instapool", function () { + const connectorName = "COMPOUND-TEST-A" + + let dsaWallet0 + let masterSigner; + let instaConnectorsV2; + let connector; + + const wallets = provider.getWallets() + const [wallet0, wallet1, wallet2, wallet3] = wallets + before(async () => { + masterSigner = await getMasterSigner(wallet3) + instaConnectorsV2 = await ethers.getContractAt(abis.core.connectorsV2, addresses.core.connectorsV2); + connector = await deployAndEnableConnector({ + connectorName, + contractArtifact: connectV2CompoundArtifacts, + signer: masterSigner, + connectors: instaConnectorsV2 + }) + console.log("Connector address", connector.address) + }) + + it("Should have contracts deployed.", async function () { + expect(!!instaConnectorsV2.address).to.be.true; + expect(!!connector.address).to.be.true; + expect(!!masterSigner.address).to.be.true; + }); + + describe("DSA wallet setup", function () { + it("Should build DSA v2", async function () { + dsaWallet0 = await buildDSAv2(wallet0.address) + expect(!!dsaWallet0.address).to.be.true; + }); + + it("Deposit ETH into DSA wallet", async function () { + await wallet0.sendTransaction({ + to: dsaWallet0.address, + value: ethers.utils.parseEther("10") + }); + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte(ethers.utils.parseEther("10")); + }); + }); + + describe("Main", function () { + + it("Should deposit ETH in Compound", async function () { + const amount = ethers.utils.parseEther("1") // 1 ETH + const flashloanAmount = ethers.utils.parseEther("100") // 100 ETH + const ethAddress = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + + const IdOne = "2878734423" + const IdTwo = "783243246" + + const spells = [ + { + connector: connectorName, + method: "deposit", + args: ["ETH-A", flashloanAmount, 0, IdOne] + }, + { + connector: connectorName, + method: "withdraw", + args: ["ETH-A", amount, IdOne, IdTwo] + }, + { + connector: "INSTAPOOL-A", + method: "flashPayback", + args: [ethAddress, flashloanAmount, IdTwo, 0], + } + ] + + const calldata = encodeFlashcastData(spells); + + const spells2 = [ + { + connector: "INSTAPOOL-A", + method: "flashBorrowAndCast", + args: [ + "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", + flashloanAmount, + 0, // route + calldata, + ], + } + ] + + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells2), wallet1.address) + const receipt = await tx.wait() + }); + }) +}) From d26e38e7cd8a0fba2099255461b7bdb7c8f3015d Mon Sep 17 00:00:00 2001 From: Thrilok kumar Date: Thu, 24 Jun 2021 17:08:42 +0530 Subject: [PATCH 13/45] Update instapool.test.js --- test/instapool/instapool.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/instapool/instapool.test.js b/test/instapool/instapool.test.js index c0a0fb81..fa211e8d 100644 --- a/test/instapool/instapool.test.js +++ b/test/instapool/instapool.test.js @@ -61,7 +61,7 @@ describe("Instapool", function () { describe("Main", function () { - it("Should deposit ETH in Compound", async function () { + it("Should take 100 ETH flashloan from Instapool", async function () { const amount = ethers.utils.parseEther("1") // 1 ETH const flashloanAmount = ethers.utils.parseEther("100") // 100 ETH const ethAddress = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" From 17fc4f6acba37d8ea1be1cbd7814ea1a32b3db4a Mon Sep 17 00:00:00 2001 From: Thrilok kumar Date: Fri, 25 Jun 2021 00:24:05 +0530 Subject: [PATCH 14/45] Update main.sol --- contracts/mainnet/connectors/weth/main.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/mainnet/connectors/weth/main.sol b/contracts/mainnet/connectors/weth/main.sol index cbab1c6b..9a5f1444 100644 --- a/contracts/mainnet/connectors/weth/main.sol +++ b/contracts/mainnet/connectors/weth/main.sol @@ -1,8 +1,8 @@ pragma solidity ^0.7.0; /** - * @title Basic. - * @dev Deposit & Withdraw from DSA. + * @title WETH. + * @dev Wrap and Unwrap WETH. */ import { DSMath } from "../../common/math.sol"; From 1a9ab0ac702a253cf009451e4d6501001800e48c Mon Sep 17 00:00:00 2001 From: Thrilok Kumar Date: Fri, 25 Jun 2021 01:25:04 +0530 Subject: [PATCH 15/45] Fixed approve(token.approve => approve()) --- contracts/mainnet/common/basic.sol | 11 ++++++++++- contracts/mainnet/connectors/1inch/main.sol | 2 +- contracts/mainnet/connectors/1proto/main.sol | 4 ++-- contracts/mainnet/connectors/aave/v1/main.sol | 4 ++-- contracts/mainnet/connectors/aave/v2-amm/main.sol | 4 ++-- contracts/mainnet/connectors/aave/v2/main.sol | 4 ++-- contracts/mainnet/connectors/compound/main.sol | 8 ++++---- contracts/mainnet/connectors/cream/main.sol | 8 ++++---- contracts/mainnet/connectors/dydx/main.sol | 12 ++++++------ contracts/mainnet/connectors/erc20_staking/main.sol | 2 +- .../mainnet/connectors/guniswap_v3_erc20/main.sol | 12 ++++++------ .../connectors/guniswap_v3_erc20_staking/main.sol | 2 +- contracts/mainnet/connectors/kyber/main.sol | 2 +- contracts/mainnet/connectors/makerdao/main.sol | 8 ++++---- contracts/mainnet/connectors/oasis/main.sol | 4 ++-- contracts/mainnet/connectors/polygon-bridge/main.sol | 4 ++-- .../mainnet/connectors/refinance/helpers/aaveV1.sol | 4 ++-- .../mainnet/connectors/refinance/helpers/aaveV2.sol | 4 ++-- .../connectors/refinance/helpers/compound.sol | 4 ++-- contracts/mainnet/connectors/reflexer/main.sol | 6 +++--- contracts/mainnet/connectors/uniswap/helpers.sol | 6 +++--- contracts/mainnet/connectors/uniswap/main.sol | 4 ++-- contracts/mainnet/connectors/weth/main.sol | 2 +- contracts/mainnet/connectors_old/1inch.sol | 6 +++--- contracts/polygon/common/basic.sol | 11 ++++++++++- contracts/polygon/connectors/aave/v2/main.sol | 4 ++-- contracts/polygon/connectors/paraswap/helpers.sol | 2 +- 27 files changed, 81 insertions(+), 63 deletions(-) diff --git a/contracts/mainnet/common/basic.sol b/contracts/mainnet/common/basic.sol index fb0882b5..febeb784 100644 --- a/contracts/mainnet/common/basic.sol +++ b/contracts/mainnet/common/basic.sol @@ -27,6 +27,15 @@ abstract contract Basic is DSMath, Stores { return abi.encode(eventName, eventParam); } + function approve(TokenInterface token, address spender, uint256 amount) internal { + try token.approve(spender, amount) { + + } catch { + token.approve(spender, 0); + token.approve(spender, amount); + } + } + function changeEthAddress(address buy, address sell) internal pure returns(TokenInterface _buy, TokenInterface _sell){ _buy = buy == ethAddr ? TokenInterface(wethAddr) : TokenInterface(buy); _sell = sell == ethAddr ? TokenInterface(wethAddr) : TokenInterface(sell); @@ -38,7 +47,7 @@ abstract contract Basic is DSMath, Stores { function convertWethToEth(bool isEth, TokenInterface token, uint amount) internal { if(isEth) { - token.approve(address(token), amount); + approve(token, address(token), amount); token.withdraw(amount); } } diff --git a/contracts/mainnet/connectors/1inch/main.sol b/contracts/mainnet/connectors/1inch/main.sol index 31901334..cc3b7384 100644 --- a/contracts/mainnet/connectors/1inch/main.sol +++ b/contracts/mainnet/connectors/1inch/main.sol @@ -74,7 +74,7 @@ abstract contract OneInchResolverHelpers is OneInchResolver { if (address(_sellAddr) == ethAddr) { ethAmt = oneInchData._sellAmt; } else { - TokenInterface(_sellAddr).approve(oneInchAddr, oneInchData._sellAmt); + approve(TokenInterface(_sellAddr), oneInchAddr, oneInchData._sellAmt); } require(checkOneInchSig(oneInchData.callData), "Not-swap-function"); diff --git a/contracts/mainnet/connectors/1proto/main.sol b/contracts/mainnet/connectors/1proto/main.sol index aec46e0d..f3c2f380 100644 --- a/contracts/mainnet/connectors/1proto/main.sol +++ b/contracts/mainnet/connectors/1proto/main.sol @@ -30,7 +30,7 @@ abstract contract OneProtoResolver is Helpers, Events { if (address(_sellAddr) == ethAddr) { ethAmt = _sellAmt; } else { - _sellAddr.approve(address(oneProto), _sellAmt); + approve(_sellAddr, address(oneProto), _sellAmt); } @@ -64,7 +64,7 @@ abstract contract OneProtoResolver is Helpers, Events { if (address(_sellAddr) == ethAddr) { ethAmt = _sellAmt; } else { - _sellAddr.approve(address(oneProto), _sellAmt); + approve(_sellAddr, address(oneProto), _sellAmt); } uint initalBal = getTokenBal(_buyAddr); diff --git a/contracts/mainnet/connectors/aave/v1/main.sol b/contracts/mainnet/connectors/aave/v1/main.sol index 3dd63cc6..0354c0e8 100644 --- a/contracts/mainnet/connectors/aave/v1/main.sol +++ b/contracts/mainnet/connectors/aave/v1/main.sol @@ -37,7 +37,7 @@ abstract contract AaveResolver is Events, Helpers { } else { TokenInterface tokenContract = TokenInterface(token); _amt = _amt == uint(-1) ? tokenContract.balanceOf(address(this)) : _amt; - tokenContract.approve(aaveProvider.getLendingPoolCore(), _amt); + approve(tokenContract, aaveProvider.getLendingPoolCore(), _amt); } aave.deposit{value: ethAmt}(token, _amt, referralCode); @@ -129,7 +129,7 @@ abstract contract AaveResolver is Events, Helpers { if (token == ethAddr) { ethAmt = _amt; } else { - TokenInterface(token).approve(aaveProvider.getLendingPoolCore(), _amt); + approve(TokenInterface(token), aaveProvider.getLendingPoolCore(), _amt); } aave.repay{value: ethAmt}(token, _amt, payable(address(this))); diff --git a/contracts/mainnet/connectors/aave/v2-amm/main.sol b/contracts/mainnet/connectors/aave/v2-amm/main.sol index 996976a9..4c2f624e 100644 --- a/contracts/mainnet/connectors/aave/v2-amm/main.sol +++ b/contracts/mainnet/connectors/aave/v2-amm/main.sol @@ -42,7 +42,7 @@ abstract contract AaveResolver is Events, Helpers { _amt = _amt == uint(-1) ? tokenContract.balanceOf(address(this)) : _amt; } - tokenContract.approve(address(aave), _amt); + approve(tokenContract, address(aave), _amt); aave.deposit(_token, _amt, address(this), referralCode); @@ -153,7 +153,7 @@ abstract contract AaveResolver is Events, Helpers { if (isEth) convertEthToWeth(isEth, tokenContract, _amt); - tokenContract.approve(address(aave), _amt); + approve(tokenContract, address(aave), _amt); aave.repay(_token, _amt, rateMode, address(this)); diff --git a/contracts/mainnet/connectors/aave/v2/main.sol b/contracts/mainnet/connectors/aave/v2/main.sol index 7a37ad79..4b9f4656 100644 --- a/contracts/mainnet/connectors/aave/v2/main.sol +++ b/contracts/mainnet/connectors/aave/v2/main.sol @@ -42,7 +42,7 @@ abstract contract AaveResolver is Events, Helpers { _amt = _amt == uint(-1) ? tokenContract.balanceOf(address(this)) : _amt; } - tokenContract.approve(address(aave), _amt); + approve(tokenContract, address(aave), _amt); aave.deposit(_token, _amt, address(this), referralCode); @@ -153,7 +153,7 @@ abstract contract AaveResolver is Events, Helpers { if (isEth) convertEthToWeth(isEth, tokenContract, _amt); - tokenContract.approve(address(aave), _amt); + approve(tokenContract, address(aave), _amt); aave.repay(_token, _amt, rateMode, address(this)); diff --git a/contracts/mainnet/connectors/compound/main.sol b/contracts/mainnet/connectors/compound/main.sol index fa4a653c..01ee5fc3 100644 --- a/contracts/mainnet/connectors/compound/main.sol +++ b/contracts/mainnet/connectors/compound/main.sol @@ -40,7 +40,7 @@ abstract contract CompoundResolver is Events, Helpers { } else { TokenInterface tokenContract = TokenInterface(token); _amt = _amt == uint(-1) ? tokenContract.balanceOf(address(this)) : _amt; - tokenContract.approve(cToken, _amt); + approve(tokenContract, cToken, _amt); require(CTokenInterface(cToken).mint(_amt) == 0, "deposit-failed"); } setUint(setId, _amt); @@ -196,7 +196,7 @@ abstract contract CompoundResolver is Events, Helpers { } else { TokenInterface tokenContract = TokenInterface(token); require(tokenContract.balanceOf(address(this)) >= _amt, "not-enough-token"); - tokenContract.approve(cToken, _amt); + approve(tokenContract, cToken, _amt); require(cTokenContract.repayBorrow(_amt) == 0, "repay-failed."); } setUint(setId, _amt); @@ -254,7 +254,7 @@ abstract contract CompoundResolver is Events, Helpers { } else { TokenInterface tokenContract = TokenInterface(token); _amt = _amt == uint(-1) ? tokenContract.balanceOf(address(this)) : _amt; - tokenContract.approve(cToken, _amt); + approve(tokenContract, cToken, _amt); require(ctokenContract.mint(_amt) == 0, "deposit-ctoken-failed."); } @@ -385,7 +385,7 @@ abstract contract CompoundResolver is Events, Helpers { } else { TokenInterface tokenContract = TokenInterface(tokenToPay); require(tokenContract.balanceOf(address(this)) >= _amt, "not-enough-token"); - tokenContract.approve(cTokenPay, _amt); + approve(tokenContract, cTokenPay, _amt); require(cTokenContract.liquidateBorrow(borrower, _amt, cTokenColl) == 0, "liquidate-failed"); } diff --git a/contracts/mainnet/connectors/cream/main.sol b/contracts/mainnet/connectors/cream/main.sol index 8d3849d4..2852147a 100644 --- a/contracts/mainnet/connectors/cream/main.sol +++ b/contracts/mainnet/connectors/cream/main.sol @@ -40,7 +40,7 @@ abstract contract CreamResolver is Events, Helpers { } else { TokenInterface tokenContract = TokenInterface(token); _amt = _amt == uint(-1) ? tokenContract.balanceOf(address(this)) : _amt; - tokenContract.approve(cToken, _amt); + approve(tokenContract, cToken, _amt); require(CTokenInterface(cToken).mint(_amt) == 0, "deposit-failed"); } setUint(setId, _amt); @@ -196,7 +196,7 @@ abstract contract CreamResolver is Events, Helpers { } else { TokenInterface tokenContract = TokenInterface(token); require(tokenContract.balanceOf(address(this)) >= _amt, "not-enough-token"); - tokenContract.approve(cToken, _amt); + approve(tokenContract, cToken, _amt); require(cTokenContract.repayBorrow(_amt) == 0, "repay-failed."); } setUint(setId, _amt); @@ -254,7 +254,7 @@ abstract contract CreamResolver is Events, Helpers { } else { TokenInterface tokenContract = TokenInterface(token); _amt = _amt == uint(-1) ? tokenContract.balanceOf(address(this)) : _amt; - tokenContract.approve(cToken, _amt); + approve(tokenContract, cToken, _amt); require(ctokenContract.mint(_amt) == 0, "deposit-ctoken-failed."); } @@ -384,7 +384,7 @@ abstract contract CreamResolver is Events, Helpers { } else { TokenInterface tokenContract = TokenInterface(tokenToPay); require(tokenContract.balanceOf(address(this)) >= _amt, "not-enough-token"); - tokenContract.approve(cTokenPay, _amt); + approve(tokenContract, cTokenPay, _amt); require(cTokenContract.liquidateBorrow(borrower, _amt, cTokenColl) == 0, "liquidate-failed"); } diff --git a/contracts/mainnet/connectors/dydx/main.sol b/contracts/mainnet/connectors/dydx/main.sol index d38588e8..dedad6ac 100644 --- a/contracts/mainnet/connectors/dydx/main.sol +++ b/contracts/mainnet/connectors/dydx/main.sol @@ -36,11 +36,11 @@ abstract contract DyDxResolver is Events, Helpers { TokenInterface tokenContract = TokenInterface(wethAddr); _amt = _amt == uint(-1) ? address(this).balance : _amt; tokenContract.deposit{value: _amt}(); - tokenContract.approve(address(solo), _amt); + approve(tokenContract, address(solo), _amt); } else { TokenInterface tokenContract = TokenInterface(token); _amt = _amt == uint(-1) ? tokenContract.balanceOf(address(this)) : _amt; - tokenContract.approve(address(solo), _amt); + approve(tokenContract, address(solo), _amt); } solo.operate(getAccountArgs(), getActionsArgs(_marketId, _amt, true)); @@ -77,7 +77,7 @@ abstract contract DyDxResolver is Events, Helpers { if (token == ethAddr) { TokenInterface tokenContract = TokenInterface(wethAddr); - tokenContract.approve(address(tokenContract), _amt); + approve(tokenContract, address(tokenContract), _amt); tokenContract.withdraw(_amt); } @@ -111,7 +111,7 @@ abstract contract DyDxResolver is Events, Helpers { if (token == ethAddr) { TokenInterface tokenContract = TokenInterface(wethAddr); - tokenContract.approve(address(tokenContract), _amt); + approve(tokenContract, address(tokenContract), _amt); tokenContract.withdraw(_amt); } @@ -148,11 +148,11 @@ abstract contract DyDxResolver is Events, Helpers { TokenInterface tokenContract = TokenInterface(wethAddr); require(address(this).balance >= _amt, "not-enough-eth"); tokenContract.deposit{value: _amt}(); - tokenContract.approve(address(solo), _amt); + approve(tokenContract, address(solo), _amt); } else { TokenInterface tokenContract = TokenInterface(token); require(tokenContract.balanceOf(address(this)) >= _amt, "not-enough-token"); - tokenContract.approve(address(solo), _amt); + approve(tokenContract, address(solo), _amt); } solo.operate(getAccountArgs(), getActionsArgs(_marketId, _amt, true)); diff --git a/contracts/mainnet/connectors/erc20_staking/main.sol b/contracts/mainnet/connectors/erc20_staking/main.sol index 176b21fa..70f83947 100644 --- a/contracts/mainnet/connectors/erc20_staking/main.sol +++ b/contracts/mainnet/connectors/erc20_staking/main.sol @@ -38,7 +38,7 @@ contract Main is Helpers, Events { _amt = _amt == uint(-1) ? stakingToken.balanceOf(address(this)) : _amt; - stakingToken.approve(address(stakingContract), _amt); + approve(stakingToken, address(stakingContract), _amt); stakingContract.stake(_amt); setUint(setId, _amt); diff --git a/contracts/mainnet/connectors/guniswap_v3_erc20/main.sol b/contracts/mainnet/connectors/guniswap_v3_erc20/main.sol index 3c7895bd..77b36687 100644 --- a/contracts/mainnet/connectors/guniswap_v3_erc20/main.sol +++ b/contracts/mainnet/connectors/guniswap_v3_erc20/main.sol @@ -54,12 +54,12 @@ abstract contract UniswapV3Resolver is Events, Helpers { if (depositData.amount0In > 0) { IERC20 _token0 = depositData.poolContract.token0(); convertEthToWeth(address(_token0) == wethAddr, TokenInterface(address(_token0)), depositData.amount0In); - _token0.safeApprove(address(pool), depositData.amount0In); + approve(TokenInterface(address(_token0)), address(pool), depositData.amount0In); } if (depositData.amount1In > 0) { IERC20 _token1 = depositData.poolContract.token1(); convertEthToWeth(address(_token1) == wethAddr, TokenInterface(address(_token1)), depositData.amount1In); - _token1.safeApprove(address(pool), depositData.amount1In); + approve(TokenInterface(address(_token1)), address(pool), depositData.amount1In); } (uint amount0, uint amount1,) = depositData.poolContract.mint(depositData.mintAmount, address(this)); @@ -151,7 +151,7 @@ abstract contract UniswapV3Resolver is Events, Helpers { depositAndSwap.mintAmount; if (address(depositAndSwap._token0) == wethAddr) { - depositAndSwap._token1.approve(address(gUniRouter), amount1In); + approve(depositAndSwap._token1, address(gUniRouter), amount1In); (depositAndSwap.amount0, depositAndSwap.amount1, depositAndSwap.mintAmount) = gUniRouter.rebalanceAndAddLiquidityETH{value: amount0In}( @@ -166,7 +166,7 @@ abstract contract UniswapV3Resolver is Events, Helpers { address(this) ); } else if (address(depositAndSwap._token1) == wethAddr) { - depositAndSwap._token0.approve(address(gUniRouter), amount0In); + approve(depositAndSwap._token0, address(gUniRouter), amount0In); (depositAndSwap.amount0, depositAndSwap.amount1,depositAndSwap. mintAmount) = gUniRouter.rebalanceAndAddLiquidityETH{value: amount1In}( @@ -181,8 +181,8 @@ abstract contract UniswapV3Resolver is Events, Helpers { address(this) ); } else { - depositAndSwap._token0.approve(address(gUniRouter), amount0In); - depositAndSwap._token1.approve(address(gUniRouter), amount1In); + approve(depositAndSwap._token0, address(gUniRouter), amount0In); + approve(depositAndSwap._token1, address(gUniRouter), amount1In); (depositAndSwap.amount0, depositAndSwap.amount1, depositAndSwap.mintAmount) = gUniRouter.rebalanceAndAddLiquidity( depositAndSwap.poolContract, diff --git a/contracts/mainnet/connectors/guniswap_v3_erc20_staking/main.sol b/contracts/mainnet/connectors/guniswap_v3_erc20_staking/main.sol index 4cd4fbcc..47a2d62a 100644 --- a/contracts/mainnet/connectors/guniswap_v3_erc20_staking/main.sol +++ b/contracts/mainnet/connectors/guniswap_v3_erc20_staking/main.sol @@ -35,7 +35,7 @@ contract Main is Helpers, Events { _amt = _amt == uint(-1) ? stakingTokenContract.balanceOf(address(this)) : _amt; - stakingTokenContract.approve(address(stakingContract), _amt); + approve(stakingTokenContract, address(stakingContract), _amt); stakingContract.stake(_amt); setUint(setId, _amt); diff --git a/contracts/mainnet/connectors/kyber/main.sol b/contracts/mainnet/connectors/kyber/main.sol index 8961b851..cdf810a7 100644 --- a/contracts/mainnet/connectors/kyber/main.sol +++ b/contracts/mainnet/connectors/kyber/main.sol @@ -37,7 +37,7 @@ abstract contract KyberResolver is Helpers, Events { } else { TokenInterface sellContract = TokenInterface(sellAddr); _sellAmt = _sellAmt == uint(-1) ? sellContract.balanceOf(address(this)) : _sellAmt; - sellContract.approve(address(kyber), _sellAmt); + approve(sellContract, address(kyber), _sellAmt); } uint _buyAmt = kyber.trade{value: ethAmt}( diff --git a/contracts/mainnet/connectors/makerdao/main.sol b/contracts/mainnet/connectors/makerdao/main.sol index e9240770..ae77595e 100644 --- a/contracts/mainnet/connectors/makerdao/main.sol +++ b/contracts/mainnet/connectors/makerdao/main.sol @@ -96,7 +96,7 @@ abstract contract MakerResolver is Helpers, Events { _amt = _amt == uint(-1) ? tokenContract.balanceOf(address(this)) : _amt; } - tokenContract.approve(address(colAddr), _amt); + approve(tokenContract, address(colAddr), _amt); tokenJoinContract.join(address(this), _amt); VatLike(managerContract.vat()).frob( @@ -245,7 +245,7 @@ abstract contract MakerResolver is Helpers, Events { require(_maxDebt >= _amt, "paying-excess-debt"); - daiJoinContract.dai().approve(address(daiJoinContract), _amt); + approve(daiJoinContract.dai(), address(daiJoinContract), _amt); daiJoinContract.join(urn, _amt); managerContract.frob( @@ -357,7 +357,7 @@ abstract contract MakerResolver is Helpers, Events { _amtDeposit = _amtDeposit == uint(-1) ? makerData.tokenContract.balanceOf(address(this)) : _amtDeposit; } - makerData.tokenContract.approve(address(makerData.colAddr), _amtDeposit); + approve(makerData.tokenContract, address(makerData.colAddr), _amtDeposit); makerData.tokenJoinContract.join(urn, _amtDeposit); managerContract.frob( @@ -462,7 +462,7 @@ abstract contract MakerResolver is Helpers, Events { VatLike vat = daiJoinContract.vat(); uint chi = potContract.drip(); - daiJoinContract.dai().approve(address(daiJoinContract), _amt); + approve(daiJoinContract.dai(), address(daiJoinContract), _amt); daiJoinContract.join(address(this), _amt); if (vat.can(address(this), address(potContract)) == 0) { vat.hope(address(potContract)); diff --git a/contracts/mainnet/connectors/oasis/main.sol b/contracts/mainnet/connectors/oasis/main.sol index 4761ad65..07393309 100644 --- a/contracts/mainnet/connectors/oasis/main.sol +++ b/contracts/mainnet/connectors/oasis/main.sol @@ -49,7 +49,7 @@ contract OasisResolver is DSMath, Basic, Events { bool isEth = address(_sellAddr) == wethAddr; convertEthToWeth(isEth, _sellAddr, _expectedAmt); - _sellAddr.approve(address(oasis), _expectedAmt); + approve(_sellAddr, address(oasis), _expectedAmt); uint _sellAmt = oasis.buyAllAmount( address(_buyAddr), @@ -104,7 +104,7 @@ contract OasisResolver is DSMath, Basic, Events { bool isEth = address(_sellAddr) == wethAddr; convertEthToWeth(isEth, _sellAddr, _sellAmt); - _sellAddr.approve(address(oasis), _sellAmt); + approve(_sellAddr, address(oasis), _sellAmt); uint _buyAmt = oasis.sellAllAmount( address(_sellAddr), diff --git a/contracts/mainnet/connectors/polygon-bridge/main.sol b/contracts/mainnet/connectors/polygon-bridge/main.sol index 0540f3c0..5c2356e2 100644 --- a/contracts/mainnet/connectors/polygon-bridge/main.sol +++ b/contracts/mainnet/connectors/polygon-bridge/main.sol @@ -36,10 +36,10 @@ abstract contract PolygonBridgeResolver is Events, Helpers { TokenInterface _token = TokenInterface(token); _amt = _amt == uint(-1) ? _token.balanceOf(address(this)) : _amt; if (migrator.rootToChildToken(token) != address(0)) { - _token.approve(erc20Predicate, _amt); + approve(_token, erc20Predicate, _amt); migrator.depositFor(targetDsa, token, abi.encode(_amt)); } else { - _token.approve(address(migratorPlasma), _amt); + approve(_token, address(migratorPlasma), _amt); migratorPlasma.depositERC20ForUser(token, targetDsa, _amt); } } diff --git a/contracts/mainnet/connectors/refinance/helpers/aaveV1.sol b/contracts/mainnet/connectors/refinance/helpers/aaveV1.sol index bc33c624..dc8a3d33 100644 --- a/contracts/mainnet/connectors/refinance/helpers/aaveV1.sol +++ b/contracts/mainnet/connectors/refinance/helpers/aaveV1.sol @@ -102,7 +102,7 @@ contract AaveV1Helpers is protocolHelpers { if (isEth) { ethAmt = _amt; } else { - token.approve(address(aaveCore), _amt); + approve(token, address(aaveCore), _amt); } transferFees(_token, feeAmt); @@ -180,7 +180,7 @@ contract AaveV1Helpers is protocolHelpers { if (isEth) { ethAmt = amt; } else { - token.approve(address(aaveCore), amt); + approve(token, address(aaveCore), amt); } aave.repay{value:ethAmt}(_token, amt, payable(address(this))); diff --git a/contracts/mainnet/connectors/refinance/helpers/aaveV2.sol b/contracts/mainnet/connectors/refinance/helpers/aaveV2.sol index 488deae3..9e8b5f27 100644 --- a/contracts/mainnet/connectors/refinance/helpers/aaveV2.sol +++ b/contracts/mainnet/connectors/refinance/helpers/aaveV2.sol @@ -109,7 +109,7 @@ contract AaveV2Helpers is protocolHelpers { convertEthToWeth(isEth, token, _amt); - token.approve(address(aave), _amt); + approve(token, address(aave), _amt); aave.deposit(address(token), _amt, address(this), getReferralCode); @@ -178,7 +178,7 @@ contract AaveV2Helpers is protocolHelpers { convertEthToWeth(isEth, token, _amt); - token.approve(address(aave), _amt); + approve(token, address(aave), _amt); aave.repay(address(token), _amt, rateMode, address(this)); } diff --git a/contracts/mainnet/connectors/refinance/helpers/compound.sol b/contracts/mainnet/connectors/refinance/helpers/compound.sol index c6bebc42..56e90cc8 100644 --- a/contracts/mainnet/connectors/refinance/helpers/compound.sol +++ b/contracts/mainnet/connectors/refinance/helpers/compound.sol @@ -83,7 +83,7 @@ contract CompoundHelpers is protocolHelpers { (uint feeAmt, uint _amt) = calculateFee(amt, fee, false); if (_token != ethAddr) { - token.approve(address(ctoken), _amt); + approve(token, address(ctoken), _amt); require(ctoken.mint(_amt) == 0, "deposit-failed"); } else { CETHInterface(address(ctoken)).mint{value:_amt}(); @@ -138,7 +138,7 @@ contract CompoundHelpers is protocolHelpers { amt = ctoken.borrowBalanceCurrent(address(this)); } if (address(token) != wethAddr) { - token.approve(address(ctoken), amt); + approve(token, address(ctoken), amt); require(ctoken.repayBorrow(amt) == 0, "repay-failed."); } else { CETHInterface(address(ctoken)).repayBorrow{value:amt}(); diff --git a/contracts/mainnet/connectors/reflexer/main.sol b/contracts/mainnet/connectors/reflexer/main.sol index ffeb6fe3..1a6d47eb 100644 --- a/contracts/mainnet/connectors/reflexer/main.sol +++ b/contracts/mainnet/connectors/reflexer/main.sol @@ -73,7 +73,7 @@ abstract contract GebResolver is Helpers, Events { _amt = _amt == uint(-1) ? tokenContract.balanceOf(address(this)) : _amt; } - tokenContract.approve(address(colAddr), _amt); + approve(tokenContract, address(colAddr), _amt); tokenJoinContract.join(address(this), _amt); SafeEngineLike(managerContract.safeEngine()).modifySAFECollateralization( @@ -222,7 +222,7 @@ abstract contract GebResolver is Helpers, Events { require(_maxDebt >= _amt, "paying-excess-debt"); - coinJoinContract.systemCoin().approve(address(coinJoinContract), _amt); + approve(coinJoinContract.systemCoin(), address(coinJoinContract), _amt); coinJoinContract.join(handler, _amt); managerContract.modifySAFECollateralization( @@ -335,7 +335,7 @@ abstract contract GebResolver is Helpers, Events { _amtDeposit = _amtDeposit == uint(-1) ? gebData.tokenContract.balanceOf(address(this)) : _amtDeposit; } - gebData.tokenContract.approve(address(gebData.colAddr), _amtDeposit); + approve(gebData.tokenContract, address(gebData.colAddr), _amtDeposit); gebData.tokenJoinContract.join(handler, _amtDeposit); managerContract.modifySAFECollateralization( diff --git a/contracts/mainnet/connectors/uniswap/helpers.sol b/contracts/mainnet/connectors/uniswap/helpers.sol index 10569cd8..f435a78b 100644 --- a/contracts/mainnet/connectors/uniswap/helpers.sol +++ b/contracts/mainnet/connectors/uniswap/helpers.sol @@ -78,8 +78,8 @@ abstract contract Helpers is DSMath, Basic { isEth = address(_tokenB) == wethAddr; convertEthToWeth(isEth, _tokenB, _amtB); - _tokenA.approve(address(router), _amtA); - _tokenB.approve(address(router), _amtB); + approve(_tokenA, address(router), _amtA); + approve(_tokenB, address(router), _amtB); uint minAmtA = getMinAmount(_tokenA, _amtA, slippage); uint minAmtB = getMinAmount(_tokenB, _amtB, slippage); @@ -141,6 +141,6 @@ abstract contract Helpers is DSMath, Basic { TokenInterface uniToken = TokenInterface(exchangeAddr); _uniAmt = _amt == uint(-1) ? uniToken.balanceOf(address(this)) : _amt; - uniToken.approve(address(router), _uniAmt); + approve(uniToken, address(router), _uniAmt); } } \ No newline at end of file diff --git a/contracts/mainnet/connectors/uniswap/main.sol b/contracts/mainnet/connectors/uniswap/main.sol index 68da00f5..18f0140b 100644 --- a/contracts/mainnet/connectors/uniswap/main.sol +++ b/contracts/mainnet/connectors/uniswap/main.sol @@ -114,7 +114,7 @@ abstract contract UniswapResolver is Helpers, Events { bool isEth = address(_sellAddr) == wethAddr; convertEthToWeth(isEth, _sellAddr, _expectedAmt); - _sellAddr.approve(address(router), _expectedAmt); + approve(_sellAddr, address(router), _expectedAmt); uint _sellAmt = router.swapTokensForExactTokens( _buyAmt, @@ -171,7 +171,7 @@ abstract contract UniswapResolver is Helpers, Events { bool isEth = address(_sellAddr) == wethAddr; convertEthToWeth(isEth, _sellAddr, _sellAmt); - _sellAddr.approve(address(router), _sellAmt); + approve(_sellAddr, address(router), _sellAmt); uint _buyAmt = router.swapExactTokensForTokens( _sellAmt, diff --git a/contracts/mainnet/connectors/weth/main.sol b/contracts/mainnet/connectors/weth/main.sol index 9a5f1444..f4d8f056 100644 --- a/contracts/mainnet/connectors/weth/main.sol +++ b/contracts/mainnet/connectors/weth/main.sol @@ -50,7 +50,7 @@ abstract contract Resolver is Events, DSMath, Basic, Helpers { uint _amt = getUint(getId, amt); _amt = _amt == uint(-1) ? wethContract.balanceOf(address(this)) : _amt; - wethContract.approve(wethAddr, _amt); + approve(wethContract, wethAddr, _amt); wethContract.withdraw(_amt); setUint(setId, _amt); diff --git a/contracts/mainnet/connectors_old/1inch.sol b/contracts/mainnet/connectors_old/1inch.sol index fc4b728d..796c909e 100644 --- a/contracts/mainnet/connectors_old/1inch.sol +++ b/contracts/mainnet/connectors_old/1inch.sol @@ -155,7 +155,7 @@ abstract contract OneProtoResolver is OneHelpers { if (address(_sellAddr) == ethAddr) { ethAmt = _sellAmt; } else { - _sellAddr.approve(address(oneProtoContract), _sellAmt); + approve(_sellAddr, address(oneProtoContract), _sellAmt); } @@ -197,7 +197,7 @@ abstract contract OneProtoResolver is OneHelpers { if (address(_sellAddr) == ethAddr) { ethAmt = _sellAmt; } else { - _sellAddr.approve(address(oneSplitContract), _sellAmt); + approve(_sellAddr, address(oneSplitContract), _sellAmt); } uint initalBal = getTokenBal(_buyAddr); @@ -442,7 +442,7 @@ abstract contract OneInchResolverHelpers is OneProtoResolverHelpers { if (address(_sellAddr) == ethAddr) { ethAmt = oneInchData._sellAmt; } else { - TokenInterface(_sellAddr).approve(getOneInchAddress(), oneInchData._sellAmt); + approve(TokenInterface(_sellAddr), getOneInchAddress(), oneInchData._sellAmt); } require(checkOneInchSig(oneInchData.callData), "Not-swap-function"); diff --git a/contracts/polygon/common/basic.sol b/contracts/polygon/common/basic.sol index fea2b1ec..a5c83c7e 100644 --- a/contracts/polygon/common/basic.sol +++ b/contracts/polygon/common/basic.sol @@ -32,13 +32,22 @@ abstract contract Basic is DSMath, Stores { _sell = sell == maticAddr ? TokenInterface(wmaticAddr) : TokenInterface(sell); } + function approve(TokenInterface token, address spender, uint256 amount) internal { + try token.approve(spender, amount) { + + } catch { + token.approve(spender, 0); + token.approve(spender, amount); + } + } + function convertMaticToWmatic(bool isMatic, TokenInterface token, uint amount) internal { if(isMatic) token.deposit{value: amount}(); } function convertWmaticToMatic(bool isMatic, TokenInterface token, uint amount) internal { if(isMatic) { - token.approve(address(token), amount); + approve(token, address(token), amount); token.withdraw(amount); } } diff --git a/contracts/polygon/connectors/aave/v2/main.sol b/contracts/polygon/connectors/aave/v2/main.sol index eddf8855..a76873e0 100644 --- a/contracts/polygon/connectors/aave/v2/main.sol +++ b/contracts/polygon/connectors/aave/v2/main.sol @@ -43,7 +43,7 @@ abstract contract AaveResolver is Events, Helpers { _amt = _amt == uint(-1) ? tokenContract.balanceOf(address(this)) : _amt; } - tokenContract.approve(address(aave), _amt); + approve(tokenContract, address(aave), _amt); aave.deposit(_token, _amt, address(this), referralCode); @@ -154,7 +154,7 @@ abstract contract AaveResolver is Events, Helpers { if (isEth) convertMaticToWmatic(isEth, tokenContract, _amt); - tokenContract.approve(address(aave), _amt); + approve(tokenContract, address(aave), _amt); aave.repay(_token, _amt, rateMode, address(this)); diff --git a/contracts/polygon/connectors/paraswap/helpers.sol b/contracts/polygon/connectors/paraswap/helpers.sol index 3a54f10f..0f0bc803 100644 --- a/contracts/polygon/connectors/paraswap/helpers.sol +++ b/contracts/polygon/connectors/paraswap/helpers.sol @@ -45,7 +45,7 @@ abstract contract Helpers is DSMath, Basic { maticAmt = swapData._sellAmt; } else { address tokenProxy = AugustusSwapperInterface(paraswap).getTokenTransferProxy(); - TokenInterface(_sellAddr).approve(tokenProxy, swapData._sellAmt); + approve(TokenInterface(_sellAddr), tokenProxy, swapData._sellAmt); } swapData._buyAmt = _swapHelper(swapData, maticAmt); From 217b3d402cee6b8b138bbb8d4459380e7f8dbfe8 Mon Sep 17 00:00:00 2001 From: Thrilok Kumar Date: Fri, 25 Jun 2021 01:29:19 +0530 Subject: [PATCH 16/45] Reverted change in old connector --- contracts/mainnet/connectors_old/1inch.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/mainnet/connectors_old/1inch.sol b/contracts/mainnet/connectors_old/1inch.sol index 796c909e..fc4b728d 100644 --- a/contracts/mainnet/connectors_old/1inch.sol +++ b/contracts/mainnet/connectors_old/1inch.sol @@ -155,7 +155,7 @@ abstract contract OneProtoResolver is OneHelpers { if (address(_sellAddr) == ethAddr) { ethAmt = _sellAmt; } else { - approve(_sellAddr, address(oneProtoContract), _sellAmt); + _sellAddr.approve(address(oneProtoContract), _sellAmt); } @@ -197,7 +197,7 @@ abstract contract OneProtoResolver is OneHelpers { if (address(_sellAddr) == ethAddr) { ethAmt = _sellAmt; } else { - approve(_sellAddr, address(oneSplitContract), _sellAmt); + _sellAddr.approve(address(oneSplitContract), _sellAmt); } uint initalBal = getTokenBal(_buyAddr); @@ -442,7 +442,7 @@ abstract contract OneInchResolverHelpers is OneProtoResolverHelpers { if (address(_sellAddr) == ethAddr) { ethAmt = oneInchData._sellAmt; } else { - approve(TokenInterface(_sellAddr), getOneInchAddress(), oneInchData._sellAmt); + TokenInterface(_sellAddr).approve(getOneInchAddress(), oneInchData._sellAmt); } require(checkOneInchSig(oneInchData.callData), "Not-swap-function"); From 2d4341a8013d6435cf986cc630b5ce9754e3a574 Mon Sep 17 00:00:00 2001 From: Thrilok Kumar Date: Fri, 25 Jun 2021 01:31:04 +0530 Subject: [PATCH 17/45] Minor fix --- contracts/mainnet/connectors/guniswap_v3_erc20/main.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/mainnet/connectors/guniswap_v3_erc20/main.sol b/contracts/mainnet/connectors/guniswap_v3_erc20/main.sol index 77b36687..f7ee90e7 100644 --- a/contracts/mainnet/connectors/guniswap_v3_erc20/main.sol +++ b/contracts/mainnet/connectors/guniswap_v3_erc20/main.sol @@ -151,7 +151,7 @@ abstract contract UniswapV3Resolver is Events, Helpers { depositAndSwap.mintAmount; if (address(depositAndSwap._token0) == wethAddr) { - approve(depositAndSwap._token1, address(gUniRouter), amount1In); + approve(TokenInterface(address(depositAndSwap._token1)), address(gUniRouter), amount1In); (depositAndSwap.amount0, depositAndSwap.amount1, depositAndSwap.mintAmount) = gUniRouter.rebalanceAndAddLiquidityETH{value: amount0In}( @@ -166,7 +166,7 @@ abstract contract UniswapV3Resolver is Events, Helpers { address(this) ); } else if (address(depositAndSwap._token1) == wethAddr) { - approve(depositAndSwap._token0, address(gUniRouter), amount0In); + approve(TokenInterface(address(depositAndSwap._token0)), address(gUniRouter), amount0In); (depositAndSwap.amount0, depositAndSwap.amount1,depositAndSwap. mintAmount) = gUniRouter.rebalanceAndAddLiquidityETH{value: amount1In}( @@ -181,8 +181,8 @@ abstract contract UniswapV3Resolver is Events, Helpers { address(this) ); } else { - approve(depositAndSwap._token0, address(gUniRouter), amount0In); - approve(depositAndSwap._token1, address(gUniRouter), amount1In); + approve(TokenInterface(address(depositAndSwap._token0)), address(gUniRouter), amount0In); + approve(TokenInterface(address(depositAndSwap._token1)), address(gUniRouter), amount1In); (depositAndSwap.amount0, depositAndSwap.amount1, depositAndSwap.mintAmount) = gUniRouter.rebalanceAndAddLiquidity( depositAndSwap.poolContract, From 8c9866d4e5d87f81bee8e6e2bfbe0a1c6679c87d Mon Sep 17 00:00:00 2001 From: Thrilok Kumar Date: Fri, 25 Jun 2021 20:48:51 +0530 Subject: [PATCH 18/45] Minor change --- contracts/mainnet/connectors/1proto/main.sol | 2 +- scripts/deploy.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/mainnet/connectors/1proto/main.sol b/contracts/mainnet/connectors/1proto/main.sol index f3c2f380..d347b629 100644 --- a/contracts/mainnet/connectors/1proto/main.sol +++ b/contracts/mainnet/connectors/1proto/main.sol @@ -2,7 +2,7 @@ pragma solidity ^0.7.0; pragma experimental ABIEncoderV2; /** - * @title 1Proto. + * @title 1Inch(On-chain). * @dev On-chain and off-chian DEX Aggregator. */ diff --git a/scripts/deploy.js b/scripts/deploy.js index 681cd095..e00420c9 100644 --- a/scripts/deploy.js +++ b/scripts/deploy.js @@ -9,6 +9,7 @@ async function main() { const connectMapping = { '1INCH-A': 'ConnectV2OneInch', + '1INCH-B': 'ConnectV2OneProto', 'AAVE-V1-A': 'ConnectV2AaveV1', 'AAVE-V2-A': 'ConnectV2AaveV2', 'AUTHORITY-A': 'ConnectV2Auth', From c3b327f1a681b98a44f6b710da5dcd97d99783bc Mon Sep 17 00:00:00 2001 From: Thrilok Kumar Date: Fri, 25 Jun 2021 20:57:53 +0530 Subject: [PATCH 19/45] Updated 1inch connector --- contracts/mainnet/connectors/1inch/helpers.sol | 9 +++++++-- contracts/mainnet/connectors/1inch/main.sol | 4 ++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/contracts/mainnet/connectors/1inch/helpers.sol b/contracts/mainnet/connectors/1inch/helpers.sol index 1aa7b6a1..41cc4b0d 100644 --- a/contracts/mainnet/connectors/1inch/helpers.sol +++ b/contracts/mainnet/connectors/1inch/helpers.sol @@ -9,10 +9,15 @@ abstract contract Helpers is DSMath, Basic { /** * @dev 1Inch Address */ - address internal constant oneInchAddr = 0x111111125434b319222CdBf8C261674aDB56F3ae; + address internal constant oneInchAddr = 0x11111112542D85B3EF69AE05771c2dCCff4fAa26; /** * @dev 1inch swap function sig */ - bytes4 internal constant oneInchSig = 0x90411a32; + bytes4 internal constant oneInchSwapSig = 0x7c025200; + + /** + * @dev 1inch swap function sig + */ + bytes4 internal constant oneInchUnoswapSig = 0x2e95b6c8; } \ No newline at end of file diff --git a/contracts/mainnet/connectors/1inch/main.sol b/contracts/mainnet/connectors/1inch/main.sol index cc3b7384..a1c3eec0 100644 --- a/contracts/mainnet/connectors/1inch/main.sol +++ b/contracts/mainnet/connectors/1inch/main.sol @@ -25,7 +25,7 @@ abstract contract OneInchResolver is Helpers, Events { assembly { sig := mload(add(_data, 32)) } - isOk = sig == oneInchSig; + isOk = isOk = sig == oneInchSwapSig || sig == oneInchUnoswapSig; } /** @@ -124,5 +124,5 @@ abstract contract OneInch is OneInchResolverHelpers { } contract ConnectV2OneInch is OneInch { - string public name = "1Inch-v1"; + string public name = "1Inch-v1.1"; } From 53a725a716e3126aed11326980e176219ca09dff Mon Sep 17 00:00:00 2001 From: Thrilok Kumar Date: Fri, 25 Jun 2021 21:00:34 +0530 Subject: [PATCH 20/45] Added polygon 1inch connector --- contracts/polygon/connectors/1inch/events.sol | 12 ++ .../polygon/connectors/1inch/helpers.sol | 23 ++++ .../polygon/connectors/1inch/interface.sol | 30 ++++ contracts/polygon/connectors/1inch/main.sol | 128 ++++++++++++++++++ 4 files changed, 193 insertions(+) create mode 100644 contracts/polygon/connectors/1inch/events.sol create mode 100644 contracts/polygon/connectors/1inch/helpers.sol create mode 100644 contracts/polygon/connectors/1inch/interface.sol create mode 100644 contracts/polygon/connectors/1inch/main.sol diff --git a/contracts/polygon/connectors/1inch/events.sol b/contracts/polygon/connectors/1inch/events.sol new file mode 100644 index 00000000..bec3b27a --- /dev/null +++ b/contracts/polygon/connectors/1inch/events.sol @@ -0,0 +1,12 @@ +pragma solidity ^0.7.0; + +contract Events { + event LogSell( + address indexed buyToken, + address indexed sellToken, + uint256 buyAmt, + uint256 sellAmt, + uint256 getId, + uint256 setId + ); +} \ No newline at end of file diff --git a/contracts/polygon/connectors/1inch/helpers.sol b/contracts/polygon/connectors/1inch/helpers.sol new file mode 100644 index 00000000..41cc4b0d --- /dev/null +++ b/contracts/polygon/connectors/1inch/helpers.sol @@ -0,0 +1,23 @@ +pragma solidity ^0.7.0; + +import { TokenInterface } from "../../common/interfaces.sol"; +import { DSMath } from "../../common/math.sol"; +import { Basic } from "../../common/basic.sol"; + + +abstract contract Helpers is DSMath, Basic { + /** + * @dev 1Inch Address + */ + address internal constant oneInchAddr = 0x11111112542D85B3EF69AE05771c2dCCff4fAa26; + + /** + * @dev 1inch swap function sig + */ + bytes4 internal constant oneInchSwapSig = 0x7c025200; + + /** + * @dev 1inch swap function sig + */ + bytes4 internal constant oneInchUnoswapSig = 0x2e95b6c8; +} \ No newline at end of file diff --git a/contracts/polygon/connectors/1inch/interface.sol b/contracts/polygon/connectors/1inch/interface.sol new file mode 100644 index 00000000..f35b9277 --- /dev/null +++ b/contracts/polygon/connectors/1inch/interface.sol @@ -0,0 +1,30 @@ +pragma solidity ^0.7.0; + +import { TokenInterface } from "../../common/interfaces.sol"; + +interface OneInchInterace { + function swap( + TokenInterface fromToken, + TokenInterface toToken, + uint256 fromTokenAmount, + uint256 minReturnAmount, + uint256 guaranteedAmount, + address payable referrer, + address[] calldata callAddresses, + bytes calldata callDataConcat, + uint256[] calldata starts, + uint256[] calldata gasLimitsAndValues + ) + external + payable + returns (uint256 returnAmount); +} + +struct OneInchData { + TokenInterface sellToken; + TokenInterface buyToken; + uint _sellAmt; + uint _buyAmt; + uint unitAmt; + bytes callData; +} \ No newline at end of file diff --git a/contracts/polygon/connectors/1inch/main.sol b/contracts/polygon/connectors/1inch/main.sol new file mode 100644 index 00000000..1abae97f --- /dev/null +++ b/contracts/polygon/connectors/1inch/main.sol @@ -0,0 +1,128 @@ +pragma solidity ^0.7.0; +pragma experimental ABIEncoderV2; + +/** + * @title 1Inch. + * @dev On-chain DEX Aggregator. + */ + +// import files from common directory +import { TokenInterface , MemoryInterface } from "../../common/interfaces.sol"; +import { Stores } from "../../common/stores.sol"; +import { OneInchInterace, OneInchData } from "./interface.sol"; +import { Helpers } from "./helpers.sol"; +import { Events } from "./events.sol"; + +abstract contract OneInchResolver is Helpers, Events { + /** + * @dev 1inch swap uses `.call()`. This function restrict it to call only swap/trade functionality + * @param callData - calldata to extract the first 4 bytes for checking function signature + */ + function checkOneInchSig(bytes memory callData) internal pure returns(bool isOk) { + bytes memory _data = callData; + bytes4 sig; + // solium-disable-next-line security/no-inline-assembly + assembly { + sig := mload(add(_data, 32)) + } + isOk = isOk = sig == oneInchSwapSig || sig == oneInchUnoswapSig; + } + + /** + * @dev 1inch API swap handler + * @param oneInchData - contains data returned from 1inch API. Struct defined in interfaces.sol + * @param ethAmt - Eth to swap for .value() + */ + function oneInchSwap( + OneInchData memory oneInchData, + uint ethAmt + ) internal returns (uint buyAmt) { + TokenInterface buyToken = oneInchData.buyToken; + (uint _buyDec, uint _sellDec) = getTokensDec(buyToken, oneInchData.sellToken); + uint _sellAmt18 = convertTo18(_sellDec, oneInchData._sellAmt); + uint _slippageAmt = convert18ToDec(_buyDec, wmul(oneInchData.unitAmt, _sellAmt18)); + + uint initalBal = getTokenBal(buyToken); + + // solium-disable-next-line security/no-call-value + (bool success, ) = oneInchAddr.call{value: ethAmt}(oneInchData.callData); + if (!success) revert("1Inch-swap-failed"); + + uint finalBal = getTokenBal(buyToken); + + buyAmt = sub(finalBal, initalBal); + + require(_slippageAmt <= buyAmt, "Too much slippage"); + } + +} + +abstract contract OneInchResolverHelpers is OneInchResolver { + + /** + * @dev Gets the swapping data from 1inch's API. + * @param oneInchData Struct with multiple swap data defined in interfaces.sol + * @param setId Set token amount at this ID in `InstaMemory` Contract. + */ + function _sell( + OneInchData memory oneInchData, + uint setId + ) internal returns (OneInchData memory) { + TokenInterface _sellAddr = oneInchData.sellToken; + + uint ethAmt; + if (address(_sellAddr) == maticAddr) { + ethAmt = oneInchData._sellAmt; + } else { + approve(TokenInterface(_sellAddr), oneInchAddr, oneInchData._sellAmt); + } + + require(checkOneInchSig(oneInchData.callData), "Not-swap-function"); + + oneInchData._buyAmt = oneInchSwap(oneInchData, ethAmt); + setUint(setId, oneInchData._buyAmt); + + return oneInchData; + + // emitLogSellThree(oneInchData, setId); + } +} + +abstract contract OneInch is OneInchResolverHelpers { + /** + * @dev Sell ETH/ERC20_Token using 1Inch. + * @notice Swap tokens from exchanges like kyber, 0x etc, with calculation done off-chain. + * @param buyAddr The address of the token to buy.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param sellAddr The address of the token to sell.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param sellAmt The amount of the token to sell. + * @param unitAmt The amount of buyAmt/sellAmt with slippage. + * @param callData Data from 1inch API. + * @param setId ID stores the amount of token brought. + */ + function sell( + address buyAddr, + address sellAddr, + uint sellAmt, + uint unitAmt, + bytes calldata callData, + uint setId + ) external payable returns (string memory _eventName, bytes memory _eventParam) { + OneInchData memory oneInchData = OneInchData({ + buyToken: TokenInterface(buyAddr), + sellToken: TokenInterface(sellAddr), + unitAmt: unitAmt, + callData: callData, + _sellAmt: sellAmt, + _buyAmt: 0 + }); + + oneInchData = _sell(oneInchData, setId); + + _eventName = "LogSell(address,address,uint256,uint256,uint256,uint256)"; + _eventParam = abi.encode(buyAddr, sellAddr, oneInchData._buyAmt, oneInchData._sellAmt, 0, setId); + } +} + +contract ConnectV2OneInchPolygon is OneInch { + string public name = "1Inch-v1"; +} From 5341aeb1b4bec6d18760011609a275f38871986f Mon Sep 17 00:00:00 2001 From: Thrilok Kumar Date: Fri, 25 Jun 2021 22:45:54 +0530 Subject: [PATCH 21/45] Updated package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index df593366..9cfa0ad1 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ }, "devDependencies": { "@nomiclabs/hardhat-ethers": "^2.0.2", - "@nomiclabs/hardhat-etherscan": "^2.1.2", + "@nomiclabs/hardhat-etherscan": "^2.1.3", "@nomiclabs/hardhat-waffle": "^2.0.1", "@nomiclabs/hardhat-web3": "^2.0.0", "@openzeppelin/test-helpers": "^0.5.6", From 17569033568ebfbc365d2762f017a705bc6164a9 Mon Sep 17 00:00:00 2001 From: Thrilok Kumar Date: Fri, 25 Jun 2021 22:49:14 +0530 Subject: [PATCH 22/45] Updated connector version --- contracts/mainnet/connectors/1inch/main.sol | 2 +- contracts/mainnet/connectors/1proto/main.sol | 2 +- contracts/mainnet/connectors/aave/v1/main.sol | 2 +- contracts/mainnet/connectors/aave/v2/main.sol | 2 +- contracts/mainnet/connectors/compound/main.sol | 2 +- contracts/mainnet/connectors/makerdao/main.sol | 2 +- contracts/mainnet/connectors/uniswap/main.sol | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/contracts/mainnet/connectors/1inch/main.sol b/contracts/mainnet/connectors/1inch/main.sol index a1c3eec0..dab1199d 100644 --- a/contracts/mainnet/connectors/1inch/main.sol +++ b/contracts/mainnet/connectors/1inch/main.sol @@ -124,5 +124,5 @@ abstract contract OneInch is OneInchResolverHelpers { } contract ConnectV2OneInch is OneInch { - string public name = "1Inch-v1.1"; + string public name = "1Inch-v1.2"; } diff --git a/contracts/mainnet/connectors/1proto/main.sol b/contracts/mainnet/connectors/1proto/main.sol index d347b629..963e6551 100644 --- a/contracts/mainnet/connectors/1proto/main.sol +++ b/contracts/mainnet/connectors/1proto/main.sol @@ -221,5 +221,5 @@ abstract contract OneProto is OneProtoResolverHelpers { } contract ConnectV2OneProto is OneProto { - string public name = "1proto-v1"; + string public name = "1Proto-v1.1"; } diff --git a/contracts/mainnet/connectors/aave/v1/main.sol b/contracts/mainnet/connectors/aave/v1/main.sol index 0354c0e8..03f900ef 100644 --- a/contracts/mainnet/connectors/aave/v1/main.sol +++ b/contracts/mainnet/connectors/aave/v1/main.sol @@ -166,5 +166,5 @@ abstract contract AaveResolver is Events, Helpers { } contract ConnectV2AaveV1 is AaveResolver { - string constant public name = "AaveV1-v1"; + string constant public name = "AaveV1-v1.1"; } diff --git a/contracts/mainnet/connectors/aave/v2/main.sol b/contracts/mainnet/connectors/aave/v2/main.sol index 4b9f4656..d3f7d01a 100644 --- a/contracts/mainnet/connectors/aave/v2/main.sol +++ b/contracts/mainnet/connectors/aave/v2/main.sol @@ -211,5 +211,5 @@ abstract contract AaveResolver is Events, Helpers { } contract ConnectV2AaveV2 is AaveResolver { - string constant public name = "AaveV2-v1"; + string constant public name = "AaveV2-v1.1"; } diff --git a/contracts/mainnet/connectors/compound/main.sol b/contracts/mainnet/connectors/compound/main.sol index 01ee5fc3..9753b275 100644 --- a/contracts/mainnet/connectors/compound/main.sol +++ b/contracts/mainnet/connectors/compound/main.sol @@ -437,5 +437,5 @@ abstract contract CompoundResolver is Events, Helpers { } contract ConnectV2Compound is CompoundResolver { - string public name = "Compound-v1"; + string public name = "Compound-v1.1"; } diff --git a/contracts/mainnet/connectors/makerdao/main.sol b/contracts/mainnet/connectors/makerdao/main.sol index ae77595e..3afff9b3 100644 --- a/contracts/mainnet/connectors/makerdao/main.sol +++ b/contracts/mainnet/connectors/makerdao/main.sol @@ -519,5 +519,5 @@ abstract contract MakerResolver is Helpers, Events { } contract ConnectV2MakerDAO is MakerResolver { - string public constant name = "MakerDAO-v1.1"; + string public constant name = "MakerDAO-v1.2"; } diff --git a/contracts/mainnet/connectors/uniswap/main.sol b/contracts/mainnet/connectors/uniswap/main.sol index 18f0140b..a8ee9959 100644 --- a/contracts/mainnet/connectors/uniswap/main.sol +++ b/contracts/mainnet/connectors/uniswap/main.sol @@ -192,5 +192,5 @@ abstract contract UniswapResolver is Helpers, Events { } contract ConnectV2UniswapV2 is UniswapResolver { - string public constant name = "UniswapV2-v1"; + string public constant name = "UniswapV2-v1.1"; } From 9b9fb2d7fbc0b3ba2741fa2cb4c57a39b3a61c23 Mon Sep 17 00:00:00 2001 From: Thrilok Kumar Date: Fri, 25 Jun 2021 23:57:39 +0530 Subject: [PATCH 23/45] Minor change --- contracts/polygon/connectors/aave/v2/main.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/polygon/connectors/aave/v2/main.sol b/contracts/polygon/connectors/aave/v2/main.sol index a76873e0..5dc8578c 100644 --- a/contracts/polygon/connectors/aave/v2/main.sol +++ b/contracts/polygon/connectors/aave/v2/main.sol @@ -211,6 +211,6 @@ abstract contract AaveResolver is Events, Helpers { } } -contract ConnectV2AaveV2 is AaveResolver { +contract ConnectV2AaveV2Polygon is AaveResolver { string constant public name = "AaveV2-v1"; } From acafed23d013187328b14ea8f04a75a9ffc2099a Mon Sep 17 00:00:00 2001 From: Thrilok Kumar Date: Sat, 26 Jun 2021 00:05:28 +0530 Subject: [PATCH 24/45] Updated connector list --- docs/connectors.json | 15 ++++++++------- hardhat.config.js | 10 ++++++++-- scripts/deploy.js | 9 --------- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/docs/connectors.json b/docs/connectors.json index 858ea7bf..755b74da 100644 --- a/docs/connectors.json +++ b/docs/connectors.json @@ -1,14 +1,15 @@ { "1" : { - "1INCH-A": "0x2A6d6d4EE84015F7D64B4d1F66a409bA3f2BAC00", - "1INCH-B": "0x36880391afb430e99d43fe94217446b56d4f2c5b", - "AAVE-V1-A": "0x127d8cD0E2b2E0366D522DeA53A787bfE9002C14", - "AAVE-V2-A": "0x497Bc53507DF17e60F731e9534cff74E8BC9DBb8", "AUTHORITY-A": "0x351Bb32e90C35647Df7a584f3c1a3A0c38F31c68", "BASIC-A": "0x9926955e0Dd681Dc303370C52f4Ad0a4dd061687", + "1INCH-A": "0x235fca310ac7be45c7ad45f111203468743e4b7c", + "1INCH-B": "0xaBac3dCf164eD827EAfda8e05eCc8208D6bc5E04", + "COMPOUND-A": "0xbb153cf09a123746e0eb3b3a436c544a7eeb24b6", + "AAVE-V1-A": "0x612c5CA43230D9F97a0ac87E4420F66b8DF97e9D", + "AAVE-V2-A": "0x68b27A84101ac5120bBAb7Ce8d6b096C961df52C", + "MAKERDAO-A": "0x4049db23c605b197f764072569b8db2464653ef6", + "UNISWAP-V2-A": "0x1E5CE41BdB653734445FeC3553b61FebDdaFC43c", "COMP-A": "0xB446e325D44C52b93eC122Bf76301f235f90B9c9", - "COMPOUND-A": "0x911F4e4e762AeFA6F2Fc1b24e6B1A928200a88a8", - "MAKERDAO-A": "0x29AA7b765008b5dDbD687413B7F0D6E9d349F765", "UNISWAP-A": "0xA4BF319968986D2352FA1c550D781bBFCCE3FcaB", "POLYGON-BRIDGE-A": "0x1b79B302132370B434fb7807b36CB72FB0510aD5", "AAVE-CLAIM-A": "0x611C1FA59Aa1d6352c4C8bD44882063c6aEE85E0", @@ -20,7 +21,7 @@ "INSTAPOOL-A": "0x5806Af7AB22E2916fA582Ff05731Bf7C682387B2", "MAKERDAO-CLAIM-A": "0x2f8cBE650af98602a215b6482F2aD60893C5A4E8", "WETH-A": "0x22075fa719eFb02Ca3cF298AFa9C974B7465E5D3", - "REFINANCE-A": "0x9eA34bE6dA51aa9F6408FeA79c946FDCFA424442" + "REFINANCE-A": "0x9eA34bE6dA51aa9F6408FeA79c946FDCFA424442", "INST-A": "0x52C2C4a0db049255fF345EB9D3Fb1f555b7a924A" }, "137" : { diff --git a/hardhat.config.js b/hardhat.config.js index a3d99c31..79716efc 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -24,7 +24,13 @@ module.exports = { solidity: { compilers: [ { - version: "0.7.6" + version: "0.7.6", + settings: { + optimizer: { + enabled: false, + runs: 200 + } + } }, { version: "0.6.0" @@ -47,7 +53,7 @@ module.exports = { url: `https://eth-mainnet.alchemyapi.io/v2/${ALCHEMY_ID}`, accounts: [`0x${PRIVATE_KEY}`], timeout: 150000, - gasPrice: parseInt(utils.parseUnits("34", "gwei")) + gasPrice: parseInt(utils.parseUnits("30", "gwei")) }, hardhat: { forking: { diff --git a/scripts/deploy.js b/scripts/deploy.js index e00420c9..26f65d8e 100644 --- a/scripts/deploy.js +++ b/scripts/deploy.js @@ -28,15 +28,6 @@ async function main() { for (const key in connectMapping) { addressMapping[key] = await deployConnector(connectMapping[key]) } - - const connectorsAbi = [ - "function addConnectors(string[] _connectorNames, address[] _connectors)" - ] - - // Replace the address with correct v2 connectors registry address - const connectorsContract = new ethers.Contract("0x84b457c6D31025d56449D5A01F0c34bF78636f67", connectorsAbi, wallet) - - await connectorsContract.addConnectors(Object.keys(addressMapping), Object.values(addressMapping)) } main() From a22103533b8281034fa939682c55c4537338a255 Mon Sep 17 00:00:00 2001 From: Thrilok Kumar Date: Sat, 26 Jun 2021 00:06:08 +0530 Subject: [PATCH 25/45] Minor fix --- contracts/polygon/connectors/1inch/main.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/polygon/connectors/1inch/main.sol b/contracts/polygon/connectors/1inch/main.sol index 1abae97f..38a720c0 100644 --- a/contracts/polygon/connectors/1inch/main.sol +++ b/contracts/polygon/connectors/1inch/main.sol @@ -25,7 +25,7 @@ abstract contract OneInchResolver is Helpers, Events { assembly { sig := mload(add(_data, 32)) } - isOk = isOk = sig == oneInchSwapSig || sig == oneInchUnoswapSig; + isOk = sig == oneInchSwapSig || sig == oneInchUnoswapSig; } /** From 88604da176211c1fb25ac84ffeaa3e2b45f1e3f5 Mon Sep 17 00:00:00 2001 From: Thrilok Kumar Date: Sat, 26 Jun 2021 00:30:19 +0530 Subject: [PATCH 26/45] Updated connector list --- docs/connectors.json | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/connectors.json b/docs/connectors.json index 858ea7bf..3936bab9 100644 --- a/docs/connectors.json +++ b/docs/connectors.json @@ -24,6 +24,7 @@ "INST-A": "0x52C2C4a0db049255fF345EB9D3Fb1f555b7a924A" }, "137" : { + "1INCH-A": "0xC0d9210496afE9763F5d8cEb8deFfBa817232A9e", "AAVE-V2-A": "0xE84d8010Afc3663919F44685cB53ED88866da3eE", "AUTHORITY-A": "0xf73C94402BC24148b744083eD02654EEc2C37D5B", "BASIC-A": "0x1cAF5EC802ca602E98139AD96A8f2B7BC524264E", From a14f4c6e025b90e26025d1dd8e465d80d9b45ac1 Mon Sep 17 00:00:00 2001 From: Thrilok Kumar Date: Sat, 26 Jun 2021 01:25:34 +0530 Subject: [PATCH 27/45] Updated flashloan code --- .../mainnet/connectors/instapool_v2/main.sol | 92 ++++--------------- .../connectors/instapool_v2/variables.sol | 4 +- 2 files changed, 18 insertions(+), 78 deletions(-) diff --git a/contracts/mainnet/connectors/instapool_v2/main.sol b/contracts/mainnet/connectors/instapool_v2/main.sol index 5e8e75bc..7c436aa0 100644 --- a/contracts/mainnet/connectors/instapool_v2/main.sol +++ b/contracts/mainnet/connectors/instapool_v2/main.sol @@ -3,10 +3,11 @@ pragma experimental ABIEncoderV2; /** * @title Instapool. - * @dev Flash Loan in DSA. + * @dev Inbuilt Flash Loan in DSA */ import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; +import { Address } from "@openzeppelin/contracts/utils/Address.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { TokenInterface } from "../../common/interfaces.sol"; import { AccountInterface } from "./interfaces.sol"; @@ -20,10 +21,8 @@ contract LiquidityResolver is DSMath, Stores, Variables, Events { /** * @dev Borrow Flashloan and Cast spells. - * @notice Borrows flashloan and cast the spells. * @param token Token Address. * @param amt Token Amount. - * @param route Route to borrow. * @param data targets & data for cast. */ function flashBorrowAndCast( @@ -31,25 +30,20 @@ contract LiquidityResolver is DSMath, Stores, Variables, Events { uint amt, uint route, bytes memory data - ) external payable returns (string memory _eventName, bytes memory _eventParam) { + ) external payable { AccountInterface(address(this)).enable(address(instaPool)); + (string[] memory _targets, bytes[] memory callDatas) = abi.decode(data, (string[], bytes[])); - address[] memory tokens = new address[](1); - uint[] memory amts = new uint[](1); - tokens[0] = token; - amts[0] = amt; + bytes memory callData = abi.encodeWithSignature("cast(string[],bytes[],address)", _targets, callDatas, address(instaPool)); - instaPool.initiateFlashLoan(tokens, amts, route, data); + instaPool.initiateFlashLoan(token, amt, route, callData); + emit LogFlashBorrow(token, amt); AccountInterface(address(this)).disable(address(instaPool)); - - _eventName = "LogFlashBorrow(address,uint256)"; - _eventParam = abi.encode(token, amt); } /** * @dev Return token to InstaPool. - * @notice Payback borrowed flashloan. * @param token Token Address. * @param amt Token Amount. * @param getId Get token amount at this ID from `InstaMemory` Contract. @@ -60,78 +54,24 @@ contract LiquidityResolver is DSMath, Stores, Variables, Events { uint amt, uint getId, uint setId - ) external payable returns (string memory _eventName, bytes memory _eventParam) { + ) external payable { uint _amt = getUint(getId, amt); IERC20 tokenContract = IERC20(token); - tokenContract.safeTransfer(address(instaPool), _amt); + if (token == ethAddr) { + Address.sendValue(payable(address(instaPool)), _amt); + } else { + tokenContract.safeTransfer(address(instaPool), _amt); + } + setUint(setId, _amt); - _eventName = "LogFlashPayback(address,uint256)"; - _eventParam = abi.encode(token, _amt); - } - - /** - * @dev Borrow Flashloan and Cast spells. - * @notice Borrows multiple flashloan tokens and cast the spells. - * @param tokens Array of token Addresses. - * @param amts Array of token Amounts. - * @param route Route to borrow. - * @param data targets & data for cast. - */ - function flashMultiBorrowAndCast( - address[] calldata tokens, - uint[] calldata amts, - uint route, - bytes calldata data - ) external payable returns (string memory _eventName, bytes memory _eventParam) { - AccountInterface(address(this)).enable(address(instaPool)); - - instaPool.initiateFlashLoan(tokens, amts, route, data); - - AccountInterface(address(this)).disable(address(instaPool)); - - _eventName = "LogFlashMultiBorrow(address[],uint256[])"; - _eventParam = abi.encode(tokens, amts); - } - - /** - * @dev Return Multiple token liquidity to InstaPool. - * @notice Payback borrowed multiple flashloan tokens. - * @param tokens Array of token addresses. - * @param amts Array of token amounts. - * @param getId get token amounts at this IDs from `InstaMemory` Contract. - * @param setId set token amounts at this IDs in `InstaMemory` Contract. - */ - function flashMultiPayback( - address[] calldata tokens, - uint[] calldata amts, - uint[] calldata getId, - uint[] calldata setId - ) external payable returns (string memory _eventName, bytes memory _eventParam) { - uint _length = tokens.length; - - uint[] memory _amts = new uint[](_length); - - for (uint i = 0; i < _length; i++) { - uint _amt = getUint(getId[i], amts[i]); - - _amts[i] = _amt; - - IERC20 tokenContract = IERC20(tokens[i]); - - tokenContract.safeTransfer(address(instaPool), _amt); - - setUint(setId[i], _amt); - } - - _eventName = "LogFlashMultiPayback(address[],uint256[])"; - _eventParam = abi.encode(tokens, _amts); + emit LogFlashPayback(token, _amt); } } contract ConnectV2InstaPool is LiquidityResolver { - string public name = "Instapool-v1"; + string public name = "Instapool-v1.1"; } \ No newline at end of file diff --git a/contracts/mainnet/connectors/instapool_v2/variables.sol b/contracts/mainnet/connectors/instapool_v2/variables.sol index a6d80231..47dcc9f7 100644 --- a/contracts/mainnet/connectors/instapool_v2/variables.sol +++ b/contracts/mainnet/connectors/instapool_v2/variables.sol @@ -6,7 +6,7 @@ import { InstaFlashV2Interface } from "./interfaces.sol"; contract Variables { /** - * @dev Instapool / Receiver contract proxy + * @dev Instapool contract proxy */ - InstaFlashV2Interface public constant instaPool = InstaFlashV2Interface(0x691d4172331a11912c6D0e6D1A002E3d7CED6a66); + InstaFlashV2Interface public constant instaPool = InstaFlashV2Interface(0x2a1739D7F07d40e76852Ca8f0D82275Aa087992F); } \ No newline at end of file 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 28/45] 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 29/45] 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 30/45] 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 31/45] 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 32/45] 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 33/45] 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 11c08ea7441765c0d83bf937f6ec9a29fa7bfbb5 Mon Sep 17 00:00:00 2001 From: Thrilok kumar Date: Wed, 14 Jul 2021 19:17:55 +0530 Subject: [PATCH 34/45] Fixed instapool interface --- contracts/mainnet/connectors/instapool_v2/interfaces.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/mainnet/connectors/instapool_v2/interfaces.sol b/contracts/mainnet/connectors/instapool_v2/interfaces.sol index 506a2edb..dee5cc3d 100644 --- a/contracts/mainnet/connectors/instapool_v2/interfaces.sol +++ b/contracts/mainnet/connectors/instapool_v2/interfaces.sol @@ -2,10 +2,10 @@ pragma solidity >=0.7.0; pragma experimental ABIEncoderV2; interface InstaFlashV2Interface { - function initiateFlashLoan(address[] calldata tokens, uint256[] calldata amts, uint route, bytes calldata data) external; + function initiateFlashLoan(address token, uint256 amt, uint route, bytes calldata data) external; } interface AccountInterface { function enable(address) external; function disable(address) external; -} \ No newline at end of file +} From 67e07913edc78c00e6ef8ad333471e5fbec5ade3 Mon Sep 17 00:00:00 2001 From: Edward Mulraney Date: Mon, 26 Jul 2021 08:41:32 -0400 Subject: [PATCH 35/45] 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 36/45] 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 37/45] 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 38/45] 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 9fcd712d0e6dbcd5abfe833014ac31108f8e0a9f Mon Sep 17 00:00:00 2001 From: Aleksandr S Date: Mon, 26 Jul 2021 18:59:54 +0300 Subject: [PATCH 39/45] check if public function is payable --- status-checks/check.js | 40 +++++++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/status-checks/check.js b/status-checks/check.js index 8c9addbe..a76d81ae 100644 --- a/status-checks/check.js +++ b/status-checks/check.js @@ -180,10 +180,20 @@ const parseCode = async (connector) => { func = [] } } - funcs = funcs + const allPublicFuncs = funcs .filter(({ raw }) => { - if ((raw.includes('external') || raw.includes('public')) && - raw.includes('returns')) { + return raw.includes('external') || raw.includes('public') + }) + .map(f => { + const name = f.raw.split('(')[0].split('function')[1].trim() + return { + ...f, + name + } + }) + funcs = allPublicFuncs + .filter(({ raw }) => { + if (raw.includes('returns')) { const returns = raw.split('returns')[1].split('(')[1].split(')')[0] return returns.includes('string') && returns.includes('bytes') } @@ -193,11 +203,9 @@ const parseCode = async (connector) => { const args = f.raw.split('(')[1].split(')')[0].split(',') .map(arg => arg.trim()) .filter(arg => arg !== '') - const name = f.raw.split('(')[0].split('function')[1].trim() return { ...f, - args, - name + args } }) const eventsPath = `${connector.path}/events.sol` @@ -229,7 +237,8 @@ const parseCode = async (connector) => { eventsFirstLines, mainEvents, mainEventsLines, - funcs + funcs, + allPublicFuncs } } catch (error) { return Promise.reject(error) @@ -262,6 +271,21 @@ const checkComments = async (connector) => { } } +const checkPublicFuncs = async (connector) => { + try { + const errors = [] + for (let i1 = 0; i1 < connector.allPublicFuncs.length; i1++) { + const { raw, firstLine, name } = connector.allPublicFuncs[i1] + if (!raw.includes('payable')) { + errors.push(`public function ${name} is not payable at ${connector.path}/main.sol:${firstLine}`) + } + } + return errors + } catch (error) { + return Promise.reject(error) + } +} + const checkName = async (connector) => { try { const strs = connector.code.split('\n') @@ -313,12 +337,14 @@ async function checkMain () { const commentsErrors = await checkComments(connectors[index]) const nameErrors = await checkName(connectors[index]) const headCommentsErrors = await checkHeadComments(connectors[index]) + const publicFuncsErrors = await checkPublicFuncs(connectors[index]) errors.push(...forbiddenErrors) errors.push(...eventsErrors) errors.push(...commentsErrors) errors.push(...nameErrors) errors.push(...headCommentsErrors) + errors.push(...publicFuncsErrors) warnings.push(...eventsWarnings) } if (errors.length) { From f81af1743ea00e6a45719ea73785a2f6d14976eb Mon Sep 17 00:00:00 2001 From: Edward Mulraney Date: Fri, 30 Jul 2021 10:31:46 +0100 Subject: [PATCH 40/45] 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 b62d3ec04c0d36bd9390d0c4d9bb0a20631e0e6e Mon Sep 17 00:00:00 2001 From: Thrilok Kumar Date: Tue, 3 Aug 2021 20:57:53 +0530 Subject: [PATCH 41/45] Cleared TODO --- contracts/mainnet/mapping/reflexer.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/mainnet/mapping/reflexer.sol b/contracts/mainnet/mapping/reflexer.sol index 1f81ba6a..14c20bf0 100644 --- a/contracts/mainnet/mapping/reflexer.sol +++ b/contracts/mainnet/mapping/reflexer.sol @@ -17,9 +17,9 @@ interface MappingControllerInterface { function hasRole(address,address) external view returns (bool); } contract Helpers { - // TODO: thrilok, verify this address - ConnectorsInterface public constant connectors = ConnectorsInterface(0xFE2390DAD597594439f218190fC2De40f9Cf1179); + 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)); uint public version = 1; From dedd38239aa3984050413de144c1571e17a86fe7 Mon Sep 17 00:00:00 2001 From: Thrilok Kumar Date: Tue, 3 Aug 2021 20:58:30 +0530 Subject: [PATCH 42/45] Updated solidity version of reflexer mapping contract --- contracts/mainnet/mapping/reflexer.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/mainnet/mapping/reflexer.sol b/contracts/mainnet/mapping/reflexer.sol index 14c20bf0..c9092d3e 100644 --- a/contracts/mainnet/mapping/reflexer.sol +++ b/contracts/mainnet/mapping/reflexer.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.6.0; +pragma solidity ^0.7.0; pragma experimental ABIEncoderV2; interface CollateralJoinInterface { From cf8cc260909b6230e98dd9934fd36ce8867c463c Mon Sep 17 00:00:00 2001 From: Thrilok Kumar Date: Tue, 3 Aug 2021 21:32:27 +0530 Subject: [PATCH 43/45] Deployed InstaMappingController contract --- contracts/mapping/InstaMappingController.sol | 14 ++-- docs/connectors.json | 68 +++++++++++--------- scripts/deployInstaMappingController.js | 36 +++++++++++ scripts/deployMappingContract.js | 38 +++++++++++ 4 files changed, 121 insertions(+), 35 deletions(-) create mode 100644 scripts/deployInstaMappingController.js create mode 100644 scripts/deployMappingContract.js diff --git a/contracts/mapping/InstaMappingController.sol b/contracts/mapping/InstaMappingController.sol index c7287aaf..274990e8 100644 --- a/contracts/mapping/InstaMappingController.sol +++ b/contracts/mapping/InstaMappingController.sol @@ -1,6 +1,5 @@ // SPDX-License-Identifier: MIT - -pragma solidity >=0.6.0 <0.8.0; +pragma solidity ^0.7.0; import "@openzeppelin/contracts/utils/EnumerableSet.sol"; import "@openzeppelin/contracts/utils/Address.sol"; @@ -10,6 +9,10 @@ interface IndexInterface { function master() external view returns (address); } +interface ConnectorsInterface { + function chief(address) external view returns (bool); +} + contract InstaMappingController is Context { using EnumerableSet for EnumerableSet.AddressSet; using Address for address; @@ -18,6 +21,9 @@ contract InstaMappingController is Context { IndexInterface public constant instaIndex = IndexInterface(0x2971AdFa57b20E5a416aE5a708A8655A9c74f723); + ConnectorsInterface public constant connectors = + ConnectorsInterface(0x97b0B3A8bDeFE8cB9563a3c610019Ad10DB8aD11); // InstaConnectorsV2 + /** * @dev Emitted when `account` is granted `role`. @@ -39,8 +45,8 @@ contract InstaMappingController is Context { modifier onlyMaster { require( - instaIndex.master() == _msgSender(), - "MappingController: sender must be master" + instaIndex.master() == _msgSender() || connectors.chief(_msgSender()), + "MappingController: sender must be master or chief" ); _; } diff --git a/docs/connectors.json b/docs/connectors.json index 98e66de9..52b4877d 100644 --- a/docs/connectors.json +++ b/docs/connectors.json @@ -1,35 +1,41 @@ { - "1" : { - "AUTHORITY-A": "0x351Bb32e90C35647Df7a584f3c1a3A0c38F31c68", - "BASIC-A": "0x9926955e0Dd681Dc303370C52f4Ad0a4dd061687", - "1INCH-A": "0x235fca310ac7be45c7ad45f111203468743e4b7c", - "1INCH-B": "0xaBac3dCf164eD827EAfda8e05eCc8208D6bc5E04", - "COMPOUND-A": "0xbb153cf09a123746e0eb3b3a436c544a7eeb24b6", - "AAVE-V1-A": "0x612c5CA43230D9F97a0ac87E4420F66b8DF97e9D", - "AAVE-V2-A": "0x68b27A84101ac5120bBAb7Ce8d6b096C961df52C", - "MAKERDAO-A": "0x4049db23c605b197f764072569b8db2464653ef6", - "UNISWAP-V2-A": "0x1E5CE41BdB653734445FeC3553b61FebDdaFC43c", - "COMP-A": "0xB446e325D44C52b93eC122Bf76301f235f90B9c9", - "UNISWAP-A": "0xA4BF319968986D2352FA1c550D781bBFCCE3FcaB", - "POLYGON-BRIDGE-A": "0x1b79B302132370B434fb7807b36CB72FB0510aD5", - "AAVE-CLAIM-A": "0x611C1FA59Aa1d6352c4C8bD44882063c6aEE85E0", - "AAVE-STAKE-A": "0xf73c94402bc24148b744083ed02654eec2c37d5b", - "G-UNISWAP-A": "0x2fca923c7535083f25f761dcf289d7d81f024dda", - "INST-STAKING-A": "0x37a63939e128d284e0eae5d3e517aad44f5204d4", - "AAVE-V2-IMPORT-B": "0x6fe05374924830B6aC98849f75A3D5766E51Ef10", - "COMPOUND-IMPORT-B": "0xdA101870ca6136539628F28041E1B55baf4EB6C0", - "INSTAPOOL-A": "0x5806Af7AB22E2916fA582Ff05731Bf7C682387B2", - "MAKERDAO-CLAIM-A": "0x2f8cBE650af98602a215b6482F2aD60893C5A4E8", - "WETH-A": "0x22075fa719eFb02Ca3cF298AFa9C974B7465E5D3", - "REFINANCE-A": "0x9eA34bE6dA51aa9F6408FeA79c946FDCFA424442", - "INST-A": "0x52C2C4a0db049255fF345EB9D3Fb1f555b7a924A" + "connectors": + { + "1" : { + "AUTHORITY-A": "0x351Bb32e90C35647Df7a584f3c1a3A0c38F31c68", + "BASIC-A": "0x9926955e0Dd681Dc303370C52f4Ad0a4dd061687", + "1INCH-A": "0x235fca310ac7be45c7ad45f111203468743e4b7c", + "1INCH-B": "0xaBac3dCf164eD827EAfda8e05eCc8208D6bc5E04", + "COMPOUND-A": "0xbb153cf09a123746e0eb3b3a436c544a7eeb24b6", + "AAVE-V1-A": "0x612c5CA43230D9F97a0ac87E4420F66b8DF97e9D", + "AAVE-V2-A": "0x68b27A84101ac5120bBAb7Ce8d6b096C961df52C", + "MAKERDAO-A": "0x4049db23c605b197f764072569b8db2464653ef6", + "UNISWAP-V2-A": "0x1E5CE41BdB653734445FeC3553b61FebDdaFC43c", + "COMP-A": "0xB446e325D44C52b93eC122Bf76301f235f90B9c9", + "UNISWAP-A": "0xA4BF319968986D2352FA1c550D781bBFCCE3FcaB", + "POLYGON-BRIDGE-A": "0x1b79B302132370B434fb7807b36CB72FB0510aD5", + "AAVE-CLAIM-A": "0x611C1FA59Aa1d6352c4C8bD44882063c6aEE85E0", + "AAVE-STAKE-A": "0xf73c94402bc24148b744083ed02654eec2c37d5b", + "G-UNISWAP-A": "0x2fca923c7535083f25f761dcf289d7d81f024dda", + "INST-STAKING-A": "0x37a63939e128d284e0eae5d3e517aad44f5204d4", + "AAVE-V2-IMPORT-B": "0x6fe05374924830B6aC98849f75A3D5766E51Ef10", + "COMPOUND-IMPORT-B": "0xdA101870ca6136539628F28041E1B55baf4EB6C0", + "INSTAPOOL-A": "0x5806Af7AB22E2916fA582Ff05731Bf7C682387B2", + "MAKERDAO-CLAIM-A": "0x2f8cBE650af98602a215b6482F2aD60893C5A4E8", + "WETH-A": "0x22075fa719eFb02Ca3cF298AFa9C974B7465E5D3", + "REFINANCE-A": "0x9eA34bE6dA51aa9F6408FeA79c946FDCFA424442", + "INST-A": "0x52C2C4a0db049255fF345EB9D3Fb1f555b7a924A" + }, + "137" : { + "1INCH-A": "0xC0d9210496afE9763F5d8cEb8deFfBa817232A9e", + "AAVE-V2-A": "0xE84d8010Afc3663919F44685cB53ED88866da3eE", + "AUTHORITY-A": "0xf73C94402BC24148b744083eD02654EEc2C37D5B", + "BASIC-A": "0x1cAF5EC802ca602E98139AD96A8f2B7BC524264E", + "AAVE-CLAIM-A": "0xC7Cb1dE2721BFC0E0DA1b9D526bCdC54eF1C0eFC", + "PARASWAP-A": "0xFb3a1D56eD56F046721B9aCa749895100754578b" + } }, - "137" : { - "1INCH-A": "0xC0d9210496afE9763F5d8cEb8deFfBa817232A9e", - "AAVE-V2-A": "0xE84d8010Afc3663919F44685cB53ED88866da3eE", - "AUTHORITY-A": "0xf73C94402BC24148b744083eD02654EEc2C37D5B", - "BASIC-A": "0x1cAF5EC802ca602E98139AD96A8f2B7BC524264E", - "AAVE-CLAIM-A": "0xC7Cb1dE2721BFC0E0DA1b9D526bCdC54eF1C0eFC", - "PARASWAP-A": "0xFb3a1D56eD56F046721B9aCa749895100754578b" + "mappings": { + "InstaMappingController": "0xDdd075D5e1024901E4038461e1e4BbC3A48a08d4" } } diff --git a/scripts/deployInstaMappingController.js b/scripts/deployInstaMappingController.js new file mode 100644 index 00000000..f6919bb2 --- /dev/null +++ b/scripts/deployInstaMappingController.js @@ -0,0 +1,36 @@ +const hre = require('hardhat') +const { ethers } = hre + +async function main () { + if (hre.network.name === 'mainnet') { + console.log( + '\n\n Deploying Contracts to mainnet. Hit ctrl + c to abort' + ) + } else if (hre.network.name === 'hardhat') { + console.log( + '\n\n Deploying Contracts to hardhat.' + ) + } + + const InstaMappingController = await ethers.getContractFactory('InstaMappingController') + const instaMappingController = await InstaMappingController.deploy() + await instaMappingController.deployed() + + console.log('InstaMappingController deployed: ', instaMappingController.address) + + if (hre.network.name === 'mainnet') { + await hre.run('verify:verify', { + address: instaMappingController.address, + constructorArguments: [] + }) + } else if (hre.network.name === 'hardhat') { + console.log("Contracts deployed.") + } +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/deployMappingContract.js b/scripts/deployMappingContract.js new file mode 100644 index 00000000..7fce2acd --- /dev/null +++ b/scripts/deployMappingContract.js @@ -0,0 +1,38 @@ +const hre = require('hardhat') +const { ethers } = hre + +async function main () { + if (hre.network.name === 'mainnet') { + console.log( + '\n\n Deploying Contracts to mainnet. Hit ctrl + c to abort' + ) + } else if (hre.network.name === 'hardhat') { + console.log( + '\n\n Deploying Contracts to hardhat.' + ) + } + + const mappingContract = "CONTRACT_NAME" + + const InstaProtocolMapping = await ethers.getContractFactory(mappingContract) + const instaProtocolMapping = await InstaProtocolMapping.deploy() + await instaProtocolMapping.deployed() + + console.log(`${mappingContract} deployed: `, instaProtocolMapping.address) + + if (hre.network.name === 'mainnet') { + await hre.run('verify:verify', { + address: instaProtocolMapping.address, + constructorArguments: [] + }) + } else if (hre.network.name === 'hardhat') { + console.log("Contracts deployed.") + } +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) From 9d60daa894d1e6022d9d5fe8c68c56f6aa228be3 Mon Sep 17 00:00:00 2001 From: Thrilok Kumar Date: Tue, 3 Aug 2021 21:45:03 +0530 Subject: [PATCH 44/45] 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 45/45] 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",