Omniscia Sova Network Audit

GatedMintRWA Code Style Findings

GatedMintRWA Code Style Findings

GMR-01C: Inefficient Array Resize Operation

Description:

The referenced code block will copy all identified deposits of the user to a new array so as to resize it.

Example:

src/token/GatedMintRWA.sol
224function getUserPendingDeposits(address user) external view returns (bytes32[] memory) {
225 bytes32[] memory userDeposits = new bytes32[](userDepositIds[user].length);
226 uint256 count = 0;
227
228 for (uint256 i = 0; i < userDepositIds[user].length;) {
229 bytes32 depositId = userDepositIds[user][i];
230
231 // Query the escrow for deposit status
232 (,,,, uint8 state) = getDepositDetails(depositId);
233
234 // Only include if state is PENDING (0)
235 if (state == 0) {
236 // 0 = PENDING in the DepositState enum
237 userDeposits[count] = depositId;
238 count++;
239 }
240
241 unchecked {
242 ++i;
243 }
244 }
245
246 // Resize array to fit only pending deposits
247 bytes32[] memory result = new bytes32[](count);
248 for (uint256 i = 0; i < count;) {
249 result[i] = userDeposits[i];
250
251 unchecked {
252 ++i;
253 }
254 }
255
256 return result;
257}

Recommendation:

We advise a low-level assembly mutation to be performed on the userDeposits array directly, assigning its length to the identified deposit count and optimizing the code's gas cost significantly.

Alleviation (e4d6885477438291b0d3ca477fab7e088522967b):

The referenced loop has been replaced by an in-place memory array resize operation as advised, significantly reducing the code's gas cost.

GMR-02C: Inefficient mapping Lookups

Description:

The linked statements perform key-based lookup operations on mapping declarations from storage multiple times for the same key redundantly.

Example:

src/token/GatedMintRWA.sol
228for (uint256 i = 0; i < userDepositIds[user].length;) {
229 bytes32 depositId = userDepositIds[user][i];

Recommendation:

As the lookups internally perform an expensive keccak256 operation, we advise the lookups to be cached wherever possible to a single local declaration that either holds the value of the mapping in case of primitive types or holds a storage pointer to the struct contained.

As the compiler's optimizations may take care of these caching operations automatically at-times, we advise the optimization to be selectively applied, tested, and then fully adopted to ensure that the proposed caching model indeed leads to a reduction in gas costs.

Alleviation (e4d6885477438291b0d3ca477fab7e088522967b):

The code was updated to load all deposit IDs of a user in memory, optimizing the code's gas cost significantly.

GMR-03C: Redundant Casting of Enum

Description:

The GatedMintRWA::getUserPendingDeposits will utilize a hardcoded 0 value to validate an enum's state which significantly hurts the code's legibility.

Example:

src/token/GatedMintRWA.sol
224function getUserPendingDeposits(address user) external view returns (bytes32[] memory) {
225 bytes32[] memory userDeposits = new bytes32[](userDepositIds[user].length);
226 uint256 count = 0;
227
228 for (uint256 i = 0; i < userDepositIds[user].length;) {
229 bytes32 depositId = userDepositIds[user][i];
230
231 // Query the escrow for deposit status
232 (,,,, uint8 state) = getDepositDetails(depositId);
233
234 // Only include if state is PENDING (0)
235 if (state == 0) {
236 // 0 = PENDING in the DepositState enum
237 userDeposits[count] = depositId;
238 count++;
239 }
240
241 unchecked {
242 ++i;
243 }
244 }
245
246 // Resize array to fit only pending deposits
247 bytes32[] memory result = new bytes32[](count);
248 for (uint256 i = 0; i < count;) {
249 result[i] = userDeposits[i];
250
251 unchecked {
252 ++i;
253 }
254 }
255
256 return result;
257}
258
259/**
260 * @notice Get details for a specific deposit (from Escrow)
261 * @param depositId The unique identifier of the deposit
262 * @return depositor The address that initiated the deposit
263 * @return recipient The address that will receive shares if approved
264 * @return assetAmount The amount of assets deposited
265 * @return expirationTime The timestamp after which deposit can be reclaimed
266 * @return state The current state of the deposit (0=PENDING, 1=ACCEPTED, 2=REFUNDED)
267 */
268function getDepositDetails(bytes32 depositId)
269 public
270 view
271 returns (address depositor, address recipient, uint256 assetAmount, uint256 expirationTime, uint8 state)
272{
273 GatedMintEscrow.PendingDeposit memory deposit = GatedMintEscrow(escrow).getPendingDeposit(depositId);
274 return (deposit.depositor, deposit.recipient, deposit.assetAmount, deposit.expirationTime, uint8(deposit.state));
275}

Recommendation:

We advise the code to either cast the desired enum state to the uint8 data type in the comparison within the GatedMintRWA::getUserPendingDeposits function, or to remove the casting operation from the GatedMintRWA::getDepositDetails function.

Alleviation (e4d6885477438291b0d3ca477fab7e088522967b):

The code was revised to yield and utilize an enum value directly, optimizing the code's legibility.