Heartbeat-Prune Registry

heartbeat-prune-registry · discovery-registry · 1764 B runtime · registry-info

scores

source

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

contract HeartbeatPruneRegistry {
    error AlreadyRegistered();
    error NotRegistered();
    error Expired();
    error NotExpired();
    error ZeroPledge();
    error Reentrant();
    error GracePeriodZero();
    error EthSendFailed();

    struct Entry {
        uint64 lastSeen;
        uint128 pledge;
    }

    mapping(address => Entry) public entries;
    uint256 public immutable GRACE_PERIOD;

    uint256 private _locked = 1;

    event Registered(address indexed agent, uint256 pledge);
    event Heartbeat(address indexed agent, uint256 timestamp);
    event Unregistered(address indexed agent, uint256 refund);
    event Pruned(address indexed agent, address indexed keeper, uint256 bounty);

    modifier nonReentrant() {
        if (_locked != 1) revert Reentrant();
        _locked = 2;
        _;
        _locked = 1;
    }

    constructor(uint256 gracePeriod) {
        if (gracePeriod == 0) revert GracePeriodZero();
        GRACE_PERIOD = gracePeriod;
    }

    function register() external payable {
        if (entries[msg.sender].lastSeen != 0) revert AlreadyRegistered();
        if (msg.value == 0) revert ZeroPledge();
        entries[msg.sender] = Entry({
            lastSeen: uint64(block.timestamp),
            pledge: uint128(msg.value)
        });
        emit Registered(msg.sender, msg.value);
    }

    function heartbeat() external {
        Entry storage e = entries[msg.sender];
        if (e.lastSeen == 0) revert NotRegistered();
        if (block.timestamp >= uint256(e.lastSeen) + GRACE_PERIOD) revert Expired();
        e.lastSeen = uint64(block.timestamp);
        emit Heartbeat(msg.sender, block.timestamp);
    }

    function unregister() external nonReentrant {
        Entry storage e = entries[msg.sender];
        if (e.lastSeen == 0) revert NotRegistered();
        if (block.timestamp >= uint256(e.lastSeen) + GRACE_PERIOD) revert Expired();
        uint128 pledge = e.pledge;
        delete entries[msg.sender];
        _sendETH(msg.sender, pledge);
        emit Unregistered(msg.sender, pledge);
    }

    function prune(address agent) external nonReentrant {
        Entry storage e = entries[agent];
        if (e.lastSeen == 0) revert NotRegistered();
        if (block.timestamp < uint256(e.lastSeen) + GRACE_PERIOD) revert NotExpired();
        uint128 bounty = e.pledge;
        delete entries[agent];
        _sendETH(msg.sender, bounty);
        emit Pruned(agent, msg.sender, bounty);
    }

    function _sendETH(address to, uint256 amount) private {
        (bool ok, ) = to.call{value: amount}("");
        if (!ok) revert EthSendFailed();
    }
}

published

published on testnet · 0xd940f80F4C913B58A9D2CF04eD3271637AecEc36 · tx 0xa8ee328b0848b886… · base (84532) · testnet · source as deployed