PloutoReserve
Holds the 10% share. Every outflow is a two-step, time-delayed operation with a recorded purpose.
Constants
uint256 public constant TIMELOCK_DELAY = 2 days;uint256 public constant EXECUTION_WINDOW = 14 days; bytes32 public constant GOVERNOR_ROLE = keccak256("GOVERNOR_ROLE");bytes32 public constant GUARDIAN_ROLE = keccak256("GUARDIAN_ROLE");Inflows
function depositRevenue() external payable { if (msg.sender != revenueRouter) revert NotRouter(); if (msg.value == 0) revert ZeroAmount(); totalRevenueReceived += msg.value; _deposits.push(Deposit(uint64(block.timestamp), msg.value, true, msg.sender)); emit RevenueDeposited(msg.sender, msg.value, address(this).balance);} receive() external payable { totalDonationsReceived += msg.value; _deposits.push(Deposit(uint64(block.timestamp), msg.value, false, msg.sender)); emit DonationReceived(msg.sender, msg.value, address(this).balance);}Every deposit carries an isProtocolRevenue flag, so history can always be separated and a donation can never inflate the revenue figure.
Proposing
function proposeWithdrawal(address token, address recipient, uint256 amount, string calldata purpose) external onlyRole(GOVERNOR_ROLE) returns (uint256 id)token == address(0) means native ETH. An empty purpose reverts with EmptyPurpose() — a movement must always say what it is for, permanently and on chain.
Executing
function executeWithdrawal(uint256 id) external onlyRole(GOVERNOR_ROLE) nonReentrantRequires state Pending, block.timestamp >= readyAt, and block.timestamp <= readyAt + EXECUTION_WINDOW. Errors: NotPending, TimelockNotElapsed, ProposalExpired, InsufficientBalance.
State is written to Executed and the event emitted before the transfer, and the whole function is nonReentrant.
Vetoing
function cancelWithdrawal(uint256 id, string calldata reason) externalCallable by GOVERNOR_ROLE or GUARDIAN_ROLE. Any pending proposal can be cancelled at any point during its window, and the reason is recorded.
Failure is safe
If the recipient rejects the ETH, the transfer fails, the whole transaction reverts, and the funds stay put. A test drives a contract whose receive() reverts and asserts the reserve balance is unchanged.
What is deliberately absent
- No
rescue(),sweep()orwithdrawAll(). - No way for the admin to bypass the timelock.
- No trading logic of any kind.
- No ability to pay stakers — that is the staking contract's 30%, and the two never mix.
Full history
depositCount() / getDeposit(i) and nextWithdrawalId() / getWithdrawal(id) expose everything. The reserve page renders all of it, including cancelled and expired proposals — nothing is filtered out for looking bad.