Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: E2E Test Suite

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

jobs:
e2e:
name: Run E2E Tests
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v6

- name: Setup Modern Rust Toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown

- name: Install stellar-cli
uses: stellar/setup-stellar-cli@v1
with:
version: 21.6.0

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: keeper/package-lock.json

- name: Install Keeper Dependencies
run: |
cd keeper
npm install

- name: Start Soroban Network
run: |
chmod +x scripts/start-network.sh
./scripts/start-network.sh

- name: Setup Contracts
run: |
chmod +x scripts/setup-contracts.sh
./scripts/setup-contracts.sh

- name: Run E2E Test
run: |
cd keeper
node __tests__/e2e-run.js

- name: Export Logs on Failure
if: failure()
run: |
docker logs soroban-local
145 changes: 145 additions & 0 deletions keeper/__tests__/e2e-run.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
const {
Keypair,
rpc,
Contract,
TransactionBuilder,
BASE_FEE,
Networks,

Check failure on line 7 in keeper/__tests__/e2e-run.js

View workflow job for this annotation

GitHub Actions / build-and-test (20.x, keeper)

'Networks' is assigned a value but never used

Check failure on line 7 in keeper/__tests__/e2e-run.js

View workflow job for this annotation

GitHub Actions / Lint, Test, and Coverage (20.x)

'Networks' is assigned a value but never used
Address,

Check failure on line 8 in keeper/__tests__/e2e-run.js

View workflow job for this annotation

GitHub Actions / build-and-test (20.x, keeper)

'Address' is assigned a value but never used

Check failure on line 8 in keeper/__tests__/e2e-run.js

View workflow job for this annotation

GitHub Actions / Lint, Test, and Coverage (20.x)

'Address' is assigned a value but never used
scValToNative,
} = require('@stellar/stellar-sdk');
const { spawn, execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
const dotenv = require('dotenv');

// Load .env.test from the root
const envPath = path.resolve(__dirname, '../../.env.test');
if (!fs.existsSync(envPath)) {
console.error('.env.test not found. Run scripts/setup-contracts.sh first.');
process.exit(1);
}

const envConfig = dotenv.parse(fs.readFileSync(envPath));
for (const k in envConfig) {
process.env[k] = envConfig[k];
}

const RPC_URL = process.env.SOROBAN_RPC_URL;
const NETWORK_PASSPHRASE = process.env.NETWORK_PASSPHRASE;
const CONTRACT_ID = process.env.CONTRACT_ID;
const TARGET_ID = process.env.TARGET_ID;
const CREATOR_SECRET = process.env.CREATOR_SECRET;

const server = new rpc.Server(RPC_URL);
const creatorKeypair = Keypair.fromSecret(CREATOR_SECRET);

async function registerTask() {
console.log('Registering a task using stellar-cli...');

Check failure on line 39 in keeper/__tests__/e2e-run.js

View workflow job for this annotation

GitHub Actions / build-and-test (20.x, keeper)

Trailing spaces not allowed

Check failure on line 39 in keeper/__tests__/e2e-run.js

View workflow job for this annotation

GitHub Actions / Lint, Test, and Coverage (20.x)

Trailing spaces not allowed
// Construct the command
// Note: we use --config as the argument name because that's what's in lib.rs: register(env, config)
const cmd = `stellar contract invoke \
--id ${CONTRACT_ID} \
--source creator \
--network local \
-- \
register \
--config '{
"creator": "${creatorKeypair.publicKey()}",
"target": "${TARGET_ID}",
"function": "get_token",
"args": [],
"resolver": null,
"interval": 5,
"last_run": 0,
"gas_balance": "1000",
"whitelist": [],
"is_active": true
}'`;

console.log('Executing:', cmd);
const output = execSync(cmd, { encoding: 'utf8' });
console.log('CLI Output:', output);

// Extract task ID from output (it's usually the last line or just the value)
const taskId = parseInt(output.trim(), 10);
console.log('Task ID registered:', taskId);
return taskId;
}

async function getTaskLastRun(taskId) {
const contract = new Contract(CONTRACT_ID);
const result = await server.simulateTransaction(
new TransactionBuilder(await server.getAccount(creatorKeypair.publicKey()), {
fee: BASE_FEE,
networkPassphrase: NETWORK_PASSPHRASE,
})
.addOperation(contract.call('get_task', rpc.nativeToScVal(taskId, { type: 'u64' })))
.setTimeout(30)
.build()

Check failure on line 80 in keeper/__tests__/e2e-run.js

View workflow job for this annotation

GitHub Actions / build-and-test (20.x, keeper)

Missing trailing comma

Check failure on line 80 in keeper/__tests__/e2e-run.js

View workflow job for this annotation

GitHub Actions / Lint, Test, and Coverage (20.x)

Missing trailing comma
);

Check failure on line 82 in keeper/__tests__/e2e-run.js

View workflow job for this annotation

GitHub Actions / build-and-test (20.x, keeper)

Trailing spaces not allowed

Check failure on line 82 in keeper/__tests__/e2e-run.js

View workflow job for this annotation

GitHub Actions / Lint, Test, and Coverage (20.x)

Trailing spaces not allowed
if (result.error) {
throw new Error('Failed to get task: ' + JSON.stringify(result.error));
}

const task = scValToNative(result.result.retval);
return task ? task.last_run : null;
}

async function runTest() {
try {
const taskId = await registerTask();
const initialLastRun = await getTaskLastRun(taskId);
console.log('Initial last_run:', initialLastRun);

console.log('Starting keeper process...');
// Ensure we are in the keeper directory
const keeperDir = path.resolve(__dirname, '..');

Check failure on line 100 in keeper/__tests__/e2e-run.js

View workflow job for this annotation

GitHub Actions / build-and-test (20.x, keeper)

Trailing spaces not allowed

Check failure on line 100 in keeper/__tests__/e2e-run.js

View workflow job for this annotation

GitHub Actions / Lint, Test, and Coverage (20.x)

Trailing spaces not allowed
const keeper = spawn('node', ['index.js'], {
cwd: keeperDir,
env: {

Check failure on line 103 in keeper/__tests__/e2e-run.js

View workflow job for this annotation

GitHub Actions / build-and-test (20.x, keeper)

Trailing spaces not allowed

Check failure on line 103 in keeper/__tests__/e2e-run.js

View workflow job for this annotation

GitHub Actions / Lint, Test, and Coverage (20.x)

Trailing spaces not allowed
...process.env,

Check failure on line 104 in keeper/__tests__/e2e-run.js

View workflow job for this annotation

GitHub Actions / build-and-test (20.x, keeper)

Trailing spaces not allowed

Check failure on line 104 in keeper/__tests__/e2e-run.js

View workflow job for this annotation

GitHub Actions / Lint, Test, and Coverage (20.x)

Trailing spaces not allowed
POLLING_INTERVAL_MS: '2000',
LOG_LEVEL: 'debug',
// Make sure data dir is clean for E2E
DATA_DIR: path.join(keeperDir, 'data-e2e')

Check failure on line 108 in keeper/__tests__/e2e-run.js

View workflow job for this annotation

GitHub Actions / build-and-test (20.x, keeper)

Missing trailing comma

Check failure on line 108 in keeper/__tests__/e2e-run.js

View workflow job for this annotation

GitHub Actions / Lint, Test, and Coverage (20.x)

Missing trailing comma
},
stdio: 'inherit',
});

console.log('Waiting for keeper to execute task...');
let executed = false;
const startTime = Date.now();
const timeout = 60000; // 60 seconds

while (Date.now() - startTime < timeout) {
await new Promise((r) => setTimeout(r, 5000));
const currentLastRun = await getTaskLastRun(taskId);
console.log('Current last_run:', currentLastRun);

Check failure on line 122 in keeper/__tests__/e2e-run.js

View workflow job for this annotation

GitHub Actions / build-and-test (20.x, keeper)

Trailing spaces not allowed

Check failure on line 122 in keeper/__tests__/e2e-run.js

View workflow job for this annotation

GitHub Actions / Lint, Test, and Coverage (20.x)

Trailing spaces not allowed
if (currentLastRun > initialLastRun) {
console.log('SUCCESS: Task executed!');
executed = true;
break;
}
}

keeper.kill('SIGINT');

if (!executed) {
console.error('FAILED: Task was not executed within timeout');
process.exit(1);
}

console.log('E2E Test Passed!');
process.exit(0);
} catch (err) {
console.error('Error during E2E test:', err);
process.exit(1);
}
}

runTest();
75 changes: 75 additions & 0 deletions scripts/setup-contracts.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#!/bin/bash
set -e

# Configuration
RPC_URL="http://localhost:8000/soroban/rpc"
NETWORK_PASSPHRASE="Local Sandbox Stellar Network ; September 2018"

# 1. Setup Network in CLI
echo "Configuring stellar-cli network..."
stellar network add --rpc-url "$RPC_URL" --network-passphrase "$NETWORK_PASSPHRASE" local || true

# 2. Generate and Fund Identities
echo "Generating and funding identities..."
for name in deployer keeper creator; do
stellar keys generate --network local $name || true
# Funding is usually automatic with --network local if quickstart is running,
# but we can try to fund again just in case.
# stellar keys fund $name --network local || true
done

# 3. Build Contracts
echo "Building contracts..."
(cd contract && cargo build --target wasm32-unknown-unknown --release)

WASM_PATH="contract/target/wasm32-unknown-unknown/release/soro_task_contract.wasm"

# 4. Deploy Main Contract
echo "Deploying SoroTask contract..."
CONTRACT_ID=$(stellar contract deploy \
--wasm "$WASM_PATH" \
--source deployer \
--network local)

echo "CONTRACT_ID: $CONTRACT_ID"

# 5. Deploy Mock Target Contract
echo "Deploying Mock Target contract..."
TARGET_ID=$(stellar contract deploy \
--wasm "$WASM_PATH" \
--source deployer \
--network local)

echo "TARGET_ID: $TARGET_ID"

# 6. Deploy Native Token Contract
echo "Deploying Native Token contract..."
# Note: stellar contract asset deploy --asset native is deprecated in some versions but works in 21.x.x
# Some versions use stellar contract id asset native
TOKEN_ID=$(stellar contract id asset --asset native --network local || stellar contract id asset native --network local)
stellar contract asset deploy --asset native --source deployer --network local || true
echo "TOKEN_ID: $TOKEN_ID"

# 7. Initialize Main Contract
echo "Initializing SoroTask contract..."
stellar contract invoke \
--id "$CONTRACT_ID" \
--source deployer \
--network local \
-- \
init --token "$TOKEN_ID"

# Save addresses for test
cat <<EOF > .env.test
SOROBAN_RPC_URL="$RPC_URL"
NETWORK_PASSPHRASE="$NETWORK_PASSPHRASE"
CONTRACT_ID="$CONTRACT_ID"
TARGET_ID="$TARGET_ID"
TOKEN_ID="$TOKEN_ID"
KEEPER_SECRET="$(stellar keys show keeper)"
CREATOR_SECRET="$(stellar keys show creator)"
POLLING_INTERVAL_MS=2000
LOG_LEVEL=debug
EOF

echo "Setup COMPLETE!"
41 changes: 41 additions & 0 deletions scripts/start-network.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/bin/bash
set -e

# Configuration
QUICKSTART_IMAGE="stellar/quickstart:latest"
NETWORK_NAME="soroban-local"
RPC_PORT=8000

# Cleanup old container if it exists
if [ "$(docker ps -aq -f name=$NETWORK_NAME)" ]; then
echo "Stopping and removing existing $NETWORK_NAME container..."
docker stop $NETWORK_NAME || true
docker rm $NETWORK_NAME || true
fi

echo "Starting Soroban local network (quickstart)..."
docker run -d \
--name $NETWORK_NAME \
-p $RPC_PORT:8000 \
$QUICKSTART_IMAGE \
--local \
--enable-soroban-rpc

# Wait for RPC to be ready
echo "Waiting for Soroban RPC to be ready..."
MAX_RETRIES=30
RETRY_COUNT=0
until curl -s -X POST -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":"1","method":"getNetwork"}' \
http://localhost:$RPC_PORT/soroban/rpc > /dev/null; do
RETRY_COUNT=$((RETRY_COUNT + 1))
if [ $RETRY_COUNT -ge $MAX_RETRIES ]; then
echo "Error: Soroban RPC failed to start after $MAX_RETRIES attempts."
docker logs $NETWORK_NAME
exit 1
fi
echo "Still waiting... ($RETRY_COUNT/$MAX_RETRIES)"
sleep 5
done

echo "Soroban local network is READY!"
Loading