Skip to content

Latest commit

 

History

History
504 lines (376 loc) · 12.8 KB

File metadata and controls

504 lines (376 loc) · 12.8 KB

Testing and Debugging on XPR Network

This guide covers testing smart contracts, debugging failed transactions, and resolving common issues.

Contract Testing

Local Testing Setup

# Install test dependencies
npm install --save-dev proton-tsc @proton/vert jest @types/jest ts-jest

Note: proton-tsc provides types and includes proton-asc compiler. Tests use @proton/vert for blockchain simulation.

Test Configuration

// jest.config.js
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  testMatch: ['**/tests/**/*.test.ts'],
  moduleFileExtensions: ['ts', 'js'],
};

Basic Contract Test

// tests/mycontract.test.ts
import { Blockchain, nameToBigInt, expectToThrow } from '@proton/vert';

describe('MyContract', () => {
  let blockchain: Blockchain;
  let contract: any;
  let user1: any;
  let user2: any;

  beforeAll(async () => {
    // Initialize blockchain
    blockchain = new Blockchain();

    // Create accounts
    [user1, user2] = blockchain.createAccounts('user1', 'user2');

    // Deploy contract (reads .wasm and .abi from the target dir)
    contract = blockchain.createContract('mycontract', 'assembly/target/mycontract.contract');
  });

  beforeEach(async () => {
    // Reset state between tests
    blockchain.resetTables();
  });

  test('should store value', async () => {
    await contract.actions.store(['user1', 'Hello World']).send('user1@active');

    // Check table
    const rows = contract.tables.mydata().getTableRows();
    expect(rows.length).toBe(1);
    expect(rows[0].value).toBe('Hello World');
  });

  test('should fail without auth', async () => {
    await expectToThrow(
      contract.actions.store(['user1', 'Hello']).send('user2@active'),
      'missing authority of user1'
    );
  });

  test('should validate input', async () => {
    await expectToThrow(
      contract.actions.store(['user1', '']).send('user1@active'),
      'Value cannot be empty'
    );
  });
});

Testing Token Transfers

test('should handle incoming transfer', async () => {
  // Setup token contract
  // proton-tsc ships a prebuilt eosio.token (abi + wasm) for tests
  const eosioToken = blockchain.createContract('eosio.token', 'node_modules/proton-tsc/external/eosio.token/eosio.token');

  // Create and issue tokens
  await eosioToken.actions.create(['eosio.token', '1000000.0000 XPR']).send();
  await eosioToken.actions.issue(['user1', '1000.0000 XPR', 'initial']).send();

  // Transfer to contract
  await eosioToken.actions.transfer([
    'user1', 'mycontract', '100.0000 XPR', 'deposit'
  ]).send('user1@active');

  // Verify contract received and processed it
  const deposits = contract.tables.deposits().getTableRows();
  expect(deposits[0].amount).toBe('100.0000 XPR');
});

Testing Time-Dependent Logic

test('should expire after duration', async () => {
  // Create challenge
  await contract.actions.create(['user1', '100.0000 XPR', 1, 300]).send('user1@active');

  // Advance blockchain time by 5 minutes
  blockchain.addTime(TimePointSec.from(300));  // import { TimePointSec } from '@greymass/eosio'

  // Now it should be resolvable
  await contract.actions.resolve([1]).send('resolver@active');

  const challenge = contract.tables.challenges().getTableRow(1n);
  expect(challenge.status).toBe(2); // RESOLVED
});

Testnet Testing

Setup Testnet Account

# Switch to testnet
proton chain:set proton-test

# Create testnet account (if needed)
proton account:create mytestaccount

# Get test tokens
proton faucet:claim XPR mytestaccount

Deploy to Testnet

# Build
npm run build

# Deploy
proton contract:set mytestaccount ./assembly/target

# Initialize
proton action mytestaccount init '{"owner":"mytestaccount"}' mytestaccount

Testnet Verification Checklist

  • Contract deploys without errors
  • Init action succeeds
  • All actions work as expected
  • Table data persists correctly
  • Inline actions execute
  • Notifications fire to other contracts
  • Error messages are clear

Debugging Failed Transactions

Reading Error Messages

When a transaction fails, the error message contains clues:

Error: assertion failure with message: Insufficient balance

The message after "with message:" is from your contract's check() calls.

Common Error Patterns

Error Cause Solution
missing authority of X Wrong authorization Use correct account in authorization
assertion failure with message: ... Contract validation failed Check the condition in your contract
account does not exist Invalid account name Verify account exists on chain
table not found Querying non-existent table Check contract is deployed, table name correct
your requireGet() message (C++ contracts: unable to find key) Row doesn't exist Use get() with a null check instead of requireGet()
transaction has expired, expiration is ... (expired_tx_exception) Transaction took too long Increase expireSeconds
duplicate transaction <id> Same tx submitted twice Add unique data or wait
... has insufficient objective cpu resources / ... was executing for too long (tx_cpu_usage_exceeded) Not enough CPU Stake more CPU or optimize contract
account X has insufficient ram; needs N bytes has M bytes (ram_usage_exceeded) Not enough RAM Buy more RAM

Debugging with Proton CLI

# Check transaction details
proton transaction:get TRANSACTION_ID

# Check account resources
proton account myaccount

# Check table data
proton table mycontract mytable

# Execute an action (no flags; output includes the transaction id)
proton action mycontract myaction '{}' myaccount

Debugging with Hyperion

# Get recent actions for account
curl "https://proton.eosusa.io/v2/history/get_actions?account=mycontract&limit=10"

# Get specific transaction
curl "https://proton.eosusa.io/v2/history/get_transaction?id=TX_ID"

# Filter by action name
curl "https://proton.eosusa.io/v2/history/get_actions?account=mycontract&filter=mycontract:myaction"

Using Console Logs

In contracts, use print() for debugging (visible in local tests, not on mainnet):

import { print, printi, printui } from 'proton-tsc';

@action("debug")
debugAction(value: u64): void {
  print("Starting debug action\n");
  printui(value);
  print("\n");

  // Your logic here
  const result = value * 2;
  print("Result: ");
  printui(result);
  print("\n");
}

Debugging Frontend Issues

Transaction Errors

try {
  const result = await session.transact({
    actions: [/* ... */]
  }, { broadcast: true });
} catch (error: any) {
  console.error('Transaction failed:', error);

  // Parse the error
  if (error.message.includes('assertion failure')) {
    const match = error.message.match(/assertion failure with message: (.+)/);
    const contractError = match ? match[1] : 'Unknown contract error';
    showUserError(contractError);
  } else if (error.message.includes('User cancelled')) {
    // User rejected in wallet
    showUserError('Transaction cancelled');
  } else if (error.message.includes('expired')) {
    showUserError('Transaction expired, please try again');
  } else {
    showUserError('Transaction failed: ' + error.message);
  }
}

RPC Query Debugging

async function debugTableQuery(code: string, table: string, scope: string) {
  console.log(`Querying: ${code}::${table} (scope: ${scope})`);

  try {
    const result = await rpc.get_table_rows({
      code,
      scope,
      table,
      json: true,
      limit: 10
    });

    console.log('Result:', JSON.stringify(result, null, 2));
    console.log(`Found ${result.rows.length} rows, more: ${result.more}`);

    return result.rows;
  } catch (error) {
    console.error('Query failed:', error);

    // Check if table exists
    const abi = await rpc.get_abi(code);
    const tableExists = abi.abi?.tables?.some(t => t.name === table);
    console.log(`Table "${table}" exists in ABI: ${tableExists}`);

    throw error;
  }
}

Session Debugging

// Debug session state
function debugSession(session: any) {
  console.log('Session debug:');
  console.log('  Actor:', session?.auth?.actor);
  console.log('  Permission:', session?.auth?.permission);
  console.log('  Chain ID:', session?.chainId);
  console.log('  Link type:', session?.link?.walletType);

  // Check if session is valid
  if (!session) {
    console.error('Session is null/undefined');
  } else if (!session.auth) {
    console.error('Session has no auth');
  } else if (!session.transact) {
    console.error('Session missing transact method');
  }
}

Block Explorer Debugging

Proton Scan

For any transaction, check the block explorer:

https://explorer.xprnetwork.org/tx/TRANSACTION_ID

This shows:

  • All actions in the transaction
  • Inline actions triggered
  • Table deltas (data changes)
  • CPU/NET/RAM usage
  • Error messages (if failed)

Useful Explorer Views

# Account overview
https://explorer.xprnetwork.org/account/ACCOUNT_NAME

# Contract ABI
https://explorer.xprnetwork.org/account/ACCOUNT_NAME?tab=contract

# Recent transactions
https://explorer.xprnetwork.org/account/ACCOUNT_NAME?tab=transactions

# Table data
https://explorer.xprnetwork.org/account/ACCOUNT_NAME?tab=tables

Automated Testing Script

#!/bin/bash
# test-contract.sh

set -e

CONTRACT="mycontract"
TESTNET_ACCOUNT="mytest"

echo "=== Building contract ==="
npm run build

echo "=== Deploying to testnet ==="
proton chain:set proton-test
proton contract:set $TESTNET_ACCOUNT ./assembly/target

echo "=== Running tests ==="

# Test 1: Initialize
echo "Test 1: Init"
proton action $CONTRACT init '{"owner":"'$TESTNET_ACCOUNT'"}' $TESTNET_ACCOUNT

# Test 2: Store value
echo "Test 2: Store"
proton action $CONTRACT store '{"owner":"'$TESTNET_ACCOUNT'","value":"test123"}' $TESTNET_ACCOUNT

# Test 3: Verify table
echo "Test 3: Verify"
RESULT=$(proton table $CONTRACT mydata)
if echo "$RESULT" | grep -q "test123"; then
  echo "✓ Value stored correctly"
else
  echo "✗ Value not found in table"
  exit 1
fi

# Test 4: Error case (should fail)
echo "Test 4: Error handling"
if proton action $CONTRACT store '{"owner":"'$TESTNET_ACCOUNT'","value":""}' $TESTNET_ACCOUNT 2>&1 | grep -q "assertion failure"; then
  echo "✓ Empty value correctly rejected"
else
  echo "✗ Should have rejected empty value"
  exit 1
fi

echo "=== All tests passed ==="

Performance Profiling

Measure CPU Usage

// In contract, time-intensive operations
const startTime = currentTimeMs();

// ... expensive operation ...

const elapsed = currentTimeMs() - startTime;
print(`Operation took ${elapsed}ms\n`);

Optimize Table Queries

// BAD: Iterating entire table
let total: u64 = 0;
let cursor = this.dataTable.first();
while (cursor) {
  total += cursor.value;
  cursor = this.dataTable.next(cursor);
}

// GOOD: Use a secondary index for point lookups
// getBySecondaryU64(value, indexPosition) returns ONE row (or null), not an array;
// for ranges, use lowerBound/upperBound on the index and walk with next()
const row = this.dataTable.getBySecondaryU64(owner.N, 0);  // 0 = first @secondary index
const total: u64 = row ? row.value : 0;

RAM Optimization

There is no in-contract RAM query in proton-tsc. Measure RAM per action from the outside: the explorer shows the RAM delta per action, and proton table output shows the payer of each row. Keep rows small (fixed-width fields, no unbounded strings) and let the caller pay (store(row, payer)).


CI/CD Integration

GitHub Actions Example

# .github/workflows/test.yml
name: Test Contract

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'

      - name: Install dependencies
        run: npm install

      - name: Build contract
        run: npm run build

      - name: Run tests
        run: npm test

      - name: Upload artifacts
        uses: actions/upload-artifact@v3
        with:
          name: contract-build
          path: assembly/target/

Quick Debug Checklist

When something doesn't work:

  1. Check the error message - Read it carefully
  2. Verify authorization - Is the right account signing?
  3. Check table state - Is the data what you expect?
  4. Test on testnet first - Never debug on mainnet
  5. Check ABI - Is the contract deployed with current ABI?
  6. Check resources - Does account have RAM/CPU/NET?
  7. Check block explorer - What does the transaction show?
  8. Add logging - Use print() in tests
  9. Isolate the issue - Test one thing at a time
  10. Check timestamps - Is time-dependent logic correct?