feat: gas optimizations to broker logic with fixes - #232
Open
Ronnieraj37 wants to merge 1 commit into
Open
Conversation
Pull Request ReviewThis Solidity upgradeable lending-broker PR improves fee-on-transfer repayment accounting, introduces a dedicated liquidation input error, and adds gas optimizations for rate accrual, whitelist retrieval, and fixed-position scans. It also appends an indexed fixed-term lookup mapping with a one-time reinitializer for existing proxies and maintains that index during term additions, updates, and removals. Sensitive ContentNo sensitive content detected. Security IssuesNo serious security issues detected. Generated by Hashdit Bot. This tool can absolutely NOT replace manual audits. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
📄 Description
A few gas optimizations in
src/broker/, plus two correctness fixes I cameacross while reading through it.
Scope:
LendingBroker.sol,LendingBrokerOperatorLib.sol,RateCalculator.sol.No storage slots reordered; one new variable is appended (see Upgrade safety).
🧠 Rationale
Two correctness issues worth fixing regardless of gas:
_pullPaymentcredited the requestedamountrather than the amount received, so a fee-taking loan token would under-collect
on every repay. Now credits the balance delta (as
_borrowFromMoolahalreadydoes), with an
InsufficientAmountguard inrepayAll. No-op for zero-feetokens; relevant given USDT's owner-settable fee.
InvalidMarketId;changed to a new
InconsistentInput.The gas changes remove work from the hot paths (accrual, fixed repay, borrow):
an unbounded term scan becomes O(1), a full array-to-memory copy becomes a
storage scan, and a same-block accrual short-circuits instead of rewriting
storage.
Honest trade-off: on the current test suite (~1 fixed position, 3 terms) the
bundle is near break-even — the fee-on-transfer safety reads (change 1) offset
the wins, and changes 6 & 7 only pull ahead at scale (more positions/terms). I
verified deployment state on BSC mainnet (
maxFixedLoanPositions = 100); theper-change effects are in the summary below so maintainers can weigh each.
Upgrade safety
termIndexPlusOneis appended after the existing V2 storage — no slot moves.Proxies that already have terms need a one-time backfill, run atomically with
the upgrade:
initializeTermIndexis areinitializer(2). Skipping it on a broker withexisting terms leaves the index empty and
_getTermByIdrevertsTermNotFound.Fresh deployments (and brokers with no terms) don't need it.
🧪 Example / Testing
forge build— clean.forge test --match-path 'test/broker/*'— passes.I can add dedicated tests for the new paths (term-index add/remove with
swap-and-pop re-pointing, the backfill reinitializer, a fee-on-transfer repay
case) if maintainers want them in this PR.
🧬 Changes Summary
Notable changes:
balance delta, not the requested amount;
repayAllgains anInsufficientAmountguard.InconsistentInput(fix) — wasInvalidMarketId. Note: this changes a revert selector.RateCalculatorcleanup (gas) — shared accrual helper, same-blockshort-circuit, and drop a redundant
SLOADingetRate(~1.75k vs ~6.9k onsame-block accrual; −288 bytes).
RateCalculator.registerBroker: struct literal → direct field assignment(gas) — minor runtime saving + smaller bytecode (one-time admin call).
getLiquidationWhitelist→EnumerableSet.values()(gas) — ~18%cheaper, scales with set size.
memory→storagescan (gas) — up to ~8.4k savedat 10 positions (break-even ~2).
termIndexPlusOnemapping (gas) —~1.7k/borrow at 3 terms, scales with term count.
Notes / decisions
Things I deliberately left as-is, with the reasoning:
worse — it adds a cold
SSTOREto every borrow, and the whole-collectionoperations (
delete, bulk-assign in liquidation/refinance) get more expensive.(
_getFixedPositionByPosId,_removeFixedPositionByPosId,_updateFixedPosition) exist in bothLendingBrokerand the operator library.Internal library functions are inlined, so sharing them saves nothing; making
them
public(delegatecall) measured +245 gas/call. Keeping theduplication is the cheaper option, at the cost of keeping the two copies in
sync.
RateConfigleft unpacked. Packing would save on the once-per-brokerregisterBrokerbut cost on every accrual (shared-slot read-modify-write), andaccrual is the hot path.
in the loops, manual bounds-check elision), but for audited financial contracts
the readability/auditability cost isn't worth it in a general contribution. If
a specific hot path is worth it — the liquidation cascade is the best candidate
— I'd propose that as its own narrowly-scoped PR.
Happy to split the correctness fixes and the optimizations into separate PRs if
that's easier to review — and glad to take a look at other parts of the codebase
too if the maintainers would find it useful.