Skip to content

Commit

Permalink
Liquidator bot: read position state (collateral and numTokens) from s…
Browse files Browse the repository at this point in the history
…ame on-chain query + Update SponsorReporter (#1805)

Signed-off-by: Nick Pai <npai.nyc@gmail.com>
  • Loading branch information
nicholaspai authored Aug 5, 2020
1 parent 3e83c14 commit 370a635
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 59 deletions.
89 changes: 47 additions & 42 deletions financial-templates-lib/clients/ExpiringMultiPartyClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// positions, undisputed Liquidations, expired liquidations, disputed liquidations.

const { LiquidationStatesEnum } = require("@umaprotocol/common");
const Promise = require("bluebird");

class ExpiringMultiPartyClient {
/**
Expand Down Expand Up @@ -91,31 +92,34 @@ class ExpiringMultiPartyClient {
}

async update() {
// Since this function can have a relatively long run-time, we should be safe and check contract state explicitly at the same block height.
// This way we won't experience errors where the contract state changes between calls.
this.latestBlock = await this.web3.eth.getBlockNumber();

this.collateralRequirement = this.toBN(
(await this.emp.methods.collateralRequirement().call(undefined, this.latestBlock)).toString()
);
this.liquidationLiveness = Number(await this.emp.methods.liquidationLiveness().call(undefined, this.latestBlock));

const events = await this.emp.getPastEvents("NewSponsor", { fromBlock: 0, toBlock: this.latestBlock });
// Fetch contract state variables in parallel.
const [collateralRequirement, liquidationLiveness, events, cumulativeFeeMultiplier] = await Promise.all([
this.emp.methods.collateralRequirement().call(),
this.emp.methods.liquidationLiveness().call(),
this.emp.getPastEvents("NewSponsor", { fromBlock: 0 }),
this.emp.methods.cumulativeFeeMultiplier().call()
]);
this.collateralRequirement = this.toBN(collateralRequirement.toString());
this.liquidationLiveness = Number(liquidationLiveness);
this.sponsorAddresses = [...new Set(events.map(e => e.returnValues.sponsor))];

// Fetch information about each sponsor.
const positions = await Promise.all(
this.sponsorAddresses.map(address => this.emp.methods.positions(address).call(undefined, this.latestBlock))
);
const collateral = await Promise.all(
this.sponsorAddresses.map(address => this.emp.methods.getCollateral(address).call(undefined, this.latestBlock))
);
this.cumulativeFeeMultiplier = this.toBN(cumulativeFeeMultiplier.toString());

// Fetch sponsor position, liquidation, and current time data in parallel batches, 20 at a time, to be safe and not overload the web3 node.
const WEB3_CALLS_BATCH_SIZE = 20;
const [positions, allLiquidations, currentTime] = await Promise.all([
Promise.map(this.sponsorAddresses, address => this.emp.methods.positions(address).call(), {
concurrency: WEB3_CALLS_BATCH_SIZE
}),
Promise.map(this.sponsorAddresses, address => this.emp.methods.getLiquidations(address).call(), {
concurrency: WEB3_CALLS_BATCH_SIZE
}),
this.emp.methods.getCurrentTime().call()
]);

const undisputedLiquidations = [];
const expiredLiquidations = [];
const disputedLiquidations = [];
for (const address of this.sponsorAddresses) {
const liquidations = await this.emp.methods.getLiquidations(address).call(undefined, this.latestBlock);
for (let liquidations of allLiquidations) {
for (const [id, liquidation] of liquidations.entries()) {
// Liquidations that have had all of their rewards withdrawn will still show up here but have their properties
// set to default values. We can skip them.
Expand All @@ -139,7 +143,7 @@ class ExpiringMultiPartyClient {
// Get all undisputed liquidations.
if (this._isLiquidationPreDispute(liquidation)) {
// Determine whether liquidation has expired.
if (!(await this._isExpired(liquidation))) {
if (!(await this._isExpired(liquidation, currentTime))) {
undisputedLiquidations.push(liquidationData);
} else {
expiredLiquidations.push(liquidationData);
Expand All @@ -153,25 +157,27 @@ class ExpiringMultiPartyClient {
this.expiredLiquidations = expiredLiquidations;
this.disputedLiquidations = disputedLiquidations;

this.positions = this.sponsorAddresses.reduce(
(acc, address, i) =>
// Filter out empty positions.
positions[i].rawCollateral.toString() === "0"
? acc
: /* eslint-disable indent */
acc.concat([
{
sponsor: address,
withdrawalRequestPassTimestamp: positions[i].withdrawalRequestPassTimestamp,
withdrawalRequestAmount: positions[i].withdrawalRequestAmount.toString(),
numTokens: positions[i].tokensOutstanding.toString(),
amountCollateral: collateral[i].toString(),
hasPendingWithdrawal: positions[i].withdrawalRequestPassTimestamp > 0
}
]),
[]
);
this.lastUpdateTimestamp = await this.emp.methods.getCurrentTime().call(undefined, this.latestBlock);
this.positions = this.sponsorAddresses.reduce((acc, address, i) => {
const rawCollateral = this.toBN(positions[i].rawCollateral.toString());
// Filter out empty positions.
return rawCollateral.isZero()
? acc
: /* eslint-disable indent */
acc.concat([
{
sponsor: address,
withdrawalRequestPassTimestamp: positions[i].withdrawalRequestPassTimestamp,
withdrawalRequestAmount: positions[i].withdrawalRequestAmount.toString(),
numTokens: positions[i].tokensOutstanding.toString(),
amountCollateral: rawCollateral
.mul(this.cumulativeFeeMultiplier)
.div(this.toBN(this.toWei("1")))
.toString(),
hasPendingWithdrawal: positions[i].withdrawalRequestPassTimestamp > 0
}
]);
}, []);
this.lastUpdateTimestamp = currentTime;
this.logger.debug({
at: "ExpiringMultiPartyClient",
message: "Expiring multi party state updated",
Expand All @@ -193,8 +199,7 @@ class ExpiringMultiPartyClient {
);
}

async _isExpired(liquidation) {
const currentTime = await this.emp.methods.getCurrentTime().call(undefined, this.latestBlock);
async _isExpired(liquidation, currentTime) {
return Number(liquidation.liquidationTime) + this.liquidationLiveness <= currentTime;
}

Expand Down
1 change: 1 addition & 0 deletions financial-templates-lib/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"@umaprotocol/common": "^1.0.0",
"@umaprotocol/core": "^1.0.0",
"@uniswap/sdk": "^2.0.5",
"bluebird": "^3.7.2",
"dotenv": "^6.2.0",
"minimist": "^1.2.0",
"node-fetch": "^2.6.0",
Expand Down
22 changes: 19 additions & 3 deletions reporters/SponsorReporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,21 @@ class SponsorReporter {

this.formatDecimalString = createFormatFunction(this.web3, 2, 4);

this.lastUpdateTimestamp = 0;
this.updateThresholdSeconds = 60;

this.empProps = empProps;
}

async update() {
await this.empClient.update();
await this.priceFeed.update();
const currentTimestamp = Math.floor(Date.now() / 1000);
if (currentTimestamp < this.lastUpdateTimestamp + this.updateThresholdSeconds) {
return;
} else {
await this.empClient.update();
await this.priceFeed.update();
this.lastUpdateTimestamp = this.empClient.lastUpdateTimestamp;
}
}

// Iterate over monitored wallets and generate key metrics.
Expand Down Expand Up @@ -71,11 +80,18 @@ class SponsorReporter {

async generateSponsorsTable() {
await this.update();
console.log(italic(`- There are ${this.empClient.getAllPositions().length} current sponsors`));
console.log(italic("- All current token sponsors within the specified EMP are printed"));

// For all positions current open in the UMA ecosystem, generate a table.
const allPositions = this.empClient.getAllPositions();
const currentPrice = this.priceFeed.getCurrentPrice();
const allPositions = this.empClient.getAllPositions().sort((p1, p2) => {
return Number(
this._calculatePositionCRPercent(p1.amountCollateral, p1.numTokens, currentPrice)
.sub(this._calculatePositionCRPercent(p2.amountCollateral, p2.numTokens, currentPrice))
.div(this.web3.utils.toBN(this.web3.utils.toWei("1")))
);
});

// Define the table column headings before hand to re-use the variables.
const colHeadings = [
Expand Down
28 changes: 14 additions & 14 deletions reporters/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,16 +109,7 @@ async function run(
10
);

// 6. Sponsor reporter to generate metrics on monitored positions.
const sponsorReporter = new SponsorReporter(
empClient,
tokenBalanceClient,
walletsToMonitor,
referencePriceFeed,
empProps
);

// 7. Global summary reporter reporter to generate EMP wide metrics.
// 6. Global summary reporter reporter to generate EMP wide metrics.
const globalSummaryReporter = new GlobalSummaryReporter(
empEventClient,
referencePriceFeed,
Expand All @@ -131,14 +122,23 @@ async function run(
periodLengthSeconds
);

// 7. Sponsor reporter to generate metrics on monitored positions.
const sponsorReporter = new SponsorReporter(
empClient,
tokenBalanceClient,
walletsToMonitor,
referencePriceFeed,
empProps
);

console.log(boldUnderline("1. Monitored wallets risk metrics🔎"));
await sponsorReporter.generateMonitoredWalletMetrics();

console.log(boldUnderline("2. Sponsor table💸"));
await sponsorReporter.generateSponsorsTable();

console.log(boldUnderline("3. Global summary stats🌎"));
console.log(boldUnderline("2. Global summary stats🌎"));
await globalSummaryReporter.generateSummaryStatsTable();

console.log(boldUnderline("3. Sponsor table💸"));
await sponsorReporter.generateSponsorsTable();
}

async function Poll(callback) {
Expand Down

0 comments on commit 370a635

Please sign in to comment.