Platform: Code4rena
Start Date: 24/10/2023
Pot Size: $36,500 USDC
Total HM: 4
Participants: 147
Period: 6 days
Judge: 0xDjango
Id: 299
League: ETH
Rank: 93/147
Findings: 1
Award: $6.46
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: 0xVolcano
Also found by: 0x11singh99, 0xAadi, 0xAnah, 0xgrbr, 0xhacksmithh, 0xhex, 0xpiken, 0xta, J4X, JCK, K42, Raihan, Rolezn, SAQ, SM3_SS, Sathish9098, SovaSlava, ThreeSigma, Udsen, arjun16, aslanbek, brakelessak, castle_chain, evmboi32, hunter_w3b, lsaudit, naman1778, niser93, nuthan2x, oakcobalt, pavankv, petrichor, phenom80, radev_sw, shamsulhaq123, tabriz, thekmj, unique, yashgoel72, ybansal2403
6.4563 USDC - $6.46
https://github.com/code-423n4/2023-10-ethena/blob/ee67d9b542642c9757a6b826c82d0cae60256509/contracts/EthenaMinting.sol#L391C2-L396C4 File: contracts/EthenaMinting.sol
function _deduplicateOrder(address sender, uint256 nonce) private returns (bool) { 392: (bool valid, uint256 invalidatorSlot, uint256 invalidator, uint256 invalidatorBit) = verifyNonce(sender, nonce); 393: mapping(uint256 => uint256) storage invalidatorStorage = _orderBitmaps[sender]; invalidatorStorage[invalidatorSlot] = invalidator | invalidatorBit; return valid; }
We look into the implementation of the function verifyNonce() which is being called in the line 392. The function has the following implementation:
function verifyNonce(address sender, uint256 nonce) public view override returns (bool, uint256, uint256, uint256) { if (nonce == 0) revert InvalidNonce(); uint256 invalidatorSlot = uint64(nonce) >> 8; uint256 invalidatorBit = 1 << uint8(nonce); mapping(uint256 => uint256) storage invalidatorStorage = _orderBitmaps[sender]; uint256 invalidator = invalidatorStorage[invalidatorSlot]; if (invalidator & invalidatorBit != 0) revert InvalidNonce(); return (true, invalidatorSlot, invalidator, invalidatorBit); }
Put the above implementation in the context of our main function and we see a similar line in both functions.
393: mapping(uint256 => uint256) storage invalidatorStorage = _orderBitmaps[sender];
This means we are making this calls twice, one in the function being called, and the other one by the calling function.
To avoid this, we can inline the function verifyNonce() and refactor the code as follows:
function _deduplicateOrder(address sender, uint256 nonce) private returns (bool) { if (nonce == 0) revert InvalidNonce(); uint256 invalidatorSlot = uint64(nonce) >> 8; uint256 invalidatorBit = 1 << uint8(nonce); mapping(uint256 => uint256) storage invalidatorStorage = _orderBitmaps[sender]; uint256 invalidator = invalidatorStorage[invalidatorSlot]; if (invalidator & invalidatorBit != 0) revert InvalidNonce(); invalidatorStorage[invalidatorSlot] = invalidator | invalidatorBit; return true; }
https://github.com/code-423n4/2023-10-ethena/blob/main/contracts/EthenaMinting.sol#L376C1-L386C1
function verifyNonce(address sender, uint256 nonce) public view override returns (bool, uint256, uint256, uint256) { if (nonce == 0) revert InvalidNonce(); uint256 invalidatorSlot = uint64(nonce) >> 8; uint256 invalidatorBit = 1 << uint8(nonce); 381: mapping(uint256 => uint256) storage invalidatorStorage = _orderBitmaps[sender]; 382: uint256 invalidator = invalidatorStorage[invalidatorSlot]; if (invalidator & invalidatorBit != 0) revert InvalidNonce(); return (true, invalidatorSlot, invalidator, invalidatorBit); }
Of interest to us is the statements on line 381 and 382:
381: mapping(uint256 => uint256) storage invalidatorStorage = _orderBitmaps[sender]; 382: uint256 invalidator = invalidatorStorage[invalidatorSlot];
Instead Of creating a separate storage variable "invalidatorStorage", directly get the "invalidator" access from the state variable read. Use the below Code snippet
- mapping(uint256 => uint256) storage invalidatorStorage = _orderBitmaps[sender]; - uint256 invalidator = invalidatorStorage[invalidatorSlot]; + uint256 invalidator = _orderBitmaps[sender][invalidatorSlot];
function verifyOrder(Order calldata order, Signature calldata signature) public view override returns (bool, bytes32) { bytes32 taker_order_hash = hashOrder(order); address signer = ECDSA.recover(taker_order_hash, signature.signature_bytes); if (!(signer == order.benefactor || delegatedSigner[signer][order.benefactor])) revert InvalidSignature(); 343: if (order.beneficiary == address(0)) revert InvalidAmount(); 344: if (order.collateral_amount == 0) revert InvalidAmount(); 345: if (order.usde_amount == 0) revert InvalidAmount(); 346: if (block.timestamp > order.expiry) revert SignatureExpired(); return (true, taker_order_hash); }
Apply the checks in the line 343-346 in the beginning of the function to save gas
#0 - c4-pre-sort
2023-11-01T15:25:22Z
raymondfam marked the issue as sufficient quality report
#1 - c4-judge
2023-11-10T20:18:42Z
fatherGoose1 marked the issue as grade-b