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 | Belongarrow-up-right

  • 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 view

  • function checkCustomerInfo(address signer, CustomerInfo calldata customerInfo, VenueRules memory rules) external view

  • function 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 view

  • function checkCustomerInfo(address signer, CustomerInfo calldata customerInfo, VenueRules calldata rules) external view

  • function 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

1. Deploy dummy contracts

  • Calldata vs Memory dummy contract (contains two functions: one accepts struct as memory, one as calldata).

  • Calldata caller contract (calls the dummy contract methods to measure gas).

Files (examples provided below):

  • contracts/mocks/CalldataVsMemoryDummy.sol

  • contracts/mocks/CalldataCaller.sol

2

2. Run the test

Add and run the test:

The test deploys the dummy contract and the caller, constructs a sample AccessTokenInfo struct, calls both the memory and calldata variants via the caller contract, measures gas, and prints the comparison.

3

3. Observe results

Example output (from the POC run):

  • MEMORY version gas cost: 34116 gas

  • CALLDATA version gas cost: 33460 gas

  • Gas saved: 656 gas (~1.92%)

The test also asserts that calldata variant gas is less-than-or-equal to memory variant gas.

Dummy contract: CalldataVsMemoryDummy.sol

Caller contract: CalldataCaller.sol

Hardhat test: test/v2/platform/poc-calldata-optimization.test.ts

chevron-rightSample POC output (collapsed)hashtag

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?