Skip to content

Commit

Permalink
Added LiquidatorLib and VirtualCpmm tests
Browse files Browse the repository at this point in the history
  • Loading branch information
asselstine committed May 10, 2022
1 parent 94f6e66 commit bd9e309
Show file tree
Hide file tree
Showing 6 changed files with 300 additions and 1 deletion.
88 changes: 88 additions & 0 deletions SlippageDerivation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Calculating Virtual CPMM LP by slippage and swap size

Let a be the amount of x we are exchanging to get b amount of y. Therefore:

(x + a) * (y - b) = k

We want to solve for x.

We know:

et = execution price of the trade = b/a
ex = exchange rate before trade = y/x

Let's now tune the liquidity to determine slippage. Let's say we want 1% slippage.

We want et/ex = 0.99

So we have:

et = 0.99*ex

We know the exchange rate and we know b, so we can solve for a:

b/a = 0.99*ex

b/(0.99*ex) = a

We know the exchange rate:

ex = y/x

now solve for y

x*ex = y

Here is our formula:

(x + a) * (y - b) = x * y
xy - bx + ay - ab = x * y
-bx + ay - ab = 0
ay = bx + ab

solve for x:
ay - ab = bx
x = (ay - ab)/b
x = (ay)/b - a

x = (a*x*ex)/b - a

x = (b*x*ex)/(b*0.99*ex) - b/(0.99*ex)
x - (b*x*ex)/(b*0.99*ex) = - b/(0.99*ex)
x(1 - b*ex/(b*0.99*ex)) = - b/(0.99*ex)

x = (-b / (0.99 * ex)) / (1 - b*ex/(b*0.99*ex))
x = (b / (0.99 * ex)) / (b*ex/(b*0.99*ex) - 1)


if b = 100
ex = 2

Then

x = (-100 / (0.99*2)) / (1 - (100*2)/(100 *0.99*2))
x = 5000

=>

2 = y/5000

10000 = y

Let's try it

x = 5000
y = 10000
trading a of x for b of y.
b = 100
solve for a

ay = bx + ab
ay - ab = bx
a(y - b) = bx
a = bx / (y - b)
a = 100 * 5000 / (10000 - 100)
a = 50.5

Right on point!

5 changes: 4 additions & 1 deletion contracts/PrizePoolLiquidator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,10 @@ contract PrizePoolLiquidator {
_prizePool.award(msg.sender, amountOut);
target.want.transferFrom(msg.sender, target.target, amountIn);

listener.afterSwap(_prizePool, _prizePool.getTicket(), amountOut, target.want, amountIn);
IPrizePoolLiquidatorListener _listener = listener;
if (address(_listener) != address(0)) {
_listener.afterSwap(_prizePool, _prizePool.getTicket(), amountOut, target.want, amountIn);
}

return amountOut;
}
Expand Down
48 changes: 48 additions & 0 deletions contracts/test/libraries/LiquidatorLibHarness.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// SPDX-License-Identifier: GPL-3.0

pragma solidity 0.8.6;

import "../../libraries/LiquidatorLib.sol";

contract LiquidatorLibHarness {
using LiquidatorLib for LiquidatorLib.State;
using SafeMath for uint256;
using SafeCast for uint256;
using PRBMathSD59x18Typed for PRBMath.SD59x18;

LiquidatorLib.State state;

function setState(
int256 exchangeRate,
uint256 lastSaleTime,
int256 deltaRatePerSecond,
int256 maxSlippage
) external {
state = LiquidatorLib.State({
exchangeRate: PRBMath.SD59x18(exchangeRate),
lastSaleTime: lastSaleTime,
deltaRatePerSecond: PRBMath.SD59x18(deltaRatePerSecond),
maxSlippage: PRBMath.SD59x18(maxSlippage)
});
}

function computeExchangeRate(uint256 _currentTime) external view returns (int256) {
return state.computeExchangeRate(_currentTime).value;
}

function computeExactAmountInAtTime(uint256 availableBalance, uint256 amountOut, uint256 currentTime) external view returns (uint256) {
return state.computeExactAmountInAtTime(availableBalance, amountOut, currentTime);
}

function computeExactAmountOutAtTime(uint256 availableBalance, uint256 amountIn, uint256 currentTime) external view returns (uint256) {
return state.computeExactAmountOutAtTime(availableBalance, amountIn, currentTime);
}

function swapExactAmountInAtTime(
uint256 availableBalance,
uint256 amountIn,
uint256 currentTime
) external returns (uint256) {
return state.swapExactAmountInAtTime(availableBalance, amountIn, currentTime);
}
}
34 changes: 34 additions & 0 deletions contracts/test/libraries/VirtualCpmmLibHarness.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: GPL-3.0

pragma solidity 0.8.6;

import "@prb/math/contracts/PRBMathSD59x18.sol";

import "../../libraries/VirtualCpmmLib.sol";

contract VirtualCpmmLibHarness {
using SafeCast for uint256;

function newCpmm(
int256 maxSlippage,
int256 exchangeRate,
uint256 haveAmount
) external pure returns (VirtualCpmmLib.Cpmm memory) {
return VirtualCpmmLib.newCpmm(
PRBMath.SD59x18(maxSlippage),
PRBMath.SD59x18(exchangeRate),
PRBMathSD59x18Typed.fromInt(haveAmount.toInt256())
);
}

// given an input amount of an asset and pair reserves, returns the maximum output amount of the other asset
function getAmountOut(uint amountIn, uint x, uint y) external pure returns (uint amountOut) {
return VirtualCpmmLib.getAmountOut(amountIn, x, y);
}

// given an output amount of an asset and pair reserves, returns a required input amount of the other asset
function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) external pure returns (uint amountIn) {
return VirtualCpmmLib.getAmountIn(amountOut, reserveIn, reserveOut);
}

}
72 changes: 72 additions & 0 deletions test/libraries/LiquidatorLibHarness.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { expect } from 'chai';
import { BigNumber, Contract, ContractFactory } from 'ethers';
import { ethers } from 'hardhat';

const { utils } = ethers;
const { parseEther: toWei } = utils;

describe('LiquidatorLibHarness', () => {
let liquidatorLibHarness: Contract;
let LiquidatorLibHarnessFactory: ContractFactory;

before(async () => {
LiquidatorLibHarnessFactory = await ethers.getContractFactory('LiquidatorLibHarness');
liquidatorLibHarness = await LiquidatorLibHarnessFactory.deploy();

const exchangeRate = toWei('2') // want:have
const lastSaleTime = '10'
const deltaRatePerSecond = toWei('0.01') // increases by 1% each second
const maxSlippage = toWei('0.01')

await liquidatorLibHarness.setState(
exchangeRate,
lastSaleTime,
deltaRatePerSecond,
maxSlippage
)
})

describe('computeExchangeRate()', () => {
it('should start at the current exchange rate when delta time is zero', async () => {
expect(await liquidatorLibHarness.computeExchangeRate('10')).to.equal(toWei('2'))
})

it('should increase the exchange rate by delta time', async () => {
// 10 seconds, 1 percent each second, => Delta exchange rate = 10% x 2 = 0.2
// = 2 + 0.2 = 2.2
expect(await liquidatorLibHarness.computeExchangeRate('20')).to.equal(toWei('2.2'))
})
})

describe('computeExactAmountInAtTime()', () => {
it('should compute how much can be purchased at time = 0', async () => {
expect(await liquidatorLibHarness.computeExactAmountInAtTime(toWei('1000'), toWei('100'), '10')).to.equal('50050050050050050049')
})

it('should return 0 if available balance is zero', async () => {
expect(await liquidatorLibHarness.computeExactAmountInAtTime('0', toWei('100'), '10')).to.equal('0')
})
})

describe('computeExactAmountOutAtTime()', () => {
it('should compute how much can be purchased at time = 0', async () => {
expect(await liquidatorLibHarness.computeExactAmountOutAtTime(toWei('1000'), toWei('50'), '10')).to.equal('99900099900099900099')
})

it('should return 0 if available balance is zero', async () => {
expect(await liquidatorLibHarness.computeExactAmountOutAtTime('0', toWei('100'), '10')).to.equal('0')
})
})

describe('swapExactAmountInAtTime()', () => {
it('should swap correctly', async () => {
await liquidatorLibHarness.swapExactAmountInAtTime(toWei('1000'), toWei('100'), '10')
expect(await liquidatorLibHarness.computeExchangeRate('10')).to.equal('1992023936159616893')
})

it('should revert if there is insufficient balance', async () => {
await expect(liquidatorLibHarness.swapExactAmountInAtTime(toWei('50'), toWei('100'), '10')).to.be.revertedWith('Whoops! have exceeds available')
})
})

});
54 changes: 54 additions & 0 deletions test/libraries/VirtualCpmmLibHarness.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { expect } from 'chai';
import { BigNumber, Contract, ContractFactory } from 'ethers';
import { ethers } from 'hardhat';

const { utils } = ethers;
const { parseEther: toWei } = utils;

describe('VirtualCpmmLibHarness', () => {
let virtualCpmmLibHarness: Contract;
let VirtualCpmmLibHarnessFactory: ContractFactory;

before(async () => {
VirtualCpmmLibHarnessFactory = await ethers.getContractFactory('VirtualCpmmLibHarness');
virtualCpmmLibHarness = await VirtualCpmmLibHarnessFactory.deploy();
});

describe("newCpmm()", () => {
it("should have correct LP for one percent slippage", async () => {

const cpmm = await virtualCpmmLibHarness.newCpmm(
toWei('0.01'),
toWei('2'),
'100'
)

expect(cpmm.want).to.equal('5000')
expect(cpmm.have).to.equal('10000')

})

it('should have correct LP for ten percent slippage', async () => {
const cpmm = await virtualCpmmLibHarness.newCpmm(
toWei('0.1'),
toWei('2'),
'100'
)

expect(cpmm.want).to.equal('500')
expect(cpmm.have).to.equal('1000')
})
})

describe("getAmountOut()", () => {
it('should be correct', async () => {
expect(await virtualCpmmLibHarness.getAmountOut(550, 5000, 10000)).to.equal(990)
})
})

describe('getAmountIn()', () => {
it('should be correct', async () => {
expect(await virtualCpmmLibHarness.getAmountIn(990, 5000, 10000)).to.equal(549)
})
})
});

0 comments on commit bd9e309

Please sign in to comment.