Skip to content

feat(wrapper): operator-controlled pricing via MethodConfig registry#232

Merged
AntonioVentilii merged 5 commits into
mainfrom
av/wrapper-method-config-pricing
Jul 15, 2026
Merged

feat(wrapper): operator-controlled pricing via MethodConfig registry#232
AntonioVentilii merged 5 commits into
mainfrom
av/wrapper-method-config-pricing

Conversation

@AntonioVentilii

@AntonioVentilii AntonioVentilii commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

Moves fee and forwarded-cycle amounts out of caller arguments and into an operator-controlled, per-(target, method) registry. Callers choose only which payment type to use; they can no longer set what the wrapper charges or forwards.

Problem

bridge_call charged a caller-supplied fee_amount and, independently, forwarded a caller-supplied cycles_to_forward from the wrapper's own cycle balance. The two were unrelated, so a caller could pay a trivial (or zero) fee while forwarding a large amount and drain the wrapper. Ingress callers can't attach cycles at all, so they paid nothing.

That drain is the sharp edge of a deeper mismatch: PAPI is a payment gateway where the API operator sets the price. Caller-supplied fee_amount/cycles_to_forward let the customer name their own price, which is wrong regardless of the drain.

Behavior

  • Registry (state.rs): (target, method) → MethodConfig { fee, supported, forward_cycles }, persisted across upgrades via pre_upgrade/post_upgrade.
  • Admin API (controller-only, is_controller guard): set_method_config / remove_method_config; plus get_method_config / list_method_configs queries.
  • Config validation (validate_config): a method that forwards cycles must denominate its fee in cycles and set it >= the forwarded amount. A draining config cannot be stored.
  • bridge_call: looks up the config, rejects unconfigured (target, method), takes the fee and forward amount from the config, and requires a cycle-denominated payment type when forwarding (token payments credit a token account, not the cycle balance).

The caller can no longer influence what the wrapper pays out, so the drain is closed structurally rather than by a numeric check. This also completes the existing MethodConfig { fee, forward_cycles } scaffolding, which was defined but never wired in.

Breaking change

Callers:

  • fee_amount and cycles_to_forward are removed from Call0Args / CallBlobArgs / CallTextArgs. Callers send target, method, args, and payment only.
  • A call to a (target, method) that hasn't been registered is rejected with No price is configured.

Operators:

  • Each (target, method) must be registered via set_method_config before it is callable.

ic_papi_wrapper.did is regenerated and the README updated to match.

Tests

  • Unit (validate_config): accepts no-forward and covered-forward configs; rejects a token-denominated fee with forwarding; rejects a fee below the forwarded amount.
  • Integration (PocketIC): self-call rejected; unconfigured method rejected; non-controller set_method_config rejected.

Known gaps

  • MethodConfig.supported is stored but not yet enforced per-method in bridge_call (payment-type support still comes from the global PAYMENT_GUARD).
  • No configure-as-controller → priced-call end-to-end test; the priced path is covered indirectly by the validation and rejection tests.
  • The registry uses heap state with stable_save/stable_restore; ic-stable-structures would scale better if config counts grow large.

Move fee and forwarded-cycle amounts from caller-supplied arguments to a
server-side, controller-managed registry keyed by (target, method). Callers
now choose only the payment type; they can no longer pay a trivial fee while
forwarding a large amount to drain the wrapper's cycle balance.

- Registry in state.rs with controller-gated set/remove and query reads,
  persisted across upgrades.
- set_method_config validates that a cycle-forwarding method charges its fee
  in cycles and covers the forwarded amount.
- bridge_call looks up the config, rejects unconfigured methods, and requires
  a cycle-denominated payment when forwarding cycles.
- Drops fee_amount/cycles_to_forward from Call0Args/CallBlobArgs/CallTextArgs;
  regenerated .did and updated README.
@AntonioVentilii
AntonioVentilii marked this pull request as ready for review July 15, 2026 11:08
@AntonioVentilii
AntonioVentilii requested a review from a team as a code owner July 15, 2026 11:08
@zeropath-ai

zeropath-ai Bot commented Jul 15, 2026

Copy link
Copy Markdown

No security or compliance issues detected. Reviewed everything up to 41ea101.

Security Overview
Detected Code Changes
Change Type Relevant files
Enhancement ► README.md
    Update pricing/flow to configure per-method price instead of caller-set fees
Enhancement ► src/wrapper/ic_papi_wrapper.did
    Add MethodConfig, MethodKey, FeeDenom, FeeSpec, VendorPaymentConfig types; extend API to get/list/remove/set method configs; update Call args to reflect new pricing model and remove fee/cycles fields where appropriate
Enhancement ► src/wrapper/src/api/call.rs
    Integrate method config lookup; charge operator-configured fee; forward configured cycles; enforce cycle-payments for forwarded cycles; add is_cycle_payment helper
Enhancement ► src/wrapper/src/domain/errors.rs
    Add new BridgeError variants for MethodNotConfigured and ForwardRequiresCyclePayment; update Display messages
Enhancement ► src/wrapper/src/domain/types.rs
    Replace fee_amount and cycles_to_forward with internal MethodConfig-based handling; add MethodKey; annotate Call args with new guidance/comments
Enhancement ► src/wrapper/src/lib.rs
    Introduce controller-only configuration section; add ensure_controller, validate_config; implement set_method_config, remove_method_config, get_method_config, list_method_configs; add upgrade hooks (pre_upgrade/post_upgrade); expose tests for configuration behavior
Enhancement ► src/wrapper/src/state.rs
    Add new state module to store per-(target, method) MethodConfig in a thread-local registry with persistence hooks
Enhancement ► src/wrapper/tests/it/bridge_tests.rs
    Adjust tests to use new MethodConfig/MethodKey types; prepare for new config-based flow; import FeeDenom, FeeSpec, MethodConfig, MethodKey

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses the wrapper “cycle-drain” vulnerability by moving fee and forwarded-cycles selection from caller-controlled inputs to an operator-controlled, persisted (target, method) -> MethodConfig registry, and updating the wrapper’s public API, Candid interface, documentation, and tests accordingly.

Changes:

  • Introduces a heap-backed MethodConfig registry persisted across upgrades (pre_upgrade/post_upgrade) and exposes controller-gated admin/query APIs to manage it.
  • Updates bridge_call to require a configured (target, method), charge the operator-set fee, and forward operator-set cycles (with a guard requiring cycle-denominated payment when forwarding).
  • Removes caller-supplied fee_amount / cycles_to_forward from the public call* APIs and regenerates the .did plus README updates and integration tests.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/wrapper/tests/it/bridge_tests.rs Updates integration tests for operator-priced flow (unconfigured method + controller-gated config).
src/wrapper/src/state.rs Adds in-canister registry storage for method pricing configs.
src/wrapper/src/lib.rs Adds controller-only config endpoints, config validation, and upgrade persistence hooks.
src/wrapper/src/domain/types.rs Updates argument types to remove caller-priced fields; adds hashing for MethodKey.
src/wrapper/src/domain/errors.rs Adds errors for “method not configured” and “forward requires cycle payment”.
src/wrapper/src/api/call.rs Switches bridge_call to registry-based pricing and configured forwarded cycles.
src/wrapper/ic_papi_wrapper.did Regenerates the public Candid interface for the breaking API + new admin endpoints.
README.md Updates usage/docs to reflect operator-controlled pricing and new configuration flow.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/wrapper/src/api/call.rs
Comment thread README.md Outdated
Comment thread README.md Outdated
Comment thread src/wrapper/src/lib.rs
AntonioVentilii and others added 2 commits July 15, 2026 13:13
…re, fix README

- Add #[must_use] to state query fns and controller query endpoints
- Allow needless_pass_by_value on candid endpoints taking owned MethodKey
- post_upgrade: log (not swallow) stable-restore failures so an empty
  registry after a bad upgrade is diagnosable
- README: use CallerPaysIcrc2Tokens (not a cycles variant) in the ckUSDC
  example; note that MethodConfig.supported is not yet enforced
@AntonioVentilii
AntonioVentilii enabled auto-merge (squash) July 15, 2026 11:22
…fig-pricing

# Conflicts:
#	src/wrapper/src/api/call.rs
#	src/wrapper/src/domain/errors.rs
#	src/wrapper/tests/it/bridge_tests.rs
@AntonioVentilii
AntonioVentilii merged commit 54fac7c into main Jul 15, 2026
13 checks passed
@AntonioVentilii
AntonioVentilii deleted the av/wrapper-method-config-pricing branch July 15, 2026 11:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants