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": []
}
}}
Submitted on: 2025-10-30 12:47:21
Comments
Log in to comment.
No comments yet.