Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[JS->TS] Migrate ensure_owl_liquidity.js #820

Merged
merged 4 commits into from
May 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 0 additions & 72 deletions scripts/ensure_owl_liquidity.js

This file was deleted.

117 changes: 117 additions & 0 deletions scripts/ensure_owl_liquidity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import BN from "bn.js";
import { placeFeeTokenLiquidityOrders } from "../src/fee_token_liquidity";
import { getOrdersPaginated } from "../src/onchain_reading";
import type { Order } from "../src/encoding";
import { factory } from "../src/logging";
const log = factory.getLogger("scripts.owl_liquidity");

const BatchExchange = artifacts.require("BatchExchange");
const MAXU32 = new BN(2).pow(new BN(32)).sub(new BN(1));
const MIN_OWL_LIQUIDITY = new BN(10).pow(new BN(17));
const SELL_AMOUNT_OWL = new BN(10).pow(new BN(18)).mul(new BN(5));

// All orders provided by this liquidity script will sell OWL for a very high price:
// At 1000 [token]/[OWL]. In most of the cases this will ensure that 1 [OWL] is valued
// higher than 1 dollar. For tokens valued below 1/10000 USD, OWL can be extracted profitably
// from these orders. However, since we only sell 5 OWL and 10 OWL have to be spent to add one token,
// stealing OWL by adding new tokens is not profitable.
const PRICE_FOR_PROVISION = new BN(10000);

const containsSellOrderProvidingLiquidity = function (orders: Order<BN>[]) {
return orders.some(
(order) =>
order.sellTokenBalance.gt(MIN_OWL_LIQUIDITY) &&
order.remainingAmount.gt(MIN_OWL_LIQUIDITY),
);
};

// This function checks whether it is likely that Gnosis has already provided liquidity for this token
// with a liquidity-order. The check depends on the match of two order criteria: SellAmount and validUntil.
// Despite being just an heuristic check, it should be sufficient for now.
const hasOWLLiquidityOrderAlreadyBeenPlaced = function (orders: Order<BN>[]) {
return orders.some(
(order) =>
order.priceDenominator.eq(SELL_AMOUNT_OWL) &&
new BN(order.validUntil).eq(MAXU32),
);
};

module.exports = async (callback: Truffle.ScriptCallback) => {
try {
const exchange = await BatchExchange.deployed();
const owlTokenAddress = await exchange.tokenIdToAddressMap(0);
const [liquidityEnsurer] = await web3.eth.getAccounts();
log.info(`Using account ${liquidityEnsurer}`);
// check that liquidityEnsurer has sufficient OWL in the exchange:
const owlBalance = await exchange.getBalance(
liquidityEnsurer,
owlTokenAddress,
);
if (new BN(10).pow(new BN(18)).gt(owlBalance)) {
callback(
"Error: OWL balance is below the 10 OWL threshold, please stock it up again",
);
}

// Get the order data
const numTokens = (await exchange.numTokens()).toNumber();
const batchId = (await exchange.getCurrentBatchId()).toNumber();
log.info("Retrieving orders from exchange. This may take a while...");
let orders = await getOrdersPaginated(exchange.contract, 100);
orders = orders.filter(
(order) => order.validUntil >= batchId && order.validFrom <= batchId,
);

// Ensure OWL-liquidity is given
const tokensRequiringLiquidity = [];
for (let tokenId = 1; tokenId < numTokens; tokenId++) {
const tokenAddress = await exchange.tokenIdToAddressMap(tokenId);
log.info(`Checking liquidity for token ${tokenId} - ${tokenAddress}`);
const ordersForTokenId = orders.filter(
(order) => order.buyToken == tokenId && order.sellToken == 0,
);
if (
!containsSellOrderProvidingLiquidity(ordersForTokenId) &&
!hasOWLLiquidityOrderAlreadyBeenPlaced(ordersForTokenId)
) {
tokensRequiringLiquidity.push(tokenId);
} else {
log.info(
` Liquidity for ${tokenAddress} is given or has been provided in the past`,
);
}
}
if (tokensRequiringLiquidity) {
log.info(
`Attempting to place orders for tokens ${tokensRequiringLiquidity}`,
);
const successTokens = await placeFeeTokenLiquidityOrders(
exchange,
tokensRequiringLiquidity,
PRICE_FOR_PROVISION,
SELL_AMOUNT_OWL,
artifacts,
);
if (successTokens && successTokens.length) {
log.info(
`Successfully placed fee token liquidity orders for tokens: ${successTokens}`,
);
}
// This scenario is actually quite common. In fact, always
// happens once a non-ERC20 token has been registered on exchange.
const failedTokens = tokensRequiringLiquidity.filter(
(x) => !successTokens.includes(x),
);
if (failedTokens && failedTokens.length) {
log.warn(
`No orders placed for ${failedTokens} (Likely not ERC20s on this network).`,
);
}
} else {
log.info("Did not detect any tokens requiring liquidity");
}
callback();
} catch (error) {
callback(error);
}
};
8 changes: 4 additions & 4 deletions src/fee_token_liquidity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@ export async function placeFeeTokenLiquidityOrders(
provisionPrice: BN,
sellAmountOwl: BN,
artifacts: Truffle.Artifacts,
): Promise<(string | undefined)[]> {
): Promise<number[]> {
const minBuyAmounts = [];
const validTokenIds = [];
const validTokenIds: number[] = [];
const feeToken = await fetchTokenInfoFromExchange(exchange, [0], artifacts);
// This is expected to always be OWL which has 18 digits.
const feeDigits = feeToken.get(0)?.decimals || 18;
Expand All @@ -82,7 +82,7 @@ export async function placeFeeTokenLiquidityOrders(
);
for (const tokenId of tokenIds) {
const numDigits = tokenInfo.get(tokenId)?.decimals;
if (numDigits && numDigits != -1) {
if (numDigits) {
validTokenIds.push(tokenId);
if (numDigits < feeDigits) {
minBuyAmounts.push(
Expand Down Expand Up @@ -113,5 +113,5 @@ export async function placeFeeTokenLiquidityOrders(
Array(numOrders).fill(sellAmountOwl), // sellAmount
);

return await Promise.all(validTokenIds.map((i) => tokenInfo.get(i)?.address));
return validTokenIds;
}