Platform: Code4rena
Start Date: 06/12/2022
Pot Size: $36,500 USDC
Total HM: 16
Participants: 119
Period: 3 days
Judge: berndartmueller
Total Solo HM: 2
Id: 189
League: ETH
Rank: 119/119
Findings: 1
Award: $0.61
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: RaymondFam
Also found by: 0xdeadbeef0x, 0xhacksmithh, AkshaySrivastav, Awesome, Bnke0x0, CRYP70, HollaDieWaldfee, JC, Parth, Rahoz, Tutturu, __141345__, ahmedov, ajtra, asgeir, aviggiano, bin2chen, btk, carrotsmuggler, cccz, chaduke, cryptonue, dic0de, fatherOfBlocks, fs0c, hansfriese, jonatascm, karanctf, ladboy233, lumoswiz, martin, obront, pashov, pauliax, rvierdiiev, shark, simon135, supernova, tourist, yellowBirdy, zapaz, zaskoh
0.6136 USDC - $0.61
https://github.com/code-423n4/2022-12-escher/blob/main/src/minters/FixedPrice.sol#L109 https://github.com/code-423n4/2022-12-escher/blob/main/src/minters/LPDA.sol#L85 https://github.com/code-423n4/2022-12-escher/blob/main/src/minters/LPDA.sol#L86 https://github.com/code-423n4/2022-12-escher/blob/main/src/minters/LPDA.sol#L105 https://github.com/code-423n4/2022-12-escher/blob/main/src/minters/OpenEdition.sol#L92
transfer()
function forwards only 2300 gas. After EIP-1884 contract fallback functions can consume more than 2300 gas. If the the receiver is a smart contract and its fallback or receive function has a code that uses more than 2300 gas, transfer will always fail.
Reference: ConsenSys Blog
PoC is demonstrated only for LPDA.sol
contract. It is similar for FixedPrice.sol
and OpenEdition.sol
.
// SPDX-License-Identifier: MIT pragma solidity ^0.8.17; import "forge-std/Test.sol"; import {EscherTest} from "./utils/EscherTest.sol"; import {LPDAFactory, LPDA} from "src/minters/LPDAFactory.sol"; contract SaleReceiver { address public teamMember1 = address(9001); // EOA event Received(address indexed sender, uint256 indexed amount); receive() external payable { (bool success, ) = msg.sender.call{value: msg.value}(""); require(success, "Transfer failed"); emit Received(msg.sender, msg.value); } } contract LPDATransfer is EscherTest { LPDAFactory public lpdaSales; LPDA.Sale public lpdaSale; SaleReceiver public saleReceiver; LPDA public sale; function setUp() public virtual override { saleReceiver = new SaleReceiver(); super.setUp(); lpdaSales = new LPDAFactory(); // set up a LPDA Sale lpdaSale = LPDA.Sale({ currentId: uint48(0), finalId: uint48(10), edition: address(edition), startPrice: uint80(uint256(1 ether)), finalPrice: uint80(uint256(0.1 ether)), dropPerSecond: uint80(uint256(0.1 ether) / 1 days), startTime: uint96(block.timestamp), saleReceiver: payable(address(saleReceiver)), endTime: uint96(block.timestamp + 1 days) }); } function test_TransferWhenFinalIdReached() public { sale = LPDA(lpdaSales.createLPDASale(lpdaSale)); // authorize the lpda sale to mint tokens edition.grantRole(edition.MINTER_ROLE(), address(sale)); sale.buy{value: 1 ether}(1); assertEq(address(sale).balance, 1 ether); vm.warp(block.timestamp + 1 days); assertApproxEqRel(sale.getPrice(), 0.9 ether, lpdaSale.dropPerSecond); // buy the rest // this will auto end the sale sale.buy{value: uint256((0.9 ether + lpdaSale.dropPerSecond) * 9)}(9); } }
│ │ ├─ [97] SaleReceiver::receive{value: 8550000000000334400}() │ │ │ └─ ← "EvmError: OutOfGas" │ │ └─ ← "EvmError: Revert" │ └─ ← "EvmError: Revert" └─ ← "EvmError: Revert" Test result: FAILED. 0 passed; 1 failed; finished in 3.60ms Failing tests: Encountered 1 failing test in test/LPDATransfer.t.sol:LPDATransfer
Foundry
Recommended way is to use call
with Reentrancy guard.
#0 - c4-judge
2022-12-10T00:30:25Z
berndartmueller marked the issue as duplicate of #99
#1 - c4-judge
2023-01-03T12:48:06Z
berndartmueller marked the issue as satisfactory