PAYDAY

Your competitor has just set up a node operator fee claiming contract for their users. It would be a shame if it stopped working properly...

MERKLE TREE

The merkle tree contains 20 recipients, each of which can withdraw a given ETH balance.

The leafes store the keccak256 hash of values with the following layout:

  • first 32 bytes: recipient address
  • second 32 bytes: amount (uint72) followed by validUntil timestamp in milliseconds (uint184)

More information about the tree can be found in the src/merkleData directory.

WINNING CONDITION

The contract has less than 1 ETH in funds, without any of the recipients claiming their balance.

src
Distributor.sol
MerkleProof.sol
interfaces
merkleData
script
pragma solidity 0.8.20;

import {MerkleProof} from "./MerkleProof.sol";
import {IDistributor} from "./interfaces/IDistributor.sol";

contract Distributor is IDistributor {
    bytes32 public root;
    mapping(address => bool) public hasClaimed;

    constructor(bytes32 _root) payable {
        root = _root;
    }

    function withdraw(
        bytes calldata params,
        bytes32[] calldata proof
    ) external {
        require(params.length == 64, "invalid params");

        bytes32 leaf = keccak256(params);
        require(MerkleProof.verifyProof(leaf, root, proof), "invalid proof");

        (address recipient, uint72 amount, uint184 validUntil) = decodeParams(
            params
        );

        require(!hasClaimed[recipient], "already claimed");
        require(validUntil >= block.timestamp, "expired");

        hasClaimed[recipient] = true;
        (bool success, ) = recipient.call{value: amount}("");
        require(success, "failed to send ether");
    }

    function decodeParams(
        bytes memory params
    ) private pure returns (address, uint72, uint184) {
        bytes32 first;
        bytes32 second;

        assembly {
            first := mload(add(params, 0x20))
            second := mload(add(params, 0x40))
        }

        address recipient = address(uint160(uint256(first)));
        uint72 amount = uint72(uint256(second) >> 184);
        uint184 validUntil = uint184(uint256(second) >> 72);

        return (recipient, amount, validUntil);
    }
}