Skip to content

Latest commit

 

History

History
54 lines (41 loc) · 2.86 KB

029.md

File metadata and controls

54 lines (41 loc) · 2.86 KB

Scruffy Chartreuse Wolverine

Medium

Low streamLengthInTicks value can Lead to Economic Attack

Description

The StreamEscrow contract allows users to specify the streamLengthInTicks during stream creation without enforcing a minimum bound.

This flexibility can be exploited by malicious actors to set streamLengthInTicks to extremely low values (e.g., streamLengthInTicks = 1), effectively converting the streaming mechanism into an immediate ETH transfer. This bypasses the intended time-distribution of funds.

https://github.com/sherlock-audit/2024-11-nounsdao/blob/main/nouns-monorepo/packages/nouns-contracts/contracts/StreamEscrow.sol#L112C5-L130

function createStream(uint256 nounId, uint16 streamLengthInTicks) public payable {
        require(allowedToCreateStream[msg.sender], 'not allowed');
        require(isApprovedOrOwner(msg.sender, nounId), 'only noun owner or approved');
        require(!isStreamActive(nounId), 'stream active');

        // register new stream
        uint128 ethPerTick = toUint128(msg.value / streamLengthInTicks);
        uint32 streamLastTick = currentTick + streamLengthInTicks;
        ethStreamEndingAtTick[streamLastTick] += ethPerTick;

        // the remainder is immediately streamed to the DAO
        uint256 remainder = msg.value % streamLengthInTicks;
        sendETHToTreasury(remainder);

        uint128 newEthStreamedPerTick = ethStreamedPerTick + ethPerTick;
        ethStreamedPerTick = newEthStreamedPerTick;
        streams[nounId] = Stream({ ethPerTick: ethPerTick, canceled: false, lastTick: streamLastTick });
        emit StreamCreated(nounId, msg.value, streamLengthInTicks, ethPerTick, newEthStreamedPerTick, streamLastTick);
    }

Prerequisites:

  • The attacker must be an address permitted to create streams (allowedToCreateStream[msg.sender] == true).
  • The attacker must send ETH when creating the stream.
  • The attacker sets streamLengthInTicks to a very low value during stream creation.
  • The contract computes ethPerTick = msg.value / 1 = msg.value, effectively streaming the entire ETH amount in a single tick.
  • Upon the next forwardAll call, the entire ETH amount is sent to the treasury immediately.
  • The streaming mechanism intended to distribute ETH over time is effectively bypassed, allowing immediate transfer of funds.

Mitigation Recommendations

Implement a minimum allowable value for streamLengthInTicks to prevent setting it to excessively low values.

uint16 public constant MIN_STREAM_LENGTH_IN_TICKS = 10; // Example minimum

function createStream(uint256 nounId, uint16 streamLengthInTicks) public payable {
    require(streamLengthInTicks >= MIN_STREAM_LENGTH_IN_TICKS, "streamLengthInTicks below minimum allowed");
    // Existing logic...
}