diff --git a/.github/workflows/cross-chain-solana.yml b/.github/workflows/cross-chain-solana.yml new file mode 100644 index 000000000..d8c21c2ae --- /dev/null +++ b/.github/workflows/cross-chain-solana.yml @@ -0,0 +1,110 @@ +name: Cross-chain Solana + +on: + schedule: + - cron: "0 0 * * *" + push: + branches: + - main + paths: + - "cross-chain/solana/**" + - ".github/workflows/cross-chain-solana.yml" + pull_request: + +jobs: + contracts-detect-changes: + runs-on: ubuntu-latest + outputs: + path-filter: ${{ steps.filter.outputs.path-filter }} + steps: + - uses: actions/checkout@v3 + if: github.event_name == 'pull_request' + + - uses: dorny/paths-filter@v2 + if: github.event_name == 'pull_request' + id: filter + with: + filters: | + path-filter: + - './cross-chain/solana/**' + - './.github/workflows/cross-chain-solana.yml' + + contracts-build-and-test: + needs: contracts-detect-changes + if: | + github.event_name != 'pull_request' + || needs.contracts-detect-changes.outputs.path-filter == 'true' + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./cross-chain/solana + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-node@v3 + with: + node-version: "16.x" + cache: "yarn" + cache-dependency-path: cross-chain/solana/yarn.lock + + # A workaround for transitive dependencies that use the obsolete git:// + # prefix instead of the recommended https:// + - name: Configure git to not use unauthenticated protocol + run: git config --global url."https://".insteadOf git:// + + - name: Install Solana + run: | + sh -c "$(curl -sSfL https://release.solana.com/v1.16.5/install)" + export PATH="/home/runner/.local/share/solana/install/active_release/bin:$PATH" + + - name: Install Rust + run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + + - name: Install Anchor + run: | + cargo install --git https://github.com/coral-xyz/anchor --tag v0.28.0 anchor-cli --locked + + - name: Install dependencies + run: yarn install + + - name: Anchor build + run: | + export PATH="/home/runner/.local/share/solana/install/active_release/bin:$PATH" + yarn build + + # There is problem with a `solana-keygen new` command. The workaround is to create a keyfile manually just for testing purposes. + - name: Run tests + run: | + export PATH="/home/runner/.local/share/solana/install/active_release/bin:$PATH" + solana config set -u localhost + echo "[132,196,51,204,234,79,135,213,169,81,250,90,73,168,28,41,193,49,153,71,192,44,228,74,230,78,211,95,54,241,137,50,119,155,54,147,229,137,197,57,146,111,140,177,222,54,120,254,254,250,191,135,251,47,163,16,180,160,110,108,54,111,84,76]" >> ~/.config/solana/id.json + yarn test + + contracts-format: + needs: contracts-detect-changes + if: | + github.event_name == 'push' + || needs.contracts-detect-changes.outputs.path-filter == 'true' + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./cross-chain/solana + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-node@v3 + with: + node-version: "16.x" + cache: "yarn" + cache-dependency-path: cross-chain/solana/yarn.lock + + # A workaround for transitive dependencies that use the obsolete git:// + # prefix instead of the recommended https:// + - name: Configure git to not use unauthenticated protocol + run: git config --global url."https://".insteadOf git:// + + - name: Install dependencies + run: yarn install + + - name: Check formatting + run: yarn format diff --git a/cross-chain/solana/.gitignore b/cross-chain/solana/.gitignore index 8d401163f..8ab586cef 100644 --- a/cross-chain/solana/.gitignore +++ b/cross-chain/solana/.gitignore @@ -6,3 +6,4 @@ target node_modules test-ledger .yarn +solana.env diff --git a/cross-chain/solana/.prettierrc.js b/cross-chain/solana/.prettierrc.js new file mode 100644 index 000000000..89028b307 --- /dev/null +++ b/cross-chain/solana/.prettierrc.js @@ -0,0 +1,3 @@ +module.exports = { + ...require("@thesis-co/prettier-config"), +} \ No newline at end of file diff --git a/cross-chain/solana/migrations/deploy.ts b/cross-chain/solana/migrations/deploy.ts index 82fb175fa..28cf44789 100644 --- a/cross-chain/solana/migrations/deploy.ts +++ b/cross-chain/solana/migrations/deploy.ts @@ -1,12 +1,73 @@ -// Migrations are an early feature. Currently, they're nothing more than this -// single deploy script that's invoked from the CLI, injecting a provider -// configured from the workspace's Anchor.toml. - -const anchor = require("@coral-xyz/anchor"); +import * as anchor from "@coral-xyz/anchor" +import * as web3 from "@solana/web3.js" +import fs from "fs" +import { Keypair } from "@solana/web3.js" +import dotenv from "dotenv" module.exports = async function (provider) { - // Configure client to use the provider. - anchor.setProvider(provider); + dotenv.config({ path: "../solana.env" }) + + anchor.setProvider(provider) + + const program = anchor.workspace.Tbtc + // This wallet deployed the program and is also an authority + const authority = loadKey(process.env.WALLET) + const tbtcKeys = loadKey(process.env.TBTC_KEYS) + const minterKeys = loadKey(process.env.MINTER_KEYS) + + const [tbtcMintPDA] = web3.PublicKey.findProgramAddressSync( + [ + anchor.utils.bytes.utf8.encode("tbtc-mint"), + tbtcKeys.publicKey.toBuffer(), + ], + program.programId + ) + + // Initalize tbtc program + await program.methods + .initialize() + .accounts({ + tbtcMint: tbtcMintPDA, + tbtc: tbtcKeys.publicKey, + authority: authority.publicKey, + }) + .signers([tbtcKeys]) + .rpc() + + const [minterInfoPDA] = web3.PublicKey.findProgramAddressSync( + [ + anchor.utils.bytes.utf8.encode("minter-info"), + tbtcKeys.publicKey.toBuffer(), + minterKeys.publicKey.toBuffer(), + ], + program.programId + ) + + // add minter keys (minterKeys is the wormholeGateway-keypair) + await program.methods + .addMinter() + .accounts({ + tbtc: tbtcKeys.publicKey, + authority: authority.publicKey, + minter: minterKeys.publicKey, + payer: authority.publicKey, + minterInfo: minterInfoPDA, + }) + .signers([minterKeys]) + .rpc() + + // add a guardian? + + // update mappings (self, arbitrum, optimism, polygon) +} + +function loadKey(filename: string): Keypair { + try { + const contents = fs.readFileSync(filename).toString() + const bs = Uint8Array.from(JSON.parse(contents)) - // Add your deploy script here. -}; + return Keypair.fromSecretKey(bs) + } catch { + console.log("Unable to read keypair...", filename) + } +} diff --git a/cross-chain/solana/package.json b/cross-chain/solana/package.json index 4a866202d..d14822030 100644 --- a/cross-chain/solana/package.json +++ b/cross-chain/solana/package.json @@ -1,17 +1,26 @@ -{ +{ + "name": "@keep-network/tbtc-v2-solana", + "version": "1.0.0-dev", + "description": "tBTC v2 on Solana", + "license": "GPL-3.0-only", "scripts": { + "build": "anchor build", + "format": "npm run lint", + "format:fix": "npm run lint:fix", "lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w", - "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check" - }, - "dependencies": { - "@coral-xyz/anchor": "^0.28.0" + "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check", + "test": "anchor test" }, "devDependencies": { + "@coral-xyz/anchor": "^0.28.0", "@solana/spl-token": "^0.3.8", "@solana/web3.js": "^1.77.3", "@types/bn.js": "^5.1.0", "@types/chai": "^4.3.0", "@types/mocha": "^9.0.0", + "@types/node": "^18.11.18", + "@thesis-co/eslint-config": "github:thesis/eslint-config", + "dotenv": "^16.3.1", "chai": "^4.3.4", "mocha": "^9.0.3", "prettier": "^2.6.2", diff --git a/cross-chain/solana/scripts/deploy.sh b/cross-chain/solana/scripts/deploy.sh new file mode 100755 index 000000000..cfe008620 --- /dev/null +++ b/cross-chain/solana/scripts/deploy.sh @@ -0,0 +1,72 @@ +#!/bin/bash +set -eo pipefail + +help() { + echo -e "\nUsage: $0 --cluster " + + echo -e "\nCommand line arguments:\n" + echo -e "\t--cluster: \n" \ + "\t\tAvailable deployment clusters: 'mainnet-beta', 'devnet', and 'localnet'." \ + "Overrides a cluster set in Anchor.toml" + exit 1 # Exit script after printing help +} + +# Setting env variables in the current bash shell +source solana.env + +# Transform long options to short ones +for arg in "$@"; do + shift + case "$arg" in + "--cluster") set -- "$@" "-n" ;; + "--help") set -- "$@" "-h" ;; + *) set -- "$@" "$arg" ;; + esac +done + +# Parse short options +OPTIND=1 +while getopts "n:w:h" opt; do + case "$opt" in + n) CLUSTER="$OPTARG" ;; + h) help ;; + ?) help ;; # Print help in case parameter is non-existent + esac +done +shift $(expr $OPTIND - 1) # remove options from positional parameters + +[ -z "$CLUSTER" ] && { + echo "'--cluster' option not provided" >&2 + help + exit 1 +} + +[ -z "$WALLET" ] && { + echo "'WALLET' env var is not set" >&2 + exit 1 +} + +[ -z "$TBTC_KEYS" ] && { + echo "'WALLET' env var is not set" >&2 + exit 1 +} + +[ -z "$MINTER_KEYS" ] && { + echo "'MINTER_KEYS' env var is not set" >&2 + exit 1 +} + +echo "Building workspace for cluster: $CLUSTER ..." +anchor build --provider.cluster $CLUSTER + +echo "Syncing the program's id ..." +anchor keys sync + +echo "Building workspace again to include new program ID in the binary ..." +anchor build --provider.cluster $CLUSTER + +echo "Deploying program(s) for cluster: $CLUSTER ..." +anchor deploy --provider.cluster $CLUSTER --provider.wallet $WALLET + +echo "Migrating..." +anchor migrate --provider.cluster $CLUSTER --provider.wallet $WALLET diff --git a/cross-chain/solana/scripts/transfer_authority.sh b/cross-chain/solana/scripts/transfer_authority.sh new file mode 100755 index 000000000..f6489d506 --- /dev/null +++ b/cross-chain/solana/scripts/transfer_authority.sh @@ -0,0 +1,5 @@ +#!/bin/bash +set -eo pipefail + +# TODO: transfer upgrade authority to Threshold Council multisig +solana program set-upgrade-authority -k --new-upgrade-authority diff --git a/cross-chain/solana/solana.env.template b/cross-chain/solana/solana.env.template new file mode 100644 index 000000000..d2833955a --- /dev/null +++ b/cross-chain/solana/solana.env.template @@ -0,0 +1,4 @@ +WALLET= +TBTC_KEYS= +MINTER_KEYS= +UPGRADE_AUTHORITY= \ No newline at end of file