57810 sc insight gas optimization use calldata for external struct parameters in checkaccesstokeninfo or checkcustomerinfo or checkpromoterpaymentdistribution
Submitted on Oct 29th 2025 at 01:17:42 UTC by @hunterine123 for Audit Comp | Belong
Report ID: #57810
Report Type: Smart Contract
Report severity: Insight
Target: https://github.com/immunefi-team/audit-comp-belong/blob/main/contracts/v2/utils/SignatureVerifier.sol
Description
Summary
The following external view functions in SignatureVerifier.sol accept struct parameters as memory:
function checkAccessTokenInfo(address signer, AccessTokenInfo memory accessTokenInfo) external viewfunction checkCustomerInfo(address signer, CustomerInfo calldata customerInfo, VenueRules memory rules) external viewfunction checkPromoterPaymentDistribution(address signer, PromoterInfo memory promoterInfo) external view
Using memory for external function parameters forces an unnecessary copy from calldata to memory when the function is called from another contract. While view functions cost no gas when called off-chain, contract-to-contract calls incur extra gas due to the copy.
Impact
Extra gas spent in contract-to-contract calls.
Reduced clarity about parameter mutability.
Not aligned with Solidity best practices for external read-only parameters.
Recommendation
Change the struct parameters to calldata where applicable:
function checkAccessTokenInfo(address signer, AccessTokenInfo calldata accessTokenInfo) external viewfunction checkCustomerInfo(address signer, CustomerInfo calldata customerInfo, VenueRules calldata rules) external viewfunction checkPromoterPaymentDistribution(address signer, PromoterInfo calldata promoterInfo) external view
This removes unnecessary copies, signals read-only data, and improves gas efficiency and maintainability.
Proof of Concept
A POC demonstrates gas difference between memory and calldata for a struct parameter when invoked via a contract-to-contract call.
Steps to run the POC:
1. Deploy dummy contracts
Calldata vs Memory dummy contract (contains two functions: one accepts struct as
memory, one ascalldata).Calldata caller contract (calls the dummy contract methods to measure gas).
Files (examples provided below):
contracts/mocks/CalldataVsMemoryDummy.solcontracts/mocks/CalldataCaller.sol
Dummy contract: CalldataVsMemoryDummy.sol
Caller contract: CalldataCaller.sol
Hardhat test: test/v2/platform/poc-calldata-optimization.test.ts
Sample POC output (collapsed)
Calldata vs Memory Gas Optimization POC
====================================================================== 📊 CALLDATA vs MEMORY Gas Comparison - AccessTokenInfo Struct
🔴 CURRENT Implementation (MEMORY): function checkDummy(AccessTokenInfo memory accessTokenInfo) ⛽ Gas cost: 34116 gas
🟢 OPTIMIZED Implementation (CALLDATA): function checkDummy(AccessTokenInfo calldata accessTokenInfo) ⛽ Gas cost: 33460 gas
────────────────────────────────────────────────────────────────────── 💰 Gas Saved: 656 gas 📉 Percentage Saved: 1.92%
✔ should compare gas costs: MEMORY vs CALLDATA (called from another contract)
Files/Lines referenced
Affected functions in
SignatureVerifier.sol:Line 53:
checkAccessTokenInfo(AccessTokenInfo memory)Line 157:
checkCustomerInfo(VenueRules memory)Line 204:
checkPromoterPaymentDistribution(PromoterInfo memory)
Conclusion
Switching these external struct parameters from memory to calldata reduces unnecessary memory copies for contract-to-contract calls, improves gas efficiency, and follows Solidity best practices for read-only external parameters.
Was this helpful?