From c0507e9264cd53f2d6411568aa96dc7096962209 Mon Sep 17 00:00:00 2001 From: Edward Mulraney Date: Tue, 22 Jun 2021 13:37:48 +0100 Subject: [PATCH] add hint helper functions to Liquity resolver --- contracts/protocols/mainnet/liquity.sol | 74 ++++++++++++++++++++++++- test/liquity.js | 39 +++++++++++++ 2 files changed, 111 insertions(+), 2 deletions(-) diff --git a/contracts/protocols/mainnet/liquity.sol b/contracts/protocols/mainnet/liquity.sol index c22f149..5e3907e 100644 --- a/contracts/protocols/mainnet/liquity.sol +++ b/contracts/protocols/mainnet/liquity.sol @@ -30,13 +30,53 @@ interface PoolLike { function getETH() external view returns (uint); } -contract DSMath { +interface HintHelpersLike { + function computeNominalCR(uint _coll, uint _debt) external pure returns (uint); + function computeCR(uint _coll, uint _debt, uint _price) external pure returns (uint); + function getApproxHint(uint _CR, uint _numTrials, uint _inputRandomSeed) external view returns ( + address hintAddress, + uint diff, + uint latestRandomSeed + ); + function getRedemptionHints(uint _LUSDamount, uint _price, uint _maxIterations) external view returns ( + address firstHint, + uint partialRedemptionHintNICR, + uint truncatedLUSDamount + ); +} + +interface SortedTrovesLike { + function getSize() external view returns (uint256); + function findInsertPosition(uint256 _ICR, address _prevId, address _nextId) external view returns (address, address); +} + +contract Math { + /* DSMath add */ function add(uint x, uint y) internal pure returns (uint z) { require((z = x + y) >= x, "math-not-safe"); } + + /* DSMath mul */ + function mul(uint x, uint y) internal pure returns (uint z) { + require(y == 0 || (z = x * y) / y == x, "math-not-safe"); + } + + /* Uniswap V2 sqrt */ + function sqrt(uint y) internal pure returns (uint z) { + if (y > 3) { + z = y; + uint x = y / 2 + 1; + while (x < z) { + z = x; + x = (y / x + x) / 2; + } + } else if (y != 0) { + z = 1; + } + } } -contract Helpers is DSMath { +contract Helpers is Math { TroveManagerLike internal constant troveManager = TroveManagerLike(0xA39739EF8b0231DbFA0DcdA07d7e29faAbCf4bb2); @@ -52,6 +92,12 @@ contract Helpers is DSMath { PoolLike internal constant defaultPool = PoolLike(0x896a3F03176f05CFbb4f006BfCd8723F2B0D741C); + HintHelpersLike internal constant hintHelpers = + HintHelpersLike(0xE84251b93D9524E0d2e621Ba7dc7cb3579F997C0); + + SortedTrovesLike internal constant sortedTroves = + SortedTrovesLike(0x8FdD3fbFEb32b28fb73555518f8b361bCeA741A6); + struct Trove { uint collateral; uint debt; @@ -120,6 +166,30 @@ contract Resolver is Helpers { bool isInRecoveryMode = troveManager.checkRecoveryMode(oracleEthPrice); return System(borrowFee, ethTvl, tcr, isInRecoveryMode); } + + function getTrovePositionHints(uint collateral, uint debt, uint searchIterations) external view returns ( + address upperHint, + address lowerHint + ) { + // See: https://github.com/liquity/dev#supplying-hints-to-trove-operations + uint nominalCr = hintHelpers.computeNominalCR(collateral, debt); + searchIterations = searchIterations == 0 ? mul(10, sqrt(sortedTroves.getSize())) : searchIterations; + (address hintAddress, ,) = hintHelpers.getApproxHint(nominalCr, searchIterations, 3); + return sortedTroves.findInsertPosition(nominalCr, hintAddress, hintAddress); + } + + function getRedemptionPositionHints(uint amount, uint oracleEthPrice, uint searchIterations) external view returns ( + uint partialHintNicr, + address firstHint, + address upperHint, + address lowerHint + ) { + // See: https://github.com/liquity/dev#hints-for-redeemcollateral + (firstHint, partialHintNicr, ) = hintHelpers.getRedemptionHints(amount, oracleEthPrice, 0); + searchIterations = searchIterations == 0 ? mul(10, sqrt(sortedTroves.getSize())) : searchIterations; + (address hintAddress, ,) = hintHelpers.getApproxHint(partialHintNicr, searchIterations, 3); + (upperHint, lowerHint) = sortedTroves.findInsertPosition(partialHintNicr, hintAddress, hintAddress); + } } contract InstaLiquityResolver is Resolver { diff --git a/test/liquity.js b/test/liquity.js index b4a9170..69dee3a 100644 --- a/test/liquity.js +++ b/test/liquity.js @@ -112,6 +112,45 @@ describe("InstaLiquityResolver", () => { expect(systemState).to.eql(expectedSystemState); }); }); + + describe("getTrovePositionHints()", () => { + it("returns the upper and lower address of Troves nearest to the given Trove", async () => { + const collateral = hre.ethers.utils.parseEther("10"); + const debt = hre.ethers.utils.parseUnits("5000", 18); + const searchIterations = 10; + const [upperHint, lowerHint] = await liquity.getTrovePositionHints( + collateral, + debt, + searchIterations + ); + + expect(upperHint).eq("0xbf9a4eCC4151f28C03100bA2C0555a3D3e439e69"); + expect(lowerHint).eq("0xa4FC81A7AB93360543eb1e814D0127f466012CED"); + }); + }); + + describe("getRedemptionPositionHints()", () => { + it("returns the upper and lower address of the range of Troves to be redeemed against the given amount", async () => { + const amount = hre.ethers.utils.parseUnits("10000", 18); // 10,000 LUSD + const oracleEthPrice = await liquityPriceOracle.callStatic.fetchPrice(); + const searchIterations = 10; + const [ + partialRedemptionHintNicr, + firstHint, + upperHint, + lowerHint, + ] = await liquity.getRedemptionPositionHints( + amount, + oracleEthPrice, + searchIterations + ); + + expect(partialRedemptionHintNicr).eq("69529933762909647"); + expect(firstHint).eq("0xc16aDd8bA17ab81B27e930Da8a67848120565d8c"); + expect(upperHint).eq("0x66882C005188F0F4d95825ED7A7F78ed3055f167"); + expect(lowerHint).eq("0x0C22C11a8ed4C23ffD19629283548B1692b58e92"); + }); + }); }); const resetHardhatBlockNumber = async (blockNumber) => {