rewardSwap.test.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. /* global artifacts, web3, contract */
  2. require('chai').use(require('bn-chai')(web3.utils.BN)).use(require('chai-as-promised')).should()
  3. const { toBN } = require('web3-utils')
  4. const { takeSnapshot, revertSnapshot, mineBlock } = require('../scripts/ganacheHelper')
  5. const { tornadoFormula, reverseTornadoFormula } = require('../src/utils')
  6. const Torn = artifacts.require('TORNMock')
  7. const RewardSwap = artifacts.require('RewardSwapMock')
  8. const tornConfig = require('torn-token')
  9. const RLP = require('rlp')
  10. const MONTH = toBN(60 * 60 * 24 * 30)
  11. const DURATION = toBN(60 * 60 * 24 * 365)
  12. // Set time to beginning of a second
  13. async function timeReset() {
  14. const delay = 1000 - new Date().getMilliseconds()
  15. await new Promise((resolve) => setTimeout(resolve, delay))
  16. await mineBlock()
  17. }
  18. async function getNextAddr(sender, offset = 0) {
  19. const nonce = await web3.eth.getTransactionCount(sender)
  20. return (
  21. '0x' +
  22. web3.utils
  23. .sha3(RLP.encode([sender, Number(nonce) + Number(offset)]))
  24. .slice(12)
  25. .substring(14)
  26. )
  27. }
  28. contract('RewardSwap', (accounts) => {
  29. let torn
  30. let rewardSwap
  31. let amount
  32. const tornCap = toBN(tornConfig.torn.cap)
  33. const miningCap = toBN(tornConfig.torn.distribution.miningV2.amount)
  34. const initialTornBalance = toBN(tornConfig.miningV2.initialBalance)
  35. let yearLiquidity
  36. let delta = toBN(100000) // 0.0000000000001 torn error
  37. // eslint-disable-next-line no-unused-vars
  38. const sender = accounts[0]
  39. const recipient = accounts[1]
  40. // eslint-disable-next-line no-unused-vars
  41. const relayer = accounts[2]
  42. let snapshotId
  43. const thirtyDays = 30 * 24 * 3600
  44. const poolWeight = 1e11
  45. before(async () => {
  46. const swapExpectedAddr = await getNextAddr(accounts[0], 1)
  47. torn = await Torn.new(sender, thirtyDays, [
  48. { to: swapExpectedAddr, amount: miningCap.toString() },
  49. { to: sender, amount: tornCap.sub(miningCap).toString() },
  50. ])
  51. rewardSwap = await RewardSwap.new(
  52. torn.address,
  53. sender,
  54. miningCap.toString(),
  55. initialTornBalance.toString(),
  56. poolWeight,
  57. )
  58. yearLiquidity = miningCap.sub(initialTornBalance)
  59. amount = toBN(await rewardSwap.poolWeight()).mul(toBN(7)) // 10**10
  60. snapshotId = await takeSnapshot()
  61. })
  62. beforeEach(async () => {
  63. await timeReset()
  64. })
  65. describe('#formula test', () => {
  66. it('should work', async () => {
  67. let formulaReturn
  68. let expectedReturn
  69. amount = amount.mul(toBN(5))
  70. for (let i = 1; i < 11; i++) {
  71. amount = amount.div(toBN(i))
  72. formulaReturn = tornadoFormula({ balance: initialTornBalance, amount })
  73. expectedReturn = await rewardSwap.getExpectedReturn(amount)
  74. expectedReturn.sub(formulaReturn).should.be.lte.BN(delta)
  75. }
  76. })
  77. })
  78. describe('#constructor', () => {
  79. it('should initialize', async () => {
  80. const tokenFromContract = await rewardSwap.torn()
  81. tokenFromContract.should.be.equal(torn.address)
  82. })
  83. })
  84. it('should return as expected', async () => {
  85. const balanceBefore = await torn.balanceOf(recipient)
  86. const expectedReturn = await rewardSwap.getExpectedReturn(amount)
  87. await rewardSwap.swap(recipient, amount, { from: sender })
  88. const balanceAfter = await torn.balanceOf(recipient)
  89. balanceAfter.sub(balanceBefore).should.be.eq.BN(expectedReturn)
  90. })
  91. it('reverse rate', async () => {
  92. const tokens = await rewardSwap.getExpectedReturn(amount)
  93. const balance = await rewardSwap.tornVirtualBalance()
  94. const points = reverseTornadoFormula({ balance, tokens })
  95. points.sub(amount).should.be.lt.BN(toBN(1))
  96. })
  97. it('should be approximately additive', async () => {
  98. const amount = toBN(10).pow(toBN(10)).mul(toBN(2))
  99. const delta = toBN('1000') // max floating point error
  100. const balanceBefore1 = await torn.balanceOf(recipient)
  101. await rewardSwap.swap(recipient, amount, { from: sender })
  102. const balanceAfter1 = await torn.balanceOf(recipient)
  103. await revertSnapshot(snapshotId.result)
  104. snapshotId = await takeSnapshot()
  105. const balanceBefore2 = await torn.balanceOf(recipient)
  106. await rewardSwap.swap(recipient, amount.div(toBN(2)), { from: sender })
  107. await rewardSwap.swap(recipient, amount.div(toBN(2)), { from: sender })
  108. const balanceAfter2 = await torn.balanceOf(recipient)
  109. balanceBefore1.sub(balanceBefore2).should.be.lt.BN(delta)
  110. balanceAfter1.sub(balanceAfter2).should.be.lt.BN(delta)
  111. })
  112. describe('#swap', () => {
  113. it('should work as uniswap without vested tokens', async () => {
  114. const startTimestamp = await rewardSwap.startTimestamp()
  115. await rewardSwap.setTimestamp(startTimestamp)
  116. const expectedTokens = await rewardSwap.getExpectedReturn(amount)
  117. const formulaReturn = tornadoFormula({ balance: initialTornBalance, amount })
  118. expectedTokens.sub(formulaReturn).should.be.lte.BN(delta)
  119. const tornVirtualBalance = await rewardSwap.tornVirtualBalance()
  120. tornVirtualBalance.should.be.eq.BN(initialTornBalance)
  121. const balanceBefore = await torn.balanceOf(recipient)
  122. await rewardSwap.swap(recipient, amount, { from: sender })
  123. const balanceAfter = await torn.balanceOf(recipient)
  124. balanceAfter.should.be.eq.BN(balanceBefore.add(expectedTokens))
  125. })
  126. it('should work with vested tokens (a half of year passed)', async () => {
  127. let startTimestamp = await rewardSwap.startTimestamp()
  128. const currentTimestamp = startTimestamp.add(DURATION.div(toBN(2)))
  129. await rewardSwap.setTimestamp(currentTimestamp)
  130. const tornVirtualBalance = await rewardSwap.tornVirtualBalance()
  131. tornVirtualBalance.should.be.eq.BN(yearLiquidity.div(toBN(2)).add(initialTornBalance))
  132. const formulaReturn = tornadoFormula({ balance: tornVirtualBalance, amount })
  133. const expectedTokens = await rewardSwap.getExpectedReturn(amount)
  134. expectedTokens.sub(formulaReturn).should.be.lte.BN(delta)
  135. const balanceBefore = await torn.balanceOf(recipient)
  136. await rewardSwap.swap(recipient, amount, { from: sender })
  137. const balanceAfter = await torn.balanceOf(recipient)
  138. balanceAfter.should.be.eq.BN(balanceBefore.add(expectedTokens))
  139. })
  140. it('should not add any tokens after one year', async () => {
  141. let startTimestamp = await rewardSwap.startTimestamp()
  142. let currentTimestamp = startTimestamp.add(DURATION)
  143. await rewardSwap.setTimestamp(currentTimestamp)
  144. let tornVirtualBalance = await rewardSwap.tornVirtualBalance()
  145. tornVirtualBalance.should.be.eq.BN(miningCap)
  146. const formulaReturn = tornadoFormula({ balance: tornVirtualBalance, amount })
  147. const expectedTokens = await rewardSwap.getExpectedReturn(amount)
  148. expectedTokens.sub(formulaReturn).should.be.lte.BN(delta)
  149. currentTimestamp = currentTimestamp.add(MONTH)
  150. await rewardSwap.setTimestamp(currentTimestamp)
  151. tornVirtualBalance = await rewardSwap.tornVirtualBalance()
  152. tornVirtualBalance.should.be.eq.BN(miningCap)
  153. })
  154. })
  155. afterEach(async () => {
  156. await revertSnapshot(snapshotId.result)
  157. // eslint-disable-next-line require-atomic-updates
  158. snapshotId = await takeSnapshot()
  159. })
  160. })