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
178 changes: 178 additions & 0 deletions .github/workflows/performance.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
name: Performance Benchmarks

on:
push:
branches: [main]
pull_request:
paths:
- 'contracts/**'
- 'gas_thresholds.json'
- 'gas_baseline.json'

permissions:
contents: read
pull-requests: write

jobs:
gas-benchmarks:
name: Gas Usage Benchmarks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown

- name: Cache cargo
uses: Swatinem/rust-cache@v2

- name: Build WASM (release)
run: cargo build --target wasm32-unknown-unknown --release -p teachlink-contract

- name: Check WASM binary size
run: |
WASM_PATH="target/wasm32-unknown-unknown/release/teachlink_contract.wasm"
if [ -f "$WASM_PATH" ]; then
SIZE=$(stat -c%s "$WASM_PATH")
echo "WASM size: $SIZE bytes"
if [ "$SIZE" -gt 307200 ]; then
echo "::error::WASM binary size ($SIZE bytes) exceeds 300 KB threshold"
exit 1
elif [ "$SIZE" -gt 256000 ]; then
echo "::warning::WASM binary size ($SIZE bytes) approaching 300 KB limit"
fi
fi

- name: Run gas benchmarks
run: |
cargo test --release -p teachlink-contract --test test_gas_benchmarks -- --nocapture 2>&1 | tee gas_output.txt

- name: Run benchmark analysis
run: |
python3 scripts/run_gas_benchmarks.py --output gas_benchmark_report.json || true

- name: Upload benchmark report
uses: actions/upload-artifact@v4
if: always()
with:
name: gas-benchmark-report
path: |
gas_benchmark_report.json
gas_output.txt

- name: Comment PR with benchmark results
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
let report = {};
try {
report = JSON.parse(fs.readFileSync('gas_benchmark_report.json', 'utf8'));
} catch (e) {
console.log('No benchmark report found');
return;
}

const summary = report.summary || {};
const regressions = report.regressions || [];
const operations = report.operations || {};

let body = '## Gas Benchmark Results\n\n';
body += `| Metric | Value |\n|--------|-------|\n`;
body += `| Operations Tested | ${summary.total_operations || 0} |\n`;
body += `| Passed | ${summary.passed || 0} |\n`;
body += `| Failed | ${summary.failed || 0} |\n`;
body += `| Regressions | ${summary.regressions || 0} |\n\n`;

if (operations && Object.keys(operations).length > 0) {
body += '### Operation Gas Usage\n\n';
body += '| Operation | Gas Used | Threshold | Status |\n';
body += '|-----------|----------|-----------|--------|\n';
for (const [name, data] of Object.entries(operations)) {
const status = data.within_threshold ? 'Pass' : 'FAIL';
const pctChange = data.pct_change !== undefined ? ` (${data.pct_change > 0 ? '+' : ''}${data.pct_change}%)` : '';
body += `| ${name} | ${data.gas_used} | ${data.threshold}${pctChange} | ${status} |\n`;
}
body += '\n';
}

if (regressions.length > 0) {
body += '### Regressions Detected\n\n';
for (const r of regressions) {
const icon = r.severity === 'critical' ? 'CRITICAL' : 'WARNING';
body += `- **${icon}** ${r.operation}: ${r.baseline} -> ${r.current} (${r.change_pct > 0 ? '+' : ''}${r.change_pct}%)\n`;
}
body += '\n';
}

if (report.wasm_binary && report.wasm_binary.exists) {
body += `### WASM Binary Size\n\n`;
body += `- Size: ${report.wasm_binary.size_bytes} bytes\n`;
body += `- Threshold: ${report.wasm_binary.max_bytes} bytes\n`;
body += `- Status: ${report.wasm_binary.within_threshold ? 'OK' : 'EXCEEDS LIMIT'}\n\n`;
}

body += `> Generated at: ${report.generated_at || 'N/A'}\n`;

await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: body,
});

- name: Fail on critical regressions
if: always()
run: |
if [ -f gas_benchmark_report.json ]; then
FAILED=$(python3 -c "
import json, sys
try:
with open('gas_benchmark_report.json') as f:
r = json.load(f)
print(r.get('summary', {}).get('failed', 0))
except:
print(0)
")
if [ "$FAILED" -gt 0 ]; then
echo "::error::Gas benchmark regressions detected"
exit 1
fi
fi

performance-regression:
name: Performance Regression Check
runs-on: ubuntu-latest
needs: gas-benchmarks
steps:
- uses: actions/checkout@v4

- name: Download benchmark reports
uses: actions/download-artifact@v4
with:
name: gas-benchmark-report

- name: Validate against thresholds
run: |
if [ -f gas_benchmark_report.json ]; then
echo "Checking regression thresholds..."
python3 -c "
import json, sys
with open('gas_benchmark_report.json') as f:
report = json.load(f)
regressions = report.get('regressions', [])
critical = [r for r in regressions if r.get('severity') == 'critical']
if critical:
print(f'CRITICAL: {len(critical)} gas regressions found')
for r in critical:
print(f' - {r[\"operation\"]}: {r[\"change_pct\"]:+.1f}%')
sys.exit(1)
else:
print('No critical regressions detected')
"
else
echo "No benchmark report found, skipping validation"
fi
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@
**/*.rs.bk
Cargo.lock
.DS_Store

# Gas benchmark artifacts
gas_output.txt
gas_benchmark_report.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
{
"generators": {
"address": 4,
"nonce": 0,
"mux_id": 0
},
"auth": [
[],
[],
[
[
"CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M",
{
"function": {
"contract_fn": {
"contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
"function_name": "add_supported_chain",
"args": [
{
"u32": 1
}
]
}
},
"sub_invocations": []
}
]
]
],
"ledger": {
"protocol_version": 25,
"sequence_number": 0,
"timestamp": 0,
"network_id": "0000000000000000000000000000000000000000000000000000000000000000",
"base_reserve": 0,
"min_persistent_entry_ttl": 4096,
"min_temp_entry_ttl": 16,
"max_entry_ttl": 6312000,
"ledger_entries": [
{
"entry": {
"last_modified_ledger_seq": 0,
"data": {
"contract_data": {
"ext": "v0",
"contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
"key": "ledger_key_contract_instance",
"durability": "persistent",
"val": {
"contract_instance": {
"executable": {
"wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"storage": [
{
"key": {
"symbol": "admin"
},
"val": {
"address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M"
}
},
{
"key": {
"symbol": "bridgefee"
},
"val": {
"i128": "0"
}
},
{
"key": {
"symbol": "chains"
},
"val": {
"map": [
{
"key": {
"u32": 1
},
"val": {
"bool": true
}
}
]
}
},
{
"key": {
"symbol": "fee_rcpt"
},
"val": {
"address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
}
},
{
"key": {
"symbol": "min_valid"
},
"val": {
"u32": 2
}
},
{
"key": {
"symbol": "nonce"
},
"val": {
"u64": "0"
}
},
{
"key": {
"symbol": "token"
},
"val": {
"address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
}
},
{
"key": {
"symbol": "validtor"
},
"val": {
"map": []
}
}
]
}
}
}
},
"ext": "v0"
},
"live_until": 4095
},
{
"entry": {
"last_modified_ledger_seq": 0,
"data": {
"contract_data": {
"ext": "v0",
"contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M",
"key": {
"ledger_key_nonce": {
"nonce": "801925984706572462"
}
},
"durability": "temporary",
"val": "void"
}
},
"ext": "v0"
},
"live_until": 6311999
},
{
"entry": {
"last_modified_ledger_seq": 0,
"data": {
"contract_code": {
"ext": "v0",
"hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"code": ""
}
},
"ext": "v0"
},
"live_until": 4095
}
]
},
"events": []
}
Loading
Loading