diff --git a/.github/actions/setup-stellar/action.yml b/.github/actions/setup-stellar/action.yml new file mode 100644 index 000000000..cb04ee560 --- /dev/null +++ b/.github/actions/setup-stellar/action.yml @@ -0,0 +1,93 @@ +name: Setup Stellar CLI & Network +description: Install Stellar CLI and start a local Stellar network + +runs: + using: 'composite' + steps: + - name: Install stable Rust toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Install Stellar CLI + shell: bash + run: cargo install --locked stellar-cli --version 22.2.0 --features opt + + - name: Set environment variables + id: env + shell: bash + run: | + RPC=http://localhost:8000 + echo "horizon_rpc=$RPC/" >> $GITHUB_OUTPUT + echo "soroban_rpc=$RPC/soroban/rpc" >> $GITHUB_OUTPUT + echo "friendbot=$RPC/friendbot" >> $GITHUB_OUTPUT + echo "network_passphrase='Standalone Network ; February 2017'" >> $GITHUB_OUTPUT + + - name: Start Stellar local network + shell: bash + run: | + stellar container start local --protocol-version 22 + + - name: Wait for Stellar network + shell: bash + run: | + MAX_WAIT=120 + ELAPSED=0 + + echo "Waiting for Stellar network to become ready..." + while true; do + echo "Checking Friendbot: ${{ steps.env.outputs.friendbot }} ..." + if curl -s "${{ steps.env.outputs.friendbot }}" | grep -q '"status"'; then + echo "✅ Stellar Network is ready" + exit 0 + fi + + sleep 2 + ELAPSED=$((ELAPSED + 2)) + + if [ "$ELAPSED" -ge "$MAX_WAIT" ]; then + echo "Timed out after $MAX_WAIT seconds waiting for Stellar network." + exit 1 + fi + + echo " - Stellar Network not ready yet" + + done + + - name: Add as configured network + shell: bash + run: | + stellar network add local \ + --rpc-url "${{ steps.env.outputs.soroban_rpc }}" \ + --network-passphrase "${{ steps.env.outputs.network_passphrase }}" + + - name: Prepare local.json + shell: bash + run: | + echo '{ + "chains": { + "stellar": { + "name": "Stellar", + "axelarId": "stellar", + "networkType": "local", + "chainType": "stellar", + "tokenSymbol": "XLM", + "tokenAddress": "CDMLFMKMMD7MWZP3FKUBZPVHTUEDLSX4BYGYKH4GCESXYHS3IHQ4EIG4", + "rpc": "${{ steps.env.outputs.soroban_rpc }}", + "horizonRpc": "${{ steps.env.outputs.horizon_rpc }}", + "contracts": {} + } + } + }' > ./axelar-chains-config/info/local.json + + - name: Display local.json + shell: bash + run: cat ./axelar-chains-config/info/local.json + + - name: Prepare .env + shell: bash + run: | + # Since the root account is derived from the network passphrase, it can be safely considered static. + echo "PRIVATE_KEY=SC5O7VZUXDJ6JBDSZ74DSERXL7W3Y5LTOAMRF7RQRL3TAGAPS7LUVG3L" >> .env + echo "ACCOUNT_ID=GBZXN7PIRZGNMHGA7MUUUF4GWPY5AYPV6LY4UV2GL6VJGIQRXFDNMADI" >> .env + echo "ENV=local" >> .env + echo "CHAIN=stellar" >> .env + echo "YES=true" >> .env diff --git a/.github/workflows/test-stellar.yaml b/.github/workflows/test-stellar.yaml new file mode 100644 index 000000000..31bd4f114 --- /dev/null +++ b/.github/workflows/test-stellar.yaml @@ -0,0 +1,73 @@ +name: Test Stellar + +on: pull_request + +jobs: + check-relevant-changes: + name: Check for Relevant Changes + runs-on: blacksmith-2vcpu-ubuntu-2204 + outputs: + run_tests: ${{ steps.filter.outputs.stellar == 'true' || steps.filter.outputs.common == 'true' || steps.filter.outputs.github == 'true' }} + steps: + - uses: actions/checkout@v4 + - uses: dorny/paths-filter@v3 + id: filter + with: + filters: | + stellar: + - 'stellar/**' + common: + - 'common/**' + github: + - '.github/workflows/test-stellar.yaml' + - name: Summarize Changes + run: | + echo "Changes in stellar: ${{ steps.filter.outputs.stellar }}" + echo "Changes in common: ${{ steps.filter.outputs.common }}" + echo "Changes in github: ${{ steps.filter.outputs.github }}" + + test-stellar: + name: Test Stellar + needs: check-relevant-changes + if: ${{ needs.check-relevant-changes.outputs.run_tests == 'true' }} + runs-on: blacksmith-8vcpu-ubuntu-2204 + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Checkout axelar-amplifier-stellar repo + uses: actions/checkout@v4 + with: + repository: axelarnetwork/axelar-amplifier-stellar + path: axelar-amplifier-stellar + + - name: Get latest short commit for axelar-amplifier-stellar + id: commit_hash + run: | + cd axelar-amplifier-stellar + git fetch --all + COMMIT_HASH=$(git rev-parse --short HEAD) + echo "hash=${COMMIT_HASH}" >> $GITHUB_OUTPUT + + - name: Install Node.js + uses: useblacksmith/setup-node@v5 + with: + node-version: 18.x + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Setup Stellar network + uses: ./.github/actions/setup-stellar + + ###### Command: Deploy Contract ###### + + - name: Deploy AxelarOperators + run: node stellar/deploy-contract deploy AxelarOperators --version ${{ steps.commit_hash.outputs.hash }} + + - name: Deploy AxelarGasService + run: node stellar/deploy-contract deploy AxelarGasService --version ${{ steps.commit_hash.outputs.hash }} + + - name: Deploy AxelarGateway + run: node stellar/deploy-contract deploy AxelarGateway --version ${{ steps.commit_hash.outputs.hash }} diff --git a/stellar/generate-bindings.js b/stellar/generate-bindings.js index 735c9e7d0..c46ec646f 100644 --- a/stellar/generate-bindings.js +++ b/stellar/generate-bindings.js @@ -5,13 +5,15 @@ const { execSync } = require('child_process'); const { loadConfig } = require('../evm/utils'); const path = require('path'); const { stellarCmd, getNetworkPassphrase } = require('./utils'); -const { addEnvOption } = require('../common'); +const { addEnvOption, getChainConfig } = require('../common'); const { validateParameters } = require('../common/utils'); require('./cli-utils'); -function processCommand(options, _, chain) { +function processCommand(options, config) { const { artifactPath, contractId, outputDir } = options; + const chain = getChainConfig(config, options.chainName); + validateParameters({ isValidStellarAddress: { contractId }, }); @@ -43,7 +45,7 @@ function main() { program.action((options) => { const config = loadConfig(options.env); - processCommand(options, config, config.stellar); + processCommand(options, config); }); program.parse(); diff --git a/stellar/utils.js b/stellar/utils.js index b1aa8eb82..52a170387 100644 --- a/stellar/utils.js +++ b/stellar/utils.js @@ -50,7 +50,7 @@ const VERSIONED_CUSTOM_MIGRATION_DATA_TYPES = { function getNetworkPassphrase(networkType) { switch (networkType) { case 'local': - return Networks.SANDBOX; + return Networks.STANDALONE; case 'futurenet': return Networks.FUTURENET; case 'testnet': @@ -173,7 +173,7 @@ async function sendTransaction(tx, server, action, options = {}) { } async function broadcast(operation, wallet, chain, action, options = {}, simulateTransaction = false) { - const server = new rpc.Server(chain.rpc); + const server = new rpc.Server(chain.rpc, { allowHttp: chain.networkType === 'local' }); if (options.estimateCost) { const tx = await buildTransaction(operation, server, wallet, chain.networkType, options); @@ -202,11 +202,21 @@ function getAssetCode(balance, chain) { return balance.asset_type === 'native' ? chain.tokenSymbol : balance.asset_code; } +/* + * To enable connecting to the local network, allowHttp needs to be set to true. + * This is necessary because the local network does not accept HTTPS requests. + */ +function getRpcOptions(chain) { + return { + allowHttp: chain.networkType === 'local', + }; +} + async function getWallet(chain, options) { const keypair = Keypair.fromSecret(options.privateKey); const address = keypair.publicKey(); - const provider = new rpc.Server(chain.rpc); - const horizonServer = new Horizon.Server(chain.horizonRpc); + const provider = new rpc.Server(chain.rpc, getRpcOptions(chain)); + const horizonServer = new Horizon.Server(chain.horizonRpc, getRpcOptions(chain)); const balances = await getBalances(horizonServer, address); printInfo('Wallet address', address);