Module 1, Burn Hour, is live. Module 4 drafts at 12:00 UTC. Read it

Module 3: Top Buyer

The whole 40% mechanic share goes to the TOP_BUYER pot, credited after 00:00 UTC to the largest single $HIBOU buy of the UTC day.

Status
Approved
Sinks
Top buyer
Code · TopBuyer.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {IHibouModule, SwapContext} from "../IHibouModule.sol";

/// @title Top Buyer (module 3)
/// @notice The whole mechanic share goes to the TOP_BUYER pot, paid after 00:00 UTC to the
///         largest $HIBOU buy of the day.
contract TopBuyer is IHibouModule {
    function split(SwapContext calldata) external pure returns (uint16[5] memory w) {
        w[3] = 10_000;
    }
}
Tests
    function test_badModulesFallBack() public {
        BadSum bad = new BadSum();
        GasHog hog = new GasHog();
        vm.startPrank(owner);
        registry.registerGenesis(address(bad), authorA, 0, "bad", true);
        vm.stopPrank();
        (uint16[5] memory w, bool fb) = hook.weightsFor(_ctx());
        assertTrue(fb);
        assertEq(w[1], 10_000);
        vm.prank(owner);
        registry.registerGenesis(address(hog), authorA, 0, "hog", true);
        (w, fb) = hook.weightsFor{gas: 1_000_000}(_ctx());
        assertTrue(fb);
        assertEq(w[1], 10_000);
        // swaps still work with a bad module live
        _buy(1 ether, alice);
        assertEq(hook.feesRouted(), 0.01 ether);
    }

    function test_topBuyerPot() public {
        vm.prank(owner);
        registry.registerGenesis(address(topBuyer), authorA, 0, "Top Buyer", true);
        _buy(1 ether, alice);
        _buy(3 ether, bob);
        _buy(2 ether, alice);
        uint256 day = block.timestamp / 1 days;
        (address leader,, uint128 pot,) = hook.topBuys(day);
        assertEq(leader, bob);
        assertEq(pot, 0.024 ether, "40% of 6 ETH * 1%");
        vm.expectRevert(HibouDispatcher.DayNotOver.selector);
        hook.settleTopBuyer(day);
        vm.warp(block.timestamp + 1 days);
        hook.settleTopBuyer(day);
        assertEq(hook.credits(bob), 0.024 ether);
        vm.prank(bob);
        hook.claim();
        assertEq(bob.balance, 0.024 ether);
    }

    function test_pauseFallsBack() public {
        vm.prank(owner);
        registry.registerGenesis(address(topBuyer), authorA, 0, "Top Buyer", true);
        vm.expectRevert(HibouDispatcher.NotGuardian.selector);
        hook.setPaused(true);
        vm.prank(guardian);
        hook.setPaused(true);
        _buy(1 ether, alice);
        assertEq(hook.credits(authorA), 0, "paused: no author share");
        assertEq(hook.stakersPending(), 0.006 ether);
    }

    function test_ownerWithdrawKeepsCredits() public {
        _genesis();
        _buy(1 ether, alice);
        // anyone can top up the contract; funding is income through the same split
        (bool ok,) = address(hook).call{value: 1 ether}("");
        assertTrue(ok);
        uint256 owed = hook.totalOwed();
        assertEq(hook.available(), 0);
        vm.prank(owner);
        hook.withdrawAll();
        assertEq(hook.shortfall(), owed);
        assertEq(hook.credits(authorA), 0.002 ether + 0.2 ether, "credit survives the withdrawal");
        vm.prank(authorA);
        vm.expectRevert();
        hook.claim();
        // refill fills the shortfall first
        vm.deal(address(this), 10 ether);
        (ok,) = address(hook).call{value: owed}("");
        assertTrue(ok);
        assertEq(hook.shortfall(), 0);
        vm.prank(authorA);
        hook.claim();
    }

    function test_screen() public {
        EvilStore s = new EvilStore();
        EvilCall c = new EvilCall();
        vm.startPrank(agent);
        vm.expectRevert();
        registry.propose(address(s), address(s).codehash, 0, authorA, 1, "evil");
        vm.expectRevert();
        registry.propose(address(c), address(c).codehash, 0, authorA, 1, "evil");
        vm.stopPrank();
    }

    function test_startersPassScreen() public view {
        this.screen(address(burnHour));
        this.screen(address(stockWeather));
        this.screen(address(topBuyer));
    }

    function test_codeHashMustMatch() public {
        vm.prank(agent);
        vm.expectRevert();
        registry.propose(address(topBuyer), keccak256("other"), 0, authorA, 1, "Top Buyer");
    }

    function test_onlyAgentProposes() public {
        address t = address(topBuyer);
        bytes32 h = t.codehash;
        vm.expectRevert(ModuleRegistry.NotAgent.selector);
        registry.propose(t, h, 0, authorA, 1, "Top Buyer");
    }

    function test_fullVoteFlow() public {
        _genesis();
        _stake(alice, 100_000 ether); // above 0.5% of the ~10.15M supply
        _stake(bob, 10_000 ether);
        address t = address(topBuyer);
        vm.prank(agent);
        uint256 id = registry.propose(t, t.codehash, keccak256("fork"), authorB, 7, "Top Buyer v2");

        vm.prank(alice);
        registry.vote(id, true);
        vm.prank(bob);
        registry.vote(id, false);
        vm.prank(alice);
        vm.expectRevert(ModuleRegistry.AlreadyVoted.selector);
        registry.vote(id, true);

        // stake locked until the vote closes
        vm.prank(alice);
        vm.expectRevert();
        staking.unstake(1 ether);

        uint256 t0 = block.timestamp;
        vm.expectRevert(ModuleRegistry.VoteOpen.selector);
        registry.finalize(id);
        vm.warp(t0 + 24 hours);
        registry.finalize(id);
        ModuleRegistry.Proposal memory p = registry.getProposal(id);
        assertEq(uint8(p.state), uint8(ModuleRegistry.State.Queued));

        vm.expectRevert();
        registry.activate(id);
        vm.warp(t0 + 48 hours);
        registry.activate(id);
        assertEq(hook.module(), t);
        assertEq(hook.author(), authorB);
        assertEq(uint8(registry.getProposal(0).state), uint8(ModuleRegistry.State.Replaced));
        assertEq(registry.shipped(), 4);

        vm.prank(alice);
        staking.unstake(1 ether);
    }

    function test_quorumNotMet() public {
        _stake(bob, 10_000 ether); // under 0.5%
        address t = address(topBuyer);
        vm.prank(agent);
        uint256 id = registry.propose(t, t.codehash, 0, authorB, 1, "x");
        vm.prank(bob);
        registry.vote(id, true);
        vm.warp(block.timestamp + 24 hours);
        registry.finalize(id);
        assertEq(uint8(registry.getProposal(id).state), uint8(ModuleRegistry.State.Defeated));
    }

    function test_noVoteAfterClose() public {
        _stake(alice, 1 ether);
        address t = address(topBuyer);
        vm.prank(agent);
        uint256 id = registry.propose(t, t.codehash, 0, authorB, 1, "x");
        vm.warp(block.timestamp + 24 hours);
        vm.prank(alice);
        vm.expectRevert(ModuleRegistry.NotVoting.selector);
        registry.vote(id, true);
    }

    function test_submitIdeaBurns() public {
        vm.startPrank(alice);
        hibou.approve(address(registry), 1_000 ether);
        uint256 id = registry.submitIdea("Burn twice on Fridays");
        vm.stopPrank();
        assertEq(id, 1);
        assertEq(hibou.balanceOf(registry.DEAD()), 1_000 ether);
        vm.prank(agent);
        registry.recordIdea(bob, "Rebate the first buy of each hour");
        assertEq(registry.ideaCount(), 2);
    }

    function test_genesisClosed() public {
        _genesis();
        vm.prank(owner);
        vm.expectRevert(ModuleRegistry.GenesisIsClosed.selector);
        registry.registerGenesis(address(topBuyer), authorA, 0, "x", true);
    }

    function test_onlyRegistrySetsModule() public {
        vm.expectRevert(HibouDispatcher.NotRegistry.selector);
        hook.setModule(address(topBuyer), alice);
    }

    function test_routerBuysAndSells() public {
        vm.prank(owner);
        registry.registerGenesis(address(topBuyer), authorA, 0, "Top Buyer", true);
        HibouRouter router = new HibouRouter(pm, key);
        vm.deal(bob, 2 ether);
        vm.prank(bob);
        uint256 out = router.swapEthForHibou{value: 1 ether}(1, bob);
        assertEq(hibou.balanceOf(bob), 50_000 ether + out);
        (address leader,,,) = hook.topBuys(block.timestamp / 1 days);
        assertEq(leader, bob, "hookData carries the real buyer");
        vm.startPrank(bob);
        hibou.approve(address(router), out);
        uint256 back = router.swapHibouForEth(out, 1, bob);
        vm.stopPrank();
        assertGt(back, 0.9 ether);
        assertGt(address(hook).balance, 0.019 ether);
    }
Fork report
Tests
3 passed, 0 failed
Cases
test_fullVoteFlow, test_startersPassScreen, test_topBuyerPot
Fork
local · local tests only: this module reads no external contract
Source hash
0xf1a92428800e12335569de26d0449a8996022541178eb473cf2f86794a1b0e49
Report hash
0x6cd995de770b3f9127b64303652c80b7050a6da749cd05115b9f8e0087e2af9f
Generated
2026-10-27 05:02 UTC

Vote

Pre-approved at genesis with the other starter modules. Every module after these goes through a 24 h vote and a 24 h timelock.

Onchain

Contract
at deployment
Author
Genesis
Code hash
set by the registry at deployment
Reads
swap direction and size
Source
0xf1a92428800e12335569de26d0449a8996022541178eb473cf2f86794a1b0e49
How a module works
Submit an ideaFree on the site. The agent picks one a day.