From 06114a840b2f1288ee54e50c8a548652ec4091b0 Mon Sep 17 00:00:00 2001 From: Rick Date: Tue, 20 Apr 2021 19:50:39 +0100 Subject: [PATCH 01/58] Add hintExamples script --- packages/contracts/hardhat.config.js | 2 +- packages/contracts/utils/hintExamples.js | 70 ++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 packages/contracts/utils/hintExamples.js diff --git a/packages/contracts/hardhat.config.js b/packages/contracts/hardhat.config.js index a8a748055..8f31df2f2 100644 --- a/packages/contracts/hardhat.config.js +++ b/packages/contracts/hardhat.config.js @@ -1,6 +1,6 @@ require("@nomiclabs/hardhat-truffle5"); require("@nomiclabs/hardhat-ethers"); -require("@nomiclabs/hardhat-etherscan"); +// require("@nomiclabs/hardhat-etherscan"); require("solidity-coverage"); require("hardhat-gas-reporter"); diff --git a/packages/contracts/utils/hintExamples.js b/packages/contracts/utils/hintExamples.js new file mode 100644 index 000000000..f7f96039d --- /dev/null +++ b/packages/contracts/utils/hintExamples.js @@ -0,0 +1,70 @@ + +const { TestHelper: th } = require("../utils/testHelpers.js") +const dh = require("./deploymentHelpers.js") +const [alice, bob, carol] = await ethers.provider.listAccounts() + +async function main() { + const coreContracts = await dh.deployLiquityCoreHardhat() + const ARBITRARY_ADDRESS = "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419" + const LQTYContracts = await dh.deployLQTYContractsHardhat( + ARBITRARY_ADDRESS, + ARBITRARY_ADDRESS, + ARBITRARY_ADDRESS + ) + + const { troveManager, borrowerOperations, hintHelpers, sortedTroves, priceFeedTestnet } = coreContracts + + await dh.connectCoreContracts(coreContracts, LQTYContracts) + await dh.connectLQTYContracts(LQTYContracts) + await dh.connectLQTYContractsToCore(LQTYContracts, coreContracts) + + // Examples of off-chain hint calculation for Open Trove + + // Off-chain calculations + const toWei = web3.utils.toWei + const toBN = web3.utils.toBN + + const price = toBN(toWei('2500')) + await priceFeedTestnet.setPrice(price) + + const LUSDAmount = toBN(toWei('2500')) // borrower wants to withdraw 2500 LUSD + const ETHColl = toBN(toWei('5')) // borrower wants to lock 5 ETH collateral + + // Call deployed TroveManager contract to read the liquidation reserve and latest borrowing fee + const liquidationReserve = await troveManager.LUSD_GAS_COMPENSATION() + const expectedFee = await troveManager.getBorrowingFeeWithDecay(LUSDAmount) + + // Total debt of the new trove = LUSD amount drawn, plus fee, plus the liquidation reserve + const expectedDebt = LUSDAmount.add(expectedFee).add(liquidationReserve) + + // Get the nominal NICR of the new trove + const _1e20 = toBN(toWei('100')) + const NICR = ETHColl.mul(_1e20).div(expectedDebt) + + // Get an approximate address hint from the deployed HintHelper contract. Use (15 * number of troves) trials + // to get an approx. hint that is close to the right position. + const numTroves = await sortedTroves.getSize() + const numTrials = numTroves.mul(toBN('15')) + const { 0: approxHint } = await hintHelpers.getApproxHint(NICR, numTrials, 42) // random seed of 42 + + // Use the approximate hint to get the exact upper and lower hints from the deployed SortedTroves contract + const { 0: upperHint, 1: lowerHint } = await sortedTroves.findInsertPosition(NICR, approxHint, approxHint) + + // Finally, call openTrove with the exact upperHint and lowerHint + const maxFee = '5'.concat('0'.repeat(16)) // Slippage protection: 5% + await borrowerOperations.openTrove(maxFee, LUSDAmount, upperHint, lowerHint, { value: ETHColl }) + + console.log(await troveManager.getCurrentICR(alice, price)) + + + + // --- adjust trove --- +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error); + process.exit(1); + }); + From 4e80ab64bee58dfad409299beab566046220339b Mon Sep 17 00:00:00 2001 From: Daniel Simon Date: Tue, 27 Apr 2021 20:19:12 +0700 Subject: [PATCH 02/58] fix: untangle StabilityDeposit events in subgraph They used to always come in the order of: 1. ETHGainWithdrawn 2. UserDepositUpdated At some point the order was swapped in _some_ StabilityPool functions, but not all of them. Let's virtually restore the original order in the subgraph mappings. --- packages/subgraph/schema.graphql | 3 ++ .../subgraph/src/mappings/StabilityPool.ts | 35 ++++++++++++++++++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/packages/subgraph/schema.graphql b/packages/subgraph/schema.graphql index af033de9a..9626caa3d 100644 --- a/packages/subgraph/schema.graphql +++ b/packages/subgraph/schema.graphql @@ -33,6 +33,9 @@ type Global @entity { "Only used internally as temporary storage. Will always be null in queries" currentRedemption: Redemption + + "Only used internally as temporary storage. Will always be null in queries" + tmpDepositUpdate: BigInt } type SystemState @entity { diff --git a/packages/subgraph/src/mappings/StabilityPool.ts b/packages/subgraph/src/mappings/StabilityPool.ts index 07b5a1814..976d49bb8 100644 --- a/packages/subgraph/src/mappings/StabilityPool.ts +++ b/packages/subgraph/src/mappings/StabilityPool.ts @@ -1,22 +1,55 @@ +import { BigInt } from "@graphprotocol/graph-ts"; + import { UserDepositChanged, ETHGainWithdrawn } from "../../generated/templates/StabilityPool/StabilityPool"; +import { BIGINT_ZERO } from "../utils/bignumbers"; + +import { getGlobal } from "../entities/Global"; + import { updateStabilityDeposit, withdrawCollateralGainFromStabilityDeposit } from "../entities/StabilityDeposit"; +// Read the value of tmpDepositUpdate from the Global entity, and replace it with: +// - null, if it wasn't null +// - valueToSetIfNull if it was null +// +// Returns the value of tmpDepositUpdate before the swap. +function swapTmpDepositUpdate(valueToSetIfNull: BigInt): BigInt | null { + let global = getGlobal(); + + let tmpDepositUpdate = global.tmpDepositUpdate; + global.tmpDepositUpdate = tmpDepositUpdate == null ? valueToSetIfNull : null; + global.save(); + + return tmpDepositUpdate; +} + export function handleUserDepositChanged(event: UserDepositChanged): void { - updateStabilityDeposit(event, event.params._depositor, event.params._newDeposit); + let ethGainWithdrawn = swapTmpDepositUpdate(event.params._newDeposit); + + if (ethGainWithdrawn != null) { + updateStabilityDeposit(event, event.params._depositor, event.params._newDeposit); + } } export function handleETHGainWithdrawn(event: ETHGainWithdrawn): void { + // Leave a non-null dummy value to signal to handleUserDepositChanged() + // that ETH gains have been withdrawn + let depositUpdate = swapTmpDepositUpdate(BIGINT_ZERO); + withdrawCollateralGainFromStabilityDeposit( event, event.params._depositor, event.params._ETH, event.params._LUSDLoss ); + + if (depositUpdate != null) { + updateStabilityDeposit(event, event.params._depositor, depositUpdate as BigInt); + } } From ff8371d92fb50e1979cbb39631a16f8c6358d45d Mon Sep 17 00:00:00 2001 From: David Anguera Date: Tue, 27 Apr 2021 16:06:13 +0200 Subject: [PATCH 03/58] lqty apr tooltip fixed --- packages/dev-frontend/src/components/Stability/Yield.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/dev-frontend/src/components/Stability/Yield.tsx b/packages/dev-frontend/src/components/Stability/Yield.tsx index 4f6c9775f..dfb81e9a4 100644 --- a/packages/dev-frontend/src/components/Stability/Yield.tsx +++ b/packages/dev-frontend/src/components/Stability/Yield.tsx @@ -41,6 +41,7 @@ export const Yield: React.FC = () => { const remainingLqtyOneYear = remainingStabilityPoolLQTYReward.mul(yearlyHalvingSchedule); const remainingLqtyInUSD = remainingLqtyOneYear.mul(lqtyPrice); const aprPercentage = remainingLqtyInUSD.div(lusdInStabilityPool).mul(100); + const remainingLqtyInUSDTooltip = remainingStabilityPoolLQTYReward.mul(lqtyPrice); if (aprPercentage.isZero) return null; @@ -61,7 +62,7 @@ export const Yield: React.FC = () => { ($ - {remainingLqtyInUSD.shorten()} * 50% / ${lusdInStabilityPool.shorten()}) * 100 = + {remainingLqtyInUSDTooltip.shorten()} * 50% / ${lusdInStabilityPool.shorten()}) * 100 = {aprPercentage.toString(2)}% From 379b9c6a3cf495fa1d80ba4cfc36e8fb1d7accee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Fingen?= Date: Tue, 27 Apr 2021 19:24:39 +0200 Subject: [PATCH 04/58] subgraph: Add tokens --- packages/subgraph/schema.graphql | 58 ++++++++++++++ packages/subgraph/src/entities/SystemState.ts | 1 + packages/subgraph/src/entities/Token.ts | 33 ++++++++ .../subgraph/src/entities/TokenBalance.ts | 76 +++++++++++++++++++ packages/subgraph/src/mappings/Token.ts | 11 +++ .../subgraph/src/mappings/TroveManager.ts | 11 ++- packages/subgraph/src/utils/constants.ts | 1 + packages/subgraph/subgraph.yaml | 60 ++++++++++++++- 8 files changed, 249 insertions(+), 2 deletions(-) create mode 100644 packages/subgraph/src/entities/Token.ts create mode 100644 packages/subgraph/src/entities/TokenBalance.ts create mode 100644 packages/subgraph/src/mappings/Token.ts create mode 100644 packages/subgraph/src/utils/constants.ts diff --git a/packages/subgraph/schema.graphql b/packages/subgraph/schema.graphql index af033de9a..dbc2a826f 100644 --- a/packages/subgraph/schema.graphql +++ b/packages/subgraph/schema.graphql @@ -51,6 +51,8 @@ type SystemState @entity { tokensInStabilityPool: BigDecimal! collSurplusPoolBalance: BigDecimal! + LUSDTotalSupply: BigDecimal! + cause: Change @derivedFrom(field: "systemStateAfter") } @@ -67,6 +69,9 @@ type User @entity { liquidations: [Liquidation!]! @derivedFrom(field: "liquidator") redemptions: [Redemption!]! @derivedFrom(field: "redeemer") + + balances: [TokenBalance!] @derivedFrom(field: "owner") + allowances: [TokenAllowance!] @derivedFrom(field: "owner") } enum TroveStatus { @@ -284,3 +289,56 @@ type LqtyStakeChange implements Change @entity { issuanceGain: BigDecimal redemptionGain: BigDecimal } + +type Token @entity { + id: ID! + name: String! + symbol: String! + totalSupply: BigDecimal! + balances: [TokenBalance!] @derivedFrom(field: "token") + allowances: [TokenAllowance!] @derivedFrom(field: "token") + changes: [TokenChange!]! @derivedFrom(field: "token") +} + +type TokenBalance @entity { + id: ID! + token: Token! + owner: User! + balance: BigDecimal! + changes: [TokenChange!]! @derivedFrom(field: "tokenBalance") +} + +type TokenAllowance @entity { + id: ID! + token: Token! + owner: User! + spender: User! + value: BigDecimal! +} + +enum TokenOperation { + tokenMinted + tokenBurned + tokenApproved + tokenTransferred +} + +type TokenChange implements Change @entity { + id: ID! + sequenceNumber: Int! + transaction: Transaction! + systemStateBefore: SystemState! + systemStateAfter: SystemState! + + token: Token! + tokenBalance: TokenBalance! + tokenOperation: TokenOperation! + + totalSupplyBefore: BigDecimal! + totalSupplyChange: BigDecimal! + totalSupplyAfter: BigDecimal! + + balanceBefore: BigDecimal! + balanceChange: BigDecimal! + balanceAfter: BigDecimal! +} diff --git a/packages/subgraph/src/entities/SystemState.ts b/packages/subgraph/src/entities/SystemState.ts index 78b8bea59..f06f91f8f 100644 --- a/packages/subgraph/src/entities/SystemState.ts +++ b/packages/subgraph/src/entities/SystemState.ts @@ -37,6 +37,7 @@ export function getCurrentSystemState(): SystemState { newSystemState.totalDebt = DECIMAL_ZERO; newSystemState.tokensInStabilityPool = DECIMAL_ZERO; newSystemState.collSurplusPoolBalance = DECIMAL_ZERO; + newSystemState.LUSDTotalSupply = DECIMAL_ZERO; newSystemState.save(); let global = getGlobal(); diff --git a/packages/subgraph/src/entities/Token.ts b/packages/subgraph/src/entities/Token.ts new file mode 100644 index 000000000..13a8730b6 --- /dev/null +++ b/packages/subgraph/src/entities/Token.ts @@ -0,0 +1,33 @@ +import { Address } from "@graphprotocol/graph-ts"; + +import { Token } from "../../generated/schema"; +import { ERC20 } from "../../generated/templates/Token/ERC20" +import { DECIMAL_ZERO } from "../utils/bignumbers"; + +export function createToken(address: Address, name: string, symbol: string): Token { + let id = address.toHexString(); + let token = new Token(id); + token.name = name; + token.symbol = symbol; + token.totalSupply = DECIMAL_ZERO; + token.save(); + + return token; +} + +export function getToken(_token: Address): Token { + let id = _token.toHexString(); + let tokenOrNull = Token.load(id); + + if (tokenOrNull != null) { + return tokenOrNull as Token; + } else { + // Bind the contract to the address that emitted the event + let contract = ERC20.bind(_token); + + // Access state variables and functions by calling them + let name = contract.name(); + let symbol = contract.symbol(); + return createToken(_token, name, symbol); + } +} diff --git a/packages/subgraph/src/entities/TokenBalance.ts b/packages/subgraph/src/entities/TokenBalance.ts new file mode 100644 index 000000000..2051f2198 --- /dev/null +++ b/packages/subgraph/src/entities/TokenBalance.ts @@ -0,0 +1,76 @@ +import { ethereum, Address, BigInt } from "@graphprotocol/graph-ts"; + +import { DECIMAL_ZERO, decimalize } from "../utils/bignumbers"; +import { ZERO_ADDRESS } from "../utils/constants"; + +import { TokenBalance } from "../../generated/schema"; + +import { getUser } from "./User"; +import { getToken } from "./Token"; +//import { beginChange, initChange, finishChange } from "./Change"; + +export function getTokenBalance(_token: Address, _owner: Address): TokenBalance { + let id = _token.toHexString() + _owner.toHexString(); + let user = getUser(_owner); + let balanceOrNull = TokenBalance.load(id); + + if (balanceOrNull != null) { + return balanceOrNull as TokenBalance; + } else { + let newBalance = new TokenBalance(id); + + newBalance.token = _token.toHexString(); + newBalance.owner = user.id; + newBalance.balance = DECIMAL_ZERO; + newBalance.save(); + + return newBalance; + } +} + +export function updateBalance(_event: ethereum.Event, _from: Address, _to: Address, _value: BigInt): void { + let tokenAddress = _event.address; + let token = getToken(tokenAddress); + let decimalValue = decimalize(_value); + + if (_from.toHexString() == ZERO_ADDRESS) { // mint + // increase total supply + token.totalSupply = token.totalSupply.plus(decimalValue); + token.save(); + } else { + // decrease from balance + let tokenBalanceFrom = getTokenBalance(tokenAddress, _from); + tokenBalanceFrom.balance = tokenBalanceFrom.balance.minus(decimalValue); + tokenBalanceFrom.save(); + } + if (_to.toHexString() == ZERO_ADDRESS) { // burn + // decrease total supply + token.totalSupply = token.totalSupply.minus(decimalValue); + token.save(); + } else { + // increase to balance + let tokenBalanceTo = getTokenBalance(tokenAddress, _to); + tokenBalanceTo.balance = tokenBalanceTo.balance.plus(decimalValue); + tokenBalanceTo.save(); + } + + // TODO + /* + let tokenChange = startLQTYTokenChange(_event); + tokenChange.token = token.id; + tokenChange.operation = getOperationType(token, nextTokenAmount); + tokenChange.amountBefore = token.amount; + tokenChange.amountChange = nextTokenAmount.minus(token.amount); + tokenChange.amountAfter = nextTokenAmount; + + token.amount = nextTokenAmount; + + handleLQTYTokenChange(tokenChange); + + finishLQTYTokenChange(tokenChange); + */ +} + +export function updateAllowance(_event: ethereum.Event, _owner: Address, _spender: Address, _value: BigInt): void { + let tokenAddress = _event.address; +} diff --git a/packages/subgraph/src/mappings/Token.ts b/packages/subgraph/src/mappings/Token.ts new file mode 100644 index 000000000..ae4dd88eb --- /dev/null +++ b/packages/subgraph/src/mappings/Token.ts @@ -0,0 +1,11 @@ +import { Transfer, Approval } from '../../generated/templates/LQTYToken/LQTYToken'; + +import { updateBalance, updateAllowance } from "../entities/TokenBalance"; + +export function handleTokenTransfer(event: Transfer): void { + updateBalance(event, event.params.from, event.params.to, event.params.value); +} + +export function handleTokenApproval(event: Approval): void { + updateAllowance(event, event.params.owner, event.params.spender, event.params.value); +} diff --git a/packages/subgraph/src/mappings/TroveManager.ts b/packages/subgraph/src/mappings/TroveManager.ts index ba6c8550f..c7f8029e0 100644 --- a/packages/subgraph/src/mappings/TroveManager.ts +++ b/packages/subgraph/src/mappings/TroveManager.ts @@ -8,13 +8,15 @@ import { StabilityPoolAddressChanged, CollSurplusPoolAddressChanged, PriceFeedAddressChanged, - LQTYStakingAddressChanged + LQTYStakingAddressChanged, + LUSDTokenAddressChanged, } from "../../generated/TroveManager/TroveManager"; import { BorrowerOperations, StabilityPool, CollSurplusPool, LQTYStaking + Token, } from "../../generated/templates"; import { BIGINT_ZERO } from "../utils/bignumbers"; @@ -25,6 +27,7 @@ import { finishCurrentLiquidation } from "../entities/Liquidation"; import { finishCurrentRedemption } from "../entities/Redemption"; import { updateTrove } from "../entities/Trove"; import { updatePriceFeedAddress, updateTotalRedistributed } from "../entities/Global"; +import { createToken } from "../entities/Token"; export function handleBorrowerOperationsAddressChanged( event: BorrowerOperationsAddressChanged @@ -44,6 +47,12 @@ export function handleLQTYStakingAddressChanged(event: LQTYStakingAddressChanged LQTYStaking.create(event.params._lqtyStakingAddress); } +export function handleLUSDTokenAddressChanged(event: LUSDTokenAddressChanged): void { + let tokenAddress = event.params._newLUSDTokenAddress + Token.create(tokenAddress); + createToken(tokenAddress, "LUSD Stablecoin", "LUSD"); +} + export function handlePriceFeedAddressChanged(event: PriceFeedAddressChanged): void { updatePriceFeedAddress(event.params._newPriceFeedAddress); } diff --git a/packages/subgraph/src/utils/constants.ts b/packages/subgraph/src/utils/constants.ts new file mode 100644 index 000000000..f78f4aa03 --- /dev/null +++ b/packages/subgraph/src/utils/constants.ts @@ -0,0 +1 @@ +export let ZERO_ADDRESS = '0x' + '0'.repeat(40); diff --git a/packages/subgraph/subgraph.yaml b/packages/subgraph/subgraph.yaml index 88a5c1c6d..512d3c6f1 100644 --- a/packages/subgraph/subgraph.yaml +++ b/packages/subgraph/subgraph.yaml @@ -8,8 +8,9 @@ dataSources: kind: ethereum/contract network: mainnet source: + address: "0xA39739EF8b0231DbFA0DcdA07d7e29faAbCf4bb2" #address: "0x56fcdA0436E5C7a33ee5bfe292f11AC66429Eb5c" - address: "0x6645E03DA2a711f780af7cCE1019Cb9a9135C898" + #address: "0x6645E03DA2a711f780af7cCE1019Cb9a9135C898" abi: TroveManager # startBlock: 8110721 mapping: @@ -43,6 +44,8 @@ dataSources: handler: handlePriceFeedAddressChanged - event: LQTYStakingAddressChanged(address) handler: handleLQTYStakingAddressChanged + - event: LUSDTokenAddressChanged(address) + handler: handleLUSDTokenAddressChanged - event: TroveUpdated(indexed address,uint256,uint256,uint256,uint8) handler: handleTroveUpdated - event: TroveLiquidated(indexed address,uint256,uint256,uint8) @@ -51,6 +54,35 @@ dataSources: handler: handleLiquidation - event: Redemption(uint256,uint256,uint256,uint256) handler: handleRedemption + - name: LQTYToken + kind: ethereum/contract + network: mainnet + source: + address: "0x6DEA81C8171D0bA574754EF6F8b412F2Ed88c54D" + #address: "0x3f74634451f613693f221b1fef42D442bAE7B470" + abi: LQTYToken + startBlock: 12178551 + mapping: + file: ./src/mappings/Token.ts + language: wasm/assemblyscript + kind: ethereum/events + apiVersion: 0.0.4 + entities: + - Global + - User + - Transaction + - Token + - TokenChange + abis: + - name: LQTYToken + file: ../lib-ethers/abi/LQTYToken.json + - name: ERC20 + file: ../lib-ethers/abi/ERC20Mock.json + eventHandlers: + - event: Transfer(indexed address,indexed address,uint256) + handler: handleTokenTransfer + - event: Approval(indexed address,indexed address,uint256) + handler: handleTokenApproval templates: - name: BorrowerOperations kind: ethereum/contract @@ -159,3 +191,29 @@ templates: handler: handleStakeChanged - event: StakingGainsWithdrawn(indexed address,uint256,uint256) handler: handleStakeGainsWithdrawn + - name: Token + kind: ethereum/contract + network: mainnet + source: + abi: IERC20 + mapping: + file: ./src/mappings/Token.ts + language: wasm/assemblyscript + kind: ethereum/events + apiVersion: 0.0.4 + entities: + - Global + - User + - Transaction + - Token + - TokenChange + abis: + - name: IERC20 + file: ../lib-ethers/abi/IERC20.json + - name: ERC20 + file: ../lib-ethers/abi/ERC20Mock.json + eventHandlers: + - event: Transfer(indexed address,indexed address,uint256) + handler: handleTokenTransfer + - event: Approval(indexed address,indexed address,uint256) + handler: handleTokenApproval From 7d449602bd9df323292300e7588390ceb6553adb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Fingen?= Date: Tue, 27 Apr 2021 22:13:24 +0200 Subject: [PATCH 05/58] contracts: Add token allowances --- .../subgraph/src/entities/TokenAllowance.ts | 37 +++++++++++++++++++ .../subgraph/src/entities/TokenBalance.ts | 4 -- 2 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 packages/subgraph/src/entities/TokenAllowance.ts diff --git a/packages/subgraph/src/entities/TokenAllowance.ts b/packages/subgraph/src/entities/TokenAllowance.ts new file mode 100644 index 000000000..2bb8ea5ef --- /dev/null +++ b/packages/subgraph/src/entities/TokenAllowance.ts @@ -0,0 +1,37 @@ +import { ethereum, Address, BigInt } from "@graphprotocol/graph-ts"; + +import { DECIMAL_ZERO, decimalize } from "../utils/bignumbers"; + +import { TokenAllowance } from "../../generated/schema"; + +import { getUser } from "./User"; + +export function getTokenAllowance(_token: Address, _owner: Address, _spender: Address): TokenAllowance { + let id = _token.toHexString() + _owner.toHexString() + _spender.toHexString(); + let owner = getUser(_owner); + let spender = getUser(_spender); + let allowanceOrNull = TokenAllowance.load(id); + + if (allowanceOrNull != null) { + return allowanceOrNull as TokenAllowance; + } else { + let newAllowance = new TokenAllowance(id); + + newAllowance.token = _token.toHexString(); + newAllowance.owner = owner.id; + newAllowance.spender = spender.id; + newAllowance.value = DECIMAL_ZERO; + newAllowance.save(); + + return newAllowance; + } +} + +export function updateAllowance(_event: ethereum.Event, _owner: Address, _spender: Address, _value: BigInt): void { + let tokenAddress = _event.address; + let decimalValue = decimalize(_value); + + let tokenAllowance = getTokenAllowance(tokenAddress, _owner, _spender); + tokenAllowance.value = decimalValue; + tokenAllowance.save(); +} diff --git a/packages/subgraph/src/entities/TokenBalance.ts b/packages/subgraph/src/entities/TokenBalance.ts index 2051f2198..9adb6e092 100644 --- a/packages/subgraph/src/entities/TokenBalance.ts +++ b/packages/subgraph/src/entities/TokenBalance.ts @@ -70,7 +70,3 @@ export function updateBalance(_event: ethereum.Event, _from: Address, _to: Addre finishLQTYTokenChange(tokenChange); */ } - -export function updateAllowance(_event: ethereum.Event, _owner: Address, _spender: Address, _value: BigInt): void { - let tokenAddress = _event.address; -} From 50edec65cbe81c76ada891f584906bafc5618896 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Fingen?= Date: Tue, 27 Apr 2021 23:02:44 +0200 Subject: [PATCH 06/58] subgraph: Add tokens, historic total supply --- packages/subgraph/schema.graphql | 14 ++------ packages/subgraph/src/entities/SystemState.ts | 1 - packages/subgraph/src/entities/Token.ts | 32 +++++++++++++++++-- .../subgraph/src/entities/TokenBalance.ts | 26 +++------------ packages/subgraph/src/mappings/Token.ts | 3 +- .../subgraph/src/mappings/TroveManager.ts | 2 +- 6 files changed, 40 insertions(+), 38 deletions(-) diff --git a/packages/subgraph/schema.graphql b/packages/subgraph/schema.graphql index dbc2a826f..b1af45328 100644 --- a/packages/subgraph/schema.graphql +++ b/packages/subgraph/schema.graphql @@ -51,8 +51,6 @@ type SystemState @entity { tokensInStabilityPool: BigDecimal! collSurplusPoolBalance: BigDecimal! - LUSDTotalSupply: BigDecimal! - cause: Change @derivedFrom(field: "systemStateAfter") } @@ -305,7 +303,6 @@ type TokenBalance @entity { token: Token! owner: User! balance: BigDecimal! - changes: [TokenChange!]! @derivedFrom(field: "tokenBalance") } type TokenAllowance @entity { @@ -317,10 +314,8 @@ type TokenAllowance @entity { } enum TokenOperation { - tokenMinted - tokenBurned - tokenApproved - tokenTransferred + mintTokens + burnTokens } type TokenChange implements Change @entity { @@ -331,14 +326,9 @@ type TokenChange implements Change @entity { systemStateAfter: SystemState! token: Token! - tokenBalance: TokenBalance! tokenOperation: TokenOperation! totalSupplyBefore: BigDecimal! totalSupplyChange: BigDecimal! totalSupplyAfter: BigDecimal! - - balanceBefore: BigDecimal! - balanceChange: BigDecimal! - balanceAfter: BigDecimal! } diff --git a/packages/subgraph/src/entities/SystemState.ts b/packages/subgraph/src/entities/SystemState.ts index f06f91f8f..78b8bea59 100644 --- a/packages/subgraph/src/entities/SystemState.ts +++ b/packages/subgraph/src/entities/SystemState.ts @@ -37,7 +37,6 @@ export function getCurrentSystemState(): SystemState { newSystemState.totalDebt = DECIMAL_ZERO; newSystemState.tokensInStabilityPool = DECIMAL_ZERO; newSystemState.collSurplusPoolBalance = DECIMAL_ZERO; - newSystemState.LUSDTotalSupply = DECIMAL_ZERO; newSystemState.save(); let global = getGlobal(); diff --git a/packages/subgraph/src/entities/Token.ts b/packages/subgraph/src/entities/Token.ts index 13a8730b6..bf3b5c7d7 100644 --- a/packages/subgraph/src/entities/Token.ts +++ b/packages/subgraph/src/entities/Token.ts @@ -1,9 +1,11 @@ -import { Address } from "@graphprotocol/graph-ts"; +import { ethereum, Address, BigDecimal } from "@graphprotocol/graph-ts"; -import { Token } from "../../generated/schema"; +import { Token, TokenChange } from "../../generated/schema"; import { ERC20 } from "../../generated/templates/Token/ERC20" import { DECIMAL_ZERO } from "../utils/bignumbers"; +import { beginChange, initChange, finishChange } from "./Change"; + export function createToken(address: Address, name: string, symbol: string): Token { let id = address.toHexString(); let token = new Token(id); @@ -31,3 +33,29 @@ export function getToken(_token: Address): Token { return createToken(_token, name, symbol); } } + +function startTokenChange(event: ethereum.Event): TokenChange { + let sequenceNumber = beginChange(event); + let tokenChange = new TokenChange(sequenceNumber.toString()); + initChange(tokenChange, event, sequenceNumber); + return tokenChange; +} + +function finishTokenChange(tokenChange: TokenChange): void { + finishChange(tokenChange); + tokenChange.save(); +} + +export function changeToken(_event: ethereum.Event, _token: Token, _newTotalSupply: BigDecimal, _operation: string): void { + let tokenChange = startTokenChange(_event); + tokenChange.token = _token.id; + tokenChange.tokenOperation = _operation; + tokenChange.totalSupplyBefore = _token.totalSupply; + tokenChange.totalSupplyChange = _newTotalSupply.minus(_token.totalSupply); + tokenChange.totalSupplyAfter = _newTotalSupply; + + _token.totalSupply = _newTotalSupply; + _token.save(); + + finishTokenChange(tokenChange); +} diff --git a/packages/subgraph/src/entities/TokenBalance.ts b/packages/subgraph/src/entities/TokenBalance.ts index 9adb6e092..57c01ed99 100644 --- a/packages/subgraph/src/entities/TokenBalance.ts +++ b/packages/subgraph/src/entities/TokenBalance.ts @@ -6,7 +6,7 @@ import { ZERO_ADDRESS } from "../utils/constants"; import { TokenBalance } from "../../generated/schema"; import { getUser } from "./User"; -import { getToken } from "./Token"; +import { getToken, changeToken } from "./Token"; //import { beginChange, initChange, finishChange } from "./Change"; export function getTokenBalance(_token: Address, _owner: Address): TokenBalance { @@ -35,8 +35,8 @@ export function updateBalance(_event: ethereum.Event, _from: Address, _to: Addre if (_from.toHexString() == ZERO_ADDRESS) { // mint // increase total supply - token.totalSupply = token.totalSupply.plus(decimalValue); - token.save(); + let newTotalSupply = token.totalSupply.plus(decimalValue); + changeToken(_event, token, newTotalSupply, "mintTokens"); } else { // decrease from balance let tokenBalanceFrom = getTokenBalance(tokenAddress, _from); @@ -45,28 +45,12 @@ export function updateBalance(_event: ethereum.Event, _from: Address, _to: Addre } if (_to.toHexString() == ZERO_ADDRESS) { // burn // decrease total supply - token.totalSupply = token.totalSupply.minus(decimalValue); - token.save(); + let newTotalSupply = token.totalSupply.minus(decimalValue); + changeToken(_event, token, newTotalSupply, "burnTokens"); } else { // increase to balance let tokenBalanceTo = getTokenBalance(tokenAddress, _to); tokenBalanceTo.balance = tokenBalanceTo.balance.plus(decimalValue); tokenBalanceTo.save(); } - - // TODO - /* - let tokenChange = startLQTYTokenChange(_event); - tokenChange.token = token.id; - tokenChange.operation = getOperationType(token, nextTokenAmount); - tokenChange.amountBefore = token.amount; - tokenChange.amountChange = nextTokenAmount.minus(token.amount); - tokenChange.amountAfter = nextTokenAmount; - - token.amount = nextTokenAmount; - - handleLQTYTokenChange(tokenChange); - - finishLQTYTokenChange(tokenChange); - */ } diff --git a/packages/subgraph/src/mappings/Token.ts b/packages/subgraph/src/mappings/Token.ts index ae4dd88eb..9c8b8488b 100644 --- a/packages/subgraph/src/mappings/Token.ts +++ b/packages/subgraph/src/mappings/Token.ts @@ -1,6 +1,7 @@ import { Transfer, Approval } from '../../generated/templates/LQTYToken/LQTYToken'; -import { updateBalance, updateAllowance } from "../entities/TokenBalance"; +import { updateBalance } from "../entities/TokenBalance"; +import { updateAllowance } from "../entities/TokenAllowance"; export function handleTokenTransfer(event: Transfer): void { updateBalance(event, event.params.from, event.params.to, event.params.value); diff --git a/packages/subgraph/src/mappings/TroveManager.ts b/packages/subgraph/src/mappings/TroveManager.ts index c7f8029e0..fc64308d4 100644 --- a/packages/subgraph/src/mappings/TroveManager.ts +++ b/packages/subgraph/src/mappings/TroveManager.ts @@ -15,7 +15,7 @@ import { BorrowerOperations, StabilityPool, CollSurplusPool, - LQTYStaking + LQTYStaking, Token, } from "../../generated/templates"; From 3705542d3e028abb56d7f62ed628b3b31d7cf8b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Fingen?= Date: Wed, 28 Apr 2021 10:59:50 +0200 Subject: [PATCH 07/58] subgraph: Remove token changes --- packages/subgraph/schema.graphql | 21 -------------- packages/subgraph/src/entities/Token.ts | 28 +------------------ .../subgraph/src/entities/TokenBalance.ts | 11 ++++---- packages/subgraph/subgraph.yaml | 2 -- 4 files changed, 6 insertions(+), 56 deletions(-) diff --git a/packages/subgraph/schema.graphql b/packages/subgraph/schema.graphql index b1af45328..d071b111f 100644 --- a/packages/subgraph/schema.graphql +++ b/packages/subgraph/schema.graphql @@ -295,7 +295,6 @@ type Token @entity { totalSupply: BigDecimal! balances: [TokenBalance!] @derivedFrom(field: "token") allowances: [TokenAllowance!] @derivedFrom(field: "token") - changes: [TokenChange!]! @derivedFrom(field: "token") } type TokenBalance @entity { @@ -312,23 +311,3 @@ type TokenAllowance @entity { spender: User! value: BigDecimal! } - -enum TokenOperation { - mintTokens - burnTokens -} - -type TokenChange implements Change @entity { - id: ID! - sequenceNumber: Int! - transaction: Transaction! - systemStateBefore: SystemState! - systemStateAfter: SystemState! - - token: Token! - tokenOperation: TokenOperation! - - totalSupplyBefore: BigDecimal! - totalSupplyChange: BigDecimal! - totalSupplyAfter: BigDecimal! -} diff --git a/packages/subgraph/src/entities/Token.ts b/packages/subgraph/src/entities/Token.ts index bf3b5c7d7..72d2ae019 100644 --- a/packages/subgraph/src/entities/Token.ts +++ b/packages/subgraph/src/entities/Token.ts @@ -1,6 +1,6 @@ import { ethereum, Address, BigDecimal } from "@graphprotocol/graph-ts"; -import { Token, TokenChange } from "../../generated/schema"; +import { Token } from "../../generated/schema"; import { ERC20 } from "../../generated/templates/Token/ERC20" import { DECIMAL_ZERO } from "../utils/bignumbers"; @@ -33,29 +33,3 @@ export function getToken(_token: Address): Token { return createToken(_token, name, symbol); } } - -function startTokenChange(event: ethereum.Event): TokenChange { - let sequenceNumber = beginChange(event); - let tokenChange = new TokenChange(sequenceNumber.toString()); - initChange(tokenChange, event, sequenceNumber); - return tokenChange; -} - -function finishTokenChange(tokenChange: TokenChange): void { - finishChange(tokenChange); - tokenChange.save(); -} - -export function changeToken(_event: ethereum.Event, _token: Token, _newTotalSupply: BigDecimal, _operation: string): void { - let tokenChange = startTokenChange(_event); - tokenChange.token = _token.id; - tokenChange.tokenOperation = _operation; - tokenChange.totalSupplyBefore = _token.totalSupply; - tokenChange.totalSupplyChange = _newTotalSupply.minus(_token.totalSupply); - tokenChange.totalSupplyAfter = _newTotalSupply; - - _token.totalSupply = _newTotalSupply; - _token.save(); - - finishTokenChange(tokenChange); -} diff --git a/packages/subgraph/src/entities/TokenBalance.ts b/packages/subgraph/src/entities/TokenBalance.ts index 57c01ed99..4f1dd3634 100644 --- a/packages/subgraph/src/entities/TokenBalance.ts +++ b/packages/subgraph/src/entities/TokenBalance.ts @@ -6,8 +6,7 @@ import { ZERO_ADDRESS } from "../utils/constants"; import { TokenBalance } from "../../generated/schema"; import { getUser } from "./User"; -import { getToken, changeToken } from "./Token"; -//import { beginChange, initChange, finishChange } from "./Change"; +import { getToken } from "./Token"; export function getTokenBalance(_token: Address, _owner: Address): TokenBalance { let id = _token.toHexString() + _owner.toHexString(); @@ -35,8 +34,8 @@ export function updateBalance(_event: ethereum.Event, _from: Address, _to: Addre if (_from.toHexString() == ZERO_ADDRESS) { // mint // increase total supply - let newTotalSupply = token.totalSupply.plus(decimalValue); - changeToken(_event, token, newTotalSupply, "mintTokens"); + token.totalSupply = token.totalSupply.plus(decimalValue); + token.save(); } else { // decrease from balance let tokenBalanceFrom = getTokenBalance(tokenAddress, _from); @@ -45,8 +44,8 @@ export function updateBalance(_event: ethereum.Event, _from: Address, _to: Addre } if (_to.toHexString() == ZERO_ADDRESS) { // burn // decrease total supply - let newTotalSupply = token.totalSupply.minus(decimalValue); - changeToken(_event, token, newTotalSupply, "burnTokens"); + token.totalSupply = token.totalSupply.minus(decimalValue); + token.save(); } else { // increase to balance let tokenBalanceTo = getTokenBalance(tokenAddress, _to); diff --git a/packages/subgraph/subgraph.yaml b/packages/subgraph/subgraph.yaml index 512d3c6f1..1ce586b7a 100644 --- a/packages/subgraph/subgraph.yaml +++ b/packages/subgraph/subgraph.yaml @@ -72,7 +72,6 @@ dataSources: - User - Transaction - Token - - TokenChange abis: - name: LQTYToken file: ../lib-ethers/abi/LQTYToken.json @@ -206,7 +205,6 @@ templates: - User - Transaction - Token - - TokenChange abis: - name: IERC20 file: ../lib-ethers/abi/IERC20.json From 79db565fa7c51f05629c98250aeb6f9678db1134 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Fingen?= Date: Wed, 28 Apr 2021 11:02:28 +0200 Subject: [PATCH 08/58] subgraph: Remove dangling commas --- packages/subgraph/src/mappings/TroveManager.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/subgraph/src/mappings/TroveManager.ts b/packages/subgraph/src/mappings/TroveManager.ts index fc64308d4..50b20ddb7 100644 --- a/packages/subgraph/src/mappings/TroveManager.ts +++ b/packages/subgraph/src/mappings/TroveManager.ts @@ -9,14 +9,14 @@ import { CollSurplusPoolAddressChanged, PriceFeedAddressChanged, LQTYStakingAddressChanged, - LUSDTokenAddressChanged, + LUSDTokenAddressChanged } from "../../generated/TroveManager/TroveManager"; import { BorrowerOperations, StabilityPool, CollSurplusPool, LQTYStaking, - Token, + Token } from "../../generated/templates"; import { BIGINT_ZERO } from "../utils/bignumbers"; From 6bbf2e54bd75de0e7011a686b6f9861b084bf73a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Fingen?= Date: Wed, 28 Apr 2021 11:28:49 +0200 Subject: [PATCH 09/58] subgraph: Fix Token events import --- packages/subgraph/src/mappings/Token.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/subgraph/src/mappings/Token.ts b/packages/subgraph/src/mappings/Token.ts index 9c8b8488b..bfc556519 100644 --- a/packages/subgraph/src/mappings/Token.ts +++ b/packages/subgraph/src/mappings/Token.ts @@ -1,4 +1,4 @@ -import { Transfer, Approval } from '../../generated/templates/LQTYToken/LQTYToken'; +import { Transfer, Approval } from '../../generated/templates/Token/IERC20'; import { updateBalance } from "../entities/TokenBalance"; import { updateAllowance } from "../entities/TokenAllowance"; From bbe63e90bb0148ed5b7345dc36d5c44e57ac02fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Fingen?= Date: Wed, 28 Apr 2021 11:40:00 +0200 Subject: [PATCH 10/58] subgraph: Replace ERC20Mock abi with ERC20 --- packages/subgraph/abi/ERC20.json | 334 +++++++++++++++++++++++++++++++ packages/subgraph/subgraph.yaml | 4 +- 2 files changed, 336 insertions(+), 2 deletions(-) create mode 100644 packages/subgraph/abi/ERC20.json diff --git a/packages/subgraph/abi/ERC20.json b/packages/subgraph/abi/ERC20.json new file mode 100644 index 000000000..2059dc436 --- /dev/null +++ b/packages/subgraph/abi/ERC20.json @@ -0,0 +1,334 @@ +[ + { + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "symbol", + "type": "string" + }, + { + "internalType": "address", + "name": "initialAccount", + "type": "address" + }, + { + "internalType": "uint256", + "name": "initialBalance", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "burn", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "addedValue", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/packages/subgraph/subgraph.yaml b/packages/subgraph/subgraph.yaml index 1ce586b7a..7033e7f33 100644 --- a/packages/subgraph/subgraph.yaml +++ b/packages/subgraph/subgraph.yaml @@ -76,7 +76,7 @@ dataSources: - name: LQTYToken file: ../lib-ethers/abi/LQTYToken.json - name: ERC20 - file: ../lib-ethers/abi/ERC20Mock.json + file: ./abi/ERC20.json eventHandlers: - event: Transfer(indexed address,indexed address,uint256) handler: handleTokenTransfer @@ -209,7 +209,7 @@ templates: - name: IERC20 file: ../lib-ethers/abi/IERC20.json - name: ERC20 - file: ../lib-ethers/abi/ERC20Mock.json + file: ./abi/ERC20.json eventHandlers: - event: Transfer(indexed address,indexed address,uint256) handler: handleTokenTransfer From 19f67f86e8c0b9e23b966a612f6e461024219a3c Mon Sep 17 00:00:00 2001 From: David Anguera Date: Wed, 28 Apr 2021 13:05:24 +0200 Subject: [PATCH 11/58] update variable names --- packages/dev-frontend/src/components/Stability/Yield.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/dev-frontend/src/components/Stability/Yield.tsx b/packages/dev-frontend/src/components/Stability/Yield.tsx index dfb81e9a4..3dd531c85 100644 --- a/packages/dev-frontend/src/components/Stability/Yield.tsx +++ b/packages/dev-frontend/src/components/Stability/Yield.tsx @@ -39,9 +39,9 @@ export const Yield: React.FC = () => { const yearlyHalvingSchedule = 0.5; // 50% see LQTY distribution schedule for more info const remainingLqtyOneYear = remainingStabilityPoolLQTYReward.mul(yearlyHalvingSchedule); - const remainingLqtyInUSD = remainingLqtyOneYear.mul(lqtyPrice); - const aprPercentage = remainingLqtyInUSD.div(lusdInStabilityPool).mul(100); - const remainingLqtyInUSDTooltip = remainingStabilityPoolLQTYReward.mul(lqtyPrice); + const remainingLqtyOneYearInUSD = remainingLqtyOneYear.mul(lqtyPrice); + const aprPercentage = remainingLqtyOneYearInUSD.div(lusdInStabilityPool).mul(100); + const remainingLqtyInUSD = remainingStabilityPoolLQTYReward.mul(lqtyPrice); if (aprPercentage.isZero) return null; @@ -62,7 +62,7 @@ export const Yield: React.FC = () => { ($ - {remainingLqtyInUSDTooltip.shorten()} * 50% / ${lusdInStabilityPool.shorten()}) * 100 = + {remainingLqtyInUSD.shorten()} * 50% / ${lusdInStabilityPool.shorten()}) * 100 = {aprPercentage.toString(2)}% From 7f3f18892d2f9ab21452303d8433c0511bfc2398 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Fingen?= Date: Wed, 28 Apr 2021 23:16:38 +0200 Subject: [PATCH 12/58] fixup! subgraph: Remove token changes --- packages/subgraph/src/entities/Token.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/subgraph/src/entities/Token.ts b/packages/subgraph/src/entities/Token.ts index 72d2ae019..87cfe7580 100644 --- a/packages/subgraph/src/entities/Token.ts +++ b/packages/subgraph/src/entities/Token.ts @@ -4,8 +4,6 @@ import { Token } from "../../generated/schema"; import { ERC20 } from "../../generated/templates/Token/ERC20" import { DECIMAL_ZERO } from "../utils/bignumbers"; -import { beginChange, initChange, finishChange } from "./Change"; - export function createToken(address: Address, name: string, symbol: string): Token { let id = address.toHexString(); let token = new Token(id); From 3272d02cb33e67f352f2184069a665a2ee85e738 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Fingen?= Date: Wed, 28 Apr 2021 23:17:16 +0200 Subject: [PATCH 13/58] fixup! subgraph: Remove token changes --- packages/subgraph/src/entities/Token.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/subgraph/src/entities/Token.ts b/packages/subgraph/src/entities/Token.ts index 87cfe7580..13a8730b6 100644 --- a/packages/subgraph/src/entities/Token.ts +++ b/packages/subgraph/src/entities/Token.ts @@ -1,4 +1,4 @@ -import { ethereum, Address, BigDecimal } from "@graphprotocol/graph-ts"; +import { Address } from "@graphprotocol/graph-ts"; import { Token } from "../../generated/schema"; import { ERC20 } from "../../generated/templates/Token/ERC20" From 29dac183304bd0c7520cce278550853c057a0dad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Fingen?= Date: Wed, 28 Apr 2021 23:34:05 +0200 Subject: [PATCH 14/58] subgraph: Convert decimal values from tokens to integers --- packages/subgraph/schema.graphql | 6 +++--- packages/subgraph/src/entities/Token.ts | 4 ++-- packages/subgraph/src/entities/TokenAllowance.ts | 7 +++---- packages/subgraph/src/entities/TokenBalance.ts | 13 ++++++------- 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/packages/subgraph/schema.graphql b/packages/subgraph/schema.graphql index d071b111f..58a3e8b3a 100644 --- a/packages/subgraph/schema.graphql +++ b/packages/subgraph/schema.graphql @@ -292,7 +292,7 @@ type Token @entity { id: ID! name: String! symbol: String! - totalSupply: BigDecimal! + totalSupply: BigInt! balances: [TokenBalance!] @derivedFrom(field: "token") allowances: [TokenAllowance!] @derivedFrom(field: "token") } @@ -301,7 +301,7 @@ type TokenBalance @entity { id: ID! token: Token! owner: User! - balance: BigDecimal! + balance: BigInt! } type TokenAllowance @entity { @@ -309,5 +309,5 @@ type TokenAllowance @entity { token: Token! owner: User! spender: User! - value: BigDecimal! + value: BigInt! } diff --git a/packages/subgraph/src/entities/Token.ts b/packages/subgraph/src/entities/Token.ts index 13a8730b6..c757dd26e 100644 --- a/packages/subgraph/src/entities/Token.ts +++ b/packages/subgraph/src/entities/Token.ts @@ -2,14 +2,14 @@ import { Address } from "@graphprotocol/graph-ts"; import { Token } from "../../generated/schema"; import { ERC20 } from "../../generated/templates/Token/ERC20" -import { DECIMAL_ZERO } from "../utils/bignumbers"; +import { BIGINT_ZERO } from "../utils/bignumbers"; export function createToken(address: Address, name: string, symbol: string): Token { let id = address.toHexString(); let token = new Token(id); token.name = name; token.symbol = symbol; - token.totalSupply = DECIMAL_ZERO; + token.totalSupply = BIGINT_ZERO; token.save(); return token; diff --git a/packages/subgraph/src/entities/TokenAllowance.ts b/packages/subgraph/src/entities/TokenAllowance.ts index 2bb8ea5ef..5bc853e3f 100644 --- a/packages/subgraph/src/entities/TokenAllowance.ts +++ b/packages/subgraph/src/entities/TokenAllowance.ts @@ -1,6 +1,6 @@ import { ethereum, Address, BigInt } from "@graphprotocol/graph-ts"; -import { DECIMAL_ZERO, decimalize } from "../utils/bignumbers"; +import { BIGINT_ZERO } from "../utils/bignumbers"; import { TokenAllowance } from "../../generated/schema"; @@ -20,7 +20,7 @@ export function getTokenAllowance(_token: Address, _owner: Address, _spender: Ad newAllowance.token = _token.toHexString(); newAllowance.owner = owner.id; newAllowance.spender = spender.id; - newAllowance.value = DECIMAL_ZERO; + newAllowance.value = BIGINT_ZERO; newAllowance.save(); return newAllowance; @@ -29,9 +29,8 @@ export function getTokenAllowance(_token: Address, _owner: Address, _spender: Ad export function updateAllowance(_event: ethereum.Event, _owner: Address, _spender: Address, _value: BigInt): void { let tokenAddress = _event.address; - let decimalValue = decimalize(_value); let tokenAllowance = getTokenAllowance(tokenAddress, _owner, _spender); - tokenAllowance.value = decimalValue; + tokenAllowance.value = _value; tokenAllowance.save(); } diff --git a/packages/subgraph/src/entities/TokenBalance.ts b/packages/subgraph/src/entities/TokenBalance.ts index 4f1dd3634..5e8ba4b91 100644 --- a/packages/subgraph/src/entities/TokenBalance.ts +++ b/packages/subgraph/src/entities/TokenBalance.ts @@ -1,6 +1,6 @@ import { ethereum, Address, BigInt } from "@graphprotocol/graph-ts"; -import { DECIMAL_ZERO, decimalize } from "../utils/bignumbers"; +import { BIGINT_ZERO } from "../utils/bignumbers"; import { ZERO_ADDRESS } from "../utils/constants"; import { TokenBalance } from "../../generated/schema"; @@ -20,7 +20,7 @@ export function getTokenBalance(_token: Address, _owner: Address): TokenBalance newBalance.token = _token.toHexString(); newBalance.owner = user.id; - newBalance.balance = DECIMAL_ZERO; + newBalance.balance = BIGINT_ZERO; newBalance.save(); return newBalance; @@ -30,26 +30,25 @@ export function getTokenBalance(_token: Address, _owner: Address): TokenBalance export function updateBalance(_event: ethereum.Event, _from: Address, _to: Address, _value: BigInt): void { let tokenAddress = _event.address; let token = getToken(tokenAddress); - let decimalValue = decimalize(_value); if (_from.toHexString() == ZERO_ADDRESS) { // mint // increase total supply - token.totalSupply = token.totalSupply.plus(decimalValue); + token.totalSupply = token.totalSupply.plus(_value); token.save(); } else { // decrease from balance let tokenBalanceFrom = getTokenBalance(tokenAddress, _from); - tokenBalanceFrom.balance = tokenBalanceFrom.balance.minus(decimalValue); + tokenBalanceFrom.balance = tokenBalanceFrom.balance.minus(_value); tokenBalanceFrom.save(); } if (_to.toHexString() == ZERO_ADDRESS) { // burn // decrease total supply - token.totalSupply = token.totalSupply.minus(decimalValue); + token.totalSupply = token.totalSupply.minus(_value); token.save(); } else { // increase to balance let tokenBalanceTo = getTokenBalance(tokenAddress, _to); - tokenBalanceTo.balance = tokenBalanceTo.balance.plus(decimalValue); + tokenBalanceTo.balance = tokenBalanceTo.balance.plus(_value); tokenBalanceTo.save(); } } From 9011c405f3cbccab8ab845823852027c3d38ec02 Mon Sep 17 00:00:00 2001 From: Rick Date: Thu, 29 Apr 2021 12:12:06 +0100 Subject: [PATCH 15/58] contracts: Add hint examples script --- packages/contracts/utils/hintExamples.js | 75 ++++++++++++++++++++---- 1 file changed, 65 insertions(+), 10 deletions(-) diff --git a/packages/contracts/utils/hintExamples.js b/packages/contracts/utils/hintExamples.js index f7f96039d..7bcfbbe7f 100644 --- a/packages/contracts/utils/hintExamples.js +++ b/packages/contracts/utils/hintExamples.js @@ -1,9 +1,13 @@ const { TestHelper: th } = require("../utils/testHelpers.js") const dh = require("./deploymentHelpers.js") -const [alice, bob, carol] = await ethers.provider.listAccounts() + +// const [borrower, A, B, C] = (() => Array.from(Array(4), x => web3.eth.accounts.create().address))() async function main() { + const accounts = await web3.eth.getAccounts() + const [borrower, A, B] = accounts + const coreContracts = await dh.deployLiquityCoreHardhat() const ARBITRARY_ADDRESS = "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419" const LQTYContracts = await dh.deployLQTYContractsHardhat( @@ -20,12 +24,11 @@ async function main() { // Examples of off-chain hint calculation for Open Trove - // Off-chain calculations const toWei = web3.utils.toWei const toBN = web3.utils.toBN const price = toBN(toWei('2500')) - await priceFeedTestnet.setPrice(price) + await priceFeedTestnet.setPrice(toBN(toWei('2500'))) const LUSDAmount = toBN(toWei('2500')) // borrower wants to withdraw 2500 LUSD const ETHColl = toBN(toWei('5')) // borrower wants to lock 5 ETH collateral @@ -39,26 +42,78 @@ async function main() { // Get the nominal NICR of the new trove const _1e20 = toBN(toWei('100')) - const NICR = ETHColl.mul(_1e20).div(expectedDebt) + let NICR = ETHColl.mul(_1e20).div(expectedDebt) // Get an approximate address hint from the deployed HintHelper contract. Use (15 * number of troves) trials // to get an approx. hint that is close to the right position. - const numTroves = await sortedTroves.getSize() - const numTrials = numTroves.mul(toBN('15')) - const { 0: approxHint } = await hintHelpers.getApproxHint(NICR, numTrials, 42) // random seed of 42 + let numTroves = await sortedTroves.getSize() + let numTrials = numTroves.mul(toBN('15')) + let { 0: approxHint } = await hintHelpers.getApproxHint(NICR, numTrials, 42) // random seed of 42 // Use the approximate hint to get the exact upper and lower hints from the deployed SortedTroves contract - const { 0: upperHint, 1: lowerHint } = await sortedTroves.findInsertPosition(NICR, approxHint, approxHint) + let { 0: upperHint, 1: lowerHint } = await sortedTroves.findInsertPosition(NICR, approxHint, approxHint) // Finally, call openTrove with the exact upperHint and lowerHint const maxFee = '5'.concat('0'.repeat(16)) // Slippage protection: 5% await borrowerOperations.openTrove(maxFee, LUSDAmount, upperHint, lowerHint, { value: ETHColl }) - console.log(await troveManager.getCurrentICR(alice, price)) + // --- adjust trove --- + const collIncrease = toBN(toWei('1')) // borrower wants to add 1 ETH + const LUSDRepayment = toBN(toWei('230')) // borrower wants to repay 230 LUSD + // Get trove's current debt and coll + const {0: debt, 1: coll} = await troveManager.getEntireDebtAndColl(borrower) + + const newDebt = debt.sub(LUSDRepayment) + const newColl = coll.add(collIncrease) - // --- adjust trove --- + NICR = newColl.mul(_1e20).div(newDebt) + + // Get an approximate address hint from the deployed HintHelper contract. Use (15 * number of troves) trials + // to get an approx. hint that is close to the right position. + numTroves = await sortedTroves.getSize() + numTrials = numTroves.mul(toBN('15')) + ({0: approxHint} = await hintHelpers.getApproxHint(NICR, numTrials, 42)) + + // Use the approximate hint to get the exact upper and lower hints from the deployed SortedTroves contract + ({ 0: upperHint, 1: lowerHint } = await sortedTroves.findInsertPosition(NICR, approxHint, approxHint)) + + // Call adjustTrove with the exact upperHint and lowerHint + await borrowerOperations.adjustTrove(maxFee, 0, LUSDRepayment, false, upperHint, lowerHint, {value: collIncrease}) + + + // --- RedeemCollateral --- + + // Get the redemptions hints from the deployed HintHelpers contract + const redemptionhint = await hintHelpers.getRedemptionHints(LUSDAmount, price, 50) + + const {0: firstRedemptionHint, 1: partialRedemptionNewICR, 2: truncatedLUSDAmount} = redemptionhint + + // Get the approximate partial redemption hint + const { + hintAddress: approxPartialRedemptionHint, + latestRandomSeed + } = await contracts.hintHelpers.getApproxHint(partialRedemptionNewICR, numTrials, 42) + + /* Use the approximate partial redemption hint to get the exact partial redemption hint from the + * deployed SortedTroves contract + */ + const exactPartialRedemptionHint = (await sortedTroves.findInsertPosition(partialRedemptionNewICR, + approxPartialRedemptionHint, + approxPartialRedemptionHint)) + + /* Finally, perform the on-chain redemption, passing the truncated LUSD amount, the correct hints, and the expected + * ICR of the final partially redeemed trove in the sequence. + */ + await troveManager.redeemCollateral(truncatedLUSDAmount, + firstRedemptionHint, + exactPartialRedemptionHint[0], + exactPartialRedemptionHint[1], + partialRedemptionNewICR, + 0, maxFee, + { from: redeemer }, + ) } main() From ac7e8e5fcbed11575074999f691e298981c464c0 Mon Sep 17 00:00:00 2001 From: Daniel Simon Date: Thu, 29 Apr 2021 15:13:45 +0700 Subject: [PATCH 16/58] feat: generate subgraph manifest from a template --- packages/subgraph/.gitignore | 1 + packages/subgraph/package.json | 7 +- packages/subgraph/src/entities/Token.ts | 4 +- .../subgraph/src/entities/TokenAllowance.ts | 15 +++- .../subgraph/src/entities/TokenBalance.ts | 15 +++- .../src/mappings/BorrowerOperations.ts | 2 +- .../subgraph/src/mappings/CollSurplusPool.ts | 4 +- packages/subgraph/src/mappings/LqtyStake.ts | 5 +- .../subgraph/src/mappings/StabilityPool.ts | 5 +- packages/subgraph/src/mappings/Token.ts | 2 +- .../subgraph/src/mappings/TroveManager.ts | 39 +------- .../{subgraph.yaml => subgraph.yaml.js} | 90 +++++++++---------- 12 files changed, 78 insertions(+), 111 deletions(-) rename packages/subgraph/{subgraph.yaml => subgraph.yaml.js} (73%) diff --git a/packages/subgraph/.gitignore b/packages/subgraph/.gitignore index 874209833..32f59b741 100644 --- a/packages/subgraph/.gitignore +++ b/packages/subgraph/.gitignore @@ -1,3 +1,4 @@ node_modules /build /generated +/subgraph.yaml diff --git a/packages/subgraph/package.json b/packages/subgraph/package.json index 2d66ffbcd..a2a4f2dbf 100644 --- a/packages/subgraph/package.json +++ b/packages/subgraph/package.json @@ -5,7 +5,9 @@ "scripts": { "create": "graph create liquity/liquity-protocol --node https://api.thegraph.com/deploy/", "create-local": "graph create liquity/liquity-protocol --node http://127.0.0.1:8020", - "prepare": "graph codegen", + "prepare": "run-s prepare:*", + "prepare:manifest": "node subgraph.yaml.js", + "prepare:codegen": "graph codegen", "build": "graph build", "deploy": "graph deploy liquity/liquity-protocol --ipfs https://api.thegraph.com/ipfs/ --node https://api.thegraph.com/deploy/", "deploy-local": "graph deploy liquity/liquity-protocol --ipfs http://localhost:5001 --node http://127.0.0.1:8020", @@ -13,6 +15,7 @@ }, "devDependencies": { "@graphprotocol/graph-cli": "^0.20.0", - "@graphprotocol/graph-ts": "^0.20.0" + "@graphprotocol/graph-ts": "^0.20.0", + "npm-run-all": "^4.1.5" } } diff --git a/packages/subgraph/src/entities/Token.ts b/packages/subgraph/src/entities/Token.ts index c757dd26e..9b0e81bc7 100644 --- a/packages/subgraph/src/entities/Token.ts +++ b/packages/subgraph/src/entities/Token.ts @@ -1,10 +1,10 @@ import { Address } from "@graphprotocol/graph-ts"; import { Token } from "../../generated/schema"; -import { ERC20 } from "../../generated/templates/Token/ERC20" +import { ERC20 } from "../../generated/LUSDToken/ERC20"; import { BIGINT_ZERO } from "../utils/bignumbers"; -export function createToken(address: Address, name: string, symbol: string): Token { +function createToken(address: Address, name: string, symbol: string): Token { let id = address.toHexString(); let token = new Token(id); token.name = name; diff --git a/packages/subgraph/src/entities/TokenAllowance.ts b/packages/subgraph/src/entities/TokenAllowance.ts index 5bc853e3f..4edbb7515 100644 --- a/packages/subgraph/src/entities/TokenAllowance.ts +++ b/packages/subgraph/src/entities/TokenAllowance.ts @@ -6,8 +6,12 @@ import { TokenAllowance } from "../../generated/schema"; import { getUser } from "./User"; -export function getTokenAllowance(_token: Address, _owner: Address, _spender: Address): TokenAllowance { - let id = _token.toHexString() + _owner.toHexString() + _spender.toHexString(); +export function getTokenAllowance( + _token: Address, + _owner: Address, + _spender: Address +): TokenAllowance { + let id = _token.toHexString() + "-" + _owner.toHexString() + "-" + _spender.toHexString(); let owner = getUser(_owner); let spender = getUser(_spender); let allowanceOrNull = TokenAllowance.load(id); @@ -27,7 +31,12 @@ export function getTokenAllowance(_token: Address, _owner: Address, _spender: Ad } } -export function updateAllowance(_event: ethereum.Event, _owner: Address, _spender: Address, _value: BigInt): void { +export function updateAllowance( + _event: ethereum.Event, + _owner: Address, + _spender: Address, + _value: BigInt +): void { let tokenAddress = _event.address; let tokenAllowance = getTokenAllowance(tokenAddress, _owner, _spender); diff --git a/packages/subgraph/src/entities/TokenBalance.ts b/packages/subgraph/src/entities/TokenBalance.ts index 5e8ba4b91..46d559689 100644 --- a/packages/subgraph/src/entities/TokenBalance.ts +++ b/packages/subgraph/src/entities/TokenBalance.ts @@ -9,7 +9,7 @@ import { getUser } from "./User"; import { getToken } from "./Token"; export function getTokenBalance(_token: Address, _owner: Address): TokenBalance { - let id = _token.toHexString() + _owner.toHexString(); + let id = _token.toHexString() + "-" + _owner.toHexString(); let user = getUser(_owner); let balanceOrNull = TokenBalance.load(id); @@ -27,11 +27,17 @@ export function getTokenBalance(_token: Address, _owner: Address): TokenBalance } } -export function updateBalance(_event: ethereum.Event, _from: Address, _to: Address, _value: BigInt): void { +export function updateBalance( + _event: ethereum.Event, + _from: Address, + _to: Address, + _value: BigInt +): void { let tokenAddress = _event.address; let token = getToken(tokenAddress); - if (_from.toHexString() == ZERO_ADDRESS) { // mint + if (_from.toHexString() == ZERO_ADDRESS) { + // mint // increase total supply token.totalSupply = token.totalSupply.plus(_value); token.save(); @@ -41,7 +47,8 @@ export function updateBalance(_event: ethereum.Event, _from: Address, _to: Addre tokenBalanceFrom.balance = tokenBalanceFrom.balance.minus(_value); tokenBalanceFrom.save(); } - if (_to.toHexString() == ZERO_ADDRESS) { // burn + if (_to.toHexString() == ZERO_ADDRESS) { + // burn // decrease total supply token.totalSupply = token.totalSupply.minus(_value); token.save(); diff --git a/packages/subgraph/src/mappings/BorrowerOperations.ts b/packages/subgraph/src/mappings/BorrowerOperations.ts index 9c7938f96..214d8b67f 100644 --- a/packages/subgraph/src/mappings/BorrowerOperations.ts +++ b/packages/subgraph/src/mappings/BorrowerOperations.ts @@ -2,7 +2,7 @@ import { TroveManager } from "../../generated/TroveManager/TroveManager"; import { BorrowerOperations, TroveUpdated -} from "../../generated/templates/BorrowerOperations/BorrowerOperations"; +} from "../../generated/BorrowerOperations/BorrowerOperations"; import { getTroveOperationFromBorrowerOperation } from "../types/TroveOperation"; diff --git a/packages/subgraph/src/mappings/CollSurplusPool.ts b/packages/subgraph/src/mappings/CollSurplusPool.ts index b9e77d75d..3571eda98 100644 --- a/packages/subgraph/src/mappings/CollSurplusPool.ts +++ b/packages/subgraph/src/mappings/CollSurplusPool.ts @@ -1,6 +1,4 @@ -import { - CollBalanceUpdated -} from "../../generated/templates/CollSurplusPool/CollSurplusPool"; +import { CollBalanceUpdated } from "../../generated/CollSurplusPool/CollSurplusPool"; import { updateUserClaimColl } from "../entities/User"; diff --git a/packages/subgraph/src/mappings/LqtyStake.ts b/packages/subgraph/src/mappings/LqtyStake.ts index 1b73443a4..f10402ea9 100644 --- a/packages/subgraph/src/mappings/LqtyStake.ts +++ b/packages/subgraph/src/mappings/LqtyStake.ts @@ -1,7 +1,4 @@ -import { - StakeChanged, - StakingGainsWithdrawn -} from "../../generated/templates/LQTYStaking/LQTYStaking"; +import { StakeChanged, StakingGainsWithdrawn } from "../../generated/LQTYStaking/LQTYStaking"; import { updateStake, withdrawStakeGains } from "../entities/LqtyStake"; diff --git a/packages/subgraph/src/mappings/StabilityPool.ts b/packages/subgraph/src/mappings/StabilityPool.ts index 976d49bb8..c61522793 100644 --- a/packages/subgraph/src/mappings/StabilityPool.ts +++ b/packages/subgraph/src/mappings/StabilityPool.ts @@ -1,9 +1,6 @@ import { BigInt } from "@graphprotocol/graph-ts"; -import { - UserDepositChanged, - ETHGainWithdrawn -} from "../../generated/templates/StabilityPool/StabilityPool"; +import { UserDepositChanged, ETHGainWithdrawn } from "../../generated/StabilityPool/StabilityPool"; import { BIGINT_ZERO } from "../utils/bignumbers"; diff --git a/packages/subgraph/src/mappings/Token.ts b/packages/subgraph/src/mappings/Token.ts index bfc556519..5b081f32e 100644 --- a/packages/subgraph/src/mappings/Token.ts +++ b/packages/subgraph/src/mappings/Token.ts @@ -1,4 +1,4 @@ -import { Transfer, Approval } from '../../generated/templates/Token/IERC20'; +import { Transfer, Approval } from "../../generated/LUSDToken/ERC20"; import { updateBalance } from "../entities/TokenBalance"; import { updateAllowance } from "../entities/TokenAllowance"; diff --git a/packages/subgraph/src/mappings/TroveManager.ts b/packages/subgraph/src/mappings/TroveManager.ts index 50b20ddb7..00026232f 100644 --- a/packages/subgraph/src/mappings/TroveManager.ts +++ b/packages/subgraph/src/mappings/TroveManager.ts @@ -4,20 +4,8 @@ import { TroveLiquidated, Liquidation, Redemption, - BorrowerOperationsAddressChanged, - StabilityPoolAddressChanged, - CollSurplusPoolAddressChanged, - PriceFeedAddressChanged, - LQTYStakingAddressChanged, - LUSDTokenAddressChanged + PriceFeedAddressChanged } from "../../generated/TroveManager/TroveManager"; -import { - BorrowerOperations, - StabilityPool, - CollSurplusPool, - LQTYStaking, - Token -} from "../../generated/templates"; import { BIGINT_ZERO } from "../utils/bignumbers"; @@ -27,31 +15,6 @@ import { finishCurrentLiquidation } from "../entities/Liquidation"; import { finishCurrentRedemption } from "../entities/Redemption"; import { updateTrove } from "../entities/Trove"; import { updatePriceFeedAddress, updateTotalRedistributed } from "../entities/Global"; -import { createToken } from "../entities/Token"; - -export function handleBorrowerOperationsAddressChanged( - event: BorrowerOperationsAddressChanged -): void { - BorrowerOperations.create(event.params._newBorrowerOperationsAddress); -} - -export function handleStabilityPoolAddressChanged(event: StabilityPoolAddressChanged): void { - StabilityPool.create(event.params._stabilityPoolAddress); -} - -export function handleCollSurplusPoolAddressChanged(event: CollSurplusPoolAddressChanged): void { - CollSurplusPool.create(event.params._collSurplusPoolAddress); -} - -export function handleLQTYStakingAddressChanged(event: LQTYStakingAddressChanged): void { - LQTYStaking.create(event.params._lqtyStakingAddress); -} - -export function handleLUSDTokenAddressChanged(event: LUSDTokenAddressChanged): void { - let tokenAddress = event.params._newLUSDTokenAddress - Token.create(tokenAddress); - createToken(tokenAddress, "LUSD Stablecoin", "LUSD"); -} export function handlePriceFeedAddressChanged(event: PriceFeedAddressChanged): void { updatePriceFeedAddress(event.params._newPriceFeedAddress); diff --git a/packages/subgraph/subgraph.yaml b/packages/subgraph/subgraph.yaml.js similarity index 73% rename from packages/subgraph/subgraph.yaml rename to packages/subgraph/subgraph.yaml.js index 7033e7f33..b30ab69ba 100644 --- a/packages/subgraph/subgraph.yaml +++ b/packages/subgraph/subgraph.yaml.js @@ -1,6 +1,22 @@ +const fs = require("fs"); + +const { addresses } = require("@liquity/lib-ethers/deployments/mainnet.json"); +// const { addresses } = require("@liquity/lib-ethers/deployments/dev.json"); + +// https://etherscan.io/tx/0x0b612c6ffcef059a1cb9ceda83dee44a3d74e35d5018f1b8b486f3186cd7850e +const startBlock = 12178551; +// const startBlock = 0; + +const yaml = (strings, ...keys) => + strings + .flatMap((string, i) => [string, Array.isArray(keys[i]) ? keys[i].join("") : keys[i]]) + .join("") + .substring(1); // Skip initial newline + +const manifest = yaml` specVersion: 0.0.2 -description: Liquity Decentralized Borrowing Protocol -repository: https://github.com/liquity/subgraph +description: Liquity is a decentralized borrowing protocol offering interest-free liquidity against collateral in Ether. +repository: https://github.com/liquity/dev/tree/main/packages/subgraph schema: file: ./schema.graphql dataSources: @@ -8,11 +24,9 @@ dataSources: kind: ethereum/contract network: mainnet source: - address: "0xA39739EF8b0231DbFA0DcdA07d7e29faAbCf4bb2" - #address: "0x56fcdA0436E5C7a33ee5bfe292f11AC66429Eb5c" - #address: "0x6645E03DA2a711f780af7cCE1019Cb9a9135C898" abi: TroveManager - # startBlock: 8110721 + address: "${addresses.troveManager}" + startBlock: ${startBlock} mapping: file: ./src/mappings/TroveManager.ts language: wasm/assemblyscript @@ -34,18 +48,8 @@ dataSources: - name: PriceFeed file: ../lib-ethers/abi/PriceFeed.json eventHandlers: - - event: BorrowerOperationsAddressChanged(address) - handler: handleBorrowerOperationsAddressChanged - - event: StabilityPoolAddressChanged(address) - handler: handleStabilityPoolAddressChanged - - event: CollSurplusPoolAddressChanged(address) - handler: handleCollSurplusPoolAddressChanged - event: PriceFeedAddressChanged(address) handler: handlePriceFeedAddressChanged - - event: LQTYStakingAddressChanged(address) - handler: handleLQTYStakingAddressChanged - - event: LUSDTokenAddressChanged(address) - handler: handleLUSDTokenAddressChanged - event: TroveUpdated(indexed address,uint256,uint256,uint256,uint8) handler: handleTroveUpdated - event: TroveLiquidated(indexed address,uint256,uint256,uint8) @@ -54,40 +58,13 @@ dataSources: handler: handleLiquidation - event: Redemption(uint256,uint256,uint256,uint256) handler: handleRedemption - - name: LQTYToken - kind: ethereum/contract - network: mainnet - source: - address: "0x6DEA81C8171D0bA574754EF6F8b412F2Ed88c54D" - #address: "0x3f74634451f613693f221b1fef42D442bAE7B470" - abi: LQTYToken - startBlock: 12178551 - mapping: - file: ./src/mappings/Token.ts - language: wasm/assemblyscript - kind: ethereum/events - apiVersion: 0.0.4 - entities: - - Global - - User - - Transaction - - Token - abis: - - name: LQTYToken - file: ../lib-ethers/abi/LQTYToken.json - - name: ERC20 - file: ./abi/ERC20.json - eventHandlers: - - event: Transfer(indexed address,indexed address,uint256) - handler: handleTokenTransfer - - event: Approval(indexed address,indexed address,uint256) - handler: handleTokenApproval -templates: - name: BorrowerOperations kind: ethereum/contract network: mainnet source: abi: BorrowerOperations + address: "${addresses.borrowerOperations}" + startBlock: ${startBlock} mapping: file: ./src/mappings/BorrowerOperations.ts language: wasm/assemblyscript @@ -116,6 +93,8 @@ templates: network: mainnet source: abi: StabilityPool + address: "${addresses.stabilityPool}" + startBlock: ${startBlock} mapping: file: ./src/mappings/StabilityPool.ts language: wasm/assemblyscript @@ -144,6 +123,8 @@ templates: network: mainnet source: abi: CollSurplusPool + address: "${addresses.collSurplusPool}" + startBlock: ${startBlock} mapping: file: ./src/mappings/CollSurplusPool.ts language: wasm/assemblyscript @@ -169,6 +150,8 @@ templates: network: mainnet source: abi: LQTYStaking + address: "${addresses.lqtyStaking}" + startBlock: ${startBlock} mapping: file: ./src/mappings/LqtyStake.ts language: wasm/assemblyscript @@ -190,11 +173,18 @@ templates: handler: handleStakeChanged - event: StakingGainsWithdrawn(indexed address,uint256,uint256) handler: handleStakeGainsWithdrawn - - name: Token +${[ + ["LUSDToken", addresses.lusdToken], + ["LQTYToken", addresses.lqtyToken] +].map( + ([name, address]) => yaml` + - name: ${name} kind: ethereum/contract network: mainnet source: - abi: IERC20 + abi: ERC20 + address: "${address}" + startBlock: ${startBlock} mapping: file: ./src/mappings/Token.ts language: wasm/assemblyscript @@ -206,8 +196,6 @@ templates: - Transaction - Token abis: - - name: IERC20 - file: ../lib-ethers/abi/IERC20.json - name: ERC20 file: ./abi/ERC20.json eventHandlers: @@ -215,3 +203,7 @@ templates: handler: handleTokenTransfer - event: Approval(indexed address,indexed address,uint256) handler: handleTokenApproval +` +)}`; + +fs.writeFileSync("subgraph.yaml", manifest); From 56fa8b4cde1165f8615e097b0bb7cf1018a8c654 Mon Sep 17 00:00:00 2001 From: Daniel Simon Date: Fri, 30 Apr 2021 06:54:25 +0700 Subject: [PATCH 17/58] chore: rename subgraph to match listing on Explorer --- packages/subgraph/package.json | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/subgraph/package.json b/packages/subgraph/package.json index a2a4f2dbf..3c889273d 100644 --- a/packages/subgraph/package.json +++ b/packages/subgraph/package.json @@ -3,14 +3,13 @@ "version": "0.1.0", "private": true, "scripts": { - "create": "graph create liquity/liquity-protocol --node https://api.thegraph.com/deploy/", - "create-local": "graph create liquity/liquity-protocol --node http://127.0.0.1:8020", + "create-local": "graph create liquity/liquity --node http://127.0.0.1:8020", "prepare": "run-s prepare:*", "prepare:manifest": "node subgraph.yaml.js", "prepare:codegen": "graph codegen", "build": "graph build", - "deploy": "graph deploy liquity/liquity-protocol --ipfs https://api.thegraph.com/ipfs/ --node https://api.thegraph.com/deploy/", - "deploy-local": "graph deploy liquity/liquity-protocol --ipfs http://localhost:5001 --node http://127.0.0.1:8020", + "deploy": "graph deploy liquity/liquity --debug --ipfs https://api.thegraph.com/ipfs/ --node https://api.thegraph.com/deploy/", + "deploy-local": "graph deploy liquity/liquity --ipfs http://localhost:5001 --node http://127.0.0.1:8020", "graph": "graph" }, "devDependencies": { From 90e75f62ae7fe71412d0484ba5682e95a30a07df Mon Sep 17 00:00:00 2001 From: Edward Mulraney Date: Fri, 30 Apr 2021 18:53:02 +0100 Subject: [PATCH 18/58] fix broken lqty staking decrements --- packages/subgraph/src/entities/Global.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/subgraph/src/entities/Global.ts b/packages/subgraph/src/entities/Global.ts index d649a5908..686c78c48 100644 --- a/packages/subgraph/src/entities/Global.ts +++ b/packages/subgraph/src/entities/Global.ts @@ -154,10 +154,12 @@ export function handleLQTYStakeChange( } else if (stakeChange.stakeOperation == "stakeIncreased") { global.totalLQTYTokensStaked = global.totalLQTYTokensStaked.plus(stakeChange.amountChange); } else if (stakeChange.stakeOperation == "stakeDecreased") { - global.totalLQTYTokensStaked = global.totalLQTYTokensStaked.minus(stakeChange.amountChange); + // amountChange is a negative number + global.totalLQTYTokensStaked = global.totalLQTYTokensStaked.plus(stakeChange.amountChange); } else if (stakeChange.stakeOperation == "stakeRemoved") { global.numberOfActiveLQTYStakes--; - global.totalLQTYTokensStaked = global.totalLQTYTokensStaked.minus(stakeChange.amountChange); + // amountChange is a negative number + global.totalLQTYTokensStaked = global.totalLQTYTokensStaked.plus(stakeChange.amountChange); } global.save(); From d664bacce077aeb7672510f350a7478847edcdaf Mon Sep 17 00:00:00 2001 From: Edward Mulraney Date: Mon, 3 May 2021 12:09:03 +0100 Subject: [PATCH 19/58] simplify branching logic --- packages/subgraph/src/entities/Global.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/packages/subgraph/src/entities/Global.ts b/packages/subgraph/src/entities/Global.ts index 686c78c48..c3bb1d1b5 100644 --- a/packages/subgraph/src/entities/Global.ts +++ b/packages/subgraph/src/entities/Global.ts @@ -150,17 +150,10 @@ export function handleLQTYStakeChange( global.totalNumberOfLQTYStakes++; } global.numberOfActiveLQTYStakes++; - global.totalLQTYTokensStaked = global.totalLQTYTokensStaked.plus(stakeChange.amountChange); - } else if (stakeChange.stakeOperation == "stakeIncreased") { - global.totalLQTYTokensStaked = global.totalLQTYTokensStaked.plus(stakeChange.amountChange); - } else if (stakeChange.stakeOperation == "stakeDecreased") { - // amountChange is a negative number - global.totalLQTYTokensStaked = global.totalLQTYTokensStaked.plus(stakeChange.amountChange); } else if (stakeChange.stakeOperation == "stakeRemoved") { global.numberOfActiveLQTYStakes--; - // amountChange is a negative number - global.totalLQTYTokensStaked = global.totalLQTYTokensStaked.plus(stakeChange.amountChange); } + global.totalLQTYTokensStaked = global.totalLQTYTokensStaked.plus(stakeChange.amountChange); global.save(); } From f7302d69234bf46484a51a81929b6eb4279e3272 Mon Sep 17 00:00:00 2001 From: Daniel Simon Date: Tue, 4 May 2021 13:23:00 +0700 Subject: [PATCH 20/58] feat: add `startBlock` to deployment manifests --- .../sdk/lib-ethers.ethersliquityconnection.md | 1 + ...hers.ethersliquityconnection.startblock.md | 13 ++ .../deployments/default/goerli.json | 1 + .../lib-ethers/deployments/default/kovan.json | 1 + .../deployments/default/mainnet.json | 1 + .../deployments/default/rinkeby.json | 1 + .../deployments/default/ropsten.json | 1 + packages/lib-ethers/etc/lib-ethers.api.md | 1 + .../lib-ethers/src/EthersLiquityConnection.ts | 3 + packages/lib-ethers/src/contracts.ts | 1 + packages/lib-ethers/utils/deploy.ts | 112 ++++++++++-------- 11 files changed, 88 insertions(+), 48 deletions(-) create mode 100644 docs/sdk/lib-ethers.ethersliquityconnection.startblock.md diff --git a/docs/sdk/lib-ethers.ethersliquityconnection.md b/docs/sdk/lib-ethers.ethersliquityconnection.md index bd2b79641..7deb35c84 100644 --- a/docs/sdk/lib-ethers.ethersliquityconnection.md +++ b/docs/sdk/lib-ethers.ethersliquityconnection.md @@ -30,6 +30,7 @@ Exposed through [ReadableEthersLiquity.connection](./lib-ethers.readableethersli | [liquidityMiningLQTYRewardRate](./lib-ethers.ethersliquityconnection.liquiditymininglqtyrewardrate.md) | [Decimal](./lib-base.decimal.md) | Amount of LQTY collectively rewarded to stakers of the liquidity mining pool per second. | | [provider](./lib-ethers.ethersliquityconnection.provider.md) | [EthersProvider](./lib-ethers.ethersprovider.md) | Ethers Provider used for connecting to the network. | | [signer?](./lib-ethers.ethersliquityconnection.signer.md) | [EthersSigner](./lib-ethers.etherssigner.md) | (Optional) Ethers Signer used for sending transactions. | +| [startBlock](./lib-ethers.ethersliquityconnection.startblock.md) | number | Number of block in which the first Liquity contract was deployed. | | [totalStabilityPoolLQTYReward](./lib-ethers.ethersliquityconnection.totalstabilitypoollqtyreward.md) | [Decimal](./lib-base.decimal.md) | Total amount of LQTY allocated for rewarding stability depositors. | | [version](./lib-ethers.ethersliquityconnection.version.md) | string | Version of the Liquity contracts (Git commit hash). | diff --git a/docs/sdk/lib-ethers.ethersliquityconnection.startblock.md b/docs/sdk/lib-ethers.ethersliquityconnection.startblock.md new file mode 100644 index 000000000..a4a257713 --- /dev/null +++ b/docs/sdk/lib-ethers.ethersliquityconnection.startblock.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [@liquity/lib-ethers](./lib-ethers.md) > [EthersLiquityConnection](./lib-ethers.ethersliquityconnection.md) > [startBlock](./lib-ethers.ethersliquityconnection.startblock.md) + +## EthersLiquityConnection.startBlock property + +Number of block in which the first Liquity contract was deployed. + +Signature: + +```typescript +readonly startBlock: number; +``` diff --git a/packages/lib-ethers/deployments/default/goerli.json b/packages/lib-ethers/deployments/default/goerli.json index 76e9a1552..c55eb16a1 100644 --- a/packages/lib-ethers/deployments/default/goerli.json +++ b/packages/lib-ethers/deployments/default/goerli.json @@ -8,6 +8,7 @@ "_priceFeedIsTestnet": true, "_uniTokenIsMock": false, "_isDev": false, + "startBlock": 4547952, "addresses": { "activePool": "0x5948018DEeC14642E6127c5a3AC4081798bB73d8", "borrowerOperations": "0xa36bA16411AF139737E8E345Cd9422a47856bECa", diff --git a/packages/lib-ethers/deployments/default/kovan.json b/packages/lib-ethers/deployments/default/kovan.json index b5731fb86..903df9714 100644 --- a/packages/lib-ethers/deployments/default/kovan.json +++ b/packages/lib-ethers/deployments/default/kovan.json @@ -8,6 +8,7 @@ "_priceFeedIsTestnet": false, "_uniTokenIsMock": false, "_isDev": false, + "startBlock": 24134777, "addresses": { "activePool": "0x2FEB8CC8eD32117D1F39168543f29c30fdf10105", "borrowerOperations": "0xA289111CC4b306E3F5F15c654D4c318B3dA51813", diff --git a/packages/lib-ethers/deployments/default/mainnet.json b/packages/lib-ethers/deployments/default/mainnet.json index eb4c5d9fb..8cc61c129 100644 --- a/packages/lib-ethers/deployments/default/mainnet.json +++ b/packages/lib-ethers/deployments/default/mainnet.json @@ -8,6 +8,7 @@ "_priceFeedIsTestnet": false, "_uniTokenIsMock": false, "_isDev": false, + "startBlock": 12178551, "addresses": { "activePool": "0xDf9Eb223bAFBE5c5271415C75aeCD68C21fE3D7F", "borrowerOperations": "0x24179CD81c9e782A4096035f7eC97fB8B783e007", diff --git a/packages/lib-ethers/deployments/default/rinkeby.json b/packages/lib-ethers/deployments/default/rinkeby.json index e4104e803..d62dff24c 100644 --- a/packages/lib-ethers/deployments/default/rinkeby.json +++ b/packages/lib-ethers/deployments/default/rinkeby.json @@ -8,6 +8,7 @@ "_priceFeedIsTestnet": false, "_uniTokenIsMock": false, "_isDev": false, + "startBlock": 8341450, "addresses": { "activePool": "0xbA49275F8F890E7296F64b3e81F1Ada656030150", "borrowerOperations": "0x91656701b33eca6425A239930FccAA842D0E2031", diff --git a/packages/lib-ethers/deployments/default/ropsten.json b/packages/lib-ethers/deployments/default/ropsten.json index d72e2baba..648d60126 100644 --- a/packages/lib-ethers/deployments/default/ropsten.json +++ b/packages/lib-ethers/deployments/default/ropsten.json @@ -8,6 +8,7 @@ "_priceFeedIsTestnet": true, "_uniTokenIsMock": false, "_isDev": false, + "startBlock": 9961294, "addresses": { "activePool": "0x8bE79B54Bff7754B57294077c2B5017AF9f57dC2", "borrowerOperations": "0xfe9049E677C5773dd72ac7E19c38c68aB0891744", diff --git a/packages/lib-ethers/etc/lib-ethers.api.md b/packages/lib-ethers/etc/lib-ethers.api.md index 0219d74ed..0dcf0da6a 100644 --- a/packages/lib-ethers/etc/lib-ethers.api.md +++ b/packages/lib-ethers/etc/lib-ethers.api.md @@ -233,6 +233,7 @@ export interface EthersLiquityConnection extends EthersLiquityConnectionOptional readonly _priceFeedIsTestnet: boolean; readonly provider: EthersProvider; readonly signer?: EthersSigner; + readonly startBlock: number; readonly totalStabilityPoolLQTYReward: Decimal; readonly version: string; } diff --git a/packages/lib-ethers/src/EthersLiquityConnection.ts b/packages/lib-ethers/src/EthersLiquityConnection.ts index 828588125..641dc50e3 100644 --- a/packages/lib-ethers/src/EthersLiquityConnection.ts +++ b/packages/lib-ethers/src/EthersLiquityConnection.ts @@ -66,6 +66,9 @@ export interface EthersLiquityConnection extends EthersLiquityConnectionOptional /** Date when the Liquity contracts were deployed. */ readonly deploymentDate: Date; + /** Number of block in which the first Liquity contract was deployed. */ + readonly startBlock: number; + /** Time period (in seconds) after `deploymentDate` during which redemptions are disabled. */ readonly bootstrapPeriod: number; diff --git a/packages/lib-ethers/src/contracts.ts b/packages/lib-ethers/src/contracts.ts index 5ec874d96..55cf63231 100644 --- a/packages/lib-ethers/src/contracts.ts +++ b/packages/lib-ethers/src/contracts.ts @@ -224,6 +224,7 @@ export interface _LiquityDeploymentJSON { readonly addresses: _LiquityContractAddresses; readonly version: string; readonly deploymentDate: number; + readonly startBlock: number; readonly bootstrapPeriod: number; readonly totalStabilityPoolLQTYReward: string; readonly liquidityMiningLQTYRewardRate: string; diff --git a/packages/lib-ethers/utils/deploy.ts b/packages/lib-ethers/utils/deploy.ts index 3619b63f5..74d0708c6 100644 --- a/packages/lib-ethers/utils/deploy.ts +++ b/packages/lib-ethers/utils/deploy.ts @@ -25,12 +25,12 @@ export const setSilent = (s: boolean): void => { silent = s; }; -const deployContract = async ( +const deployContractAndGetBlockNumber = async ( deployer: Signer, getContractFactory: (name: string, signer: Signer) => Promise, contractName: string, ...args: unknown[] -) => { +): Promise<[address: string, blockNumber: number]> => { log(`Deploying ${contractName} ...`); const contract = await (await getContractFactory(contractName, deployer)).deploy(...args); @@ -45,17 +45,28 @@ const deployContract = async ( log(); - return contract.address; + return [contract.address, receipt.blockNumber]; }; +const deployContract: ( + ...p: Parameters +) => Promise = (...p) => deployContractAndGetBlockNumber(...p).then(([a]) => a); + const deployContracts = async ( deployer: Signer, getContractFactory: (name: string, signer: Signer) => Promise, priceFeedIsTestnet = true, overrides?: Overrides -): Promise> => { +): Promise<[addresses: Omit<_LiquityContractAddresses, "uniToken">, startBlock: number]> => { + const [activePoolAddress, startBlock] = await deployContractAndGetBlockNumber( + deployer, + getContractFactory, + "ActivePool", + { ...overrides } + ); + const addresses = { - activePool: await deployContract(deployer, getContractFactory, "ActivePool", { ...overrides }), + activePool: activePoolAddress, borrowerOperations: await deployContract(deployer, getContractFactory, "BorrowerOperations", { ...overrides }), @@ -95,40 +106,44 @@ const deployContracts = async ( unipool: await deployContract(deployer, getContractFactory, "Unipool", { ...overrides }) }; - return { - ...addresses, - lusdToken: await deployContract( - deployer, - getContractFactory, - "LUSDToken", - addresses.troveManager, - addresses.stabilityPool, - addresses.borrowerOperations, - { ...overrides } - ), + return [ + { + ...addresses, + lusdToken: await deployContract( + deployer, + getContractFactory, + "LUSDToken", + addresses.troveManager, + addresses.stabilityPool, + addresses.borrowerOperations, + { ...overrides } + ), - lqtyToken: await deployContract( - deployer, - getContractFactory, - "LQTYToken", - addresses.communityIssuance, - addresses.lqtyStaking, - addresses.lockupContractFactory, - Wallet.createRandom().address, // _bountyAddress (TODO: parameterize this) - addresses.unipool, // _lpRewardsAddress - Wallet.createRandom().address, // _multisigAddress (TODO: parameterize this) - { ...overrides } - ), + lqtyToken: await deployContract( + deployer, + getContractFactory, + "LQTYToken", + addresses.communityIssuance, + addresses.lqtyStaking, + addresses.lockupContractFactory, + Wallet.createRandom().address, // _bountyAddress (TODO: parameterize this) + addresses.unipool, // _lpRewardsAddress + Wallet.createRandom().address, // _multisigAddress (TODO: parameterize this) + { ...overrides } + ), - multiTroveGetter: await deployContract( - deployer, - getContractFactory, - "MultiTroveGetter", - addresses.troveManager, - addresses.sortedTroves, - { ...overrides } - ) - }; + multiTroveGetter: await deployContract( + deployer, + getContractFactory, + "MultiTroveGetter", + addresses.troveManager, + addresses.sortedTroves, + { ...overrides } + ) + }, + + startBlock + ]; }; export const deployTellorCaller = ( @@ -324,18 +339,19 @@ export const deployAndSetupContracts = async ( _uniTokenIsMock: !wethAddress, _isDev, - addresses: await deployContracts( - deployer, - getContractFactory, - _priceFeedIsTestnet, - overrides - ).then(async addresses => ({ - ...addresses, + ...(await deployContracts(deployer, getContractFactory, _priceFeedIsTestnet, overrides).then( + async ([addresses, startBlock]) => ({ + startBlock, + + addresses: { + ...addresses, - uniToken: await (wethAddress - ? createUniswapV2Pair(deployer, wethAddress, addresses.lusdToken, overrides) - : deployMockUniToken(deployer, getContractFactory, overrides)) - })) + uniToken: await (wethAddress + ? createUniswapV2Pair(deployer, wethAddress, addresses.lusdToken, overrides) + : deployMockUniToken(deployer, getContractFactory, overrides)) + } + }) + )) }; const contracts = _connectToContracts(deployer, deployment); From 930cb6fd8d8b2cb47808200700a19389e3a23672 Mon Sep 17 00:00:00 2001 From: Daniel Simon Date: Tue, 4 May 2021 13:41:13 +0700 Subject: [PATCH 21/58] feat: parameterize network in subgraph manifest template --- packages/subgraph/package.json | 3 +++ packages/subgraph/subgraph.yaml.js | 8 +++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/subgraph/package.json b/packages/subgraph/package.json index 3c889273d..1e3944176 100644 --- a/packages/subgraph/package.json +++ b/packages/subgraph/package.json @@ -7,6 +7,9 @@ "prepare": "run-s prepare:*", "prepare:manifest": "node subgraph.yaml.js", "prepare:codegen": "graph codegen", + "prepare-local": "run-s prepare-local:*", + "prepare-local:manifest": "node subgraph.yaml.js dev", + "prepare-local:codegen": "graph codegen", "build": "graph build", "deploy": "graph deploy liquity/liquity --debug --ipfs https://api.thegraph.com/ipfs/ --node https://api.thegraph.com/deploy/", "deploy-local": "graph deploy liquity/liquity --ipfs http://localhost:5001 --node http://127.0.0.1:8020", diff --git a/packages/subgraph/subgraph.yaml.js b/packages/subgraph/subgraph.yaml.js index b30ab69ba..414ad903e 100644 --- a/packages/subgraph/subgraph.yaml.js +++ b/packages/subgraph/subgraph.yaml.js @@ -1,11 +1,9 @@ const fs = require("fs"); -const { addresses } = require("@liquity/lib-ethers/deployments/mainnet.json"); -// const { addresses } = require("@liquity/lib-ethers/deployments/dev.json"); +const network = process.argv[2] || "mainnet"; +const { addresses, startBlock } = require(`@liquity/lib-ethers/deployments/${network}.json`); -// https://etherscan.io/tx/0x0b612c6ffcef059a1cb9ceda83dee44a3d74e35d5018f1b8b486f3186cd7850e -const startBlock = 12178551; -// const startBlock = 0; +console.log(`Preparing subgraph manifest for network "${network}"`); const yaml = (strings, ...keys) => strings From 2cced78037c2a7441bb106a6b095dfd7604ba493 Mon Sep 17 00:00:00 2001 From: Daniel Simon Date: Fri, 30 Apr 2021 18:24:35 +0700 Subject: [PATCH 22/58] feat: add borrowing fees to subgraph --- packages/subgraph/schema.graphql | 8 ++++++-- packages/subgraph/src/entities/Global.ts | 17 +++++++++++++++-- packages/subgraph/src/entities/Trove.ts | 11 ++++++++++- .../subgraph/src/mappings/BorrowerOperations.ts | 11 +++++++++-- packages/subgraph/subgraph.yaml.js | 2 ++ 5 files changed, 42 insertions(+), 7 deletions(-) diff --git a/packages/subgraph/schema.graphql b/packages/subgraph/schema.graphql index c90725c09..55ed67c01 100644 --- a/packages/subgraph/schema.graphql +++ b/packages/subgraph/schema.graphql @@ -20,6 +20,8 @@ type Global @entity { numberOfActiveLQTYStakes: Int! totalLQTYTokensStaked: BigDecimal! + totalBorrowingFeesPaid: BigDecimal! + "Total redistributed per-stake collateral" rawTotalRedistributedCollateral: BigInt! @@ -176,6 +178,8 @@ type TroveChange implements Change @entity { debtChange: BigDecimal! debtAfter: BigDecimal! + borrowingFee: BigDecimal + collateralRatioBefore: BigDecimal collateralRatioAfter: BigDecimal @@ -287,8 +291,8 @@ type LqtyStakeChange implements Change @entity { amountChange: BigDecimal! amountAfter: BigDecimal! - issuanceGain: BigDecimal - redemptionGain: BigDecimal + issuanceGain: BigDecimal! + redemptionGain: BigDecimal! } type Token @entity { diff --git a/packages/subgraph/src/entities/Global.ts b/packages/subgraph/src/entities/Global.ts index c3bb1d1b5..23a3334dd 100644 --- a/packages/subgraph/src/entities/Global.ts +++ b/packages/subgraph/src/entities/Global.ts @@ -1,8 +1,8 @@ -import { Value, BigInt, Address } from "@graphprotocol/graph-ts"; +import { Value, BigInt, BigDecimal, Address } from "@graphprotocol/graph-ts"; import { Global, LqtyStakeChange } from "../../generated/schema"; -import { BIGINT_ZERO, DECIMAL_ZERO } from "../utils/bignumbers"; +import { BIGINT_ZERO, DECIMAL_ZERO, decimalize } from "../utils/bignumbers"; const onlyGlobalId = "only"; @@ -29,6 +29,7 @@ export function getGlobal(): Global { newGlobal.totalNumberOfLQTYStakes = 0; newGlobal.numberOfActiveLQTYStakes = 0; newGlobal.totalLQTYTokensStaked = DECIMAL_ZERO; + newGlobal.totalBorrowingFeesPaid = DECIMAL_ZERO; return newGlobal; } @@ -56,6 +57,12 @@ export function getChangeSequenceNumber(): i32 { return increaseCounter("changeCount"); } +export function getLastChangeSequenceNumber(): i32 { + let global = getGlobal(); + + return global.changeCount - 1; +} + export function getLiquidationSequenceNumber(): i32 { return increaseCounter("liquidationCount"); } @@ -157,3 +164,9 @@ export function handleLQTYStakeChange( global.totalLQTYTokensStaked = global.totalLQTYTokensStaked.plus(stakeChange.amountChange); global.save(); } + +export function increaseTotalBorrowingFeesPaid(_LUSDFee: BigInt): void { + let global = getGlobal(); + global.totalBorrowingFeesPaid = global.totalBorrowingFeesPaid.plus(decimalize(_LUSDFee)); + global.save(); +} diff --git a/packages/subgraph/src/entities/Trove.ts b/packages/subgraph/src/entities/Trove.ts index fcee97902..de9ff2fbc 100644 --- a/packages/subgraph/src/entities/Trove.ts +++ b/packages/subgraph/src/entities/Trove.ts @@ -14,7 +14,8 @@ import { increaseNumberOfLiquidatedTroves, increaseNumberOfRedeemedTroves, increaseNumberOfOpenTroves, - increaseNumberOfTrovesClosedByOwner + increaseNumberOfTrovesClosedByOwner, + getLastChangeSequenceNumber } from "./Global"; import { beginChange, initChange, finishChange } from "./Change"; import { getCurrentPrice, updateSystemStateByTroveChange } from "./SystemState"; @@ -161,3 +162,11 @@ export function updateTrove( trove.save(); } + +export function setBorrowingFeeOfLastTroveChange(_LUSDFee: BigInt): void { + let lastChangeSequenceNumber = getLastChangeSequenceNumber(); + + let lastTroveChange = TroveChange.load(lastChangeSequenceNumber.toString()); + lastTroveChange.borrowingFee = decimalize(_LUSDFee); + lastTroveChange.save(); +} diff --git a/packages/subgraph/src/mappings/BorrowerOperations.ts b/packages/subgraph/src/mappings/BorrowerOperations.ts index 214d8b67f..181926d21 100644 --- a/packages/subgraph/src/mappings/BorrowerOperations.ts +++ b/packages/subgraph/src/mappings/BorrowerOperations.ts @@ -1,12 +1,14 @@ import { TroveManager } from "../../generated/TroveManager/TroveManager"; import { BorrowerOperations, - TroveUpdated + TroveUpdated, + LUSDBorrowingFeePaid } from "../../generated/BorrowerOperations/BorrowerOperations"; import { getTroveOperationFromBorrowerOperation } from "../types/TroveOperation"; -import { updateTrove } from "../entities/Trove"; +import { setBorrowingFeeOfLastTroveChange, updateTrove } from "../entities/Trove"; +import { increaseTotalBorrowingFeesPaid } from "../entities/Global"; export function handleTroveUpdated(event: TroveUpdated): void { let borrowerOperations = BorrowerOperations.bind(event.address); @@ -25,3 +27,8 @@ export function handleTroveUpdated(event: TroveUpdated): void { snapshots.value1 ); } + +export function handleLUSDBorrowingFeePaid(event: LUSDBorrowingFeePaid): void { + setBorrowingFeeOfLastTroveChange(event.params._LUSDFee); + increaseTotalBorrowingFeesPaid(event.params._LUSDFee); +} diff --git a/packages/subgraph/subgraph.yaml.js b/packages/subgraph/subgraph.yaml.js index 414ad903e..1ec4a97bb 100644 --- a/packages/subgraph/subgraph.yaml.js +++ b/packages/subgraph/subgraph.yaml.js @@ -86,6 +86,8 @@ dataSources: eventHandlers: - event: TroveUpdated(indexed address,uint256,uint256,uint256,uint8) handler: handleTroveUpdated + - event: LUSDBorrowingFeePaid(indexed address,uint256) + handler: handleLUSDBorrowingFeePaid - name: StabilityPool kind: ethereum/contract network: mainnet From 15bcd15bb6e06e9c160b509c46dae53db23e226a Mon Sep 17 00:00:00 2001 From: Edward Mulraney Date: Tue, 4 May 2021 18:58:06 +0100 Subject: [PATCH 23/58] add subgraph support for frontend tracking --- packages/subgraph/schema.graphql | 10 +++++++ packages/subgraph/src/entities/Frontend.ts | 27 +++++++++++++++++++ .../subgraph/src/entities/StabilityDeposit.ts | 2 +- .../subgraph/src/mappings/StabilityPool.ts | 13 ++++++++- packages/subgraph/subgraph.yaml | 11 +++++--- 5 files changed, 58 insertions(+), 5 deletions(-) create mode 100644 packages/subgraph/src/entities/Frontend.ts diff --git a/packages/subgraph/schema.graphql b/packages/subgraph/schema.graphql index c90725c09..719f36512 100644 --- a/packages/subgraph/schema.graphql +++ b/packages/subgraph/schema.graphql @@ -64,6 +64,7 @@ type User @entity { trove: Trove stabilityDeposit: StabilityDeposit stake: LqtyStake + frontend: Frontend collSurplus: BigDecimal! collSurplusChanges: [CollSurplusChange!]! @derivedFrom(field: "user") @@ -116,6 +117,8 @@ type StabilityDeposit @entity { depositedAmount: BigDecimal! + frontend: Frontend + changes: [StabilityDepositChange!]! @derivedFrom(field: "stabilityDeposit") } @@ -314,3 +317,10 @@ type TokenAllowance @entity { spender: User! value: BigInt! } + +type Frontend @entity { + id: ID! + owner: User! + kickbackRate: BigDecimal! + deposits: [StabilityDeposit!]! @derivedFrom(field: "frontend") +} diff --git a/packages/subgraph/src/entities/Frontend.ts b/packages/subgraph/src/entities/Frontend.ts new file mode 100644 index 000000000..35c89ec9d --- /dev/null +++ b/packages/subgraph/src/entities/Frontend.ts @@ -0,0 +1,27 @@ +import { Address, BigInt, log } from "@graphprotocol/graph-ts"; +import { Frontend } from "../../generated/schema"; +import { decimalize } from "../utils/bignumbers"; +import { getUser } from "./User"; + +export function registerFrontend(ownerAddress: Address, kickbackRate: BigInt): void { + let owner = getUser(ownerAddress); + let frontend = new Frontend(owner.id); + + frontend.owner = owner.id; + frontend.kickbackRate = decimalize(kickbackRate); + + frontend.save(); +} + +export function assignFrontendToDepositor( + depositorAddress: Address, + frontendAddress: Address +): void { + let frontend = Frontend.load(frontendAddress.toHexString()); + + if (frontend != null) { + let depositor = getUser(depositorAddress); + depositor.frontend = frontend.id; + depositor.save(); + } +} diff --git a/packages/subgraph/src/entities/StabilityDeposit.ts b/packages/subgraph/src/entities/StabilityDeposit.ts index 83d0acb1f..bcd2fb038 100644 --- a/packages/subgraph/src/entities/StabilityDeposit.ts +++ b/packages/subgraph/src/entities/StabilityDeposit.ts @@ -20,7 +20,7 @@ function getStabilityDeposit(_user: Address): StabilityDeposit { newStabilityDeposit.owner = owner.id; newStabilityDeposit.depositedAmount = DECIMAL_ZERO; - + newStabilityDeposit.frontend = owner.frontend; owner.stabilityDeposit = newStabilityDeposit.id; owner.save(); diff --git a/packages/subgraph/src/mappings/StabilityPool.ts b/packages/subgraph/src/mappings/StabilityPool.ts index 976d49bb8..8db63dcaa 100644 --- a/packages/subgraph/src/mappings/StabilityPool.ts +++ b/packages/subgraph/src/mappings/StabilityPool.ts @@ -2,7 +2,9 @@ import { BigInt } from "@graphprotocol/graph-ts"; import { UserDepositChanged, - ETHGainWithdrawn + ETHGainWithdrawn, + FrontEndRegistered, + FrontEndTagSet } from "../../generated/templates/StabilityPool/StabilityPool"; import { BIGINT_ZERO } from "../utils/bignumbers"; @@ -13,6 +15,7 @@ import { updateStabilityDeposit, withdrawCollateralGainFromStabilityDeposit } from "../entities/StabilityDeposit"; +import { registerFrontend, assignFrontendToDepositor } from "../entities/Frontend"; // Read the value of tmpDepositUpdate from the Global entity, and replace it with: // - null, if it wasn't null @@ -53,3 +56,11 @@ export function handleETHGainWithdrawn(event: ETHGainWithdrawn): void { updateStabilityDeposit(event, event.params._depositor, depositUpdate as BigInt); } } + +export function handleFrontendRegistered(event: FrontEndRegistered): void { + registerFrontend(event.params._frontEnd, event.params._kickbackRate); +} + +export function handleFrontendTagSet(event: FrontEndTagSet): void { + assignFrontendToDepositor(event.params._depositor, event.params._frontEnd); +} diff --git a/packages/subgraph/subgraph.yaml b/packages/subgraph/subgraph.yaml index 7033e7f33..88a462f48 100644 --- a/packages/subgraph/subgraph.yaml +++ b/packages/subgraph/subgraph.yaml @@ -8,9 +8,9 @@ dataSources: kind: ethereum/contract network: mainnet source: - address: "0xA39739EF8b0231DbFA0DcdA07d7e29faAbCf4bb2" + # address: "0xA39739EF8b0231DbFA0DcdA07d7e29faAbCf4bb2" #address: "0x56fcdA0436E5C7a33ee5bfe292f11AC66429Eb5c" - #address: "0x6645E03DA2a711f780af7cCE1019Cb9a9135C898" + address: "0x2C0199127cb3737D20227566ae7D9bC1F45297e2" abi: TroveManager # startBlock: 8110721 mapping: @@ -58,7 +58,7 @@ dataSources: kind: ethereum/contract network: mainnet source: - address: "0x6DEA81C8171D0bA574754EF6F8b412F2Ed88c54D" + # address: "0x6DEA81C8171D0bA574754EF6F8b412F2Ed88c54D" #address: "0x3f74634451f613693f221b1fef42D442bAE7B470" abi: LQTYToken startBlock: 12178551 @@ -129,6 +129,7 @@ templates: - StabilityDeposit - StabilityDepositChange - SystemState + - Frontend abis: - name: StabilityPool file: ../lib-ethers/abi/StabilityPool.json @@ -139,6 +140,10 @@ templates: handler: handleUserDepositChanged - event: ETHGainWithdrawn(indexed address,uint256,uint256) handler: handleETHGainWithdrawn + - event: FrontEndRegistered(indexed address,uint256) + handler: handleFrontendRegistered + - event: FrontEndTagSet(indexed address,indexed address) + handler: handleFrontendTagSet - name: CollSurplusPool kind: ethereum/contract network: mainnet From 81b2c7734339d35bd424264736e9e357d3ba21a0 Mon Sep 17 00:00:00 2001 From: Daniel Simon Date: Wed, 5 May 2021 18:45:09 +0700 Subject: [PATCH 24/58] feat: add redemption fees to subgraph --- packages/subgraph/schema.graphql | 5 +++++ packages/subgraph/src/entities/Global.ts | 1 + packages/subgraph/src/entities/Redemption.ts | 8 +++++++- packages/subgraph/src/mappings/TroveManager.ts | 3 ++- 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/subgraph/schema.graphql b/packages/subgraph/schema.graphql index 55ed67c01..8fbdad0eb 100644 --- a/packages/subgraph/schema.graphql +++ b/packages/subgraph/schema.graphql @@ -20,7 +20,10 @@ type Global @entity { numberOfActiveLQTYStakes: Int! totalLQTYTokensStaked: BigDecimal! + "Total amount of LUSD paid as borrowing fees" totalBorrowingFeesPaid: BigDecimal! + "Total amount of ETH paid as redemption fees" + totalRedemptionFeesPaid: BigDecimal! "Total redistributed per-stake collateral" rawTotalRedistributedCollateral: BigInt! @@ -245,6 +248,8 @@ type Redemption @entity { collateralRedeemed: BigDecimal! partial: Boolean! + fee: BigDecimal! + troveChanges: [TroveChange!]! @derivedFrom(field: "redemption") } diff --git a/packages/subgraph/src/entities/Global.ts b/packages/subgraph/src/entities/Global.ts index 23a3334dd..ca3589df0 100644 --- a/packages/subgraph/src/entities/Global.ts +++ b/packages/subgraph/src/entities/Global.ts @@ -30,6 +30,7 @@ export function getGlobal(): Global { newGlobal.numberOfActiveLQTYStakes = 0; newGlobal.totalLQTYTokensStaked = DECIMAL_ZERO; newGlobal.totalBorrowingFeesPaid = DECIMAL_ZERO; + newGlobal.totalRedemptionFeesPaid = DECIMAL_ZERO; return newGlobal; } diff --git a/packages/subgraph/src/entities/Redemption.ts b/packages/subgraph/src/entities/Redemption.ts index d62a4e39d..65fff0903 100644 --- a/packages/subgraph/src/entities/Redemption.ts +++ b/packages/subgraph/src/entities/Redemption.ts @@ -23,6 +23,7 @@ export function getCurrentRedemption(event: ethereum.Event): Redemption { newRedemption.tokensActuallyRedeemed = DECIMAL_ZERO; newRedemption.collateralRedeemed = DECIMAL_ZERO; newRedemption.partial = false; + newRedemption.fee = DECIMAL_ZERO; newRedemption.save(); let global = getGlobal(); @@ -39,16 +40,21 @@ export function finishCurrentRedemption( event: ethereum.Event, _attemptedLUSDAmount: BigInt, _actualLUSDAmount: BigInt, - _ETHSent: BigInt + _ETHSent: BigInt, + _ETHFee: BigInt ): void { + let fee = decimalize(_ETHFee); + let currentRedemption = getCurrentRedemption(event); currentRedemption.tokensAttemptedToRedeem = decimalize(_attemptedLUSDAmount); currentRedemption.tokensActuallyRedeemed = decimalize(_actualLUSDAmount); currentRedemption.collateralRedeemed = decimalize(_ETHSent); currentRedemption.partial = _actualLUSDAmount < _attemptedLUSDAmount; + currentRedemption.fee = fee; currentRedemption.save(); let global = getGlobal(); global.currentRedemption = null; + global.totalRedemptionFeesPaid = global.totalRedemptionFeesPaid.plus(fee); global.save(); } diff --git a/packages/subgraph/src/mappings/TroveManager.ts b/packages/subgraph/src/mappings/TroveManager.ts index 00026232f..333db148e 100644 --- a/packages/subgraph/src/mappings/TroveManager.ts +++ b/packages/subgraph/src/mappings/TroveManager.ts @@ -79,6 +79,7 @@ export function handleRedemption(event: Redemption): void { event, event.params._attemptedLUSDAmount, event.params._actualLUSDAmount, - event.params._ETHSent + event.params._ETHSent, + event.params._ETHFee ); } From 3e08cbd4aeade1f4b72c06cd12695a789d7c4f36 Mon Sep 17 00:00:00 2001 From: Edward Mulraney Date: Fri, 7 May 2021 13:29:27 +0100 Subject: [PATCH 25/58] use new template approach. add README for subgraph --- packages/subgraph/README.md | 30 +++++++++++++++++++ .../subgraph/src/entities/StabilityDeposit.ts | 8 ++++- .../subgraph/src/mappings/StabilityPool.ts | 2 +- 3 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 packages/subgraph/README.md diff --git a/packages/subgraph/README.md b/packages/subgraph/README.md new file mode 100644 index 000000000..91cf1da69 --- /dev/null +++ b/packages/subgraph/README.md @@ -0,0 +1,30 @@ +# Liquity Subgraph + +Contains the entities and dependencies to populate a subgraph for Liquity protocol. + + +# Development quickstart +You need to run a Graph Node locally. + +1. Clone Graph Node: `git clone https://github.com/graphprotocol/graph-node` +2. Move into the docker directory: `cd graph-node/docker` +3. Start Graph Node docker instance: `docker-compose up -d` +4. Read the logs from your Graph Node: `docker ps | grep graph-node | cut -f 1 -d ' ' | xargs docker logs -f` +5. Start your local Liquity dev chain: `cd your_liquity_repo_path && yarn start-dev-chain` +6. Compile Liquity subgraph: `yarn prepare:subgraph && yarn build:subgraph` +7. Deploy Liquity subgraph to your Graph Node: `cd packages/subgraph && yarn prepare-local && yarn create-local && yarn deploy-local` +8. Open Graph Node graphql API instance in your browser: `http://127.0.0.1:8000/subgraphs/name/liquity/liquity` +9. Open Liquity in your browser: `http://localhost:3000` + +# Making subgraph code changes +Having done all of the above, if you make subgraph code changes you'll need to run the following: +1. Recompile local changes: `yarn prepare:subgraph && yarn build:subgraph` +2. Redeploy local changes: `cd packages/subgraph && yarn prepare-local && yarn create-local && yarn deploy-local` + +# Gotchas + +## Stopping and starting dev chain +If you stop and start your local dev chain you need to redeploy your subgraph because the contract addresses will have changed. + +## Unregistered frontends +Local instance runs with a frontend ID of Ethereum zero address (`0x0000000000000000000000000000000000000000`) to register the local frontend run `liquity.registerFrontend(0.9).then(console.log)` in your browser console and update `packages/dev-frontend/src/config`'s `ADDRESS_ZERO` to the address returned in your console and refresh the page. \ No newline at end of file diff --git a/packages/subgraph/src/entities/StabilityDeposit.ts b/packages/subgraph/src/entities/StabilityDeposit.ts index bcd2fb038..118a9c9c3 100644 --- a/packages/subgraph/src/entities/StabilityDeposit.ts +++ b/packages/subgraph/src/entities/StabilityDeposit.ts @@ -20,7 +20,6 @@ function getStabilityDeposit(_user: Address): StabilityDeposit { newStabilityDeposit.owner = owner.id; newStabilityDeposit.depositedAmount = DECIMAL_ZERO; - newStabilityDeposit.frontend = owner.frontend; owner.stabilityDeposit = newStabilityDeposit.id; owner.save(); @@ -76,6 +75,7 @@ export function updateStabilityDeposit( ): void { let stabilityDeposit = getStabilityDeposit(_user); let newDepositedAmount = decimalize(_amount); + let owner = getUser(_user); if (newDepositedAmount == stabilityDeposit.depositedAmount) { // Don't create a StabilityDepositChange when there's no change... duh. @@ -83,6 +83,12 @@ export function updateStabilityDeposit( return; } + if (owner.frontend != stabilityDeposit.frontend) { + // FrontEndTagSet is emitted just before UserDepositChanged event + // FrontEndTagSet sets the owner.frontend, so we can use that + stabilityDeposit.frontend = owner.frontend; + } + updateStabilityDepositByOperation( event, stabilityDeposit, diff --git a/packages/subgraph/src/mappings/StabilityPool.ts b/packages/subgraph/src/mappings/StabilityPool.ts index 8db63dcaa..0a422603a 100644 --- a/packages/subgraph/src/mappings/StabilityPool.ts +++ b/packages/subgraph/src/mappings/StabilityPool.ts @@ -5,7 +5,7 @@ import { ETHGainWithdrawn, FrontEndRegistered, FrontEndTagSet -} from "../../generated/templates/StabilityPool/StabilityPool"; +} from "../../generated/StabilityPool/StabilityPool"; import { BIGINT_ZERO } from "../utils/bignumbers"; From e0c925969204c8f9e43e8073a967b1e7445a3d15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Fingen?= Date: Thu, 13 May 2021 12:40:16 +0200 Subject: [PATCH 26/58] contracts: Fix Uniswap typos --- .../contracts/mainnetDeployment/deploymentParams.localFork.js | 2 +- .../contracts/mainnetDeployment/deploymentParams.mainnet.js | 2 +- .../contracts/mainnetDeployment/deploymentParams.rinkeby.js | 2 +- packages/contracts/mainnetDeployment/mainnetDeployment.js | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/contracts/mainnetDeployment/deploymentParams.localFork.js b/packages/contracts/mainnetDeployment/deploymentParams.localFork.js index 352e00e49..f4f17d0c0 100644 --- a/packages/contracts/mainnetDeployment/deploymentParams.localFork.js +++ b/packages/contracts/mainnetDeployment/deploymentParams.localFork.js @@ -5,7 +5,7 @@ const externalAddrs = { TELLOR_MASTER:"0x88dF592F8eb5D7Bd38bFeF7dEb0fBc02cf3778a0", // https://uniswap.org/docs/v2/smart-contracts/factory/ UNISWAP_V2_FACTORY: "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f", - UNIWAP_V2_ROUTER02: "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D", + UNISWAP_V2_ROUTER02: "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D", // https://etherscan.io/token/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 WETH_ERC20: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", } diff --git a/packages/contracts/mainnetDeployment/deploymentParams.mainnet.js b/packages/contracts/mainnetDeployment/deploymentParams.mainnet.js index b291fc8ad..9653a2506 100644 --- a/packages/contracts/mainnetDeployment/deploymentParams.mainnet.js +++ b/packages/contracts/mainnetDeployment/deploymentParams.mainnet.js @@ -5,7 +5,7 @@ const externalAddrs = { TELLOR_MASTER:"0x88dF592F8eb5D7Bd38bFeF7dEb0fBc02cf3778a0", // https://uniswap.org/docs/v2/smart-contracts/factory/ UNISWAP_V2_FACTORY: "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f", - UNIWAP_V2_ROUTER02: "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D", + UNISWAP_V2_ROUTER02: "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D", // https://etherscan.io/token/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 WETH_ERC20: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", } diff --git a/packages/contracts/mainnetDeployment/deploymentParams.rinkeby.js b/packages/contracts/mainnetDeployment/deploymentParams.rinkeby.js index 30ec26d8b..90c9dd258 100644 --- a/packages/contracts/mainnetDeployment/deploymentParams.rinkeby.js +++ b/packages/contracts/mainnetDeployment/deploymentParams.rinkeby.js @@ -5,7 +5,7 @@ const externalAddrs = { TELLOR_MASTER:"0x20374E579832859f180536A69093A126Db1c8aE9", // https://uniswap.org/docs/v2/smart-contracts/factory/ UNISWAP_V2_FACTORY: "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f", - UNIWAP_V2_ROUTER02: "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D", + UNISWAP_V2_ROUTER02: "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D", WETH_ERC20: "0xc778417e063141139fce010982780140aa0cd5ab", } diff --git a/packages/contracts/mainnetDeployment/mainnetDeployment.js b/packages/contracts/mainnetDeployment/mainnetDeployment.js index 84a30fe87..f2a676f4e 100644 --- a/packages/contracts/mainnetDeployment/mainnetDeployment.js +++ b/packages/contracts/mainnetDeployment/mainnetDeployment.js @@ -23,7 +23,7 @@ async function mainnetDeploy(configParams) { let deployerETHBalance = await ethers.provider.getBalance(deployerWallet.address) console.log(`deployerETHBalance before: ${deployerETHBalance}`) - // Get UniswaV2Factory instance at its deployed address + // Get UniswapV2Factory instance at its deployed address const uniswapV2Factory = new ethers.Contract( configParams.externalAddrs.UNISWAP_V2_FACTORY, UniswapV2Factory.abi, @@ -312,7 +312,7 @@ async function mainnetDeploy(configParams) { // // Get the UniswapV2Router contract // const uniswapV2Router02 = new ethers.Contract( - // configParams.externalAddrs.UNIWAP_V2_ROUTER02, + // configParams.externalAddrs.UNISWAP_V2_ROUTER02, // UniswapV2Router02.abi, // deployerWallet // ) From 48ce9ee849bc8543df4b776b6bbc3ca437acee2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Fingen?= Date: Thu, 13 May 2021 12:40:40 +0200 Subject: [PATCH 27/58] contracts: Add entries to gitignore --- packages/contracts/.gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/contracts/.gitignore b/packages/contracts/.gitignore index 3ce651b0f..d69dfce37 100644 --- a/packages/contracts/.gitignore +++ b/packages/contracts/.gitignore @@ -4,6 +4,8 @@ # Truffle contract JSONs /build/contracts +/bin + # Buidler compilation cache /cache @@ -17,6 +19,7 @@ /.vscode +yarn-error.log # solidity-coverage /.coverage_artifacts @@ -34,6 +37,7 @@ # brownie __pycache__ +.pytest_cache .history .hypothesis/ build/ From db7751e8a9e2d809e76e12e2fcb70f2e77ba9c14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Fingen?= Date: Thu, 13 May 2021 12:46:13 +0200 Subject: [PATCH 28/58] fixup! contracts: Add entries to gitignore --- packages/contracts/.gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/contracts/.gitignore b/packages/contracts/.gitignore index d69dfce37..8529439c1 100644 --- a/packages/contracts/.gitignore +++ b/packages/contracts/.gitignore @@ -17,7 +17,7 @@ /secrets.js -/.vscode +.vscode yarn-error.log From 567450ca06563a2fd303386bc6b2f74aaa17aa6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Fingen?= Date: Thu, 13 May 2021 12:48:25 +0200 Subject: [PATCH 29/58] contracts: Make npm package public --- packages/contracts/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/contracts/package.json b/packages/contracts/package.json index dc46554b7..af59bc71f 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -1,7 +1,6 @@ { "name": "@liquity/contracts", "version": "1.0.0", - "private": true, "description": "", "main": "truffle-config.js", "directories": { From 7c83ea11378454629618b3808b16fbfda69ee3e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Fingen?= Date: Thu, 13 May 2021 12:54:13 +0200 Subject: [PATCH 30/58] contracts: Add .npmrc file --- packages/contracts/.npmrc | 1 + 1 file changed, 1 insertion(+) create mode 100644 packages/contracts/.npmrc diff --git a/packages/contracts/.npmrc b/packages/contracts/.npmrc new file mode 100644 index 000000000..94a06c218 --- /dev/null +++ b/packages/contracts/.npmrc @@ -0,0 +1 @@ +access=public From ece6d0cbaea7eb41e01c85d90961c9b0c9a1571b Mon Sep 17 00:00:00 2001 From: bojan-liquity <73836320+bojan-liquity@users.noreply.github.com> Date: Sat, 22 May 2021 08:13:33 +0200 Subject: [PATCH 31/58] Update Liquity Whitepaper rev. 0.2.tex Changed redistribution table CR number --- papers/whitepaper/Liquity Whitepaper rev. 0.2.tex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/papers/whitepaper/Liquity Whitepaper rev. 0.2.tex b/papers/whitepaper/Liquity Whitepaper rev. 0.2.tex index f0b0ecb68..f7610804a 100644 --- a/papers/whitepaper/Liquity Whitepaper rev. 0.2.tex +++ b/papers/whitepaper/Liquity Whitepaper rev. 0.2.tex @@ -233,7 +233,7 @@ \subsection{Redistribute undercollateralized Troves to other borrowers} \hline \textcolor{red}{D} & \textcolor{red}{$8000$} & \textcolor{red}{$4.3$} & \textcolor{red}{$108\%$} & \textcolor{red}{$-8000.00$} & \textcolor{red}{$-4.30$} & \textcolor{red}{$0.00$} & \textcolor{red}{$0.00$} & \textcolor{red}{n/a} & \textcolor{red}{$-600$} \\ \hlineB{2.5} - Total & $20000$ & $16.8$ & $17\%$ & $0.00$ & $0.00$ & $20000.00$ & $16.80$ & $17\%$ & $0.00$ \\ + Total & $20000$ & $16.8$ & $168\%$ & $0.00$ & $0.00$ & $20000.00$ & $16.80$ & $168\%$ & $0.00$ \\ \hline \end{tabular} \end{center} From 9c79c9ec3fc997eb20e5fa2c8d283b606ccad62c Mon Sep 17 00:00:00 2001 From: bojan-liquity <73836320+bojan-liquity@users.noreply.github.com> Date: Sat, 22 May 2021 08:38:14 +0200 Subject: [PATCH 32/58] Update Liquity Whitepaper rev. 0.2.tex Adding footnote to recovery mode table --- papers/whitepaper/Liquity Whitepaper rev. 0.2.tex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/papers/whitepaper/Liquity Whitepaper rev. 0.2.tex b/papers/whitepaper/Liquity Whitepaper rev. 0.2.tex index f7610804a..d82f8cd17 100644 --- a/papers/whitepaper/Liquity Whitepaper rev. 0.2.tex +++ b/papers/whitepaper/Liquity Whitepaper rev. 0.2.tex @@ -12,6 +12,7 @@ \usepackage{float} \usepackage{tabularx} \usepackage[table]{colortbl} +\usepackage{tablefootnote} \renewcommand\theadalign{bc} \renewcommand\theadfont{\bfseries} @@ -233,7 +234,7 @@ \subsection{Redistribute undercollateralized Troves to other borrowers} \hline \textcolor{red}{D} & \textcolor{red}{$8000$} & \textcolor{red}{$4.3$} & \textcolor{red}{$108\%$} & \textcolor{red}{$-8000.00$} & \textcolor{red}{$-4.30$} & \textcolor{red}{$0.00$} & \textcolor{red}{$0.00$} & \textcolor{red}{n/a} & \textcolor{red}{$-600$} \\ \hlineB{2.5} - Total & $20000$ & $16.8$ & $168\%$ & $0.00$ & $0.00$ & $20000.00$ & $16.80$ & $168\%$ & $0.00$ \\ + Total & $20000$ & $16.8$ & $168\%$ & $0.00$ & $0.00$ & $20000.00$ & $16.80$ & $168\%$ \tablefootnote{For simplicity reason we are disregarding the Gas Compensation in the calculation} & $0.00$ \\ \hline \end{tabular} \end{center} From 5bd633db6ce6bd511657fe79063bc131c50dc321 Mon Sep 17 00:00:00 2001 From: Daniel Simon Date: Thu, 3 Jun 2021 11:00:17 +0700 Subject: [PATCH 33/58] fix: eliminate bogus `TroveChange` upon capped liquidation The issue was introduced when capped liquidations were added to the backend. Up until then, the `_coll` and `_debt` parameters of `TroveLiquidated` events reflected the final state of the Trove immediately before the liquidation. The subgraph relied on this to insert an `accrueRewards` operation in case there have been redistributions between the last Trove adjustment and the liquidation. However, in the case of a capped liquidation, `_coll` now reflects the capped portion of the collateral that's liquidated, so we can no longer rely on the event's parameters. Instead, we should calculate the redistribution from the Trove's snapshots, stake and the global redistribution counters. Fixes #580. --- packages/subgraph/src/entities/Trove.ts | 51 +++++++++++++++---- .../src/mappings/BorrowerOperations.ts | 11 +--- .../subgraph/src/mappings/TroveManager.ts | 47 ++++------------- packages/subgraph/subgraph.yaml.js | 2 + 4 files changed, 56 insertions(+), 55 deletions(-) diff --git a/packages/subgraph/src/entities/Trove.ts b/packages/subgraph/src/entities/Trove.ts index de9ff2fbc..017db23f8 100644 --- a/packages/subgraph/src/entities/Trove.ts +++ b/packages/subgraph/src/entities/Trove.ts @@ -15,7 +15,8 @@ import { increaseNumberOfRedeemedTroves, increaseNumberOfOpenTroves, increaseNumberOfTrovesClosedByOwner, - getLastChangeSequenceNumber + getLastChangeSequenceNumber, + getGlobal } from "./Global"; import { beginChange, initChange, finishChange } from "./Change"; import { getCurrentPrice, updateSystemStateByTroveChange } from "./SystemState"; @@ -91,10 +92,9 @@ export function updateTrove( _borrower: Address, _coll: BigInt, _debt: BigInt, - stake: BigInt, - snapshotETH: BigInt, - snapshotLUSDDebt: BigInt + stake: BigInt ): void { + let global = getGlobal(); let trove = getTrove(_borrower); let newCollateral = decimalize(_coll); let newDebt = decimalize(_debt); @@ -120,8 +120,8 @@ export function updateTrove( troveChange.debtAfter = trove.debt; troveChange.collateralRatioAfter = calculateCollateralRatio(trove.collateral, trove.debt, price); - troveChange.collateralChange = troveChange.collateralAfter - troveChange.collateralBefore; - troveChange.debtChange = troveChange.debtAfter - troveChange.debtBefore; + troveChange.collateralChange = troveChange.collateralAfter.minus(troveChange.collateralBefore); + troveChange.debtChange = troveChange.debtAfter.minus(troveChange.debtBefore); if (isLiquidation(operation)) { let currentLiquidation = getCurrentLiquidation(event); @@ -139,12 +139,18 @@ export function updateTrove( trove.rawCollateral = _coll; trove.rawDebt = _debt; trove.rawStake = stake; - trove.rawSnapshotOfTotalRedistributedCollateral = snapshotETH; - trove.rawSnapshotOfTotalRedistributedDebt = snapshotLUSDDebt; if (stake != BIGINT_ZERO) { - trove.collateralRatioSortKey = (_debt * BIGINT_SCALING_FACTOR) / stake - snapshotLUSDDebt; + trove.rawSnapshotOfTotalRedistributedCollateral = global.rawTotalRedistributedCollateral; + trove.rawSnapshotOfTotalRedistributedDebt = global.rawTotalRedistributedDebt; + + trove.collateralRatioSortKey = _debt + .times(BIGINT_SCALING_FACTOR) + .div(stake) + .minus(global.rawTotalRedistributedDebt); } else { + trove.rawSnapshotOfTotalRedistributedCollateral = BIGINT_ZERO; + trove.rawSnapshotOfTotalRedistributedDebt = BIGINT_ZERO; trove.collateralRatioSortKey = null; } @@ -170,3 +176,30 @@ export function setBorrowingFeeOfLastTroveChange(_LUSDFee: BigInt): void { lastTroveChange.borrowingFee = decimalize(_LUSDFee); lastTroveChange.save(); } + +export function applyRedistributionToTroveBeforeLiquidation( + event: ethereum.Event, + _borrower: Address +): void { + let global = getGlobal(); + let trove = getTrove(_borrower); + + let redistributedCollateral = global.rawTotalRedistributedCollateral + .minus(trove.rawSnapshotOfTotalRedistributedCollateral) + .times(trove.rawStake) + .div(BIGINT_SCALING_FACTOR); + + let redistributedDebt = global.rawTotalRedistributedDebt + .minus(trove.rawSnapshotOfTotalRedistributedDebt) + .times(trove.rawStake) + .div(BIGINT_SCALING_FACTOR); + + updateTrove( + event, + "accrueRewards", + _borrower, + trove.rawCollateral.plus(redistributedCollateral), + trove.rawDebt.plus(redistributedDebt), + BIGINT_ZERO // No need to calculate new stake, because we know the Trove is being liquidated + ); +} diff --git a/packages/subgraph/src/mappings/BorrowerOperations.ts b/packages/subgraph/src/mappings/BorrowerOperations.ts index 181926d21..256f4e4b2 100644 --- a/packages/subgraph/src/mappings/BorrowerOperations.ts +++ b/packages/subgraph/src/mappings/BorrowerOperations.ts @@ -1,6 +1,4 @@ -import { TroveManager } from "../../generated/TroveManager/TroveManager"; import { - BorrowerOperations, TroveUpdated, LUSDBorrowingFeePaid } from "../../generated/BorrowerOperations/BorrowerOperations"; @@ -11,20 +9,13 @@ import { setBorrowingFeeOfLastTroveChange, updateTrove } from "../entities/Trove import { increaseTotalBorrowingFeesPaid } from "../entities/Global"; export function handleTroveUpdated(event: TroveUpdated): void { - let borrowerOperations = BorrowerOperations.bind(event.address); - let troveManagerAddress = borrowerOperations.troveManager(); - let troveManager = TroveManager.bind(troveManagerAddress); - let snapshots = troveManager.rewardSnapshots(event.params._borrower); - updateTrove( event, getTroveOperationFromBorrowerOperation(event.params.operation), event.params._borrower, event.params._coll, event.params._debt, - event.params.stake, - snapshots.value0, - snapshots.value1 + event.params.stake ); } diff --git a/packages/subgraph/src/mappings/TroveManager.ts b/packages/subgraph/src/mappings/TroveManager.ts index 333db148e..6f04bbc95 100644 --- a/packages/subgraph/src/mappings/TroveManager.ts +++ b/packages/subgraph/src/mappings/TroveManager.ts @@ -1,19 +1,17 @@ import { - TroveManager, TroveUpdated, TroveLiquidated, Liquidation, Redemption, - PriceFeedAddressChanged + PriceFeedAddressChanged, + LTermsUpdated } from "../../generated/TroveManager/TroveManager"; -import { BIGINT_ZERO } from "../utils/bignumbers"; - import { getTroveOperationFromTroveManagerOperation } from "../types/TroveOperation"; import { finishCurrentLiquidation } from "../entities/Liquidation"; import { finishCurrentRedemption } from "../entities/Redemption"; -import { updateTrove } from "../entities/Trove"; +import { applyRedistributionToTroveBeforeLiquidation, updateTrove } from "../entities/Trove"; import { updatePriceFeedAddress, updateTotalRedistributed } from "../entities/Global"; export function handlePriceFeedAddressChanged(event: PriceFeedAddressChanged): void { @@ -21,48 +19,23 @@ export function handlePriceFeedAddressChanged(event: PriceFeedAddressChanged): v } export function handleTroveUpdated(event: TroveUpdated): void { - let troveManager = TroveManager.bind(event.address); - let snapshots = troveManager.rewardSnapshots(event.params._borrower); - updateTrove( event, getTroveOperationFromTroveManagerOperation(event.params._operation), event.params._borrower, event.params._coll, event.params._debt, - event.params._stake, - snapshots.value0, - snapshots.value1 + event.params._stake ); } export function handleTroveLiquidated(event: TroveLiquidated): void { - updateTrove( - event, - "accrueRewards", - event.params._borrower, - event.params._coll, - event.params._debt, - BIGINT_ZERO, - BIGINT_ZERO, - BIGINT_ZERO - ); - - updateTrove( - event, - getTroveOperationFromTroveManagerOperation(event.params._operation), - event.params._borrower, - BIGINT_ZERO, - BIGINT_ZERO, - BIGINT_ZERO, - BIGINT_ZERO, - BIGINT_ZERO - ); + applyRedistributionToTroveBeforeLiquidation(event, event.params._borrower); + // No need to close the Trove yet, as TroveLiquidated will be followed by a TroveUpdated event + // that sets collateral and debt to 0. } export function handleLiquidation(event: Liquidation): void { - let troveManager = TroveManager.bind(event.address); - finishCurrentLiquidation( event, event.params._liquidatedColl, @@ -70,8 +43,6 @@ export function handleLiquidation(event: Liquidation): void { event.params._collGasCompensation, event.params._LUSDGasCompensation ); - - updateTotalRedistributed(troveManager.L_ETH(), troveManager.L_LUSDDebt()); } export function handleRedemption(event: Redemption): void { @@ -83,3 +54,7 @@ export function handleRedemption(event: Redemption): void { event.params._ETHFee ); } + +export function handleLTermsUpdated(event: LTermsUpdated): void { + updateTotalRedistributed(event.params._L_ETH, event.params._L_LUSDDebt); +} diff --git a/packages/subgraph/subgraph.yaml.js b/packages/subgraph/subgraph.yaml.js index d5028225e..26daa0b1e 100644 --- a/packages/subgraph/subgraph.yaml.js +++ b/packages/subgraph/subgraph.yaml.js @@ -56,6 +56,8 @@ dataSources: handler: handleLiquidation - event: Redemption(uint256,uint256,uint256,uint256) handler: handleRedemption + - event: LTermsUpdated(uint256,uint256) + handler: handleLTermsUpdated - name: BorrowerOperations kind: ethereum/contract network: mainnet From 510475a21674fd5a0119b60224d42ba68da4b274 Mon Sep 17 00:00:00 2001 From: bojan-liquity <73836320+bojan-liquity@users.noreply.github.com> Date: Thu, 3 Jun 2021 12:21:32 +0200 Subject: [PATCH 34/58] Update Liquity Whitepaper rev. 0.2.tex Changed wording on two occasions. --- papers/whitepaper/Liquity Whitepaper rev. 0.2.tex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/papers/whitepaper/Liquity Whitepaper rev. 0.2.tex b/papers/whitepaper/Liquity Whitepaper rev. 0.2.tex index d82f8cd17..7b0302031 100644 --- a/papers/whitepaper/Liquity Whitepaper rev. 0.2.tex +++ b/papers/whitepaper/Liquity Whitepaper rev. 0.2.tex @@ -113,7 +113,7 @@ \subsection{Borrower operations } \textit{Liquidation Reserve}. When a borrower opens a new Trove, an amount of 200 LUSD is reserved and held back by the protocol as a compensation for the gas costs if the Trove needs to be liquidated at some point. The 200 LUSD is added to the Trove's debt, impacting its collateral ratio. When a borrower closes their Trove, the Liquidation Reserve is refunded, i.e. the corresponding 200 LUSD debt on the Trove is cancelled. The borrower thus needs to pay back 200 LUSD less to fully pay off their debt. \\ -\textit{Borrowing Fee}. The protocol charges a one-time Borrowing Fee for the borrowed liquidity. The fee is added to the Trove's debt and is given by a \textbf{base rate} (see 3.3 Redemption mechanism “Redemption fee and base rate”) multiplied by the amount of liquidity drawn by the borrower. The minimum Borrowing Fee is 0.5\%, and the maximum is 5\%. \\ +\textit{Borrowing Fee}. The protocol charges a one-time Borrowing Fee for the borrowed liquidity. The fee is added to the Trove's debt and is given by a \textbf{base rate} + 0.5\% (see 3.3 Redemption mechanism “Redemption fee and base rate”) multiplied by the amount of liquidity drawn by the borrower. The minimum Borrowing Fee is 0.5\%, and the maximum is 5\%. \\ \begin{tcolorbox} \textbf{Example}\\ @@ -164,7 +164,7 @@ \subsection{Redemption mechanism} The system first applies the decay rate to the current base rate: $$b(t):=b(t-1)\times\delta^{\triangle t}=0.014\times0.94^2=0.01237$$ -It then increases the base rate given the redeemed amount ($\alpha= 0.5$): +It then increases the base rate in proportion to the fraction of total supply redeemed ($\alpha= 0.5$): $$b(t):=b(t-1)+0.5\times\frac{m}{n}=0.01237+0.5\times\frac{150000}{10000000}=0.01987$$ \\ From a51ff1e92f8e632e07a93b576f945098eafbaa00 Mon Sep 17 00:00:00 2001 From: Daniel Simon Date: Fri, 4 Jun 2021 10:54:53 +0700 Subject: [PATCH 35/58] fix: use events to update price in subgraph Previously, price was checked via `eth_call` upon creating a new Transaction entity. The problem is that calls see the blockchain state after all the transactions in the current block have been executed. This means if there was a Liquity TX mined in the same block as a (Chainlink) price update, but ordered _before_ the price update, the subgraph would prematurely use the updated price to calculate ICRs while processing the Liquity TX. Instead of calling the PriceFeed, use its event `LastGoodPriceUpdated`, which is correctly ordered with regard to other Liquity events. Fixes #581. --- .../TestContracts/PriceFeedTestnet.sol | 3 ++ packages/subgraph/schema.graphql | 4 +- packages/subgraph/src/calls/PriceFeed.ts | 9 ---- packages/subgraph/src/entities/Change.ts | 9 +--- packages/subgraph/src/entities/Global.ts | 13 +----- packages/subgraph/src/entities/LqtyStake.ts | 2 +- .../subgraph/src/entities/StabilityDeposit.ts | 4 +- packages/subgraph/src/entities/SystemState.ts | 42 +++++++++++-------- packages/subgraph/src/entities/Transaction.ts | 6 --- packages/subgraph/src/entities/Trove.ts | 2 +- packages/subgraph/src/entities/User.ts | 7 ++-- packages/subgraph/src/mappings/PriceFeed.ts | 7 ++++ .../subgraph/src/mappings/TroveManager.ts | 7 +--- packages/subgraph/src/utils/bignumbers.ts | 2 - packages/subgraph/subgraph.yaml.js | 40 ++++++++++-------- 15 files changed, 70 insertions(+), 87 deletions(-) delete mode 100644 packages/subgraph/src/calls/PriceFeed.ts create mode 100644 packages/subgraph/src/mappings/PriceFeed.ts diff --git a/packages/contracts/contracts/TestContracts/PriceFeedTestnet.sol b/packages/contracts/contracts/TestContracts/PriceFeedTestnet.sol index 0cd8f477a..ffdb76d9f 100644 --- a/packages/contracts/contracts/TestContracts/PriceFeedTestnet.sol +++ b/packages/contracts/contracts/TestContracts/PriceFeedTestnet.sol @@ -20,6 +20,9 @@ contract PriceFeedTestnet is IPriceFeed { } function fetchPrice() external override returns (uint256) { + // Fire an event just like the mainnet version would. + // This lets the subgraph rely on events to get the latest price even when developing locally. + emit LastGoodPriceUpdated(_price); return _price; } diff --git a/packages/subgraph/schema.graphql b/packages/subgraph/schema.graphql index f51ad962a..218fe2136 100644 --- a/packages/subgraph/schema.graphql +++ b/packages/subgraph/schema.graphql @@ -2,8 +2,6 @@ type Global @entity { "There should be only one System entity with an ID of 'only'" id: ID! - priceFeedAddress: Bytes - systemStateCount: Int! transactionCount: Int! changeCount: Int! @@ -50,7 +48,7 @@ type SystemState @entity { "Can be used to chronologically sort SystemStates" sequenceNumber: Int! - price: BigDecimal! + price: BigDecimal totalCollateral: BigDecimal! totalDebt: BigDecimal! diff --git a/packages/subgraph/src/calls/PriceFeed.ts b/packages/subgraph/src/calls/PriceFeed.ts deleted file mode 100644 index 8bbbf94ad..000000000 --- a/packages/subgraph/src/calls/PriceFeed.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Address, BigInt } from "@graphprotocol/graph-ts"; - -import { PriceFeed } from "../../generated/TroveManager/PriceFeed"; - -export function getPrice(priceFeedAddress: Address): BigInt { - let priceFeed = PriceFeed.bind(priceFeedAddress); - - return priceFeed.fetchPrice(); -} diff --git a/packages/subgraph/src/entities/Change.ts b/packages/subgraph/src/entities/Change.ts index f6f5ef34e..c69881eed 100644 --- a/packages/subgraph/src/entities/Change.ts +++ b/packages/subgraph/src/entities/Change.ts @@ -4,14 +4,7 @@ import { getChangeSequenceNumber } from "./Global"; import { getTransaction } from "./Transaction"; import { getCurrentSystemState } from "./SystemState"; -export function beginChange(event: ethereum.Event): i32 { - // Pre-create the Transaction entity that this change will eventually refer to (if it doesn't - // exist yet). - - // This is needed because creating a new Transaction may have the side effect of increasing the - // change sequence number through creating a PriceChange. - getTransaction(event); - +export function beginChange(): i32 { return getChangeSequenceNumber(); } diff --git a/packages/subgraph/src/entities/Global.ts b/packages/subgraph/src/entities/Global.ts index ca3589df0..d3c99b14d 100644 --- a/packages/subgraph/src/entities/Global.ts +++ b/packages/subgraph/src/entities/Global.ts @@ -1,4 +1,4 @@ -import { Value, BigInt, BigDecimal, Address } from "@graphprotocol/graph-ts"; +import { Value, BigInt } from "@graphprotocol/graph-ts"; import { Global, LqtyStakeChange } from "../../generated/schema"; @@ -72,17 +72,6 @@ export function getRedemptionSequenceNumber(): i32 { return increaseCounter("redemptionCount"); } -export function updatePriceFeedAddress(priceFeedAddress: Address): void { - let global = getGlobal(); - - global.priceFeedAddress = priceFeedAddress; - global.save(); -} - -export function getPriceFeedAddress(): Address { - return getGlobal().priceFeedAddress as Address; -} - export function updateTotalRedistributed(L_ETH: BigInt, L_LUSDDebt: BigInt): void { let global = getGlobal(); diff --git a/packages/subgraph/src/entities/LqtyStake.ts b/packages/subgraph/src/entities/LqtyStake.ts index fedeafaa6..07012427b 100644 --- a/packages/subgraph/src/entities/LqtyStake.ts +++ b/packages/subgraph/src/entities/LqtyStake.ts @@ -9,7 +9,7 @@ import { getUser } from "./User"; import { handleLQTYStakeChange } from "./Global"; function startLQTYStakeChange(event: ethereum.Event): LqtyStakeChange { - let sequenceNumber = beginChange(event); + let sequenceNumber = beginChange(); let stakeChange = new LqtyStakeChange(sequenceNumber.toString()); stakeChange.issuanceGain = DECIMAL_ZERO; stakeChange.redemptionGain = DECIMAL_ZERO; diff --git a/packages/subgraph/src/entities/StabilityDeposit.ts b/packages/subgraph/src/entities/StabilityDeposit.ts index 118a9c9c3..9b401685f 100644 --- a/packages/subgraph/src/entities/StabilityDeposit.ts +++ b/packages/subgraph/src/entities/StabilityDeposit.ts @@ -28,7 +28,7 @@ function getStabilityDeposit(_user: Address): StabilityDeposit { } function createStabilityDepositChange(event: ethereum.Event): StabilityDepositChange { - let sequenceNumber = beginChange(event); + let sequenceNumber = beginChange(); let stabilityDepositChange = new StabilityDepositChange(sequenceNumber.toString()); initChange(stabilityDepositChange, event, sequenceNumber); @@ -112,7 +112,7 @@ export function withdrawCollateralGainFromStabilityDeposit( let stabilityDeposit = getStabilityDeposit(_user) as StabilityDeposit; let depositLoss = decimalize(_LUSDLoss); - let newDepositedAmount = stabilityDeposit.depositedAmount - depositLoss; + let newDepositedAmount = stabilityDeposit.depositedAmount.minus(depositLoss); updateStabilityDepositByOperation( event, diff --git a/packages/subgraph/src/entities/SystemState.ts b/packages/subgraph/src/entities/SystemState.ts index 78b8bea59..88a167ffe 100644 --- a/packages/subgraph/src/entities/SystemState.ts +++ b/packages/subgraph/src/entities/SystemState.ts @@ -1,4 +1,4 @@ -import { ethereum, BigDecimal } from "@graphprotocol/graph-ts"; +import { ethereum, BigDecimal, BigInt } from "@graphprotocol/graph-ts"; import { SystemState, @@ -8,7 +8,7 @@ import { CollSurplusChange } from "../../generated/schema"; -import { decimalize, DECIMAL_INITIAL_PRICE, DECIMAL_ZERO, DECIMAL_ONE } from "../utils/bignumbers"; +import { decimalize, DECIMAL_ZERO, DECIMAL_ONE } from "../utils/bignumbers"; import { calculateCollateralRatio } from "../utils/collateralRatio"; import { @@ -18,9 +18,7 @@ import { isRecoveryModeLiquidation } from "../types/TroveOperation"; -import { getPrice } from "../calls/PriceFeed"; - -import { getGlobal, getSystemStateSequenceNumber, getPriceFeedAddress } from "./Global"; +import { getGlobal, getSystemStateSequenceNumber } from "./Global"; import { beginChange, initChange, finishChange } from "./Change"; export function getCurrentSystemState(): SystemState { @@ -32,7 +30,6 @@ export function getCurrentSystemState(): SystemState { let newSystemState = new SystemState(sequenceNumber.toString()); newSystemState.sequenceNumber = sequenceNumber; - newSystemState.price = DECIMAL_INITIAL_PRICE; newSystemState.totalCollateral = DECIMAL_ZERO; newSystemState.totalDebt = DECIMAL_ZERO; newSystemState.tokensInStabilityPool = DECIMAL_ZERO; @@ -60,16 +57,17 @@ export function bumpSystemState(systemState: SystemState): void { global.save(); } -// To make sure this returns the latest price, a Transaction entity should be created for the -// triggering event beforehand, either directly or indirectly through creating a Change entity export function getCurrentPrice(): BigDecimal { let currentSystemState = getCurrentSystemState(); - return currentSystemState.price; + // The backend always starts with fetching the latest price, so LastGoodPriceUpdated will be + // the first event emitted. We can be sure that by the time we need the price, it will have been + // initialized. + return currentSystemState.price!; } function createPriceChange(event: ethereum.Event): PriceChange { - let sequenceNumber = beginChange(event); + let sequenceNumber = beginChange(); let priceChange = new PriceChange(sequenceNumber.toString()); initChange(priceChange, event, sequenceNumber); @@ -82,13 +80,23 @@ function finishPriceChange(priceChange: PriceChange): void { } /* - * Call the PriceFeed to get the latest price, and update the SystemState through a PriceChange - * if it has changed. + * Update SystemState through a PriceChange if _lastGoodPrice is different from the last recorded + * price. */ -export function checkPrice(event: ethereum.Event): void { +export function updatePrice(event: ethereum.Event, _lastGoodPrice: BigInt): void { let systemState = getCurrentSystemState(); - let oldPrice = systemState.price; - let newPrice = decimalize(getPrice(getPriceFeedAddress())); + let oldPriceOrNull = systemState.price; + let newPrice = decimalize(_lastGoodPrice); + + if (oldPriceOrNull == null) { + // On first price event, just initialize price in the current system state without creating + // a price change. + systemState.price = newPrice; + systemState.save(); + return; + } + + let oldPrice = oldPriceOrNull!; if (newPrice != oldPrice) { let priceChange = createPriceChange(event); @@ -96,7 +104,7 @@ export function checkPrice(event: ethereum.Event): void { systemState.price = newPrice; bumpSystemState(systemState); - priceChange.priceChange = newPrice - oldPrice; + priceChange.priceChange = newPrice.minus(oldPrice); finishPriceChange(priceChange); } } @@ -143,7 +151,7 @@ export function updateSystemStateByTroveChange(troveChange: TroveChange): void { systemState.totalCollateralRatio = calculateCollateralRatio( systemState.totalCollateral, systemState.totalDebt, - systemState.price + systemState.price! // A trove change is guaranteed to be preceeded by a price update ); bumpSystemState(systemState); diff --git a/packages/subgraph/src/entities/Transaction.ts b/packages/subgraph/src/entities/Transaction.ts index 90fb807a0..3817c1a9e 100644 --- a/packages/subgraph/src/entities/Transaction.ts +++ b/packages/subgraph/src/entities/Transaction.ts @@ -3,14 +3,10 @@ import { ethereum } from "@graphprotocol/graph-ts"; import { Transaction } from "../../generated/schema"; import { getTransactionSequenceNumber } from "./Global"; -import { checkPrice } from "./SystemState"; /* * Return existing entity for the transaction that emitted this event, or create and return a new * one if none exists yet. - * - * When creating a new Transaction, it checks if the price has changed. This may have the side - * effect of creating new SystemState and PriceChange entities. */ export function getTransaction(event: ethereum.Event): Transaction { let transactionId = event.transaction.hash.toHex(); @@ -26,8 +22,6 @@ export function getTransaction(event: ethereum.Event): Transaction { newTransaction.timestamp = event.block.timestamp.toI32(); newTransaction.save(); - checkPrice(event); - return newTransaction; } } diff --git a/packages/subgraph/src/entities/Trove.ts b/packages/subgraph/src/entities/Trove.ts index 017db23f8..916b88dc5 100644 --- a/packages/subgraph/src/entities/Trove.ts +++ b/packages/subgraph/src/entities/Trove.ts @@ -74,7 +74,7 @@ function setTroveStatus(trove: Trove, status: string): void { } function createTroveChange(event: ethereum.Event): TroveChange { - let sequenceNumber = beginChange(event); + let sequenceNumber = beginChange(); let troveChange = new TroveChange(sequenceNumber.toString()); initChange(troveChange, event, sequenceNumber); diff --git a/packages/subgraph/src/entities/User.ts b/packages/subgraph/src/entities/User.ts index 7fc7c3e25..007aad5b5 100644 --- a/packages/subgraph/src/entities/User.ts +++ b/packages/subgraph/src/entities/User.ts @@ -23,7 +23,7 @@ export function getUser(_user: Address): User { } function createCollSurplusChange(event: ethereum.Event): CollSurplusChange { - let sequenceNumber = beginChange(event); + let sequenceNumber = beginChange(); let collSurplusChange = new CollSurplusChange(sequenceNumber.toString()); initChange(collSurplusChange, event, sequenceNumber); @@ -52,8 +52,9 @@ export function updateUserClaimColl( collSurplusChange.collSurplusBefore = user.collSurplus; collSurplusChange.collSurplusAfter = newCollSurplus; - collSurplusChange.collSurplusChange = - collSurplusChange.collSurplusAfter - collSurplusChange.collSurplusBefore; + collSurplusChange.collSurplusChange = collSurplusChange.collSurplusAfter.minus( + collSurplusChange.collSurplusBefore + ); updateSystemStateByCollSurplusChange(collSurplusChange); finishCollSurplusChange(collSurplusChange); diff --git a/packages/subgraph/src/mappings/PriceFeed.ts b/packages/subgraph/src/mappings/PriceFeed.ts new file mode 100644 index 000000000..f1a73a921 --- /dev/null +++ b/packages/subgraph/src/mappings/PriceFeed.ts @@ -0,0 +1,7 @@ +import { LastGoodPriceUpdated } from "../../generated/PriceFeed/PriceFeed"; + +import { updatePrice } from "../entities/SystemState"; + +export function handleLastGoodPriceUpdated(event: LastGoodPriceUpdated): void { + updatePrice(event, event.params._lastGoodPrice); +} diff --git a/packages/subgraph/src/mappings/TroveManager.ts b/packages/subgraph/src/mappings/TroveManager.ts index 6f04bbc95..7683e55a3 100644 --- a/packages/subgraph/src/mappings/TroveManager.ts +++ b/packages/subgraph/src/mappings/TroveManager.ts @@ -3,7 +3,6 @@ import { TroveLiquidated, Liquidation, Redemption, - PriceFeedAddressChanged, LTermsUpdated } from "../../generated/TroveManager/TroveManager"; @@ -12,11 +11,7 @@ import { getTroveOperationFromTroveManagerOperation } from "../types/TroveOperat import { finishCurrentLiquidation } from "../entities/Liquidation"; import { finishCurrentRedemption } from "../entities/Redemption"; import { applyRedistributionToTroveBeforeLiquidation, updateTrove } from "../entities/Trove"; -import { updatePriceFeedAddress, updateTotalRedistributed } from "../entities/Global"; - -export function handlePriceFeedAddressChanged(event: PriceFeedAddressChanged): void { - updatePriceFeedAddress(event.params._newPriceFeedAddress); -} +import { updateTotalRedistributed } from "../entities/Global"; export function handleTroveUpdated(event: TroveUpdated): void { updateTrove( diff --git a/packages/subgraph/src/utils/bignumbers.ts b/packages/subgraph/src/utils/bignumbers.ts index 3042e1751..278d2b28c 100644 --- a/packages/subgraph/src/utils/bignumbers.ts +++ b/packages/subgraph/src/utils/bignumbers.ts @@ -12,8 +12,6 @@ export let BIGINT_MAX_UINT256 = BigInt.fromUnsignedBytes( Bytes.fromHexString("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF") as Bytes ); -export let DECIMAL_INITIAL_PRICE = BigDecimal.fromString("200"); - export function decimalize(bigInt: BigInt): BigDecimal { return bigInt.divDecimal(DECIMAL_SCALING_FACTOR); } diff --git a/packages/subgraph/subgraph.yaml.js b/packages/subgraph/subgraph.yaml.js index 26daa0b1e..672b9f0dc 100644 --- a/packages/subgraph/subgraph.yaml.js +++ b/packages/subgraph/subgraph.yaml.js @@ -34,7 +34,6 @@ dataSources: - Global - User - Transaction - - PriceChange - Trove - TroveChange - Redemption @@ -43,11 +42,7 @@ dataSources: abis: - name: TroveManager file: ../lib-ethers/abi/TroveManager.json - - name: PriceFeed - file: ../lib-ethers/abi/PriceFeed.json eventHandlers: - - event: PriceFeedAddressChanged(address) - handler: handlePriceFeedAddressChanged - event: TroveUpdated(indexed address,uint256,uint256,uint256,uint8) handler: handleTroveUpdated - event: TroveLiquidated(indexed address,uint256,uint256,uint8) @@ -74,22 +69,40 @@ dataSources: - Global - User - Transaction - - PriceChange - Trove - TroveChange - SystemState abis: - name: BorrowerOperations file: ../lib-ethers/abi/BorrowerOperations.json - - name: TroveManager - file: ../lib-ethers/abi/TroveManager.json - - name: PriceFeed - file: ../lib-ethers/abi/PriceFeed.json eventHandlers: - event: TroveUpdated(indexed address,uint256,uint256,uint256,uint8) handler: handleTroveUpdated - event: LUSDBorrowingFeePaid(indexed address,uint256) handler: handleLUSDBorrowingFeePaid + - name: PriceFeed + kind: ethereum/contract + network: mainnet + source: + abi: PriceFeed + address: "${addresses.priceFeed}" + startBlock: ${startBlock} + mapping: + file: ./src/mappings/PriceFeed.ts + language: wasm/assemblyscript + kind: ethereum/events + apiVersion: 0.0.4 + entities: + - Global + - Transaction + - PriceChange + - SystemState + abis: + - name: PriceFeed + file: ../lib-ethers/abi/PriceFeed.json + eventHandlers: + - event: LastGoodPriceUpdated(uint256) + handler: handleLastGoodPriceUpdated - name: StabilityPool kind: ethereum/contract network: mainnet @@ -106,7 +119,6 @@ dataSources: - Global - User - Transaction - - PriceChange - StabilityDeposit - StabilityDepositChange - SystemState @@ -114,8 +126,6 @@ dataSources: abis: - name: StabilityPool file: ../lib-ethers/abi/StabilityPool.json - - name: PriceFeed - file: ../lib-ethers/abi/PriceFeed.json eventHandlers: - event: UserDepositChanged(indexed address,uint256) handler: handleUserDepositChanged @@ -147,8 +157,6 @@ dataSources: abis: - name: CollSurplusPool file: ../lib-ethers/abi/CollSurplusPool.json - - name: PriceFeed - file: ../lib-ethers/abi/PriceFeed.json eventHandlers: - event: CollBalanceUpdated(indexed address,uint256) handler: handleCollSurplusBalanceUpdated @@ -173,8 +181,6 @@ dataSources: abis: - name: LQTYStaking file: ../lib-ethers/abi/LQTYStaking.json - - name: PriceFeed - file: ../lib-ethers/abi/PriceFeed.json eventHandlers: - event: StakeChanged(indexed address,uint256) handler: handleStakeChanged From 3768d5a4f3d244cda36b40fcc9dbcc5b5a441d76 Mon Sep 17 00:00:00 2001 From: Daniel Simon Date: Fri, 4 Jun 2021 11:01:52 +0700 Subject: [PATCH 36/58] chore: update lib-subgraph to latest subgraph schema --- packages/lib-subgraph/apollo.config.js | 2 +- packages/lib-subgraph/src/SubgraphLiquity.ts | 6 +++--- packages/lib-subgraph/types/Global.ts | 2 +- packages/lib-subgraph/types/TroveWithoutRewards.ts | 8 ++++---- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/lib-subgraph/apollo.config.js b/packages/lib-subgraph/apollo.config.js index e21426ad5..85688bd51 100644 --- a/packages/lib-subgraph/apollo.config.js +++ b/packages/lib-subgraph/apollo.config.js @@ -2,7 +2,7 @@ module.exports = { client: { service: { name: "liquity-subgraph", - url: "http://localhost:8000/subgraphs/name/liquity/subgraph" + url: "http://localhost:8000/subgraphs/name/liquity/liquity" } } }; diff --git a/packages/lib-subgraph/src/SubgraphLiquity.ts b/packages/lib-subgraph/src/SubgraphLiquity.ts index 400c9bcd4..b5d416491 100644 --- a/packages/lib-subgraph/src/SubgraphLiquity.ts +++ b/packages/lib-subgraph/src/SubgraphLiquity.ts @@ -136,7 +136,7 @@ const troveBeforeRedistribution = new Query< query TroveWithoutRewards($address: ID!) { user(id: $address) { id - currentTrove { + trove { id ...TroveRawFields } @@ -145,8 +145,8 @@ const troveBeforeRedistribution = new Query< ${troveRawFields} `, ({ data: { user } }, { address }) => { - if (user?.currentTrove) { - return troveFromRawFields(user.currentTrove); + if (user?.trove) { + return troveFromRawFields(user.trove); } else { return new TroveWithPendingRedistribution(address, "nonExistent"); } diff --git a/packages/lib-subgraph/types/Global.ts b/packages/lib-subgraph/types/Global.ts index b019522da..33df2f629 100644 --- a/packages/lib-subgraph/types/Global.ts +++ b/packages/lib-subgraph/types/Global.ts @@ -13,7 +13,7 @@ export interface Global_global_currentSystemState { * Sequence number as an ID (string) */ id: string; - price: any; + price: any | null; totalCollateral: any; totalDebt: any; tokensInStabilityPool: any; diff --git a/packages/lib-subgraph/types/TroveWithoutRewards.ts b/packages/lib-subgraph/types/TroveWithoutRewards.ts index 45a1fc47b..44cb658e8 100644 --- a/packages/lib-subgraph/types/TroveWithoutRewards.ts +++ b/packages/lib-subgraph/types/TroveWithoutRewards.ts @@ -9,7 +9,7 @@ import { TroveStatus } from "./globalTypes"; // GraphQL query operation: TroveWithoutRewards // ==================================================== -export interface TroveWithoutRewards_user_currentTrove_owner { +export interface TroveWithoutRewards_user_trove_owner { __typename: "User"; /** * User's Ethereum address as a hex-string @@ -17,13 +17,13 @@ export interface TroveWithoutRewards_user_currentTrove_owner { id: string; } -export interface TroveWithoutRewards_user_currentTrove { +export interface TroveWithoutRewards_user_trove { __typename: "Trove"; /** * Owner's ID + '-' + an incremented integer */ id: string; - owner: TroveWithoutRewards_user_currentTrove_owner; + owner: TroveWithoutRewards_user_trove_owner; status: TroveStatus; rawCollateral: any; rawDebt: any; @@ -44,7 +44,7 @@ export interface TroveWithoutRewards_user { * User's Ethereum address as a hex-string */ id: string; - currentTrove: TroveWithoutRewards_user_currentTrove | null; + trove: TroveWithoutRewards_user_trove | null; } export interface TroveWithoutRewards { From dced7a47d73b9c9b44d863d9851701fbd785d274 Mon Sep 17 00:00:00 2001 From: Daniel Simon Date: Fri, 4 Jun 2021 18:50:11 +0700 Subject: [PATCH 37/58] fix: always subtract gas compensation from total collateral Total collateral was incorrect after a redistribution, because we weren't subtracting the 0.5% collateral paid out as gas compensation. Fixes #591. --- packages/subgraph/src/entities/SystemState.ts | 49 +++++++++++++------ packages/subgraph/src/utils/bignumbers.ts | 4 ++ .../subgraph/src/utils/collateralRatio.ts | 4 +- 3 files changed, 41 insertions(+), 16 deletions(-) diff --git a/packages/subgraph/src/entities/SystemState.ts b/packages/subgraph/src/entities/SystemState.ts index 88a167ffe..b31b87406 100644 --- a/packages/subgraph/src/entities/SystemState.ts +++ b/packages/subgraph/src/entities/SystemState.ts @@ -8,7 +8,13 @@ import { CollSurplusChange } from "../../generated/schema"; -import { decimalize, DECIMAL_ZERO, DECIMAL_ONE } from "../utils/bignumbers"; +import { + decimalize, + DECIMAL_ZERO, + DECIMAL_ONE, + DECIMAL_COLLATERAL_GAS_COMPENSATION_DIVISOR, + DECIMAL_PRECISION +} from "../utils/bignumbers"; import { calculateCollateralRatio } from "../utils/collateralRatio"; import { @@ -116,14 +122,18 @@ function tryToOffsetWithTokensFromStabilityPool( ): void { if (debtToLiquidate <= systemState.tokensInStabilityPool) { // Completely offset - systemState.totalCollateral -= collateralToLiquidate; - systemState.totalDebt -= debtToLiquidate; - systemState.tokensInStabilityPool -= debtToLiquidate; + systemState.totalCollateral = systemState.totalCollateral.minus(collateralToLiquidate); + systemState.totalDebt = systemState.totalDebt.minus(debtToLiquidate); + systemState.tokensInStabilityPool = systemState.tokensInStabilityPool.minus(debtToLiquidate); } else if (systemState.tokensInStabilityPool > DECIMAL_ZERO) { // Partially offset, emptying the pool - systemState.totalCollateral -= - (collateralToLiquidate * systemState.tokensInStabilityPool) / debtToLiquidate; - systemState.totalDebt -= systemState.tokensInStabilityPool; + systemState.totalCollateral = systemState.totalCollateral.minus( + collateralToLiquidate + .times(systemState.tokensInStabilityPool) + .div(debtToLiquidate) + .truncate(DECIMAL_PRECISION) + ); + systemState.totalDebt = systemState.totalDebt.minus(systemState.tokensInStabilityPool); systemState.tokensInStabilityPool = DECIMAL_ZERO; } else { // Empty pool @@ -135,15 +145,22 @@ export function updateSystemStateByTroveChange(troveChange: TroveChange): void { let operation = troveChange.troveOperation; if (isBorrowerOperation(operation) || isRedemption(operation)) { - systemState.totalCollateral += troveChange.collateralChange; - systemState.totalDebt += troveChange.debtChange; + systemState.totalCollateral = systemState.totalCollateral.plus(troveChange.collateralChange); + systemState.totalDebt = systemState.totalDebt.plus(troveChange.debtChange); } else if (isLiquidation(operation)) { - // TODO gas compensation + let collateral = troveChange.collateralBefore; + let debt = troveChange.debtBefore; + let collateralGasCompensation = collateral + .div(DECIMAL_COLLATERAL_GAS_COMPENSATION_DIVISOR) + .truncate(DECIMAL_PRECISION); + + systemState.totalCollateral = systemState.totalCollateral.minus(collateralGasCompensation); + if (!isRecoveryModeLiquidation(operation) || troveChange.collateralRatioBefore > DECIMAL_ONE) { tryToOffsetWithTokensFromStabilityPool( systemState, - -troveChange.collateralChange, - -troveChange.debtChange + collateral.minus(collateralGasCompensation), + debt ); } } @@ -164,7 +181,9 @@ export function updateSystemStateByStabilityDepositChange( let operation = stabilityDepositChange.stabilityDepositOperation; if (operation == "depositTokens" || operation == "withdrawTokens") { - systemState.tokensInStabilityPool += stabilityDepositChange.depositedAmountChange; + systemState.tokensInStabilityPool = systemState.tokensInStabilityPool.plus( + stabilityDepositChange.depositedAmountChange + ); } bumpSystemState(systemState); @@ -173,7 +192,9 @@ export function updateSystemStateByStabilityDepositChange( export function updateSystemStateByCollSurplusChange(collSurplusChange: CollSurplusChange): void { let systemState = getCurrentSystemState(); - systemState.collSurplusPoolBalance += collSurplusChange.collSurplusChange; + systemState.collSurplusPoolBalance = systemState.collSurplusPoolBalance.plus( + collSurplusChange.collSurplusChange + ); bumpSystemState(systemState); } diff --git a/packages/subgraph/src/utils/bignumbers.ts b/packages/subgraph/src/utils/bignumbers.ts index 278d2b28c..14cab324c 100644 --- a/packages/subgraph/src/utils/bignumbers.ts +++ b/packages/subgraph/src/utils/bignumbers.ts @@ -1,5 +1,7 @@ import { Bytes, BigInt, BigDecimal } from "@graphprotocol/graph-ts"; +export let DECIMAL_PRECISION = 18; + // E.g. 1.5 is represented as 1.5 * 10^18, where 10^18 is called the scaling factor export let DECIMAL_SCALING_FACTOR = BigDecimal.fromString("1000000000000000000"); export let BIGINT_SCALING_FACTOR = BigInt.fromI32(10).pow(18); @@ -7,6 +9,8 @@ export let BIGINT_SCALING_FACTOR = BigInt.fromI32(10).pow(18); export let DECIMAL_ZERO = BigDecimal.fromString("0"); export let DECIMAL_ONE = BigDecimal.fromString("1"); +export let DECIMAL_COLLATERAL_GAS_COMPENSATION_DIVISOR = BigDecimal.fromString("200"); + export let BIGINT_ZERO = BigInt.fromI32(0); export let BIGINT_MAX_UINT256 = BigInt.fromUnsignedBytes( Bytes.fromHexString("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF") as Bytes diff --git a/packages/subgraph/src/utils/collateralRatio.ts b/packages/subgraph/src/utils/collateralRatio.ts index 94b540ebf..b82eb0419 100644 --- a/packages/subgraph/src/utils/collateralRatio.ts +++ b/packages/subgraph/src/utils/collateralRatio.ts @@ -1,6 +1,6 @@ import { BigDecimal } from "@graphprotocol/graph-ts"; -import { DECIMAL_ZERO } from "./bignumbers"; +import { DECIMAL_PRECISION, DECIMAL_ZERO } from "./bignumbers"; export function calculateCollateralRatio( collateral: BigDecimal, @@ -11,5 +11,5 @@ export function calculateCollateralRatio( return null; } - return (collateral * price) / debt; + return collateral.times(price).div(debt).truncate(DECIMAL_PRECISION); } From a015ef9fd97df3324d2a73f8fac983d220595625 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Fingen?= Date: Wed, 9 Jun 2021 18:11:12 +0200 Subject: [PATCH 38/58] contracts: Fix simulation tests --- packages/contracts/brownie-config.yaml | 2 +- packages/contracts/tests/simulation_test.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/contracts/brownie-config.yaml b/packages/contracts/brownie-config.yaml index 8a118381b..7ddfef7d6 100644 --- a/packages/contracts/brownie-config.yaml +++ b/packages/contracts/brownie-config.yaml @@ -7,7 +7,7 @@ networks: default_contract_owner: true cmd_settings: port: 8545 - gas_limit: 6721975 + gas_limit: 8000000 accounts: 1000 chain_id: 1337 network_id: 1588949648 diff --git a/packages/contracts/tests/simulation_test.py b/packages/contracts/tests/simulation_test.py index 7047c2dee..958bcf811 100644 --- a/packages/contracts/tests/simulation_test.py +++ b/packages/contracts/tests/simulation_test.py @@ -137,6 +137,7 @@ def contracts(): contracts.lockupContractFactory.address, accounts[0], # bountyAddress accounts[0], # lpRewardsAddress + accounts[0], # multisigAddress { 'from': accounts[0] } ) From 9d5bc8dc6c1650e8f789b0303b4ebab780189387 Mon Sep 17 00:00:00 2001 From: bojan-liquity <73836320+bojan-liquity@users.noreply.github.com> Date: Fri, 11 Jun 2021 09:38:38 +0200 Subject: [PATCH 39/58] Rename Liquity Whitepaper rev. 0.2.tex to Liquity Whitepaper rev. 0.3.tex --- ...ty Whitepaper rev. 0.2.tex => Liquity Whitepaper rev. 0.3.tex} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename papers/whitepaper/{Liquity Whitepaper rev. 0.2.tex => Liquity Whitepaper rev. 0.3.tex} (100%) diff --git a/papers/whitepaper/Liquity Whitepaper rev. 0.2.tex b/papers/whitepaper/Liquity Whitepaper rev. 0.3.tex similarity index 100% rename from papers/whitepaper/Liquity Whitepaper rev. 0.2.tex rename to papers/whitepaper/Liquity Whitepaper rev. 0.3.tex From 0bf96e76cf503e8217fe4878e57f4dfc0b60ffb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Fingen?= Date: Fri, 11 Jun 2021 16:05:05 +0200 Subject: [PATCH 40/58] contracts: Add balancer pool deployment script --- .../contracts/hardhat.config.mainnet-fork.js | 4 +- .../contracts/mainnetDeployment/ABIs/ERC20.js | 402 +++--- .../mainnetDeployment/ABIs/IVault.js | 1136 ++++++++++++++++ .../contracts/mainnetDeployment/ABIs/WETH.js | 8 + .../ABIs/WeightedPool2Tokens.js | 1148 +++++++++++++++++ .../ABIs/WeightedPool2TokensFactory.js | 131 ++ .../balancerPoolDeployment.js | 154 +++ 7 files changed, 2781 insertions(+), 202 deletions(-) create mode 100644 packages/contracts/mainnetDeployment/ABIs/IVault.js create mode 100644 packages/contracts/mainnetDeployment/ABIs/WETH.js create mode 100644 packages/contracts/mainnetDeployment/ABIs/WeightedPool2Tokens.js create mode 100644 packages/contracts/mainnetDeployment/ABIs/WeightedPool2TokensFactory.js create mode 100644 packages/contracts/mainnetDeployment/balancerPoolDeployment.js diff --git a/packages/contracts/hardhat.config.mainnet-fork.js b/packages/contracts/hardhat.config.mainnet-fork.js index 606618433..48c3ec6be 100644 --- a/packages/contracts/hardhat.config.mainnet-fork.js +++ b/packages/contracts/hardhat.config.mainnet-fork.js @@ -58,10 +58,10 @@ module.exports = { accounts: accountsList, gas: 10000000, // tx gas limit blockGasLimit: 12500000, - gasPrice: 20000000000, + gasPrice: process.env.GAS_PRICE ? parseInt(process.env.GAS_PRICE) : 20000000000, forking: { url: alchemyUrl(), - blockNumber: 12152522 + blockNumber: process.env.BLOCK_NUMBER ? parseInt(process.env.BLOCK_NUMBER) : 12152522 } } }, diff --git a/packages/contracts/mainnetDeployment/ABIs/ERC20.js b/packages/contracts/mainnetDeployment/ABIs/ERC20.js index 3ade5b3d5..4c605dc5d 100644 --- a/packages/contracts/mainnetDeployment/ABIs/ERC20.js +++ b/packages/contracts/mainnetDeployment/ABIs/ERC20.js @@ -1,226 +1,228 @@ -const ERC20Abi = [ +const ERC20 = { + "abi": [ { - "constant": true, - "inputs": [], - "name": "name", - "outputs": [ - { - "name": "", - "type": "string" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" + "constant": true, + "inputs": [], + "name": "name", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" }, { - "constant": false, - "inputs": [ - { - "name": "_spender", - "type": "address" - }, - { - "name": "_value", - "type": "uint256" - } - ], - "name": "approve", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" + "constant": false, + "inputs": [ + { + "name": "_spender", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" }, { - "constant": true, - "inputs": [], - "name": "totalSupply", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" }, { - "constant": false, - "inputs": [ - { - "name": "_from", - "type": "address" - }, - { - "name": "_to", - "type": "address" - }, - { - "name": "_value", - "type": "uint256" - } - ], - "name": "transferFrom", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" + "constant": false, + "inputs": [ + { + "name": "_from", + "type": "address" + }, + { + "name": "_to", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" }, { - "constant": true, - "inputs": [], - "name": "decimals", - "outputs": [ - { - "name": "", - "type": "uint8" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" + "constant": true, + "inputs": [], + "name": "decimals", + "outputs": [ + { + "name": "", + "type": "uint8" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" }, { - "constant": true, - "inputs": [ - { - "name": "_owner", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "name": "balance", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "name": "balance", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" }, { - "constant": true, - "inputs": [], - "name": "symbol", - "outputs": [ - { - "name": "", - "type": "string" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" + "constant": true, + "inputs": [], + "name": "symbol", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" }, { - "constant": false, - "inputs": [ - { - "name": "_to", - "type": "address" - }, - { - "name": "_value", - "type": "uint256" - } - ], - "name": "transfer", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" + "constant": false, + "inputs": [ + { + "name": "_to", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" }, { - "constant": true, - "inputs": [ - { - "name": "_owner", - "type": "address" - }, - { - "name": "_spender", - "type": "address" - } - ], - "name": "allowance", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + }, + { + "name": "_spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" }, { - "payable": true, - "stateMutability": "payable", - "type": "fallback" + "payable": true, + "stateMutability": "payable", + "type": "fallback" }, { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "name": "spender", - "type": "address" - }, - { - "indexed": false, - "name": "value", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" }, { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "from", - "type": "address" - }, - { - "indexed": true, - "name": "to", - "type": "address" - }, - { - "indexed": false, - "name": "value", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "from", + "type": "address" + }, + { + "indexed": true, + "name": "to", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" } -] + ] +} module.exports = { - ERC20Abi: ERC20Abi -} \ No newline at end of file + ERC20: ERC20 +} diff --git a/packages/contracts/mainnetDeployment/ABIs/IVault.js b/packages/contracts/mainnetDeployment/ABIs/IVault.js new file mode 100644 index 000000000..4425f61c0 --- /dev/null +++ b/packages/contracts/mainnetDeployment/ABIs/IVault.js @@ -0,0 +1,1136 @@ +const IVault = { + "abi": [ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract IAuthorizer", + "name": "newAuthorizer", + "type": "address" + } + ], + "name": "AuthorizerChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "ExternalBalanceTransfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract IFlashLoanRecipient", + "name": "recipient", + "type": "address" + }, + { + "indexed": true, + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "feeAmount", + "type": "uint256" + } + ], + "name": "FlashLoan", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": true, + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "int256", + "name": "delta", + "type": "int256" + } + ], + "name": "InternalBalanceChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "paused", + "type": "bool" + } + ], + "name": "PausedStateChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "liquidityProvider", + "type": "address" + }, + { + "indexed": false, + "internalType": "contract IERC20[]", + "name": "tokens", + "type": "address[]" + }, + { + "indexed": false, + "internalType": "int256[]", + "name": "deltas", + "type": "int256[]" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "protocolFeeAmounts", + "type": "uint256[]" + } + ], + "name": "PoolBalanceChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "assetManager", + "type": "address" + }, + { + "indexed": true, + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "int256", + "name": "cashDelta", + "type": "int256" + }, + { + "indexed": false, + "internalType": "int256", + "name": "managedDelta", + "type": "int256" + } + ], + "name": "PoolBalanceManaged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "poolAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "enum IVault.PoolSpecialization", + "name": "specialization", + "type": "uint8" + } + ], + "name": "PoolRegistered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "relayer", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "RelayerApprovalChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "contract IERC20", + "name": "tokenIn", + "type": "address" + }, + { + "indexed": true, + "internalType": "contract IERC20", + "name": "tokenOut", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + } + ], + "name": "Swap", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "contract IERC20[]", + "name": "tokens", + "type": "address[]" + } + ], + "name": "TokensDeregistered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "contract IERC20[]", + "name": "tokens", + "type": "address[]" + }, + { + "indexed": false, + "internalType": "address[]", + "name": "assetManagers", + "type": "address[]" + } + ], + "name": "TokensRegistered", + "type": "event" + }, + { + "inputs": [], + "name": "WETH", + "outputs": [ + { + "internalType": "contract IWETH", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "enum IVault.SwapKind", + "name": "kind", + "type": "uint8" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "assetInIndex", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "assetOutIndex", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "internalType": "struct IVault.BatchSwapStep[]", + "name": "swaps", + "type": "tuple[]" + }, + { + "internalType": "contract IAsset[]", + "name": "assets", + "type": "address[]" + }, + { + "components": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "bool", + "name": "fromInternalBalance", + "type": "bool" + }, + { + "internalType": "address payable", + "name": "recipient", + "type": "address" + }, + { + "internalType": "bool", + "name": "toInternalBalance", + "type": "bool" + } + ], + "internalType": "struct IVault.FundManagement", + "name": "funds", + "type": "tuple" + }, + { + "internalType": "int256[]", + "name": "limits", + "type": "int256[]" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "batchSwap", + "outputs": [ + { + "internalType": "int256[]", + "name": "", + "type": "int256[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "internalType": "contract IERC20[]", + "name": "tokens", + "type": "address[]" + } + ], + "name": "deregisterTokens", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address payable", + "name": "recipient", + "type": "address" + }, + { + "components": [ + { + "internalType": "contract IAsset[]", + "name": "assets", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "minAmountsOut", + "type": "uint256[]" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + }, + { + "internalType": "bool", + "name": "toInternalBalance", + "type": "bool" + } + ], + "internalType": "struct IVault.ExitPoolRequest", + "name": "request", + "type": "tuple" + } + ], + "name": "exitPool", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IFlashLoanRecipient", + "name": "recipient", + "type": "address" + }, + { + "internalType": "contract IERC20[]", + "name": "tokens", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "amounts", + "type": "uint256[]" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "name": "flashLoan", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getAuthorizer", + "outputs": [ + { + "internalType": "contract IAuthorizer", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getDomainSeparator", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "internalType": "contract IERC20[]", + "name": "tokens", + "type": "address[]" + } + ], + "name": "getInternalBalance", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + } + ], + "name": "getNextNonce", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getPausedState", + "outputs": [ + { + "internalType": "bool", + "name": "paused", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "pauseWindowEndTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "bufferPeriodEndTime", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + } + ], + "name": "getPool", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "enum IVault.PoolSpecialization", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + } + ], + "name": "getPoolTokenInfo", + "outputs": [ + { + "internalType": "uint256", + "name": "cash", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "managed", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lastChangeBlock", + "type": "uint256" + }, + { + "internalType": "address", + "name": "assetManager", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + } + ], + "name": "getPoolTokens", + "outputs": [ + { + "internalType": "contract IERC20[]", + "name": "tokens", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "balances", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "lastChangeBlock", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getProtocolFeesCollector", + "outputs": [ + { + "internalType": "contract IProtocolFeesCollector", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "internalType": "address", + "name": "relayer", + "type": "address" + } + ], + "name": "hasApprovedRelayer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "components": [ + { + "internalType": "contract IAsset[]", + "name": "assets", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "maxAmountsIn", + "type": "uint256[]" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + }, + { + "internalType": "bool", + "name": "fromInternalBalance", + "type": "bool" + } + ], + "internalType": "struct IVault.JoinPoolRequest", + "name": "request", + "type": "tuple" + } + ], + "name": "joinPool", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "enum IVault.PoolBalanceOpKind", + "name": "kind", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct IVault.PoolBalanceOp[]", + "name": "ops", + "type": "tuple[]" + } + ], + "name": "managePoolBalance", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "enum IVault.UserBalanceOpKind", + "name": "kind", + "type": "uint8" + }, + { + "internalType": "contract IAsset", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address payable", + "name": "recipient", + "type": "address" + } + ], + "internalType": "struct IVault.UserBalanceOp[]", + "name": "ops", + "type": "tuple[]" + } + ], + "name": "manageUserBalance", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "enum IVault.SwapKind", + "name": "kind", + "type": "uint8" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "assetInIndex", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "assetOutIndex", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "internalType": "struct IVault.BatchSwapStep[]", + "name": "swaps", + "type": "tuple[]" + }, + { + "internalType": "contract IAsset[]", + "name": "assets", + "type": "address[]" + }, + { + "components": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "bool", + "name": "fromInternalBalance", + "type": "bool" + }, + { + "internalType": "address payable", + "name": "recipient", + "type": "address" + }, + { + "internalType": "bool", + "name": "toInternalBalance", + "type": "bool" + } + ], + "internalType": "struct IVault.FundManagement", + "name": "funds", + "type": "tuple" + } + ], + "name": "queryBatchSwap", + "outputs": [ + { + "internalType": "int256[]", + "name": "assetDeltas", + "type": "int256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "enum IVault.PoolSpecialization", + "name": "specialization", + "type": "uint8" + } + ], + "name": "registerPool", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "internalType": "contract IERC20[]", + "name": "tokens", + "type": "address[]" + }, + { + "internalType": "address[]", + "name": "assetManagers", + "type": "address[]" + } + ], + "name": "registerTokens", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IAuthorizer", + "name": "newAuthorizer", + "type": "address" + } + ], + "name": "setAuthorizer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "paused", + "type": "bool" + } + ], + "name": "setPaused", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "relayer", + "type": "address" + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "setRelayerApproval", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "internalType": "enum IVault.SwapKind", + "name": "kind", + "type": "uint8" + }, + { + "internalType": "contract IAsset", + "name": "assetIn", + "type": "address" + }, + { + "internalType": "contract IAsset", + "name": "assetOut", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "internalType": "struct IVault.SingleSwap", + "name": "singleSwap", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "bool", + "name": "fromInternalBalance", + "type": "bool" + }, + { + "internalType": "address payable", + "name": "recipient", + "type": "address" + }, + { + "internalType": "bool", + "name": "toInternalBalance", + "type": "bool" + } + ], + "internalType": "struct IVault.FundManagement", + "name": "funds", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "limit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "swap", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + } + ] +} + +module.exports = { + IVault: IVault +} diff --git a/packages/contracts/mainnetDeployment/ABIs/WETH.js b/packages/contracts/mainnetDeployment/ABIs/WETH.js new file mode 100644 index 000000000..d609e4722 --- /dev/null +++ b/packages/contracts/mainnetDeployment/ABIs/WETH.js @@ -0,0 +1,8 @@ +// https://etherscan.io/address/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2#code +const WETH = { + "abi": [{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"guy","type":"address"},{"name":"wad","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"src","type":"address"},{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"wad","type":"uint256"}],"name":"withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"deposit","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":true,"name":"guy","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":true,"name":"dst","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"dst","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Withdrawal","type":"event"}] +} + +module.exports = { + WETH: WETH +} diff --git a/packages/contracts/mainnetDeployment/ABIs/WeightedPool2Tokens.js b/packages/contracts/mainnetDeployment/ABIs/WeightedPool2Tokens.js new file mode 100644 index 000000000..f662270b9 --- /dev/null +++ b/packages/contracts/mainnetDeployment/ABIs/WeightedPool2Tokens.js @@ -0,0 +1,1148 @@ +const WeightedPool2Tokens = { + "abi": [ + { + "inputs": [ + { + "components": [ + { + "internalType": "contract IVault", + "name": "vault", + "type": "address" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "symbol", + "type": "string" + }, + { + "internalType": "contract IERC20", + "name": "token0", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "token1", + "type": "address" + }, + { + "internalType": "uint256", + "name": "normalizedWeight0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "normalizedWeight1", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "swapFeePercentage", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "pauseWindowDuration", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "bufferPeriodDuration", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "oracleEnabled", + "type": "bool" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "internalType": "struct WeightedPool2Tokens.NewPoolParams", + "name": "params", + "type": "tuple" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "enabled", + "type": "bool" + } + ], + "name": "OracleEnabledChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "paused", + "type": "bool" + } + ], + "name": "PausedStateChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "swapFeePercentage", + "type": "uint256" + } + ], + "name": "SwapFeePercentageChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "enableOracle", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "selector", + "type": "bytes4" + } + ], + "name": "getActionId", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getAuthorizer", + "outputs": [ + { + "internalType": "contract IAuthorizer", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getInvariant", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getLargestSafeQueryWindow", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "getLastInvariant", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "enum IPriceOracle.Variable", + "name": "variable", + "type": "uint8" + } + ], + "name": "getLatest", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getMiscData", + "outputs": [ + { + "internalType": "int256", + "name": "logInvariant", + "type": "int256" + }, + { + "internalType": "int256", + "name": "logTotalSupply", + "type": "int256" + }, + { + "internalType": "uint256", + "name": "oracleSampleCreationTimestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "oracleIndex", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "oracleEnabled", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "swapFeePercentage", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getNormalizedWeights", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "enum IPriceOracle.Variable", + "name": "variable", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "ago", + "type": "uint256" + } + ], + "internalType": "struct IPriceOracle.OracleAccumulatorQuery[]", + "name": "queries", + "type": "tuple[]" + } + ], + "name": "getPastAccumulators", + "outputs": [ + { + "internalType": "int256[]", + "name": "results", + "type": "int256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getPausedState", + "outputs": [ + { + "internalType": "bool", + "name": "paused", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "pauseWindowEndTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "bufferPeriodEndTime", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getPoolId", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "getSample", + "outputs": [ + { + "internalType": "int256", + "name": "logPairPrice", + "type": "int256" + }, + { + "internalType": "int256", + "name": "accLogPairPrice", + "type": "int256" + }, + { + "internalType": "int256", + "name": "logBptPrice", + "type": "int256" + }, + { + "internalType": "int256", + "name": "accLogBptPrice", + "type": "int256" + }, + { + "internalType": "int256", + "name": "logInvariant", + "type": "int256" + }, + { + "internalType": "int256", + "name": "accLogInvariant", + "type": "int256" + }, + { + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getSwapFeePercentage", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "enum IPriceOracle.Variable", + "name": "variable", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "secs", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "ago", + "type": "uint256" + } + ], + "internalType": "struct IPriceOracle.OracleAverageQuery[]", + "name": "queries", + "type": "tuple[]" + } + ], + "name": "getTimeWeightedAverage", + "outputs": [ + { + "internalType": "uint256[]", + "name": "results", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getTotalSamples", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "getVault", + "outputs": [ + { + "internalType": "contract IVault", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "addedValue", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "nonces", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "balances", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "lastChangeBlock", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "protocolSwapFeePercentage", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "name": "onExitPool", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "balances", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "lastChangeBlock", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "protocolSwapFeePercentage", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "name": "onJoinPool", + "outputs": [ + { + "internalType": "uint256[]", + "name": "amountsIn", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "dueProtocolFeeAmounts", + "type": "uint256[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "enum IVault.SwapKind", + "name": "kind", + "type": "uint8" + }, + { + "internalType": "contract IERC20", + "name": "tokenIn", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "tokenOut", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "lastChangeBlock", + "type": "uint256" + }, + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "internalType": "struct IPoolSwapStructs.SwapRequest", + "name": "request", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "balanceTokenIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "balanceTokenOut", + "type": "uint256" + } + ], + "name": "onSwap", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "permit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "balances", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "lastChangeBlock", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "protocolSwapFeePercentage", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "name": "queryExit", + "outputs": [ + { + "internalType": "uint256", + "name": "bptIn", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "amountsOut", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "balances", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "lastChangeBlock", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "protocolSwapFeePercentage", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "name": "queryJoin", + "outputs": [ + { + "internalType": "uint256", + "name": "bptOut", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "amountsIn", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "paused", + "type": "bool" + } + ], + "name": "setPaused", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "swapFeePercentage", + "type": "uint256" + } + ], + "name": "setSwapFeePercentage", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } + ] +} + +module.exports = { + WeightedPool2Tokens: WeightedPool2Tokens +} diff --git a/packages/contracts/mainnetDeployment/ABIs/WeightedPool2TokensFactory.js b/packages/contracts/mainnetDeployment/ABIs/WeightedPool2TokensFactory.js new file mode 100644 index 000000000..eee12af5f --- /dev/null +++ b/packages/contracts/mainnetDeployment/ABIs/WeightedPool2TokensFactory.js @@ -0,0 +1,131 @@ +const WeightedPool2TokensFactory = { + "abi": [ + { + "inputs": [ + { + "internalType": "contract IVault", + "name": "vault", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "PoolCreated", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "symbol", + "type": "string" + }, + { + "internalType": "contract IERC20[]", + "name": "tokens", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "weights", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "swapFeePercentage", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "oracleEnabled", + "type": "bool" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "create", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getPauseConfiguration", + "outputs": [ + { + "internalType": "uint256", + "name": "pauseWindowDuration", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "bufferPeriodDuration", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getVault", + "outputs": [ + { + "internalType": "contract IVault", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "isPoolFromFactory", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + } + ] +} + +module.exports = { + WeightedPool2TokensFactory: WeightedPool2TokensFactory +} diff --git a/packages/contracts/mainnetDeployment/balancerPoolDeployment.js b/packages/contracts/mainnetDeployment/balancerPoolDeployment.js new file mode 100644 index 000000000..5401c1323 --- /dev/null +++ b/packages/contracts/mainnetDeployment/balancerPoolDeployment.js @@ -0,0 +1,154 @@ +// Test with: +// GAS_PRICE=0 BLOCK_NUMBER=12612689 npx hardhat run mainnetDeployment/balancerPoolDeployment.js --config hardhat.config.mainnet-fork.js + +const { WeightedPool2TokensFactory } = require("./ABIs/WeightedPool2TokensFactory.js") +const { WeightedPool2Tokens } = require("./ABIs/WeightedPool2Tokens.js") +const { IVault } = require("./ABIs/IVault.js") +const { ERC20 } = require("./ABIs/ERC20.js") +const { WETH: WETH_ABI } = require("./ABIs/WETH.js") +const toBigNum = ethers.BigNumber.from +const { TestHelper: th, TimeValues: timeVals } = require("../utils/testHelpers.js") +const { dec } = th +// Addresses are the same on all networks + +const VAULT = '0xBA12222222228d8Ba445958a75a0704d566BF2C8'; + +const WEIGHTED_POOL_FACTORY = '0x8E9aa87E45e92bad84D5F8DD1bff34Fb92637dE9'; +const ORACLE_POOL_FACTORY = '0xA5bf2ddF098bb0Ef6d120C98217dD6B141c74EE0'; + +const DELEGATE_OWNER = '0xBA1BA1ba1BA1bA1bA1Ba1BA1ba1BA1bA1ba1ba1B'; + +// Mainnet addresses; adjust for testnets + +const WETH = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'; +const LUSD = '0x5f98805A4E8be255a32880FDeC7F6728C6568bA0'; +const PRICE_FEED = '0x4c517D4e2C851CA76d7eC94B805269Df0f2201De'; + +const tokens = [LUSD, WETH]; +const weights = [toBigNum(dec(4, 17)), toBigNum(dec(6, 17))]; + +const NAME = 'WETH/LUSD Pool'; +const SYMBOL = '60WETH-40LUSD'; +const swapFeePercentage = toBigNum(dec(5, 15)); // 0.5% + +const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; + +const INITIAL_FUNDING = toBigNum(dec(5, 22)); // $50k + +async function main() { + // Uncomment for testing: + + const impersonateAddress = "0x787EfF01c9FdC1918d1AA6eeFf089B191e2922E4" + await hre.network.provider.request({ + method: "hardhat_impersonateAccount", + params: [ impersonateAddress ] + }) + const deployerWallet = await ethers.provider.getSigner(impersonateAddress) + const deployerWalletAddress = impersonateAddress + + //const deployerWallet = (await ethers.getSigners())[0] + //const deployerWalletAddress = deployerWallet.address + + const factory = new ethers.Contract( + ORACLE_POOL_FACTORY, + WeightedPool2TokensFactory.abi, + deployerWallet + ); + const vault = new ethers.Contract( + VAULT, + IVault.abi, + deployerWallet + ); + + const priceFeed = await ethers.getContractAt('PriceFeed', PRICE_FEED) + + // ZERO_ADDRESS owner means fixed swap fees + // DELEGATE_OWNER grants permission to governance for dynamic fee management + // Any other address lets that address directly set the fees + const oracleEnabled = true; + const tx1 = await factory.create( + NAME, SYMBOL, tokens, weights, + swapFeePercentage, oracleEnabled, + //DELEGATE_OWNER + impersonateAddress + ); + const receipt1 = await tx1.wait(); + + // We need to get the new pool address out of the PoolCreated event + // (Or just grab it from Etherscan) + const events = receipt1.events.filter((e) => e.event === 'PoolCreated'); + const poolAddress = events[0].args.pool; + + // We're going to need the PoolId later, so ask the contract for it + const pool = new ethers.Contract( + poolAddress, + WeightedPool2Tokens.abi, + deployerWallet + ); + const poolId = await pool.getPoolId(); + + // Tokens must be in the same order + // Values must be decimal-normalized! + const eth_price = await priceFeed.lastGoodPrice(); + const weth_balance = INITIAL_FUNDING.mul(weights[1]).div(eth_price); + const lusd_balance = INITIAL_FUNDING.mul(weights[0]).div(toBigNum(dec(1, 18))); + const initialBalances = [lusd_balance, weth_balance]; + th.logBN('Initial LUSD', lusd_balance.toString()) + th.logBN('Initial WETH', weth_balance.toString()) + + const JOIN_KIND_INIT = 0; + + // Construct magic userData + const initUserData = + ethers.utils.defaultAbiCoder.encode(['uint256', 'uint256[]'], + [JOIN_KIND_INIT, initialBalances]); + const joinPoolRequest = { + assets: tokens, + maxAmountsIn: initialBalances, + userData: initUserData, + fromInternalBalance: false + } + + // Caller is "you". joinPool takes a sender (source of initialBalances) + // And a receiver (where BPT are sent). Normally, both are the caller. + // If you have a User Balance of any of these tokens, you can set + // fromInternalBalance to true, and fund a pool with no token transfers + // (well, except for the BPT out) + + // Need to approve the Vault to transfer the tokens! + const weth = new ethers.Contract( + WETH, + WETH_ABI.abi, + deployerWallet + ) + th.logBN('weth balance: ', await weth.balanceOf(deployerWalletAddress)) + const currentWethBalance = await weth.balanceOf(deployerWalletAddress) + if (currentWethBalance.lt(weth_balance)) { + const txDepositWeth = await weth.deposit({ value: weth_balance.sub(currentWethBalance) }); + await txDepositWeth.wait() + } + th.logBN('weth balance: ', await weth.balanceOf(deployerWalletAddress)) + const txApproveWeth = await weth.approve(VAULT, weth_balance); + await txApproveWeth.wait() + const lusd = new ethers.Contract( + LUSD, + ERC20.abi, + deployerWallet + ) + const txApproveLusd = await lusd.approve(VAULT, toBigNum(dec(1, 36))); + await txApproveLusd.wait() + + // joins and exits are done on the Vault, not the pool + const tx2 = await vault.joinPool(poolId, deployerWalletAddress, deployerWalletAddress, joinPoolRequest); + // You can wait for it like this, or just print the tx hash and monitor + const receipt2 = await tx2.wait(); + console.log('Final tx status:', receipt2.status) + th.logBN('Pool BPT tokens', await pool.totalSupply()) +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error); + process.exit(1); + }); From 8d7948dd03fa373ea94aa854cf9119a43ab8edb7 Mon Sep 17 00:00:00 2001 From: Daniel Simon Date: Tue, 15 Jun 2021 13:49:20 +0700 Subject: [PATCH 41/58] chore: update ethers to v5.3 --- packages/dev-frontend/package.json | 4 +- .../src/components/Transaction.tsx | 5 +- packages/lib-base/package.json | 2 +- packages/lib-ethers/package.json | 2 +- packages/lib-react/package.json | 2 +- packages/lib-subgraph/package.json | 2 +- yarn.lock | 630 ++++++++++-------- 7 files changed, 378 insertions(+), 269 deletions(-) diff --git a/packages/dev-frontend/package.json b/packages/dev-frontend/package.json index 96b315c69..f91ac0404 100644 --- a/packages/dev-frontend/package.json +++ b/packages/dev-frontend/package.json @@ -4,7 +4,7 @@ "private": true, "homepage": ".", "dependencies": { - "@ethersproject/abi": "5.0.13", + "@ethersproject/abi": "5.3.1", "@fortawesome/fontawesome-svg-core": "1.2.34", "@fortawesome/free-regular-svg-icons": "5.15.2", "@fortawesome/free-solid-svg-icons": "5.15.2", @@ -27,7 +27,7 @@ "@web3-react/injected-connector": "6.0.7", "@web3-react/types": "6.0.7", "cross-env": "7.0.3", - "ethers": "5.0.32", + "ethers": "5.3.1", "npm-run-all": "4.1.5", "react": "17.0.1", "react-circular-progressbar": "2.0.3", diff --git a/packages/dev-frontend/src/components/Transaction.tsx b/packages/dev-frontend/src/components/Transaction.tsx index 6c3460115..75ae926d6 100644 --- a/packages/dev-frontend/src/components/Transaction.tsx +++ b/packages/dev-frontend/src/components/Transaction.tsx @@ -237,9 +237,8 @@ export function Transaction { +const tryToGetRevertReason = async (provider: Provider, tx: TransactionReceipt) => { try { - const tx = await provider.getTransaction(hash); const result = await provider.call(tx, tx.blockNumber); if (hexDataLength(result) % 32 === 4 && hexDataSlice(result, 0, 4) === "0x08c379a0") { @@ -322,7 +321,7 @@ export const TransactionMonitor: React.FC = () => { id }); } else { - const reason = await tryToGetRevertReason(provider, txHash); + const reason = await tryToGetRevertReason(provider, receipt.rawReceipt); if (cancelled) { return; diff --git a/packages/lib-base/package.json b/packages/lib-base/package.json index a22c5f22e..08add7412 100644 --- a/packages/lib-base/package.json +++ b/packages/lib-base/package.json @@ -22,7 +22,7 @@ "test": "mocha --require ts-node/register" }, "dependencies": { - "@ethersproject/bignumber": "5.0.15" + "@ethersproject/bignumber": "5.3.0" }, "devDependencies": { "@microsoft/api-extractor": "7.13.2", diff --git a/packages/lib-ethers/package.json b/packages/lib-ethers/package.json index ff2c2477e..d820a903d 100644 --- a/packages/lib-ethers/package.json +++ b/packages/lib-ethers/package.json @@ -56,7 +56,7 @@ "dotenv": "8.2.0", "eslint": "7.22.0", "eslint-plugin-tsdoc": "0.2.11", - "ethers": "5.0.32", + "ethers": "5.3.1", "fs-extra": "9.1.0", "hardhat": "2.1.1", "npm-run-all": "4.1.5", diff --git a/packages/lib-react/package.json b/packages/lib-react/package.json index 5dc753cdd..349c80973 100644 --- a/packages/lib-react/package.json +++ b/packages/lib-react/package.json @@ -15,7 +15,7 @@ "react": "^16.13.1" }, "devDependencies": { - "@ethersproject/bignumber": "5.0.15", + "@ethersproject/bignumber": "5.3.0", "@types/react": "17.0.3", "@typescript-eslint/eslint-plugin": "4.17.0", "@typescript-eslint/parser": "4.18.0", diff --git a/packages/lib-subgraph/package.json b/packages/lib-subgraph/package.json index bba1b7e04..32c6801fa 100644 --- a/packages/lib-subgraph/package.json +++ b/packages/lib-subgraph/package.json @@ -26,4 +26,4 @@ "cross-fetch": "~3.0.6", "graphql": "^15.3.0" } -} \ No newline at end of file +} diff --git a/yarn.lock b/yarn.lock index 848dfff14..c09d9d5f2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1547,21 +1547,6 @@ "@ethersproject/properties" ">=5.0.0-beta.131" "@ethersproject/strings" ">=5.0.0-beta.130" -"@ethersproject/abi@5.0.13", "@ethersproject/abi@^5.0.0-beta.146", "@ethersproject/abi@^5.0.10": - version "5.0.13" - resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.0.13.tgz#600a559c3730467716595658beaa2894b4352bcc" - integrity sha512-2coOH3D7ra1lwamKEH0HVc+Jbcsw5yfeCgmY8ekhCDualEiyyovD2qDcMBBcY3+kjoLHVTmo7ost6MNClxdOrg== - dependencies: - "@ethersproject/address" "^5.0.9" - "@ethersproject/bignumber" "^5.0.13" - "@ethersproject/bytes" "^5.0.9" - "@ethersproject/constants" "^5.0.8" - "@ethersproject/hash" "^5.0.10" - "@ethersproject/keccak256" "^5.0.7" - "@ethersproject/logger" "^5.0.8" - "@ethersproject/properties" "^5.0.7" - "@ethersproject/strings" "^5.0.8" - "@ethersproject/abi@5.0.7": version "5.0.7" resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.0.7.tgz#79e52452bd3ca2956d0e1c964207a58ad1a0ee7b" @@ -1577,6 +1562,36 @@ "@ethersproject/properties" "^5.0.3" "@ethersproject/strings" "^5.0.4" +"@ethersproject/abi@5.3.1", "@ethersproject/abi@^5.3.0": + version "5.3.1" + resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.3.1.tgz#69a1a496729d3a83521675a57cbe21f3cc27241c" + integrity sha512-F98FWTJG7nWWAQ4DcV6R0cSlrj67MWK3ylahuFbzkumem5cLWg1p7fZ3vIdRoS1c7TEf55Lvyx0w7ICR47IImw== + dependencies: + "@ethersproject/address" "^5.3.0" + "@ethersproject/bignumber" "^5.3.0" + "@ethersproject/bytes" "^5.3.0" + "@ethersproject/constants" "^5.3.0" + "@ethersproject/hash" "^5.3.0" + "@ethersproject/keccak256" "^5.3.0" + "@ethersproject/logger" "^5.3.0" + "@ethersproject/properties" "^5.3.0" + "@ethersproject/strings" "^5.3.0" + +"@ethersproject/abi@^5.0.0-beta.146": + version "5.0.13" + resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.0.13.tgz#600a559c3730467716595658beaa2894b4352bcc" + integrity sha512-2coOH3D7ra1lwamKEH0HVc+Jbcsw5yfeCgmY8ekhCDualEiyyovD2qDcMBBcY3+kjoLHVTmo7ost6MNClxdOrg== + dependencies: + "@ethersproject/address" "^5.0.9" + "@ethersproject/bignumber" "^5.0.13" + "@ethersproject/bytes" "^5.0.9" + "@ethersproject/constants" "^5.0.8" + "@ethersproject/hash" "^5.0.10" + "@ethersproject/keccak256" "^5.0.7" + "@ethersproject/logger" "^5.0.8" + "@ethersproject/properties" "^5.0.7" + "@ethersproject/strings" "^5.0.8" + "@ethersproject/abi@^5.0.2": version "5.1.0" resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.1.0.tgz#d582c9f6a8e8192778b5f2c991ce19d7b336b0c5" @@ -1592,18 +1607,18 @@ "@ethersproject/properties" "^5.1.0" "@ethersproject/strings" "^5.1.0" -"@ethersproject/abstract-provider@5.0.10", "@ethersproject/abstract-provider@^5.0.8": - version "5.0.10" - resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.0.10.tgz#a533aed39a5f27312745c8c4c40fa25fc884831c" - integrity sha512-OSReY5iz94iIaPlRvLiJP8YVIvQLx4aUvMMnHWSaA/vTU8QHZmgNlt4OBdYV1+aFY8Xl+VRYiWBHq72ZDKXXCQ== +"@ethersproject/abstract-provider@5.3.0", "@ethersproject/abstract-provider@^5.3.0": + version "5.3.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.3.0.tgz#f4c0ae4a4cef9f204d7781de805fd44b72756c81" + integrity sha512-1+MLhGP1GwxBDBNwMWVmhCsvKwh4gK7oIfOrmlmePNeskg1NhIrYssraJBieaFNHUYfKEd/1DjiVZMw8Qu5Cxw== dependencies: - "@ethersproject/bignumber" "^5.0.13" - "@ethersproject/bytes" "^5.0.9" - "@ethersproject/logger" "^5.0.8" - "@ethersproject/networks" "^5.0.7" - "@ethersproject/properties" "^5.0.7" - "@ethersproject/transactions" "^5.0.9" - "@ethersproject/web" "^5.0.12" + "@ethersproject/bignumber" "^5.3.0" + "@ethersproject/bytes" "^5.3.0" + "@ethersproject/logger" "^5.3.0" + "@ethersproject/networks" "^5.3.0" + "@ethersproject/properties" "^5.3.0" + "@ethersproject/transactions" "^5.3.0" + "@ethersproject/web" "^5.3.0" "@ethersproject/abstract-provider@^5.1.0": version "5.1.0" @@ -1618,16 +1633,16 @@ "@ethersproject/transactions" "^5.1.0" "@ethersproject/web" "^5.1.0" -"@ethersproject/abstract-signer@5.0.14", "@ethersproject/abstract-signer@^5.0.10": - version "5.0.14" - resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.0.14.tgz#30ef912b0f86599d90fdffc65c110452e7b55cf1" - integrity sha512-JztBwVO7o5OHLh2vyjordlS4/1EjRyaECtc8vPdXTF1i4dXN+J0coeRoPN6ZFbBvi/YbaB6br2fvqhst1VQD/g== +"@ethersproject/abstract-signer@5.3.0", "@ethersproject/abstract-signer@^5.0.10", "@ethersproject/abstract-signer@^5.3.0": + version "5.3.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.3.0.tgz#05172b653e15b535ed5854ef5f6a72f4b441052d" + integrity sha512-w8IFwOYqiPrtvosPuArZ3+QPR2nmdVTRrVY8uJYL3NNfMmQfTy3V3l2wbzX47UUlNbPJY+gKvzJAyvK1onZxJg== dependencies: - "@ethersproject/abstract-provider" "^5.0.8" - "@ethersproject/bignumber" "^5.0.13" - "@ethersproject/bytes" "^5.0.9" - "@ethersproject/logger" "^5.0.8" - "@ethersproject/properties" "^5.0.7" + "@ethersproject/abstract-provider" "^5.3.0" + "@ethersproject/bignumber" "^5.3.0" + "@ethersproject/bytes" "^5.3.0" + "@ethersproject/logger" "^5.3.0" + "@ethersproject/properties" "^5.3.0" "@ethersproject/abstract-signer@^5.1.0": version "5.1.0" @@ -1640,7 +1655,18 @@ "@ethersproject/logger" "^5.1.0" "@ethersproject/properties" "^5.1.0" -"@ethersproject/address@5.0.11", "@ethersproject/address@>=5.0.0-beta.128", "@ethersproject/address@^5.0.0", "@ethersproject/address@^5.0.4", "@ethersproject/address@^5.0.9": +"@ethersproject/address@5.3.0", "@ethersproject/address@^5.0.0", "@ethersproject/address@^5.0.9", "@ethersproject/address@^5.3.0": + version "5.3.0" + resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.3.0.tgz#e53b69eacebf332e8175de814c5e6507d6932518" + integrity sha512-29TgjzEBK+gUEUAOfWCG7s9IxLNLCqvr+oDSk6L9TXD0VLvZJKhJV479tKQqheVA81OeGxfpdxYtUVH8hqlCvA== + dependencies: + "@ethersproject/bignumber" "^5.3.0" + "@ethersproject/bytes" "^5.3.0" + "@ethersproject/keccak256" "^5.3.0" + "@ethersproject/logger" "^5.3.0" + "@ethersproject/rlp" "^5.3.0" + +"@ethersproject/address@>=5.0.0-beta.128", "@ethersproject/address@^5.0.4": version "5.0.11" resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.0.11.tgz#12022e8c590c33939beb5ab18b401ecf585eac59" integrity sha512-Et4GBdD8/tsBGjCEOKee9upN29qjL5kbRcmJifb4Penmiuh9GARXL2/xpXvEp5EW+EIW/rfCHFJrkYBgoQFQBw== @@ -1662,12 +1688,12 @@ "@ethersproject/logger" "^5.1.0" "@ethersproject/rlp" "^5.1.0" -"@ethersproject/base64@5.0.9", "@ethersproject/base64@^5.0.7": - version "5.0.9" - resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.0.9.tgz#bb1f35d3dba92082a574d5e2418f9202a0a1a7e6" - integrity sha512-37RBz5LEZ9SlTNGiWCYFttnIN9J7qVs9Xo2EbqGqDH5LfW9EIji66S+YDMpXVo1zWDax1FkEldAoatxHK2gfgA== +"@ethersproject/base64@5.3.0", "@ethersproject/base64@^5.3.0": + version "5.3.0" + resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.3.0.tgz#b831fb35418b42ad24d943c557259062b8640824" + integrity sha512-JIqgtOmgKcbc2sjGWTXyXktqUhvFUDte8fPVsAaOrcPiJf6YotNF+nsrOYGC9pbHBEGSuSBp3QR0varkO8JHEw== dependencies: - "@ethersproject/bytes" "^5.0.9" + "@ethersproject/bytes" "^5.3.0" "@ethersproject/base64@^5.1.0": version "5.1.0" @@ -1676,15 +1702,24 @@ dependencies: "@ethersproject/bytes" "^5.1.0" -"@ethersproject/basex@5.0.9", "@ethersproject/basex@^5.0.7": - version "5.0.9" - resolved "https://registry.yarnpkg.com/@ethersproject/basex/-/basex-5.0.9.tgz#00d727a031bac563cb8bb900955206f1bf3cf1fc" - integrity sha512-FANswl1IN3PS0eltQxH2aM2+utPrkLUVG4XVFi6SafRG9EpAqXCgycxC8PU90mPGhigYTpg9cnTB5mCZ6ejQjw== +"@ethersproject/basex@5.3.0", "@ethersproject/basex@^5.3.0": + version "5.3.0" + resolved "https://registry.yarnpkg.com/@ethersproject/basex/-/basex-5.3.0.tgz#02dea3ab8559ae625c6d548bc11773432255c916" + integrity sha512-8J4nS6t/SOnoCgr3DF5WCSRLC5YwTKYpZWJqeyYQLX+86TwPhtzvHXacODzcDII9tWKhVg6g0Bka8JCBWXsCiQ== dependencies: - "@ethersproject/bytes" "^5.0.9" - "@ethersproject/properties" "^5.0.7" + "@ethersproject/bytes" "^5.3.0" + "@ethersproject/properties" "^5.3.0" + +"@ethersproject/bignumber@5.3.0", "@ethersproject/bignumber@^5.0.13", "@ethersproject/bignumber@^5.3.0": + version "5.3.0" + resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.3.0.tgz#74ab2ec9c3bda4e344920565720a6ee9c794e9db" + integrity sha512-5xguJ+Q1/zRMgHgDCaqAexx/8DwDVLRemw2i6uR8KyGjwGdXI8f32QZZ1cKGucBN6ekJvpUpHy6XAuQnTv0mPA== + dependencies: + "@ethersproject/bytes" "^5.3.0" + "@ethersproject/logger" "^5.3.0" + bn.js "^4.11.9" -"@ethersproject/bignumber@5.0.15", "@ethersproject/bignumber@>=5.0.0-beta.130", "@ethersproject/bignumber@^5.0.13", "@ethersproject/bignumber@^5.0.7": +"@ethersproject/bignumber@>=5.0.0-beta.130", "@ethersproject/bignumber@^5.0.7": version "5.0.15" resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.0.15.tgz#b089b3f1e0381338d764ac1c10512f0c93b184ed" integrity sha512-MTADqnyacvdRwtKh7o9ujwNDSM1SDJjYDMYAzjIgjoi9rh6TY4suMbhCa3i2vh3SUXiXSICyTI8ui+NPdrZ9Lw== @@ -1702,7 +1737,14 @@ "@ethersproject/logger" "^5.1.0" bn.js "^4.4.0" -"@ethersproject/bytes@5.0.11", "@ethersproject/bytes@>=5.0.0-beta.129", "@ethersproject/bytes@^5.0.4", "@ethersproject/bytes@^5.0.9": +"@ethersproject/bytes@5.3.0", "@ethersproject/bytes@^5.0.9", "@ethersproject/bytes@^5.3.0": + version "5.3.0" + resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.3.0.tgz#473e0da7f831d535b2002be05e6f4ca3729a1bc9" + integrity sha512-rqLJjdVqCcn7glPer7Fxh87PRqlnRScVAoxcIP3PmOUNApMWJ6yRdOFfo2KvPAdO7Le3yEI1o0YW+Yvr7XCYvw== + dependencies: + "@ethersproject/logger" "^5.3.0" + +"@ethersproject/bytes@>=5.0.0-beta.129", "@ethersproject/bytes@^5.0.4": version "5.0.11" resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.0.11.tgz#21118e75b1d00db068984c15530e316021101276" integrity sha512-D51plLYY5qF05AsoVQwIZVLqlBkaTPVHVP/1WmmBIWyHB0cRW0C9kh0kx5Exo51rB63Hk8PfHxc7SmpoaQFEyg== @@ -1716,7 +1758,14 @@ dependencies: "@ethersproject/logger" "^5.1.0" -"@ethersproject/constants@5.0.10", "@ethersproject/constants@>=5.0.0-beta.128", "@ethersproject/constants@^5.0.4", "@ethersproject/constants@^5.0.8": +"@ethersproject/constants@5.3.0", "@ethersproject/constants@^5.0.8", "@ethersproject/constants@^5.3.0": + version "5.3.0" + resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.3.0.tgz#a5d6d86c0eec2c64c3024479609493b9afb3fc77" + integrity sha512-4y1feNOwEpgjAfiCFWOHznvv6qUF/H6uI0UKp8xdhftb+H+FbKflXg1pOgH5qs4Sr7EYBL+zPyPb+YD5g1aEyw== + dependencies: + "@ethersproject/bignumber" "^5.3.0" + +"@ethersproject/constants@>=5.0.0-beta.128", "@ethersproject/constants@^5.0.4": version "5.0.10" resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.0.10.tgz#eb0c604fbc44c53ba9641eed31a1d0c9e1ebcadc" integrity sha512-OSo8jxkHLDXieCy8bgOFR7lMfgPxEzKvSDdP+WAWHCDM8+orwch0B6wzkTmiQFgryAtIctrBt5glAdJikZ3hGw== @@ -1730,22 +1779,37 @@ dependencies: "@ethersproject/bignumber" "^5.1.0" -"@ethersproject/contracts@5.0.12": - version "5.0.12" - resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.0.12.tgz#6d488db46221258399dfe80b89bf849b3afd7897" - integrity sha512-srijy31idjz8bE+gL1I6IRj2H4I9dUwfQ+QroLrIgNdGArqY8y2iFUKa3QTy+JBX26fJsdYiCQi1kKkaNpnMpQ== - dependencies: - "@ethersproject/abi" "^5.0.10" - "@ethersproject/abstract-provider" "^5.0.8" - "@ethersproject/abstract-signer" "^5.0.10" - "@ethersproject/address" "^5.0.9" - "@ethersproject/bignumber" "^5.0.13" - "@ethersproject/bytes" "^5.0.9" - "@ethersproject/constants" "^5.0.8" - "@ethersproject/logger" "^5.0.8" - "@ethersproject/properties" "^5.0.7" - -"@ethersproject/hash@5.0.12", "@ethersproject/hash@>=5.0.0-beta.128", "@ethersproject/hash@^5.0.10", "@ethersproject/hash@^5.0.4": +"@ethersproject/contracts@5.3.0": + version "5.3.0" + resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.3.0.tgz#ad699a3abaae30bfb6422cf31813a663b2d4099c" + integrity sha512-eDyQ8ltykvyQqnGZxb/c1e0OnEtzqXhNNC4BX8nhYBCaoBrYYuK/1fLmyEvc5+XUMoxNhwpYkoSSwvPLci7/Zg== + dependencies: + "@ethersproject/abi" "^5.3.0" + "@ethersproject/abstract-provider" "^5.3.0" + "@ethersproject/abstract-signer" "^5.3.0" + "@ethersproject/address" "^5.3.0" + "@ethersproject/bignumber" "^5.3.0" + "@ethersproject/bytes" "^5.3.0" + "@ethersproject/constants" "^5.3.0" + "@ethersproject/logger" "^5.3.0" + "@ethersproject/properties" "^5.3.0" + "@ethersproject/transactions" "^5.3.0" + +"@ethersproject/hash@5.3.0", "@ethersproject/hash@^5.0.10", "@ethersproject/hash@^5.3.0": + version "5.3.0" + resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.3.0.tgz#f65e3bf3db3282df4da676db6cfa049535dd3643" + integrity sha512-gAFZSjUPQ32CIfoKSMtMEQ+IO0kQxqhwz9fCIFt2DtAq2u4pWt8mL9Z5P0r6KkLcQU8LE9FmuPPyd+JvBzmr1w== + dependencies: + "@ethersproject/abstract-signer" "^5.3.0" + "@ethersproject/address" "^5.3.0" + "@ethersproject/bignumber" "^5.3.0" + "@ethersproject/bytes" "^5.3.0" + "@ethersproject/keccak256" "^5.3.0" + "@ethersproject/logger" "^5.3.0" + "@ethersproject/properties" "^5.3.0" + "@ethersproject/strings" "^5.3.0" + +"@ethersproject/hash@>=5.0.0-beta.128", "@ethersproject/hash@^5.0.4": version "5.0.12" resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.0.12.tgz#1074599f7509e2ca2bb7a3d4f4e39ab3a796da42" integrity sha512-kn4QN+fhNFbUgX3XZTZUaQixi0oyfIEY+hfW+KtkHu+rq7dV76oAIvaLEEynu1/4npOL38E4X4YI42gGZk+C0Q== @@ -1773,44 +1837,52 @@ "@ethersproject/properties" "^5.1.0" "@ethersproject/strings" "^5.1.0" -"@ethersproject/hdnode@5.0.10", "@ethersproject/hdnode@^5.0.8": - version "5.0.10" - resolved "https://registry.yarnpkg.com/@ethersproject/hdnode/-/hdnode-5.0.10.tgz#f7cdf154bf5d104c76dce2940745fc71d9e7eb1b" - integrity sha512-ZLwMtIcXK7xz2lSITDCl40W04CtRq4K9NwBxhCzdzPdaz6XnoJMwGz2YMVLg+8ksseq+RYtTwIIXtlK6vyvQyg== - dependencies: - "@ethersproject/abstract-signer" "^5.0.10" - "@ethersproject/basex" "^5.0.7" - "@ethersproject/bignumber" "^5.0.13" - "@ethersproject/bytes" "^5.0.9" - "@ethersproject/logger" "^5.0.8" - "@ethersproject/pbkdf2" "^5.0.7" - "@ethersproject/properties" "^5.0.7" - "@ethersproject/sha2" "^5.0.7" - "@ethersproject/signing-key" "^5.0.8" - "@ethersproject/strings" "^5.0.8" - "@ethersproject/transactions" "^5.0.9" - "@ethersproject/wordlists" "^5.0.8" - -"@ethersproject/json-wallets@5.0.12", "@ethersproject/json-wallets@^5.0.10": - version "5.0.12" - resolved "https://registry.yarnpkg.com/@ethersproject/json-wallets/-/json-wallets-5.0.12.tgz#8946a0fcce1634b636313a50330b7d30a24996e8" - integrity sha512-nac553zGZnOewpjlqbfy7WBl8m3y7qudzRsI2dCxrediYtPIVIs9f6Pbnou8vDmmp8X4/U4W788d+Ma88o+Gbg== - dependencies: - "@ethersproject/abstract-signer" "^5.0.10" - "@ethersproject/address" "^5.0.9" - "@ethersproject/bytes" "^5.0.9" - "@ethersproject/hdnode" "^5.0.8" - "@ethersproject/keccak256" "^5.0.7" - "@ethersproject/logger" "^5.0.8" - "@ethersproject/pbkdf2" "^5.0.7" - "@ethersproject/properties" "^5.0.7" - "@ethersproject/random" "^5.0.7" - "@ethersproject/strings" "^5.0.8" - "@ethersproject/transactions" "^5.0.9" +"@ethersproject/hdnode@5.3.0", "@ethersproject/hdnode@^5.3.0": + version "5.3.0" + resolved "https://registry.yarnpkg.com/@ethersproject/hdnode/-/hdnode-5.3.0.tgz#26fed65ffd5c25463fddff13f5fb4e5617553c94" + integrity sha512-zLmmtLNoDMGoYRdjOab01Zqkvp+TmZyCGDAMQF1Bs3yZyBs/kzTNi1qJjR1jVUcPP5CWGtjFwY8iNG8oNV9J8g== + dependencies: + "@ethersproject/abstract-signer" "^5.3.0" + "@ethersproject/basex" "^5.3.0" + "@ethersproject/bignumber" "^5.3.0" + "@ethersproject/bytes" "^5.3.0" + "@ethersproject/logger" "^5.3.0" + "@ethersproject/pbkdf2" "^5.3.0" + "@ethersproject/properties" "^5.3.0" + "@ethersproject/sha2" "^5.3.0" + "@ethersproject/signing-key" "^5.3.0" + "@ethersproject/strings" "^5.3.0" + "@ethersproject/transactions" "^5.3.0" + "@ethersproject/wordlists" "^5.3.0" + +"@ethersproject/json-wallets@5.3.0", "@ethersproject/json-wallets@^5.3.0": + version "5.3.0" + resolved "https://registry.yarnpkg.com/@ethersproject/json-wallets/-/json-wallets-5.3.0.tgz#7b1a5ff500c12aa8597ae82c8939837b0449376e" + integrity sha512-/xwbqaIb5grUIGNmeEaz8GdcpmDr++X8WT4Jqcclnxow8PXCUHFeDxjf3O+nSuoqOYG/Ds0+BI5xuQKbva6Xkw== + dependencies: + "@ethersproject/abstract-signer" "^5.3.0" + "@ethersproject/address" "^5.3.0" + "@ethersproject/bytes" "^5.3.0" + "@ethersproject/hdnode" "^5.3.0" + "@ethersproject/keccak256" "^5.3.0" + "@ethersproject/logger" "^5.3.0" + "@ethersproject/pbkdf2" "^5.3.0" + "@ethersproject/properties" "^5.3.0" + "@ethersproject/random" "^5.3.0" + "@ethersproject/strings" "^5.3.0" + "@ethersproject/transactions" "^5.3.0" aes-js "3.0.0" scrypt-js "3.0.1" -"@ethersproject/keccak256@5.0.9", "@ethersproject/keccak256@>=5.0.0-beta.127", "@ethersproject/keccak256@^5.0.0-beta.130", "@ethersproject/keccak256@^5.0.3", "@ethersproject/keccak256@^5.0.7": +"@ethersproject/keccak256@5.3.0", "@ethersproject/keccak256@^5.0.7", "@ethersproject/keccak256@^5.3.0": + version "5.3.0" + resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.3.0.tgz#fb5cd36bdfd6fa02e2ea84964078a9fc6bd731be" + integrity sha512-Gv2YqgIUmRbYVNIibafT0qGaeGYLIA/EdWHJ7JcVxVSs2vyxafGxOJ5VpSBHWeOIsE6OOaCelYowhuuTicgdFQ== + dependencies: + "@ethersproject/bytes" "^5.3.0" + js-sha3 "0.5.7" + +"@ethersproject/keccak256@>=5.0.0-beta.127", "@ethersproject/keccak256@^5.0.0-beta.130", "@ethersproject/keccak256@^5.0.3": version "5.0.9" resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.0.9.tgz#ca0d86e4af56c13b1ef25e533bde3e96d28f647d" integrity sha512-zhdUTj6RGtCJSgU+bDrWF6cGbvW453LoIC1DSNWrTlXzC7WuH4a+EiPrgc7/kNoRxerKuA/cxYlI8GwNtVtDlw== @@ -1826,7 +1898,12 @@ "@ethersproject/bytes" "^5.1.0" js-sha3 "0.5.7" -"@ethersproject/logger@5.0.10", "@ethersproject/logger@>=5.0.0-beta.129", "@ethersproject/logger@^5.0.5", "@ethersproject/logger@^5.0.8": +"@ethersproject/logger@5.3.0", "@ethersproject/logger@^5.0.8", "@ethersproject/logger@^5.3.0": + version "5.3.0" + resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.3.0.tgz#7a69fa1d4ca0d4b7138da1627eb152f763d84dd0" + integrity sha512-8bwJ2gxJGkZZnpQSq5uSiZSJjyVTWmlGft4oH8vxHdvO1Asy4TwVepAhPgxIQIMxXZFUNMych1YjIV4oQ4I7dA== + +"@ethersproject/logger@>=5.0.0-beta.129", "@ethersproject/logger@^5.0.5": version "5.0.10" resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.0.10.tgz#fd884688b3143253e0356ef92d5f22d109d2e026" integrity sha512-0y2T2NqykDrbPM3Zw9RSbPkDOxwChAL8detXaom76CfYoGxsOnRP/zTX8OUAV+x9LdwzgbWvWmeXrc0M7SuDZw== @@ -1836,12 +1913,12 @@ resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.1.0.tgz#4cdeeefac029373349d5818f39c31b82cc6d9bbf" integrity sha512-wtUaD1lBX10HBXjjKV9VHCBnTdUaKQnQ2XSET1ezglqLdPdllNOIlLfhyCRqXm5xwcjExVI5ETokOYfjPtaAlw== -"@ethersproject/networks@5.0.9", "@ethersproject/networks@^5.0.7": - version "5.0.9" - resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.0.9.tgz#ec5da11e4d4bfd69bec4eaebc9ace33eb9569279" - integrity sha512-L8+VCQwArBLGkxZb/5Ns/OH/OxP38AcaveXIxhUTq+VWpXYjrObG3E7RDQIKkUx1S1IcQl/UWTz5w4DK0UitJg== +"@ethersproject/networks@5.3.1", "@ethersproject/networks@^5.3.0": + version "5.3.1" + resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.3.1.tgz#78fe08324cee289ce239acf8c746121934b2ef61" + integrity sha512-6uQKHkYChlsfeiZhQ8IHIqGE/sQsf25o9ZxAYpMxi15dLPzz3IxOEF5KiSD32aHwsjXVBKBSlo+teAXLlYJybw== dependencies: - "@ethersproject/logger" "^5.0.8" + "@ethersproject/logger" "^5.3.0" "@ethersproject/networks@^5.1.0": version "5.1.0" @@ -1850,15 +1927,22 @@ dependencies: "@ethersproject/logger" "^5.1.0" -"@ethersproject/pbkdf2@5.0.9", "@ethersproject/pbkdf2@^5.0.7": - version "5.0.9" - resolved "https://registry.yarnpkg.com/@ethersproject/pbkdf2/-/pbkdf2-5.0.9.tgz#be39c7f0a66c0d3cb1ad1dbb12a78e9bcdf9b5ae" - integrity sha512-ItE/wQ/WVw/ajEHPUVgfu0aEvksPgOQc+278bke8sGKnGO3ppjmqp0MHh17tHc1EBTzJbSms5aLIqc56qZ/oiA== +"@ethersproject/pbkdf2@5.3.0", "@ethersproject/pbkdf2@^5.3.0": + version "5.3.0" + resolved "https://registry.yarnpkg.com/@ethersproject/pbkdf2/-/pbkdf2-5.3.0.tgz#8adbb41489c3c9f319cc44bc7d3e6095fd468dc8" + integrity sha512-Q9ChVU6gBFiex0FSdtzo4b0SAKz3ZYcYVFLrEWHL0FnHvNk3J3WgAtRNtBQGQYn/T5wkoTdZttMbfBkFlaiWcA== dependencies: - "@ethersproject/bytes" "^5.0.9" - "@ethersproject/sha2" "^5.0.7" + "@ethersproject/bytes" "^5.3.0" + "@ethersproject/sha2" "^5.3.0" + +"@ethersproject/properties@5.3.0", "@ethersproject/properties@^5.0.7", "@ethersproject/properties@^5.3.0": + version "5.3.0" + resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.3.0.tgz#feef4c4babeb7c10a6b3449575016f4ad2c092b2" + integrity sha512-PaHxJyM5/bfusk6vr3yP//JMnm4UEojpzuWGTmtL5X4uNhNnFNvlYilZLyDr4I9cTkIbipCMsAuIcXWsmdRnEw== + dependencies: + "@ethersproject/logger" "^5.3.0" -"@ethersproject/properties@5.0.9", "@ethersproject/properties@>=5.0.0-beta.131", "@ethersproject/properties@^5.0.3", "@ethersproject/properties@^5.0.7": +"@ethersproject/properties@>=5.0.0-beta.131", "@ethersproject/properties@^5.0.3": version "5.0.9" resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.0.9.tgz#d7aae634680760136ea522e25c3ef043ec15b5c2" integrity sha512-ZCjzbHYTw+rF1Pn8FDCEmx3gQttwIHcm/6Xee8g/M3Ga3SfW4tccNMbs5zqnBH0E4RoOPaeNgyg1O68TaF0tlg== @@ -1872,46 +1956,46 @@ dependencies: "@ethersproject/logger" "^5.1.0" -"@ethersproject/providers@5.0.24": - version "5.0.24" - resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.0.24.tgz#4c638a029482d052faa18364b5e0e2d3ddd9c0cb" - integrity sha512-M4Iw1r4gGJkt7ZUa++iREuviKL/DIpmIMsaUlVlXtV+ZrUXeN8xQ3zOTrbz7R4h9W9oljBZM7i4D3Kn1krJ30A== - dependencies: - "@ethersproject/abstract-provider" "^5.0.8" - "@ethersproject/abstract-signer" "^5.0.10" - "@ethersproject/address" "^5.0.9" - "@ethersproject/basex" "^5.0.7" - "@ethersproject/bignumber" "^5.0.13" - "@ethersproject/bytes" "^5.0.9" - "@ethersproject/constants" "^5.0.8" - "@ethersproject/hash" "^5.0.10" - "@ethersproject/logger" "^5.0.8" - "@ethersproject/networks" "^5.0.7" - "@ethersproject/properties" "^5.0.7" - "@ethersproject/random" "^5.0.7" - "@ethersproject/rlp" "^5.0.7" - "@ethersproject/sha2" "^5.0.7" - "@ethersproject/strings" "^5.0.8" - "@ethersproject/transactions" "^5.0.9" - "@ethersproject/web" "^5.0.12" +"@ethersproject/providers@5.3.1": + version "5.3.1" + resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.3.1.tgz#a12c6370e8cbc0968c9744641b8ef90b0dd5ec2b" + integrity sha512-HC63vENTrur6/JKEhcQbA8PRDj1FAesdpX98IW+xAAo3EAkf70ou5fMIA3KCGzJDLNTeYA4C2Bonz849tVLekg== + dependencies: + "@ethersproject/abstract-provider" "^5.3.0" + "@ethersproject/abstract-signer" "^5.3.0" + "@ethersproject/address" "^5.3.0" + "@ethersproject/basex" "^5.3.0" + "@ethersproject/bignumber" "^5.3.0" + "@ethersproject/bytes" "^5.3.0" + "@ethersproject/constants" "^5.3.0" + "@ethersproject/hash" "^5.3.0" + "@ethersproject/logger" "^5.3.0" + "@ethersproject/networks" "^5.3.0" + "@ethersproject/properties" "^5.3.0" + "@ethersproject/random" "^5.3.0" + "@ethersproject/rlp" "^5.3.0" + "@ethersproject/sha2" "^5.3.0" + "@ethersproject/strings" "^5.3.0" + "@ethersproject/transactions" "^5.3.0" + "@ethersproject/web" "^5.3.0" bech32 "1.1.4" - ws "7.2.3" + ws "7.4.6" -"@ethersproject/random@5.0.9", "@ethersproject/random@^5.0.7": - version "5.0.9" - resolved "https://registry.yarnpkg.com/@ethersproject/random/-/random-5.0.9.tgz#1903d4436ba66e4c8ac77968b16f756abea3a0d0" - integrity sha512-DANG8THsKqFbJOantrxumtG6gyETNE54VfbsWa+SQAT8WKpDo9W/X5Zhh73KuhClaey1UI32uVmISZeq/Zxn1A== +"@ethersproject/random@5.3.0", "@ethersproject/random@^5.3.0": + version "5.3.0" + resolved "https://registry.yarnpkg.com/@ethersproject/random/-/random-5.3.0.tgz#7c46bf36e50cb0d0550bc8c666af8e1d4496dc1a" + integrity sha512-A5SL/4inutSwt3Fh2OD0x2gz+x6GHmuUnIPkR7zAiTidMD2N8F6tZdMF1hlQKWVCcVMWhEQg8mWijhEzm6BBYw== dependencies: - "@ethersproject/bytes" "^5.0.9" - "@ethersproject/logger" "^5.0.8" + "@ethersproject/bytes" "^5.3.0" + "@ethersproject/logger" "^5.3.0" -"@ethersproject/rlp@5.0.9", "@ethersproject/rlp@^5.0.7": - version "5.0.9" - resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.0.9.tgz#da205bf8a34d3c3409eb73ddd237130a4b376aff" - integrity sha512-ns1U7ZMVeruUW6JXc4om+1w3w4ynHN/0fpwmeNTsAjwGKoF8SAUgue6ylKpHKWSti2idx7jDxbn8hNNFHk67CA== +"@ethersproject/rlp@5.3.0", "@ethersproject/rlp@^5.0.7", "@ethersproject/rlp@^5.3.0": + version "5.3.0" + resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.3.0.tgz#7cb93a7b5dfa69163894153c9d4b0d936f333188" + integrity sha512-oI0joYpsRanl9guDubaW+1NbcpK0vJ3F/6Wpcanzcnqq+oaW9O5E98liwkEDPcb16BUTLIJ+ZF8GPIHYxJ/5Pw== dependencies: - "@ethersproject/bytes" "^5.0.9" - "@ethersproject/logger" "^5.0.8" + "@ethersproject/bytes" "^5.3.0" + "@ethersproject/logger" "^5.3.0" "@ethersproject/rlp@^5.1.0": version "5.1.0" @@ -1921,24 +2005,26 @@ "@ethersproject/bytes" "^5.1.0" "@ethersproject/logger" "^5.1.0" -"@ethersproject/sha2@5.0.9", "@ethersproject/sha2@^5.0.7": - version "5.0.9" - resolved "https://registry.yarnpkg.com/@ethersproject/sha2/-/sha2-5.0.9.tgz#41275ee03e6e1660b3c997754005e089e936adc6" - integrity sha512-5FH4s47gM7N1fFAYQ1+m7aX0SbLg0Xr+6tvqndmNqc382/qBIbzXiGlUookrsjlPb6gLNurnTssCXjNM72J6lQ== +"@ethersproject/sha2@5.3.0", "@ethersproject/sha2@^5.3.0": + version "5.3.0" + resolved "https://registry.yarnpkg.com/@ethersproject/sha2/-/sha2-5.3.0.tgz#209f9a1649f7d2452dcd5e5b94af43b7f3f42366" + integrity sha512-r5ftlwKcocYEuFz2JbeKOT5SAsCV4m1RJDsTOEfQ5L67ZC7NFDK5i7maPdn1bx4nPhylF9VAwxSrQ1esmwzylg== dependencies: - "@ethersproject/bytes" "^5.0.9" - "@ethersproject/logger" "^5.0.8" - hash.js "1.1.3" + "@ethersproject/bytes" "^5.3.0" + "@ethersproject/logger" "^5.3.0" + hash.js "1.1.7" -"@ethersproject/signing-key@5.0.11", "@ethersproject/signing-key@^5.0.8": - version "5.0.11" - resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.0.11.tgz#19fc5c4597e18ad0a5efc6417ba5b74069fdd2af" - integrity sha512-Jfcru/BGwdkXhLxT+8WCZtFy7LL0TPFZw05FAb5asxB/MyVsEfNdNxGDtjVE9zXfmRSPe/EusXYY4K7wcygOyQ== +"@ethersproject/signing-key@5.3.0", "@ethersproject/signing-key@^5.0.8", "@ethersproject/signing-key@^5.3.0": + version "5.3.0" + resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.3.0.tgz#a96c88f8173e1abedfa35de32d3e5db7c48e5259" + integrity sha512-+DX/GwHAd0ok1bgedV1cKO0zfK7P/9aEyNoaYiRsGHpCecN7mhLqcdoUiUzE7Uz86LBsxm5ssK0qA1kBB47fbQ== dependencies: - "@ethersproject/bytes" "^5.0.9" - "@ethersproject/logger" "^5.0.8" - "@ethersproject/properties" "^5.0.7" + "@ethersproject/bytes" "^5.3.0" + "@ethersproject/logger" "^5.3.0" + "@ethersproject/properties" "^5.3.0" + bn.js "^4.11.9" elliptic "6.5.4" + hash.js "1.1.7" "@ethersproject/signing-key@^5.1.0": version "5.1.0" @@ -1951,18 +2037,27 @@ bn.js "^4.4.0" elliptic "6.5.4" -"@ethersproject/solidity@5.0.10": - version "5.0.10" - resolved "https://registry.yarnpkg.com/@ethersproject/solidity/-/solidity-5.0.10.tgz#128c9289761cf83d81ff62a1195d6079a924a86c" - integrity sha512-8OG3HLqynWXDA6mVIHuHfF/ojTTwBahON7hc9GAKCqglzXCkVA3OpyxOJXPzjHClRIAUUiU7r9oy9Z/nsjtT/g== +"@ethersproject/solidity@5.3.0": + version "5.3.0" + resolved "https://registry.yarnpkg.com/@ethersproject/solidity/-/solidity-5.3.0.tgz#2a0b00b4aaaef99a080ddea13acab1fa35cd4a93" + integrity sha512-uLRBaNUiISHbut94XKewJgQh6UmydWTBp71I7I21pkjVXfZO2dJ5EOo3jCnumJc01M4LOm79dlNNmF3oGIvweQ== dependencies: - "@ethersproject/bignumber" "^5.0.13" - "@ethersproject/bytes" "^5.0.9" - "@ethersproject/keccak256" "^5.0.7" - "@ethersproject/sha2" "^5.0.7" - "@ethersproject/strings" "^5.0.8" + "@ethersproject/bignumber" "^5.3.0" + "@ethersproject/bytes" "^5.3.0" + "@ethersproject/keccak256" "^5.3.0" + "@ethersproject/sha2" "^5.3.0" + "@ethersproject/strings" "^5.3.0" -"@ethersproject/strings@5.0.10", "@ethersproject/strings@>=5.0.0-beta.130", "@ethersproject/strings@^5.0.4", "@ethersproject/strings@^5.0.8": +"@ethersproject/strings@5.3.0", "@ethersproject/strings@^5.0.8", "@ethersproject/strings@^5.3.0": + version "5.3.0" + resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.3.0.tgz#a6b640aab56a18e0909f657da798eef890968ff0" + integrity sha512-j/AzIGZ503cvhuF2ldRSjB0BrKzpsBMtCieDtn4TYMMZMQ9zScJn9wLzTQl/bRNvJbBE6TOspK0r8/Ngae/f2Q== + dependencies: + "@ethersproject/bytes" "^5.3.0" + "@ethersproject/constants" "^5.3.0" + "@ethersproject/logger" "^5.3.0" + +"@ethersproject/strings@>=5.0.0-beta.130", "@ethersproject/strings@^5.0.4": version "5.0.10" resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.0.10.tgz#ddce1e9724f4ac4f3f67e0cac0b48748e964bfdb" integrity sha512-KAeoS1tZ9/5ECXiIZA6S6hywbD0so2VmuW+Wfyo5EDXeyZ6Na1nxTPhTnW7voQmjbeYJffCrOc0qLFJeylyg7w== @@ -1980,7 +2075,22 @@ "@ethersproject/constants" "^5.1.0" "@ethersproject/logger" "^5.1.0" -"@ethersproject/transactions@5.0.11", "@ethersproject/transactions@^5.0.0-beta.135", "@ethersproject/transactions@^5.0.9": +"@ethersproject/transactions@5.3.0", "@ethersproject/transactions@^5.3.0": + version "5.3.0" + resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.3.0.tgz#49b86f2bafa4d0bdf8e596578fc795ee47c50458" + integrity sha512-cdfK8VVyW2oEBCXhURG0WQ6AICL/r6Gmjh0e4Bvbv6MCn/GBd8FeBH3rtl7ho+AW50csMKeGv3m3K1HSHB2jMQ== + dependencies: + "@ethersproject/address" "^5.3.0" + "@ethersproject/bignumber" "^5.3.0" + "@ethersproject/bytes" "^5.3.0" + "@ethersproject/constants" "^5.3.0" + "@ethersproject/keccak256" "^5.3.0" + "@ethersproject/logger" "^5.3.0" + "@ethersproject/properties" "^5.3.0" + "@ethersproject/rlp" "^5.3.0" + "@ethersproject/signing-key" "^5.3.0" + +"@ethersproject/transactions@^5.0.0-beta.135": version "5.0.11" resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.0.11.tgz#b31df5292f47937136a45885d6ee6112477c13df" integrity sha512-ftsRvR9+gQp7L63F6+XmstvsZ4w8GtWvQB08e/zB+oB86Fnhq8+i/tkgpJplSHC8I/qgiCisva+M3u2GVhDFPA== @@ -2010,46 +2120,46 @@ "@ethersproject/rlp" "^5.1.0" "@ethersproject/signing-key" "^5.1.0" -"@ethersproject/units@5.0.11": - version "5.0.11" - resolved "https://registry.yarnpkg.com/@ethersproject/units/-/units-5.0.11.tgz#f82f6e353ac0d6fa43b17337790f1f9aa72cb4c8" - integrity sha512-nOSPmcCWyB/dwoBRhhTtPGCsTbiXqmc7Q0Adwvafc432AC7hy3Fj3IFZtnSXsbtJ/GdHCIUIoA8gtvxSsFuBJg== +"@ethersproject/units@5.3.0": + version "5.3.0" + resolved "https://registry.yarnpkg.com/@ethersproject/units/-/units-5.3.0.tgz#c4d1493532ad3d4ddf6e2bc4f8c94a2db933a8f5" + integrity sha512-BkfccZGwfJ6Ob+AelpIrgAzuNhrN2VLp3AILnkqTOv+yBdsc83V4AYf25XC/u0rHnWl6f4POaietPwlMqP2vUg== dependencies: - "@ethersproject/bignumber" "^5.0.13" - "@ethersproject/constants" "^5.0.8" - "@ethersproject/logger" "^5.0.8" + "@ethersproject/bignumber" "^5.3.0" + "@ethersproject/constants" "^5.3.0" + "@ethersproject/logger" "^5.3.0" -"@ethersproject/wallet@5.0.12": - version "5.0.12" - resolved "https://registry.yarnpkg.com/@ethersproject/wallet/-/wallet-5.0.12.tgz#bfb96f95e066b4b1b4591c4615207b87afedda8b" - integrity sha512-rboJebGf47/KPZrKZQdYg9BAYuXbc/OwcUyML1K1f2jnJeo1ObWV11U1PAWTjTbhhSy6/Fg+34GO2yMb5Dt1Rw== - dependencies: - "@ethersproject/abstract-provider" "^5.0.8" - "@ethersproject/abstract-signer" "^5.0.10" - "@ethersproject/address" "^5.0.9" - "@ethersproject/bignumber" "^5.0.13" - "@ethersproject/bytes" "^5.0.9" - "@ethersproject/hash" "^5.0.10" - "@ethersproject/hdnode" "^5.0.8" - "@ethersproject/json-wallets" "^5.0.10" - "@ethersproject/keccak256" "^5.0.7" - "@ethersproject/logger" "^5.0.8" - "@ethersproject/properties" "^5.0.7" - "@ethersproject/random" "^5.0.7" - "@ethersproject/signing-key" "^5.0.8" - "@ethersproject/transactions" "^5.0.9" - "@ethersproject/wordlists" "^5.0.8" - -"@ethersproject/web@5.0.14", "@ethersproject/web@^5.0.12": - version "5.0.14" - resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.0.14.tgz#6e7bebdd9fb967cb25ee60f44d9218dc0803bac4" - integrity sha512-QpTgplslwZ0Sp9oKNLoRuS6TKxnkwfaEk3gr7zd7XLF8XBsYejsrQO/03fNfnMx/TAT/RR6WEw/mbOwpRSeVRA== +"@ethersproject/wallet@5.3.0": + version "5.3.0" + resolved "https://registry.yarnpkg.com/@ethersproject/wallet/-/wallet-5.3.0.tgz#91946b470bd279e39ade58866f21f92749d062af" + integrity sha512-boYBLydG6671p9QoG6EinNnNzbm7DNOjVT20eV8J6HQEq4aUaGiA2CytF2vK+2rOEWbzhZqoNDt6AlkE1LlsTg== + dependencies: + "@ethersproject/abstract-provider" "^5.3.0" + "@ethersproject/abstract-signer" "^5.3.0" + "@ethersproject/address" "^5.3.0" + "@ethersproject/bignumber" "^5.3.0" + "@ethersproject/bytes" "^5.3.0" + "@ethersproject/hash" "^5.3.0" + "@ethersproject/hdnode" "^5.3.0" + "@ethersproject/json-wallets" "^5.3.0" + "@ethersproject/keccak256" "^5.3.0" + "@ethersproject/logger" "^5.3.0" + "@ethersproject/properties" "^5.3.0" + "@ethersproject/random" "^5.3.0" + "@ethersproject/signing-key" "^5.3.0" + "@ethersproject/transactions" "^5.3.0" + "@ethersproject/wordlists" "^5.3.0" + +"@ethersproject/web@5.3.0", "@ethersproject/web@^5.3.0": + version "5.3.0" + resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.3.0.tgz#7959c403f6476c61515008d8f92da51c553a8ee1" + integrity sha512-Ni6/DHnY6k/TD41LEkv0RQDx4jqWz5e/RZvrSecsxGYycF+MFy2z++T/yGc2peRunLOTIFwEksgEGGlbwfYmhQ== dependencies: - "@ethersproject/base64" "^5.0.7" - "@ethersproject/bytes" "^5.0.9" - "@ethersproject/logger" "^5.0.8" - "@ethersproject/properties" "^5.0.7" - "@ethersproject/strings" "^5.0.8" + "@ethersproject/base64" "^5.3.0" + "@ethersproject/bytes" "^5.3.0" + "@ethersproject/logger" "^5.3.0" + "@ethersproject/properties" "^5.3.0" + "@ethersproject/strings" "^5.3.0" "@ethersproject/web@^5.1.0": version "5.1.0" @@ -2062,16 +2172,16 @@ "@ethersproject/properties" "^5.1.0" "@ethersproject/strings" "^5.1.0" -"@ethersproject/wordlists@5.0.10", "@ethersproject/wordlists@^5.0.8": - version "5.0.10" - resolved "https://registry.yarnpkg.com/@ethersproject/wordlists/-/wordlists-5.0.10.tgz#177b9a0b4d72b9c4f304d08b36612d6c60e9b896" - integrity sha512-jWsEm1iJzpg9SCXnNfFz+tcp4Ofzv0TJb6mj+soCNcar9GcT0yGz62ZsHC3pLQWaF4LkCzGwRJHJTXKjHQfG1A== +"@ethersproject/wordlists@5.3.0", "@ethersproject/wordlists@^5.3.0": + version "5.3.0" + resolved "https://registry.yarnpkg.com/@ethersproject/wordlists/-/wordlists-5.3.0.tgz#45a0205f5178c1de33d316cb2ab7ed5eac3c06c5" + integrity sha512-JcwumCZcsUxgWpiFU/BRy6b4KlTRdOmYvOKZcAw/3sdF93/pZyPW5Od2hFkHS8oWp4xS06YQ+qHqQhdcxdHafQ== dependencies: - "@ethersproject/bytes" "^5.0.9" - "@ethersproject/hash" "^5.0.10" - "@ethersproject/logger" "^5.0.8" - "@ethersproject/properties" "^5.0.7" - "@ethersproject/strings" "^5.0.8" + "@ethersproject/bytes" "^5.3.0" + "@ethersproject/hash" "^5.3.0" + "@ethersproject/logger" "^5.3.0" + "@ethersproject/properties" "^5.3.0" + "@ethersproject/strings" "^5.3.0" "@fortawesome/fontawesome-common-types@^0.2.34": version "0.2.34" @@ -9010,41 +9120,41 @@ ethereumjs-util@^7.0.2, ethereumjs-util@^7.0.9: ethjs-util "0.1.6" rlp "^2.2.4" -ethers@5.0.32, ethers@^5.0.0, ethers@^5.0.32: - version "5.0.32" - resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.0.32.tgz#f009970be31d96a589bf0ce597a39c10c7e297a6" - integrity sha512-rORfGWR0HsA4pjKMMcWZorw12DHsXqfIAuPVHJsXt+vI24jvXcVqx+rLsSvgOoLdaCMdxiN5qlIq2+4axKG31g== - dependencies: - "@ethersproject/abi" "5.0.13" - "@ethersproject/abstract-provider" "5.0.10" - "@ethersproject/abstract-signer" "5.0.14" - "@ethersproject/address" "5.0.11" - "@ethersproject/base64" "5.0.9" - "@ethersproject/basex" "5.0.9" - "@ethersproject/bignumber" "5.0.15" - "@ethersproject/bytes" "5.0.11" - "@ethersproject/constants" "5.0.10" - "@ethersproject/contracts" "5.0.12" - "@ethersproject/hash" "5.0.12" - "@ethersproject/hdnode" "5.0.10" - "@ethersproject/json-wallets" "5.0.12" - "@ethersproject/keccak256" "5.0.9" - "@ethersproject/logger" "5.0.10" - "@ethersproject/networks" "5.0.9" - "@ethersproject/pbkdf2" "5.0.9" - "@ethersproject/properties" "5.0.9" - "@ethersproject/providers" "5.0.24" - "@ethersproject/random" "5.0.9" - "@ethersproject/rlp" "5.0.9" - "@ethersproject/sha2" "5.0.9" - "@ethersproject/signing-key" "5.0.11" - "@ethersproject/solidity" "5.0.10" - "@ethersproject/strings" "5.0.10" - "@ethersproject/transactions" "5.0.11" - "@ethersproject/units" "5.0.11" - "@ethersproject/wallet" "5.0.12" - "@ethersproject/web" "5.0.14" - "@ethersproject/wordlists" "5.0.10" +ethers@5.3.1, ethers@^5.0.0, ethers@^5.0.32: + version "5.3.1" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.3.1.tgz#1f018f0aeb651576cd84fd987a45f0b99646d761" + integrity sha512-xCKmC0gsZ9gks89ZfK3B1y6LlPdvX5fxDtu9SytnpdDJR1M7pmJI+4H0AxQPMgUYr7GtQdmECLR0gWdJQ+lZYw== + dependencies: + "@ethersproject/abi" "5.3.1" + "@ethersproject/abstract-provider" "5.3.0" + "@ethersproject/abstract-signer" "5.3.0" + "@ethersproject/address" "5.3.0" + "@ethersproject/base64" "5.3.0" + "@ethersproject/basex" "5.3.0" + "@ethersproject/bignumber" "5.3.0" + "@ethersproject/bytes" "5.3.0" + "@ethersproject/constants" "5.3.0" + "@ethersproject/contracts" "5.3.0" + "@ethersproject/hash" "5.3.0" + "@ethersproject/hdnode" "5.3.0" + "@ethersproject/json-wallets" "5.3.0" + "@ethersproject/keccak256" "5.3.0" + "@ethersproject/logger" "5.3.0" + "@ethersproject/networks" "5.3.1" + "@ethersproject/pbkdf2" "5.3.0" + "@ethersproject/properties" "5.3.0" + "@ethersproject/providers" "5.3.1" + "@ethersproject/random" "5.3.0" + "@ethersproject/rlp" "5.3.0" + "@ethersproject/sha2" "5.3.0" + "@ethersproject/signing-key" "5.3.0" + "@ethersproject/solidity" "5.3.0" + "@ethersproject/strings" "5.3.0" + "@ethersproject/transactions" "5.3.0" + "@ethersproject/units" "5.3.0" + "@ethersproject/wallet" "5.3.0" + "@ethersproject/web" "5.3.0" + "@ethersproject/wordlists" "5.3.0" ethers@^4.0.0-beta.1, ethers@^4.0.32, ethers@^4.0.40: version "4.0.48" @@ -10627,7 +10737,7 @@ hash.js@1.1.3: inherits "^2.0.3" minimalistic-assert "^1.0.0" -hash.js@^1.0.0, hash.js@^1.0.3, hash.js@^1.1.7: +hash.js@1.1.7, hash.js@^1.0.0, hash.js@^1.0.3, hash.js@^1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== @@ -21697,10 +21807,10 @@ write-file-atomic@^3.0.0: signal-exit "^3.0.2" typedarray-to-buffer "^3.1.5" -ws@7.2.3: - version "7.2.3" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.2.3.tgz#a5411e1fb04d5ed0efee76d26d5c46d830c39b46" - integrity sha512-HTDl9G9hbkNDk98naoR/cHDws7+EyYMOdL1BmjsZXRUjf7d+MficC4B7HLUPlSiho0vg+CWKrGIt/VJBd1xunQ== +ws@7.4.6: + version "7.4.6" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" + integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== ws@^3.0.0: version "3.3.3" From 9e34df69e09c95176a0d6ece7e3835248321e7d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Fingen?= Date: Wed, 16 Jun 2021 16:09:41 +0200 Subject: [PATCH 42/58] contracts: Balancer pool: use exact LUSD approval amount --- packages/contracts/mainnetDeployment/balancerPoolDeployment.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/contracts/mainnetDeployment/balancerPoolDeployment.js b/packages/contracts/mainnetDeployment/balancerPoolDeployment.js index 5401c1323..682237775 100644 --- a/packages/contracts/mainnetDeployment/balancerPoolDeployment.js +++ b/packages/contracts/mainnetDeployment/balancerPoolDeployment.js @@ -135,7 +135,7 @@ async function main() { ERC20.abi, deployerWallet ) - const txApproveLusd = await lusd.approve(VAULT, toBigNum(dec(1, 36))); + const txApproveLusd = await lusd.approve(VAULT, lusd_balance); await txApproveLusd.wait() // joins and exits are done on the Vault, not the pool From 38a4a78e10929727cf8763b6508c21f0cfd07ded Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Fingen?= Date: Wed, 16 Jun 2021 16:10:15 +0200 Subject: [PATCH 43/58] contracts: Balancer pool: Comment mainnet fork test stuff out --- .../mainnetDeployment/balancerPoolDeployment.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/contracts/mainnetDeployment/balancerPoolDeployment.js b/packages/contracts/mainnetDeployment/balancerPoolDeployment.js index 682237775..ff0497fe2 100644 --- a/packages/contracts/mainnetDeployment/balancerPoolDeployment.js +++ b/packages/contracts/mainnetDeployment/balancerPoolDeployment.js @@ -37,7 +37,7 @@ const INITIAL_FUNDING = toBigNum(dec(5, 22)); // $50k async function main() { // Uncomment for testing: - + /* const impersonateAddress = "0x787EfF01c9FdC1918d1AA6eeFf089B191e2922E4" await hre.network.provider.request({ method: "hardhat_impersonateAccount", @@ -45,9 +45,10 @@ async function main() { }) const deployerWallet = await ethers.provider.getSigner(impersonateAddress) const deployerWalletAddress = impersonateAddress + */ - //const deployerWallet = (await ethers.getSigners())[0] - //const deployerWalletAddress = deployerWallet.address + const deployerWallet = (await ethers.getSigners())[0] + const deployerWalletAddress = deployerWallet.address const factory = new ethers.Contract( ORACLE_POOL_FACTORY, @@ -69,8 +70,7 @@ async function main() { const tx1 = await factory.create( NAME, SYMBOL, tokens, weights, swapFeePercentage, oracleEnabled, - //DELEGATE_OWNER - impersonateAddress + DELEGATE_OWNER ); const receipt1 = await tx1.wait(); From a56f9b61966d744384eb8e856d933ee215218c03 Mon Sep 17 00:00:00 2001 From: Daniel Simon Date: Wed, 16 Jun 2021 18:19:56 +0700 Subject: [PATCH 44/58] feat: make use of Ethers' transaction replacement detection --- .../lib-ethers.ethersliquity.adjusttrove.md | 2 +- ...b-ethers.ethersliquity.approveunitokens.md | 2 +- .../lib-ethers.ethersliquity.borrowlusd.md | 2 +- ...rs.ethersliquity.claimcollateralsurplus.md | 2 +- .../lib-ethers.ethersliquity.closetrove.md | 2 +- ...-ethers.ethersliquity.depositcollateral.md | 2 +- ...thersliquity.depositlusdinstabilitypool.md | 2 +- ...thers.ethersliquity.exitliquiditymining.md | 2 +- .../sdk/lib-ethers.ethersliquity.liquidate.md | 2 +- .../lib-ethers.ethersliquity.liquidateupto.md | 2 +- .../sdk/lib-ethers.ethersliquity.opentrove.md | 2 +- .../lib-ethers.ethersliquity.redeemlusd.md | 2 +- ...b-ethers.ethersliquity.registerfrontend.md | 2 +- .../sdk/lib-ethers.ethersliquity.repaylusd.md | 2 +- docs/sdk/lib-ethers.ethersliquity.sendlqty.md | 2 +- docs/sdk/lib-ethers.ethersliquity.sendlusd.md | 2 +- .../sdk/lib-ethers.ethersliquity.stakelqty.md | 2 +- ...lib-ethers.ethersliquity.stakeunitokens.md | 2 +- ...rsliquity.transfercollateralgaintotrove.md | 2 +- .../lib-ethers.ethersliquity.unstakelqty.md | 2 +- ...b-ethers.ethersliquity.unstakeunitokens.md | 2 +- ...ethers.ethersliquity.withdrawcollateral.md | 2 +- ...sliquity.withdrawgainsfromstabilitypool.md | 2 +- ....ethersliquity.withdrawgainsfromstaking.md | 2 +- ...y.withdrawlqtyrewardfromliquiditymining.md | 2 +- ...rsliquity.withdrawlusdfromstabilitypool.md | 2 +- ...-ethers.etherstransactioncancellederror.md | 26 +++++ ...therstransactioncancellederror.rawerror.md | 11 ++ ...ioncancellederror.rawreplacementreceipt.md | 11 ++ docs/sdk/lib-ethers.md | 1 + ...ethersliquitytransaction.waitforreceipt.md | 4 + packages/examples/package.json | 2 +- packages/fuzzer/package.json | 2 +- packages/lib-ethers/etc/lib-ethers.api.md | 39 +++++++ packages/lib-ethers/package.json | 2 +- packages/lib-ethers/src/EthersLiquity.ts | 26 +++++ .../src/PopulatableEthersLiquity.ts | 109 ++++++++++++++++-- packages/providers/package.json | 4 +- 38 files changed, 250 insertions(+), 39 deletions(-) create mode 100644 docs/sdk/lib-ethers.etherstransactioncancellederror.md create mode 100644 docs/sdk/lib-ethers.etherstransactioncancellederror.rawerror.md create mode 100644 docs/sdk/lib-ethers.etherstransactioncancellederror.rawreplacementreceipt.md diff --git a/docs/sdk/lib-ethers.ethersliquity.adjusttrove.md b/docs/sdk/lib-ethers.ethersliquity.adjusttrove.md index aa9182922..b9fb38566 100644 --- a/docs/sdk/lib-ethers.ethersliquity.adjusttrove.md +++ b/docs/sdk/lib-ethers.ethersliquity.adjusttrove.md @@ -26,7 +26,7 @@ Promise<[TroveAdjustmentDetails](./lib-base.troveadjustmentdetails.md)> ## Exceptions -Throws [EthersTransactionFailedError](./lib-ethers.etherstransactionfailederror.md) in case of transaction failure. +Throws [EthersTransactionFailedError](./lib-ethers.etherstransactionfailederror.md) in case of transaction failure. Throws [EthersTransactionCancelledError](./lib-ethers.etherstransactioncancellederror.md) if the transaction is cancelled or replaced. diff --git a/docs/sdk/lib-ethers.ethersliquity.depositcollateral.md b/docs/sdk/lib-ethers.ethersliquity.depositcollateral.md index f3812131e..5a2485eb5 100644 --- a/docs/sdk/lib-ethers.ethersliquity.depositcollateral.md +++ b/docs/sdk/lib-ethers.ethersliquity.depositcollateral.md @@ -25,7 +25,7 @@ Promise<[TroveAdjustmentDetails](./lib-base.troveadjustmentdetails.md)> ## Exceptions -Throws [EthersTransactionFailedError](./lib-ethers.etherstransactionfailederror.md) in case of transaction failure. +Throws [EthersTransactionFailedError](./lib-ethers.etherstransactionfailederror.md) in case of transaction failure. Throws [EthersTransactionCancelledError](./lib-ethers.etherstransactioncancellederror.md) if the transaction is cancelled or replaced. diff --git a/docs/sdk/lib-ethers.ethersliquity.liquidateupto.md b/docs/sdk/lib-ethers.ethersliquity.liquidateupto.md index fa73e8288..2cdbc2059 100644 --- a/docs/sdk/lib-ethers.ethersliquity.liquidateupto.md +++ b/docs/sdk/lib-ethers.ethersliquity.liquidateupto.md @@ -25,5 +25,5 @@ Promise<[LiquidationDetails](./lib-base.liquidationdetails.md)> ## Exceptions -Throws [EthersTransactionFailedError](./lib-ethers.etherstransactionfailederror.md) in case of transaction failure. +Throws [EthersTransactionFailedError](./lib-ethers.etherstransactionfailederror.md) in case of transaction failure. Throws [EthersTransactionCancelledError](./lib-ethers.etherstransactioncancellederror.md) if the transaction is cancelled or replaced. diff --git a/docs/sdk/lib-ethers.ethersliquity.opentrove.md b/docs/sdk/lib-ethers.ethersliquity.opentrove.md index d4de3c92f..c0eefd6ac 100644 --- a/docs/sdk/lib-ethers.ethersliquity.opentrove.md +++ b/docs/sdk/lib-ethers.ethersliquity.opentrove.md @@ -26,7 +26,7 @@ Promise<[TroveCreationDetails](./lib-base.trovecreationdetails.md)> ## Exceptions -Throws [EthersTransactionFailedError](./lib-ethers.etherstransactionfailederror.md) in case of transaction failure. +Throws [EthersTransactionFailedError](./lib-ethers.etherstransactionfailederror.md) in case of transaction failure. Throws [EthersTransactionCancelledError](./lib-ethers.etherstransactioncancellederror.md) if the transaction is cancelled or replaced. ## Remarks diff --git a/docs/sdk/lib-ethers.ethersliquity.redeemlusd.md b/docs/sdk/lib-ethers.ethersliquity.redeemlusd.md index 6671bf7c9..22679df53 100644 --- a/docs/sdk/lib-ethers.ethersliquity.redeemlusd.md +++ b/docs/sdk/lib-ethers.ethersliquity.redeemlusd.md @@ -26,7 +26,7 @@ Promise<[RedemptionDetails](./lib-base.redemptiondetails.md)> ## Exceptions -Throws [EthersTransactionFailedError](./lib-ethers.etherstransactionfailederror.md) in case of transaction failure. +Throws [EthersTransactionFailedError](./lib-ethers.etherstransactionfailederror.md) in case of transaction failure. Throws [EthersTransactionCancelledError](./lib-ethers.etherstransactioncancellederror.md) if the transaction is cancelled or replaced. ## Remarks diff --git a/docs/sdk/lib-ethers.ethersliquity.registerfrontend.md b/docs/sdk/lib-ethers.ethersliquity.registerfrontend.md index 7b4b79e67..54c10b1da 100644 --- a/docs/sdk/lib-ethers.ethersliquity.registerfrontend.md +++ b/docs/sdk/lib-ethers.ethersliquity.registerfrontend.md @@ -25,5 +25,5 @@ Promise<void> ## Exceptions -Throws [EthersTransactionFailedError](./lib-ethers.etherstransactionfailederror.md) in case of transaction failure. +Throws [EthersTransactionFailedError](./lib-ethers.etherstransactionfailederror.md) in case of transaction failure. Throws [EthersTransactionCancelledError](./lib-ethers.etherstransactioncancellederror.md) if the transaction is cancelled or replaced. diff --git a/docs/sdk/lib-ethers.ethersliquity.repaylusd.md b/docs/sdk/lib-ethers.ethersliquity.repaylusd.md index 6f2cca0c7..9380c76a3 100644 --- a/docs/sdk/lib-ethers.ethersliquity.repaylusd.md +++ b/docs/sdk/lib-ethers.ethersliquity.repaylusd.md @@ -25,7 +25,7 @@ Promise<[TroveAdjustmentDetails](./lib-base.troveadjustmentdetails.md) + +[Home](./index.md) > [@liquity/lib-ethers](./lib-ethers.md) > [EthersTransactionCancelledError](./lib-ethers.etherstransactioncancellederror.md) + +## EthersTransactionCancelledError class + +Thrown when a transaction is cancelled or replaced by a different transaction. + +Signature: + +```typescript +export declare class EthersTransactionCancelledError extends Error +``` +Extends: Error + +## Remarks + +The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `EthersTransactionCancelledError` class. + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [rawError](./lib-ethers.etherstransactioncancellederror.rawerror.md) | | Error | | +| [rawReplacementReceipt](./lib-ethers.etherstransactioncancellederror.rawreplacementreceipt.md) | | [EthersTransactionReceipt](./lib-ethers.etherstransactionreceipt.md) | | + diff --git a/docs/sdk/lib-ethers.etherstransactioncancellederror.rawerror.md b/docs/sdk/lib-ethers.etherstransactioncancellederror.rawerror.md new file mode 100644 index 000000000..1e4c44805 --- /dev/null +++ b/docs/sdk/lib-ethers.etherstransactioncancellederror.rawerror.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [@liquity/lib-ethers](./lib-ethers.md) > [EthersTransactionCancelledError](./lib-ethers.etherstransactioncancellederror.md) > [rawError](./lib-ethers.etherstransactioncancellederror.rawerror.md) + +## EthersTransactionCancelledError.rawError property + +Signature: + +```typescript +readonly rawError: Error; +``` diff --git a/docs/sdk/lib-ethers.etherstransactioncancellederror.rawreplacementreceipt.md b/docs/sdk/lib-ethers.etherstransactioncancellederror.rawreplacementreceipt.md new file mode 100644 index 000000000..c151c7277 --- /dev/null +++ b/docs/sdk/lib-ethers.etherstransactioncancellederror.rawreplacementreceipt.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [@liquity/lib-ethers](./lib-ethers.md) > [EthersTransactionCancelledError](./lib-ethers.etherstransactioncancellederror.md) > [rawReplacementReceipt](./lib-ethers.etherstransactioncancellederror.rawreplacementreceipt.md) + +## EthersTransactionCancelledError.rawReplacementReceipt property + +Signature: + +```typescript +readonly rawReplacementReceipt: EthersTransactionReceipt; +``` diff --git a/docs/sdk/lib-ethers.md b/docs/sdk/lib-ethers.md index 0f90cf3cc..1acac6a5d 100644 --- a/docs/sdk/lib-ethers.md +++ b/docs/sdk/lib-ethers.md @@ -10,6 +10,7 @@ | --- | --- | | [BlockPolledLiquityStore](./lib-ethers.blockpolledliquitystore.md) | Ethers-based [LiquityStore](./lib-base.liquitystore.md) that updates state whenever there's a new block. | | [EthersLiquity](./lib-ethers.ethersliquity.md) | Convenience class that combines multiple interfaces of the library in one object. | +| [EthersTransactionCancelledError](./lib-ethers.etherstransactioncancellederror.md) | Thrown when a transaction is cancelled or replaced by a different transaction. | | [EthersTransactionFailedError](./lib-ethers.etherstransactionfailederror.md) | Thrown by [EthersLiquity](./lib-ethers.ethersliquity.md) in case of transaction failure. | | [PopulatableEthersLiquity](./lib-ethers.populatableethersliquity.md) | Ethers-based implementation of [PopulatableLiquity](./lib-base.populatableliquity.md). | | [PopulatedEthersLiquityTransaction](./lib-ethers.populatedethersliquitytransaction.md) | A transaction that has been prepared for sending. | diff --git a/docs/sdk/lib-ethers.sentethersliquitytransaction.waitforreceipt.md b/docs/sdk/lib-ethers.sentethersliquitytransaction.waitforreceipt.md index f62443785..8834e17d0 100644 --- a/docs/sdk/lib-ethers.sentethersliquitytransaction.waitforreceipt.md +++ b/docs/sdk/lib-ethers.sentethersliquitytransaction.waitforreceipt.md @@ -17,3 +17,7 @@ Promise<[MinedReceipt](./lib-base.minedreceipt.md)<[EthersTransact Either a [FailedReceipt](./lib-base.failedreceipt.md) or a [SuccessfulReceipt](./lib-base.successfulreceipt.md). +## Exceptions + +Throws [EthersTransactionCancelledError](./lib-ethers.etherstransactioncancellederror.md) if the transaction is cancelled or replaced. + diff --git a/packages/examples/package.json b/packages/examples/package.json index d91da912b..a4e76280d 100644 --- a/packages/examples/package.json +++ b/packages/examples/package.json @@ -4,7 +4,7 @@ "private": true, "devDependencies": { "chalk": "^4.1.0", - "ethers": "^5.0.0" + "ethers": "^5.3.0" }, "scripts": { "liqbot": "node src/liqbot.js" diff --git a/packages/fuzzer/package.json b/packages/fuzzer/package.json index 2b7ca864d..679648a22 100644 --- a/packages/fuzzer/package.json +++ b/packages/fuzzer/package.json @@ -9,7 +9,7 @@ "@types/yargs": "^16.0.0", "colors": "^1.4.0", "dotenv": "^8.2.0", - "ethers": "^5.0.0", + "ethers": "^5.3.0", "ts-node": "^9.0.0", "typescript": "~4.1.0", "yargs": "^16.0.3" diff --git a/packages/lib-ethers/etc/lib-ethers.api.md b/packages/lib-ethers/etc/lib-ethers.api.md index 0dcf0da6a..f15800358 100644 --- a/packages/lib-ethers/etc/lib-ethers.api.md +++ b/packages/lib-ethers/etc/lib-ethers.api.md @@ -9,6 +9,7 @@ import { BlockTag } from '@ethersproject/abstract-provider'; import { CollateralGainTransferDetails } from '@liquity/lib-base'; import { Decimal } from '@liquity/lib-base'; import { Decimalish } from '@liquity/lib-base'; +import { ErrorCode } from '@ethersproject/logger'; import { FailedReceipt } from '@liquity/lib-base'; import { Fees } from '@liquity/lib-base'; import { FrontendStatus } from '@liquity/lib-base'; @@ -262,6 +263,16 @@ export type EthersProvider = Provider; // @public export type EthersSigner = Signer; +// @public +export class EthersTransactionCancelledError extends Error { + // @internal + constructor(rawError: _RawTransactionReplacedError); + // (undocumented) + readonly rawError: Error; + // (undocumented) + readonly rawReplacementReceipt: EthersTransactionReceipt; +} + // @public export class EthersTransactionFailedError extends TransactionFailedError> { constructor(message: string, failedReceipt: FailedReceipt); @@ -390,6 +401,34 @@ export class PopulatedEthersRedemption extends PopulatedEthersLiquityTransaction readonly redeemableLUSDAmount: Decimal; } +// @internal (undocumented) +export enum _RawErrorReason { + // (undocumented) + TRANSACTION_CANCELLED = "cancelled", + // (undocumented) + TRANSACTION_FAILED = "transaction failed", + // (undocumented) + TRANSACTION_REPLACED = "replaced", + // (undocumented) + TRANSACTION_REPRICED = "repriced" +} + +// @internal (undocumented) +export interface _RawTransactionReplacedError extends Error { + // (undocumented) + cancelled: boolean; + // (undocumented) + code: ErrorCode.TRANSACTION_REPLACED; + // (undocumented) + hash: string; + // (undocumented) + reason: _RawErrorReason.TRANSACTION_CANCELLED | _RawErrorReason.TRANSACTION_REPLACED | _RawErrorReason.TRANSACTION_REPRICED; + // (undocumented) + receipt: EthersTransactionReceipt; + // (undocumented) + replacement: EthersTransactionResponse; +} + // @public export class ReadableEthersLiquity implements ReadableLiquity { // @internal diff --git a/packages/lib-ethers/package.json b/packages/lib-ethers/package.json index d820a903d..ebbf24570 100644 --- a/packages/lib-ethers/package.json +++ b/packages/lib-ethers/package.json @@ -35,7 +35,7 @@ }, "peerDependencies": { "@liquity/lib-base": "^3.0.0", - "ethers": "^5.0.0" + "ethers": "^5.3.0" }, "devDependencies": { "@microsoft/api-extractor": "7.13.2", diff --git a/packages/lib-ethers/src/EthersLiquity.ts b/packages/lib-ethers/src/EthersLiquity.ts index f0375e083..538db3b8c 100644 --- a/packages/lib-ethers/src/EthersLiquity.ts +++ b/packages/lib-ethers/src/EthersLiquity.ts @@ -306,6 +306,7 @@ export class EthersLiquity implements ReadableEthersLiquity, TransactableLiquity * * @throws * Throws {@link EthersTransactionFailedError} in case of transaction failure. + * Throws {@link EthersTransactionCancelledError} if the transaction is cancelled or replaced. */ openTrove( params: TroveCreationParams, @@ -320,6 +321,7 @@ export class EthersLiquity implements ReadableEthersLiquity, TransactableLiquity * * @throws * Throws {@link EthersTransactionFailedError} in case of transaction failure. + * Throws {@link EthersTransactionCancelledError} if the transaction is cancelled or replaced. */ closeTrove(overrides?: EthersTransactionOverrides): Promise { return this.send.closeTrove(overrides).then(waitForSuccess); @@ -330,6 +332,7 @@ export class EthersLiquity implements ReadableEthersLiquity, TransactableLiquity * * @throws * Throws {@link EthersTransactionFailedError} in case of transaction failure. + * Throws {@link EthersTransactionCancelledError} if the transaction is cancelled or replaced. */ adjustTrove( params: TroveAdjustmentParams, @@ -344,6 +347,7 @@ export class EthersLiquity implements ReadableEthersLiquity, TransactableLiquity * * @throws * Throws {@link EthersTransactionFailedError} in case of transaction failure. + * Throws {@link EthersTransactionCancelledError} if the transaction is cancelled or replaced. */ depositCollateral( amount: Decimalish, @@ -357,6 +361,7 @@ export class EthersLiquity implements ReadableEthersLiquity, TransactableLiquity * * @throws * Throws {@link EthersTransactionFailedError} in case of transaction failure. + * Throws {@link EthersTransactionCancelledError} if the transaction is cancelled or replaced. */ withdrawCollateral( amount: Decimalish, @@ -370,6 +375,7 @@ export class EthersLiquity implements ReadableEthersLiquity, TransactableLiquity * * @throws * Throws {@link EthersTransactionFailedError} in case of transaction failure. + * Throws {@link EthersTransactionCancelledError} if the transaction is cancelled or replaced. */ borrowLUSD( amount: Decimalish, @@ -384,6 +390,7 @@ export class EthersLiquity implements ReadableEthersLiquity, TransactableLiquity * * @throws * Throws {@link EthersTransactionFailedError} in case of transaction failure. + * Throws {@link EthersTransactionCancelledError} if the transaction is cancelled or replaced. */ repayLUSD( amount: Decimalish, @@ -402,6 +409,7 @@ export class EthersLiquity implements ReadableEthersLiquity, TransactableLiquity * * @throws * Throws {@link EthersTransactionFailedError} in case of transaction failure. + * Throws {@link EthersTransactionCancelledError} if the transaction is cancelled or replaced. */ liquidate( address: string | string[], @@ -415,6 +423,7 @@ export class EthersLiquity implements ReadableEthersLiquity, TransactableLiquity * * @throws * Throws {@link EthersTransactionFailedError} in case of transaction failure. + * Throws {@link EthersTransactionCancelledError} if the transaction is cancelled or replaced. */ liquidateUpTo( maximumNumberOfTrovesToLiquidate: number, @@ -428,6 +437,7 @@ export class EthersLiquity implements ReadableEthersLiquity, TransactableLiquity * * @throws * Throws {@link EthersTransactionFailedError} in case of transaction failure. + * Throws {@link EthersTransactionCancelledError} if the transaction is cancelled or replaced. */ depositLUSDInStabilityPool( amount: Decimalish, @@ -442,6 +452,7 @@ export class EthersLiquity implements ReadableEthersLiquity, TransactableLiquity * * @throws * Throws {@link EthersTransactionFailedError} in case of transaction failure. + * Throws {@link EthersTransactionCancelledError} if the transaction is cancelled or replaced. */ withdrawLUSDFromStabilityPool( amount: Decimalish, @@ -455,6 +466,7 @@ export class EthersLiquity implements ReadableEthersLiquity, TransactableLiquity * * @throws * Throws {@link EthersTransactionFailedError} in case of transaction failure. + * Throws {@link EthersTransactionCancelledError} if the transaction is cancelled or replaced. */ withdrawGainsFromStabilityPool( overrides?: EthersTransactionOverrides @@ -467,6 +479,7 @@ export class EthersLiquity implements ReadableEthersLiquity, TransactableLiquity * * @throws * Throws {@link EthersTransactionFailedError} in case of transaction failure. + * Throws {@link EthersTransactionCancelledError} if the transaction is cancelled or replaced. */ transferCollateralGainToTrove( overrides?: EthersTransactionOverrides @@ -479,6 +492,7 @@ export class EthersLiquity implements ReadableEthersLiquity, TransactableLiquity * * @throws * Throws {@link EthersTransactionFailedError} in case of transaction failure. + * Throws {@link EthersTransactionCancelledError} if the transaction is cancelled or replaced. */ sendLUSD( toAddress: string, @@ -493,6 +507,7 @@ export class EthersLiquity implements ReadableEthersLiquity, TransactableLiquity * * @throws * Throws {@link EthersTransactionFailedError} in case of transaction failure. + * Throws {@link EthersTransactionCancelledError} if the transaction is cancelled or replaced. */ sendLQTY( toAddress: string, @@ -507,6 +522,7 @@ export class EthersLiquity implements ReadableEthersLiquity, TransactableLiquity * * @throws * Throws {@link EthersTransactionFailedError} in case of transaction failure. + * Throws {@link EthersTransactionCancelledError} if the transaction is cancelled or replaced. */ redeemLUSD( amount: Decimalish, @@ -521,6 +537,7 @@ export class EthersLiquity implements ReadableEthersLiquity, TransactableLiquity * * @throws * Throws {@link EthersTransactionFailedError} in case of transaction failure. + * Throws {@link EthersTransactionCancelledError} if the transaction is cancelled or replaced. */ claimCollateralSurplus(overrides?: EthersTransactionOverrides): Promise { return this.send.claimCollateralSurplus(overrides).then(waitForSuccess); @@ -531,6 +548,7 @@ export class EthersLiquity implements ReadableEthersLiquity, TransactableLiquity * * @throws * Throws {@link EthersTransactionFailedError} in case of transaction failure. + * Throws {@link EthersTransactionCancelledError} if the transaction is cancelled or replaced. */ stakeLQTY(amount: Decimalish, overrides?: EthersTransactionOverrides): Promise { return this.send.stakeLQTY(amount, overrides).then(waitForSuccess); @@ -541,6 +559,7 @@ export class EthersLiquity implements ReadableEthersLiquity, TransactableLiquity * * @throws * Throws {@link EthersTransactionFailedError} in case of transaction failure. + * Throws {@link EthersTransactionCancelledError} if the transaction is cancelled or replaced. */ unstakeLQTY(amount: Decimalish, overrides?: EthersTransactionOverrides): Promise { return this.send.unstakeLQTY(amount, overrides).then(waitForSuccess); @@ -551,6 +570,7 @@ export class EthersLiquity implements ReadableEthersLiquity, TransactableLiquity * * @throws * Throws {@link EthersTransactionFailedError} in case of transaction failure. + * Throws {@link EthersTransactionCancelledError} if the transaction is cancelled or replaced. */ withdrawGainsFromStaking(overrides?: EthersTransactionOverrides): Promise { return this.send.withdrawGainsFromStaking(overrides).then(waitForSuccess); @@ -561,6 +581,7 @@ export class EthersLiquity implements ReadableEthersLiquity, TransactableLiquity * * @throws * Throws {@link EthersTransactionFailedError} in case of transaction failure. + * Throws {@link EthersTransactionCancelledError} if the transaction is cancelled or replaced. */ registerFrontend(kickbackRate: Decimalish, overrides?: EthersTransactionOverrides): Promise { return this.send.registerFrontend(kickbackRate, overrides).then(waitForSuccess); @@ -580,6 +601,7 @@ export class EthersLiquity implements ReadableEthersLiquity, TransactableLiquity * * @throws * Throws {@link EthersTransactionFailedError} in case of transaction failure. + * Throws {@link EthersTransactionCancelledError} if the transaction is cancelled or replaced. */ approveUniTokens(allowance?: Decimalish, overrides?: EthersTransactionOverrides): Promise { return this.send.approveUniTokens(allowance, overrides).then(waitForSuccess); @@ -590,6 +612,7 @@ export class EthersLiquity implements ReadableEthersLiquity, TransactableLiquity * * @throws * Throws {@link EthersTransactionFailedError} in case of transaction failure. + * Throws {@link EthersTransactionCancelledError} if the transaction is cancelled or replaced. */ stakeUniTokens(amount: Decimalish, overrides?: EthersTransactionOverrides): Promise { return this.send.stakeUniTokens(amount, overrides).then(waitForSuccess); @@ -600,6 +623,7 @@ export class EthersLiquity implements ReadableEthersLiquity, TransactableLiquity * * @throws * Throws {@link EthersTransactionFailedError} in case of transaction failure. + * Throws {@link EthersTransactionCancelledError} if the transaction is cancelled or replaced. */ unstakeUniTokens(amount: Decimalish, overrides?: EthersTransactionOverrides): Promise { return this.send.unstakeUniTokens(amount, overrides).then(waitForSuccess); @@ -610,6 +634,7 @@ export class EthersLiquity implements ReadableEthersLiquity, TransactableLiquity * * @throws * Throws {@link EthersTransactionFailedError} in case of transaction failure. + * Throws {@link EthersTransactionCancelledError} if the transaction is cancelled or replaced. */ withdrawLQTYRewardFromLiquidityMining(overrides?: EthersTransactionOverrides): Promise { return this.send.withdrawLQTYRewardFromLiquidityMining(overrides).then(waitForSuccess); @@ -620,6 +645,7 @@ export class EthersLiquity implements ReadableEthersLiquity, TransactableLiquity * * @throws * Throws {@link EthersTransactionFailedError} in case of transaction failure. + * Throws {@link EthersTransactionCancelledError} if the transaction is cancelled or replaced. */ exitLiquidityMining(overrides?: EthersTransactionOverrides): Promise { return this.send.exitLiquidityMining(overrides).then(waitForSuccess); diff --git a/packages/lib-ethers/src/PopulatableEthersLiquity.ts b/packages/lib-ethers/src/PopulatableEthersLiquity.ts index 317be6e21..9df9c596e 100644 --- a/packages/lib-ethers/src/PopulatableEthersLiquity.ts +++ b/packages/lib-ethers/src/PopulatableEthersLiquity.ts @@ -3,6 +3,8 @@ import assert from "assert"; import { BigNumber, BigNumberish } from "@ethersproject/bignumber"; import { AddressZero } from "@ethersproject/constants"; import { Log } from "@ethersproject/abstract-provider"; +import { ErrorCode } from "@ethersproject/logger"; +import { Transaction } from "@ethersproject/transactions"; import { CollateralGainTransferDetails, @@ -43,7 +45,6 @@ import { import { EthersLiquityConnection, _getContracts, - _getProvider, _requireAddress, _requireSigner } from "./EthersLiquityConnection"; @@ -112,6 +113,75 @@ function* generateTrials(totalNumberOfTrials: number) { } } +/** @internal */ +export enum _RawErrorReason { + TRANSACTION_FAILED = "transaction failed", + TRANSACTION_CANCELLED = "cancelled", + TRANSACTION_REPLACED = "replaced", + TRANSACTION_REPRICED = "repriced" +} + +const transactionReplacementReasons: unknown[] = [ + _RawErrorReason.TRANSACTION_CANCELLED, + _RawErrorReason.TRANSACTION_REPLACED, + _RawErrorReason.TRANSACTION_REPRICED +]; + +interface RawTransactionFailedError extends Error { + code: ErrorCode.CALL_EXCEPTION; + reason: _RawErrorReason.TRANSACTION_FAILED; + transactionHash: string; + transaction: Transaction; + receipt: EthersTransactionReceipt; +} + +/** @internal */ +export interface _RawTransactionReplacedError extends Error { + code: ErrorCode.TRANSACTION_REPLACED; + reason: + | _RawErrorReason.TRANSACTION_CANCELLED + | _RawErrorReason.TRANSACTION_REPLACED + | _RawErrorReason.TRANSACTION_REPRICED; + cancelled: boolean; + hash: string; + replacement: EthersTransactionResponse; + receipt: EthersTransactionReceipt; +} + +const hasProp = (o: T, p: P): o is T & { [_ in P]: unknown } => p in o; + +const isTransactionFailedError = (error: Error): error is RawTransactionFailedError => + hasProp(error, "code") && + error.code === ErrorCode.CALL_EXCEPTION && + hasProp(error, "reason") && + error.reason === _RawErrorReason.TRANSACTION_FAILED; + +const isTransactionReplacedError = (error: Error): error is _RawTransactionReplacedError => + hasProp(error, "code") && + error.code === ErrorCode.TRANSACTION_REPLACED && + hasProp(error, "reason") && + transactionReplacementReasons.includes(error.reason); + +/** + * Thrown when a transaction is cancelled or replaced by a different transaction. + * + * @public + */ +export class EthersTransactionCancelledError extends Error { + readonly rawReplacementReceipt: EthersTransactionReceipt; + readonly rawError: Error; + + /** @internal */ + constructor(rawError: _RawTransactionReplacedError) { + assert(rawError.reason !== _RawErrorReason.TRANSACTION_REPRICED); + + super(`Transaction ${rawError.reason}`); + this.name = "TransactionCancelledError"; + this.rawReplacementReceipt = rawError.receipt; + this.rawError = rawError; + } +} + /** * A transaction that has already been sent. * @@ -150,18 +220,41 @@ export class SentEthersLiquityTransaction : _pendingReceipt; } + private async _waitForRawReceipt(confirmations?: number) { + try { + return await this.rawSentTransaction.wait(confirmations); + } catch (error: unknown) { + if (error instanceof Error) { + if (isTransactionFailedError(error)) { + return error.receipt; + } + + if (isTransactionReplacedError(error)) { + if (error.cancelled) { + throw new EthersTransactionCancelledError(error); + } else { + return error.receipt; + } + } + } + + throw error; + } + } + /** {@inheritDoc @liquity/lib-base#SentLiquityTransaction.getReceipt} */ async getReceipt(): Promise> { - return this._receiptFrom( - await _getProvider(this._connection).getTransactionReceipt(this.rawSentTransaction.hash) - ); + return this._receiptFrom(await this._waitForRawReceipt(0)); } - /** {@inheritDoc @liquity/lib-base#SentLiquityTransaction.waitForReceipt} */ + /** + * {@inheritDoc @liquity/lib-base#SentLiquityTransaction.waitForReceipt} + * + * @throws + * Throws {@link EthersTransactionCancelledError} if the transaction is cancelled or replaced. + */ async waitForReceipt(): Promise> { - const receipt = this._receiptFrom( - await _getProvider(this._connection).waitForTransaction(this.rawSentTransaction.hash) - ); + const receipt = this._receiptFrom(await this._waitForRawReceipt()); assert(receipt.status !== "pending"); return receipt; diff --git a/packages/providers/package.json b/packages/providers/package.json index a4e733bd5..492d847c5 100644 --- a/packages/providers/package.json +++ b/packages/providers/package.json @@ -8,11 +8,11 @@ "prepare": "tsc --project tsconfig.dist.json" }, "devDependencies": { - "ethers": "^5.0.0", + "ethers": "^5.3.0", "ts-node": "^9.1.1", "typescript": "~4.1.0" }, "peerDependencies": { - "ethers": "^5.0.0" + "ethers": "^5.3.0" } } From 8f54a6bebe32fbd4fd65fc9a227078fde0676abe Mon Sep 17 00:00:00 2001 From: Daniel Simon Date: Fri, 18 Jun 2021 13:30:18 +0700 Subject: [PATCH 45/58] feat: optimize WebSocketAugmentedProvider for replacement detection --- .../src/WebSocketAugmentedProvider.ts | 140 +++++++++++++++--- 1 file changed, 121 insertions(+), 19 deletions(-) diff --git a/packages/providers/src/WebSocketAugmentedProvider.ts b/packages/providers/src/WebSocketAugmentedProvider.ts index d252150df..ce054e7a7 100644 --- a/packages/providers/src/WebSocketAugmentedProvider.ts +++ b/packages/providers/src/WebSocketAugmentedProvider.ts @@ -1,6 +1,7 @@ import { TransactionRequest, TransactionReceipt, + TransactionResponse, BlockTag, EventType, Listener, @@ -10,6 +11,7 @@ import { import { BaseProvider, Web3Provider } from "@ethersproject/providers"; import { Networkish } from "@ethersproject/networks"; import { Deferrable } from "@ethersproject/properties"; +import { hexDataLength } from "@ethersproject/bytes"; import { WebSocketProvider } from "./WebSocketProvider"; @@ -30,11 +32,53 @@ export const isWebSocketAugmentedProvider = ( const isHeaderNotFoundError = (error: any) => typeof error === "object" && typeof error.message === "string" && - error.message.includes("header not found"); + (error.message.includes( + // geth + "header not found" + ) || + error.message.includes( + // openethereum + "request is not supported because your node is running with state pruning" + )); + +const isTransactionHash = (eventName: EventType): eventName is string => + typeof eventName === "string" && hexDataLength(eventName) === 32; const loadBalancingGlitchRetryIntervalMs = 200; const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); +type BlockListenerContext = { + isActive: () => boolean; + removeMe: () => void; +}; + +const waitFor = (f: (t: T) => Promise) => (g: (u: U) => void) => ( + t: T, + { isActive }: BlockListenerContext +) => { + f(t).then(u => { + if (u !== null && isActive()) { + g(u); + } + }); +}; + +const pass = (f: (t: T) => void) => (t: T, _: BlockListenerContext) => { + f(t); +}; + +const passOnce = (f: (t: T) => void) => (t: T, { removeMe }: BlockListenerContext) => { + f(t); + removeMe(); +}; + +const sequence = ( + f: (_: (_: U) => void) => (_: T, context: BlockListenerContext) => void, + g: (_: (_: V) => void) => (_: U, context: BlockListenerContext) => void +) => (h: (_: V) => void) => (t: T, context: BlockListenerContext) => { + f(u => g(h)(u, context))(t, context); +}; + export const WebSocketAugmented = BaseProvider>(Base: T) => { let webSocketAugmentedProvider = class extends Base implements WebSocketAugmentedProvider { _wsProvider?: WebSocketProvider; @@ -44,7 +88,7 @@ export const WebSocketAugmented = BaseProvide _seenBlock = 0; _blockListenerScheduled = false; - readonly _blockListeners = new Set<(blockNumber: number) => void>(); + readonly _blockListeners = new Map<(_: never) => void, (blockNumber: number) => void>(); readonly _blockListener = this._onBlock.bind(this); openWebSocket(url: string, network: Networkish) { @@ -91,7 +135,7 @@ export const WebSocketAugmented = BaseProvide setTimeout(() => { this._blockListenerScheduled = false; - [...this._blockListeners].forEach(listener => listener(this._seenBlock)); + [...this._blockListeners].forEach(([, listener]) => listener(this._seenBlock)); }, 50); } } @@ -165,17 +209,39 @@ export const WebSocketAugmented = BaseProvide } } + _wrap( + f: (t: T) => void, + g: (f: (t: T) => void) => (u: U, { removeMe }: BlockListenerContext) => void + ): [(t: T) => void, (u: U) => void] { + return [ + f, + (u: U) => + g(f)(u, { + isActive: () => this._blockListeners.has(f), + removeMe: () => this._blockListeners.delete(f) + }) + ]; + } + on(eventName: EventType, listener: Listener) { - if (eventName === "block") { - return this._addBlockListener(listener); + if (isTransactionHash(eventName)) { + const fetchReceipt = this._getTransactionReceiptFromLatest.bind(this, eventName); + const [, passReceipt] = this._wrap(listener, waitFor(fetchReceipt)); + + passReceipt(undefined); + + return this._addBlockListener(listener, passReceipt); + } else if (eventName === "block") { + return this._addBlockListener(...this._wrap(listener, pass)); } else { return super.on(eventName, listener); } } - _addBlockListener(listener: (blockNumber: number) => void) { - if (!this._blockListeners.has(listener)) { - this._blockListeners.add(listener); + _addBlockListener(key: (_: never) => void, blockListener: (blockNumber: number) => void) { + if (!this._blockListeners.has(key)) { + this._blockListeners.set(key, blockListener); + if (this._blockListeners.size === 1) { this._startBlockEvents(); } @@ -184,28 +250,31 @@ export const WebSocketAugmented = BaseProvide } once(eventName: EventType, listener: Listener) { - if (eventName === "block") { - const listenOnce = (blockNumber: number) => { - listener(blockNumber); - this._removeBlockListener(listenOnce); - }; - return this._addBlockListener(listenOnce); + if (isTransactionHash(eventName)) { + const fetchReceipt = this._getTransactionReceiptFromLatest.bind(this, eventName); + const [, passReceiptOnce] = this._wrap(listener, sequence(waitFor(fetchReceipt), passOnce)); + + passReceiptOnce(undefined); + + return this._addBlockListener(listener, passReceiptOnce); + } else if (eventName === "block") { + return this._addBlockListener(...this._wrap(listener, passOnce)); } else { return super.once(eventName, listener); } } off(eventName: EventType, listener: Listener) { - if (eventName === "block") { + if (isTransactionHash(eventName) || eventName === "block") { return this._removeBlockListener(listener); } else { return super.off(eventName, listener); } } - _removeBlockListener(listener: (blockNumber: number) => void) { - if (this._blockListeners.has(listener)) { - this._blockListeners.delete(listener); + _removeBlockListener(key: (_: never) => void) { + if (this._blockListeners.has(key)) { + this._blockListeners.delete(key); if (this._blockListeners.size === 0) { this._stopBlockEvents(); } @@ -213,12 +282,45 @@ export const WebSocketAugmented = BaseProvide return this; } + async getTransaction(transactionHash: string | Promise) { + const txPromises: Promise[] = [ + super.getTransaction(transactionHash), + ...(this._wsProvider?.isReady ? [this._wsProvider.getTransaction(transactionHash)] : []) + ]; + + const first = await Promise.race(txPromises); + const tx = first ?? (await Promise.all(txPromises)).find(tx => tx !== null) ?? null; + + return tx as TransactionResponse; + } + getTransactionReceipt(transactionHash: string | Promise) { - return this._wsProvider?.ready + return this._wsProvider?.isReady ? this._wsProvider.getTransactionReceipt(transactionHash) : super.getTransactionReceipt(transactionHash); } + getTransactionCount( + addressOrName: string | Promise, + blockTag?: BlockTag | Promise + ) { + return this._wsProvider?.isReady + ? this._wsProvider.getTransactionCount(addressOrName, blockTag) + : super.getTransactionCount(addressOrName, blockTag); + } + + getBlock(blockHashOrBlockTag: BlockTag | string | Promise) { + return this._wsProvider?.isReady + ? this._wsProvider.getBlock(blockHashOrBlockTag) + : super.getBlock(blockHashOrBlockTag); + } + + getBlockWithTransactions(blockHashOrBlockTag: BlockTag | string | Promise) { + return this._wsProvider?.isReady + ? this._wsProvider.getBlockWithTransactions(blockHashOrBlockTag) + : super.getBlockWithTransactions(blockHashOrBlockTag); + } + async _blockContainsTx(blockNumber: number, txHash: string) { let block: Block | null; From 571d59fd2cab634875dff801e6c9b3eff753c2fd Mon Sep 17 00:00:00 2001 From: Daniel Simon Date: Fri, 18 Jun 2021 13:30:58 +0700 Subject: [PATCH 46/58] feat: detect cancellation of TXs that have been broadcast --- .../src/components/Transaction.tsx | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/packages/dev-frontend/src/components/Transaction.tsx b/packages/dev-frontend/src/components/Transaction.tsx index 75ae926d6..b09a82398 100644 --- a/packages/dev-frontend/src/components/Transaction.tsx +++ b/packages/dev-frontend/src/components/Transaction.tsx @@ -7,7 +7,7 @@ import { defaultAbiCoder } from "@ethersproject/abi"; import { buildStyles, CircularProgressbarWithChildren } from "react-circular-progressbar"; import "react-circular-progressbar/dist/styles.css"; -import { EthersTransactionOverrides } from "@liquity/lib-ethers"; +import { EthersTransactionOverrides, EthersTransactionCancelledError } from "@liquity/lib-ethers"; import { SentLiquityTransaction, LiquityReceipt } from "@liquity/lib-base"; import { useLiquity } from "../hooks/LiquityContext"; @@ -343,14 +343,21 @@ export const TransactionMonitor: React.FC = () => { return; } - console.error(`Failed to get receipt for tx ${txHash}`); - console.error(rawError); + finished = true; + + if (rawError instanceof EthersTransactionCancelledError) { + console.log(`Cancelled tx ${txHash}`); + setTransactionState({ type: "cancelled", id }); + } else { + console.error(`Failed to get receipt for tx ${txHash}`); + console.error(rawError); - setTransactionState({ - type: "failed", - id, - error: new Error("Failed") - }); + setTransactionState({ + type: "failed", + id, + error: new Error("Failed") + }); + } } }; From 2a251c3e54993b7b03c8b0047bb31cd98b457e5f Mon Sep 17 00:00:00 2001 From: Daniel Simon Date: Fri, 18 Jun 2021 16:32:20 +0700 Subject: [PATCH 47/58] fix: properly disable max button if collateral is maxed out Fixes #606. --- packages/dev-frontend/src/components/Trove/Adjusting.tsx | 6 ++++-- packages/dev-frontend/src/components/Trove/Opening.tsx | 5 +++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/dev-frontend/src/components/Trove/Adjusting.tsx b/packages/dev-frontend/src/components/Trove/Adjusting.tsx index c6ac78fb0..8cb25647e 100644 --- a/packages/dev-frontend/src/components/Trove/Adjusting.tsx +++ b/packages/dev-frontend/src/components/Trove/Adjusting.tsx @@ -133,8 +133,10 @@ export const Adjusting: React.FC = () => { const maxBorrowingRate = borrowingRate.add(0.005); const updatedTrove = isDirty ? new Trove(collateral, totalDebt) : trove; const feePct = new Percent(borrowingRate); - const maxEth = accountBalance.gt(GAS_ROOM_ETH) ? accountBalance.sub(GAS_ROOM_ETH) : Decimal.ZERO; - const maxCollateral = collateral.add(maxEth); + const availableEth = accountBalance.gt(GAS_ROOM_ETH) + ? accountBalance.sub(GAS_ROOM_ETH) + : Decimal.ZERO; + const maxCollateral = trove.collateral.add(availableEth); const collateralMaxedOut = collateral.eq(maxCollateral); const collateralRatio = !collateral.isZero && !netDebt.isZero ? updatedTrove.collateralRatio(price) : undefined; diff --git a/packages/dev-frontend/src/components/Trove/Opening.tsx b/packages/dev-frontend/src/components/Trove/Opening.tsx index b96ebf811..4d96a93fc 100644 --- a/packages/dev-frontend/src/components/Trove/Opening.tsx +++ b/packages/dev-frontend/src/components/Trove/Opening.tsx @@ -54,8 +54,9 @@ export const Opening: React.FC = () => { const totalDebt = borrowAmount.add(LUSD_LIQUIDATION_RESERVE).add(fee); const isDirty = !collateral.isZero || !borrowAmount.isZero; const trove = isDirty ? new Trove(collateral, totalDebt) : EMPTY_TROVE; - const maxEth = accountBalance.gt(GAS_ROOM_ETH) ? accountBalance.sub(GAS_ROOM_ETH) : Decimal.ZERO; - const maxCollateral = collateral.add(maxEth); + const maxCollateral = accountBalance.gt(GAS_ROOM_ETH) + ? accountBalance.sub(GAS_ROOM_ETH) + : Decimal.ZERO; const collateralMaxedOut = collateral.eq(maxCollateral); const collateralRatio = !collateral.isZero && !borrowAmount.isZero ? trove.collateralRatio(price) : undefined; From 7fa51158d82641cee478f07a849b5338a667c4e8 Mon Sep 17 00:00:00 2001 From: Daniel Simon Date: Wed, 12 May 2021 18:50:23 +0700 Subject: [PATCH 48/58] refactor: stop using `_CachedReadableLiquity` It's kinda pointless. We should eliminate it in the next breaking version. --- .../lib-ethers/src/ReadableEthersLiquity.ts | 241 +++++++++--------- 1 file changed, 117 insertions(+), 124 deletions(-) diff --git a/packages/lib-ethers/src/ReadableEthersLiquity.ts b/packages/lib-ethers/src/ReadableEthersLiquity.ts index af9d4f4d2..7aa6e8f6d 100644 --- a/packages/lib-ethers/src/ReadableEthersLiquity.ts +++ b/packages/lib-ethers/src/ReadableEthersLiquity.ts @@ -12,9 +12,7 @@ import { TroveListingParams, TroveWithPendingRedistribution, UserTrove, - UserTroveStatus, - _CachedReadableLiquity, - _LiquityReadCache + UserTroveStatus } from "@liquity/lib-base"; import { MultiTroveGetter } from "../types"; @@ -537,205 +535,200 @@ export interface ReadableEthersLiquityWithStore { - private _store: BlockPolledLiquityStore; +class _BlockPolledReadableEthersLiquity + implements ReadableEthersLiquityWithStore { + readonly connection: EthersLiquityConnection; + readonly store: BlockPolledLiquityStore; + + private readonly _readable: ReadableEthersLiquity; - constructor(store: BlockPolledLiquityStore) { - this._store = store; + constructor(readable: ReadableEthersLiquity) { + const store = new BlockPolledLiquityStore(readable); + + this.store = store; + this.connection = readable.connection; + this._readable = readable; } private _blockHit(overrides?: EthersCallOverrides): boolean { return ( !overrides || overrides.blockTag === undefined || - overrides.blockTag === this._store.state.blockTag + overrides.blockTag === this.store.state.blockTag ); } private _userHit(address?: string, overrides?: EthersCallOverrides): boolean { return ( this._blockHit(overrides) && - (address === undefined || address === this._store.connection.userAddress) + (address === undefined || address === this.store.connection.userAddress) ); } private _frontendHit(address?: string, overrides?: EthersCallOverrides): boolean { return ( this._blockHit(overrides) && - (address === undefined || address === this._store.connection.frontendTag) + (address === undefined || address === this.store.connection.frontendTag) ); } - getTotalRedistributed(overrides?: EthersCallOverrides): Trove | undefined { - if (this._blockHit(overrides)) { - return this._store.state.totalRedistributed; - } + hasStore(store?: EthersLiquityStoreOption): boolean { + return store === undefined || store === "blockPolled"; } - getTroveBeforeRedistribution( + async getTotalRedistributed(overrides?: EthersCallOverrides): Promise { + return this._blockHit(overrides) + ? this.store.state.totalRedistributed + : this._readable.getTotalRedistributed(overrides); + } + + async getTroveBeforeRedistribution( address?: string, overrides?: EthersCallOverrides - ): TroveWithPendingRedistribution | undefined { - if (this._userHit(address, overrides)) { - return this._store.state.troveBeforeRedistribution; - } + ): Promise { + return this._userHit(address, overrides) + ? this.store.state.troveBeforeRedistribution + : this._readable.getTroveBeforeRedistribution(address, overrides); } - getTrove(address?: string, overrides?: EthersCallOverrides): UserTrove | undefined { - if (this._userHit(address, overrides)) { - return this._store.state.trove; - } + async getTrove(address?: string, overrides?: EthersCallOverrides): Promise { + return this._userHit(address, overrides) + ? this.store.state.trove + : this._readable.getTrove(address, overrides); } - getNumberOfTroves(overrides?: EthersCallOverrides): number | undefined { - if (this._blockHit(overrides)) { - return this._store.state.numberOfTroves; - } + async getNumberOfTroves(overrides?: EthersCallOverrides): Promise { + return this._blockHit(overrides) + ? this.store.state.numberOfTroves + : this._readable.getNumberOfTroves(overrides); } - getPrice(overrides?: EthersCallOverrides): Decimal | undefined { - if (this._blockHit(overrides)) { - return this._store.state.price; - } + async getPrice(overrides?: EthersCallOverrides): Promise { + return this._blockHit(overrides) ? this.store.state.price : this._readable.getPrice(overrides); } - getTotal(overrides?: EthersCallOverrides): Trove | undefined { - if (this._blockHit(overrides)) { - return this._store.state.total; - } + async getTotal(overrides?: EthersCallOverrides): Promise { + return this._blockHit(overrides) ? this.store.state.total : this._readable.getTotal(overrides); } - getStabilityDeposit( + async getStabilityDeposit( address?: string, overrides?: EthersCallOverrides - ): StabilityDeposit | undefined { - if (this._userHit(address, overrides)) { - return this._store.state.stabilityDeposit; - } + ): Promise { + return this._userHit(address, overrides) + ? this.store.state.stabilityDeposit + : this._readable.getStabilityDeposit(address, overrides); } - getRemainingStabilityPoolLQTYReward(overrides?: EthersCallOverrides): Decimal | undefined { - if (this._blockHit(overrides)) { - return this._store.state.remainingStabilityPoolLQTYReward; - } + async getRemainingStabilityPoolLQTYReward(overrides?: EthersCallOverrides): Promise { + return this._blockHit(overrides) + ? this.store.state.remainingStabilityPoolLQTYReward + : this._readable.getRemainingStabilityPoolLQTYReward(overrides); } - getLUSDInStabilityPool(overrides?: EthersCallOverrides): Decimal | undefined { - if (this._blockHit(overrides)) { - return this._store.state.lusdInStabilityPool; - } + async getLUSDInStabilityPool(overrides?: EthersCallOverrides): Promise { + return this._blockHit(overrides) + ? this.store.state.lusdInStabilityPool + : this._readable.getLUSDInStabilityPool(overrides); } - getLUSDBalance(address?: string, overrides?: EthersCallOverrides): Decimal | undefined { - if (this._userHit(address, overrides)) { - return this._store.state.lusdBalance; - } + async getLUSDBalance(address?: string, overrides?: EthersCallOverrides): Promise { + return this._userHit(address, overrides) + ? this.store.state.lusdBalance + : this._readable.getLUSDBalance(address, overrides); } - getLQTYBalance(address?: string, overrides?: EthersCallOverrides): Decimal | undefined { - if (this._userHit(address, overrides)) { - return this._store.state.lqtyBalance; - } + async getLQTYBalance(address?: string, overrides?: EthersCallOverrides): Promise { + return this._userHit(address, overrides) + ? this.store.state.lqtyBalance + : this._readable.getLQTYBalance(address, overrides); } - getUniTokenBalance(address?: string, overrides?: EthersCallOverrides): Decimal | undefined { - if (this._userHit(address, overrides)) { - return this._store.state.uniTokenBalance; - } + async getUniTokenBalance(address?: string, overrides?: EthersCallOverrides): Promise { + return this._userHit(address, overrides) + ? this.store.state.uniTokenBalance + : this._readable.getUniTokenBalance(address, overrides); } - getUniTokenAllowance(address?: string, overrides?: EthersCallOverrides): Decimal | undefined { - if (this._userHit(address, overrides)) { - return this._store.state.uniTokenAllowance; - } + async getUniTokenAllowance(address?: string, overrides?: EthersCallOverrides): Promise { + return this._userHit(address, overrides) + ? this.store.state.uniTokenAllowance + : this._readable.getUniTokenAllowance(address, overrides); } - getRemainingLiquidityMiningLQTYReward(overrides?: EthersCallOverrides): Decimal | undefined { - if (this._blockHit(overrides)) { - return this._store.state.remainingLiquidityMiningLQTYReward; - } + async getRemainingLiquidityMiningLQTYReward(overrides?: EthersCallOverrides): Promise { + return this._blockHit(overrides) + ? this.store.state.remainingLiquidityMiningLQTYReward + : this._readable.getRemainingLiquidityMiningLQTYReward(overrides); } - getLiquidityMiningStake(address?: string, overrides?: EthersCallOverrides): Decimal | undefined { - if (this._userHit(address, overrides)) { - return this._store.state.liquidityMiningStake; - } + async getLiquidityMiningStake( + address?: string, + overrides?: EthersCallOverrides + ): Promise { + return this._userHit(address, overrides) + ? this.store.state.liquidityMiningStake + : this._readable.getLiquidityMiningStake(address, overrides); } - getTotalStakedUniTokens(overrides?: EthersCallOverrides): Decimal | undefined { - if (this._blockHit(overrides)) { - return this._store.state.totalStakedUniTokens; - } + async getTotalStakedUniTokens(overrides?: EthersCallOverrides): Promise { + return this._blockHit(overrides) + ? this.store.state.totalStakedUniTokens + : this._readable.getTotalStakedUniTokens(overrides); } - getLiquidityMiningLQTYReward( + async getLiquidityMiningLQTYReward( address?: string, overrides?: EthersCallOverrides - ): Decimal | undefined { - if (this._userHit(address, overrides)) { - return this._store.state.liquidityMiningLQTYReward; - } + ): Promise { + return this._userHit(address, overrides) + ? this.store.state.liquidityMiningLQTYReward + : this._readable.getLiquidityMiningLQTYReward(address, overrides); } - getCollateralSurplusBalance( + async getCollateralSurplusBalance( address?: string, overrides?: EthersCallOverrides - ): Decimal | undefined { - if (this._userHit(address, overrides)) { - return this._store.state.collateralSurplusBalance; - } + ): Promise { + return this._userHit(address, overrides) + ? this.store.state.collateralSurplusBalance + : this._readable.getCollateralSurplusBalance(address, overrides); } - getFees(overrides?: EthersCallOverrides): Fees | undefined { - if (this._blockHit(overrides)) { - return this._store.state.fees; - } + async getFees(overrides?: EthersCallOverrides): Promise { + return this._blockHit(overrides) ? this.store.state.fees : this._readable.getFees(overrides); } - getLQTYStake(address?: string, overrides?: EthersCallOverrides): LQTYStake | undefined { - if (this._userHit(address, overrides)) { - return this._store.state.lqtyStake; - } + async getLQTYStake(address?: string, overrides?: EthersCallOverrides): Promise { + return this._userHit(address, overrides) + ? this.store.state.lqtyStake + : this._readable.getLQTYStake(address, overrides); } - getTotalStakedLQTY(overrides?: EthersCallOverrides): Decimal | undefined { - if (this._blockHit(overrides)) { - return this._store.state.totalStakedLQTY; - } + async getTotalStakedLQTY(overrides?: EthersCallOverrides): Promise { + return this._blockHit(overrides) + ? this.store.state.totalStakedLQTY + : this._readable.getTotalStakedLQTY(overrides); } - getFrontendStatus( + async getFrontendStatus( address?: string, overrides?: EthersCallOverrides - ): { status: "unregistered" } | { status: "registered"; kickbackRate: Decimal } | undefined { - if (this._frontendHit(address, overrides)) { - return this._store.state.frontend; - } - } - - getTroves() { - return undefined; + ): Promise { + return this._frontendHit(address, overrides) + ? this.store.state.frontend + : this._readable.getFrontendStatus(address, overrides); } -} -class _BlockPolledReadableEthersLiquity - extends _CachedReadableLiquity<[overrides?: EthersCallOverrides]> - implements ReadableEthersLiquityWithStore { - readonly connection: EthersLiquityConnection; - readonly store: BlockPolledLiquityStore; - - constructor(readable: ReadableEthersLiquity) { - const store = new BlockPolledLiquityStore(readable); - - super(readable, new BlockPolledLiquityStoreBasedCache(store)); + getTroves( + params: TroveListingParams & { beforeRedistribution: true }, + overrides?: EthersCallOverrides + ): Promise; - this.store = store; - this.connection = readable.connection; - } + getTroves(params: TroveListingParams, overrides?: EthersCallOverrides): Promise; - hasStore(store?: EthersLiquityStoreOption): boolean { - return store === undefined || store === "blockPolled"; + getTroves(params: TroveListingParams, overrides?: EthersCallOverrides): Promise { + return this._readable.getTroves(params, overrides); } _getActivePool(): Promise { From 26ef873caada2f52d022ea0bc387b16845b5ff4f Mon Sep 17 00:00:00 2001 From: Daniel Simon Date: Mon, 24 May 2021 13:55:15 +0700 Subject: [PATCH 49/58] feat: estimate the gas cost increase of TXs due to fee decay Transactions that borrow LUSD must pay a variable borrowing fee, which is added to the Trove's debt. This fee increases whenever a redemption occurs, and otherwise decays exponentially. Due to this decay, a Trove's collateral ratio can end up being higher than initially calculated if the transaction is pending for a long time. When this happens, the backend has to iterate over the sorted list of Troves to find a new position for the Trove, which costs extra gas. The SDK can now estimate how much the gas costs of the transaction may increase due to this decay, and can include additional gas to ensure that it will still succeed, even if it ends up pending for a relatively long time. The new parameter `borrowingFeeDecayToleranceMinutes` specifies the length of time that should be covered by the extra gas. --- ...arams.borrowingfeedecaytoleranceminutes.md | 22 ++ ...perationoptionalparams.maxborrowingrate.md | 13 + ...ethers.borrowingoperationoptionalparams.md | 21 ++ .../lib-ethers.ethersliquity.adjusttrove.md | 4 +- docs/sdk/lib-ethers.ethersliquity.md | 4 +- .../sdk/lib-ethers.ethersliquity.opentrove.md | 4 +- docs/sdk/lib-ethers.md | 1 + ...rs.populatableethersliquity.adjusttrove.md | 4 +- .../lib-ethers.populatableethersliquity.md | 4 +- ...hers.populatableethersliquity.opentrove.md | 4 +- ...tedethersliquitytransaction.gasheadroom.md | 20 ++ ...thers.populatedethersliquitytransaction.md | 1 + ...thers.sendableethersliquity.adjusttrove.md | 4 +- docs/sdk/lib-ethers.sendableethersliquity.md | 4 +- ...-ethers.sendableethersliquity.opentrove.md | 4 +- packages/lib-ethers/etc/lib-ethers.api.md | 27 +- packages/lib-ethers/package.json | 5 +- packages/lib-ethers/scripts/spam-troves.ts | 104 +++++++ .../lib-ethers/src/BlockPolledLiquityStore.ts | 41 +-- packages/lib-ethers/src/EthersLiquity.ts | 25 +- .../lib-ethers/src/EthersLiquityConnection.ts | 8 +- .../src/PopulatableEthersLiquity.ts | 283 +++++++++++++++--- .../lib-ethers/src/ReadableEthersLiquity.ts | 36 ++- .../lib-ethers/src/SendableEthersLiquity.ts | 15 +- packages/lib-ethers/src/_utils.ts | 23 ++ packages/lib-ethers/src/contracts.ts | 12 + packages/lib-ethers/test/Liquity.test.ts | 13 +- yarn.lock | 12 + 28 files changed, 587 insertions(+), 131 deletions(-) create mode 100644 docs/sdk/lib-ethers.borrowingoperationoptionalparams.borrowingfeedecaytoleranceminutes.md create mode 100644 docs/sdk/lib-ethers.borrowingoperationoptionalparams.maxborrowingrate.md create mode 100644 docs/sdk/lib-ethers.borrowingoperationoptionalparams.md create mode 100644 docs/sdk/lib-ethers.populatedethersliquitytransaction.gasheadroom.md create mode 100644 packages/lib-ethers/scripts/spam-troves.ts create mode 100644 packages/lib-ethers/src/_utils.ts diff --git a/docs/sdk/lib-ethers.borrowingoperationoptionalparams.borrowingfeedecaytoleranceminutes.md b/docs/sdk/lib-ethers.borrowingoperationoptionalparams.borrowingfeedecaytoleranceminutes.md new file mode 100644 index 000000000..da35f6f1d --- /dev/null +++ b/docs/sdk/lib-ethers.borrowingoperationoptionalparams.borrowingfeedecaytoleranceminutes.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [@liquity/lib-ethers](./lib-ethers.md) > [BorrowingOperationOptionalParams](./lib-ethers.borrowingoperationoptionalparams.md) > [borrowingFeeDecayToleranceMinutes](./lib-ethers.borrowingoperationoptionalparams.borrowingfeedecaytoleranceminutes.md) + +## BorrowingOperationOptionalParams.borrowingFeeDecayToleranceMinutes property + +Control the amount of extra gas included attached to the transaction. + +Signature: + +```typescript +borrowingFeeDecayToleranceMinutes?: number; +``` + +## Remarks + +Transactions that borrow LUSD must pay a variable borrowing fee, which is added to the Trove's debt. This fee increases whenever a redemption occurs, and otherwise decays exponentially. Due to this decay, a Trove's collateral ratio can end up being higher than initially calculated if the transaction is pending for a long time. When this happens, the backend has to iterate over the sorted list of Troves to find a new position for the Trove, which costs extra gas. + +The SDK can estimate how much the gas costs of the transaction may increase due to this decay, and can include additional gas to ensure that it will still succeed, even if it ends up pending for a relatively long time. This parameter specifies the length of time that should be covered by the extra gas. + +Default: 60 minutes. + diff --git a/docs/sdk/lib-ethers.borrowingoperationoptionalparams.maxborrowingrate.md b/docs/sdk/lib-ethers.borrowingoperationoptionalparams.maxborrowingrate.md new file mode 100644 index 000000000..911318eba --- /dev/null +++ b/docs/sdk/lib-ethers.borrowingoperationoptionalparams.maxborrowingrate.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [@liquity/lib-ethers](./lib-ethers.md) > [BorrowingOperationOptionalParams](./lib-ethers.borrowingoperationoptionalparams.md) > [maxBorrowingRate](./lib-ethers.borrowingoperationoptionalparams.maxborrowingrate.md) + +## BorrowingOperationOptionalParams.maxBorrowingRate property + +Maximum acceptable [borrowing rate](./lib-base.fees.borrowingrate.md) (default: current borrowing rate plus 0.5%). + +Signature: + +```typescript +maxBorrowingRate?: Decimalish; +``` diff --git a/docs/sdk/lib-ethers.borrowingoperationoptionalparams.md b/docs/sdk/lib-ethers.borrowingoperationoptionalparams.md new file mode 100644 index 000000000..16d4ae44a --- /dev/null +++ b/docs/sdk/lib-ethers.borrowingoperationoptionalparams.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [@liquity/lib-ethers](./lib-ethers.md) > [BorrowingOperationOptionalParams](./lib-ethers.borrowingoperationoptionalparams.md) + +## BorrowingOperationOptionalParams interface + +Optional parameters of a transaction that borrows LUSD. + +Signature: + +```typescript +export interface BorrowingOperationOptionalParams +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [borrowingFeeDecayToleranceMinutes?](./lib-ethers.borrowingoperationoptionalparams.borrowingfeedecaytoleranceminutes.md) | number | (Optional) Control the amount of extra gas included attached to the transaction. | +| [maxBorrowingRate?](./lib-ethers.borrowingoperationoptionalparams.maxborrowingrate.md) | [Decimalish](./lib-base.decimalish.md) | (Optional) Maximum acceptable [borrowing rate](./lib-base.fees.borrowingrate.md) (default: current borrowing rate plus 0.5%). | + diff --git a/docs/sdk/lib-ethers.ethersliquity.adjusttrove.md b/docs/sdk/lib-ethers.ethersliquity.adjusttrove.md index b9fb38566..c3eae04b6 100644 --- a/docs/sdk/lib-ethers.ethersliquity.adjusttrove.md +++ b/docs/sdk/lib-ethers.ethersliquity.adjusttrove.md @@ -9,7 +9,7 @@ Adjust existing Trove by changing its collateral, debt, or both. Signature: ```typescript -adjustTrove(params: TroveAdjustmentParams, maxBorrowingRate?: Decimalish, overrides?: EthersTransactionOverrides): Promise; +adjustTrove(params: TroveAdjustmentParams, maxBorrowingRateOrOptionalParams?: Decimalish | BorrowingOperationOptionalParams, overrides?: EthersTransactionOverrides): Promise; ``` ## Parameters @@ -17,7 +17,7 @@ adjustTrove(params: TroveAdjustmentParams, maxBorrowingRate?: Decima | Parameter | Type | Description | | --- | --- | --- | | params | [TroveAdjustmentParams](./lib-base.troveadjustmentparams.md)<[Decimalish](./lib-base.decimalish.md)> | Parameters of the adjustment. | -| maxBorrowingRate | [Decimalish](./lib-base.decimalish.md) | Maximum acceptable [borrowing rate](./lib-base.fees.borrowingrate.md) if params includes borrowLUSD. | +| maxBorrowingRateOrOptionalParams | [Decimalish](./lib-base.decimalish.md) \| [BorrowingOperationOptionalParams](./lib-ethers.borrowingoperationoptionalparams.md) | | | overrides | [EthersTransactionOverrides](./lib-ethers.etherstransactionoverrides.md) | | Returns: diff --git a/docs/sdk/lib-ethers.ethersliquity.md b/docs/sdk/lib-ethers.ethersliquity.md index 12e57edcf..c8a45432b 100644 --- a/docs/sdk/lib-ethers.ethersliquity.md +++ b/docs/sdk/lib-ethers.ethersliquity.md @@ -29,7 +29,7 @@ The constructor for this class is marked as internal. Third-party code should no | Method | Modifiers | Description | | --- | --- | --- | -| [adjustTrove(params, maxBorrowingRate, overrides)](./lib-ethers.ethersliquity.adjusttrove.md) | | Adjust existing Trove by changing its collateral, debt, or both. | +| [adjustTrove(params, maxBorrowingRateOrOptionalParams, overrides)](./lib-ethers.ethersliquity.adjusttrove.md) | | Adjust existing Trove by changing its collateral, debt, or both. | | [approveUniTokens(allowance, overrides)](./lib-ethers.ethersliquity.approveunitokens.md) | | Allow the liquidity mining contract to use Uniswap ETH/LUSD LP tokens for [staking](./lib-base.transactableliquity.stakeunitokens.md). | | [borrowLUSD(amount, maxBorrowingRate, overrides)](./lib-ethers.ethersliquity.borrowlusd.md) | | Adjust existing Trove by borrowing more LUSD. | | [claimCollateralSurplus(overrides)](./lib-ethers.ethersliquity.claimcollateralsurplus.md) | | Claim leftover collateral after a liquidation or redemption. | @@ -65,7 +65,7 @@ The constructor for this class is marked as internal. Third-party code should no | [hasStore(store)](./lib-ethers.ethersliquity.hasstore_1.md) | | Check whether this EthersLiquity is an [EthersLiquityWithStore](./lib-ethers.ethersliquitywithstore.md)<[BlockPolledLiquityStore](./lib-ethers.blockpolledliquitystore.md)>. | | [liquidate(address, overrides)](./lib-ethers.ethersliquity.liquidate.md) | | Liquidate one or more undercollateralized Troves. | | [liquidateUpTo(maximumNumberOfTrovesToLiquidate, overrides)](./lib-ethers.ethersliquity.liquidateupto.md) | | Liquidate the least collateralized Troves up to a maximum number. | -| [openTrove(params, maxBorrowingRate, overrides)](./lib-ethers.ethersliquity.opentrove.md) | | Open a new Trove by depositing collateral and borrowing LUSD. | +| [openTrove(params, maxBorrowingRateOrOptionalParams, overrides)](./lib-ethers.ethersliquity.opentrove.md) | | Open a new Trove by depositing collateral and borrowing LUSD. | | [redeemLUSD(amount, maxRedemptionRate, overrides)](./lib-ethers.ethersliquity.redeemlusd.md) | | Redeem LUSD to native currency (e.g. Ether) at face value. | | [registerFrontend(kickbackRate, overrides)](./lib-ethers.ethersliquity.registerfrontend.md) | | Register current wallet address as a Liquity frontend. | | [repayLUSD(amount, overrides)](./lib-ethers.ethersliquity.repaylusd.md) | | Adjust existing Trove by repaying some of its debt. | diff --git a/docs/sdk/lib-ethers.ethersliquity.opentrove.md b/docs/sdk/lib-ethers.ethersliquity.opentrove.md index c0eefd6ac..d37bbc40a 100644 --- a/docs/sdk/lib-ethers.ethersliquity.opentrove.md +++ b/docs/sdk/lib-ethers.ethersliquity.opentrove.md @@ -9,7 +9,7 @@ Open a new Trove by depositing collateral and borrowing LUSD. Signature: ```typescript -openTrove(params: TroveCreationParams, maxBorrowingRate?: Decimalish, overrides?: EthersTransactionOverrides): Promise; +openTrove(params: TroveCreationParams, maxBorrowingRateOrOptionalParams?: Decimalish | BorrowingOperationOptionalParams, overrides?: EthersTransactionOverrides): Promise; ``` ## Parameters @@ -17,7 +17,7 @@ openTrove(params: TroveCreationParams, maxBorrowingRate?: Decimalish | Parameter | Type | Description | | --- | --- | --- | | params | [TroveCreationParams](./lib-base.trovecreationparams.md)<[Decimalish](./lib-base.decimalish.md)> | How much to deposit and borrow. | -| maxBorrowingRate | [Decimalish](./lib-base.decimalish.md) | Maximum acceptable [borrowing rate](./lib-base.fees.borrowingrate.md). | +| maxBorrowingRateOrOptionalParams | [Decimalish](./lib-base.decimalish.md) \| [BorrowingOperationOptionalParams](./lib-ethers.borrowingoperationoptionalparams.md) | | | overrides | [EthersTransactionOverrides](./lib-ethers.etherstransactionoverrides.md) | | Returns: diff --git a/docs/sdk/lib-ethers.md b/docs/sdk/lib-ethers.md index 1acac6a5d..55ecab4ff 100644 --- a/docs/sdk/lib-ethers.md +++ b/docs/sdk/lib-ethers.md @@ -25,6 +25,7 @@ | Interface | Description | | --- | --- | | [BlockPolledLiquityStoreExtraState](./lib-ethers.blockpolledliquitystoreextrastate.md) | Extra state added to [LiquityStoreState](./lib-base.liquitystorestate.md) by [BlockPolledLiquityStore](./lib-ethers.blockpolledliquitystore.md). | +| [BorrowingOperationOptionalParams](./lib-ethers.borrowingoperationoptionalparams.md) | Optional parameters of a transaction that borrows LUSD. | | [EthersCallOverrides](./lib-ethers.etherscalloverrides.md) | Optional parameters taken by [ReadableEthersLiquity](./lib-ethers.readableethersliquity.md) functions. | | [EthersLiquityConnection](./lib-ethers.ethersliquityconnection.md) | Information about a connection to the Liquity protocol. | | [EthersLiquityConnectionOptionalParams](./lib-ethers.ethersliquityconnectionoptionalparams.md) | Optional parameters of [ReadableEthersLiquity.connect()](./lib-ethers.readableethersliquity.connect_1.md) and [EthersLiquity.connect()](./lib-ethers.ethersliquity.connect_1.md). | diff --git a/docs/sdk/lib-ethers.populatableethersliquity.adjusttrove.md b/docs/sdk/lib-ethers.populatableethersliquity.adjusttrove.md index 0d385c1b5..4765a66d9 100644 --- a/docs/sdk/lib-ethers.populatableethersliquity.adjusttrove.md +++ b/docs/sdk/lib-ethers.populatableethersliquity.adjusttrove.md @@ -9,7 +9,7 @@ Adjust existing Trove by changing its collateral, debt, or both. Signature: ```typescript -adjustTrove(params: TroveAdjustmentParams, maxBorrowingRate?: Decimalish, overrides?: EthersTransactionOverrides): Promise>; +adjustTrove(params: TroveAdjustmentParams, maxBorrowingRateOrOptionalParams?: Decimalish | BorrowingOperationOptionalParams, overrides?: EthersTransactionOverrides): Promise>; ``` ## Parameters @@ -17,7 +17,7 @@ adjustTrove(params: TroveAdjustmentParams, maxBorrowingRate?: Decima | Parameter | Type | Description | | --- | --- | --- | | params | [TroveAdjustmentParams](./lib-base.troveadjustmentparams.md)<[Decimalish](./lib-base.decimalish.md)> | Parameters of the adjustment. | -| maxBorrowingRate | [Decimalish](./lib-base.decimalish.md) | Maximum acceptable [borrowing rate](./lib-base.fees.borrowingrate.md) if params includes borrowLUSD. | +| maxBorrowingRateOrOptionalParams | [Decimalish](./lib-base.decimalish.md) \| [BorrowingOperationOptionalParams](./lib-ethers.borrowingoperationoptionalparams.md) | | | overrides | [EthersTransactionOverrides](./lib-ethers.etherstransactionoverrides.md) | | Returns: diff --git a/docs/sdk/lib-ethers.populatableethersliquity.md b/docs/sdk/lib-ethers.populatableethersliquity.md index facbdd17c..f2b1c08f0 100644 --- a/docs/sdk/lib-ethers.populatableethersliquity.md +++ b/docs/sdk/lib-ethers.populatableethersliquity.md @@ -23,7 +23,7 @@ export declare class PopulatableEthersLiquity implements PopulatableLiquity. | | [borrowLUSD(amount, maxBorrowingRate, overrides)](./lib-ethers.populatableethersliquity.borrowlusd.md) | | Adjust existing Trove by borrowing more LUSD. | | [claimCollateralSurplus(overrides)](./lib-ethers.populatableethersliquity.claimcollateralsurplus.md) | | Claim leftover collateral after a liquidation or redemption. | @@ -33,7 +33,7 @@ export declare class PopulatableEthersLiquity implements PopulatableLiquitySignature: ```typescript -openTrove(params: TroveCreationParams, maxBorrowingRate?: Decimalish, overrides?: EthersTransactionOverrides): Promise>; +openTrove(params: TroveCreationParams, maxBorrowingRateOrOptionalParams?: Decimalish | BorrowingOperationOptionalParams, overrides?: EthersTransactionOverrides): Promise>; ``` ## Parameters @@ -17,7 +17,7 @@ openTrove(params: TroveCreationParams, maxBorrowingRate?: Decimalish | Parameter | Type | Description | | --- | --- | --- | | params | [TroveCreationParams](./lib-base.trovecreationparams.md)<[Decimalish](./lib-base.decimalish.md)> | How much to deposit and borrow. | -| maxBorrowingRate | [Decimalish](./lib-base.decimalish.md) | Maximum acceptable [borrowing rate](./lib-base.fees.borrowingrate.md). | +| maxBorrowingRateOrOptionalParams | [Decimalish](./lib-base.decimalish.md) \| [BorrowingOperationOptionalParams](./lib-ethers.borrowingoperationoptionalparams.md) | | | overrides | [EthersTransactionOverrides](./lib-ethers.etherstransactionoverrides.md) | | Returns: diff --git a/docs/sdk/lib-ethers.populatedethersliquitytransaction.gasheadroom.md b/docs/sdk/lib-ethers.populatedethersliquitytransaction.gasheadroom.md new file mode 100644 index 000000000..ec4ba0952 --- /dev/null +++ b/docs/sdk/lib-ethers.populatedethersliquitytransaction.gasheadroom.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [@liquity/lib-ethers](./lib-ethers.md) > [PopulatedEthersLiquityTransaction](./lib-ethers.populatedethersliquitytransaction.md) > [gasHeadroom](./lib-ethers.populatedethersliquitytransaction.gasheadroom.md) + +## PopulatedEthersLiquityTransaction.gasHeadroom property + +Extra gas added to the transaction's `gasLimit` on top of the estimated minimum requirement. + +Signature: + +```typescript +readonly gasHeadroom?: number; +``` + +## Remarks + +Gas estimation is based on blockchain state at the latest block. However, most transactions stay in pending state for several blocks before being included in a block. This may increase the actual gas requirements of certain Liquity transactions by the time they are eventually mined, therefore the Liquity SDK increases these transactions' `gasLimit` by default (unless `gasLimit` is [overridden](./lib-ethers.etherstransactionoverrides.md)). + +Note: even though the SDK includes gas headroom for many transaction types, currently this property is only implemented for [openTrove()](./lib-ethers.populatableethersliquity.opentrove.md), [adjustTrove()](./lib-ethers.populatableethersliquity.adjusttrove.md) and its aliases. + diff --git a/docs/sdk/lib-ethers.populatedethersliquitytransaction.md b/docs/sdk/lib-ethers.populatedethersliquitytransaction.md index 0406599c2..0a8b6a264 100644 --- a/docs/sdk/lib-ethers.populatedethersliquitytransaction.md +++ b/docs/sdk/lib-ethers.populatedethersliquitytransaction.md @@ -23,6 +23,7 @@ The constructor for this class is marked as internal. Third-party code should no | Property | Modifiers | Type | Description | | --- | --- | --- | --- | +| [gasHeadroom?](./lib-ethers.populatedethersliquitytransaction.gasheadroom.md) | | number | (Optional) Extra gas added to the transaction's gasLimit on top of the estimated minimum requirement. | | [rawPopulatedTransaction](./lib-ethers.populatedethersliquitytransaction.rawpopulatedtransaction.md) | | [EthersPopulatedTransaction](./lib-ethers.etherspopulatedtransaction.md) | Unsigned transaction object populated by Ethers. | ## Methods diff --git a/docs/sdk/lib-ethers.sendableethersliquity.adjusttrove.md b/docs/sdk/lib-ethers.sendableethersliquity.adjusttrove.md index 68e6049c5..cb54ae15a 100644 --- a/docs/sdk/lib-ethers.sendableethersliquity.adjusttrove.md +++ b/docs/sdk/lib-ethers.sendableethersliquity.adjusttrove.md @@ -9,7 +9,7 @@ Adjust existing Trove by changing its collateral, debt, or both. Signature: ```typescript -adjustTrove(params: TroveAdjustmentParams, maxBorrowingRate?: Decimalish, overrides?: EthersTransactionOverrides): Promise>; +adjustTrove(params: TroveAdjustmentParams, maxBorrowingRateOrOptionalParams?: Decimalish | BorrowingOperationOptionalParams, overrides?: EthersTransactionOverrides): Promise>; ``` ## Parameters @@ -17,7 +17,7 @@ adjustTrove(params: TroveAdjustmentParams, maxBorrowingRate?: Decima | Parameter | Type | Description | | --- | --- | --- | | params | [TroveAdjustmentParams](./lib-base.troveadjustmentparams.md)<[Decimalish](./lib-base.decimalish.md)> | Parameters of the adjustment. | -| maxBorrowingRate | [Decimalish](./lib-base.decimalish.md) | Maximum acceptable [borrowing rate](./lib-base.fees.borrowingrate.md) if params includes borrowLUSD. | +| maxBorrowingRateOrOptionalParams | [Decimalish](./lib-base.decimalish.md) \| [BorrowingOperationOptionalParams](./lib-ethers.borrowingoperationoptionalparams.md) | | | overrides | [EthersTransactionOverrides](./lib-ethers.etherstransactionoverrides.md) | | Returns: diff --git a/docs/sdk/lib-ethers.sendableethersliquity.md b/docs/sdk/lib-ethers.sendableethersliquity.md index e73ca0010..4cbc0118e 100644 --- a/docs/sdk/lib-ethers.sendableethersliquity.md +++ b/docs/sdk/lib-ethers.sendableethersliquity.md @@ -23,7 +23,7 @@ export declare class SendableEthersLiquity implements SendableLiquity. | | [borrowLUSD(amount, maxBorrowingRate, overrides)](./lib-ethers.sendableethersliquity.borrowlusd.md) | | Adjust existing Trove by borrowing more LUSD. | | [claimCollateralSurplus(overrides)](./lib-ethers.sendableethersliquity.claimcollateralsurplus.md) | | Claim leftover collateral after a liquidation or redemption. | @@ -33,7 +33,7 @@ export declare class SendableEthersLiquity implements SendableLiquitySignature: ```typescript -openTrove(params: TroveCreationParams, maxBorrowingRate?: Decimalish, overrides?: EthersTransactionOverrides): Promise>; +openTrove(params: TroveCreationParams, maxBorrowingRateOrOptionalParams?: Decimalish | BorrowingOperationOptionalParams, overrides?: EthersTransactionOverrides): Promise>; ``` ## Parameters @@ -17,7 +17,7 @@ openTrove(params: TroveCreationParams, maxBorrowingRate?: Decimalish | Parameter | Type | Description | | --- | --- | --- | | params | [TroveCreationParams](./lib-base.trovecreationparams.md)<[Decimalish](./lib-base.decimalish.md)> | How much to deposit and borrow. | -| maxBorrowingRate | [Decimalish](./lib-base.decimalish.md) | Maximum acceptable [borrowing rate](./lib-base.fees.borrowingrate.md). | +| maxBorrowingRateOrOptionalParams | [Decimalish](./lib-base.decimalish.md) \| [BorrowingOperationOptionalParams](./lib-ethers.borrowingoperationoptionalparams.md) | | | overrides | [EthersTransactionOverrides](./lib-ethers.etherstransactionoverrides.md) | | Returns: diff --git a/packages/lib-ethers/etc/lib-ethers.api.md b/packages/lib-ethers/etc/lib-ethers.api.md index f15800358..a4a313dec 100644 --- a/packages/lib-ethers/etc/lib-ethers.api.md +++ b/packages/lib-ethers/etc/lib-ethers.api.md @@ -62,11 +62,19 @@ export class BlockPolledLiquityStore extends LiquityStore Fees; } // @public export type BlockPolledLiquityStoreState = LiquityStoreState; +// @public +export interface BorrowingOperationOptionalParams { + borrowingFeeDecayToleranceMinutes?: number; + maxBorrowingRate?: Decimalish; +} + // @internal (undocumented) export function _connectByChainId(provider: EthersProvider, signer: EthersSigner | undefined, chainId: number, optionalParams: EthersLiquityConnectionOptionalParams & { useStore: T; @@ -88,7 +96,7 @@ export class EthersLiquity implements ReadableEthersLiquity, TransactableLiquity // @internal constructor(readable: ReadableEthersLiquity); // (undocumented) - adjustTrove(params: TroveAdjustmentParams, maxBorrowingRate?: Decimalish, overrides?: EthersTransactionOverrides): Promise; + adjustTrove(params: TroveAdjustmentParams, maxBorrowingRateOrOptionalParams?: Decimalish | BorrowingOperationOptionalParams, overrides?: EthersTransactionOverrides): Promise; // (undocumented) approveUniTokens(allowance?: Decimalish, overrides?: EthersTransactionOverrides): Promise; // (undocumented) @@ -117,6 +125,8 @@ export class EthersLiquity implements ReadableEthersLiquity, TransactableLiquity static _from(connection: EthersLiquityConnection): EthersLiquity; // @internal (undocumented) _getActivePool(overrides?: EthersCallOverrides): Promise; + // @internal (undocumented) + _getBlockTimestamp(blockTag?: BlockTag): Promise; // (undocumented) getCollateralSurplusBalance(address?: string, overrides?: EthersCallOverrides): Promise; // @internal (undocumented) @@ -182,7 +192,7 @@ export class EthersLiquity implements ReadableEthersLiquity, TransactableLiquity // @internal (undocumented) _mintUniToken(amount: Decimalish, address?: string, overrides?: EthersTransactionOverrides): Promise; // (undocumented) - openTrove(params: TroveCreationParams, maxBorrowingRate?: Decimalish, overrides?: EthersTransactionOverrides): Promise; + openTrove(params: TroveCreationParams, maxBorrowingRateOrOptionalParams?: Decimalish | BorrowingOperationOptionalParams, overrides?: EthersTransactionOverrides): Promise; readonly populate: PopulatableEthersLiquity; // (undocumented) redeemLUSD(amount: Decimalish, maxRedemptionRate?: Decimalish, overrides?: EthersTransactionOverrides): Promise; @@ -321,7 +331,7 @@ export class ObservableEthersLiquity implements ObservableLiquity { export class PopulatableEthersLiquity implements PopulatableLiquity { constructor(readable: ReadableEthersLiquity); // (undocumented) - adjustTrove(params: TroveAdjustmentParams, maxBorrowingRate?: Decimalish, overrides?: EthersTransactionOverrides): Promise>; + adjustTrove(params: TroveAdjustmentParams, maxBorrowingRateOrOptionalParams?: Decimalish | BorrowingOperationOptionalParams, overrides?: EthersTransactionOverrides): Promise>; // (undocumented) approveUniTokens(allowance?: Decimalish, overrides?: EthersTransactionOverrides): Promise>; // (undocumented) @@ -343,7 +353,7 @@ export class PopulatableEthersLiquity implements PopulatableLiquity>; // (undocumented) - openTrove(params: TroveCreationParams, maxBorrowingRate?: Decimalish, overrides?: EthersTransactionOverrides): Promise>; + openTrove(params: TroveCreationParams, maxBorrowingRateOrOptionalParams?: Decimalish | BorrowingOperationOptionalParams, overrides?: EthersTransactionOverrides): Promise>; // (undocumented) redeemLUSD(amount: Decimalish, maxRedemptionRate?: Decimalish, overrides?: EthersTransactionOverrides): Promise; // (undocumented) @@ -381,7 +391,8 @@ export class PopulatableEthersLiquity implements PopulatableLiquity implements PopulatedLiquityTransaction> { // @internal - constructor(rawPopulatedTransaction: EthersPopulatedTransaction, connection: EthersLiquityConnection, parse: (rawReceipt: EthersTransactionReceipt) => T); + constructor(rawPopulatedTransaction: EthersPopulatedTransaction, connection: EthersLiquityConnection, parse: (rawReceipt: EthersTransactionReceipt) => T, gasHeadroom?: number); + readonly gasHeadroom?: number; readonly rawPopulatedTransaction: EthersPopulatedTransaction; // (undocumented) send(): Promise>; @@ -449,6 +460,8 @@ export class ReadableEthersLiquity implements ReadableLiquity { static _from(connection: EthersLiquityConnection): ReadableEthersLiquity; // @internal (undocumented) _getActivePool(overrides?: EthersCallOverrides): Promise; + // @internal (undocumented) + _getBlockTimestamp(blockTag?: BlockTag): Promise; // (undocumented) getCollateralSurplusBalance(address?: string, overrides?: EthersCallOverrides): Promise; // @internal (undocumented) @@ -521,7 +534,7 @@ export const _redeemMaxIterations = 70; export class SendableEthersLiquity implements SendableLiquity { constructor(populatable: PopulatableEthersLiquity); // (undocumented) - adjustTrove(params: TroveAdjustmentParams, maxBorrowingRate?: Decimalish, overrides?: EthersTransactionOverrides): Promise>; + adjustTrove(params: TroveAdjustmentParams, maxBorrowingRateOrOptionalParams?: Decimalish | BorrowingOperationOptionalParams, overrides?: EthersTransactionOverrides): Promise>; // (undocumented) approveUniTokens(allowance?: Decimalish, overrides?: EthersTransactionOverrides): Promise>; // (undocumented) @@ -543,7 +556,7 @@ export class SendableEthersLiquity implements SendableLiquity>; // (undocumented) - openTrove(params: TroveCreationParams, maxBorrowingRate?: Decimalish, overrides?: EthersTransactionOverrides): Promise>; + openTrove(params: TroveCreationParams, maxBorrowingRateOrOptionalParams?: Decimalish | BorrowingOperationOptionalParams, overrides?: EthersTransactionOverrides): Promise>; // (undocumented) redeemLUSD(amount: Decimalish, maxRedemptionRate?: Decimalish, overrides?: EthersTransactionOverrides): Promise>; // (undocumented) diff --git a/packages/lib-ethers/package.json b/packages/lib-ethers/package.json index ebbf24570..44ea6746a 100644 --- a/packages/lib-ethers/package.json +++ b/packages/lib-ethers/package.json @@ -28,6 +28,7 @@ "save-live-version:run": "ts-node scripts/save-live-version.ts", "save-live-version:check": "run-s check-live-version", "scrape-eth-usd": "ts-node scripts/scrape-eth-usd.ts", + "spam-troves": "ts-node scripts/spam-troves.ts", "test": "hardhat test", "test-live": "run-s test-live:*", "test-live:check-version": "run-s check-live-version", @@ -47,6 +48,7 @@ "@types/mocha": "8.2.1", "@types/node": "14.14.34", "@types/sinon-chai": "3.2.5", + "@types/ws": "7.4.4", "@typescript-eslint/eslint-plugin": "4.17.0", "@typescript-eslint/parser": "4.18.0", "chai": "4.3.4", @@ -61,6 +63,7 @@ "hardhat": "2.1.1", "npm-run-all": "4.1.5", "ts-node": "9.1.1", - "typescript": "4.1.5" + "typescript": "4.1.5", + "ws": "7.4.6" } } diff --git a/packages/lib-ethers/scripts/spam-troves.ts b/packages/lib-ethers/scripts/spam-troves.ts new file mode 100644 index 000000000..afbda677a --- /dev/null +++ b/packages/lib-ethers/scripts/spam-troves.ts @@ -0,0 +1,104 @@ +import WebSocket from "ws"; +import { TransactionResponse } from "@ethersproject/abstract-provider"; +import { JsonRpcProvider } from "@ethersproject/providers"; +import { Wallet } from "@ethersproject/wallet"; + +import { Decimal, LUSD_MINIMUM_DEBT, Trove } from "@liquity/lib-base"; +import { EthersLiquity, EthersLiquityWithStore, BlockPolledLiquityStore } from "@liquity/lib-ethers"; + +import { + Batched, + BatchedProvider, + WebSocketAugmented, + WebSocketAugmentedProvider +} from "@liquity/providers"; + +const BatchedWebSocketAugmentedJsonRpcProvider = Batched(WebSocketAugmented(JsonRpcProvider)); + +Object.assign(globalThis, { WebSocket }); + +const numberOfTrovesToCreate = 1000; +const collateralRatioStart = Decimal.from(2); +const collateralRatioStep = Decimal.from(1e-6); +const funderKey = "0x4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7"; + +let provider: BatchedProvider & WebSocketAugmentedProvider & JsonRpcProvider; +let funder: Wallet; +let liquity: EthersLiquityWithStore; + +const waitForSuccess = (tx: TransactionResponse) => + tx.wait().then(receipt => { + if (!receipt.status) { + throw new Error("Transaction failed"); + } + return receipt; + }); + +const createTrove = async (nominalCollateralRatio: Decimal) => { + const randomWallet = Wallet.createRandom().connect(provider); + + const debt = LUSD_MINIMUM_DEBT.mul(2); + const collateral = debt.mul(nominalCollateralRatio); + + await funder + .sendTransaction({ + to: randomWallet.address, + value: collateral.hex + }) + .then(waitForSuccess); + + await liquity.populate + .openTrove( + Trove.recreate(new Trove(collateral, debt), liquity.store.state.borrowingRate), + {}, + { from: randomWallet.address } + ) + .then(tx => randomWallet.signTransaction(tx.rawPopulatedTransaction)) + .then(tx => provider.sendTransaction(tx)) + .then(waitForSuccess); +}; + +const runLoop = async () => { + for (let i = 0; i < numberOfTrovesToCreate; ++i) { + const collateralRatio = collateralRatioStep.mul(i).add(collateralRatioStart); + const nominalCollateralRatio = collateralRatio.div(liquity.store.state.price); + + await createTrove(nominalCollateralRatio); + + if ((i + 1) % 10 == 0) { + console.log(`Created ${i + 1} Troves.`); + } + } +}; + +const main = async () => { + provider = new BatchedWebSocketAugmentedJsonRpcProvider(); + funder = new Wallet(funderKey, provider); + + const network = await provider.getNetwork(); + + provider.chainId = network.chainId; + provider.openWebSocket( + provider.connection.url.replace(/^http/i, "ws").replace("8545", "8546"), + network + ); + + liquity = await EthersLiquity.connect(provider, { useStore: "blockPolled" }); + + let stopStore: () => void; + + return new Promise(resolve => { + liquity.store.onLoaded = resolve; + stopStore = liquity.store.start(); + }) + .then(runLoop) + .then(() => { + stopStore(); + provider.closeWebSocket(); + }); +}; + +main().catch(err => { + console.error(err); + process.exit(1); +}); diff --git a/packages/lib-ethers/src/BlockPolledLiquityStore.ts b/packages/lib-ethers/src/BlockPolledLiquityStore.ts index 265eb56d1..33aa03cd3 100644 --- a/packages/lib-ethers/src/BlockPolledLiquityStore.ts +++ b/packages/lib-ethers/src/BlockPolledLiquityStore.ts @@ -1,4 +1,3 @@ -import { BigNumber } from "@ethersproject/bignumber"; import { AddressZero } from "@ethersproject/constants"; import { @@ -8,15 +7,13 @@ import { TroveWithPendingRedistribution, StabilityDeposit, LQTYStake, - LiquityStore + LiquityStore, + Fees } from "@liquity/lib-base"; +import { decimalify, promiseAllValues } from "./_utils"; import { ReadableEthersLiquity } from "./ReadableEthersLiquity"; -import { - EthersLiquityConnection, - _getBlockTimestamp, - _getProvider -} from "./EthersLiquityConnection"; +import { EthersLiquityConnection, _getProvider } from "./EthersLiquityConnection"; import { EthersCallOverrides, EthersProvider } from "./types"; /** @@ -38,6 +35,9 @@ export interface BlockPolledLiquityStoreExtraState { * Timestamp of latest block (number of seconds since epoch). */ blockTimestamp: number; + + /** @internal */ + _feesFactory: (blockTimestamp: number, recoveryMode: boolean) => Fees; } /** @@ -48,19 +48,6 @@ export interface BlockPolledLiquityStoreExtraState { */ export type BlockPolledLiquityStoreState = LiquityStoreState; -type Resolved = T extends Promise ? U : T; -type ResolvedValues = { [P in keyof T]: Resolved }; - -const promiseAllValues = (object: T) => { - const keys = Object.keys(object); - - return Promise.all(Object.values(object)).then(values => - Object.fromEntries(values.map((value, i) => [keys[i], value])) - ) as Promise>; -}; - -const decimalify = (bigNumber: BigNumber) => Decimal.fromBigNumberString(bigNumber.toHexString()); - /** * Ethers-based {@link @liquity/lib-base#LiquityStore} that updates state whenever there's a new * block. @@ -103,12 +90,12 @@ export class BlockPolledLiquityStore extends LiquityStore { + return this._readable._getBlockTimestamp(blockTag); + } + /** @internal */ _getFeesFactory( overrides?: EthersCallOverrides @@ -310,10 +321,12 @@ export class EthersLiquity implements ReadableEthersLiquity, TransactableLiquity */ openTrove( params: TroveCreationParams, - maxBorrowingRate?: Decimalish, + maxBorrowingRateOrOptionalParams?: Decimalish | BorrowingOperationOptionalParams, overrides?: EthersTransactionOverrides ): Promise { - return this.send.openTrove(params, maxBorrowingRate, overrides).then(waitForSuccess); + return this.send + .openTrove(params, maxBorrowingRateOrOptionalParams, overrides) + .then(waitForSuccess); } /** @@ -336,10 +349,12 @@ export class EthersLiquity implements ReadableEthersLiquity, TransactableLiquity */ adjustTrove( params: TroveAdjustmentParams, - maxBorrowingRate?: Decimalish, + maxBorrowingRateOrOptionalParams?: Decimalish | BorrowingOperationOptionalParams, overrides?: EthersTransactionOverrides ): Promise { - return this.send.adjustTrove(params, maxBorrowingRate, overrides).then(waitForSuccess); + return this.send + .adjustTrove(params, maxBorrowingRateOrOptionalParams, overrides) + .then(waitForSuccess); } /** diff --git a/packages/lib-ethers/src/EthersLiquityConnection.ts b/packages/lib-ethers/src/EthersLiquityConnection.ts index 641dc50e3..d6cf89cf0 100644 --- a/packages/lib-ethers/src/EthersLiquityConnection.ts +++ b/packages/lib-ethers/src/EthersLiquityConnection.ts @@ -1,4 +1,3 @@ -import { BigNumber } from "@ethersproject/bignumber"; import { Block, BlockTag } from "@ethersproject/abstract-provider"; import { Signer } from "@ethersproject/abstract-signer"; @@ -11,6 +10,7 @@ import rinkeby from "../deployments/rinkeby.json"; import ropsten from "../deployments/ropsten.json"; import mainnet from "../deployments/mainnet.json"; +import { numberify, panic } from "./_utils"; import { EthersProvider, EthersSigner } from "./types"; import { @@ -139,8 +139,6 @@ export const _getContracts = (connection: EthersLiquityConnection): _LiquityCont const getMulticall = (connection: EthersLiquityConnection): _Multicall | undefined => (connection as _InternalEthersLiquityConnection)._multicall; -const numberify = (bigNumber: BigNumber) => bigNumber.toNumber(); - const getTimestampFromBlock = ({ timestamp }: Block) => timestamp; /** @internal */ @@ -152,10 +150,6 @@ export const _getBlockTimestamp = ( getMulticall(connection)?.getCurrentBlockTimestamp({ blockTag }).then(numberify) ?? _getProvider(connection).getBlock(blockTag).then(getTimestampFromBlock); -const panic = (e: unknown): T => { - throw e; -}; - /** @internal */ export const _requireSigner = (connection: EthersLiquityConnection): EthersSigner => connection.signer ?? panic(new Error("Must be connected through a Signer")); diff --git a/packages/lib-ethers/src/PopulatableEthersLiquity.ts b/packages/lib-ethers/src/PopulatableEthersLiquity.ts index 9df9c596e..3e5efbf85 100644 --- a/packages/lib-ethers/src/PopulatableEthersLiquity.ts +++ b/packages/lib-ethers/src/PopulatableEthersLiquity.ts @@ -12,6 +12,7 @@ import { Decimalish, LiquidationDetails, LiquityReceipt, + LUSD_MINIMUM_DEBT, LUSD_MINIMUM_NET_DEBT, MinedReceipt, PopulatableLiquity, @@ -49,11 +50,12 @@ import { _requireSigner } from "./EthersLiquityConnection"; +import { decimalify, promiseAllValues } from "./_utils"; import { _priceFeedIsTestnet, _uniTokenIsMock } from "./contracts"; import { logsToString } from "./parseLogs"; import { ReadableEthersLiquity } from "./ReadableEthersLiquity"; -const decimalify = (bigNumber: BigNumber) => Decimal.fromBigNumberString(bigNumber.toHexString()); +const bigNumberMax = (a: BigNumber, b?: BigNumber) => (b?.gt(a) ? b : a); // With 70 iterations redemption costs about ~10M gas, and each iteration accounts for ~138k more /** @internal */ @@ -61,6 +63,7 @@ export const _redeemMaxIterations = 70; const defaultBorrowingRateSlippageTolerance = Decimal.from(0.005); // 0.5% const defaultRedemptionRateSlippageTolerance = Decimal.from(0.001); // 0.1% +const defaultBorrowingFeeDecayToleranceMinutes = 10; const noDetails = () => undefined; @@ -261,6 +264,75 @@ export class SentEthersLiquityTransaction } } +/** + * Optional parameters of a transaction that borrows LUSD. + * + * @public + */ +export interface BorrowingOperationOptionalParams { + /** + * Maximum acceptable {@link @liquity/lib-base#Fees.borrowingRate | borrowing rate} + * (default: current borrowing rate plus 0.5%). + */ + maxBorrowingRate?: Decimalish; + + /** + * Control the amount of extra gas included attached to the transaction. + * + * @remarks + * Transactions that borrow LUSD must pay a variable borrowing fee, which is added to the Trove's + * debt. This fee increases whenever a redemption occurs, and otherwise decays exponentially. + * Due to this decay, a Trove's collateral ratio can end up being higher than initially calculated + * if the transaction is pending for a long time. When this happens, the backend has to iterate + * over the sorted list of Troves to find a new position for the Trove, which costs extra gas. + * + * The SDK can estimate how much the gas costs of the transaction may increase due to this decay, + * and can include additional gas to ensure that it will still succeed, even if it ends up pending + * for a relatively long time. This parameter specifies the length of time that should be covered + * by the extra gas. + * + * Default: 60 minutes. + */ + borrowingFeeDecayToleranceMinutes?: number; +} + +const normalizeBorrowingOperationOptionalParams = ( + maxBorrowingRateOrOptionalParams: Decimalish | BorrowingOperationOptionalParams | undefined, + currentBorrowingRate: Decimal | undefined +): { + maxBorrowingRate: Decimal; + borrowingFeeDecayToleranceMinutes: number; +} => { + if (maxBorrowingRateOrOptionalParams === undefined) { + return { + maxBorrowingRate: + currentBorrowingRate?.add(defaultBorrowingRateSlippageTolerance) ?? Decimal.ZERO, + borrowingFeeDecayToleranceMinutes: defaultBorrowingFeeDecayToleranceMinutes + }; + } else if ( + typeof maxBorrowingRateOrOptionalParams === "number" || + typeof maxBorrowingRateOrOptionalParams === "string" || + maxBorrowingRateOrOptionalParams instanceof Decimal + ) { + return { + maxBorrowingRate: Decimal.from(maxBorrowingRateOrOptionalParams), + borrowingFeeDecayToleranceMinutes: defaultBorrowingFeeDecayToleranceMinutes + }; + } else { + const { maxBorrowingRate, borrowingFeeDecayToleranceMinutes } = maxBorrowingRateOrOptionalParams; + + return { + maxBorrowingRate: + maxBorrowingRate !== undefined + ? Decimal.from(maxBorrowingRate) + : currentBorrowingRate?.add(defaultBorrowingRateSlippageTolerance) ?? Decimal.ZERO, + + borrowingFeeDecayToleranceMinutes: + borrowingFeeDecayToleranceMinutes ?? defaultBorrowingFeeDecayToleranceMinutes + }; + } +}; + /** * A transaction that has been prepared for sending. * @@ -275,6 +347,22 @@ export class PopulatedEthersLiquityTransaction /** Unsigned transaction object populated by Ethers. */ readonly rawPopulatedTransaction: EthersPopulatedTransaction; + /** + * Extra gas added to the transaction's `gasLimit` on top of the estimated minimum requirement. + * + * @remarks + * Gas estimation is based on blockchain state at the latest block. However, most transactions + * stay in pending state for several blocks before being included in a block. This may increase + * the actual gas requirements of certain Liquity transactions by the time they are eventually + * mined, therefore the Liquity SDK increases these transactions' `gasLimit` by default (unless + * `gasLimit` is {@link EthersTransactionOverrides | overridden}). + * + * Note: even though the SDK includes gas headroom for many transaction types, currently this + * property is only implemented for {@link PopulatableEthersLiquity.openTrove | openTrove()}, + * {@link PopulatableEthersLiquity.adjustTrove | adjustTrove()} and its aliases. + */ + readonly gasHeadroom?: number; + private readonly _connection: EthersLiquityConnection; private readonly _parse: (rawReceipt: EthersTransactionReceipt) => T; @@ -282,11 +370,16 @@ export class PopulatedEthersLiquityTransaction constructor( rawPopulatedTransaction: EthersPopulatedTransaction, connection: EthersLiquityConnection, - parse: (rawReceipt: EthersTransactionReceipt) => T + parse: (rawReceipt: EthersTransactionReceipt) => T, + gasHeadroom?: number ) { this.rawPopulatedTransaction = rawPopulatedTransaction; this._connection = connection; this._parse = parse; + + if (gasHeadroom !== undefined) { + this.gasHeadroom = gasHeadroom; + } } /** {@inheritDoc @liquity/lib-base#PopulatedLiquityTransaction.send} */ @@ -410,7 +503,8 @@ export class PopulatableEthersLiquity private _wrapTroveChangeWithFees( params: T, - rawPopulatedTransaction: EthersPopulatedTransaction + rawPopulatedTransaction: EthersPopulatedTransaction, + gasHeadroom?: number ): PopulatedEthersLiquityTransaction<_TroveChangeWithFees> { const { borrowerOperations } = _getContracts(this._readable.connection); @@ -432,7 +526,9 @@ export class PopulatableEthersLiquity newTrove, fee }; - } + }, + + gasHeadroom ); } @@ -685,32 +781,79 @@ export class PopulatableEthersLiquity /** {@inheritDoc @liquity/lib-base#PopulatableLiquity.openTrove} */ async openTrove( params: TroveCreationParams, - maxBorrowingRate?: Decimalish, + maxBorrowingRateOrOptionalParams?: Decimalish | BorrowingOperationOptionalParams, overrides?: EthersTransactionOverrides ): Promise> { const { borrowerOperations } = _getContracts(this._readable.connection); - const normalized = _normalizeTroveCreation(params); - const { depositCollateral, borrowLUSD } = normalized; + const normalizedParams = _normalizeTroveCreation(params); + const { depositCollateral, borrowLUSD } = normalizedParams; - const fees = await this._readable.getFees(); - const borrowingRate = fees.borrowingRate(); - const newTrove = Trove.create(normalized, borrowingRate); + const [fees, blockTimestamp, total, price] = await Promise.all([ + this._readable._getFeesFactory(), + this._readable._getBlockTimestamp(), + this._readable.getTotal(), + this._readable.getPrice() + ]); + + const recoveryMode = total.collateralRatioIsBelowCritical(price); + + const decayBorrowingRate = (seconds: number) => + fees(blockTimestamp + seconds, recoveryMode).borrowingRate(); + + const currentBorrowingRate = decayBorrowingRate(0); + const newTrove = Trove.create(normalizedParams, currentBorrowingRate); + const hints = await this._findHints(newTrove); + + const { + maxBorrowingRate, + borrowingFeeDecayToleranceMinutes + } = normalizeBorrowingOperationOptionalParams( + maxBorrowingRateOrOptionalParams, + currentBorrowingRate + ); + + const txParams = (borrowLUSD: Decimal): Parameters => [ + maxBorrowingRate.hex, + borrowLUSD.hex, + ...hints, + { value: depositCollateral.hex, ...overrides } + ]; + + let gasHeadroom: number | undefined; - maxBorrowingRate = - maxBorrowingRate !== undefined - ? Decimal.from(maxBorrowingRate) - : borrowingRate.add(defaultBorrowingRateSlippageTolerance); + if (overrides?.gasLimit === undefined) { + const decayedBorrowingRate = decayBorrowingRate(60 * borrowingFeeDecayToleranceMinutes); + const decayedTrove = Trove.create(normalizedParams, decayedBorrowingRate); + const { borrowLUSD: borrowLUSDSimulatingDecay } = Trove.recreate( + decayedTrove, + currentBorrowingRate + ); + + if (decayedTrove.debt.lt(LUSD_MINIMUM_DEBT)) { + throw new Error( + `Trove's debt might fall below ${LUSD_MINIMUM_DEBT} ` + + `within ${borrowingFeeDecayToleranceMinutes} minutes` + ); + } + + const [gasNow, gasLater] = await Promise.all([ + borrowerOperations.estimateGas.openTrove(...txParams(borrowLUSD)), + borrowerOperations.estimateGas.openTrove(...txParams(borrowLUSDSimulatingDecay)) + ]); + + const gasLimit = addGasForPotentialLastFeeOperationTimeUpdate( + bigNumberMax(addGasForPotentialListTraversal(gasNow), gasLater) + ); + + gasHeadroom = gasLimit.sub(gasNow).toNumber(); + overrides = { ...overrides, gasLimit }; + } return this._wrapTroveChangeWithFees( - normalized, - await borrowerOperations.estimateAndPopulate.openTrove( - { value: depositCollateral.hex, ...overrides }, - compose(addGasForPotentialLastFeeOperationTimeUpdate, addGasForPotentialListTraversal), - maxBorrowingRate.hex, - borrowLUSD.hex, - ...(await this._findHints(newTrove)) - ) + normalizedParams, + await borrowerOperations.populateTransaction.openTrove(...txParams(borrowLUSD)), + gasHeadroom ); } @@ -761,42 +904,90 @@ export class PopulatableEthersLiquity /** {@inheritDoc @liquity/lib-base#PopulatableLiquity.adjustTrove} */ async adjustTrove( params: TroveAdjustmentParams, - maxBorrowingRate?: Decimalish, + maxBorrowingRateOrOptionalParams?: Decimalish | BorrowingOperationOptionalParams, overrides?: EthersTransactionOverrides ): Promise> { const address = _requireAddress(this._readable.connection, overrides); const { borrowerOperations } = _getContracts(this._readable.connection); - const normalized = _normalizeTroveAdjustment(params); - const { depositCollateral, withdrawCollateral, borrowLUSD, repayLUSD } = normalized; + const normalizedParams = _normalizeTroveAdjustment(params); + const { depositCollateral, withdrawCollateral, borrowLUSD, repayLUSD } = normalizedParams; - const [trove, fees] = await Promise.all([ + const [trove, feeVars] = await Promise.all([ this._readable.getTrove(address), - borrowLUSD && this._readable.getFees() + borrowLUSD && + promiseAllValues({ + fees: this._readable._getFeesFactory(), + blockTimestamp: this._readable._getBlockTimestamp(), + total: this._readable.getTotal(), + price: this._readable.getPrice() + }) ]); - const borrowingRate = fees?.borrowingRate(); - const finalTrove = trove.adjust(normalized, borrowingRate); + const decayBorrowingRate = (seconds: number) => + feeVars + ?.fees( + feeVars.blockTimestamp + seconds, + feeVars.total.collateralRatioIsBelowCritical(feeVars.price) + ) + .borrowingRate(); + + const currentBorrowingRate = decayBorrowingRate(0); + const adjustedTrove = trove.adjust(normalizedParams, currentBorrowingRate); + const hints = await this._findHints(adjustedTrove); + + const { + maxBorrowingRate, + borrowingFeeDecayToleranceMinutes + } = normalizeBorrowingOperationOptionalParams( + maxBorrowingRateOrOptionalParams, + currentBorrowingRate + ); - maxBorrowingRate = - maxBorrowingRate !== undefined - ? Decimal.from(maxBorrowingRate) - : borrowingRate?.add(defaultBorrowingRateSlippageTolerance) ?? Decimal.ZERO; + const txParams = (borrowLUSD?: Decimal): Parameters => [ + maxBorrowingRate.hex, + (withdrawCollateral ?? Decimal.ZERO).hex, + (borrowLUSD ?? repayLUSD ?? Decimal.ZERO).hex, + !!borrowLUSD, + ...hints, + { value: depositCollateral?.hex, ...overrides } + ]; + + let gasHeadroom: number | undefined; + + if (overrides?.gasLimit === undefined) { + const decayedBorrowingRate = decayBorrowingRate(60 * borrowingFeeDecayToleranceMinutes); + const decayedTrove = trove.adjust(normalizedParams, decayedBorrowingRate); + const { borrowLUSD: borrowLUSDSimulatingDecay } = trove.adjustTo( + decayedTrove, + currentBorrowingRate + ); + + if (decayedTrove.debt.lt(LUSD_MINIMUM_DEBT)) { + throw new Error( + `Trove's debt might fall below ${LUSD_MINIMUM_DEBT} ` + + `within ${borrowingFeeDecayToleranceMinutes} minutes` + ); + } + + const [gasNow, gasLater] = await Promise.all([ + borrowerOperations.estimateGas.adjustTrove(...txParams(borrowLUSD)), + borrowLUSD && + borrowerOperations.estimateGas.adjustTrove(...txParams(borrowLUSDSimulatingDecay)) + ]); + + const gasLimit = (borrowLUSD ? addGasForPotentialLastFeeOperationTimeUpdate : id)( + bigNumberMax(addGasForPotentialListTraversal(gasNow), gasLater) + ); + + gasHeadroom = gasLimit.sub(gasNow).toNumber(); + overrides = { ...overrides, gasLimit }; + } return this._wrapTroveChangeWithFees( - normalized, - await borrowerOperations.estimateAndPopulate.adjustTrove( - { value: depositCollateral?.hex, ...overrides }, - compose( - borrowLUSD ? addGasForPotentialLastFeeOperationTimeUpdate : id, - addGasForPotentialListTraversal - ), - maxBorrowingRate.hex, - (withdrawCollateral ?? Decimal.ZERO).hex, - (borrowLUSD ?? repayLUSD ?? Decimal.ZERO).hex, - !!borrowLUSD, - ...(await this._findHints(finalTrove)) - ) + normalizedParams, + await borrowerOperations.populateTransaction.adjustTrove(...txParams(borrowLUSD)), + gasHeadroom ); } diff --git a/packages/lib-ethers/src/ReadableEthersLiquity.ts b/packages/lib-ethers/src/ReadableEthersLiquity.ts index 7aa6e8f6d..4d1a927f2 100644 --- a/packages/lib-ethers/src/ReadableEthersLiquity.ts +++ b/packages/lib-ethers/src/ReadableEthersLiquity.ts @@ -1,4 +1,4 @@ -import { BigNumber } from "@ethersproject/bignumber"; +import { BlockTag } from "@ethersproject/abstract-provider"; import { Decimal, @@ -17,6 +17,7 @@ import { import { MultiTroveGetter } from "../types"; +import { decimalify, numberify, panic } from "./_utils"; import { EthersCallOverrides, EthersProvider, EthersSigner } from "./types"; import { @@ -46,10 +47,6 @@ enum BackendTroveStatus { closedByRedemption } -const panic = (error: Error): T => { - throw error; -}; - const userTroveStatusFrom = (backendStatus: BackendTroveStatus): UserTroveStatus => backendStatus === BackendTroveStatus.nonExistent ? "nonExistent" @@ -63,8 +60,6 @@ const userTroveStatusFrom = (backendStatus: BackendTroveStatus): UserTroveStatus ? "closedByRedemption" : panic(new Error(`invalid backendStatus ${backendStatus}`)); -const decimalify = (bigNumber: BigNumber) => Decimal.fromBigNumberString(bigNumber.toHexString()); -const numberify = (bigNumber: BigNumber) => bigNumber.toNumber(); const convertToDate = (timestamp: number) => new Date(timestamp * 1000); const validSortingOptions = ["ascendingCollateralRatio", "descendingCollateralRatio"]; @@ -354,7 +349,7 @@ export class ReadableEthersLiquity implements ReadableLiquity { async getRemainingLiquidityMiningLQTYReward(overrides?: EthersCallOverrides): Promise { const [calculateRemainingLQTY, blockTimestamp] = await Promise.all([ this._getRemainingLiquidityMiningLQTYRewardCalculator(overrides), - _getBlockTimestamp(this.connection, overrides?.blockTag) + this._getBlockTimestamp(overrides?.blockTag) ]); return calculateRemainingLQTY(blockTimestamp); @@ -435,6 +430,11 @@ export class ReadableEthersLiquity implements ReadableLiquity { } } + /** @internal */ + _getBlockTimestamp(blockTag?: BlockTag): Promise { + return _getBlockTimestamp(this.connection, blockTag); + } + /** @internal */ async _getFeesFactory( overrides?: EthersCallOverrides @@ -463,7 +463,7 @@ export class ReadableEthersLiquity implements ReadableLiquity { this._getFeesFactory(overrides), this.getTotal(overrides), this.getPrice(overrides), - _getBlockTimestamp(this.connection, overrides?.blockTag) + this._getBlockTimestamp(overrides?.blockTag) ]); return createFees(blockTimestamp, total.collateralRatioIsBelowCritical(price)); @@ -695,6 +695,20 @@ class _BlockPolledReadableEthersLiquity : this._readable.getCollateralSurplusBalance(address, overrides); } + async _getBlockTimestamp(blockTag?: BlockTag): Promise { + return this._blockHit({ blockTag }) + ? this.store.state.blockTimestamp + : this._readable._getBlockTimestamp(blockTag); + } + + async _getFeesFactory( + overrides?: EthersCallOverrides + ): Promise<(blockTimestamp: number, recoveryMode: boolean) => Fees> { + return this._blockHit(overrides) + ? this.store.state._feesFactory + : this._readable._getFeesFactory(overrides); + } + async getFees(overrides?: EthersCallOverrides): Promise { return this._blockHit(overrides) ? this.store.state.fees : this._readable.getFees(overrides); } @@ -739,10 +753,6 @@ class _BlockPolledReadableEthersLiquity throw new Error("Method not implemented."); } - _getFeesFactory(): Promise<(blockTimestamp: number, recoveryMode: boolean) => Fees> { - throw new Error("Method not implemented."); - } - _getRemainingLiquidityMiningLQTYRewardCalculator(): Promise<(blockTimestamp: number) => Decimal> { throw new Error("Method not implemented."); } diff --git a/packages/lib-ethers/src/SendableEthersLiquity.ts b/packages/lib-ethers/src/SendableEthersLiquity.ts index 1cc0d6522..225632632 100644 --- a/packages/lib-ethers/src/SendableEthersLiquity.ts +++ b/packages/lib-ethers/src/SendableEthersLiquity.ts @@ -20,6 +20,7 @@ import { } from "./types"; import { + BorrowingOperationOptionalParams, PopulatableEthersLiquity, PopulatedEthersLiquityTransaction, SentEthersLiquityTransaction @@ -41,12 +42,14 @@ export class SendableEthersLiquity } /** {@inheritDoc @liquity/lib-base#SendableLiquity.openTrove} */ - openTrove( + async openTrove( params: TroveCreationParams, - maxBorrowingRate?: Decimalish, + maxBorrowingRateOrOptionalParams?: Decimalish | BorrowingOperationOptionalParams, overrides?: EthersTransactionOverrides ): Promise> { - return this._populate.openTrove(params, maxBorrowingRate, overrides).then(sendTransaction); + return this._populate + .openTrove(params, maxBorrowingRateOrOptionalParams, overrides) + .then(sendTransaction); } /** {@inheritDoc @liquity/lib-base#SendableLiquity.closeTrove} */ @@ -59,10 +62,12 @@ export class SendableEthersLiquity /** {@inheritDoc @liquity/lib-base#SendableLiquity.adjustTrove} */ adjustTrove( params: TroveAdjustmentParams, - maxBorrowingRate?: Decimalish, + maxBorrowingRateOrOptionalParams?: Decimalish | BorrowingOperationOptionalParams, overrides?: EthersTransactionOverrides ): Promise> { - return this._populate.adjustTrove(params, maxBorrowingRate, overrides).then(sendTransaction); + return this._populate + .adjustTrove(params, maxBorrowingRateOrOptionalParams, overrides) + .then(sendTransaction); } /** {@inheritDoc @liquity/lib-base#SendableLiquity.depositCollateral} */ diff --git a/packages/lib-ethers/src/_utils.ts b/packages/lib-ethers/src/_utils.ts new file mode 100644 index 000000000..4e4cbf8c0 --- /dev/null +++ b/packages/lib-ethers/src/_utils.ts @@ -0,0 +1,23 @@ +import { BigNumber } from "@ethersproject/bignumber"; + +import { Decimal } from "@liquity/lib-base"; + +export const numberify = (bigNumber: BigNumber): number => bigNumber.toNumber(); + +export const decimalify = (bigNumber: BigNumber): Decimal => + Decimal.fromBigNumberString(bigNumber.toHexString()); + +export const panic = (e: unknown): T => { + throw e; +}; + +export type Resolved = T extends Promise ? U : T; +export type ResolvedValues = { [P in keyof T]: Resolved }; + +export const promiseAllValues = (object: T): Promise> => { + const keys = Object.keys(object); + + return Promise.all(Object.values(object)).then(values => + Object.fromEntries(values.map((value, i) => [keys[i], value])) + ) as Promise>; +}; diff --git a/packages/lib-ethers/src/contracts.ts b/packages/lib-ethers/src/contracts.ts index 55cf63231..25a23685b 100644 --- a/packages/lib-ethers/src/contracts.ts +++ b/packages/lib-ethers/src/contracts.ts @@ -97,6 +97,18 @@ type TypedContract = _TypeSafeContract & : never; }; + readonly estimateGas: { + [P in keyof V]: V[P] extends (...args: infer A) => unknown + ? (...args: A) => Promise + : never; + }; + + readonly populateTransaction: { + [P in keyof V]: V[P] extends (...args: infer A) => unknown + ? (...args: A) => Promise + : never; + }; + readonly estimateAndPopulate: { [P in keyof V]: V[P] extends (...args: [...infer A, infer O | undefined]) => unknown ? EstimatedContractFunction diff --git a/packages/lib-ethers/test/Liquity.test.ts b/packages/lib-ethers/test/Liquity.test.ts index cd6412ff1..262fcbc60 100644 --- a/packages/lib-ethers/test/Liquity.test.ts +++ b/packages/lib-ethers/test/Liquity.test.ts @@ -175,8 +175,11 @@ describe("EthersLiquity", () => { ]; const borrowerOperations = { - estimateAndPopulate: { - openTrove: () => ({}) + estimateGas: { + openTrove: () => Promise.resolve(BigNumber.from(1)) + }, + populateTransaction: { + openTrove: () => Promise.resolve({}) } }; @@ -190,7 +193,11 @@ describe("EthersLiquity", () => { const fakeLiquity = new PopulatableEthersLiquity(({ getNumberOfTroves: () => Promise.resolve(1000000), - getFees: () => Promise.resolve(new Fees(0, 0.99, 1, new Date(), new Date(), false)), + getTotal: () => Promise.resolve(new Trove(Decimal.from(10), Decimal.ONE)), + getPrice: () => Promise.resolve(Decimal.ONE), + _getBlockTimestamp: () => Promise.resolve(0), + _getFeesFactory: () => + Promise.resolve(() => new Fees(0, 0.99, 1, new Date(), new Date(), false)), connection: { signerOrProvider: user, diff --git a/yarn.lock b/yarn.lock index c09d9d5f2..a4cacb646 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4156,6 +4156,13 @@ "@types/webpack-sources" "*" source-map "^0.6.0" +"@types/ws@7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.4.4.tgz#93e1e00824c1de2608c30e6de4303ab3b4c0c9bc" + integrity sha512-d/7W23JAXPodQNbOZNXvl2K+bqAQrCMwlh/nuQsPSQk6Fq0opHoPrUw43aHsvSbIiQPr8Of2hkFbnz1XBFVyZQ== + dependencies: + "@types/node" "*" + "@types/yargs-parser@*": version "20.2.0" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.0.tgz#dd3e6699ba3237f0348cd085e4698780204842f9" @@ -21812,6 +21819,11 @@ ws@7.4.6: resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== +ws@7.4.6: + version "7.4.6" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" + integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== + ws@^3.0.0: version "3.3.3" resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2" From d8ed41c9180b9d5fc1bfa601aba30ab67512be3c Mon Sep 17 00:00:00 2001 From: Daniel Simon Date: Mon, 14 Jun 2021 11:20:56 +0700 Subject: [PATCH 50/58] feat: show a warning for TXs with a lot of gas headroom --- .../src/components/Trove/Adjusting.tsx | 27 ++++-- .../Trove/ExpensiveTroveChangeWarning.tsx | 86 +++++++++++++++++++ .../src/components/Trove/Opening.tsx | 25 +++++- .../src/components/Trove/TroveAction.tsx | 14 ++- .../src/components/Trove/TroveManager.tsx | 1 + .../dev-frontend/src/components/Warning.tsx | 27 ++++++ .../src/hooks/useStableTroveChange.ts | 32 +++++++ 7 files changed, 200 insertions(+), 12 deletions(-) create mode 100644 packages/dev-frontend/src/components/Trove/ExpensiveTroveChangeWarning.tsx create mode 100644 packages/dev-frontend/src/components/Warning.tsx create mode 100644 packages/dev-frontend/src/hooks/useStableTroveChange.ts diff --git a/packages/dev-frontend/src/components/Trove/Adjusting.tsx b/packages/dev-frontend/src/components/Trove/Adjusting.tsx index c6ac78fb0..b6d0f8bc3 100644 --- a/packages/dev-frontend/src/components/Trove/Adjusting.tsx +++ b/packages/dev-frontend/src/components/Trove/Adjusting.tsx @@ -9,6 +9,8 @@ import { Difference } from "@liquity/lib-base"; import { useLiquitySelector } from "@liquity/lib-react"; + +import { useStableTroveChange } from "../../hooks/useStableTroveChange"; import { ActionDescription } from "../ActionDescription"; import { useMyTransactionState } from "../Transaction"; import { TroveAction } from "./TroveAction"; @@ -19,6 +21,7 @@ import { InfoIcon } from "../InfoIcon"; import { LoadingOverlay } from "../LoadingOverlay"; import { CollateralRatio } from "./CollateralRatio"; import { EditableRow, StaticRow } from "./Editor"; +import { ExpensiveTroveChangeWarning, GasEstimationState } from "./ExpensiveTroveChangeWarning"; import { selectForTroveChangeValidation, validateTroveChange @@ -118,10 +121,6 @@ export const Adjusting: React.FC = () => { setNetDebt(trove.netDebt); }, [trove.collateral, trove.netDebt]); - if (trove.status !== "open") { - return null; - } - const isDirty = !collateral.eq(trove.collateral) || !netDebt.eq(trove.netDebt); const isDebtIncrease = netDebt.gt(trove.netDebt); const debtIncreaseAmount = isDebtIncrease ? netDebt.sub(trove.netDebt) : Decimal.ZERO; @@ -147,10 +146,17 @@ export const Adjusting: React.FC = () => { validationContext ); + const stableTroveChange = useStableTroveChange(troveChange); + const [gasEstimationState, setGasEstimationState] = useState({ type: "idle" }); + const isTransactionPending = transactionState.type === "waitingForApproval" || transactionState.type === "waitingForConfirmation"; + if (trove.status !== "open") { + return null; + } + return ( @@ -252,16 +258,25 @@ export const Adjusting: React.FC = () => { )} + + - {troveChange ? ( + {stableTroveChange ? ( Confirm diff --git a/packages/dev-frontend/src/components/Trove/ExpensiveTroveChangeWarning.tsx b/packages/dev-frontend/src/components/Trove/ExpensiveTroveChangeWarning.tsx new file mode 100644 index 000000000..0e3a70d1e --- /dev/null +++ b/packages/dev-frontend/src/components/Trove/ExpensiveTroveChangeWarning.tsx @@ -0,0 +1,86 @@ +import React, { useEffect } from "react"; + +import { Decimal, TroveChange } from "@liquity/lib-base"; +import { PopulatedEthersLiquityTransaction } from "@liquity/lib-ethers"; + +import { useLiquity } from "../../hooks/LiquityContext"; +import { Warning } from "../Warning"; + +export type GasEstimationState = + | { type: "idle" | "inProgress" } + | { type: "complete"; populatedTx: PopulatedEthersLiquityTransaction }; + +type ExpensiveTroveChangeWarningParams = { + troveChange?: Exclude, { type: "invalidCreation" }>; + maxBorrowingRate: Decimal; + borrowingFeeDecayToleranceMinutes: number; + gasEstimationState: GasEstimationState; + setGasEstimationState: (newState: GasEstimationState) => void; +}; + +export const ExpensiveTroveChangeWarning: React.FC = ({ + troveChange, + maxBorrowingRate, + borrowingFeeDecayToleranceMinutes, + gasEstimationState, + setGasEstimationState +}) => { + const { liquity } = useLiquity(); + + useEffect(() => { + if (troveChange && troveChange.type !== "closure") { + setGasEstimationState({ type: "inProgress" }); + + let cancelled = false; + + const timeoutId = setTimeout(async () => { + const populatedTx = await (troveChange.type === "creation" + ? liquity.populate.openTrove(troveChange.params, { + maxBorrowingRate, + borrowingFeeDecayToleranceMinutes + }) + : liquity.populate.adjustTrove(troveChange.params, { + maxBorrowingRate, + borrowingFeeDecayToleranceMinutes + })); + + if (!cancelled) { + setGasEstimationState({ type: "complete", populatedTx }); + console.log( + "Estimated TX cost: " + + Decimal.from(`${populatedTx.rawPopulatedTransaction.gasLimit}`).prettify(0) + ); + } + }, 333); + + return () => { + clearTimeout(timeoutId); + cancelled = true; + }; + } else { + setGasEstimationState({ type: "idle" }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [troveChange]); + + if ( + troveChange && + gasEstimationState.type === "complete" && + gasEstimationState.populatedTx.gasHeadroom !== undefined && + gasEstimationState.populatedTx.gasHeadroom >= 200000 + ) { + return troveChange.type === "creation" ? ( + + The cost of opening a Trove in this collateral ratio range is high. It is recommended to + choose a slightly different collateral ratio. + + ) : ( + + The cost of adjusting a Trove into this collateral ratio range is high. It is recommended to + choose a slightly different collateral ratio. + + ); + } + + return null; +}; diff --git a/packages/dev-frontend/src/components/Trove/Opening.tsx b/packages/dev-frontend/src/components/Trove/Opening.tsx index b96ebf811..e05721a2d 100644 --- a/packages/dev-frontend/src/components/Trove/Opening.tsx +++ b/packages/dev-frontend/src/components/Trove/Opening.tsx @@ -1,5 +1,5 @@ import React, { useCallback, useEffect, useState } from "react"; -import { Flex, Button, Box, Card, Heading } from "theme-ui"; +import { Flex, Button, Box, Card, Heading, Spinner } from "theme-ui"; import { LiquityStoreState, Decimal, @@ -9,6 +9,8 @@ import { Percent } from "@liquity/lib-base"; import { useLiquitySelector } from "@liquity/lib-react"; + +import { useStableTroveChange } from "../../hooks/useStableTroveChange"; import { ActionDescription } from "../ActionDescription"; import { useMyTransactionState } from "../Transaction"; import { TroveAction } from "./TroveAction"; @@ -19,6 +21,7 @@ import { InfoIcon } from "../InfoIcon"; import { LoadingOverlay } from "../LoadingOverlay"; import { CollateralRatio } from "./CollateralRatio"; import { EditableRow, StaticRow } from "./Editor"; +import { ExpensiveTroveChangeWarning, GasEstimationState } from "./ExpensiveTroveChangeWarning"; import { selectForTroveChangeValidation, validateTroveChange @@ -67,6 +70,9 @@ export const Opening: React.FC = () => { validationContext ); + const stableTroveChange = useStableTroveChange(troveChange); + const [gasEstimationState, setGasEstimationState] = useState({ type: "idle" }); + const transactionState = useMyTransactionState(TRANSACTION_ID); const isTransactionPending = transactionState.type === "waitingForApproval" || @@ -188,16 +194,29 @@ export const Opening: React.FC = () => { )} + + - {troveChange ? ( + {gasEstimationState.type === "inProgress" ? ( + + ) : stableTroveChange ? ( Confirm diff --git a/packages/dev-frontend/src/components/Trove/TroveAction.tsx b/packages/dev-frontend/src/components/Trove/TroveAction.tsx index d8ba7bc5d..2e9567809 100644 --- a/packages/dev-frontend/src/components/Trove/TroveAction.tsx +++ b/packages/dev-frontend/src/components/Trove/TroveAction.tsx @@ -9,23 +9,31 @@ type TroveActionProps = { transactionId: string; change: Exclude, { type: "invalidCreation" }>; maxBorrowingRate: Decimal; + borrowingFeeDecayToleranceMinutes: number; }; export const TroveAction: React.FC = ({ children, transactionId, change, - maxBorrowingRate + maxBorrowingRate, + borrowingFeeDecayToleranceMinutes }) => { const { liquity } = useLiquity(); const [sendTransaction] = useTransactionFunction( transactionId, change.type === "creation" - ? liquity.send.openTrove.bind(liquity.send, change.params, maxBorrowingRate) + ? liquity.send.openTrove.bind(liquity.send, change.params, { + maxBorrowingRate, + borrowingFeeDecayToleranceMinutes + }) : change.type === "closure" ? liquity.send.closeTrove.bind(liquity.send) - : liquity.send.adjustTrove.bind(liquity.send, change.params, maxBorrowingRate) + : liquity.send.adjustTrove.bind(liquity.send, change.params, { + maxBorrowingRate, + borrowingFeeDecayToleranceMinutes + }) ); return ; diff --git a/packages/dev-frontend/src/components/Trove/TroveManager.tsx b/packages/dev-frontend/src/components/Trove/TroveManager.tsx index 43c91d9fc..3c5d238f8 100644 --- a/packages/dev-frontend/src/components/Trove/TroveManager.tsx +++ b/packages/dev-frontend/src/components/Trove/TroveManager.tsx @@ -235,6 +235,7 @@ export const TroveManager: React.FC = ({ collateral, debt }) transactionId={`${transactionIdPrefix}${validChange.type}`} change={validChange} maxBorrowingRate={maxBorrowingRate} + borrowingFeeDecayToleranceMinutes={60} > Confirm diff --git a/packages/dev-frontend/src/components/Warning.tsx b/packages/dev-frontend/src/components/Warning.tsx new file mode 100644 index 000000000..f1f683666 --- /dev/null +++ b/packages/dev-frontend/src/components/Warning.tsx @@ -0,0 +1,27 @@ +import { Box, Flex, Text } from "theme-ui"; + +import { Icon } from "./Icon"; + +export const Warning: React.FC = ({ children }) => ( + + + + {children} + + +); diff --git a/packages/dev-frontend/src/hooks/useStableTroveChange.ts b/packages/dev-frontend/src/hooks/useStableTroveChange.ts new file mode 100644 index 000000000..17ef8f3c4 --- /dev/null +++ b/packages/dev-frontend/src/hooks/useStableTroveChange.ts @@ -0,0 +1,32 @@ +import { useEffect, useState } from "react"; +import { Decimal, TroveChange } from "@liquity/lib-base"; + +type ValidTroveChange = Exclude, { type: "invalidCreation" }>; + +const paramsEq = (a?: Decimal, b?: Decimal) => (a && b ? a.eq(b) : !a && !b); + +const equals = (a: ValidTroveChange, b: ValidTroveChange): boolean => { + return ( + a.type === b.type && + paramsEq(a.params.borrowLUSD, b.params.borrowLUSD) && + paramsEq(a.params.repayLUSD, b.params.repayLUSD) && + paramsEq(a.params.depositCollateral, b.params.depositCollateral) && + paramsEq(a.params.withdrawCollateral, b.params.withdrawCollateral) + ); +}; + +export const useStableTroveChange = ( + troveChange: ValidTroveChange | undefined +): ValidTroveChange | undefined => { + const [stableTroveChange, setStableTroveChange] = useState(troveChange); + + useEffect(() => { + if (!!stableTroveChange !== !!troveChange) { + setStableTroveChange(troveChange); + } else if (stableTroveChange && troveChange && !equals(stableTroveChange, troveChange)) { + setStableTroveChange(troveChange); + } + }, [stableTroveChange, troveChange]); + + return stableTroveChange; +}; From f140d04ef474422156093a7d27415d85e9cf8ab2 Mon Sep 17 00:00:00 2001 From: Daniel Simon Date: Fri, 18 Jun 2021 16:36:31 +0700 Subject: [PATCH 51/58] chore: update yarn.lock --- yarn.lock | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/yarn.lock b/yarn.lock index a4cacb646..5736ad9f4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9127,7 +9127,7 @@ ethereumjs-util@^7.0.2, ethereumjs-util@^7.0.9: ethjs-util "0.1.6" rlp "^2.2.4" -ethers@5.3.1, ethers@^5.0.0, ethers@^5.0.32: +ethers@5.3.1, ethers@^5.0.32, ethers@^5.3.0: version "5.3.1" resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.3.1.tgz#1f018f0aeb651576cd84fd987a45f0b99646d761" integrity sha512-xCKmC0gsZ9gks89ZfK3B1y6LlPdvX5fxDtu9SytnpdDJR1M7pmJI+4H0AxQPMgUYr7GtQdmECLR0gWdJQ+lZYw== @@ -21819,11 +21819,6 @@ ws@7.4.6: resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== -ws@7.4.6: - version "7.4.6" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" - integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== - ws@^3.0.0: version "3.3.3" resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2" From e469beb56d7bdc775c4467a07f265692756826b8 Mon Sep 17 00:00:00 2001 From: Daniel Simon Date: Mon, 21 Jun 2021 12:48:30 +0700 Subject: [PATCH 52/58] fix: improve gas cost of hint recovery when inserting into the end of SortedTroves When trying to create a new Trove that has a lower ICR than the current lowest ICR (i.e. inserting into the tail of the list), `findInsertPosition()` returns `(addressOfLowestICRTrove, address(0))`. If we blindly pass these as hints to `openTrove()` but the transaction ends up pending for so long that the backend needs to look for a new insert position, we start the traversal in the wrong direction (from the wrong end of the list), and end up having to traverse over almost the entire list. Work around this by replacing the zero address with the other hint. Workaround for #600. --- packages/lib-ethers/src/PopulatableEthersLiquity.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/lib-ethers/src/PopulatableEthersLiquity.ts b/packages/lib-ethers/src/PopulatableEthersLiquity.ts index 3e5efbf85..d39bd6b79 100644 --- a/packages/lib-ethers/src/PopulatableEthersLiquity.ts +++ b/packages/lib-ethers/src/PopulatableEthersLiquity.ts @@ -731,7 +731,13 @@ export class PopulatableEthersLiquity const { hintAddress } = results.reduce((a, b) => (a.diff.lt(b.diff) ? a : b)); - return sortedTroves.findInsertPosition(nominalCollateralRatio.hex, hintAddress, hintAddress); + const [prev, next] = await sortedTroves.findInsertPosition( + nominalCollateralRatio.hex, + hintAddress, + hintAddress + ); + + return prev === AddressZero ? [next, next] : next === AddressZero ? [prev, prev] : [prev, next]; } private async _findHints(trove: Trove): Promise<[string, string]> { From 0b3e4c4750cb222c2e90a17828a6265ad0f9783e Mon Sep 17 00:00:00 2001 From: Daniel Simon Date: Mon, 21 Jun 2021 15:49:53 +0700 Subject: [PATCH 53/58] test: fix Dev UI smoke test failure --- packages/dev-frontend/src/App.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/dev-frontend/src/App.test.tsx b/packages/dev-frontend/src/App.test.tsx index 3b06dce92..64caefd6e 100644 --- a/packages/dev-frontend/src/App.test.tsx +++ b/packages/dev-frontend/src/App.test.tsx @@ -14,7 +14,7 @@ console.log(`${trove}`); * Just a quick and dirty testcase to prove that the approach can work in our CI pipeline. */ test("there's no smoke", async () => { - const { getByText, getAllByText, getByLabelText, findByText } = render(); + const { getByText, getByLabelText, findByText } = render(); expect(await findByText(/you can borrow lusd by opening a trove/i)).toBeInTheDocument(); @@ -24,7 +24,7 @@ test("there's no smoke", async () => { fireEvent.click(getByLabelText(/^borrow$/i)); fireEvent.change(getByLabelText(/^borrow$/i), { target: { value: `${trove.debt}` } }); - const confirmButton = getAllByText(/confirm/i)[0]; + const confirmButton = await findByText(/confirm/i); fireEvent.click(confirmButton); expect(await findByText(/adjust/i)).toBeInTheDocument(); From ab72c00fd29dd4d99632eb767d988219f73aacd8 Mon Sep 17 00:00:00 2001 From: Daniel Simon Date: Tue, 22 Jun 2021 12:55:42 +0700 Subject: [PATCH 54/58] fix: require borrowing at least the minimum net debt This prevents sending TXs that are at risk of failing due to decrease of the borrowing rate (either through decay or entering recovery mode). This also fixes a problem where the confirm button on the Trove panel could get stuck if trying to open a Trove with dangerously low debt. The fee decay estimation logic would throw an error upon seeing the Trove's debt decay below the minimum within the fee decay tolerance period. Fixes #613. --- .../components/Trove/validation/validateTroveChange.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/dev-frontend/src/components/Trove/validation/validateTroveChange.tsx b/packages/dev-frontend/src/components/Trove/validation/validateTroveChange.tsx index ec012d7d3..bc67624aa 100644 --- a/packages/dev-frontend/src/components/Trove/validation/validateTroveChange.tsx +++ b/packages/dev-frontend/src/components/Trove/validation/validateTroveChange.tsx @@ -1,6 +1,7 @@ import { Decimal, LUSD_MINIMUM_DEBT, + LUSD_MINIMUM_NET_DEBT, Trove, TroveAdjustmentParams, TroveChange, @@ -161,7 +162,7 @@ export const validateTroveChange = ( }; const validateTroveCreation = ( - { depositCollateral }: TroveCreationParams, + { depositCollateral, borrowLUSD }: TroveCreationParams, { resultingTrove, recoveryMode, @@ -170,12 +171,12 @@ const validateTroveCreation = ( price }: TroveChangeValidationContext ): JSX.Element | null => { - if (resultingTrove.debt.lt(LUSD_MINIMUM_DEBT)) { + if (borrowLUSD.lt(LUSD_MINIMUM_NET_DEBT)) { return ( - Total debt must be at least{" "} + You must borrow at least{" "} - {LUSD_MINIMUM_DEBT.toString()} {COIN} + {LUSD_MINIMUM_NET_DEBT.toString()} {COIN} . From 0be7019f29782cdba6ea58fca716675a485eee07 Mon Sep 17 00:00:00 2001 From: Daniel Simon Date: Tue, 22 Jun 2021 14:23:49 +0700 Subject: [PATCH 55/58] fix: tweak copy on expensive TX warning --- .../src/components/Trove/ExpensiveTroveChangeWarning.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/dev-frontend/src/components/Trove/ExpensiveTroveChangeWarning.tsx b/packages/dev-frontend/src/components/Trove/ExpensiveTroveChangeWarning.tsx index 0e3a70d1e..9c439385a 100644 --- a/packages/dev-frontend/src/components/Trove/ExpensiveTroveChangeWarning.tsx +++ b/packages/dev-frontend/src/components/Trove/ExpensiveTroveChangeWarning.tsx @@ -71,12 +71,12 @@ export const ExpensiveTroveChangeWarning: React.FC - The cost of opening a Trove in this collateral ratio range is high. It is recommended to + The cost of opening a Trove in this collateral ratio range is rather high. To lower it, choose a slightly different collateral ratio. ) : ( - The cost of adjusting a Trove into this collateral ratio range is high. It is recommended to + The cost of adjusting a Trove into this collateral ratio range is rather high. To lower it, choose a slightly different collateral ratio. ); From c1e2c5a2d6a8d4c2d8763065c51ac1abefef080f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Fingen?= Date: Tue, 22 Jun 2021 17:07:30 +0200 Subject: [PATCH 56/58] Switch from PriceFeed to Chainlink aggregator To have a more recent ETH price. --- .../balancerPoolDeployment.js | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/contracts/mainnetDeployment/balancerPoolDeployment.js b/packages/contracts/mainnetDeployment/balancerPoolDeployment.js index ff0497fe2..7e864020e 100644 --- a/packages/contracts/mainnetDeployment/balancerPoolDeployment.js +++ b/packages/contracts/mainnetDeployment/balancerPoolDeployment.js @@ -6,6 +6,7 @@ const { WeightedPool2Tokens } = require("./ABIs/WeightedPool2Tokens.js") const { IVault } = require("./ABIs/IVault.js") const { ERC20 } = require("./ABIs/ERC20.js") const { WETH: WETH_ABI } = require("./ABIs/WETH.js") +const { ChainlinkAggregatorV3Interface } = require("./ABIs/ChainlinkAggregatorV3Interface.js") const toBigNum = ethers.BigNumber.from const { TestHelper: th, TimeValues: timeVals } = require("../utils/testHelpers.js") const { dec } = th @@ -22,7 +23,7 @@ const DELEGATE_OWNER = '0xBA1BA1ba1BA1bA1bA1Ba1BA1ba1BA1bA1ba1ba1B'; const WETH = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'; const LUSD = '0x5f98805A4E8be255a32880FDeC7F6728C6568bA0'; -const PRICE_FEED = '0x4c517D4e2C851CA76d7eC94B805269Df0f2201De'; +const CHAINLINK_ETHUSD_PROXY = '0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419'; const tokens = [LUSD, WETH]; const weights = [toBigNum(dec(4, 17)), toBigNum(dec(6, 17))]; @@ -61,7 +62,11 @@ async function main() { deployerWallet ); - const priceFeed = await ethers.getContractAt('PriceFeed', PRICE_FEED) + const chainlinkProxy = new ethers.Contract( + CHAINLINK_ETHUSD_PROXY, + ChainlinkAggregatorV3Interface, + deployerWallet + ) // ZERO_ADDRESS owner means fixed swap fees // DELEGATE_OWNER grants permission to governance for dynamic fee management @@ -87,14 +92,18 @@ async function main() { ); const poolId = await pool.getPoolId(); + // Get latest price + const chainlinkPrice = await chainlinkProxy.latestAnswer(); + // chainlink price has only 8 decimals + const eth_price = chainlinkPrice.mul(toBigNum(dec(1, 10))); + th.logBN('ETH price', eth_price) // Tokens must be in the same order // Values must be decimal-normalized! - const eth_price = await priceFeed.lastGoodPrice(); const weth_balance = INITIAL_FUNDING.mul(weights[1]).div(eth_price); const lusd_balance = INITIAL_FUNDING.mul(weights[0]).div(toBigNum(dec(1, 18))); const initialBalances = [lusd_balance, weth_balance]; - th.logBN('Initial LUSD', lusd_balance.toString()) - th.logBN('Initial WETH', weth_balance.toString()) + th.logBN('Initial LUSD', lusd_balance) + th.logBN('Initial WETH', weth_balance) const JOIN_KIND_INIT = 0; From 4db34e04ed2f7cf8ffa03206a69d481020537824 Mon Sep 17 00:00:00 2001 From: Daniel Simon Date: Fri, 25 Jun 2021 15:36:48 +0700 Subject: [PATCH 57/58] fix: insufficient gas headroom for updating `baseRate` Fixes #616. --- .../src/PopulatableEthersLiquity.ts | 18 +-- packages/lib-ethers/test/Liquity.test.ts | 106 +++++++++++++++++- 2 files changed, 113 insertions(+), 11 deletions(-) diff --git a/packages/lib-ethers/src/PopulatableEthersLiquity.ts b/packages/lib-ethers/src/PopulatableEthersLiquity.ts index d39bd6b79..cc8459105 100644 --- a/packages/lib-ethers/src/PopulatableEthersLiquity.ts +++ b/packages/lib-ethers/src/PopulatableEthersLiquity.ts @@ -71,8 +71,10 @@ const compose = (f: (_: U) => V, g: (_: T) => U) => (_: T) => f(g(_)); const id = (t: T) => t; -// Takes ~6-7K to update lastFeeOperationTime. Let's be on the safe side. -const addGasForPotentialLastFeeOperationTimeUpdate = (gas: BigNumber) => gas.add(10000); +// Takes ~6-7K (use 10K to be safe) to update lastFeeOperationTime, but the cost of calculating the +// decayed baseRate increases logarithmically with time elapsed since the last update. +const addGasForBaseRateUpdate = (maxMinutesSinceLastUpdate = 10) => (gas: BigNumber) => + gas.add(10000 + 1414 * Math.ceil(Math.log2(maxMinutesSinceLastUpdate + 1))); // First traversal in ascending direction takes ~50K, then ~13.5K per extra step. // 80K should be enough for 3 steps, plus some extra to be safe. @@ -848,7 +850,7 @@ export class PopulatableEthersLiquity borrowerOperations.estimateGas.openTrove(...txParams(borrowLUSDSimulatingDecay)) ]); - const gasLimit = addGasForPotentialLastFeeOperationTimeUpdate( + const gasLimit = addGasForBaseRateUpdate(borrowingFeeDecayToleranceMinutes)( bigNumberMax(addGasForPotentialListTraversal(gasNow), gasLater) ); @@ -982,9 +984,11 @@ export class PopulatableEthersLiquity borrowerOperations.estimateGas.adjustTrove(...txParams(borrowLUSDSimulatingDecay)) ]); - const gasLimit = (borrowLUSD ? addGasForPotentialLastFeeOperationTimeUpdate : id)( - bigNumberMax(addGasForPotentialListTraversal(gasNow), gasLater) - ); + let gasLimit = bigNumberMax(addGasForPotentialListTraversal(gasNow), gasLater); + + if (borrowLUSD) { + gasLimit = addGasForBaseRateUpdate(borrowingFeeDecayToleranceMinutes)(gasLimit); + } gasHeadroom = gasLimit.sub(gasNow).toNumber(); overrides = { ...overrides, gasLimit }; @@ -1221,7 +1225,7 @@ export class PopulatableEthersLiquity return new PopulatedEthersRedemption( await troveManager.estimateAndPopulate.redeemCollateral( { ...overrides }, - addGasForPotentialLastFeeOperationTimeUpdate, + addGasForBaseRateUpdate(), truncatedAmount.hex, firstRedemptionHint, ...partialHints, diff --git a/packages/lib-ethers/test/Liquity.test.ts b/packages/lib-ethers/test/Liquity.test.ts index 262fcbc60..eb6c7e453 100644 --- a/packages/lib-ethers/test/Liquity.test.ts +++ b/packages/lib-ethers/test/Liquity.test.ts @@ -669,7 +669,7 @@ describe("EthersLiquity", () => { it("should redeem some LUSD after the bootstrap phase", async () => { // Fast-forward 15 days - increaseTime(60 * 60 * 24 * 15); + await increaseTime(60 * 60 * 24 * 15); expect(`${await otherLiquities[0].getCollateralSurplusBalance()}`).to.equal("0"); expect(`${await otherLiquities[1].getCollateralSurplusBalance()}`).to.equal("0"); @@ -784,7 +784,7 @@ describe("EthersLiquity", () => { await otherLiquities[1].openTrove(troveCreationParams); await otherLiquities[2].openTrove(troveCreationParams); - increaseTime(60 * 60 * 24 * 15); + await increaseTime(60 * 60 * 24 * 15); }); it("should truncate the amount if it would put the last Trove below the min debt", async () => { @@ -871,7 +871,7 @@ describe("EthersLiquity", () => { ); } - increaseTime(60 * 60 * 24 * 15); + await increaseTime(60 * 60 * 24 * 15); }); it("should redeem using the maximum iterations and almost all gas", async () => { @@ -1019,7 +1019,7 @@ describe("EthersLiquity", () => { { depositCollateral: 20, borrowLUSD: 2080 } ]); - increaseTime(60 * 60 * 24 * 15); + await increaseTime(60 * 60 * 24 * 15); }); it("should include enough gas for updating lastFeeOperationTime", async () => { @@ -1172,4 +1172,102 @@ describe("EthersLiquity", () => { await waitForSuccess(liquidateMultiple.send()); }); }); + + describe("Gas estimation (fee decay)", () => { + before(async function () { + if (network.name !== "hardhat") { + this.skip(); + } + + this.timeout("1m"); + + deployment = await deployLiquity(deployer); + const [redeemedUser, ...someMoreUsers] = otherUsers.slice(0, 21); + [liquity, ...otherLiquities] = await connectUsers([user, ...someMoreUsers]); + + // Create a "slope" of Troves with similar, but slightly decreasing ICRs + await openTroves( + someMoreUsers, + someMoreUsers.map((_, i) => ({ + depositCollateral: 20, + borrowLUSD: LUSD_MINIMUM_NET_DEBT.add(i / 10) + })) + ); + + // Sweep LUSD + await Promise.all( + otherLiquities.map(async otherLiquity => + otherLiquity.sendLUSD(await user.getAddress(), await otherLiquity.getLUSDBalance(), { + gasPrice: 0 + }) + ) + ); + + const price = await liquity.getPrice(); + + // Create a "designated victim" Trove that'll be redeemed + const redeemedTroveDebt = await liquity + .getLUSDBalance() + .then(x => x.div(10).add(LUSD_LIQUIDATION_RESERVE)); + const redeemedTroveCollateral = redeemedTroveDebt.mulDiv(1.1, price); + const redeemedTrove = new Trove(redeemedTroveCollateral, redeemedTroveDebt); + + await openTroves([redeemedUser], [Trove.recreate(redeemedTrove)]); + + // Jump past bootstrap period + await increaseTime(60 * 60 * 24 * 15); + + // Increase the borrowing rate by redeeming + const { actualLUSDAmount } = await liquity.redeemLUSD(redeemedTrove.netDebt, undefined, { + gasPrice: 0 + }); + + expect(`${actualLUSDAmount}`).to.equal(`${redeemedTrove.netDebt}`); + + const borrowingRate = await liquity.getFees().then(fees => Number(fees.borrowingRate())); + expect(borrowingRate).to.be.within(0.04, 0.049); // make sure it's high, but not clamped to 5% + }); + + it("should predict the gas increase due to fee decay", async function () { + this.timeout("1m"); + + const [bottomTrove] = await liquity.getTroves({ + first: 1, + sortedBy: "ascendingCollateralRatio" + }); + + const borrowingRate = await liquity.getFees().then(fees => fees.borrowingRate()); + + for (const [borrowingFeeDecayToleranceMinutes, roughGasHeadroom] of [ + [10, 102000], + [20, 184000], + [30, 241000] + ]) { + const tx = await liquity.populate.openTrove(Trove.recreate(bottomTrove, borrowingRate), { + borrowingFeeDecayToleranceMinutes + }); + + expect(tx.gasHeadroom).to.be.within(roughGasHeadroom - 1000, roughGasHeadroom + 1000); + } + }); + + it("should include enough gas for the TX to succeed after pending", async function () { + this.timeout("1m"); + + const [bottomTrove] = await liquity.getTroves({ + first: 1, + sortedBy: "ascendingCollateralRatio" + }); + + const borrowingRate = await liquity.getFees().then(fees => fees.borrowingRate()); + + const tx = await liquity.populate.openTrove( + Trove.recreate(bottomTrove.multiply(2), borrowingRate), + { borrowingFeeDecayToleranceMinutes: 60 } + ); + + await increaseTime(60 * 60); + await waitForSuccess(tx.send()); + }); + }); }); From 3d51ea6e85bb34b3620d4bd427a33247695adc33 Mon Sep 17 00:00:00 2001 From: Daniel Simon Date: Mon, 28 Jun 2021 14:13:40 +0700 Subject: [PATCH 58/58] fix: don't let listener errors mess up event dispatch Previously, an error thrown from a listener function would cause the dispatch of the event to prematurely stop, resulting in not all listeners being notified. Ethers solves this by deferring listener invocation to the next event cycle using `setTimeout()`. Let's do the same. --- packages/providers/src/WebSocketAugmentedProvider.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/providers/src/WebSocketAugmentedProvider.ts b/packages/providers/src/WebSocketAugmentedProvider.ts index ce054e7a7..d5c214215 100644 --- a/packages/providers/src/WebSocketAugmentedProvider.ts +++ b/packages/providers/src/WebSocketAugmentedProvider.ts @@ -79,6 +79,12 @@ const sequence = ( f(u => g(h)(u, context))(t, context); }; +const defer = (f: (t: T) => void) => (t: T) => { + setTimeout(() => { + f(t); + }, 0); +}; + export const WebSocketAugmented = BaseProvider>(Base: T) => { let webSocketAugmentedProvider = class extends Base implements WebSocketAugmentedProvider { _wsProvider?: WebSocketProvider; @@ -216,7 +222,7 @@ export const WebSocketAugmented = BaseProvide return [ f, (u: U) => - g(f)(u, { + g(defer(f))(u, { isActive: () => this._blockListeners.has(f), removeMe: () => this._blockListeners.delete(f) })