From 289ed756dd4271a2b6ec4cc5122e99e2694bbb30 Mon Sep 17 00:00:00 2001 From: Michael Kim Date: Wed, 3 Apr 2024 12:07:28 +0900 Subject: [PATCH] [Relay] Add endpoints for chain information --- .../relay/src/contract/ContractManager.ts | 52 +++++++-- packages/relay/src/routers/BridgeRouter.ts | 23 ++-- packages/relay/src/routers/LedgerRouter.ts | 33 +++--- packages/relay/src/routers/TokenRouter.ts | 105 ++++++++++++++++++ packages/relay/test/Bridge.test.ts | 32 +++--- packages/relay/test/LoyaltyBridge.test.ts | 4 +- 6 files changed, 200 insertions(+), 49 deletions(-) diff --git a/packages/relay/src/contract/ContractManager.ts b/packages/relay/src/contract/ContractManager.ts index dcad8fa3..b251f746 100644 --- a/packages/relay/src/contract/ContractManager.ts +++ b/packages/relay/src/contract/ContractManager.ts @@ -16,6 +16,7 @@ import { logger } from "../common/Logger"; import { ethers } from "ethers"; import * as hre from "hardhat"; +import { HttpNetworkConfig } from "hardhat/src/types/config"; export class ContractManager { private readonly config: Config; @@ -29,17 +30,21 @@ export class ContractManager { private _sideLoyaltyExchangerContract: LoyaltyExchanger | undefined; private _sideLoyaltyTransferContract: LoyaltyTransfer | undefined; private _sideLoyaltyBridgeContract: LoyaltyBridge | undefined; - private _sideChainBridge: Bridge | undefined; + private _sideChainBridgeContract: Bridge | undefined; private _mainTokenContract: BIP20DelegatedTransfer | undefined; private _mainLoyaltyBridgeContract: Bridge | undefined; - private _mainChainBridge: Bridge | undefined; + private _mainChainBridgeContract: Bridge | undefined; private _sideChainProvider: ethers.providers.Provider | undefined; private _mainChainProvider: ethers.providers.Provider | undefined; + private _sideChainId: number | undefined; private _mainChainId: number | undefined; + private _sideChainURL: string | undefined; + private _mainChainURL: string | undefined; + constructor(config: Config) { this.config = config; } @@ -49,6 +54,10 @@ export class ContractManager { this._sideChainProvider = hre.ethers.provider; this._sideChainId = (await this._sideChainProvider.getNetwork()).chainId; + const hardhatConfig = hre.config.networks[this.config.contracts.sideChain.network] as HttpNetworkConfig; + if (hardhatConfig.url !== undefined) this._sideChainURL = hardhatConfig.url; + else this._sideChainURL = ""; + const factory1 = await hre.ethers.getContractFactory("BIP20DelegatedTransfer"); this._sideTokenContract = factory1.attach(this.config.contracts.sideChain.tokenAddress); @@ -80,10 +89,15 @@ export class ContractManager { this._sideLoyaltyBridgeContract = factory9.attach(this.config.contracts.sideChain.loyaltyBridgeAddress); const factory10 = await hre.ethers.getContractFactory("Bridge"); - this._sideChainBridge = factory10.attach(this.config.contracts.sideChain.chainBridgeAddress); + this._sideChainBridgeContract = factory10.attach(this.config.contracts.sideChain.chainBridgeAddress); await hre.changeNetwork(this.config.contracts.mainChain.network); this._mainChainProvider = hre.ethers.provider; + this._mainChainId = (await this._mainChainProvider.getNetwork()).chainId; + + const hardhatConfig2 = hre.config.networks[this.config.contracts.mainChain.network] as HttpNetworkConfig; + if (hardhatConfig2.url !== undefined) this._mainChainURL = hardhatConfig2.url; + else this._mainChainURL = ""; const factory11 = await hre.ethers.getContractFactory("BIP20DelegatedTransfer"); this._mainTokenContract = factory11.attach(this.config.contracts.mainChain.tokenAddress); @@ -92,9 +106,7 @@ export class ContractManager { this._mainLoyaltyBridgeContract = factory12.attach(this.config.contracts.mainChain.loyaltyBridgeAddress); const factory13 = await hre.ethers.getContractFactory("Bridge"); - this._mainChainBridge = factory13.attach(this.config.contracts.mainChain.chainBridgeAddress); - - this._mainChainId = (await this._mainChainProvider.getNetwork()).chainId; + this._mainChainBridgeContract = factory13.attach(this.config.contracts.mainChain.chainBridgeAddress); } public get mainChainProvider(): ethers.providers.Provider { @@ -113,6 +125,14 @@ export class ContractManager { } } + public get mainChainURL(): string { + if (this._mainChainURL !== undefined) return this._mainChainURL; + else { + logger.error("mainChainURL is not ready yet."); + process.exit(1); + } + } + public get sideChainProvider(): ethers.providers.Provider { if (this._sideChainProvider !== undefined) return this._sideChainProvider; else { @@ -129,6 +149,14 @@ export class ContractManager { } } + public get sideChainURL(): string { + if (this._sideChainURL !== undefined) return this._sideChainURL; + else { + logger.error("sideChainURL is not ready yet."); + process.exit(1); + } + } + public get sideTokenContract(): BIP20DelegatedTransfer { if (this._sideTokenContract !== undefined) return this._sideTokenContract; else { @@ -209,10 +237,10 @@ export class ContractManager { } } - public get sideChainBridge(): Bridge { - if (this._sideChainBridge !== undefined) return this._sideChainBridge; + public get sideChainBridgeContract(): Bridge { + if (this._sideChainBridgeContract !== undefined) return this._sideChainBridgeContract; else { - logger.error("sideChainBridge is not ready yet."); + logger.error("sideChainBridgeContract is not ready yet."); process.exit(1); } } @@ -233,10 +261,10 @@ export class ContractManager { } } - public get mainChainBridge(): Bridge { - if (this._mainChainBridge !== undefined) return this._mainChainBridge; + public get mainChainBridgeContract(): Bridge { + if (this._mainChainBridgeContract !== undefined) return this._mainChainBridgeContract; else { - logger.error("mainChainBridge is not ready yet."); + logger.error("mainChainBridgeContract is not ready yet."); process.exit(1); } } diff --git a/packages/relay/src/routers/BridgeRouter.ts b/packages/relay/src/routers/BridgeRouter.ts index 98ec2344..066dedb3 100644 --- a/packages/relay/src/routers/BridgeRouter.ts +++ b/packages/relay/src/routers/BridgeRouter.ts @@ -159,10 +159,17 @@ export class BridgeRouter { } } - private async getDepositId(account: string): Promise { + private async getDepositIdMainChain(account: string): Promise { while (true) { const id = ContractUtils.getRandomId(account); - if (await this.contractManager.sideChainBridge.isAvailableDepositId(id)) return id; + if (await this.contractManager.mainChainBridgeContract.isAvailableDepositId(id)) return id; + } + } + + private async getDepositIdSideChain(account: string): Promise { + while (true) { + const id = ContractUtils.getRandomId(account); + if (await this.contractManager.sideChainBridgeContract.isAvailableDepositId(id)) return id; } } @@ -186,7 +193,7 @@ export class BridgeRouter { const nonce = await this.contractManager.sideTokenContract.nonceOf(account); const message = ContractUtils.getTransferMessage( account, - this.contractManager.sideChainBridge.address, + this.contractManager.sideChainBridgeContract.address, amount, nonce, this.contractManager.sideChainId @@ -198,8 +205,8 @@ export class BridgeRouter { await this.contractManager.sideTokenContract.name(), await this.contractManager.sideTokenContract.symbol() ); - const depositId = await this.getDepositId(account); - const tx = await this.contractManager.sideChainBridge + const depositId = await this.getDepositIdSideChain(account); + const tx = await this.contractManager.sideChainBridgeContract .connect(signerItem.signer) .depositToBridge(tokenId, depositId, account, amount, signature); @@ -234,7 +241,7 @@ export class BridgeRouter { const nonce = await this.contractManager.mainTokenContract.nonceOf(account); const message = ContractUtils.getTransferMessage( account, - this.contractManager.mainChainBridge.address, + this.contractManager.mainChainBridgeContract.address, amount, nonce, this.contractManager.mainChainId @@ -246,8 +253,8 @@ export class BridgeRouter { await this.contractManager.mainTokenContract.name(), await this.contractManager.mainTokenContract.symbol() ); - const depositId = await this.getDepositId(account); - const tx = await this.contractManager.mainChainBridge + const depositId = await this.getDepositIdMainChain(account); + const tx = await this.contractManager.mainChainBridgeContract .connect(signerItem.signer) .depositToBridge(tokenId, depositId, account, amount, signature); diff --git a/packages/relay/src/routers/LedgerRouter.ts b/packages/relay/src/routers/LedgerRouter.ts index eab4491f..81292818 100644 --- a/packages/relay/src/routers/LedgerRouter.ts +++ b/packages/relay/src/routers/LedgerRouter.ts @@ -176,7 +176,7 @@ export class LedgerRouter { ); this.app.post( - "/v1/ledger/withdraw_by_bridge", + "/v1/ledger/withdraw_via_bridge", [ body("account").exists().trim().isEthereumAddress(), body("amount").exists().custom(Validation.isAmount), @@ -185,11 +185,11 @@ export class LedgerRouter { .trim() .matches(/^(0x)[0-9a-f]{130}$/i), ], - this.ledger_withdraw_by_bridge.bind(this) + this.ledger_withdraw_via_bridge.bind(this) ); this.app.post( - "/v1/ledger/deposit_by_bridge", + "/v1/ledger/deposit_via_bridge", [ body("account").exists().trim().isEthereumAddress(), body("amount").exists().custom(Validation.isAmount), @@ -198,7 +198,7 @@ export class LedgerRouter { .trim() .matches(/^(0x)[0-9a-f]{130}$/i), ], - this.ledger_deposit_by_bridge.bind(this) + this.ledger_deposit_via_bridge.bind(this) ); } @@ -624,15 +624,22 @@ export class LedgerRouter { } } - private async getDepositId(account: string): Promise { + private async getDepositIdMainChain(account: string): Promise { + while (true) { + const id = ContractUtils.getRandomId(account); + if (await this.contractManager.mainLoyaltyBridgeContract.isAvailableDepositId(id)) return id; + } + } + + private async getDepositIdSideChain(account: string): Promise { while (true) { const id = ContractUtils.getRandomId(account); if (await this.contractManager.sideLoyaltyBridgeContract.isAvailableDepositId(id)) return id; } } - private async ledger_withdraw_by_bridge(req: express.Request, res: express.Response) { - logger.http(`POST /v1/ledger/withdraw_by_bridge ${req.ip}:${JSON.stringify(req.body)}`); + private async ledger_withdraw_via_bridge(req: express.Request, res: express.Response) { + logger.http(`POST /v1/ledger/withdraw_via_bridge ${req.ip}:${JSON.stringify(req.body)}`); const errors = validationResult(req); if (!errors.isEmpty()) { @@ -663,7 +670,7 @@ export class LedgerRouter { await this.contractManager.sideTokenContract.name(), await this.contractManager.sideTokenContract.symbol() ); - const depositId = await this.getDepositId(account); + const depositId = await this.getDepositIdSideChain(account); const tx = await this.contractManager.sideLoyaltyBridgeContract .connect(signerItem.signer) .depositToBridge(tokenId, depositId, account, amount, signature); @@ -671,7 +678,7 @@ export class LedgerRouter { return res.status(200).json(this.makeResponseData(0, { tokenId, depositId, amount, txHash: tx.hash })); } catch (error: any) { const msg = ResponseMessage.getEVMErrorMessage(error); - logger.error(`POST /v1/ledger/withdraw_by_bridge : ${msg.error.message}`); + logger.error(`POST /v1/ledger/withdraw_via_bridge : ${msg.error.message}`); this.metrics.add("failure", 1); return res.status(200).json(this.makeResponseData(msg.code, undefined, msg.error)); } finally { @@ -679,8 +686,8 @@ export class LedgerRouter { } } - private async ledger_deposit_by_bridge(req: express.Request, res: express.Response) { - logger.http(`POST /v1/ledger/deposit_by_bridge ${req.ip}:${JSON.stringify(req.body)}`); + private async ledger_deposit_via_bridge(req: express.Request, res: express.Response) { + logger.http(`POST /v1/ledger/deposit_via_bridge ${req.ip}:${JSON.stringify(req.body)}`); const errors = validationResult(req); if (!errors.isEmpty()) { @@ -711,7 +718,7 @@ export class LedgerRouter { await this.contractManager.mainTokenContract.name(), await this.contractManager.mainTokenContract.symbol() ); - const depositId = await this.getDepositId(account); + const depositId = await this.getDepositIdMainChain(account); const tx = await this.contractManager.mainLoyaltyBridgeContract .connect(signerItem.signer) .depositToBridge(tokenId, depositId, account, amount, signature); @@ -719,7 +726,7 @@ export class LedgerRouter { return res.status(200).json(this.makeResponseData(0, { tokenId, depositId, amount, txHash: tx.hash })); } catch (error: any) { const msg = ResponseMessage.getEVMErrorMessage(error); - logger.error(`POST /v1/ledger/deposit_by_bridge : ${msg.error.message}`); + logger.error(`POST /v1/ledger/deposit_via_bridge : ${msg.error.message}`); this.metrics.add("failure", 1); return res.status(200).json(this.makeResponseData(msg.code, undefined, msg.error)); } finally { diff --git a/packages/relay/src/routers/TokenRouter.ts b/packages/relay/src/routers/TokenRouter.ts index 657da814..8a9c41b7 100644 --- a/packages/relay/src/routers/TokenRouter.ts +++ b/packages/relay/src/routers/TokenRouter.ts @@ -11,6 +11,8 @@ import { ContractUtils } from "../utils/ContractUtils"; import { body, param, query, validationResult } from "express-validator"; +import { AddressZero } from "@ethersproject/constants"; + import express from "express"; import { BigNumber, ethers } from "ethers"; @@ -132,6 +134,10 @@ export class TokenRouter { ], this.token_side_transfer.bind(this) ); + this.app.get("/v1/chain/main/id", [], this.chain_main_id.bind(this)); + this.app.get("/v1/chain/side/id", [], this.chain_side_id.bind(this)); + this.app.get("/v1/chain/main/info", [], this.chain_main_info.bind(this)); + this.app.get("/v1/chain/side/info", [], this.chain_side_info.bind(this)); } private async token_main_nonce(req: express.Request, res: express.Response) { @@ -273,4 +279,103 @@ export class TokenRouter { this.releaseRelaySigner(signerItem); } } + + /** + * 메인체인의 체인 아이디 + * GET /v1/chain/main/id + * @private + */ + private async chain_main_id(req: express.Request, res: express.Response) { + logger.http(`GET /v1/chain/main/id ${req.ip}:${JSON.stringify(req.params)}`); + try { + this.metrics.add("success", 1); + return res.status(200).json(this.makeResponseData(0, { chainId: this.contractManager.mainChainId })); + } catch (error: any) { + const msg = ResponseMessage.getEVMErrorMessage(error); + logger.error(`GET /v1/chain/main/id : ${msg.error.message}`); + this.metrics.add("failure", 1); + return res.status(200).json(msg); + } + } + + /** + * 사이드체인의 체인 아이디 + * GET /v1/chain/side/id + * @private + */ + private async chain_side_id(req: express.Request, res: express.Response) { + logger.http(`GET /v1/chain/side/id ${req.ip}:${JSON.stringify(req.params)}`); + try { + this.metrics.add("success", 1); + return res.status(200).json(this.makeResponseData(0, { chainId: this.contractManager.sideChainId })); + } catch (error: any) { + const msg = ResponseMessage.getEVMErrorMessage(error); + logger.error(`GET /v1/chain/side/id : ${msg.error.message}`); + this.metrics.add("failure", 1); + return res.status(200).json(msg); + } + } + + /** + * 메인체인의 체인 정보 + * GET /v1/chain/main/info + * @private + */ + private async chain_main_info(req: express.Request, res: express.Response) { + logger.http(`GET /v1/chain/main/info ${req.ip}:${JSON.stringify(req.params)}`); + try { + this.metrics.add("success", 1); + return res.status(200).json( + this.makeResponseData(0, { + url: this.contractManager.mainChainURL, + network: { + name: "main-chain", + chainId: this.contractManager.mainChainId, + ensAddress: AddressZero, + }, + contract: { + token: this.contractManager.mainTokenContract.address, + chainBridge: this.contractManager.mainChainBridgeContract.address, + loyaltyBridge: this.contractManager.mainLoyaltyBridgeContract.address, + }, + }) + ); + } catch (error: any) { + const msg = ResponseMessage.getEVMErrorMessage(error); + logger.error(`GET /v1/chain/main/info : ${msg.error.message}`); + this.metrics.add("failure", 1); + return res.status(200).json(msg); + } + } + /** + * 사이드체인의 체인 정보 + * GET /v1/chain/side/info + * @private + */ + private async chain_side_info(req: express.Request, res: express.Response) { + logger.http(`GET /v1/chain/side/info ${req.ip}:${JSON.stringify(req.params)}`); + try { + this.metrics.add("success", 1); + return res.status(200).json( + this.makeResponseData(0, { + url: this.contractManager.sideChainURL, + network: { + name: "side-chain", + chainId: this.contractManager.sideChainId, + ensAddress: AddressZero, + }, + contract: { + token: this.contractManager.sideTokenContract.address, + chainBridge: this.contractManager.sideChainBridgeContract.address, + loyaltyBridge: this.contractManager.sideLoyaltyBridgeContract.address, + }, + }) + ); + } catch (error: any) { + const msg = ResponseMessage.getEVMErrorMessage(error); + logger.error(`GET /v1/chain/side/info : ${msg.error.message}`); + this.metrics.add("failure", 1); + return res.status(200).json(msg); + } + } } diff --git a/packages/relay/test/Bridge.test.ts b/packages/relay/test/Bridge.test.ts index 6a5575af..913558f1 100644 --- a/packages/relay/test/Bridge.test.ts +++ b/packages/relay/test/Bridge.test.ts @@ -104,13 +104,15 @@ describe("Test of Bridge", function () { const amount = Amount.make(500, 18).value; const balance0 = await contractManager.mainTokenContract.balanceOf(account.address); - const balance1 = await contractManager.mainTokenContract.balanceOf(contractManager.mainChainBridge.address); + const balance1 = await contractManager.mainTokenContract.balanceOf( + contractManager.mainChainBridgeContract.address + ); const balance2 = await contractManager.sideTokenContract.balanceOf(account.address); const nonce = await contractManager.mainTokenContract.nonceOf(account.address); const message = await ContractUtils.getTransferMessage( account.address, - contractManager.mainChainBridge.address, + contractManager.mainChainBridgeContract.address, amount, nonce, contractManager.mainChainId @@ -129,25 +131,25 @@ describe("Test of Bridge", function () { expect(response.data.data.txHash).to.match(/^0x[A-Fa-f0-9]{64}$/i); /// Approve of Validators - await contractManager.sideChainBridge + await contractManager.sideChainBridgeContract .connect(deployments.accounts.bridgeValidators[0]) .withdrawFromBridge(response.data.data.tokenId, response.data.data.depositId, account.address, amount); - await contractManager.sideChainBridge + await contractManager.sideChainBridgeContract .connect(deployments.accounts.bridgeValidators[1]) .withdrawFromBridge(response.data.data.tokenId, response.data.data.depositId, account.address, amount); - await contractManager.sideChainBridge + await contractManager.sideChainBridgeContract .connect(deployments.accounts.bridgeValidators[2]) .withdrawFromBridge(response.data.data.tokenId, response.data.data.depositId, account.address, amount); /// expect(await contractManager.mainTokenContract.balanceOf(account.address)).to.deep.equal(balance0.sub(amount)); expect( - await contractManager.mainTokenContract.balanceOf(contractManager.mainChainBridge.address) + await contractManager.mainTokenContract.balanceOf(contractManager.mainChainBridgeContract.address) ).to.deep.equal(balance1.add(amount)); - const fee = await contractManager.sideChainBridge.getFee(tokenId); + const fee = await contractManager.sideChainBridgeContract.getFee(tokenId); expect(await contractManager.sideTokenContract.balanceOf(account.address)).to.deep.equal( balance2.add(amount).sub(fee) ); @@ -163,13 +165,15 @@ describe("Test of Bridge", function () { const amount = Amount.make(200, 18).value; const balance0 = await contractManager.mainTokenContract.balanceOf(account.address); - const balance1 = await contractManager.mainTokenContract.balanceOf(contractManager.mainChainBridge.address); + const balance1 = await contractManager.mainTokenContract.balanceOf( + contractManager.mainChainBridgeContract.address + ); const balance2 = await contractManager.sideTokenContract.balanceOf(account.address); const nonce = await contractManager.sideTokenContract.nonceOf(account.address); const message = await ContractUtils.getTransferMessage( account.address, - contractManager.sideChainBridge.address, + contractManager.sideChainBridgeContract.address, amount, nonce, contractManager.sideChainId @@ -188,25 +192,25 @@ describe("Test of Bridge", function () { expect(response.data.data.txHash).to.match(/^0x[A-Fa-f0-9]{64}$/i); /// Approve of Validators - await contractManager.mainChainBridge + await contractManager.mainChainBridgeContract .connect(deployments.accounts.bridgeValidators[0]) .withdrawFromBridge(response.data.data.tokenId, response.data.data.depositId, account.address, amount); - await contractManager.mainChainBridge + await contractManager.mainChainBridgeContract .connect(deployments.accounts.bridgeValidators[1]) .withdrawFromBridge(response.data.data.tokenId, response.data.data.depositId, account.address, amount); - await contractManager.mainChainBridge + await contractManager.mainChainBridgeContract .connect(deployments.accounts.bridgeValidators[2]) .withdrawFromBridge(response.data.data.tokenId, response.data.data.depositId, account.address, amount); /// - const fee = await contractManager.mainChainBridge.getFee(tokenId); + const fee = await contractManager.mainChainBridgeContract.getFee(tokenId); expect(await contractManager.mainTokenContract.balanceOf(account.address)).to.deep.equal( balance0.add(amount).sub(fee) ); expect( - await contractManager.mainTokenContract.balanceOf(contractManager.mainChainBridge.address) + await contractManager.mainTokenContract.balanceOf(contractManager.mainChainBridgeContract.address) ).to.deep.equal(balance1.sub(amount)); // expect(await contractManager.sideTokenContract.balanceOf(account.address)).to.deep.equal(balance2.sub(amount)); diff --git a/packages/relay/test/LoyaltyBridge.test.ts b/packages/relay/test/LoyaltyBridge.test.ts index a59e4998..d342a690 100644 --- a/packages/relay/test/LoyaltyBridge.test.ts +++ b/packages/relay/test/LoyaltyBridge.test.ts @@ -141,7 +141,7 @@ describe("Test of LoyaltyBridge", function () { contractManager.mainChainId ); const signature = await ContractUtils.signMessage(account, message); - const response = await client.post(URI(serverURL).directory("/v1/ledger/deposit_by_bridge").toString(), { + const response = await client.post(URI(serverURL).directory("/v1/ledger/deposit_via_bridge").toString(), { account: account.address, amount: amount.toString(), signature, @@ -202,7 +202,7 @@ describe("Test of LoyaltyBridge", function () { contractManager.sideChainId ); const signature = await ContractUtils.signMessage(account, message); - const response = await client.post(URI(serverURL).directory("/v1/ledger/withdraw_by_bridge").toString(), { + const response = await client.post(URI(serverURL).directory("/v1/ledger/withdraw_via_bridge").toString(), { account: account.address, amount: amount.toString(), signature,