AttestersV4

Description:

Proxy contract enabling upgradeable smart contract patterns. Delegates calls to an implementation contract.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "attesters.sol": {
      "content": "// testnet 0xdB4Efb8fa3685C62D6dF7CE3c65fc3EcD373E665\r
\r
// SPDX-License-Identifier: MIT\r
pragma solidity ^0.8.24;\r
\r
abstract contract UUPSUpgradeable {\r
    bytes32 private constant IMPLEMENTATION_SLOT =\r
        0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;\r
\r
    function upgradeTo(address newImplementation) external virtual {\r
        _authorizeUpgrade(newImplementation);\r
        _setImplementation(newImplementation);\r
    }\r
\r
    function _setImplementation(address newImpl) internal {\r
        require(newImpl != address(0), "bad impl");\r
        bytes32 slot = IMPLEMENTATION_SLOT;\r
        assembly {\r
            sstore(slot, newImpl)\r
        }\r
    }\r
\r
    function _authorizeUpgrade(address newImplementation) internal virtual;\r
}\r
\r
interface IERC20 {\r
    function decimals() external view returns (uint8);\r
    function balanceOf(address) external view returns (uint256);\r
    function allowance(address owner, address spender) external view returns (uint256);\r
    function transfer(address to, uint256 value) external returns (bool);\r
    function transferFrom(address from, address to, uint256 value) external returns (bool);\r
}\r
\r
contract AttestersV4 is UUPSUpgradeable {\r
    struct Attester {\r
        address wallet;\r
        string publicKey;\r
        string name;\r
        string meta;\r
        uint64 updatedAt;\r
        bool exists;\r
        bool isSlashed;\r
        uint256 totalFeesPaid;\r
        uint256 lastFeePaid;\r
        uint64  lastPaidAt;\r
        uint256 totalOverpaid;\r
        uint256 lastOverpaid;\r
    }\r
\r
    uint256 private constant NAME_MAX_LEN = 128;\r
    uint256 private constant META_MAX_LEN = 1024;\r
\r
    address private _withdrawer;\r
    address private _pendingAdmin;\r
    IERC20 public feeTokenContract;\r
    uint256 private _registerFee;\r
    bool private _initialized;\r
\r
    uint256 private _feePool;\r
    uint256 private _overpayAndDonationPool;\r
\r
    mapping(address => Attester) private attesters;\r
    address[] private _wallets;\r
\r
    event AttesterUpserted(address indexed wallet, string publicKey, string name, string meta, uint64 updatedAt);\r
    event AttesterDeleted(address indexed wallet, bool stillExists);\r
    event AttesterSlashed(address indexed wallet, bool isSlashed);\r
    event FeeCollected(address indexed payer, uint256 feeAmount, uint256 overpaid);\r
    event DonationReceived(address indexed from, uint256 amount);\r
    event Withdrawn(address indexed to, uint256 amount, uint256 fromFeePool, uint256 fromOverpayPool);\r
    event OverpayWithdrawn(address indexed to, uint256 amount);\r
    event RegisterFeeUpdated(uint256 oldFee, uint256 newFee);\r
    event AdminTransferStarted(address indexed currentAdmin, address indexed pendingAdmin);\r
    event AdminTransferred(address indexed oldAdmin, address indexed newAdmin);\r
\r
    uint256 private _locked; // 0 = unlocked, 1 = locked\r
    modifier nonReentrant() {\r
        require(_locked == 0, "reentrancy");\r
        _locked = 1;\r
        _;\r
        _locked = 0;\r
    }\r
    modifier whenInitialized() {\r
        require(_initialized, "not initialized");\r
        _;\r
    }\r
    modifier onlyAdmin() {\r
        require(_initialized, "not initialized");\r
        require(msg.sender == _withdrawer, "not admin");\r
        _;\r
    }\r
\r
    mapping(address => uint64) private _resignRequestedAt;\r
    mapping(address => uint64) private _resignRequestedAtBlock;\r
    mapping(address => uint256) private _refundableDeposit;\r
    uint256 private _depositLiability;\r
\r
    uint64 private _resignWaitSeconds;\r
    uint64 private _resignWaitBlocks;\r
\r
    constructor() {\r
        _initialized = true;\r
    }\r
\r
    function initialize(address admin, address token, uint256 fee) external {\r
        _initialize(admin, token, fee);\r
    }\r
\r
    function initializeDefault(address admin, address token) external {\r
        _initialize(admin, token, 100 * 1e18);\r
    }\r
\r
    function _initialize(address admin, address token, uint256 fee) internal {\r
        require(!_initialized, "already initialized");\r
        require(admin != address(0), "admin=0");\r
        require(token != address(0), "token=0");\r
        _withdrawer = admin;\r
        feeTokenContract = IERC20(token);\r
        _registerFee = fee;\r
        _initialized = true;\r
        _resignWaitSeconds = 30 days;\r
        _resignWaitBlocks = 0;\r
    }\r
\r
    function _authorizeUpgrade(address) internal override onlyAdmin {}\r
\r
    function feeToken() public view returns (address) { return address(feeTokenContract); }\r
    function registerFee() public view returns (uint256) { return _registerFee; }\r
    function withdrawer() public view returns (address) { return _withdrawer; }\r
    function pendingAdmin() public view returns (address) { return _pendingAdmin; }\r
    function contractTokenBalance() external view returns (uint256) { return feeTokenContract.balanceOf(address(this)); }\r
    function pools() external view returns (uint256 feePool, uint256 overpayAndDonationPool) { return (_feePool, _overpayAndDonationPool); }\r
    function depositLiability() external view returns (uint256) { return _depositLiability; }\r
\r
    function feeInfo() external view returns (uint256 registerFee_, uint256 effectiveMinimum_) {\r
        return (_registerFee, _registerFee);\r
    }\r
\r
    function resignationInfo(address wallet) external view returns (\r
        bool requested,\r
        uint64 requestedAt,\r
        uint64 readyAt,\r
        uint256 refundable\r
    ) {\r
        uint64 reqAt = _resignRequestedAt[wallet];\r
        uint64 ready = 0;\r
        if (reqAt > 0) {\r
            if (_resignWaitBlocks > 0) {\r
                ready = reqAt + _resignWaitSeconds;\r
            } else {\r
                ready = reqAt + _resignWaitSeconds;\r
            }\r
        }\r
        return (reqAt > 0, reqAt, ready, _refundableDeposit[wallet]);\r
    }\r
\r
    function transferAdmin(address newAdmin) external onlyAdmin {\r
        require(newAdmin != address(0), "admin=0");\r
        _pendingAdmin = newAdmin;\r
        emit AdminTransferStarted(_withdrawer, newAdmin);\r
    }\r
\r
    function acceptAdmin() external whenInitialized {\r
        require(msg.sender == _pendingAdmin, "not pending");\r
        address old = _withdrawer;\r
        _withdrawer = _pendingAdmin;\r
        _pendingAdmin = address(0);\r
        emit AdminTransferred(old, _withdrawer);\r
    }\r
\r
    function setRegisterFee(uint256 newFee) external onlyAdmin {\r
        uint256 old = _registerFee;\r
        _registerFee = newFee;\r
        emit RegisterFeeUpdated(old, newFee);\r
    }\r
\r
    function setResignationWait(uint64 waitSeconds, uint64 waitBlocks) external onlyAdmin {\r
        _resignWaitSeconds = waitSeconds;\r
        _resignWaitBlocks = waitBlocks;\r
    }\r
\r
    function slash(address wallet, bool slashed) external onlyAdmin {\r
        Attester storage a = attesters[wallet];\r
        require(a.exists || _resignRequestedAt[wallet] != 0 || a.wallet == wallet, "not found");\r
        a.isSlashed = slashed;\r
        a.updatedAt = uint64(block.timestamp);\r
        emit AttesterSlashed(wallet, slashed);\r
    }\r
\r
    function upsertAttester(\r
        string calldata publicKey,\r
        string calldata name,\r
        string calldata meta,\r
        uint256 amount\r
    ) external nonReentrant whenInitialized {\r
        uint256 fee = _registerFee;\r
        require(amount >= fee, "fee too low");\r
        require(bytes(name).length <= NAME_MAX_LEN, "name too long");\r
        require(bytes(meta).length <= META_MAX_LEN, "meta too long");\r
        require(feeTokenContract.transferFrom(msg.sender, address(this), amount), "transferFrom failed");\r
        uint256 overpaid = amount - fee;\r
        Attester storage a = attesters[msg.sender];\r
        bool firstTime = !a.exists && _resignRequestedAt[msg.sender] == 0 && a.wallet == address(0);\r
        a.wallet = msg.sender;\r
        a.publicKey = publicKey;\r
        a.name = name;\r
        a.meta = meta;\r
        a.updatedAt = uint64(block.timestamp);\r
        a.exists = true;\r
        if (firstTime) { _wallets.push(msg.sender); }\r
        a.totalFeesPaid += fee;\r
        a.lastFeePaid = fee;\r
        a.lastPaidAt = uint64(block.timestamp);\r
        a.totalOverpaid += overpaid;\r
        a.lastOverpaid = overpaid;\r
        _feePool += fee;\r
        _overpayAndDonationPool += overpaid;\r
        uint256 oldDep = _refundableDeposit[msg.sender];\r
        if (oldDep != fee) {\r
            if (oldDep > 0) { _depositLiability -= oldDep; }\r
            _refundableDeposit[msg.sender] = fee;\r
            _depositLiability += fee;\r
        }\r
        if (_resignRequestedAt[msg.sender] != 0) {\r
            _resignRequestedAt[msg.sender] = 0;\r
        }\r
        emit FeeCollected(msg.sender, fee, overpaid);\r
        emit AttesterUpserted(msg.sender, publicKey, name, meta, a.updatedAt);\r
    }\r
\r
    function requestResign() external whenInitialized {\r
        Attester storage a = attesters[msg.sender];\r
        require(a.exists, "not active");\r
        require(!_isSlashed(msg.sender), "slashed");\r
        a.exists = false;\r
        a.updatedAt = uint64(block.timestamp);\r
        _resignRequestedAt[msg.sender] = uint64(block.timestamp);\r
        _resignRequestedAtBlock[msg.sender] = uint64(block.number);\r
        emit AttesterDeleted(msg.sender, a.exists);\r
    }\r
\r
    function withdrawDeposit() external nonReentrant whenInitialized {\r
        uint256 amount = _refundableDeposit[msg.sender];\r
        require(amount > 0, "no deposit");\r
        require(!_isSlashed(msg.sender), "slashed");\r
        uint64 reqAt = _resignRequestedAt[msg.sender];\r
        require(reqAt > 0, "no resign req");\r
        if (_resignWaitBlocks > 0) {\r
            uint64 reqBlock = _resignRequestedAtBlock[msg.sender];\r
            require(reqBlock > 0, "no resign block");\r
            require(block.number >= uint256(reqBlock) + uint256(_resignWaitBlocks), "wait blocks");\r
        } else {\r
            require(block.timestamp >= uint256(reqAt) + uint256(_resignWaitSeconds), "wait time");\r
        }\r
        _refundableDeposit[msg.sender] = 0;\r
        _resignRequestedAt[msg.sender] = 0;\r
        _resignRequestedAtBlock[msg.sender] = 0;\r
        _depositLiability -= amount;\r
        require(feeTokenContract.transfer(msg.sender, amount), "transfer failed");\r
    }\r
\r
    function deleteSelf() external whenInitialized {\r
        Attester storage a = attesters[msg.sender];\r
        require(a.exists, "not found");\r
        a.exists = false;\r
        a.updatedAt = uint64(block.timestamp);\r
        emit AttesterDeleted(msg.sender, a.exists);\r
    }\r
\r
    function getAttester(address wallet)\r
        external\r
        view\r
        whenInitialized\r
        returns (\r
            address,\r
            string memory,\r
            string memory,\r
            string memory,\r
            uint64,\r
            bool,\r
            bool,\r
            uint256,\r
            uint256,\r
            uint64,\r
            uint256,\r
            uint256\r
        )\r
    {\r
        Attester storage a = attesters[wallet];\r
        return (\r
            a.wallet,\r
            a.publicKey,\r
            a.name,\r
            a.meta,\r
            a.updatedAt,\r
            a.exists,\r
            a.isSlashed,\r
            a.totalFeesPaid,\r
            a.lastFeePaid,\r
            a.lastPaidAt,\r
            a.totalOverpaid,\r
            a.lastOverpaid\r
        );\r
    }\r
\r
    function getAttesters(uint256 offset, uint256 max)\r
        external\r
        view\r
        whenInitialized\r
        returns (\r
            address[] memory wallets,\r
            string[] memory publicKeys,\r
            string[] memory names,\r
            string[] memory metas,\r
            uint64[] memory updatedAts,\r
            bool[] memory existences,\r
            bool[] memory slashed\r
        )\r
    {\r
        uint256 n = _wallets.length;\r
        if (offset > n) offset = n;\r
        uint256 end = offset + max;\r
        if (end > n) end = n;\r
        uint256 count = end > offset ? (end - offset) : 0;\r
\r
        wallets = new address[](count);\r
        publicKeys = new string[](count);\r
        names = new string[](count);\r
        metas = new string[](count);\r
        updatedAts = new uint64[](count);\r
        existences = new bool[](count);\r
        slashed = new bool[](count);\r
\r
        for (uint256 i = 0; i < count; i++) {\r
            address w = _wallets[offset + i];\r
            Attester storage a = attesters[w];\r
            wallets[i] = a.wallet;\r
            publicKeys[i] = a.publicKey;\r
            names[i] = a.name;\r
            metas[i] = a.meta;\r
            updatedAts[i] = a.updatedAt;\r
            existences[i] = a.exists;\r
            slashed[i] = a.isSlashed;\r
        }\r
    }\r
\r
    function pickAttesters(uint256 num) public view whenInitialized returns (address[] memory picked) {\r
        uint256 total = _wallets.length;\r
        uint256 lActiveCount = 0;\r
        for (uint256 i = 0; i < total; i++) {\r
            if (attesters[_wallets[i]].exists) lActiveCount++;\r
        }\r
        if (lActiveCount == 0) return new address[](0);\r
        if (num > lActiveCount) num = lActiveCount;\r
\r
        address[] memory active = new address[](lActiveCount);\r
        uint256 idx = 0;\r
        for (uint256 i = 0; i < total; i++) {\r
            address w = _wallets[i];\r
            if (attesters[w].exists) active[idx++] = w;\r
        }\r
\r
        picked = new address[](num);\r
        uint256[] memory indices = new uint256[](lActiveCount);\r
        for (uint256 i = 0; i < lActiveCount; i++) indices[i] = i;\r
        uint256 rand = uint256(keccak256(abi.encodePacked(blockhash(block.number - 1), msg.sender, lActiveCount)));\r
        for (uint256 i = 0; i < num; i++) {\r
            rand = uint256(keccak256(abi.encodePacked(rand, i)));\r
            uint256 j = i + (rand % (lActiveCount - i));\r
            (indices[i], indices[j]) = (indices[j], indices[i]);\r
            picked[i] = active[indices[i]];\r
        }\r
    }\r
\r
    function pickAttesters() external view whenInitialized returns (address[] memory) {\r
        return pickAttesters(3);\r
    }\r
\r
    function attesterCount() external view whenInitialized returns (uint256) { return _wallets.length; }\r
\r
    function activeCount() public view whenInitialized returns (uint256) {\r
        uint256 total = _wallets.length;\r
        uint256 active = 0;\r
        for (uint256 i = 0; i < total; i++) if (attesters[_wallets[i]].exists) active++;\r
        return active;\r
    }\r
\r
    function getActiveAttesters(uint256 offset, uint256 max)\r
        external\r
        view\r
        whenInitialized\r
        returns (\r
            address[] memory wallets,\r
            string[] memory publicKeys,\r
            string[] memory names,\r
            string[] memory metas,\r
            uint64[] memory updatedAts,\r
            bool[] memory slashed\r
        )\r
    {\r
        uint256 total = _wallets.length;\r
        if (max == 0) {\r
            return (new address[](0), new string[](0), new string[](0), new string[](0), new uint64[](0), new bool[](0));\r
        }\r
\r
        wallets = new address[](max);\r
        publicKeys = new string[](max);\r
        names = new string[](max);\r
        metas = new string[](max);\r
        updatedAts = new uint64[](max);\r
        slashed = new bool[](max);\r
\r
        uint256 collected = 0;\r
        uint256 seen = 0;\r
        for (uint256 i = 0; i < total; i++) {\r
            address w = _wallets[i];\r
            Attester storage a = attesters[w];\r
            if (!a.exists) continue;\r
            if (seen < offset) { unchecked { ++seen; } continue; }\r
            if (collected == max) break;\r
            wallets[collected] = a.wallet;\r
            publicKeys[collected] = a.publicKey;\r
            names[collected] = a.name;\r
            metas[collected] = a.meta;\r
            updatedAts[collected] = a.updatedAt;\r
            slashed[collected] = a.isSlashed;\r
            unchecked { ++collected; }\r
        }\r
\r
        assembly {\r
            mstore(wallets, collected)\r
            mstore(publicKeys, collected)\r
            mstore(names, collected)\r
            mstore(metas, collected)\r
            mstore(updatedAts, collected)\r
            mstore(slashed, collected)\r
        }\r
    }\r
\r
    function withdraw(uint256 amount) external onlyAdmin nonReentrant {\r
        uint256 bal = feeTokenContract.balanceOf(address(this));\r
        require(bal >= amount, "insufficient balance");\r
        uint256 available = bal > _depositLiability ? (bal - _depositLiability) : 0;\r
        require(available >= amount, "exceeds available");\r
        uint256 fromOverpay = amount <= _overpayAndDonationPool ? amount : _overpayAndDonationPool;\r
        uint256 fromFee = amount - fromOverpay;\r
        if (fromOverpay > 0) _overpayAndDonationPool -= fromOverpay;\r
        if (fromFee > 0) {\r
            require(_feePool >= fromFee, "fee pool short");\r
            _feePool -= fromFee;\r
        }\r
        require(feeTokenContract.transfer(_withdrawer, amount), "transfer failed");\r
        emit Withdrawn(_withdrawer, amount, fromFee, fromOverpay);\r
    }\r
\r
    function withdrawOverpay(uint256 amount) external onlyAdmin nonReentrant {\r
        uint256 bal = feeTokenContract.balanceOf(address(this));\r
        uint256 available = bal > _depositLiability ? (bal - _depositLiability) : 0;\r
        require(amount <= _overpayAndDonationPool, "overpay pool short");\r
        require(amount <= available, "exceeds available");\r
        _overpayAndDonationPool -= amount;\r
        require(feeTokenContract.transfer(_withdrawer, amount), "transfer failed");\r
        emit OverpayWithdrawn(_withdrawer, amount);\r
    }\r
\r
    function withdrawAll() external onlyAdmin nonReentrant {\r
        uint256 bal = feeTokenContract.balanceOf(address(this));\r
        require(bal > 0, "no balance");\r
        uint256 available = bal > _depositLiability ? (bal - _depositLiability) : 0;\r
        require(available > 0, "no available");\r
        uint256 fromOverpay = _overpayAndDonationPool <= available ? _overpayAndDonationPool : available;\r
        uint256 remaining = available - fromOverpay;\r
        uint256 fromFee = remaining;\r
        if (fromFee > _feePool) { fromFee = _feePool; fromOverpay = available - fromFee; }\r
        _overpayAndDonationPool -= fromOverpay;\r
        _feePool -= fromFee;\r
        require(feeTokenContract.transfer(_withdrawer, fromOverpay + fromFee), "transfer failed");\r
        emit Withdrawn(_withdrawer, fromOverpay + fromFee, fromFee, fromOverpay);\r
    }\r
\r
    function donate(uint256 amount) external nonReentrant {\r
        require(amount > 0, "amount=0");\r
        require(feeTokenContract.transferFrom(msg.sender, address(this), amount), "transferFrom failed");\r
        _overpayAndDonationPool += amount;\r
        emit DonationReceived(msg.sender, amount);\r
    }\r
\r
    function _isSlashed(address wallet) internal view returns (bool) {\r
        return attesters[wallet].isSlashed;\r
    }\r
}\r
"
    }
  },
  "settings": {
    "optimizer": {
      "enabled": true,
      "runs": 200
    },
    "outputSelection": {
      "*": {
        "*": [
          "evm.bytecode",
          "evm.deployedBytecode",
          "devdoc",
          "userdoc",
          "metadata",
          "abi"
        ]
      }
    },
    "remappings": []
  }
}}

Tags:
Proxy, Upgradeable, Factory|addr:0xd73c60ef977d9e53afa093fa4e12569ea96833a6|verified:true|block:23687672|tx:0x9711370290f4022a287f3df316031b8d6b66a97619bed91a7e12c15ea954f16d|first_check:1761824839

Submitted on: 2025-10-30 12:47:21

Comments

Log in to comment.

No comments yet.