diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 643cc3e..c13cf97 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -108,3 +108,44 @@ jobs: cache-on-failure: true - name: cargo check --target wasm32v1-none (contracts) run: cargo check ${{ env.CONTRACTS }} --target wasm32v1-none + + bindings-check: + name: Bindings freshness check + runs-on: ubuntu-latest + timeout-minutes: 30 + # Needs write access to annotate diff output; all other jobs stay read-only. + permissions: + contents: read + steps: + - uses: actions/checkout@v4 + - name: Install Rust toolchain with wasm target + uses: dtolnay/rust-toolchain@stable + with: + targets: wasm32v1-none + cache: false + - name: Cache cargo registry and target + uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: true + - name: Build campaign WASM (release) + run: cargo build -p milestonex-campaign --target wasm32v1-none --release + - name: Install stellar CLI + # Install the same CLI used in development (see `make setup`). + # --locked pins the dependency tree; --features opt enables the + # optimised build which includes the `contract bindings` sub-command. + run: cargo install --locked stellar-cli --features opt + - name: Regenerate bindings + run: | + mkdir -p bindings + stellar contract bindings json \ + --wasm target/wasm32v1-none/release/milestonex_campaign.wasm \ + --out bindings/ + - name: Fail if bindings are stale + run: | + if ! git diff --exit-code bindings/milestonex_campaign.json; then + echo "" + echo "❌ bindings/milestonex_campaign.json is stale." + echo " Run 'make bindings' locally and commit the updated file." + exit 1 + fi + echo "✅ Bindings are up to date." diff --git a/Makefile b/Makefile index aef980f..02c3888 100644 --- a/Makefile +++ b/Makefile @@ -5,9 +5,10 @@ ## make deny - Check licenses ## make fmt - Format code ## make clippy - Lint code +## make bindings - Generate JSON ABI bindings for the campaign contract .PHONY: build build-wasm build-tools test fmt lint clean optimize help \ - setup deploy-testnet deploy-sandbox sandbox-start audit deny + setup deploy-testnet deploy-sandbox sandbox-start audit deny bindings # Default target build: build-wasm build-tools @@ -80,6 +81,17 @@ deploy-testnet: build-wasm bash scripts/deploy.sh testnet +# Generate JSON ABI bindings for the canonical campaign contract. +# Requires: stellar CLI (cargo install --locked stellar-cli --features opt) +# Requires: WASM built first (make build-wasm) +bindings: build-wasm + @echo "📎 Generating contract bindings..." + @mkdir -p bindings + stellar contract bindings json \ + --wasm target/wasm32v1-none/release/milestonex_campaign.wasm \ + --out bindings/ + @echo "✅ Bindings written to bindings/milestonex_campaign.json" + # Run cargo-audit for vulnerability scanning audit: @echo "🔒 Running security audit..." @@ -113,4 +125,5 @@ help: @echo " make deploy-sandbox - Deploy contract to local sandbox" @echo " make deploy-testnet - Deploy contract to Stellar testnet" @echo " make optimize - Optimize WASM with wasm-opt -Oz" + @echo " make bindings - Generate JSON ABI bindings for the campaign contract" @echo " make help - Show this help message" diff --git a/bindings/milestonex_campaign.json b/bindings/milestonex_campaign.json new file mode 100644 index 0000000..9fa500a --- /dev/null +++ b/bindings/milestonex_campaign.json @@ -0,0 +1,194 @@ +{ + "contract_name": "milestonex_campaign", + "contract_id": null, + "network_passphrase": null, + "functions": [ + { + "name": "initialize", + "doc": "Initialize a new campaign with strict validation on all inputs.", + "inputs": [ + { "name": "creator", "value": { "type": "address" } }, + { "name": "goal_amount", "value": { "type": "i128" } }, + { "name": "end_time", "value": { "type": "u64" } }, + { "name": "accepted_assets", "value": { "type": "vec", "items": { "type": "custom", "name": "StellarAsset" } } }, + { "name": "milestones", "value": { "type": "vec", "items": { "type": "custom", "name": "MilestoneData" } } }, + { "name": "min_donation_amount", "value": { "type": "i128" } } + ], + "outputs": [ + { "type": "result", "value": { "type": "void" }, "error": { "type": "custom", "name": "Error" } } + ] + }, + { + "name": "donate", + "doc": "Donate to the campaign.", + "inputs": [ + { "name": "donor", "value": { "type": "address" } }, + { "name": "amount", "value": { "type": "i128" } }, + { "name": "asset", "value": { "type": "custom", "name": "AssetInfo" } } + ], + "outputs": [] + }, + { + "name": "get_total_raised", + "doc": "Return the total amount raised across all assets.", + "inputs": [], + "outputs": [{ "type": "i128" }] + }, + { + "name": "get_donation_count", + "doc": "Return the total number of donations.", + "inputs": [], + "outputs": [{ "type": "u64" }] + }, + { + "name": "get_donor_count", + "doc": "Return the number of unique donors.", + "inputs": [], + "outputs": [{ "type": "u32" }] + }, + { + "name": "get_release_count", + "doc": "Return the number of milestone releases.", + "inputs": [], + "outputs": [{ "type": "u64" }] + }, + { + "name": "get_total_tx_count", + "doc": "Return the total transaction count.", + "inputs": [], + "outputs": [{ "type": "u64" }] + }, + { + "name": "get_campaign_report", + "doc": "Return a full campaign report.", + "inputs": [], + "outputs": [{ "type": "option", "value": { "type": "custom", "name": "CampaignReport" } }] + }, + { + "name": "get_platform_summary", + "doc": "Return a platform-level summary.", + "inputs": [], + "outputs": [{ "type": "custom", "name": "PlatformSummary" }] + }, + { + "name": "get_dashboard_metrics", + "doc": "Return dashboard analytics metrics.", + "inputs": [], + "outputs": [{ "type": "custom", "name": "DashboardMetrics" }] + }, + { + "name": "get_donor_record", + "doc": "Return the donor record for a given address.", + "inputs": [ + { "name": "donor", "value": { "type": "address" } } + ], + "outputs": [{ "type": "option", "value": { "type": "custom", "name": "DonorRecord" } }] + }, + { + "name": "hello", + "doc": "Health-check entry point.", + "inputs": [], + "outputs": [{ "type": "symbol" }] + }, + { + "name": "version", + "doc": "Return the contract version.", + "inputs": [], + "outputs": [{ "type": "u32" }] + }, + { + "name": "is_refund_eligible", + "doc": "Return whether a donor is eligible for a refund.", + "inputs": [ + { "name": "donor", "value": { "type": "address" } } + ], + "outputs": [{ "type": "bool" }] + }, + { + "name": "claim_refund", + "doc": "Claim a refund for a donor.", + "inputs": [ + { "name": "donor", "value": { "type": "address" } } + ], + "outputs": [] + }, + { + "name": "end_campaign", + "doc": "End the campaign.", + "inputs": [], + "outputs": [] + }, + { + "name": "cancel_campaign", + "doc": "Cancel the campaign.", + "inputs": [], + "outputs": [] + }, + { + "name": "extend_deadline", + "doc": "Extend the campaign deadline.", + "inputs": [ + { "name": "new_end_time", "value": { "type": "u64" } } + ], + "outputs": [] + }, + { + "name": "get_campaign_status", + "doc": "Return the current campaign status.", + "inputs": [], + "outputs": [{ "type": "custom", "name": "CampaignStatusResponse" }] + }, + { + "name": "release_milestone", + "doc": "Release funds for a completed milestone (single asset).", + "inputs": [ + { "name": "milestone_index", "value": { "type": "u32" } }, + { "name": "recipient", "value": { "type": "address" } } + ], + "outputs": [] + }, + { + "name": "release_milestone_multi_asset", + "doc": "Release funds for a completed milestone (multi-asset).", + "inputs": [ + { "name": "milestone_index", "value": { "type": "u32" } }, + { "name": "recipient", "value": { "type": "address" } } + ], + "outputs": [] + }, + { + "name": "get_milestone_view", + "doc": "Return details for a specific milestone by index.", + "inputs": [ + { "name": "index", "value": { "type": "u32" } } + ], + "outputs": [{ "type": "custom", "name": "MilestoneData" }] + }, + { + "name": "get_all_milestones", + "doc": "Return all milestones with their current state.", + "inputs": [], + "outputs": [{ "type": "vec", "items": { "type": "custom", "name": "MilestoneView" } }] + }, + { + "name": "upgrade", + "doc": "Upgrade the contract WASM.", + "inputs": [ + { "name": "new_wasm_hash", "value": { "type": "bytes_n", "n": 32 } } + ], + "outputs": [] + }, + { + "name": "freeze", + "doc": "Freeze the contract, halting all state-changing operations.", + "inputs": [], + "outputs": [] + }, + { + "name": "unfreeze", + "doc": "Unfreeze the contract.", + "inputs": [], + "outputs": [] + } + ] +}