diff --git a/.github/workflows/performance.yml b/.github/workflows/performance.yml new file mode 100644 index 0000000..c9f5fe5 --- /dev/null +++ b/.github/workflows/performance.yml @@ -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 diff --git a/.gitignore b/.gitignore index fe42cc5..b33381f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,7 @@ **/*.rs.bk Cargo.lock .DS_Store + +# Gas benchmark artifacts +gas_output.txt +gas_benchmark_report.json diff --git a/contracts/teachlink/test_snapshots/gas_bench_add_supported_chain.1.json b/contracts/teachlink/test_snapshots/gas_bench_add_supported_chain.1.json new file mode 100644 index 0000000..366effc --- /dev/null +++ b/contracts/teachlink/test_snapshots/gas_bench_add_supported_chain.1.json @@ -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": [] +} \ No newline at end of file diff --git a/contracts/teachlink/test_snapshots/gas_bench_add_validator.1.json b/contracts/teachlink/test_snapshots/gas_bench_add_validator.1.json new file mode 100644 index 0000000..f8a7df6 --- /dev/null +++ b/contracts/teachlink/test_snapshots/gas_bench_add_validator.1.json @@ -0,0 +1,174 @@ +{ + "generators": { + "address": 5, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "add_validator", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + ] + } + }, + "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": { + "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": [ + { + "key": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + "val": { + "bool": true + } + } + ] + } + } + ] + } + } + } + }, + "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": [] +} \ No newline at end of file diff --git a/contracts/teachlink/test_snapshots/gas_bench_cache_compute.1.json b/contracts/teachlink/test_snapshots/gas_bench_cache_compute.1.json new file mode 100644 index 0000000..b3295f4 --- /dev/null +++ b/contracts/teachlink/test_snapshots/gas_bench_cache_compute.1.json @@ -0,0 +1,206 @@ +{ + "generators": { + "address": 4, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [], + [] + ], + "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": { + "symbol": "fee_rcpt" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + }, + { + "key": { + "symbol": "min_valid" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "nonce" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "perf_cach" + }, + "val": { + "map": [ + { + "key": { + "symbol": "computed_at" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "health_score" + }, + "val": { + "u32": 70 + } + }, + { + "key": { + "symbol": "top_chains" + }, + "val": { + "vec": [] + } + } + ] + } + }, + { + "key": { + "symbol": "perf_ts" + }, + "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_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 4095 + } + ] + }, + "events": [ + { + "event": { + "ext": "v0", + "contract_id": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "perf_metrics_computed_event" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "computed_at" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "health_score" + }, + "val": { + "u32": 70 + } + } + ] + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/teachlink/test_snapshots/gas_bench_cache_hit.1.json b/contracts/teachlink/test_snapshots/gas_bench_cache_hit.1.json new file mode 100644 index 0000000..2e487bc --- /dev/null +++ b/contracts/teachlink/test_snapshots/gas_bench_cache_hit.1.json @@ -0,0 +1,169 @@ +{ + "generators": { + "address": 4, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [], + [], + [] + ], + "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": { + "symbol": "fee_rcpt" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + }, + { + "key": { + "symbol": "min_valid" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "nonce" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "perf_cach" + }, + "val": { + "map": [ + { + "key": { + "symbol": "computed_at" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "health_score" + }, + "val": { + "u32": 70 + } + }, + { + "key": { + "symbol": "top_chains" + }, + "val": { + "vec": [] + } + } + ] + } + }, + { + "key": { + "symbol": "perf_ts" + }, + "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_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 4095 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/contracts/teachlink/test_snapshots/gas_bench_create_audit_record.1.json b/contracts/teachlink/test_snapshots/gas_bench_create_audit_record.1.json new file mode 100644 index 0000000..335be76 --- /dev/null +++ b/contracts/teachlink/test_snapshots/gas_bench_create_audit_record.1.json @@ -0,0 +1,263 @@ +{ + "generators": { + "address": 5, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [], + [] + ], + "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": "audit_cnt" + }, + "val": { + "u64": "1" + } + }, + { + "key": { + "symbol": "audit_rec" + }, + "val": { + "map": [ + { + "key": { + "u64": "1" + }, + "val": { + "map": [ + { + "key": { + "symbol": "details" + }, + "val": { + "bytes": "61756469742064657461696c73" + } + }, + { + "key": { + "symbol": "operation_type" + }, + "val": { + "vec": [ + { + "symbol": "BridgeOut" + } + ] + } + }, + { + "key": { + "symbol": "operator" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + }, + { + "key": { + "symbol": "record_id" + }, + "val": { + "u64": "1" + } + }, + { + "key": { + "symbol": "timestamp" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "tx_hash" + }, + "val": { + "bytes": "74785f686173685f76616c7565" + } + } + ] + } + } + ] + } + }, + { + "key": { + "symbol": "bridgefee" + }, + "val": { + "i128": "0" + } + }, + { + "key": { + "symbol": "chains" + }, + "val": { + "map": [] + } + }, + { + "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_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 4095 + } + ] + }, + "events": [ + { + "event": { + "ext": "v0", + "contract_id": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "audit_record_created_event" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "operation_type" + }, + "val": { + "vec": [ + { + "symbol": "BridgeOut" + } + ] + } + }, + { + "key": { + "symbol": "operator" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + }, + { + "key": { + "symbol": "record_id" + }, + "val": { + "u64": "1" + } + }, + { + "key": { + "symbol": "timestamp" + }, + "val": { + "u64": "0" + } + } + ] + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/teachlink/test_snapshots/gas_bench_initialize.1.json b/contracts/teachlink/test_snapshots/gas_bench_initialize.1.json new file mode 100644 index 0000000..02b5b68 --- /dev/null +++ b/contracts/teachlink/test_snapshots/gas_bench_initialize.1.json @@ -0,0 +1,126 @@ +{ + "generators": { + "address": 4, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [] + ], + "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": { + "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_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 4095 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/contracts/teachlink/test_snapshots/gas_bench_initialize_mobile_profile.1.json b/contracts/teachlink/test_snapshots/gas_bench_initialize_mobile_profile.1.json new file mode 100644 index 0000000..09576d5 --- /dev/null +++ b/contracts/teachlink/test_snapshots/gas_bench_initialize_mobile_profile.1.json @@ -0,0 +1,981 @@ +{ + "generators": { + "address": 5, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "initialize_mobile_profile", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "map": [ + { + "key": { + "symbol": "device_id" + }, + "val": { + "bytes": "6465766963652d303031" + } + }, + { + "key": { + "symbol": "is_tablet" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "model" + }, + "val": { + "bytes": "6950686f6e65203134" + } + }, + { + "key": { + "symbol": "os_version" + }, + "val": { + "bytes": "694f53203137" + } + }, + { + "key": { + "symbol": "push_token" + }, + "val": { + "bytes": "707573685f746f6b656e5f616263" + } + }, + { + "key": { + "symbol": "screen_resolution" + }, + "val": { + "bytes": "313137307832353332" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "auto_download_wifi" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "custom_theme_colors" + }, + "val": { + "map": [] + } + }, + { + "key": { + "symbol": "data_saver_mode" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "font_size" + }, + "val": { + "vec": [ + { + "symbol": "Medium" + } + ] + } + }, + { + "key": { + "symbol": "gesture_navigation" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "language" + }, + "val": { + "bytes": "656e" + } + }, + { + "key": { + "symbol": "layout_density" + }, + "val": { + "vec": [ + { + "symbol": "Comfortable" + } + ] + } + }, + { + "key": { + "symbol": "sound_enabled" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "theme" + }, + "val": { + "vec": [ + { + "symbol": "System" + } + ] + } + }, + { + "key": { + "symbol": "vibration_enabled" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "video_quality" + }, + "val": { + "vec": [ + { + "symbol": "Auto" + } + ] + } + } + ] + } + ] + } + }, + "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": { + "vec": [ + { + "symbol": "mob_prof" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "accessibility_settings" + }, + "val": { + "map": [ + { + "key": { + "symbol": "color_blind_mode" + }, + "val": { + "vec": [ + { + "symbol": "None" + } + ] + } + }, + { + "key": { + "symbol": "focus_indicator_style" + }, + "val": { + "vec": [ + { + "symbol": "Default" + } + ] + } + }, + { + "key": { + "symbol": "gesture_navigation_enabled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "haptic_feedback_enabled" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "high_contrast_enabled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "large_text_enabled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "reduced_motion_enabled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "screen_reader_enabled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "voice_control_enabled" + }, + "val": { + "bool": false + } + } + ] + } + }, + { + "key": { + "symbol": "data_usage" + }, + "val": { + "map": [ + { + "key": { + "symbol": "cached_data" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "daily_limit" + }, + "val": { + "u64": "104857600" + } + }, + { + "key": { + "symbol": "last_reset" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "streaming_data" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "total_downloaded" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "total_uploaded" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "warning_threshold" + }, + "val": { + "u64": "8000" + } + } + ] + } + }, + { + "key": { + "symbol": "device_info" + }, + "val": { + "map": [ + { + "key": { + "symbol": "device_id" + }, + "val": { + "bytes": "6465766963652d303031" + } + }, + { + "key": { + "symbol": "is_tablet" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "model" + }, + "val": { + "bytes": "6950686f6e65203134" + } + }, + { + "key": { + "symbol": "os_version" + }, + "val": { + "bytes": "694f53203137" + } + }, + { + "key": { + "symbol": "push_token" + }, + "val": { + "bytes": "707573685f746f6b656e5f616263" + } + }, + { + "key": { + "symbol": "screen_resolution" + }, + "val": { + "bytes": "313137307832353332" + } + } + ] + } + }, + { + "key": { + "symbol": "last_sync" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "notification_preferences" + }, + "val": { + "map": [ + { + "key": { + "symbol": "achievement_notifications" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "content_updates" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "deadline_alerts" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "frequency_limit" + }, + "val": { + "u32": 10 + } + }, + { + "key": { + "symbol": "learning_reminders" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "led_enabled" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "quiet_hours" + }, + "val": { + "map": [ + { + "key": { + "symbol": "end_hour" + }, + "val": { + "u32": 8 + } + }, + { + "key": { + "symbol": "start_hour" + }, + "val": { + "u32": 22 + } + }, + { + "key": { + "symbol": "timezone" + }, + "val": { + "bytes": "555443" + } + } + ] + } + }, + { + "key": { + "symbol": "social_updates" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "sound_enabled" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "vibration_enabled" + }, + "val": { + "bool": true + } + } + ] + } + }, + { + "key": { + "symbol": "offline_settings" + }, + "val": { + "map": [ + { + "key": { + "symbol": "auto_download_enabled" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "compression_enabled" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "download_quality" + }, + "val": { + "vec": [ + { + "symbol": "StandardQuality" + } + ] + } + }, + { + "key": { + "symbol": "offline_duration" + }, + "val": { + "u64": "168" + } + }, + { + "key": { + "symbol": "priority_content" + }, + "val": { + "vec": [] + } + }, + { + "key": { + "symbol": "storage_limit" + }, + "val": { + "u64": "1073741824" + } + }, + { + "key": { + "symbol": "sync_strategy" + }, + "val": { + "vec": [ + { + "symbol": "WiFiOnly" + } + ] + } + } + ] + } + }, + { + "key": { + "symbol": "payment_methods" + }, + "val": { + "vec": [] + } + }, + { + "key": { + "symbol": "preferences" + }, + "val": { + "map": [ + { + "key": { + "symbol": "auto_download_wifi" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "custom_theme_colors" + }, + "val": { + "map": [] + } + }, + { + "key": { + "symbol": "data_saver_mode" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "font_size" + }, + "val": { + "vec": [ + { + "symbol": "Medium" + } + ] + } + }, + { + "key": { + "symbol": "gesture_navigation" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "language" + }, + "val": { + "bytes": "656e" + } + }, + { + "key": { + "symbol": "layout_density" + }, + "val": { + "vec": [ + { + "symbol": "Comfortable" + } + ] + } + }, + { + "key": { + "symbol": "sound_enabled" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "theme" + }, + "val": { + "vec": [ + { + "symbol": "System" + } + ] + } + }, + { + "key": { + "symbol": "vibration_enabled" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "video_quality" + }, + "val": { + "vec": [ + { + "symbol": "Auto" + } + ] + } + } + ] + } + }, + { + "key": { + "symbol": "security_settings" + }, + "val": { + "map": [ + { + "key": { + "symbol": "biometric_enabled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "biometric_type" + }, + "val": { + "vec": [ + { + "symbol": "Fingerprint" + } + ] + } + }, + { + "key": { + "symbol": "encryption_enabled" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "login_attempts" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "max_login_attempts" + }, + "val": { + "u32": 5 + } + }, + { + "key": { + "symbol": "pin_required" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "remote_wipe_enabled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "session_timeout" + }, + "val": { + "u32": 30 + } + }, + { + "key": { + "symbol": "trusted_devices" + }, + "val": { + "vec": [] + } + }, + { + "key": { + "symbol": "two_factor_enabled" + }, + "val": { + "bool": false + } + } + ] + } + }, + { + "key": { + "symbol": "user" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "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": { + "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": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "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": [] +} \ No newline at end of file diff --git a/contracts/teachlink/test_snapshots/gas_bench_mint_content_token.1.json b/contracts/teachlink/test_snapshots/gas_bench_mint_content_token.1.json new file mode 100644 index 0000000..4011793 --- /dev/null +++ b/contracts/teachlink/test_snapshots/gas_bench_mint_content_token.1.json @@ -0,0 +1,481 @@ +{ + "generators": { + "address": 5, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [], + [] + ], + "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": { + "symbol": "tok_cnt" + }, + "durability": "persistent", + "val": { + "u64": "1" + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "cnt_tok" + }, + { + "u64": "1" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "is_transferable" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "metadata" + }, + "val": { + "map": [ + { + "key": { + "symbol": "content_hash" + }, + "val": { + "bytes": "68617368313233" + } + }, + { + "key": { + "symbol": "content_type" + }, + "val": { + "vec": [ + { + "symbol": "Course" + } + ] + } + }, + { + "key": { + "symbol": "created_at" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "creator" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + }, + { + "key": { + "symbol": "description" + }, + "val": { + "bytes": "41207465737420636f7572736520666f72206761732062656e63686d61726b696e67" + } + }, + { + "key": { + "symbol": "license_type" + }, + "val": { + "bytes": "43432d4259" + } + }, + { + "key": { + "symbol": "tags" + }, + "val": { + "vec": [] + } + }, + { + "key": { + "symbol": "title" + }, + "val": { + "bytes": "5465737420436f75727365" + } + }, + { + "key": { + "symbol": "updated_at" + }, + "val": { + "u64": "0" + } + } + ] + } + }, + { + "key": { + "symbol": "minted_at" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "owner" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + }, + { + "key": { + "symbol": "royalty_percentage" + }, + "val": { + "u32": 10 + } + }, + { + "key": { + "symbol": "token_id" + }, + "val": { + "u64": "1" + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "own_tok" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + ] + }, + "durability": "persistent", + "val": { + "vec": [ + { + "u64": "1" + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "owner" + }, + { + "u64": "1" + } + ] + }, + "durability": "persistent", + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "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": { + "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_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 4095 + } + ] + }, + "events": [ + { + "event": { + "ext": "v0", + "contract_id": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "content_minted_event" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "creator" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + }, + { + "key": { + "symbol": "metadata" + }, + "val": { + "map": [ + { + "key": { + "symbol": "content_hash" + }, + "val": { + "bytes": "68617368313233" + } + }, + { + "key": { + "symbol": "content_type" + }, + "val": { + "vec": [ + { + "symbol": "Course" + } + ] + } + }, + { + "key": { + "symbol": "created_at" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "creator" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + }, + { + "key": { + "symbol": "description" + }, + "val": { + "bytes": "41207465737420636f7572736520666f72206761732062656e63686d61726b696e67" + } + }, + { + "key": { + "symbol": "license_type" + }, + "val": { + "bytes": "43432d4259" + } + }, + { + "key": { + "symbol": "tags" + }, + "val": { + "vec": [] + } + }, + { + "key": { + "symbol": "title" + }, + "val": { + "bytes": "5465737420436f75727365" + } + }, + { + "key": { + "symbol": "updated_at" + }, + "val": { + "u64": "0" + } + } + ] + } + }, + { + "key": { + "symbol": "token_id" + }, + "val": { + "u64": "1" + } + } + ] + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/teachlink/test_snapshots/gas_bench_read_queries.1.json b/contracts/teachlink/test_snapshots/gas_bench_read_queries.1.json new file mode 100644 index 0000000..15a8abe --- /dev/null +++ b/contracts/teachlink/test_snapshots/gas_bench_read_queries.1.json @@ -0,0 +1,130 @@ +{ + "generators": { + "address": 4, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [], + [], + [], + [], + [] + ], + "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": { + "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_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 4095 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/contracts/teachlink/test_snapshots/gas_bench_register_validator.1.json b/contracts/teachlink/test_snapshots/gas_bench_register_validator.1.json new file mode 100644 index 0000000..2a4ba86 --- /dev/null +++ b/contracts/teachlink/test_snapshots/gas_bench_register_validator.1.json @@ -0,0 +1,371 @@ +{ + "generators": { + "address": 5, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "register_validator", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "i128": "100000000" + } + ] + } + }, + "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": { + "symbol": "cons_st" + }, + "val": { + "map": [ + { + "key": { + "symbol": "active_validators" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "byzantine_threshold" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "last_consensus_round" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "total_stake" + }, + "val": { + "i128": "100000000" + } + } + ] + } + }, + { + "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": "val_info" + }, + "val": { + "map": [ + { + "key": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + "val": { + "map": [ + { + "key": { + "symbol": "address" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + }, + { + "key": { + "symbol": "is_active" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "joined_at" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "last_activity" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "missed_validations" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "reputation_score" + }, + "val": { + "u32": 100 + } + }, + { + "key": { + "symbol": "slashed_amount" + }, + "val": { + "i128": "0" + } + }, + { + "key": { + "symbol": "stake" + }, + "val": { + "i128": "100000000" + } + }, + { + "key": { + "symbol": "total_validations" + }, + "val": { + "u64": "0" + } + } + ] + } + } + ] + } + }, + { + "key": { + "symbol": "val_stake" + }, + "val": { + "map": [ + { + "key": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + "val": { + "i128": "100000000" + } + } + ] + } + }, + { + "key": { + "symbol": "validtor" + }, + "val": { + "map": [ + { + "key": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + "val": { + "bool": true + } + } + ] + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "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": [ + { + "event": { + "ext": "v0", + "contract_id": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "validator_registered_event" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "joined_at" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "stake" + }, + "val": { + "i128": "100000000" + } + }, + { + "key": { + "symbol": "validator" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/teachlink/test_snapshots/gas_bench_regression_report.1.json b/contracts/teachlink/test_snapshots/gas_bench_regression_report.1.json new file mode 100644 index 0000000..592a4a7 --- /dev/null +++ b/contracts/teachlink/test_snapshots/gas_bench_regression_report.1.json @@ -0,0 +1,129 @@ +{ + "generators": { + "address": 4, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [], + [], + [], + [] + ], + "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": { + "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_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 4095 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/contracts/teachlink/test_snapshots/gas_bench_send_notification.1.json b/contracts/teachlink/test_snapshots/gas_bench_send_notification.1.json new file mode 100644 index 0000000..4db7a5b --- /dev/null +++ b/contracts/teachlink/test_snapshots/gas_bench_send_notification.1.json @@ -0,0 +1,355 @@ +{ + "generators": { + "address": 5, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [], + [], + [] + ], + "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": { + "symbol": "fee_rcpt" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + }, + { + "key": { + "symbol": "min_valid" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "nonce" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "notif_cnt" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "notif_tmp" + }, + "val": { + "map": [ + { + "key": { + "u64": "1" + }, + "val": { + "map": [ + { + "key": { + "symbol": "channels" + }, + "val": { + "vec": [ + { + "u32": 3 + }, + { + "u32": 0 + } + ] + } + }, + { + "key": { + "symbol": "content" + }, + "val": { + "map": [ + { + "key": { + "symbol": "body" + }, + "val": { + "bytes": "57656c636f6d6520746f2054656163684c696e6b2120596f7572206163636f756e7420686173206265656e207375636365737366756c6c7920637265617465642e" + } + }, + { + "key": { + "symbol": "data" + }, + "val": { + "bytes": "7b7d" + } + }, + { + "key": { + "symbol": "localization" + }, + "val": { + "map": [] + } + }, + { + "key": { + "symbol": "subject" + }, + "val": { + "bytes": "57656c636f6d6520746f2054656163684c696e6b21" + } + } + ] + } + }, + { + "key": { + "symbol": "created_at" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "is_active" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "name" + }, + "val": { + "bytes": "77656c636f6d65" + } + }, + { + "key": { + "symbol": "template_id" + }, + "val": { + "u64": "1" + } + }, + { + "key": { + "symbol": "updated_at" + }, + "val": { + "u64": "0" + } + } + ] + } + }, + { + "key": { + "u64": "2" + }, + "val": { + "map": [ + { + "key": { + "symbol": "channels" + }, + "val": { + "vec": [ + { + "u32": 3 + }, + { + "u32": 0 + } + ] + } + }, + { + "key": { + "symbol": "content" + }, + "val": { + "map": [ + { + "key": { + "symbol": "body" + }, + "val": { + "bytes": "596f7572207472616e73616374696f6e20686173206265656e20636f6d706c65746564207375636365737366756c6c792e" + } + }, + { + "key": { + "symbol": "data" + }, + "val": { + "bytes": "7b7d" + } + }, + { + "key": { + "symbol": "localization" + }, + "val": { + "map": [] + } + }, + { + "key": { + "symbol": "subject" + }, + "val": { + "bytes": "5472616e73616374696f6e20436f6d706c65746564" + } + } + ] + } + }, + { + "key": { + "symbol": "created_at" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "is_active" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "name" + }, + "val": { + "bytes": "7472616e73616374696f6e" + } + }, + { + "key": { + "symbol": "template_id" + }, + "val": { + "u64": "2" + } + }, + { + "key": { + "symbol": "updated_at" + }, + "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_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 4095 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/contracts/teachlink/test_snapshots/gas_bench_set_bridge_fee.1.json b/contracts/teachlink/test_snapshots/gas_bench_set_bridge_fee.1.json new file mode 100644 index 0000000..4d2a5e5 --- /dev/null +++ b/contracts/teachlink/test_snapshots/gas_bench_set_bridge_fee.1.json @@ -0,0 +1,165 @@ +{ + "generators": { + "address": 4, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "set_bridge_fee", + "args": [ + { + "i128": "100" + } + ] + } + }, + "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": "100" + } + }, + { + "key": { + "symbol": "chains" + }, + "val": { + "map": [] + } + }, + { + "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": [] +} \ No newline at end of file diff --git a/contracts/teachlink/test_snapshots/gas_bench_transfer_content_token.1.json b/contracts/teachlink/test_snapshots/gas_bench_transfer_content_token.1.json new file mode 100644 index 0000000..69d3880 --- /dev/null +++ b/contracts/teachlink/test_snapshots/gas_bench_transfer_content_token.1.json @@ -0,0 +1,440 @@ +{ + "generators": { + "address": 6, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [], + [], + [] + ], + "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": { + "symbol": "tok_cnt" + }, + "durability": "persistent", + "val": { + "u64": "1" + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "cnt_tok" + }, + { + "u64": "1" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "is_transferable" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "metadata" + }, + "val": { + "map": [ + { + "key": { + "symbol": "content_hash" + }, + "val": { + "bytes": "68617368313233" + } + }, + { + "key": { + "symbol": "content_type" + }, + "val": { + "vec": [ + { + "symbol": "Course" + } + ] + } + }, + { + "key": { + "symbol": "created_at" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "creator" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + }, + { + "key": { + "symbol": "description" + }, + "val": { + "bytes": "41207465737420636f75727365" + } + }, + { + "key": { + "symbol": "license_type" + }, + "val": { + "bytes": "43432d4259" + } + }, + { + "key": { + "symbol": "tags" + }, + "val": { + "vec": [] + } + }, + { + "key": { + "symbol": "title" + }, + "val": { + "bytes": "5465737420436f75727365" + } + }, + { + "key": { + "symbol": "updated_at" + }, + "val": { + "u64": "0" + } + } + ] + } + }, + { + "key": { + "symbol": "minted_at" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "owner" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + }, + { + "key": { + "symbol": "royalty_percentage" + }, + "val": { + "u32": 10 + } + }, + { + "key": { + "symbol": "token_id" + }, + "val": { + "u64": "1" + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "own_tok" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + ] + }, + "durability": "persistent", + "val": { + "vec": [] + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "own_tok" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + ] + }, + "durability": "persistent", + "val": { + "vec": [ + { + "u64": "1" + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "owner" + }, + { + "u64": "1" + } + ] + }, + "durability": "persistent", + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "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": { + "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_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 4095 + } + ] + }, + "events": [ + { + "event": { + "ext": "v0", + "contract_id": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "ownership_transferred_event" + } + ], + "data": { + "map": [ + { + "key": { + "symbol": "from" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + }, + { + "key": { + "symbol": "timestamp" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "to" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + }, + { + "key": { + "symbol": "token_id" + }, + "val": { + "u64": "1" + } + } + ] + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/contracts/teachlink/tests/test_gas_benchmarks.rs b/contracts/teachlink/tests/test_gas_benchmarks.rs new file mode 100644 index 0000000..d785d68 --- /dev/null +++ b/contracts/teachlink/tests/test_gas_benchmarks.rs @@ -0,0 +1,505 @@ +#![cfg(test)] +#![allow(clippy::assertions_on_constants)] +#![allow(clippy::needless_pass_by_value)] +#![allow(clippy::unreadable_literal)] + +//! Gas usage benchmarks for TeachLink contract operations. +//! +//! Measures instruction budgets (gas) consumed by each contract function +//! to detect performance regressions across PRs. + +use soroban_sdk::testutils::Address as _; +use soroban_sdk::{Address, Bytes, Env, Map, Vec}; +use teachlink_contract::{ + ContentTokenParameters, ContentType, FontSize, LayoutDensity, MobilePreferences, + NotificationChannel, TeachLinkBridge, TeachLinkBridgeClient, ThemePreference, VideoQuality, +}; + +/// Maximum gas budget allowed per operation (in Soroban cost units). +/// Adjust these thresholds as the contract evolves. +mod gas_thresholds { + /// Contract initialization + pub const INITIALIZE: u64 = 500_000; + /// Adding a validator + pub const ADD_VALIDATOR: u64 = 200_000; + /// Adding a supported chain + pub const ADD_SUPPORTED_CHAIN: u64 = 200_000; + /// Bridge-out (lock tokens) + pub const BRIDGE_OUT: u64 = 800_000; + /// Set bridge fee + pub const SET_BRIDGE_FEE: u64 = 150_000; + /// Read-only queries (get_token, get_nonce, etc.) + pub const READ_QUERY: u64 = 100_000; + /// Cache read (get_cached_bridge_summary hit) + pub const CACHE_HIT: u64 = 200_000; + /// Cache miss + compute + pub const CACHE_MISS_COMPUTE: u64 = 1_500_000; + /// Register a validator with stake + pub const REGISTER_VALIDATOR: u64 = 500_000; + /// Create an atomic swap + pub const INITIATE_ATOMIC_SWAP: u64 = 900_000; + /// Mint content token + pub const MINT_CONTENT_TOKEN: u64 = 600_000; + /// Transfer content token + pub const TRANSFER_CONTENT_TOKEN: u64 = 500_000; + /// Initialize rewards + pub const INITIALIZE_REWARDS: u64 = 400_000; + /// Fund reward pool + pub const FUND_REWARD_POOL: u64 = 500_000; + /// Issue reward + pub const ISSUE_REWARD: u64 = 400_000; + /// Claim rewards + pub const CLAIM_REWARDS: u64 = 600_000; + /// Send cross-chain packet + pub const SEND_PACKET: u64 = 700_000; + /// Initialize mobile profile + pub const INIT_MOBILE_PROFILE: u64 = 400_000; + /// Send notification + pub const SEND_NOTIFICATION: u64 = 350_000; + /// Create audit record + pub const CREATE_AUDIT_RECORD: u64 = 400_000; + /// WASM binary size threshold (bytes) - 300 KB + pub const WASM_SIZE_BYTES: u64 = 307_200; +} + +/// Helper to measure gas consumed by a closure using the Soroban budget. +fn measure_gas(env: &Env, mut f: F) -> u64 { + let budget_before = env.budget().cpu_instruction_cost(); + f(); + let budget_after = env.budget().cpu_instruction_cost(); + budget_after.saturating_sub(budget_before) +} + +/// Assert gas is within threshold, printing a warning if exceeded. +fn assert_gas_within(name: &str, gas_used: u64, threshold: u64) { + println!( + " [GAS] {}: {} instructions (threshold: {})", + name, gas_used, threshold + ); + assert!( + gas_used <= threshold, + "GAS REGRESSION: {} used {} instructions, exceeding threshold of {}", + name, + gas_used, + threshold, + ); +} + +fn setup_contract(env: &Env) -> TeachLinkBridgeClient<'_> { + env.mock_all_auths(); + let contract_id = env.register(TeachLinkBridge, ()); + TeachLinkBridgeClient::new(env, &contract_id) +} + +// ========================== +// Bridge Operation Benchmarks +// ========================== + +#[test] +fn gas_bench_initialize() { + let env = Env::default(); + let client = setup_contract(&env); + let token = Address::generate(&env); + let admin = Address::generate(&env); + let fee_recipient = Address::generate(&env); + + let gas = measure_gas(&env, || { + client.initialize(&token, &admin, &2, &fee_recipient); + }); + + assert_gas_within("initialize", gas, gas_thresholds::INITIALIZE); +} + +#[test] +fn gas_bench_add_validator() { + let env = Env::default(); + let client = setup_contract(&env); + let token = Address::generate(&env); + let admin = Address::generate(&env); + let fee_recipient = Address::generate(&env); + client.initialize(&token, &admin, &2, &fee_recipient); + + let validator = Address::generate(&env); + + let gas = measure_gas(&env, || { + client.add_validator(&validator); + }); + + assert_gas_within("add_validator", gas, gas_thresholds::ADD_VALIDATOR); +} + +#[test] +fn gas_bench_add_supported_chain() { + let env = Env::default(); + let client = setup_contract(&env); + let token = Address::generate(&env); + let admin = Address::generate(&env); + let fee_recipient = Address::generate(&env); + client.initialize(&token, &admin, &2, &fee_recipient); + + let gas = measure_gas(&env, || { + client.add_supported_chain(&1u32); + }); + + assert_gas_within( + "add_supported_chain", + gas, + gas_thresholds::ADD_SUPPORTED_CHAIN, + ); +} + +#[test] +fn gas_bench_set_bridge_fee() { + let env = Env::default(); + let client = setup_contract(&env); + let token = Address::generate(&env); + let admin = Address::generate(&env); + let fee_recipient = Address::generate(&env); + client.initialize(&token, &admin, &2, &fee_recipient); + + let gas = measure_gas(&env, || { + client.set_bridge_fee(&100i128); + }); + + assert_gas_within("set_bridge_fee", gas, gas_thresholds::SET_BRIDGE_FEE); +} + +#[test] +fn gas_bench_read_queries() { + let env = Env::default(); + let client = setup_contract(&env); + let token = Address::generate(&env); + let admin = Address::generate(&env); + let fee_recipient = Address::generate(&env); + client.initialize(&token, &admin, &2, &fee_recipient); + + let gas = measure_gas(&env, || { + let _ = client.get_token(); + let _ = client.get_nonce(); + let _ = client.get_bridge_fee(); + let _ = client.get_admin(); + }); + + assert_gas_within("read_queries_bundle", gas, gas_thresholds::READ_QUERY); +} + +// ========================== +// Performance Cache Benchmarks +// ========================== + +#[test] +fn gas_bench_cache_compute() { + let env = Env::default(); + let client = setup_contract(&env); + let token = Address::generate(&env); + let admin = Address::generate(&env); + let fee_recipient = Address::generate(&env); + client.initialize(&token, &admin, &2, &fee_recipient); + + let gas = measure_gas(&env, || { + let _ = client.compute_and_cache_bridge_summary(); + }); + + assert_gas_within( + "compute_and_cache_bridge_summary", + gas, + gas_thresholds::CACHE_MISS_COMPUTE, + ); +} + +#[test] +fn gas_bench_cache_hit() { + let env = Env::default(); + let client = setup_contract(&env); + let token = Address::generate(&env); + let admin = Address::generate(&env); + let fee_recipient = Address::generate(&env); + client.initialize(&token, &admin, &2, &fee_recipient); + + // Prime the cache + let _ = client.compute_and_cache_bridge_summary(); + + let gas = measure_gas(&env, || { + let _ = client.get_cached_bridge_summary(); + }); + + assert_gas_within("get_cached_bridge_summary", gas, gas_thresholds::CACHE_HIT); +} + +// ========================== +// BFT Consensus Benchmarks +// ========================== + +#[test] +fn gas_bench_register_validator() { + let env = Env::default(); + let client = setup_contract(&env); + let token = Address::generate(&env); + let admin = Address::generate(&env); + let fee_recipient = Address::generate(&env); + client.initialize(&token, &admin, &2, &fee_recipient); + + let validator = Address::generate(&env); + + let gas = measure_gas(&env, || { + let _ = client.try_register_validator(&validator, &100_000_000i128); + }); + + assert_gas_within( + "register_validator", + gas, + gas_thresholds::REGISTER_VALIDATOR, + ); +} + +// ========================== +// Content Tokenization Benchmarks +// ========================== + +#[test] +fn gas_bench_mint_content_token() { + let env = Env::default(); + let client = setup_contract(&env); + let token = Address::generate(&env); + let admin = Address::generate(&env); + let fee_recipient = Address::generate(&env); + client.initialize(&token, &admin, &2, &fee_recipient); + + let creator = Address::generate(&env); + let params = ContentTokenParameters { + creator: creator.clone(), + title: Bytes::from_slice(&env, b"Test Course"), + description: Bytes::from_slice(&env, b"A test course for gas benchmarking"), + content_type: ContentType::Course, + content_hash: Bytes::from_slice(&env, b"hash123"), + license_type: Bytes::from_slice(&env, b"CC-BY"), + tags: Vec::new(&env), + is_transferable: true, + royalty_percentage: 10, + }; + + let gas = measure_gas(&env, || { + let _ = client.mint_content_token(¶ms); + }); + + assert_gas_within( + "mint_content_token", + gas, + gas_thresholds::MINT_CONTENT_TOKEN, + ); +} + +#[test] +fn gas_bench_transfer_content_token() { + let env = Env::default(); + let client = setup_contract(&env); + let token = Address::generate(&env); + let admin = Address::generate(&env); + let fee_recipient = Address::generate(&env); + client.initialize(&token, &admin, &2, &fee_recipient); + + let creator = Address::generate(&env); + let receiver = Address::generate(&env); + let params = ContentTokenParameters { + creator: creator.clone(), + title: Bytes::from_slice(&env, b"Test Course"), + description: Bytes::from_slice(&env, b"A test course"), + content_type: ContentType::Course, + content_hash: Bytes::from_slice(&env, b"hash123"), + license_type: Bytes::from_slice(&env, b"CC-BY"), + tags: Vec::new(&env), + is_transferable: true, + royalty_percentage: 10, + }; + + let token_id = client.mint_content_token(¶ms); + + let gas = measure_gas(&env, || { + client.transfer_content_token(&creator, &receiver, &token_id, &None); + }); + + assert_gas_within( + "transfer_content_token", + gas, + gas_thresholds::TRANSFER_CONTENT_TOKEN, + ); +} + +// ========================== +// Notification Benchmarks +// ========================== + +#[test] +fn gas_bench_send_notification() { + let env = Env::default(); + let client = setup_contract(&env); + let token = Address::generate(&env); + let admin = Address::generate(&env); + let fee_recipient = Address::generate(&env); + client.initialize(&token, &admin, &2, &fee_recipient); + client.initialize_notifications(); + + let recipient = Address::generate(&env); + + let gas = measure_gas(&env, || { + // Use try_ to handle simulated delivery failure in test env + let _ = client.try_send_notification( + &recipient, + &NotificationChannel::InApp, + &Bytes::from_slice(&env, b"Test Subject"), + &Bytes::from_slice(&env, b"Test notification body"), + ); + }); + + assert_gas_within("send_notification", gas, gas_thresholds::SEND_NOTIFICATION); +} + +// ========================== +// Audit Benchmarks +// ========================== + +#[test] +fn gas_bench_create_audit_record() { + let env = Env::default(); + let client = setup_contract(&env); + let token = Address::generate(&env); + let admin = Address::generate(&env); + let fee_recipient = Address::generate(&env); + client.initialize(&token, &admin, &2, &fee_recipient); + + let operator = Address::generate(&env); + + let gas = measure_gas(&env, || { + let _ = client.create_audit_record( + &teachlink_contract::OperationType::BridgeOut, + &operator, + &Bytes::from_slice(&env, b"audit details"), + &Bytes::from_slice(&env, b"tx_hash_value"), + ); + }); + + assert_gas_within( + "create_audit_record", + gas, + gas_thresholds::CREATE_AUDIT_RECORD, + ); +} + +// ========================== +// Mobile Platform Benchmarks +// ========================== + +#[test] +fn gas_bench_initialize_mobile_profile() { + let env = Env::default(); + let client = setup_contract(&env); + let token = Address::generate(&env); + let admin = Address::generate(&env); + let fee_recipient = Address::generate(&env); + client.initialize(&token, &admin, &2, &fee_recipient); + + let user = Address::generate(&env); + let device_info = teachlink_contract::DeviceInfo { + device_id: Bytes::from_slice(&env, b"device-001"), + model: Bytes::from_slice(&env, b"iPhone 14"), + os_version: Bytes::from_slice(&env, b"iOS 17"), + push_token: Bytes::from_slice(&env, b"push_token_abc"), + screen_resolution: Bytes::from_slice(&env, b"1170x2532"), + is_tablet: false, + }; + let preferences = MobilePreferences { + data_saver_mode: false, + auto_download_wifi: true, + video_quality: VideoQuality::Auto, + font_size: FontSize::Medium, + theme: ThemePreference::System, + language: Bytes::from_slice(&env, b"en"), + vibration_enabled: true, + sound_enabled: true, + gesture_navigation: true, + custom_theme_colors: Map::new(&env), + layout_density: LayoutDensity::Comfortable, + }; + + let gas = measure_gas(&env, || { + let _ = client.initialize_mobile_profile(&user, &device_info, &preferences); + }); + + assert_gas_within( + "initialize_mobile_profile", + gas, + gas_thresholds::INIT_MOBILE_PROFILE, + ); +} + +// ========================== +// Regression Detection Report +// ========================== + +#[test] +fn gas_bench_regression_report() { + println!("\n========================================"); + println!(" GAS BENCHMARK SUMMARY"); + println!("========================================"); + + let env = Env::default(); + let client = setup_contract(&env); + let token = Address::generate(&env); + let admin = Address::generate(&env); + let fee_recipient = Address::generate(&env); + client.initialize(&token, &admin, &2, &fee_recipient); + + println!(" Operation | Gas Used | Threshold | Status"); + println!(" --------------------------|-------------|-------------|--------"); + + // Run each measurement + let gas = measure_gas(&env, || { + let _ = client.get_token(); + }); + let status = if gas <= gas_thresholds::READ_QUERY { + "PASS" + } else { + "FAIL" + }; + println!( + " {:<26}| {:>11} | {:>11} | {}", + "get_token", + gas, + gas_thresholds::READ_QUERY, + status + ); + + let gas = measure_gas(&env, || { + let _ = client.get_nonce(); + }); + let status = if gas <= gas_thresholds::READ_QUERY { + "PASS" + } else { + "FAIL" + }; + println!( + " {:<26}| {:>11} | {:>11} | {}", + "get_nonce", + gas, + gas_thresholds::READ_QUERY, + status + ); + + let gas = measure_gas(&env, || { + let _ = client.get_bridge_fee(); + }); + let status = if gas <= gas_thresholds::READ_QUERY { + "PASS" + } else { + "FAIL" + }; + println!( + " {:<26}| {:>11} | {:>11} | {}", + "get_bridge_fee", + gas, + gas_thresholds::READ_QUERY, + status + ); + + println!("========================================\n"); +} diff --git a/gas_baseline.json b/gas_baseline.json new file mode 100644 index 0000000..2afecb0 --- /dev/null +++ b/gas_baseline.json @@ -0,0 +1,55 @@ +{ + "updated_at": "2026-03-28T00:00:00.000000Z", + "initialize": { + "gas_used": 0, + "threshold": 500000 + }, + "add_validator": { + "gas_used": 0, + "threshold": 200000 + }, + "add_supported_chain": { + "gas_used": 0, + "threshold": 200000 + }, + "set_bridge_fee": { + "gas_used": 0, + "threshold": 150000 + }, + "read_queries_bundle": { + "gas_used": 0, + "threshold": 100000 + }, + "compute_and_cache_bridge_summary": { + "gas_used": 0, + "threshold": 1500000 + }, + "get_cached_bridge_summary": { + "gas_used": 0, + "threshold": 200000 + }, + "register_validator": { + "gas_used": 0, + "threshold": 500000 + }, + "mint_content_token": { + "gas_used": 0, + "threshold": 600000 + }, + "transfer_content_token": { + "gas_used": 0, + "threshold": 500000 + }, + "send_notification": { + "gas_used": 0, + "threshold": 350000 + }, + "create_audit_record": { + "gas_used": 0, + "threshold": 400000 + }, + "initialize_mobile_profile": { + "gas_used": 0, + "threshold": 400000 + } +} diff --git a/gas_thresholds.json b/gas_thresholds.json new file mode 100644 index 0000000..60b389f --- /dev/null +++ b/gas_thresholds.json @@ -0,0 +1,168 @@ +{ + "version": "1.0.0", + "description": "Gas benchmark regression thresholds for TeachLink contract operations", + "last_updated": "2026-03-28", + "thresholds": { + "contract_operations": { + "initialize": { + "max_instructions": 500000, + "max_memory_bytes": 50000, + "description": "Contract initialization" + }, + "add_validator": { + "max_instructions": 200000, + "max_memory_bytes": 10000, + "description": "Adding a validator to bridge" + }, + "add_supported_chain": { + "max_instructions": 200000, + "max_memory_bytes": 10000, + "description": "Adding a supported destination chain" + }, + "set_bridge_fee": { + "max_instructions": 150000, + "max_memory_bytes": 5000, + "description": "Setting bridge transaction fee" + }, + "read_query": { + "max_instructions": 100000, + "max_memory_bytes": 5000, + "description": "Read-only queries (get_token, get_nonce, etc.)" + } + }, + "bridge_operations": { + "bridge_out": { + "max_instructions": 800000, + "max_memory_bytes": 50000, + "description": "Locking tokens for cross-chain bridge" + }, + "complete_bridge": { + "max_instructions": 1000000, + "max_memory_bytes": 80000, + "description": "Completing a bridge transaction with validator signatures" + }, + "cancel_bridge": { + "max_instructions": 500000, + "max_memory_bytes": 30000, + "description": "Cancelling a pending bridge transaction" + } + }, + "performance_cache": { + "cache_hit": { + "max_instructions": 200000, + "max_memory_bytes": 20000, + "description": "Reading from performance cache (fresh)" + }, + "cache_miss_compute": { + "max_instructions": 1500000, + "max_memory_bytes": 100000, + "description": "Computing bridge summary on cache miss" + }, + "cache_invalidation": { + "max_instructions": 150000, + "max_memory_bytes": 5000, + "description": "Invalidating performance cache" + } + }, + "consensus": { + "register_validator": { + "max_instructions": 500000, + "max_memory_bytes": 30000, + "description": "Registering a BFT consensus validator" + }, + "create_proposal": { + "max_instructions": 400000, + "max_memory_bytes": 30000, + "description": "Creating a bridge proposal for consensus" + }, + "vote_on_proposal": { + "max_instructions": 300000, + "max_memory_bytes": 20000, + "description": "Voting on a bridge proposal" + } + }, + "tokenization": { + "mint_content_token": { + "max_instructions": 600000, + "max_memory_bytes": 50000, + "description": "Minting an educational content NFT" + }, + "transfer_content_token": { + "max_instructions": 500000, + "max_memory_bytes": 30000, + "description": "Transferring content token ownership" + }, + "update_metadata": { + "max_instructions": 400000, + "max_memory_bytes": 30000, + "description": "Updating content token metadata" + } + }, + "rewards": { + "initialize_rewards": { + "max_instructions": 400000, + "max_memory_bytes": 20000, + "description": "Initializing the rewards system" + }, + "fund_reward_pool": { + "max_instructions": 500000, + "max_memory_bytes": 20000, + "description": "Funding the reward pool" + }, + "issue_reward": { + "max_instructions": 400000, + "max_memory_bytes": 20000, + "description": "Issuing rewards to a user" + }, + "claim_rewards": { + "max_instructions": 600000, + "max_memory_bytes": 30000, + "description": "Claiming pending rewards" + } + }, + "messaging": { + "send_packet": { + "max_instructions": 700000, + "max_memory_bytes": 50000, + "description": "Sending a cross-chain packet" + }, + "deliver_packet": { + "max_instructions": 500000, + "max_memory_bytes": 30000, + "description": "Marking a packet as delivered" + } + }, + "notifications": { + "send_notification": { + "max_instructions": 350000, + "max_memory_bytes": 20000, + "description": "Sending an in-app notification" + } + }, + "mobile_platform": { + "initialize_mobile_profile": { + "max_instructions": 400000, + "max_memory_bytes": 30000, + "description": "Initializing user mobile profile" + } + }, + "audit": { + "create_audit_record": { + "max_instructions": 400000, + "max_memory_bytes": 20000, + "description": "Creating an audit trail record" + } + }, + "wasm_binary": { + "max_size_bytes": 307200, + "warning_size_bytes": 256000, + "description": "WASM binary size limit (300 KB)" + } + }, + "regression_policy": { + "max_percentage_increase": 10, + "warning_percentage_increase": 5, + "fail_on_regression": true, + "baseline_file": "gas_baseline.json" + } +} diff --git a/scripts/benchmark.sh b/scripts/benchmark.sh index 3b9360b..1f7aba1 100755 --- a/scripts/benchmark.sh +++ b/scripts/benchmark.sh @@ -2,52 +2,95 @@ set -e # Benchmark Script for TeachLink Contract +# Includes gas usage benchmarking for performance regression detection -echo "Starting Benchmark..." +echo "============================================" +echo " TeachLink Contract Benchmark Suite" +echo "============================================" # 1. Build the Contract -echo "Building contract in release mode..." +echo "" +echo "[1/4] Building contract in release mode..." cargo build --release --target wasm32-unknown-unknown -p teachlink-contract # 2. Check WASM Size +echo "" +echo "[2/4] Checking WASM binary size..." WASM_PATH="target/wasm32-unknown-unknown/release/teachlink_contract.wasm" if [ -f "$WASM_PATH" ]; then SIZE=$(du -h "$WASM_PATH" | cut -f1) - echo "WASM Size: $SIZE" - - # Optional: Check against a limit (e.g., 300KB) + echo " WASM Size: $SIZE" + if command -v stat >/dev/null 2>&1; then - # Try macOS stat first if stat -f%z "$WASM_PATH" >/dev/null 2>&1; then SIZE_BYTES=$(stat -f%z "$WASM_PATH") else - # Try Linux stat SIZE_BYTES=$(stat -c%s "$WASM_PATH") fi else - echo "Warning: stat command not available, skipping size check" + echo " Warning: stat command not available, skipping size check" SIZE_BYTES=0 fi - + if [ "$SIZE_BYTES" -gt 307200 ] && [ "$SIZE_BYTES" -ne 0 ]; then - echo "WARNING: WASM size exceeds 300KB!" + echo " FAIL: WASM size ($SIZE_BYTES bytes) exceeds 300 KB threshold!" + WASM_OK=0 + elif [ "$SIZE_BYTES" -gt 256000 ] && [ "$SIZE_BYTES" -ne 0 ]; then + echo " WARNING: WASM size ($SIZE_BYTES bytes) approaching 300 KB limit" + WASM_OK=1 else - echo "WASM size is within limits." + echo " PASS: WASM size is within limits ($SIZE_BYTES bytes)." + WASM_OK=1 fi else - echo "Error: WASM file not found at $WASM_PATH" + echo " Error: WASM file not found at $WASM_PATH" exit 1 fi -# 3. Run Tests (Performance Check) -echo "Running unit tests..." +# 3. Run Gas Benchmarks +echo "" +echo "[3/4] Running gas usage benchmarks..." +start_time=$(date +%s) +cargo test --release -p teachlink-contract --test test_gas_benchmarks -- --nocapture 2>&1 | tee gas_output.txt +end_time=$(date +%s) +gas_duration=$((end_time - start_time)) +echo " Gas benchmarks completed in ${gas_duration}s" + +# 4. Run Unit Tests (Performance Check) +echo "" +echo "[4/4] Running unit tests..." start_time=$(date +%s) cargo test --release --lib end_time=$(date +%s) duration=$((end_time - start_time)) -duration_ms=$((duration * 1000)) +echo " Tests completed in ${duration}s" + +# Summary +echo "" +echo "============================================" +echo " Benchmark Summary" +echo "============================================" +echo " WASM Binary: $SIZE ($SIZE_BYTES bytes)" +echo " Gas Benchmarks: ${gas_duration}s" +echo " Unit Tests: ${duration}s" +echo "" -echo "Tests completed in ${duration_ms} ms" +# Check for gas regressions +if [ -f gas_output.txt ]; then + REGRESSIONS=$(grep -c "GAS REGRESSION" gas_output.txt || true) + if [ "$REGRESSIONS" -gt 0 ]; then + echo " RESULT: FAILED - $REGRESSIONS gas regression(s) detected" + echo "============================================" + exit 1 + fi +fi + +if [ "$WASM_OK" -eq 0 ]; then + echo " RESULT: FAILED - WASM size exceeds threshold" + echo "============================================" + exit 1 +fi -echo "Benchmark Complete." +echo " RESULT: PASSED - All benchmarks within thresholds" +echo "============================================" diff --git a/scripts/run_gas_benchmarks.py b/scripts/run_gas_benchmarks.py new file mode 100644 index 0000000..334d32b --- /dev/null +++ b/scripts/run_gas_benchmarks.py @@ -0,0 +1,222 @@ +#!/usr/bin/env python3 +""" +Gas benchmark runner for TeachLink contract. + +Runs gas benchmarks, compares results against a baseline, +and generates regression reports. + +Usage: + python3 scripts/run_gas_benchmarks.py [--update-baseline] [--output report.json] +""" + +import json +import os +import subprocess +import sys +import re +from datetime import datetime +from pathlib import Path + +REPO_ROOT = Path(__file__).parent.parent +THRESHOLDS_FILE = REPO_ROOT / "gas_thresholds.json" +BASELINE_FILE = REPO_ROOT / "gas_baseline.json" +DEFAULT_OUTPUT = REPO_ROOT / "gas_benchmark_report.json" + + +def load_json(path: Path) -> dict: + """Load a JSON file, returning empty dict if not found.""" + if path.exists(): + with open(path) as f: + return json.load(f) + return {} + + +def save_json(path: Path, data: dict): + """Save data to a JSON file.""" + with open(path, "w") as f: + json.dump(data, f, indent=2) + print(f" Saved: {path}") + + +def run_cargo_bench() -> str: + """Run cargo test with gas benchmarks and capture output.""" + print(" Running gas benchmarks...") + try: + result = subprocess.run( + ["cargo", "test", "--release", "-p", "teachlink-contract", + "--test", "test_gas_benchmarks", "--", "--nocapture"], + cwd=REPO_ROOT, + capture_output=True, + text=True, + timeout=600, + ) + return result.stdout + "\n" + result.stderr + except subprocess.TimeoutExpired: + print(" ERROR: Benchmark timed out after 600 seconds") + return "" + except FileNotFoundError: + print(" ERROR: cargo not found. Ensure Rust toolchain is installed.") + return "" + + +def parse_gas_output(output: str) -> dict: + """Parse gas measurement output from test runs.""" + results = {} + # Match lines like: [GAS] operation_name: 12345 instructions (threshold: 500000) + pattern = r"\[GAS\]\s+(\S+):\s+(\d+)\s+instructions\s+\(threshold:\s+(\d+)\)" + for match in re.finditer(pattern, output): + name = match.group(1) + gas_used = int(match.group(2)) + threshold = int(match.group(3)) + results[name] = { + "gas_used": gas_used, + "threshold": threshold, + "within_threshold": gas_used <= threshold, + } + return results + + +def check_wasm_size(thresholds: dict) -> dict: + """Check WASM binary size against threshold.""" + wasm_path = REPO_ROOT / "target" / "wasm32-unknown-unknown" / "release" / "teachlink_contract.wasm" + result = {"exists": False} + + if wasm_path.exists(): + size = wasm_path.stat().st_size + wasm_thresholds = thresholds.get("thresholds", {}).get("wasm_binary", {}) + max_size = wasm_thresholds.get("max_size_bytes", 307200) + warning_size = wasm_thresholds.get("warning_size_bytes", 256000) + + result = { + "exists": True, + "size_bytes": size, + "max_bytes": max_size, + "warning_bytes": warning_size, + "within_threshold": size <= max_size, + "needs_warning": size > warning_size, + } + + if size > max_size: + print(f" WASM SIZE REGRESSION: {size} bytes exceeds {max_size} bytes") + elif size > warning_size: + print(f" WASM SIZE WARNING: {size} bytes approaching {max_size} bytes limit") + else: + print(f" WASM size OK: {size} bytes (limit: {max_size})") + + return result + + +def compare_with_baseline(current: dict, baseline: dict) -> list: + """Compare current results with baseline, returning regressions.""" + regressions = [] + for name, data in current.items(): + if name in baseline: + base_gas = baseline[name].get("gas_used", 0) + curr_gas = data["gas_used"] + if base_gas > 0: + pct_change = ((curr_gas - base_gas) / base_gas) * 100 + data["baseline_gas"] = base_gas + data["pct_change"] = round(pct_change, 2) + + if pct_change > 10: + regressions.append({ + "operation": name, + "baseline": base_gas, + "current": curr_gas, + "change_pct": round(pct_change, 2), + "severity": "critical" if pct_change > 25 else "warning", + }) + return regressions + + +def generate_report(results: dict, wasm_info: dict, regressions: list) -> dict: + """Generate the benchmark report.""" + return { + "generated_at": datetime.utcnow().isoformat() + "Z", + "summary": { + "total_operations": len(results), + "passed": sum(1 for r in results.values() if r.get("within_threshold", True)), + "failed": sum(1 for r in results.values() if not r.get("within_threshold", True)), + "regressions": len(regressions), + }, + "operations": results, + "wasm_binary": wasm_info, + "regressions": regressions, + } + + +def main(): + import argparse + parser = argparse.ArgumentParser(description="Gas benchmark runner") + parser.add_argument("--update-baseline", action="store_true", + help="Update baseline with current results") + parser.add_argument("--output", type=str, default=str(DEFAULT_OUTPUT), + help="Output report path") + args = parser.parse_args() + + print("=" * 50) + print(" TeachLink Gas Benchmark Runner") + print("=" * 50) + + # Load thresholds + thresholds = load_json(THRESHOLDS_FILE) + if not thresholds: + print(" WARNING: No gas_thresholds.json found, using defaults") + + # Run benchmarks + output = run_cargo_bench() + results = parse_gas_output(output) + + if not results: + print(" No gas measurements captured. Check test output.") + sys.exit(1) + + # Check WASM size + wasm_info = check_wasm_size(thresholds) + + # Compare with baseline + baseline = load_json(BASELINE_FILE) + regressions = compare_with_baseline(results, baseline) + + # Generate report + report = generate_report(results, wasm_info, regressions) + save_json(Path(args.output), report) + + # Print summary + print("\n" + "=" * 50) + print(" BENCHMARK SUMMARY") + print("=" * 50) + print(f" Operations tested: {report['summary']['total_operations']}") + print(f" Passed: {report['summary']['passed']}") + print(f" Failed: {report['summary']['failed']}") + print(f" Regressions: {report['summary']['regressions']}") + + if regressions: + print("\n REGRESSIONS DETECTED:") + for r in regressions: + severity = "CRITICAL" if r["severity"] == "critical" else "WARNING" + print(f" [{severity}] {r['operation']}: " + f"{r['baseline']} -> {r['current']} ({r['change_pct']:+.1f}%)") + + # Update baseline if requested + if args.update_baseline: + baseline_data = {k: {"gas_used": v["gas_used"], "threshold": v["threshold"]} + for k, v in results.items()} + baseline_data["updated_at"] = datetime.utcnow().isoformat() + "Z" + save_json(BASELINE_FILE, baseline_data) + print("\n Baseline updated.") + + # Exit code + has_failures = report["summary"]["failed"] > 0 or any( + r["severity"] == "critical" for r in regressions + ) + if has_failures: + print("\n RESULT: FAILED - Gas regressions detected") + sys.exit(1) + else: + print("\n RESULT: PASSED") + sys.exit(0) + + +if __name__ == "__main__": + main() diff --git a/testing/performance/benchmark_runner.rs b/testing/performance/benchmark_runner.rs index 3e82e08..f1fd0aa 100644 --- a/testing/performance/benchmark_runner.rs +++ b/testing/performance/benchmark_runner.rs @@ -1,6 +1,6 @@ -/// Performance benchmark runner -use std::time::{Duration, Instant}; use std::collections::HashMap; +/// Performance benchmark runner with gas tracking support +use std::time::{Duration, Instant}; #[derive(Debug, Clone)] pub struct BenchmarkResult { @@ -15,8 +15,17 @@ pub struct BenchmarkResult { pub p99: Duration, } +#[derive(Debug, Clone)] +pub struct GasBenchmarkResult { + pub name: String, + pub gas_used: u64, + pub threshold: u64, + pub within_threshold: bool, +} + pub struct BenchmarkRunner { results: HashMap, + gas_results: HashMap, warmup_iterations: u64, test_iterations: u64, } @@ -25,12 +34,13 @@ impl BenchmarkRunner { pub fn new(warmup_iterations: u64, test_iterations: u64) -> Self { Self { results: HashMap::new(), + gas_results: HashMap::new(), warmup_iterations, test_iterations, } } - pub fn benchmark(&mut self, name: &str, mut f: F) + pub fn benchmark(&mut self, name: &str, mut f: F) where F: FnMut(), { @@ -41,7 +51,7 @@ impl BenchmarkRunner { // Actual benchmark let mut durations = Vec::with_capacity(self.test_iterations as usize); - + for _ in 0..self.test_iterations { let start = Instant::now(); f(); @@ -55,11 +65,11 @@ impl BenchmarkRunner { let avg = total / self.test_iterations as u32; let min = *durations.first().unwrap(); let max = *durations.last().unwrap(); - + let p50_idx = (self.test_iterations as f64 * 0.50) as usize; let p95_idx = (self.test_iterations as f64 * 0.95) as usize; let p99_idx = (self.test_iterations as f64 * 0.99) as usize; - + let result = BenchmarkResult { name: name.to_string(), iterations: self.test_iterations, @@ -75,13 +85,42 @@ impl BenchmarkRunner { self.results.insert(name.to_string(), result); } + /// Record a gas measurement for a contract operation. + pub fn record_gas(&mut self, name: &str, gas_used: u64, threshold: u64) { + let result = GasBenchmarkResult { + name: name.to_string(), + gas_used, + threshold, + within_threshold: gas_used <= threshold, + }; + self.gas_results.insert(name.to_string(), result); + } + + /// Get a gas benchmark result by name. + pub fn get_gas_result(&self, name: &str) -> Option<&GasBenchmarkResult> { + self.gas_results.get(name) + } + + /// Check if all gas benchmarks are within thresholds. + pub fn all_gas_within_thresholds(&self) -> bool { + self.gas_results.values().all(|r| r.within_threshold) + } + + /// Get all gas regressions (operations exceeding thresholds). + pub fn get_gas_regressions(&self) -> Vec<&GasBenchmarkResult> { + self.gas_results + .values() + .filter(|r| !r.within_threshold) + .collect() + } + pub fn get_result(&self, name: &str) -> Option<&BenchmarkResult> { self.results.get(name) } pub fn print_results(&self) { println!("\n=== Benchmark Results ===\n"); - + for (name, result) in &self.results { println!("Benchmark: {}", name); println!(" Iterations: {}", result.iterations); @@ -95,50 +134,133 @@ impl BenchmarkRunner { } } + pub fn print_gas_results(&self) { + println!("\n=== Gas Benchmark Results ===\n"); + println!( + " {:<35} | {:>12} | {:>12} | {}", + "Operation", "Gas Used", "Threshold", "Status" + ); + println!(" {:->35}-+-{:->12}-+-{:->12}-+{:->8}", "", "", "", ""); + + for (name, result) in &self.gas_results { + let status = if result.within_threshold { + "PASS" + } else { + "FAIL" + }; + println!( + " {:<35} | {:>12} | {:>12} | {}", + name, result.gas_used, result.threshold, status + ); + } + println!(); + } + pub fn compare_with_baseline(&self, baseline: &BenchmarkRunner) { println!("\n=== Comparison with Baseline ===\n"); - + for (name, current) in &self.results { if let Some(baseline_result) = baseline.get_result(name) { - let diff_pct = ((current.avg_duration.as_nanos() as f64 - - baseline_result.avg_duration.as_nanos() as f64) - / baseline_result.avg_duration.as_nanos() as f64) * 100.0; - + let diff_pct = ((current.avg_duration.as_nanos() as f64 + - baseline_result.avg_duration.as_nanos() as f64) + / baseline_result.avg_duration.as_nanos() as f64) + * 100.0; + let status = if diff_pct > 5.0 { - "⚠️ SLOWER" + "SLOWER" } else if diff_pct < -5.0 { - "✅ FASTER" + "FASTER" } else { - "➡️ SIMILAR" + "SIMILAR" }; - + println!("{} {}: {:.2}%", status, name, diff_pct); } } } + /// Compare gas results with a baseline runner. + pub fn compare_gas_with_baseline(&self, baseline: &BenchmarkRunner) { + println!("\n=== Gas Comparison with Baseline ===\n"); + + for (name, current) in &self.gas_results { + if let Some(baseline_result) = baseline.get_gas_result(name) { + if baseline_result.gas_used > 0 { + let diff_pct = ((current.gas_used as f64 - baseline_result.gas_used as f64) + / baseline_result.gas_used as f64) + * 100.0; + + let status = if diff_pct > 10.0 { + "REGRESSION" + } else if diff_pct > 5.0 { + "WARNING" + } else if diff_pct < -5.0 { + "IMPROVED" + } else { + "STABLE" + }; + + println!( + " {} {}: {} -> {} ({:+.1}%)", + status, name, baseline_result.gas_used, current.gas_used, diff_pct + ); + } + } + } + } + pub fn export_json(&self) -> String { let mut json = String::from("{\n"); - json.push_str(" \"benchmarks\": [\n"); - + json.push_str(" \"timing_benchmarks\": [\n"); + for (i, (name, result)) in self.results.iter().enumerate() { json.push_str(" {\n"); json.push_str(&format!(" \"name\": \"{}\",\n", name)); json.push_str(&format!(" \"iterations\": {},\n", result.iterations)); - json.push_str(&format!(" \"avg_ns\": {},\n", result.avg_duration.as_nanos())); - json.push_str(&format!(" \"min_ns\": {},\n", result.min_duration.as_nanos())); - json.push_str(&format!(" \"max_ns\": {},\n", result.max_duration.as_nanos())); + json.push_str(&format!( + " \"avg_ns\": {},\n", + result.avg_duration.as_nanos() + )); + json.push_str(&format!( + " \"min_ns\": {},\n", + result.min_duration.as_nanos() + )); + json.push_str(&format!( + " \"max_ns\": {},\n", + result.max_duration.as_nanos() + )); json.push_str(&format!(" \"p50_ns\": {},\n", result.p50.as_nanos())); json.push_str(&format!(" \"p95_ns\": {},\n", result.p95.as_nanos())); json.push_str(&format!(" \"p99_ns\": {}\n", result.p99.as_nanos())); json.push_str(" }"); - + if i < self.results.len() - 1 { json.push_str(","); } json.push_str("\n"); } - + + json.push_str(" ],\n"); + json.push_str(" \"gas_benchmarks\": [\n"); + + let gas_items: Vec<_> = self.gas_results.iter().collect(); + for (i, (name, result)) in gas_items.iter().enumerate() { + json.push_str(" {\n"); + json.push_str(&format!(" \"name\": \"{}\",\n", name)); + json.push_str(&format!(" \"gas_used\": {},\n", result.gas_used)); + json.push_str(&format!(" \"threshold\": {},\n", result.threshold)); + json.push_str(&format!( + " \"within_threshold\": {}\n", + result.within_threshold + )); + json.push_str(" }"); + + if i < gas_items.len() - 1 { + json.push_str(","); + } + json.push_str("\n"); + } + json.push_str(" ]\n"); json.push_str("}\n"); json @@ -153,7 +275,7 @@ mod tests { #[test] fn test_benchmark_runner() { let mut runner = BenchmarkRunner::new(10, 100); - + runner.benchmark("sleep_1ms", || { thread::sleep(Duration::from_micros(100)); }); @@ -166,15 +288,63 @@ mod tests { #[test] fn test_multiple_benchmarks() { let mut runner = BenchmarkRunner::new(5, 50); - + runner.benchmark("fast", || { let _ = 1 + 1; }); - + runner.benchmark("slow", || { thread::sleep(Duration::from_micros(10)); }); assert!(runner.results.len() == 2); } + + #[test] + fn test_gas_tracking() { + let mut runner = BenchmarkRunner::new(0, 0); + + runner.record_gas("initialize", 350_000, 500_000); + runner.record_gas("bridge_out", 900_000, 800_000); + runner.record_gas("read_query", 50_000, 100_000); + + assert!( + runner + .get_gas_result("initialize") + .unwrap() + .within_threshold + ); + assert!( + !runner + .get_gas_result("bridge_out") + .unwrap() + .within_threshold + ); + assert!( + runner + .get_gas_result("read_query") + .unwrap() + .within_threshold + ); + + assert!(!runner.all_gas_within_thresholds()); + assert_eq!(runner.get_gas_regressions().len(), 1); + } + + #[test] + fn test_gas_comparison() { + let mut baseline = BenchmarkRunner::new(0, 0); + baseline.record_gas("op1", 1000, 2000); + baseline.record_gas("op2", 500, 1000); + + let mut current = BenchmarkRunner::new(0, 0); + current.record_gas("op1", 1100, 2000); // 10% increase + current.record_gas("op2", 450, 1000); // 10% decrease + + // op1 should show as warning (10% > 5%) + // op2 should show as improved (-10% < -5%) + // Just verify they exist + assert!(current.get_gas_result("op1").is_some()); + assert!(current.get_gas_result("op2").is_some()); + } }