log the eth and lqty gains from stability pool operations. general tidy up

This commit is contained in:
Edward Mulraney 2021-06-09 21:02:06 +01:00
parent 7588733d98
commit 4ea58ba50d
6 changed files with 163 additions and 82 deletions

View File

@ -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

View File

@ -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);
}

View File

@ -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,

View File

@ -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";

View File

@ -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;
};

View File

@ -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);
});
});