Files
dodo-contractV2/test/Attacks.test.ts
2020-10-23 17:11:50 +08:00

157 lines
6.6 KiB
TypeScript

/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
import { DODOContext, getDODOContext } from './utils/DVMContext';
import { decimalStr, gweiStr } from './utils/Converter';
import BigNumber from "bignumber.js";
import * as assert from "assert"
let lp1: string
let lp2: string
let trader: string
let hacker: string
async function init(ctx: DODOContext): Promise<void> {
await ctx.setOraclePrice(decimalStr("100"))
lp1 = ctx.spareAccounts[0]
lp2 = ctx.spareAccounts[1]
trader = ctx.spareAccounts[2]
hacker = ctx.spareAccounts[3]
await ctx.mintTestToken(lp1, decimalStr("100"), decimalStr("10000"))
await ctx.mintTestToken(lp2, decimalStr("100"), decimalStr("10000"))
await ctx.mintTestToken(trader, decimalStr("100"), decimalStr("10000"))
await ctx.mintTestToken(hacker, decimalStr("10000"), decimalStr("1000000"))
await ctx.approveDODO(lp1)
await ctx.approveDODO(lp2)
await ctx.approveDODO(trader)
await ctx.approveDODO(hacker)
}
describe("Attacks", () => {
let snapshotId: string
let ctx: DODOContext
before(async () => {
ctx = await getDODOContext()
await init(ctx);
})
beforeEach(async () => {
snapshotId = await ctx.EVM.snapshot();
});
afterEach(async () => {
await ctx.EVM.reset(snapshotId)
});
describe("Price offset attack", () => {
/*
attack describe:
1. hacker deposit a great number of base token
2. hacker buy base token
3. hacker withdraw a great number of base token
4. hacker sell or buy base token to finish the arbitrage loop
expected:
1. hacker won't earn any quote token or sell base token with price better than what dodo provides
2. quote token lp and base token lp have no loss
Same in quote direction
*/
it("attack on base token", async () => {
await ctx.DODO.methods.depositBase(decimalStr("10")).send(ctx.sendParam(lp1))
await ctx.DODO.methods.depositQuote(decimalStr("1000")).send(ctx.sendParam(lp1))
let hackerInitBaseBalance = new BigNumber(await ctx.BASE.methods.balanceOf(hacker).call())
let hackerInitQuoteBalance = new BigNumber(await ctx.QUOTE.methods.balanceOf(hacker).call())
// attack step 1
await ctx.DODO.methods.depositBase(decimalStr("5000")).send(ctx.sendParam(hacker))
// attack step 2
await ctx.DODO.methods.buyBaseToken(decimalStr("9.5"), decimalStr("2000"), "0x").send(ctx.sendParam(hacker))
// attack step 3
await ctx.DODO.methods.withdrawBase(decimalStr("5000")).send(ctx.sendParam(hacker))
// attack step 4
let hackerTempBaseBalance = new BigNumber(await ctx.BASE.methods.balanceOf(hacker).call())
if (hackerTempBaseBalance.isGreaterThan(hackerInitBaseBalance)) {
await ctx.DODO.methods.sellBaseToken(hackerTempBaseBalance.minus(hackerInitBaseBalance).toString(), "0", "0x").send(ctx.sendParam(hacker))
} else {
await ctx.DODO.methods.buyBaseToken(hackerInitBaseBalance.minus(hackerTempBaseBalance).toString(), decimalStr("5000"), "0x").send(ctx.sendParam(hacker))
}
// expected hacker no profit
let hackerBaseBalance = new BigNumber(await ctx.BASE.methods.balanceOf(hacker).call())
let hackerQuoteBalance = new BigNumber(await ctx.QUOTE.methods.balanceOf(hacker).call())
assert.ok(hackerBaseBalance.isLessThanOrEqualTo(hackerInitBaseBalance))
assert.ok(hackerQuoteBalance.isLessThanOrEqualTo(hackerInitQuoteBalance))
// expected lp no loss
let lpBaseBalance = new BigNumber(await ctx.DODO.methods.getLpBaseBalance(lp1).call())
let lpQuoteBalance = new BigNumber(await ctx.DODO.methods.getLpQuoteBalance(lp1).call())
assert.ok(lpBaseBalance.isGreaterThanOrEqualTo(decimalStr("10")))
assert.ok(lpQuoteBalance.isGreaterThanOrEqualTo(decimalStr("1000")))
})
it("attack on quote token", async () => {
await ctx.DODO.methods.depositBase(decimalStr("10")).send(ctx.sendParam(lp1))
await ctx.DODO.methods.depositQuote(decimalStr("1000")).send(ctx.sendParam(lp1))
let hackerInitBaseBalance = new BigNumber(await ctx.BASE.methods.balanceOf(hacker).call())
let hackerInitQuoteBalance = new BigNumber(await ctx.QUOTE.methods.balanceOf(hacker).call())
// attack step 1
await ctx.DODO.methods.depositQuote(decimalStr("100000")).send(ctx.sendParam(hacker))
// attack step 2
await ctx.DODO.methods.sellBaseToken(decimalStr("9"), decimalStr("500"), "0x").send(ctx.sendParam(hacker))
// attack step 3
await ctx.DODO.methods.withdrawQuote(decimalStr("100000")).send(ctx.sendParam(hacker))
// attack step 4
let hackerTempBaseBalance = new BigNumber(await ctx.BASE.methods.balanceOf(hacker).call())
if (hackerTempBaseBalance.isGreaterThan(hackerInitBaseBalance)) {
await ctx.DODO.methods.sellBaseToken(hackerTempBaseBalance.minus(hackerInitBaseBalance).toString(), "0", "0x").send(ctx.sendParam(hacker))
} else {
await ctx.DODO.methods.buyBaseToken(hackerInitBaseBalance.minus(hackerTempBaseBalance).toString(), decimalStr("5000"), "0x").send(ctx.sendParam(hacker))
}
// expected hacker no profit
let hackerBaseBalance = new BigNumber(await ctx.BASE.methods.balanceOf(hacker).call())
let hackerQuoteBalance = new BigNumber(await ctx.QUOTE.methods.balanceOf(hacker).call())
assert.ok(hackerBaseBalance.isLessThanOrEqualTo(hackerInitBaseBalance))
assert.ok(hackerQuoteBalance.isLessThanOrEqualTo(hackerInitQuoteBalance))
// expected lp no loss
let lpBaseBalance = new BigNumber(await ctx.DODO.methods.getLpBaseBalance(lp1).call())
let lpQuoteBalance = new BigNumber(await ctx.DODO.methods.getLpQuoteBalance(lp1).call())
assert.ok(lpBaseBalance.isGreaterThanOrEqualTo(decimalStr("10")))
assert.ok(lpQuoteBalance.isGreaterThanOrEqualTo(decimalStr("1000")))
})
})
describe("Front run attack", () => {
/*
attack describe:
hacker tries to front run oracle updating by sending tx with higher gas price
expected:
revert tx
*/
it("front run", async () => {
await ctx.DODO.methods.depositBase(decimalStr("10")).send(ctx.sendParam(lp1))
await ctx.DODO.methods.depositQuote(decimalStr("1000")).send(ctx.sendParam(lp1))
await assert.rejects(
ctx.DODO.methods.buyBaseToken(decimalStr("1"), decimalStr("200"), "0x").send({ from: trader, gas: 300000, gasPrice: gweiStr("200") }), /GAS_PRICE_EXCEED/
)
await assert.rejects(
ctx.DODO.methods.sellBaseToken(decimalStr("1"), decimalStr("200"), "0x").send({ from: trader, gas: 300000, gasPrice: gweiStr("200") }), /GAS_PRICE_EXCEED/
)
})
})
})