globalBook.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. const { ethers } = require("hardhat")
  2. const { expect } = require("chai")
  3. const { DEBUG } = require("./constants")
  4. function print(msg) {
  5. if (DEBUG) print(msg)
  6. }
  7. async function deployStargateContracts(_chainId) {
  8. const lzEndpointContract = await ethers.getContractFactory("LZEndpointMock") // LayerZeroEndpointMock.sol
  9. const lzEndpoint = await lzEndpointContract.deploy(_chainId)
  10. await lzEndpoint.deployed()
  11. const routerContract = await ethers.getContractFactory("Router") // Router.sol
  12. const router = await routerContract.deploy()
  13. await router.deployed()
  14. const bridgeContract = await ethers.getContractFactory("Bridge") // Bridge.sol
  15. const bridge = await bridgeContract.deploy(lzEndpoint.address, router.address)
  16. await bridge.deployed()
  17. const factoryContract = await ethers.getContractFactory("Factory") // Factory.sol
  18. const factory = await factoryContract.deploy(router.address)
  19. await factory.deployed()
  20. const feeLibraryContract = await ethers.getContractFactory("StargateFeeLibraryV01") // MirrorgateFeeLibraryV01.sol
  21. const feeLibrary = await feeLibraryContract.deploy(factory.address)
  22. await feeLibrary.deployed()
  23. await factory.setDefaultFeeLibrary(feeLibrary.address)
  24. //set deploy params
  25. await (await router.setBridgeAndFactory(bridge.address, factory.address)).wait()
  26. return { factory, router, bridge, lzEndpoint, feeLibrary }
  27. }
  28. async function deployPool(_poolContract, _factory, _router, _poolId, _token, _sharedDecimals) {
  29. //create pool
  30. await _router.createPool(_poolId, _token.address, _sharedDecimals, await _token.decimals(), "x", "x*")
  31. return _poolContract.attach(await _factory.getPool(_poolId))
  32. }
  33. async function deployToken(_tokenContract, _name, _symbol, _decimals) {
  34. const token = await _tokenContract.deploy(_name, _symbol, _decimals)
  35. await token.deployed()
  36. return token
  37. }
  38. async function mintAndApproveFunds(token, signer, amount, approveRouterAddresses) {
  39. await token.mint(signer.address, amount)
  40. for (let address of approveRouterAddresses) {
  41. await token.connect(signer).approve(address, amount)
  42. }
  43. }
  44. async function bridgeStargateEndpoints(stargateEndpoints) {
  45. for (const i in stargateEndpoints) {
  46. for (const j in stargateEndpoints) {
  47. if (i === j) continue
  48. const stargateSrc = stargateEndpoints[i]
  49. const stargateDst = stargateEndpoints[j]
  50. const remoteBridge = await stargateSrc.bridge.bridgeLookup(stargateDst.chainId)
  51. if (remoteBridge === "0x") {
  52. // set it if its not set
  53. await stargateSrc.bridge.setBridge(stargateDst.chainId, stargateDst.bridge.address)
  54. }
  55. const destLzEndpoint = await stargateSrc.lzEndpoint.lzEndpointLookup(stargateDst.bridge.address)
  56. if (destLzEndpoint === "0x0000000000000000000000000000000000000000") {
  57. // set it if its not set
  58. await stargateSrc.lzEndpoint.setDestLzEndpoint(stargateDst.bridge.address, stargateDst.lzEndpoint.address)
  59. }
  60. }
  61. }
  62. }
  63. class GlobalBook {
  64. constructor() {
  65. this.stargateEndpoints = {} //chainId => stargateEndpoint
  66. this.sharedDecimals = 6
  67. this.tokenList = []
  68. }
  69. async newStargateEndpoint(_newChainId, _name, _poolsParams) {
  70. const { factory, router, bridge, lzEndpoint, feeLibrary } = await deployStargateContracts(_newChainId, _poolsParams)
  71. //create poolInfos
  72. const poolInfos = {} //id => poolInfo
  73. //contracts deployed per pool
  74. const poolContract = await ethers.getContractFactory("Pool") //Pool.sol
  75. const mockTokenContract = await ethers.getContractFactory("MockToken") // MockTokenWithDecimals.sol
  76. for (const poolParams of _poolsParams) {
  77. const { poolId: newPoolId, tokenInfo } = poolParams
  78. const token = await deployToken(mockTokenContract, tokenInfo.name, tokenInfo.symbol, tokenInfo.decimals)
  79. this.tokenList.push(token)
  80. const newPool = await deployPool(poolContract, factory, router, newPoolId, token, this.sharedDecimals)
  81. // connect exiting pools to the new pool
  82. // for each existing pool
  83. const chainPaths = []
  84. for (const dstChainId in this.stargateEndpoints) {
  85. const dstStargateEndpoint = this.stargateEndpoints[dstChainId]
  86. //for each dst pool, create chain path to new Pool
  87. for (const dstPoolId in dstStargateEndpoint.poolInfos) {
  88. await dstStargateEndpoint.router.createChainPath(dstPoolId, _newChainId, newPoolId, 1)
  89. await dstStargateEndpoint.router.activateChainPath(dstPoolId, _newChainId, newPoolId)
  90. dstStargateEndpoint.poolInfos[dstPoolId].chainPaths.push([_newChainId, newPoolId])
  91. // new -> dst
  92. await router.createChainPath(newPoolId, dstChainId, dstPoolId, 1)
  93. await router.activateChainPath(newPoolId, dstChainId, dstPoolId)
  94. chainPaths.push([dstChainId, dstPoolId])
  95. }
  96. }
  97. poolInfos[newPoolId] = {
  98. pool: newPool,
  99. token,
  100. chainPaths,
  101. lpProviders: {},
  102. }
  103. }
  104. //assemble and return stargateEndpoint
  105. const stargateEndpoint = {
  106. name: _name,
  107. chainId: _newChainId,
  108. router,
  109. bridge,
  110. lzEndpoint,
  111. poolInfos,
  112. feeLibrary,
  113. }
  114. this.stargateEndpoints[_newChainId] = stargateEndpoint
  115. //bridge new stargate with each other
  116. await bridgeStargateEndpoints(this.stargateEndpoints)
  117. return stargateEndpoint
  118. }
  119. async provisionLiquidity(_signer, _chainId, _poolId, _amountRaw) {
  120. const amount = this.amountToPoolLD(_amountRaw, _chainId, _poolId)
  121. const stargateEndpoint = this.stargateEndpoints[_chainId]
  122. const { chainPaths, lpProviders } = stargateEndpoint.poolInfos[_poolId]
  123. await stargateEndpoint.router.connect(_signer).addLiquidity(_poolId, amount, _signer.address)
  124. for (const [dstChainId, dstPoolId] of chainPaths) {
  125. await stargateEndpoint.router.connect(_signer).sendCredits(dstChainId, _poolId, dstPoolId, _signer.address)
  126. }
  127. if (!(_signer.address in lpProviders)) {
  128. this.stargateEndpoints[_chainId].poolInfos[_poolId].lpProviders[_signer.address] = _signer
  129. }
  130. await this.audit()
  131. }
  132. async amountLDtoLP(chainId, poolId, amountLD) {
  133. const stargateEndpoint = this.stargateEndpoints[chainId]
  134. const { pool } = stargateEndpoint.poolInfos[poolId]
  135. const totalLiq = await pool.totalLiquidity()
  136. const totalSup = await pool.totalSupply()
  137. const convertRate = await pool.convertRate()
  138. const amountSD = amountLD.div(convertRate)
  139. return amountSD.mul(totalSup).div(totalLiq)
  140. }
  141. async getChainPath(_poolId, _fromChainId, _toChainId, _toPoolId) {
  142. const stargateSrc = this.stargateEndpoints[_fromChainId]
  143. const { pool } = stargateSrc.poolInfos[_poolId]
  144. const cpIndex = await pool.chainPathIndexLookup(_toChainId, _toPoolId)
  145. return await pool.chainPaths(cpIndex)
  146. }
  147. async amountToPoolLD(_amountRaw, _chainId, _poolId) {
  148. const { token } = this.stargateEndpoints[_chainId].poolInfos[_poolId]
  149. const decimals = await token.decimals()
  150. return ethers.BigNumber.from(10).pow(decimals).mul(_amountRaw)
  151. }
  152. /*
  153. CONSTRAINT 1 - IFG for each pool. the total promised liquidity to chainPath is solvent
  154. => { asset - deltaCredit == sum_over_chainPath (transactionInflight + dst.Balance + src.Credit) }
  155. CONSTRAINT 2 - global solvency. all LPs can withdraw in full (considering all fees)
  156. => { sum_over_pool_of_all_chains (asset - nonLiquidityRelatedFees) == sum_over_pool_of_all_chains (totalLiquidity) }
  157. */
  158. async audit() {
  159. /*
  160. compute global metrics across all chains
  161. - globalBookedLiquidity = (pool.totalLiquidity) sum by chains
  162. - globalEstimatedLiquidity = (unallocated liquidity (deltaCredits)
  163. + allocated liquidity (lkb + credits)) sum by chains
  164. */
  165. //for each chain
  166. for (const srcChainId in this.stargateEndpoints) {
  167. const { poolInfos } = this.stargateEndpoints[srcChainId]
  168. // for each pool of the pool
  169. for (const srcPoolId in poolInfos) {
  170. //loop over chainpaths of pool
  171. const { pool, token, chainPaths } = poolInfos[srcPoolId]
  172. const tokenBalance = await token.balanceOf(pool.address)
  173. print(tokenBalance.toString())
  174. print(pool.address)
  175. print(token.address)
  176. print(`balance pre-pool-assets${(await token.balanceOf(pool.address)).toString()}`)
  177. const convertRate = await pool.convertRate()
  178. const eqFeePool = await pool.eqFeePool()
  179. const protocolFee = await pool.protocolFeeBalance()
  180. const mintFeeBalance = await pool.mintFeeBalance()
  181. const poolAssets = tokenBalance.div(convertRate).sub(eqFeePool).sub(protocolFee).sub(mintFeeBalance)
  182. // let poolAssets = tokenBalance
  183. // .div(await pool.convertRate())
  184. // .sub(await pool.eqFeePool())
  185. // .sub(await pool.protocolFeeBalance())
  186. // .sub(await pool.mintFeeBalance())
  187. print(`balance pre-delta${(await token.balanceOf(pool.address)).toString()}`)
  188. let totalQueryBalance = poolAssets.sub(await pool.deltaCredit())
  189. print(`balance post-delta ${(await token.balanceOf(pool.address)).toString()}`)
  190. let totalPromisedBalance = ethers.BigNumber.from(0)
  191. print(`balance start ${(await token.balanceOf(pool.address)).toString()}`)
  192. // for each iterate by destination chain
  193. for (const [dstChainId, dstPoolId] of chainPaths) {
  194. const dstPool = this.stargateEndpoints[dstChainId].poolInfos[dstPoolId].pool
  195. const dstCP = await dstPool.chainPaths(await dstPool.chainPathIndexLookup(srcChainId, srcPoolId))
  196. const srcCP = await pool.chainPaths(await pool.chainPathIndexLookup(dstChainId, dstPoolId))
  197. // if no msg inFlight, dstCp.lkb === srcCp.balance
  198. const transactionsInFlight = dstCP.lkb.sub(srcCP.balance)
  199. const promisedBalance = dstCP.balance.add(srcCP.credits)
  200. totalPromisedBalance = totalPromisedBalance.add(transactionsInFlight).add(promisedBalance)
  201. print(`balance ${dstChainId} ${(await token.balanceOf(pool.address)).toString()}`)
  202. }
  203. if (totalPromisedBalance.toString() !== totalQueryBalance.toString()) {
  204. print((await pool.deltaCredit()).toString())
  205. print((await pool.convertRate()).toString())
  206. const tokenBalance3 = await token.balanceOf(pool.address)
  207. print(tokenBalance3.toString())
  208. print(token.address)
  209. print((await token.balanceOf(pool.address)).toString())
  210. print("here")
  211. }
  212. /*
  213. ASSERT - IFG constraints
  214. */
  215. expect(totalPromisedBalance).to.equal(totalQueryBalance)
  216. }
  217. }
  218. }
  219. }
  220. async function toPowerOfDecimals(_amountRaw, _token) {
  221. return ethers.BigNumber.from(10)
  222. .pow(await _token.decimals())
  223. .mul(_amountRaw)
  224. }
  225. module.exports = {
  226. GlobalBook,
  227. mintAndApproveFunds,
  228. toPowerOfDecimals,
  229. }