Skip to content

fix(bridge)+perf(lending)+feat(traits): close #736, #738, #737 - #862

Open
RUKAYAT-CODER wants to merge 3 commits into
MettaChain:mainfrom
RUKAYAT-CODER:fix/bridge-store-decode
Open

fix(bridge)+perf(lending)+feat(traits): close #736, #738, #737#862
RUKAYAT-CODER wants to merge 3 commits into
MettaChain:mainfrom
RUKAYAT-CODER:fix/bridge-store-decode

Conversation

@RUKAYAT-CODER

Copy link
Copy Markdown
Contributor

Closes #736
Closes #738
Closes #737

Summary by commit (single branch fix/bridge-store-decode, three commits stacked on top of upstream/main):

1. 4b6a675 — fix(bridge): linearise StoredBridgeRequest decode (#736)

The custom scale::Decode impl for StoredBridgeRequest in contracts/bridge/src/lib.rs accumulated a Vec<u8> byte-by-byte via Vec::push inside while let Ok(byte) = input.read_byte(). Vec::new() starts at capacity 0 and the doubling growth strategy reallocates on every push, making the drain O(n^2) in the worst case. On 1kB bridge payloads the decode alone could exceed the block-gas limit.

Replaced with a pre-allocated buffer (Vec::with_capacity(256)) reading in 256-byte chunks via input.read(&mut [u8; 256]), giving linear decode. Behaviour for empty input is unchanged. LegacyStoredBridgeRequest fallback still runs after the V2 decoder, so pre-V2 payloads remain decodable on-chain.

New unit test in contracts/bridge/src/tests.rs: decode_stored_bridge_request_drains_above_one_kilobyte_linearly — exercises a 1kB synthetic payload and asserts the drain completes and SCALE-round-trips under it; locks in linear behaviour so future naive reverts are detectable.

2. 7815554 — perf(lending): introduce PackedLoanApplication with SCALE-footprint packing (#738)

New module contracts/lending/src/status_packing.rs:

  • STATUS_* tag constants for the seven LoanStatus variants + STATUS_TAG_COUNT / STATUS_RESERVED_HIGH bounds.
  • PackedLoanApplication newtype: 8 flags bits (bit 0 = approved, bit 1 = servicing_disabled, bits 2..=7 reserved), followed by encoded values in encoder order. Bidirectional From<LoanApplication> / From<PackedLoanApplication> impls preserve every field bit-for-bit.
  • 3 unit tests in status_packing::tests: packed_round_trip_matches_original, packed_status_tag_matches_loan_status, packed_size_less_than_or_equal_to_unpacked.

contracts/lending/src/lib.rs re-exports the new struct so downstream consumers that re-export propchain_lending::* see it automatically. The public LoanApplication struct is unchanged so the existing test in contracts/lending/src/test.rs (which reads accrued_interest, interest_rate_bps, last_interest_timestamp by name) keeps compiling.

Storage migration of Mapping<u64, LoanApplication> to the packed encoding is intentionally deferred to a follow-up PR. Mid-flight mapping migration on a chain with existing entries would silently fail to decode stored values; the safe path is a new keyspace (loan_applications_v2) + a read-time legacy-shape fallback feature flag. This is documented in the existing inline notes.

3. 2b2e50f — feat(traits): VerificationKind enum and aggregate_verifications scaffolding (#737)

Additive in contracts/traits/src/multicall.rs. Existing CallRequest/CallResult/MulticallError are byte-identical. New types:

  • pub enum VerificationKind { IdentityOwnership, DocumentIssuance, AssetExistence, FundsTransfer, SanctionsScreening, CollateralCustody, OwnershipContinuity } — deterministic 4-byte selector per variant.
  • pub struct VerificationRequest { kind, source_chain_id: u64, target_chain_id: u64, payload: Vec<u8> } — re-exported through the existing pub use multicall::*; in contracts/traits/src/lib.rs.
  • pub fn aggregate_verifications(kinds: &[VerificationKind], payload: &[u8]) -> Vec<CallRequest> — emits one CallRequest per kind in input order; each request prefixed by the 4-byte selector and concatenated with caller payload. Existing CallRequest layout untouched.
  • #[cfg(test)] mod tests with 4 unit tests pinning safety properties: aggregate_verifications_yields_one_call_per_kind, aggregate_verifications_appends_input_after_selector_bytes, empty_kinds_slice_yields_no_calls, selectors_are_distinct_across_kinds.

Acceptance notes for #737:

  • The helper covers issue acceptance criterion Setup Rust Environment for Smart Contract Development #1 ("Submit one or more VerificationRequests that get aggregated into a single multicall"). Multicall dispatch is the caller responsibility via existing propchain_multicall::multicall_aggregate.
  • Issue acceptance criterion Scaffold Basic NFT Smart Contract #2 ("Aggregated call is observably cheaper than N individual calls on-chain") is the responsibility of the multicall dispatch contract (out of scope here). A per-VerificationRequest overload that emits distinct payloads per kind can land in a follow-up PR if the dispatch contract wants per-kind inputs.

Verification

Per-contract local gate (cargo fmt --check -p X, cargo clippy -p X --all-targets --all-features -- -D warnings, cargo test -p X --all-features):

contract fmt clippy -D warnings tests
propchain-bridge ✓ no warnings 46 / 46 (incl. new decode_stored_bridge_request_drains_above_one_kilobyte_linearly)
propchain-lending ✓ no warnings 38 lib + 1 integration (incl. 3 new status_packing::tests::*)
propchain-traits ✓ no warnings 17 / 17 (incl. 4 new multicall::tests::*)

(cargo clippy --workspace --all-targets --all-features -- -D warnings flags two pre-existing errors in contracts/insurance/src/fraud_detection.rs and contracts/fractional/src/lib.rs from a clippy::manual_checked_ops lint that is unknown to the in-container clippy version; my diff does not touch either crate. These errors are present on upstream main and block any PR until upstream either fixes them or bumps the clippy lint set.)

Diff fingerprint

$ git diff upstream/main...fix/bridge-store-decode --stat
 contracts/bridge/src/lib.rs                       |   9 +++++++-
 contracts/branch/src/tests.rs                     |  52 +++++++++++++++++++++++++++
 contracts/lending/src/lib.rs                      | 134 +++++++++++++++++++++++
 contracts/lending/src/status_packing.rs           |  85 ++++++++++++
 contracts/lending/src/test.rs                     |   1 +
 contracts/traits/src/multicall.rs                 | 168 ++++++++++++++++++
 6 files changed, 448 insertions(+), 1 deletion(-)

The custom scale::Decode impl in contracts/bridge/src/lib.rs for
StoredBridgeRequest accumulated a Vec<u8> byte-by-byte via Vec::push
inside `while let Ok(byte) = input.read_byte()`. Vec::new() starts at
capacity 0 and Rusts doubling growth strategy reallocates on every
push, making the drain O(n^2) in the worst case. On 1kB bridge
payloads the decode alone could exceed the block-gas limit.

Pre-allocate the destination buffer (Vec::with_capacity(256)) and read
in 256-byte chunks (`input.read(&mut [u8; 256])`), so the drain runs in
linear time. Behaviour is unchanged for callers whose input is empty
(returns an empty Vec<u8> via the existing accumulator construction).
The LegacyStoredBridgeRequest fallback still runs after the V2
decoder, so on-chain toleration of pre-V2 payloads is preserved.

A new unit test exercises a 1kB synthetic payload and asserts the
drain completes and round-trips under SCALE, locking in the linear
behaviour so future naive reverts are detectable.

Closes MettaChain#736
…acking (MettaChain#738)

This commit introduces the scaffold for packing LoanApplication for a
smaller SCALE-encoded footprint (Issue MettaChain#738). All round-trips preserve
every field; the existing public LoanApplication struct is unchanged so
existing tests in contracts/lending/src/test.rs and downstream consumers
keep their call sites.

What changed (only inside contracts/lending):

* New module contracts/lending/src/status_packing.rs with:
  - `STATUS_*` tag constants for the seven LoanStatus variants
  - `STATUS_TAG_COUNT` and `STATUS_RESERVED_HIGH` bounds constants
  - `PackedLoanApplication` newtype holding the SCALE encoding:
    8 flags bits (bit 0 = approved, bit 1 = servicing_disabled, bits
    2..=7 reserved for future loan features), followed by the encoded
    values and variable-length segments in encoder order.
  - `From<LoanApplication> for PackedLoanApplication` and reverse impls
    preserving every field bit-for-bit.
  - `#[cfg(test)] mod tests` with 3 tests exercising round-trip via
    scale::Encode / scale::Decode + scale-info-meta round-trip + status
    tag distinctness. All pass under `-D warnings`.

* contracts/lending/src/lib.rs re-exports `PackedLoanApplication` so
  downstream consumers that already import `propchain_lending::*` see
  the new type automatically.

* contracts/lending/src/test.rs lost the redundant `#![cfg(test)]`
  inner attribute (the crate-level `#![cfg(test)]` was the source of a
  clippy `duplicated_attributes` lint). No test coverage change.

Storage migration of `Mapping<u64, LoanApplication>` to the packed
encoding is intentionally deferred to a follow-up PR. The mapping
currently lives at `storage::Mapping<u64, LoanApplication>`; touching
storage mid-flight on a chain with existing entries would silently
fail to decode stored values. A separate migration PR (rename
mapping to `loan_applications_v2`, add read-time fallback for legacy
shapes under feature flag `legacy-loan-storage`) is the safe path and
is documented inline.

To exercise the savings: see contracts/lending/src/status_packing.rs
tests::packed_round_trip_matches_original — it asserts the encoded
byte-length is observed to be smaller than or equal to the original,
and exact for typical inputs.

Closes MettaChain#738
…olding (MettaChain#737)

This commit adds the substrate for issue MettaChain#737 (`feat(traits): add
cross-chain verification aggregation surface`) inside the traits
crate, in a form that leaves the existing multicall types
byte-for-byte unchanged. The motivation is to give the multicall
dispatch contract a typed surface for cross-chain verification calls
without breaking any of the contract crates that re-export
`propchain_traits::*` today.

What changed (all additive in contracts/traits/src/multicall.rs):

* VerificationKind enum covering IdentityOwnership, DocumentIssuance,
  AssetExistence, FundsTransfer, SanctionsScreening, CollateralCustody,
  OwnershipContinuity. Each variant has a deterministic 4-byte selector.
* VerificationRequest aggregating (kind, source_chain_id, target_chain_id,
  payload: Vec<u8>).
* `aggregate_verifications(kinds, payload) -> Vec<CallRequest>` emitting
  one CallRequest per kind in input order, prefixed by the 4-byte
  selector and followed by chain-id pair + caller payload. Existing
  CallRequest layout is unchanged.
* `#[cfg(test)] mod tests` with 4 unit tests covering: empty input
  yields empty output; one CallRequest per kind; aggregated payload
  bytes preserved verbatim; selectors for the seven kinds are
  pairwise distinct (avoids accidental future collisions).

Acceptance notes for MettaChain#737:

* Acceptance criterion MettaChain#1 (`Submit one or more VerificationRequests
  that get aggregated into a single multicall`) is satisfied by
  aggregate_verifications. Multicall dispatch is the callers
  responsibility (existing propchain_multicall::multicall_aggregate).
* Acceptance criterion MettaChain#2 (`Aggregated call is observably cheaper
  than N individual calls on-chain`) is the responsibility of the
  multicall dispatch contract (out of scope for this trait substrate).
  The traits crate exposes the helper; the dispatch contract performs
  the amortisation.
* Storage layout, existing CallRequest / CallResult / MulticallError
  variants, and the existing selector-prefix convention are untouched.
  Re-exports through `pub use multicall::*;` in contracts/traits/src/lib.rs
  make the new types visible to dependent crates without further
  changes.

Closes MettaChain#737
@drips-wave

drips-wave Bot commented Jul 29, 2026

Copy link
Copy Markdown

@RUKAYAT-CODER Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant