191 lines
7.0 KiB
JavaScript
191 lines
7.0 KiB
JavaScript
|
/* global artifacts, web3, contract */
|
||
|
require('chai').use(require('bn-chai')(web3.utils.BN)).use(require('chai-as-promised')).should()
|
||
|
|
||
|
const { toBN } = require('web3-utils')
|
||
|
const { takeSnapshot, revertSnapshot, mineBlock } = require('../scripts/ganacheHelper')
|
||
|
const { tornadoFormula, reverseTornadoFormula } = require('../src/utils')
|
||
|
const Torn = artifacts.require('TORNMock')
|
||
|
const RewardSwap = artifacts.require('RewardSwapMock')
|
||
|
const tornConfig = require('torn-token')
|
||
|
const RLP = require('rlp')
|
||
|
|
||
|
const MONTH = toBN(60 * 60 * 24 * 30)
|
||
|
const DURATION = toBN(60 * 60 * 24 * 365)
|
||
|
|
||
|
// Set time to beginning of a second
|
||
|
async function timeReset() {
|
||
|
const delay = 1000 - new Date().getMilliseconds()
|
||
|
await new Promise((resolve) => setTimeout(resolve, delay))
|
||
|
await mineBlock()
|
||
|
}
|
||
|
|
||
|
async function getNextAddr(sender, offset = 0) {
|
||
|
const nonce = await web3.eth.getTransactionCount(sender)
|
||
|
return (
|
||
|
'0x' +
|
||
|
web3.utils
|
||
|
.sha3(RLP.encode([sender, Number(nonce) + Number(offset)]))
|
||
|
.slice(12)
|
||
|
.substring(14)
|
||
|
)
|
||
|
}
|
||
|
|
||
|
contract('RewardSwap', (accounts) => {
|
||
|
let torn
|
||
|
let rewardSwap
|
||
|
let amount
|
||
|
const tornCap = toBN(tornConfig.torn.cap)
|
||
|
const miningCap = toBN(tornConfig.torn.distribution.miningV2.amount)
|
||
|
const initialTornBalance = toBN(tornConfig.miningV2.initialBalance)
|
||
|
let yearLiquidity
|
||
|
let delta = toBN(100000) // 0.0000000000001 torn error
|
||
|
// eslint-disable-next-line no-unused-vars
|
||
|
const sender = accounts[0]
|
||
|
const recipient = accounts[1]
|
||
|
// eslint-disable-next-line no-unused-vars
|
||
|
const relayer = accounts[2]
|
||
|
let snapshotId
|
||
|
const thirtyDays = 30 * 24 * 3600
|
||
|
const poolWeight = 1e11
|
||
|
|
||
|
before(async () => {
|
||
|
const swapExpectedAddr = await getNextAddr(accounts[0], 1)
|
||
|
torn = await Torn.new(sender, thirtyDays, [
|
||
|
{ to: swapExpectedAddr, amount: miningCap.toString() },
|
||
|
{ to: sender, amount: tornCap.sub(miningCap).toString() },
|
||
|
])
|
||
|
rewardSwap = await RewardSwap.new(
|
||
|
torn.address,
|
||
|
sender,
|
||
|
miningCap.toString(),
|
||
|
initialTornBalance.toString(),
|
||
|
poolWeight,
|
||
|
)
|
||
|
yearLiquidity = miningCap.sub(initialTornBalance)
|
||
|
amount = toBN(await rewardSwap.poolWeight()).mul(toBN(7)) // 10**10
|
||
|
|
||
|
snapshotId = await takeSnapshot()
|
||
|
})
|
||
|
|
||
|
beforeEach(async () => {
|
||
|
await timeReset()
|
||
|
})
|
||
|
|
||
|
describe('#formula test', () => {
|
||
|
it('should work', async () => {
|
||
|
let formulaReturn
|
||
|
let expectedReturn
|
||
|
amount = amount.mul(toBN(5))
|
||
|
for (let i = 1; i < 11; i++) {
|
||
|
amount = amount.div(toBN(i))
|
||
|
formulaReturn = tornadoFormula({ balance: initialTornBalance, amount })
|
||
|
expectedReturn = await rewardSwap.getExpectedReturn(amount)
|
||
|
expectedReturn.sub(formulaReturn).should.be.lte.BN(delta)
|
||
|
}
|
||
|
})
|
||
|
})
|
||
|
|
||
|
describe('#constructor', () => {
|
||
|
it('should initialize', async () => {
|
||
|
const tokenFromContract = await rewardSwap.torn()
|
||
|
tokenFromContract.should.be.equal(torn.address)
|
||
|
})
|
||
|
})
|
||
|
|
||
|
it('should return as expected', async () => {
|
||
|
const balanceBefore = await torn.balanceOf(recipient)
|
||
|
const expectedReturn = await rewardSwap.getExpectedReturn(amount)
|
||
|
await rewardSwap.swap(recipient, amount, { from: sender })
|
||
|
const balanceAfter = await torn.balanceOf(recipient)
|
||
|
balanceAfter.sub(balanceBefore).should.be.eq.BN(expectedReturn)
|
||
|
})
|
||
|
|
||
|
it('reverse rate', async () => {
|
||
|
const tokens = await rewardSwap.getExpectedReturn(amount)
|
||
|
const balance = await rewardSwap.tornVirtualBalance()
|
||
|
const points = reverseTornadoFormula({ balance, tokens })
|
||
|
points.sub(amount).should.be.lt.BN(toBN(1))
|
||
|
})
|
||
|
|
||
|
it('should be approximately additive', async () => {
|
||
|
const amount = toBN(10).pow(toBN(10)).mul(toBN(2))
|
||
|
const delta = toBN('1000') // max floating point error
|
||
|
|
||
|
const balanceBefore1 = await torn.balanceOf(recipient)
|
||
|
await rewardSwap.swap(recipient, amount, { from: sender })
|
||
|
const balanceAfter1 = await torn.balanceOf(recipient)
|
||
|
|
||
|
await revertSnapshot(snapshotId.result)
|
||
|
snapshotId = await takeSnapshot()
|
||
|
|
||
|
const balanceBefore2 = await torn.balanceOf(recipient)
|
||
|
await rewardSwap.swap(recipient, amount.div(toBN(2)), { from: sender })
|
||
|
await rewardSwap.swap(recipient, amount.div(toBN(2)), { from: sender })
|
||
|
const balanceAfter2 = await torn.balanceOf(recipient)
|
||
|
|
||
|
balanceBefore1.sub(balanceBefore2).should.be.lt.BN(delta)
|
||
|
balanceAfter1.sub(balanceAfter2).should.be.lt.BN(delta)
|
||
|
})
|
||
|
|
||
|
describe('#swap', () => {
|
||
|
it('should work as uniswap without vested tokens', async () => {
|
||
|
const startTimestamp = await rewardSwap.startTimestamp()
|
||
|
await rewardSwap.setTimestamp(startTimestamp)
|
||
|
|
||
|
const expectedTokens = await rewardSwap.getExpectedReturn(amount)
|
||
|
const formulaReturn = tornadoFormula({ balance: initialTornBalance, amount })
|
||
|
expectedTokens.sub(formulaReturn).should.be.lte.BN(delta)
|
||
|
const tornVirtualBalance = await rewardSwap.tornVirtualBalance()
|
||
|
tornVirtualBalance.should.be.eq.BN(initialTornBalance)
|
||
|
|
||
|
const balanceBefore = await torn.balanceOf(recipient)
|
||
|
await rewardSwap.swap(recipient, amount, { from: sender })
|
||
|
const balanceAfter = await torn.balanceOf(recipient)
|
||
|
balanceAfter.should.be.eq.BN(balanceBefore.add(expectedTokens))
|
||
|
})
|
||
|
|
||
|
it('should work with vested tokens (a half of year passed)', async () => {
|
||
|
let startTimestamp = await rewardSwap.startTimestamp()
|
||
|
const currentTimestamp = startTimestamp.add(DURATION.div(toBN(2)))
|
||
|
await rewardSwap.setTimestamp(currentTimestamp)
|
||
|
|
||
|
const tornVirtualBalance = await rewardSwap.tornVirtualBalance()
|
||
|
tornVirtualBalance.should.be.eq.BN(yearLiquidity.div(toBN(2)).add(initialTornBalance))
|
||
|
|
||
|
const formulaReturn = tornadoFormula({ balance: tornVirtualBalance, amount })
|
||
|
const expectedTokens = await rewardSwap.getExpectedReturn(amount)
|
||
|
|
||
|
expectedTokens.sub(formulaReturn).should.be.lte.BN(delta)
|
||
|
const balanceBefore = await torn.balanceOf(recipient)
|
||
|
await rewardSwap.swap(recipient, amount, { from: sender })
|
||
|
const balanceAfter = await torn.balanceOf(recipient)
|
||
|
balanceAfter.should.be.eq.BN(balanceBefore.add(expectedTokens))
|
||
|
})
|
||
|
|
||
|
it('should not add any tokens after one year', async () => {
|
||
|
let startTimestamp = await rewardSwap.startTimestamp()
|
||
|
let currentTimestamp = startTimestamp.add(DURATION)
|
||
|
await rewardSwap.setTimestamp(currentTimestamp)
|
||
|
|
||
|
let tornVirtualBalance = await rewardSwap.tornVirtualBalance()
|
||
|
tornVirtualBalance.should.be.eq.BN(miningCap)
|
||
|
|
||
|
const formulaReturn = tornadoFormula({ balance: tornVirtualBalance, amount })
|
||
|
const expectedTokens = await rewardSwap.getExpectedReturn(amount)
|
||
|
expectedTokens.sub(formulaReturn).should.be.lte.BN(delta)
|
||
|
|
||
|
currentTimestamp = currentTimestamp.add(MONTH)
|
||
|
await rewardSwap.setTimestamp(currentTimestamp)
|
||
|
|
||
|
tornVirtualBalance = await rewardSwap.tornVirtualBalance()
|
||
|
tornVirtualBalance.should.be.eq.BN(miningCap)
|
||
|
})
|
||
|
})
|
||
|
|
||
|
afterEach(async () => {
|
||
|
await revertSnapshot(snapshotId.result)
|
||
|
// eslint-disable-next-line require-atomic-updates
|
||
|
snapshotId = await takeSnapshot()
|
||
|
})
|
||
|
})
|