diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..36c3c1a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,41 @@ +name: Build and publish image +on: + push: + branches: + - main + tags: + - '*' + +jobs: + test_build_and_push_to_docker_registry: + name: Test, build and publish image to docker hub + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + # - uses: actions/setup-node@v3 + # with: + # node-version: '20.x' + # - name: Install dependencies + # run: npm install && npm run postinstall + # - name: Unit tests + # run: npm run test + - name: Docker login + env: + DOCKER_USERNAME: ${{secrets.DOCKER_USERNAME}} + DOCKER_PASSWORD: ${{secrets.DOCKER_ACCESS_TOKEN}} + run: | + echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin + + - name: Determine Docker Image Name + id: image + run: | + if [[ "${{github.ref_name}}" == "master" ]]; then + echo "name=xinfinorg/xdc-zero:latest" >> $GITHUB_OUTPUT + else + echo "name=xinfinorg/xdc-zero:${{github.ref_name}}" >> $GITHUB_OUTPUT + fi + + - name: Docker build and tag image + run: docker build . --file cicd/Dockerfile --tag ${{ steps.image.outputs.name }} + - name: Docker push + run: docker push ${{ steps.image.outputs.name }} diff --git a/.github/workflows/pr_build.yml b/.github/workflows/pr_build.yml new file mode 100644 index 0000000..9b792e1 --- /dev/null +++ b/.github/workflows/pr_build.yml @@ -0,0 +1,50 @@ +name: Build custom branch +on: + pull_request: + branches: + - main + +jobs: + build: + if: | + ( startsWith(github.head_ref, 'feature') || + startsWith(github.head_ref, 'fix') ) + name: Deploy on PR + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v3 + + - name: check out trigger branch + run: | + git fetch origin $BRANCH + git checkout $BRANCH + env: + BRANCH: ${{ github.head_ref }} + + - name: Record branch env + id: branch + run: | + echo "repo=${{ github.repository }}" >> $GITHUB_OUTPUT + echo "branch=$(git rev-parse --abbrev-ref HEAD)" >> $GITHUB_OUTPUT + echo "commit=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT + # echo $repo + # echo $branch + # echo $commit + + - name: Docker login + env: + DOCKER_USERNAME: ${{secrets.DOCKER_USERNAME}} + DOCKER_PASSWORD: ${{secrets.DOCKER_ACCESS_TOKEN}} + run: | + echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin + + - name: Determine Docker Image Name + id: image + run: | + echo "name=xinfinorg/xdc-zero:${{ steps.branch.outputs.branch }}" >> $GITHUB_OUTPUT + + - name: Build and push image + run: | + docker build . --file cicd/Dockerfile --tag ${{ steps.image.outputs.name }} + docker push ${{ steps.image.outputs.name }} diff --git a/cicd/.env.example b/cicd/.env.example new file mode 100644 index 0000000..50c814f --- /dev/null +++ b/cicd/.env.example @@ -0,0 +1,19 @@ + +SUBNET_PK=0x1111111111111111111111111111111111111111111111111111111111111111 +PARENTNET_PK=0x2222222222222222222222222222222222222222222222222222222222222222 +CSC=0x3333333333333333333333333333333333333333 +REVERSE_CSC=0x4444444444444444444444444444444444444444 + +# PARENTNET_URL: full url or devnet,testnet +# devnet : https://devnetstats.apothem.network/devnet/ +# testnet : https://devnetstats.apothem.network/testnet/ +PARENTNET_URL=testnet +SUBNET_URL=https://devnetstats.apothem.network/subnet/ + + +# add in later steps +# SUBNET_ZERO_CONTRACT=0x3333333333333333333333333333333333333333 +# PARENTNET_ZERO_CONTRACT=0x3333333333333333333333333333333333333333 + +# SUBNET_APP=0x3333333333333333333333333333333333333333 +# PARENTNET_APP=0x3333333333333333333333333333333333333333 \ No newline at end of file diff --git a/cicd/.gitignore b/cicd/.gitignore new file mode 100644 index 0000000..cd16d23 --- /dev/null +++ b/cicd/.gitignore @@ -0,0 +1,6 @@ +node_modules +.env +package-lock.json +mount/* +!mount/placeholder.txt +yarn.lock \ No newline at end of file diff --git a/cicd/Dockerfile b/cicd/Dockerfile new file mode 100644 index 0000000..4ddb818 --- /dev/null +++ b/cicd/Dockerfile @@ -0,0 +1,14 @@ +FROM node:20-alpine + +COPY . /app + +WORKDIR /app/cicd +RUN yarn +WORKDIR /app/endpoint +RUN yarn +WORKDIR /app/applications/subswap/contract +RUN yarn + +WORKDIR /app/cicd + +ENTRYPOINT ["node"] \ No newline at end of file diff --git a/cicd/Dockerfile.dockerignore b/cicd/Dockerfile.dockerignore new file mode 100644 index 0000000..64a4213 --- /dev/null +++ b/cicd/Dockerfile.dockerignore @@ -0,0 +1,2 @@ +**node_modules +**package-lock.json \ No newline at end of file diff --git a/cicd/README.md b/cicd/README.md new file mode 100644 index 0000000..cda6a3d --- /dev/null +++ b/cicd/README.md @@ -0,0 +1,117 @@ +# XDC ZERO CICD + +## Deploy with Docker + +### 1. Deploy XDC Zero + +#### Step 1: Create a `.env` File + +Based on the provided `.env.example`, create your own `.env` file with the following details: + +- **`PARENTNET_URL`**: RPC URL for the parentnet endpoint. +- **`SUBNET_URL`**: RPC URL for the subnet. +- **`SUBNET_PK`**: Private key for the deploy subnet XDC Zero wallet. +- **`CSC`**: Checkpoint smart contract address within the subnet chain (deployed to parentnet). +- **`REVERSE_CSC`**: Checkpoint smart contract address within the parentnet chain (deployed to subnet). + +#### Step 2: Deploy Endpoints and Register Chain + +Run the following command to deploy the endpoints and register the chain: + +```sh +docker run --env-file .env xinfinorg/xdc-zero:latest endpointandregisterchain.js +``` + +Add the output to your `.env` file: + +- **`SUBNET_ZERO_CONTRACT`**: XDC ZERO contract address for subnet. +- **`PARENTNET_ZERO_CONTRACT`**: XDC ZERO contract address for parentnet. + +### 2. Deploy Subswap +Here Subswap is our default provided application, you can also deploy your own custom app. +```sh +docker run --env-file .env xinfinorg/xdc-zero:latest subswap.js +``` + +### 3. Register Application to XDC Zero + +Add the user application contract address to your `.env` file: + +- **`SUBNET_APP`**: Subnet user application address. +- **`PARENTNET_APP`**: Parentnet user application address. + +Run the following command: + +```sh +docker run --env-file .env xinfinorg/xdc-zero:latest applicationregister.js +``` + +--- + +## Deploy with Repository + +### Step 1: Install Packages + +Run the following commands to install the necessary packages: + +```sh +cd ../endpoint +yarn +cd ../applications/subswap/contract +yarn +cd cicd +yarn +``` + + +### Step 2: Create a `.env` File to `cicd/mount` + +Based on the provided `.env.example`, create your own `.env` file with the following details: + +- **`PARENTNET_URL`**: RPC URL for the parentnet endpoint. +- **`SUBNET_URL`**: RPC URL for the subnet. +- **`SUBNET_PK`**: Private key for the deploy subnet XDC Zero wallet. +- **`CSC`**: Checkpoint smart contract address within the subnet chain (deployed to parentnet). +- **`REVERSE_CSC`**: Checkpoint smart contract address within the parentnet chain (deployed to subnet). + +### Step 3: Deploy Endpoint and Register Chain + +Navigate to the `cicd` directory and run the following command: + +```sh +cd cicd +node endpointandregisterchain.js +``` + +Add the output to your `.env` file: + +- **`SUBNET_ZERO_CONTRACT`**: XDC ZERO contract address for subnet. +- **`PARENTNET_ZERO_CONTRACT`**: XDC ZERO contract address for parentnet. + +### Step 4: Deploy Subswap +Here Subswap is our default provided application, you can also deploy your own custom app. +```sh +docker run --env-file .env xinfinorg/xdc-zero:latest subswap.js +``` + +### Step 5: Register Application + +Add the user application contract address to `cicd/mount/.env`: + +- **`SUBNET_APP`**: Subnet user application address. +- **`PARENTNET_APP`**: Parentnet user application address. + +Run the following command: + +```sh +node applicationregister.js +``` + + +## There are some application example , you can feel free to deploy + +- [Sample](../applications/sample/) + +- [Subswap](../applications/subswap/) + + diff --git a/cicd/applicationregister.js b/cicd/applicationregister.js new file mode 100755 index 0000000..22fe08c --- /dev/null +++ b/cicd/applicationregister.js @@ -0,0 +1,194 @@ +process.chdir(__dirname); +const { execSync } = require("child_process"); +const fs = require("node:fs"); +const env = require("dotenv").config({ path: "mount/.env" }); +const config = { + relativePath: "../endpoint/", +}; +const endpointConfig = {}; + +const { ethers } = require("ethers"); +const u = require("./util.js"); + +main(); + +async function main() { + console.log("start application register"); + importEndpointJson(); + initApplicationRegister(); + await u.getNetworkID(config); + configureEndpointJson(); + registerApplication(); + exportEndpointJson(); +} + +function importEndpointJson() { + if (!fs.existsSync("./mount/endpointconfig.json")) { + if ( + process.env.SUBNET_ZERO_CONTRACT && + process.env.PARENTNET_ZERO_CONTRACT + ) { + config.subnetEndpoint = process.env.SUBNET_ZERO_CONTRACT; + config.parentnetEndpoint = process.env.PARENTNET_ZERO_CONTRACT; + } else { + throw Error( + "mount/endpointconfig.json not found, and SUBNET_ZERO_CONTRACT and PARENTNET_ZERO_CONTRACT are not configured" + ); + } + endpointConfig["xdcsubnet"] = { + endpoint: config.subnetEndpoint, + applications: [], + }; + endpointConfig["xdcparentnet"] = { + endpoint: config.parentnetEndpoint, + applications: [], + }; + return; + } + + const epjs = JSON.parse( + fs.readFileSync("./mount/endpointconfig.json", "utf8") + ); + if (epjs.xdcsubnet.endpoint && epjs.xdcparentnet.endpoint) { + endpointConfig["xdcsubnet"] = epjs.xdcsubnet; + endpointConfig["xdcparentnet"] = epjs.xdcparentnet; + } else { + throw Error("invalid endpointconfig.json format"); + } +} + +function initApplicationRegister() { + if (process.env.PARENTNET_URL) { + parentnetURL = process.env.PARENTNET_URL; + if (parentnetURL == "devnet") + parentnetURL = "https://devnetstats.apothem.network/devnet"; + if (parentnetURL == "testnet") + parentnetURL = "https://devnetstats.apothem.network/testnet"; + } else { + throw Error("PARENTNET_URL not found"); + } + + const reqENV = [ + "SUBNET_PK", + "PARENTNET_PK", + "SUBNET_APP", + "PARENTNET_APP", + "SUBNET_URL", + ]; + const isEnabled = reqENV.every((envVar) => envVar in process.env); + if (!isEnabled) { + throw Error( + "incomplete ENVs, require SUBNET_PK, PARENTNET_PK, SUBNET_APP, PARENTNET_APP, SUBNET_URL" + ); + } + subnetPK = process.env.SUBNET_PK.startsWith("0x") + ? process.env.SUBNET_PK + : `0x${process.env.SUBNET_PK}`; + parentnetPK = process.env.PARENTNET_PK.startsWith("0x") + ? process.env.PARENTNET_PK + : `0x${process.env.PARENTNET_PK}`; + subnetApp = process.env.SUBNET_APP.startsWith("0x") + ? process.env.SUBNET_APP + : `0x${process.env.SUBNET_APP}`; + parentnetApp = process.env.PARENTNET_APP.startsWith("0x") + ? process.env.PARENTNET_APP + : `0x${process.env.PARENTNET_APP}`; + subnetURL = process.env.SUBNET_URL; + + config["subnetPK"] = subnetPK; + config["parentnetPK"] = parentnetPK; + config["subnetURL"] = subnetURL; + config["parentnetURL"] = parentnetURL; + config["subnetApp"] = subnetApp; + config["parentnetApp"] = parentnetApp; +} + +function configureEndpointJson() { + subApp = { + rid: config.parentnetID, + rua: config.parentnetApp, + sua: config.subnetApp, + }; + parentApp = { + rid: config.subnetID, + rua: config.subnetApp, + sua: config.parentnetApp, + }; + + existingSubApps = endpointConfig.xdcsubnet.applications; + existingParentApps = endpointConfig.xdcparentnet.applications; + + if (!(Array.isArray(existingSubApps) && Array.isArray(existingParentApps))) { + endpointConfig.xdcsubnet.applications = [subApp]; + endpointConfig.xdcparentnet.applications = [parentApp]; + } else { + subDupe = false; + for (var i = 0; i < existingSubApps.length; i++) { + if ( + existingSubApps[i].rid == subApp.rid && + existingSubApps[i].rua == subApp.rua && + existingSubApps[i].sua == subApp.sua + ) { + subDupe = true; + break; + } //don't append if app already exists + } + if (!subDupe) endpointConfig.xdcsubnet.applications.push(subApp); + + parentDupe = false; + for (var i = 0; i < existingParentApps.length; i++) { + if ( + existingParentApps[i].rid == parentApp.rid && + existingParentApps[i].rua == parentApp.rua && + existingParentApps[i].sua == parentApp.sua + ) { + parentDupe = true; + break; + } //don't append if app already exists + } + if (!parentDupe) endpointConfig.xdcparentnet.applications.push(parentApp); + } + console.log("writing endpointconfig.json"); + fs.writeFileSync( + "../endpoint/endpointconfig.json", + JSON.stringify(endpointConfig, null, 2), + "utf-8", + (err) => { + if (err) { + throw Error("error writing endpointconfig.json, " + err); + } + } + ); +} + +function registerApplication() { + console.log("writing network config"); + u.writeNetworkJson(config); + console.log("configuring PK"); + u.writeEnv(config.subnetPK, config.relativePath); + console.log("register parentnet application to subnet"); + subnetEndpointOut = u.callExec( + "cd ../endpoint; npx hardhat run scripts/registerapplications.js --network xdcsubnet" + ); + if (!subnetEndpointOut.includes("success")) + throw Error("failed to register parentnet app to subnet"); + + console.log("configuring PK"); + u.writeEnv(config.parentnetPK, config.relativePath); + console.log("register subnet application to parentnet endpoint"); + parentnetEndpointOut = u.callExec( + "cd ../endpoint; npx hardhat run scripts/registerapplications.js --network xdcparentnet" + ); + if (!parentnetEndpointOut.includes("success")) + throw Error("failed to register subnet app to parentnet"); +} + +function exportEndpointJson() { + fs.copyFileSync( + "../endpoint/endpointconfig.json", + "./mount/endpointconfig.json" + ); + ep = fs.readFileSync("../endpoint/endpointconfig.json").toString(); + console.log("SUCCESS register application, endpointconfig:"); + console.log(ep); +} diff --git a/cicd/endpointandregisterchain.js b/cicd/endpointandregisterchain.js new file mode 100644 index 0000000..fe51c89 --- /dev/null +++ b/cicd/endpointandregisterchain.js @@ -0,0 +1,183 @@ +process.chdir(__dirname); +const { execSync } = require("child_process"); +const fs = require("node:fs"); +const env = require("dotenv").config({ path: "mount/.env" }); +const config = { + relativePath: "../endpoint/", +}; +const endpointConfig = {}; + +const { ethers } = require("ethers"); +const u = require("./util.js"); + +main(); + +async function main() { + console.log("start endpoint deploy and register chain"); + initEndpointDeploy(); + await u.getNetworkID(config); + deployEndpoint(); + configureEndpointJson(); + registerEndpoint(); + exportEndpointJson(); +} + +function initEndpointDeploy() { + if (process.env.PARENTNET_URL) { + parentnetURL = process.env.PARENTNET_URL; + if (parentnetURL == "devnet") + parentnetURL = "https://devnetstats.apothem.network/devnet"; + if (parentnetURL == "testnet") + parentnetURL = "https://devnetstats.apothem.network/testnet"; + } else { + throw Error("PARENTNET_URL not found"); + } + + + const reqENV = [ + "SUBNET_PK", + "PARENTNET_PK", + "SUBNET_URL", + "CSC", + "REVERSE_CSC", + ]; + const isEnabled = reqENV.every((envVar) => envVar in process.env); + if (!isEnabled) { + throw Error( + "incomplete ENVs, require SUBNET_PK, PARENTNET_PK, SUBNET_URL, CSC, REVERSE_CSC" + ); + } + subnetPK = process.env.SUBNET_PK.startsWith("0x") + ? process.env.SUBNET_PK + : `0x${process.env.SUBNET_PK}`; + parentnetPK = process.env.PARENTNET_PK.startsWith("0x") + ? process.env.PARENTNET_PK + : `0x${process.env.PARENTNET_PK}`; + csc = process.env.CSC.startsWith("0x") + ? process.env.CSC + : `0x${process.env.CSC}`; + reverseCSC = process.env.REVERSE_CSC.startsWith("0x") + ? process.env.REVERSE_CSC + : `0x${process.env.REVERSE_CSC}`; + subnetURL = process.env.SUBNET_URL; + + // return subnetURL, parentnetURL, subnetPK, parentnetPK, csc, reverseCSC + config["subnetPK"] = subnetPK; + config["parentnetPK"] = parentnetPK; + config["subnetURL"] = subnetURL; + config["parentnetURL"] = parentnetURL; + config["csc"] = csc; + config["reverseCSC"] = reverseCSC; +} + +function configureEndpointJson() { + endpointConfig["xdcsubnet"] = { + endpoint: config.subnetEndpoint, + registers: [ + { + csc: config.reverseCSC, + endpoint: config.parentnetEndpoint, + chainId: config.parentnetID, + }, + ], + applications: [], + }; + endpointConfig["xdcparentnet"] = { + endpoint: config.parentnetEndpoint, + registers: [ + { + csc: config.csc, + endpoint: config.subnetEndpoint, + chainId: config.subnetID, + }, + ], + applications: [], + }; + + console.log("writing endpointconfig.json"); + fs.writeFileSync( + "../endpoint/endpointconfig.json", + JSON.stringify(endpointConfig, null, 2), + "utf-8", + (err) => { + if (err) { + throw Error("error writing endpointconfig.json, " + err); + } + } + ); +} + +function exportEndpointJson() { + fs.copyFileSync( + "../endpoint/endpointconfig.json", + "./mount/endpointconfig.json" + ); + ep = fs.readFileSync("../endpoint/endpointconfig.json").toString(); + console.log("SUCCESS deploy endpoint and register chain, endpointconfig:"); + console.log(ep); + console.log(); + console.log("SUCCESS deploy endpoint and register chain, env:"); + console.log("SUBNET_ZERO_CONTRACT=" + config.subnetEndpoint); + console.log("PARENTNET_ZERO_CONTRACT=" + config.parentnetEndpoint); +} + +function deployEndpoint() { + console.log("writing network config"); + u.writeNetworkJson(config); + console.log("configuring PK"); + u.writeEnv(config.subnetPK, config.relativePath); + console.log("deploying subnet endpoint"); + subnetEndpointOut = u.callExec( + "cd ../endpoint; npx hardhat run scripts/endpointdeploy.js --network xdcsubnet" + ); + subnetZeroEndpoint = parseEndpointOutput(subnetEndpointOut); + + console.log("configuring PK"); + u.writeEnv(config.parentnetPK, config.relativePath); + console.log("deploying parentnet endpoint"); + parentnetEndpointOut = u.callExec( + "cd ../endpoint; npx hardhat run scripts/endpointdeploy.js --network xdcparentnet" + ); + parentnetZeroEndpoint = parseEndpointOutput(parentnetEndpointOut); + + config["subnetEndpoint"] = subnetZeroEndpoint; + config["parentnetEndpoint"] = parentnetZeroEndpoint; +} + +function registerEndpoint() { + console.log("writing network config"); + u.writeNetworkJson(config); + console.log("configuring PK"); + u.writeEnv(config.subnetPK, config.relativePath); + console.log("register parentnet to subnet endpoint"); + subnetEndpointOut = u.callExec( + "cd ../endpoint; npx hardhat run scripts/registerchain.js --network xdcsubnet" + ); + if (!subnetEndpointOut.includes("success")) + throw Error("failed to register parentnet endpoint to subnet"); + + console.log("configuring PK"); + u.writeEnv(config.parentnetPK, config.relativePath); + console.log("register subnet to parentnet endpoint"); + parentnetEndpointOut = u.callExec( + "cd ../endpoint; npx hardhat run scripts/registerchain.js --network xdcparentnet" + ); + if (!parentnetEndpointOut.includes("success")) + throw Error("failed to register subnet endpoint to parentnet"); +} + +function parseEndpointOutput(outString) { + strArr = outString.split("\n"); + lastLine = strArr[strArr.length - 1]; + if (lastLine == "") { + strArr.pop(); + lastLine = strArr[strArr.length - 1]; + } + if (lastLine.startsWith("XDCZeroEndpoint")) { + idx = lastLine.indexOf("0x"); + address = lastLine.slice(idx, idx + 42); + return address; + } else { + throw Error("invalid output string: " + outString); + } +} diff --git a/cicd/mount/placeholder.txt b/cicd/mount/placeholder.txt new file mode 100644 index 0000000..e69de29 diff --git a/cicd/package.json b/cicd/package.json new file mode 100644 index 0000000..2db1dac --- /dev/null +++ b/cicd/package.json @@ -0,0 +1,7 @@ +{ + "name": "zero-cicd", + "dependencies": { + "dotenv": "^16.3.1", + "ethers": "^5.7.2" + } +} diff --git a/cicd/subswap.js b/cicd/subswap.js new file mode 100644 index 0000000..1e03c23 --- /dev/null +++ b/cicd/subswap.js @@ -0,0 +1,146 @@ +process.chdir(__dirname); +const { execSync } = require("child_process"); +const fs = require("node:fs"); +const env = require("dotenv").config({ path: "mount/.env" }); +const config = { + relativePath: "../applications/subswap/contract/", +}; +const endpointConfig = {}; + +const { ethers } = require("ethers"); +const u = require("./util.js"); + +main(); + +async function main() { + console.log("start subswap deploy"); + checkEndpointConfig(); + initSubswapDeploy(); + deploySubswap(); + exportSubswap(); +} + +function checkEndpointConfig() { + if (!fs.existsSync("./mount/endpointconfig.json")) { + if ( + process.env.SUBNET_ZERO_CONTRACT && + process.env.PARENTNET_ZERO_CONTRACT + ) { + config.subnetEndpoint = process.env.SUBNET_ZERO_CONTRACT; + config.parentnetEndpoint = process.env.PARENTNET_ZERO_CONTRACT; + return; + } + throw Error( + "mount/endpointconfig.json not found, and SUBNET_ZERO_CONTRACT and PARENTNET_ZERO_CONTRACT are not configured" + ); + } + + const epjs = JSON.parse( + fs.readFileSync("./mount/endpointconfig.json", "utf8") + ); + if (epjs.xdcsubnet.endpoint && epjs.xdcparentnet.endpoint) { + config.subnetEndpoint = epjs.xdcsubnet.endpoint; + config.parentnetEndpoint = epjs.xdcparentnet.endpoint; + return; + } + throw Error("endpoints not found in mount/endpointconfig.json"); +} + +function initSubswapDeploy() { + if (process.env.PARENTNET_URL) { + parentnetURL = process.env.PARENTNET_URL; + if (parentnetURL == "devnet") + parentnetURL = "https://devnetstats.apothem.network/devnet"; + if (parentnetURL == "testnet") + parentnetURL = "https://devnetstats.apothem.network/testnet"; + } else { + throw Error("PARENTNET_URL not found"); + } + + const reqENV = ["SUBNET_PK", "PARENTNET_PK", "SUBNET_URL"]; + const isEnabled = reqENV.every((envVar) => envVar in process.env); + if (!isEnabled) { + throw Error("incomplete ENVs, require SUBNET_PK, PARENTNET_PK, SUBNET_URL"); + } + subnetPK = process.env.SUBNET_PK.startsWith("0x") + ? process.env.SUBNET_PK + : `0x${process.env.SUBNET_PK}`; + parentnetPK = process.env.PARENTNET_PK.startsWith("0x") + ? process.env.PARENTNET_PK + : `0x${process.env.PARENTNET_PK}`; + subnetURL = process.env.SUBNET_URL; + + config["subnetPK"] = subnetPK; + config["parentnetPK"] = parentnetPK; + config["subnetURL"] = subnetURL; + config["parentnetURL"] = parentnetURL; +} + +function deploySubswap() { + console.log("writing network config"); + u.writeNetworkJson(config); + console.log("writing deploy.config.json"); + writeSubswapDeployJson(); + + console.log("configuring PK"); + u.writeEnv(config.subnetPK, config.relativePath); + console.log("deploying subswap on subnet"); + subnetEndpointOut = u.callExec( + "cd ../applications/subswap/contract; npx hardhat run scripts/subnettreasurydeploy.js --network xdcsubnet" + ); + subnetSubswapAddr = parseEndpointOutput(subnetEndpointOut); + + console.log("configuring PK"); + u.writeEnv(config.parentnetPK, config.relativePath); + console.log("deploying subswap on parentnet"); + parentnetEndpointOut = u.callExec( + "cd ../applications/subswap/contract; npx hardhat run scripts/subnettreasurydeploy.js --network xdcparentnet" + ); + parentnetSubswapAddr = parseEndpointOutput(parentnetEndpointOut); + + config["subnetSubswap"] = subnetSubswapAddr; + config["parentnetSubswap"] = parentnetSubswapAddr; +} + +function exportSubswap() { + finalSubnet = "SUBNET_APP=" + config.subnetSubswap; + finalParentnet = "PARENTNET_APP=" + config.parentnetSubswap; + + console.log( + "SUCCESS deploy subswap. Before register application step, please include the following into your .env " + ); + console.log(finalSubnet); + console.log(finalParentnet); +} +function writeSubswapDeployJson() { + deployJson = { + subnetendpoint: config.subnetEndpoint, + parentnetendpoint: config.parentnetEndpoint, + }; + fs.writeFileSync( + "../applications/subswap/contract/deploy.config.json", + JSON.stringify(deployJson, null, 2), + "utf-8", + (err) => { + if (err) { + throw Error("error writing deploy.config.json, " + err); + } + } + ); +} + +function parseEndpointOutput(outString) { + strArr = outString.split("\n"); + lastLine = strArr[strArr.length - 1]; + if (lastLine == "") { + strArr.pop(); + lastLine = strArr[strArr.length - 1]; + } + if (lastLine.includes("0x")) { + idx = lastLine.indexOf("0x"); + address = lastLine.slice(idx, idx + 42); + return address; + } else { + throw Error("invalid output string: " + outString); + } +} diff --git a/cicd/util.js b/cicd/util.js new file mode 100644 index 0000000..d89f934 --- /dev/null +++ b/cicd/util.js @@ -0,0 +1,84 @@ +process.chdir(__dirname); +const { execSync } = require("child_process"); +const fs = require("node:fs"); +const env = require("dotenv").config({ path: "mount/.env" }); + +const { ethers } = require("ethers"); + +function writeEnv(key, path) { + content = "PRIVATE_KEY=" + key; + fullPath = path + "/" + ".env"; + fs.writeFileSync(fullPath, content, (err) => { + if (err) { + throw Error(`error writing ${fullPath}, ` + err); + } + }); +} +function writeNetworkJson(config) { + networkJson = { + xdcsubnet: config.subnetURL, + xdcparentnet: config.parentnetURL, + }; + writeJson(networkJson, config.relativePath, "network.config.json"); +} + +function writeJson(obj, path, filename) { + fullPath = path + "/" + filename; + fs.writeFileSync(fullPath, JSON.stringify(obj, null, 2), "utf-8", (err) => { + if (err) { + throw Error(`error writing ${fullPath}, ` + err); + } + }); +} + +function callExec(command) { + try { + const stdout = execSync(command, { timeout: 200000 }); + // const stdout = execSync(command) + output = stdout.toString(); + // console.log(`${stdout}`); + console.log(output); + return output; + } catch (error) { + if (error.code) { + // Spawning child process failed + if (error.code == "ETIMEDOUT") { + throw Error("Timed out (200 seconds)"); + } else { + throw Error(error); + } + } else { + // Child was spawned but exited with non-zero exit code + // Error contains any stdout and stderr from the child + // const { stdout, stderr } = error; + // console.error({ stdout, stderr }); + throw Error(error); + } + } +} + +async function getNetworkID(config) { + subnetURL = config.subnetURL; + parentnetURL = config.parentnetURL; + console.log("getting chain ID"); + const subRPC = new ethers.providers.JsonRpcProvider(subnetURL); + subID = await subRPC.getNetwork(); + subID = subID.chainId.toString(); + console.log("subnet chain ID:", subID); + + const parentRPC = new ethers.providers.JsonRpcProvider(parentnetURL); + parentID = await parentRPC.getNetwork(); + parentID = parentID.chainId.toString(); + console.log("parentnet chain ID:", parentID); + console.log(); + + config["subnetID"] = subID; + config["parentnetID"] = parentID; +} + +module.exports = { + getNetworkID, + callExec, + writeEnv, + writeNetworkJson, +}; diff --git a/endpoint/endpointconfig.example.json b/endpoint/endpointconfig.example.json index ecfbd51..ea6f507 100644 --- a/endpoint/endpointconfig.example.json +++ b/endpoint/endpointconfig.example.json @@ -1,28 +1,35 @@ { "xdcparentnet": { - "endpoint": "0xf218D129CD4a8bB153c9593bc5dFc12FAdfc44f4", + "endpoint": "0xB575c682301e20935B8C5846313a01E6f0Bd9f4B", "registers": [ { - "csc": "0x8EC22d14cD38FAB7C5AeF1d97105A6ec3FefEe5f", - "endpoint": "0xD4449Bf3f8E6a1b3fb5224F4e1Ec4288BD765547", - "chainId": "8851" + "csc": "0x3C714ffDB5A13d8d7EF0bE5f41F12bd840DA9ef1", + "endpoint": "0x0bb5a292b13C7983BFDCd62538e0e81603793342", + "chainId": "953" + } + ], + "applications": [ + { + "rid": "953", + "rua": "0xdfc2cD2b6AA7fD236e3A4Efa255A9b81c94B3fF4", + "sua": "0x40DC79697399686cd4003Ab9B7B87115bA945397" } ] }, "xdcsubnet": { - "endpoint": "0x550491BD078F7c5f78F16395b296E80F82f58700", + "endpoint": "0x0bb5a292b13C7983BFDCd62538e0e81603793342", "registers": [ { - "csc": "0x2475Dcd4Fe333bE814Ef7C8f8CE8A1E9B5FcDEA0", - "endpoint": "0x2475Dcd4Fe333bE814Ef7C8f8CE8A1E9B5FcDEA0", + "csc": "0x720A33E9c54dDf240D42f822E8b20D9C70226AAC", + "endpoint": "0xB575c682301e20935B8C5846313a01E6f0Bd9f4B", "chainId": "551" } ], "applications": [ { - "rid": "8851", - "rua": "0x5dff28627b2E4fd9516d18db6713c90d80d50f98", - "sua": "0x5dff28627b2E4fd9516d18db6713c90d80d50f98" + "rid": "551", + "rua": "0x40DC79697399686cd4003Ab9B7B87115bA945397", + "sua": "0xdfc2cD2b6AA7fD236e3A4Efa255A9b81c94B3fF4" } ] }