SwapMath.test.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. const { expect } = require("chai")
  2. const { ethers } = require("hardhat")
  3. const { DEBUG } = require("./util/constants")
  4. const abiDecoder = require("abi-decoder")
  5. const { GlobalBook, mintAndApproveFunds, toPowerOfDecimals } = require("./util/globalBook")
  6. const routerDepl = require("./abi/router.json")
  7. abiDecoder.addABI([...routerDepl.abi])
  8. // constructs:
  9. // - globalBook (GB): includes all pools deployed.
  10. // - event: any event that alters the state of the globalBook, atomically. {swap, lp++/--, stargateFee}
  11. // - delta_conf: configuration of how the delta algo runs on pool
  12. //
  13. // required:
  14. // correct state transition: assert(GB_t+1 = GB_t.apply(event, delta_conf))
  15. // - constraints derived from the delta_conf (e.g. lkb, balance, credits)
  16. // audit GB: for any t in T, assert(GB_t.audit()). specifically, it requires:
  17. // - GB is solvent for all LP + stargateFee + equilibriumFee withdrawals at anytime
  18. // - All chainpaths are solvent for IFG(instant finality guarantee)
  19. describe("SwapMath", function () {
  20. printVerbose = function (msg) {
  21. if (DEBUG) console.log(msg)
  22. }
  23. before(async function () {
  24. this.accounts = await ethers.getSigners()
  25. this.owner = this.accounts[0]
  26. this.alice = this.accounts[1]
  27. this.bob = this.accounts[2]
  28. this.carol = this.accounts[3]
  29. this.poolAId = 0
  30. this.poolBId = 1
  31. this.weight = 1
  32. this.chainAId = 1
  33. this.chainBId = 2
  34. this.chainCId = 3
  35. })
  36. //Before Each testcase
  37. // 1. Reset contract environment
  38. // 2. Provision initial user accounts
  39. // 3. Provision initial liquidity pools and credits
  40. beforeEach(async function () {
  41. //1. Reset contract environment
  42. this.globalBook = new GlobalBook()
  43. //stargateA uses poolAId with token of 6 decimals
  44. this.stargateA = await this.globalBook.newStargateEndpoint(this.chainAId, "A", [
  45. {
  46. poolId: this.poolAId,
  47. tokenInfo: {
  48. name: "MockToken1",
  49. symbol: "MT1",
  50. decimals: 6,
  51. },
  52. },
  53. ])
  54. //stargateB uses poolBId with token of 9 decimals
  55. this.stargateB = await this.globalBook.newStargateEndpoint(this.chainBId, "B", [
  56. {
  57. poolId: this.poolBId,
  58. tokenInfo: {
  59. name: "MockToken2",
  60. symbol: "MT2",
  61. decimals: 9,
  62. },
  63. },
  64. ])
  65. //stargateC uses poolAId with token of 18 decimals
  66. this.stargateC = await this.globalBook.newStargateEndpoint(this.chainCId, "C", [
  67. {
  68. poolId: this.poolAId,
  69. tokenInfo: {
  70. name: "MockToken3",
  71. symbol: "MT3",
  72. decimals: 18,
  73. },
  74. },
  75. ])
  76. // 2. Provision initial user accounts
  77. const mintAmount = 1000000
  78. for (const token of this.globalBook.tokenList) {
  79. for (let i = 1; i < 4; i++) {
  80. await mintAndApproveFunds(token, this.accounts[i], await toPowerOfDecimals(mintAmount, token), [
  81. this.stargateA.router.address,
  82. this.stargateB.router.address,
  83. this.stargateC.router.address,
  84. ])
  85. }
  86. }
  87. // //== 3. Provision initial liquiditiy
  88. // //provision chainpath A->B
  89. printVerbose("alice lp++ 5000 to A")
  90. await this.globalBook.provisionLiquidity(this.alice, this.chainAId, this.poolAId, 5000)
  91. printVerbose("alice lp++ 5000 to B")
  92. await this.globalBook.provisionLiquidity(this.alice, this.chainBId, this.poolBId, 5000)
  93. printVerbose("alice lp++ 5000 to C")
  94. await this.globalBook.provisionLiquidity(this.alice, this.chainCId, this.poolAId, 5000)
  95. })
  96. it("no fee swap test, vanilla delta", async function () {
  97. // carol do swap A->B
  98. printVerbose("carol swap 10 from A to B")
  99. await this.stargateA.router.connect(this.carol).swap(
  100. this.stargateB.chainId,
  101. this.poolAId,
  102. this.poolBId,
  103. this.carol.address, //refund address
  104. await this.globalBook.amountToPoolLD(10, this.stargateA.chainId, this.poolAId), //amount
  105. await this.globalBook.amountToPoolLD(10, this.stargateA.chainId, this.poolAId), //minimum amount
  106. { dstGasForCall: 0, dstNativeAmount: 0, dstNativeAddr: "0x" },
  107. this.carol.address, //to address
  108. "0x" //payload
  109. )
  110. await this.globalBook.audit()
  111. //carol do swap B->A
  112. printVerbose("carol swap 10 from B to A")
  113. await this.stargateB.router.connect(this.carol).swap(
  114. this.stargateA.chainId,
  115. this.poolBId,
  116. this.poolAId,
  117. this.carol.address, //refund address
  118. await this.globalBook.amountToPoolLD(10, this.stargateB.chainId, this.poolBId), //amount
  119. await this.globalBook.amountToPoolLD(10, this.stargateB.chainId, this.poolBId), //minimum amount
  120. { dstGasForCall: 0, dstNativeAmount: 0, dstNativeAddr: "0x" },
  121. this.carol.address, //to address
  122. "0x" //payload
  123. )
  124. await this.globalBook.audit()
  125. //carol do swap C->A
  126. printVerbose("carol swap 30 from C to A")
  127. await this.stargateC.router.connect(this.carol).swap(
  128. this.stargateA.chainId,
  129. this.poolAId,
  130. this.poolAId,
  131. this.carol.address, //refund address
  132. await this.globalBook.amountToPoolLD(30, this.stargateC.chainId, this.poolAId), //amount
  133. await this.globalBook.amountToPoolLD(30, this.stargateC.chainId, this.poolAId), //minimum amount
  134. { dstGasForCall: 0, dstNativeAmount: 0, dstNativeAddr: "0x" },
  135. this.carol.address, //to address
  136. "0x" //payload
  137. )
  138. await this.globalBook.audit()
  139. })
  140. it("swap test, with sg and lp fee, vanilla delta", async function () {
  141. //set fee parameters.. initiate by feeLibrary address
  142. await this.stargateA.feeLibrary.connect(this.owner).setFees(100, 100, 0, 0)
  143. await this.stargateB.feeLibrary.connect(this.owner).setFees(200, 200, 0, 0)
  144. await this.stargateC.feeLibrary.connect(this.owner).setFees(300, 300, 0, 0)
  145. //carol do swap A->B. this would FAIL cuz the spread limit too tight
  146. await expect(
  147. this.stargateA.router.connect(this.carol).swap(
  148. this.stargateB.chainId,
  149. this.poolAId,
  150. this.poolBId,
  151. this.carol.address, //refund address
  152. await this.globalBook.amountToPoolLD(1000, this.stargateA.chainId, this.poolAId), //amount
  153. await this.globalBook.amountToPoolLD(995, this.stargateA.chainId, this.poolAId), //minimum amount
  154. { dstGasForCall: 0, dstNativeAmount: 0, dstNativeAddr: "0x" },
  155. this.carol.address, //to address
  156. "0x" //payload
  157. )
  158. ).to.be.revertedWith("Stargate: slippage too high")
  159. printVerbose("carol swaps 1000 from A to B not OK")
  160. await this.globalBook.audit()
  161. printVerbose(" book audit OK")
  162. //carol do swap A->B. this would SUCCEED
  163. await this.stargateA.router.connect(this.carol).swap(
  164. this.stargateB.chainId,
  165. this.poolAId,
  166. this.poolBId,
  167. this.carol.address, //refund address
  168. await this.globalBook.amountToPoolLD(1000, this.stargateA.chainId, this.poolAId), //amount
  169. await this.globalBook.amountToPoolLD(980, this.stargateA.chainId, this.poolAId), //minimum amount
  170. { dstGasForCall: 0, dstNativeAmount: 0, dstNativeAddr: "0x" },
  171. this.carol.address, //to address
  172. "0x" //payload
  173. )
  174. printVerbose("carol swaps 1000 A to B OK")
  175. await this.globalBook.audit()
  176. printVerbose(" book audit OK")
  177. //carol do swap B->A
  178. await this.stargateB.router.connect(this.carol).swap(
  179. this.stargateA.chainId,
  180. this.poolBId,
  181. this.poolAId,
  182. this.carol.address, //refund address
  183. await this.globalBook.amountToPoolLD(1000, this.stargateB.chainId, this.poolBId), //amount
  184. await this.globalBook.amountToPoolLD(960, this.stargateB.chainId, this.poolBId), //minimum amount
  185. { dstGasForCall: 0, dstNativeAmount: 0, dstNativeAddr: "0x" },
  186. this.carol.address, //to address
  187. "0x" //payload
  188. )
  189. printVerbose("carol swaps 1000 from B to A OK")
  190. await this.globalBook.audit()
  191. printVerbose(" book audit OK")
  192. //carol do swap C->A
  193. printVerbose("carol swap 30 from C to A")
  194. await this.stargateC.router.connect(this.carol).swap(
  195. this.stargateA.chainId,
  196. this.poolAId,
  197. this.poolAId,
  198. this.carol.address, //refund address
  199. await this.globalBook.amountToPoolLD(1000, this.stargateC.chainId, this.poolAId), //amount
  200. await this.globalBook.amountToPoolLD(940, this.stargateC.chainId, this.poolAId), //minimum amount
  201. { dstGasForCall: 0, dstNativeAmount: 0, dstNativeAddr: "0x" },
  202. this.carol.address, //to address
  203. "0x" //payload
  204. )
  205. await this.globalBook.audit()
  206. printVerbose(" book audit OK")
  207. })
  208. it("swap test, with all fee, vanilla delta", async function () {
  209. //set fee parameters.. initiate by feeLibrary address
  210. await this.stargateA.feeLibrary.connect(this.owner).setFees(100, 100, 100, 0)
  211. await this.stargateB.feeLibrary.connect(this.owner).setFees(200, 200, 100, 0)
  212. await this.stargateC.feeLibrary.connect(this.owner).setFees(300, 300, 250, 0)
  213. await expect(
  214. this.stargateA.router.connect(this.carol).swap(
  215. this.stargateB.chainId,
  216. this.poolAId,
  217. this.poolBId,
  218. this.carol.address, //refund address
  219. await this.globalBook.amountToPoolLD(1000, this.stargateA.chainId, this.poolAId), //amount
  220. await this.globalBook.amountToPoolLD(971, this.stargateA.chainId, this.poolAId), //minimum amount
  221. { dstGasForCall: 0, dstNativeAmount: 0, dstNativeAddr: "0x" },
  222. this.carol.address, //to address
  223. "0x" //payload
  224. )
  225. ).to.be.revertedWith("Stargate: slippage too high")
  226. printVerbose("carol swaps 1000 from A to B not OK")
  227. await this.globalBook.audit()
  228. printVerbose(" book audit OK")
  229. //carol do swap A->B. this would SUCCEED
  230. await this.stargateA.router.connect(this.carol).swap(
  231. this.stargateB.chainId,
  232. this.poolAId,
  233. this.poolBId,
  234. this.carol.address, //refund address
  235. await this.globalBook.amountToPoolLD(1000, this.stargateA.chainId, this.poolAId), //amount
  236. await this.globalBook.amountToPoolLD(970, this.stargateA.chainId, this.poolAId), //minimum amount
  237. { dstGasForCall: 0, dstNativeAmount: 0, dstNativeAddr: "0x" },
  238. this.carol.address, //to address
  239. "0x" //payload
  240. )
  241. printVerbose("carol swaps 1000 A to B OK")
  242. await this.globalBook.audit()
  243. printVerbose(" book audit OK")
  244. //carol do swap B->A
  245. await this.stargateB.router.connect(this.carol).swap(
  246. this.stargateA.chainId,
  247. this.poolBId,
  248. this.poolAId,
  249. this.carol.address, //refund address
  250. await this.globalBook.amountToPoolLD(1000, this.stargateB.chainId, this.poolBId), //amount
  251. await this.globalBook.amountToPoolLD(800, this.stargateB.chainId, this.poolBId), //minimum amount
  252. { dstGasForCall: 0, dstNativeAmount: 0, dstNativeAddr: "0x" },
  253. this.carol.address, //to address
  254. "0x" //payload
  255. )
  256. printVerbose("carol swaps 1000 from B to A OK")
  257. await this.globalBook.audit()
  258. printVerbose(" book audit OK")
  259. //carol do swap C->A
  260. printVerbose("carol swap 30 from C to A")
  261. await this.stargateC.router.connect(this.carol).swap(
  262. this.stargateA.chainId,
  263. this.poolAId,
  264. this.poolAId,
  265. this.carol.address, //refund address
  266. await this.globalBook.amountToPoolLD(1000, this.stargateC.chainId, this.poolAId), //amount
  267. await this.globalBook.amountToPoolLD(600, this.stargateC.chainId, this.poolAId), //minimum amount
  268. { dstGasForCall: 0, dstNativeAmount: 0, dstNativeAddr: "0x" },
  269. this.carol.address, //to address
  270. "0x" //payload
  271. )
  272. await this.globalBook.audit()
  273. printVerbose(" book audit OK")
  274. })
  275. it("add in a new stargateD, alice addLiquidity, no fee, then swap()", async function () {
  276. // carol swaps to the 2 other setup chains B + C (not yet D)
  277. let qty = 120
  278. printVerbose("carol swap 120 A to B")
  279. await this.stargateA.router
  280. .connect(this.carol)
  281. .swap(
  282. this.chainBId,
  283. this.poolAId,
  284. this.poolBId,
  285. this.carol.address,
  286. await this.globalBook.amountToPoolLD(qty, this.stargateA.chainId, this.poolAId),
  287. await this.globalBook.amountToPoolLD(qty, this.stargateA.chainId, this.poolAId),
  288. { dstGasForCall: 0, dstNativeAmount: 0, dstNativeAddr: "0x" },
  289. this.carol.address,
  290. "0x"
  291. )
  292. await this.globalBook.audit()
  293. printVerbose(" book audit OK")
  294. printVerbose("carol swap 120 A to C")
  295. await this.stargateA.router
  296. .connect(this.carol)
  297. .swap(
  298. this.chainCId,
  299. this.poolAId,
  300. this.poolAId,
  301. this.carol.address,
  302. await this.globalBook.amountToPoolLD(qty, this.stargateA.chainId, this.poolAId),
  303. await this.globalBook.amountToPoolLD(qty, this.stargateA.chainId, this.poolAId),
  304. { dstGasForCall: 0, dstNativeAmount: 0, dstNativeAddr: "0x" },
  305. this.carol.address,
  306. "0x"
  307. )
  308. await this.globalBook.audit()
  309. printVerbose(" book audit OK")
  310. // create StargateD, also creates the chainPaths for the token to the other Stargates
  311. const chainDId = 4
  312. const poolDId = 2
  313. const stargateD = await this.globalBook.newStargateEndpoint(chainDId, "D", [
  314. {
  315. poolId: poolDId,
  316. tokenInfo: {
  317. name: "MockToken",
  318. symbol: "MT4",
  319. decimals: 18,
  320. },
  321. },
  322. ])
  323. // approve add liquidity to StargateD (for alice)
  324. const tokenD = stargateD.poolInfos[poolDId].token
  325. const routerAddresses = [
  326. this.stargateA.router.address,
  327. this.stargateB.router.address,
  328. this.stargateC.router.address,
  329. stargateD.router.address,
  330. ]
  331. printVerbose("mint and approve chainD token to Alice")
  332. await mintAndApproveFunds(tokenD, this.alice, await toPowerOfDecimals(10000, tokenD), routerAddresses)
  333. printVerbose("mint and approve chainD token to Alice")
  334. await mintAndApproveFunds(tokenD, this.carol, await toPowerOfDecimals(10000, tokenD), routerAddresses)
  335. await this.globalBook.provisionLiquidity(this.alice, chainDId, poolDId, 5000)
  336. printVerbose("provide liquidity to chain D OK")
  337. await this.globalBook.provisionLiquidity(this.alice, this.chainAId, this.poolAId, 5000)
  338. printVerbose("provide liquidity to chain A and send credit to D OK")
  339. // swap from StargateD to A
  340. printVerbose("carol swap 120 D to A ")
  341. await stargateD.router
  342. .connect(this.carol)
  343. .swap(
  344. this.chainAId,
  345. poolDId,
  346. this.poolAId,
  347. this.carol.address,
  348. await this.globalBook.amountToPoolLD(qty, stargateD.chainId, poolDId),
  349. await this.globalBook.amountToPoolLD(qty, stargateD.chainId, poolDId),
  350. { dstGasForCall: 0, dstNativeAmount: 0, dstNativeAddr: "0x" },
  351. this.carol.address,
  352. "0x"
  353. )
  354. await this.globalBook.audit()
  355. })
  356. })