You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Three contracts fetch the full ProtocolParameters struct from the parameters contract multiple times inside a single transaction, and each fetch is a cross-contract invocation. creditline's get_protocol_parameters (contracts/creditline-contract/src/lib.rs:1189-1203) is called from validate_guarantee (:336), validate_reputation (:380), interest_rate_bps (:502), calculate_default_penalty (:530), warn_grace_period (:561) and mark_defaulted (:601) — so a single create_loan/mark_defaulted can invoke the parameters contract three or more times. liquidity-pool's get_upgrade_delay_seconds (contracts/liquidity-pool-contract/src/lib.rs:885-901) and reputation's (contracts/reputation-contract/src/lib.rs:292-308) each re-invoke get_parameters per upgrade call. This workstream reads the parameters once per transaction and passes the struct down.
Why this matters: the property is a transaction reads the parameters contract at most once. Parameters change only through a governed multisig proposal, so re-fetching mid-transaction is both wasteful and a (theoretical) consistency hazard if a proposal executes between reads; read-once/pass-down removes both.
Labels
area: contractstype: refactorpriority: medium
Workstream 1 — Read-once/pass-down parameters across the hot paths
Objective
Fetch ProtocolParameters exactly once per entrypoint invocation and thread it into every internal helper, across creditline, liquidity-pool and reputation.
Problem
creditline's helpers each call Self::get_protocol_parameters(env) independently (contracts/creditline-contract/src/lib.rs:336,380,502,530,561,601), and the function itself performs a cross-contract try_invoke_contract("get_parameters") (:1193-1198) with a fail-soft default (:1199-1201). create_loan reaches validate_guarantee (:336) and validate_reputation (:380) and interest_rate_bps (:502) → at least three parameter invocations. mark_defaulted reaches warn_grace_period-style grace logic (:561,601) plus calculate_default_penalty (:530) → multiple invocations. liquidity-pool's propose_upgrade calls get_upgrade_delay_seconds (contracts/liquidity-pool-contract/src/lib.rs:127) which invokes parameters (:885-901); reputation's propose_upgrade does the same (contracts/reputation-contract/src/lib.rs:235,292-308). Each invocation is a separate host call plus deserialization of the whole struct.
contracts/liquidity-pool-contract/src/lib.rs (edit — same for upgrade delay)
contracts/reputation-contract/src/lib.rs (edit — same for upgrade delay)
each affected src/tests.rs (edit — parity tests)
Implementation
In creditline, fetch ProtocolParameters once at the top of each public entrypoint (create_loan, request_loan, approve_loan, mark_defaulted, warn_grace_period, apply_late_fees) and pass &ProtocolParameters into the helpers instead of having them re-read (:336,380,502,530,561,601).
Keep get_protocol_parameters as the single fetch point (fail-soft default preserved at :1199-1201); add a PARAMS_CACHE_PER_TX (default true) gate so the old per-call fetch can be restored for bisecting.
In liquidity-pool and reputation, compute the delay from one fetch in propose_upgrade rather than re-invoking in execute_upgrade — or cache the fetched params for the call if only propose needs it (liquidity-pool lib.rs:127; reputation lib.rs:235).
Preserve the fail-soft semantics: when the parameters contract is unset or the call fails, both fall back to default_protocol_parameters() / DEFAULT_UPGRADE_DELAY_SECONDS exactly as today.
Fail-soft behaviour is unchanged: unset/failing parameters contract still yields default_protocol_parameters() and the bounded defaults (min_guarantee_percent, grace_period_seconds, upgrade_delay_seconds).
All existing parameters-driven tests (setup_parameters_with_grace_period at tests.rs:1638-1650, upgrade-delay tests) stay green.
Testing
Unit: counting mock proves one get_parameters per entrypoint; fail-soft path returns defaults.
Integration: real creditline + real parameters — outcomes identical to pre-change; a mid-transaction parameter proposal cannot be observed twice.
cargo fmt --all --check, cargo clippy --workspace --all-targets -- -D warnings, cargo build --locked --target wasm32-unknown-unknown --release, cargo test --locked all exit 0; test count does not drop; coverage on touched code not reduced.
No entrypoint signature, error code, event or parameter-default change; fail-soft semantics preserved.
No secrets; no changes outside the three contracts' src/*.
Security & compatibility considerations
The fail-soft default path (unwrap_or_else(|_| default_protocol_parameters()), creditline lib.rs:1199-1201) is a security-relevant fallback: threading a cached struct must not change which default is used when the parameters contract is missing or reverts.
Reading parameters once per transaction is also a mild consistency win — it closes the window where a proposal executed between two reads could produce a mixed-parameter decision; call this out as an intended (and safe) consequence.
Do not cache parameters across transactions (that would defeat governance); the cache is strictly per-invocation.
References
contracts/creditline-contract/src/lib.rs:336,380,502,530,561,601,1189-1203 — the repeated fetch sites.
In scope:contracts/creditline-contract/src/lib.rs, contracts/liquidity-pool-contract/src/lib.rs, contracts/reputation-contract/src/lib.rs, and the matching src/tests.rs — only. Out of scope: the parameters contract itself; the vendor-registry/vouching contracts; changing fail-soft defaults or governance; new dependencies; reformatting untouched modules. Must: follow the repo PR template exactly; keep CI green for real (fmt + clippy + build + test); add the counting-mock + fail-soft tests above; reference this issue (Closes #<n>); no secrets.
Summary
Three contracts fetch the full
ProtocolParametersstruct from the parameters contract multiple times inside a single transaction, and each fetch is a cross-contract invocation. creditline'sget_protocol_parameters(contracts/creditline-contract/src/lib.rs:1189-1203) is called fromvalidate_guarantee(:336),validate_reputation(:380),interest_rate_bps(:502),calculate_default_penalty(:530),warn_grace_period(:561) andmark_defaulted(:601) — so a singlecreate_loan/mark_defaultedcan invoke the parameters contract three or more times. liquidity-pool'sget_upgrade_delay_seconds(contracts/liquidity-pool-contract/src/lib.rs:885-901) and reputation's (contracts/reputation-contract/src/lib.rs:292-308) each re-invokeget_parametersper upgrade call. This workstream reads the parameters once per transaction and passes the struct down.Why this matters: the property is a transaction reads the parameters contract at most once. Parameters change only through a governed multisig proposal, so re-fetching mid-transaction is both wasteful and a (theoretical) consistency hazard if a proposal executes between reads; read-once/pass-down removes both.
Labels
area: contractstype: refactorpriority: mediumWorkstream 1 — Read-once/pass-down parameters across the hot paths
Objective
Fetch
ProtocolParametersexactly once per entrypoint invocation and thread it into every internal helper, across creditline, liquidity-pool and reputation.Problem
creditline's helpers each call
Self::get_protocol_parameters(env)independently (contracts/creditline-contract/src/lib.rs:336,380,502,530,561,601), and the function itself performs a cross-contracttry_invoke_contract("get_parameters")(:1193-1198) with a fail-soft default (:1199-1201).create_loanreachesvalidate_guarantee(:336) andvalidate_reputation(:380) andinterest_rate_bps(:502) → at least three parameter invocations.mark_defaultedreacheswarn_grace_period-style grace logic (:561,601) pluscalculate_default_penalty(:530) → multiple invocations. liquidity-pool'spropose_upgradecallsget_upgrade_delay_seconds(contracts/liquidity-pool-contract/src/lib.rs:127) which invokes parameters (:885-901); reputation'spropose_upgradedoes the same (contracts/reputation-contract/src/lib.rs:235,292-308). Each invocation is a separate host call plus deserialization of the whole struct.Scope
contracts/creditline-contract/src/lib.rs(edit — fetch once, pass down)contracts/liquidity-pool-contract/src/lib.rs(edit — same for upgrade delay)contracts/reputation-contract/src/lib.rs(edit — same for upgrade delay)src/tests.rs(edit — parity tests)Implementation
ProtocolParametersonce at the top of each public entrypoint (create_loan,request_loan,approve_loan,mark_defaulted,warn_grace_period,apply_late_fees) and pass&ProtocolParametersinto the helpers instead of having them re-read (:336,380,502,530,561,601).get_protocol_parametersas the single fetch point (fail-soft default preserved at:1199-1201); add aPARAMS_CACHE_PER_TX(defaulttrue) gate so the old per-call fetch can be restored for bisecting.propose_upgraderather than re-invoking inexecute_upgrade— or cache the fetched params for the call if onlyproposeneeds it (liquidity-pool lib.rs:127;reputation lib.rs:235).default_protocol_parameters()/DEFAULT_UPGRADE_DELAY_SECONDSexactly as today.parameters_invocationscounter (via (Medium) CON-E3.1 gas harness: resource-fee benchmark scaffold + reproducible measurement script #118); default off.Acceptance Criteria
create_loan/mark_defaultedinvokes the parameters contract exactly once (observable via a counting-parameters mock or the (Medium) CON-E3.1 gas harness: resource-fee benchmark scaffold + reproducible measurement script #118 harness), down from 2+.default_protocol_parameters()and the bounded defaults (min_guarantee_percent,grace_period_seconds,upgrade_delay_seconds).setup_parameters_with_grace_periodattests.rs:1638-1650, upgrade-delay tests) stay green.Testing
get_parametersper entrypoint; fail-soft path returns defaults.create_loan/mark_defaultedshows strictly fewer cross-contract invocations and lower cost.Shared acceptance criteria (whole epic)
cargo fmt --all --check,cargo clippy --workspace --all-targets -- -D warnings,cargo build --locked --target wasm32-unknown-unknown --release,cargo test --lockedall exit 0; test count does not drop; coverage on touched code not reduced.src/*.Security & compatibility considerations
unwrap_or_else(|_| default_protocol_parameters()),creditline lib.rs:1199-1201) is a security-relevant fallback: threading a cached struct must not change which default is used when the parameters contract is missing or reverts.References
contracts/creditline-contract/src/lib.rs:336,380,502,530,561,601,1189-1203— the repeated fetch sites.contracts/liquidity-pool-contract/src/lib.rs:127,885-901— upgrade-delay fetch.contracts/reputation-contract/src/lib.rs:235,292-308— upgrade-delay fetch.contracts/parameters-contract/src/lib.rs—get_parametersentrypoint.contracts/creditline-contract/src/tests.rs:1638-1650— parameters wiring helper that must stay valid.If you're solving this with AI
In scope:
contracts/creditline-contract/src/lib.rs,contracts/liquidity-pool-contract/src/lib.rs,contracts/reputation-contract/src/lib.rs, and the matchingsrc/tests.rs— only.Out of scope: the parameters contract itself; the vendor-registry/vouching contracts; changing fail-soft defaults or governance; new dependencies; reformatting untouched modules.
Must: follow the repo PR template exactly; keep CI green for real (fmt + clippy + build + test); add the counting-mock + fail-soft tests above; reference this issue (
Closes #<n>); no secrets.Contribution requirements: Follow the repo PR template exactly — https://github.com/StepFi-app/StepFi-Contracts/blob/main/.github/pull_request_template.md. Reference this issue in your PR. CI must pass green (fmt + clippy + build + tests). No secrets; no out-of-scope changes.