Skip to content

Commit

Permalink
[JS->TS] Migrate ensure_owl_liquidity.js (#820)
Browse files Browse the repository at this point in the history
* Migrate ensure_owl_liquidity.js
* improve return type and capture all possible script outcomes
  • Loading branch information
bh2smith authored May 30, 2020
1 parent 2025c76 commit 3633d36
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 76 deletions.
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;
}

0 comments on commit 3633d36

Please sign in to comment.