// SPDX-License-Identifier: UNLICENSED
// [NAME] — a hand-written solidity contract on robinhood chain.
// the state only rises, the record only lengthens, the rain only comes at a record.
//
// no inheritance from libraries, no proxy, no admin slot, no delegatecall.
// the abi is not the specification. the bytecode is.
pragma solidity 0.8.26;
interface IGasInfo {
/// wei the chain has collected from users for posting their data,
/// against wei the chain has actually paid ethereum to post it.
function getL1RewardRate() external view returns (uint64);
function getL1PricingSurplus() external view returns (int256);
function getLastL1PricingUpdateTime() external view returns (uint64);
function getL1PricingUnitsSinceUpdate() external view returns (uint64);
}
interface IERC20Minimal {
function transfer(address to, uint256 amount) external returns (bool);
function transferFrom(address from, address to, uint256 amount) external returns (bool);
}
contract Clud {
// ---------------------------------------------------------------- state
/// the worst shortfall ever observed, in wei. only rises.
uint256 public level;
/// wei held for the next rain.
uint256 public vault;
/// how many times the level has been beaten.
uint64 public rains;
/// how many times observe() has run.
uint64 public observations;
/// the block number of the previous observation.
uint64 public lastObservedBlock;
/// blocks that passed out of blockhash reach with no observer. only rises.
uint64 public unseen;
// staking, fixed point, constant time claim
uint256 public totalStaked;
uint256 public rewardIndex; // scaled by 1e27
mapping(address => uint256) public staked;
mapping(address => uint256) public rewardDebt;
IGasInfo private constant GAS_INFO = IGasInfo(0x000000000000000000000000000000000000006C);
IERC20Minimal private immutable TOKEN;
/// the caller's cut of the vault on a rain, in basis points.
uint256 private constant OBSERVER_BPS = 200; // 2%
uint256 private constant BPS = 10_000;
uint256 private constant INDEX_SCALE = 1e27;
/// the evm remembers this many block hashes and no more.
uint64 private constant HORIZON = 256;
event Observed(
uint64 indexed observation,
uint256 shortfall,
uint256 level,
uint64 interval,
uint64 unseenAdded
);
event Rained(uint64 indexed rain, uint256 shortfall, uint256 paidCaller, uint256 sweptPool);
constructor(address token) payable {
TOKEN = IERC20Minimal(token);
lastObservedBlock = uint64(block.number);
// no owner is written. there is no storage slot for one.
}
// -------------------------------------------------------------- observe
/// permissionless. takes no arguments. reverts on nothing.
function observe() external {
uint64 nowBlock = uint64(block.number);
uint64 last = lastObservedBlock;
// continuity. if the previous observation is out of blockhash reach
// the interval cannot be proven, and the gap is recorded as unproven.
uint64 unseenAdded = 0;
if (nowBlock > last + HORIZON || blockhash(last) == bytes32(0)) {
unseenAdded = nowBlock - last - (nowBlock > HORIZON ? HORIZON : nowBlock);
unseen += unseenAdded;
}
// the scalar. what the chain collected for data against what it paid.
int256 surplus = GAS_INFO.getL1PricingSurplus();
uint256 shortfall = surplus < 0 ? uint256(-surplus) : 0;
observations += 1;
lastObservedBlock = nowBlock;
emit Observed(observations, shortfall, level, nowBlock - last, unseenAdded);
if (shortfall <= level) {
// the caller has paid gas and receives nothing.
return;
}
level = shortfall;
rains += 1;
uint256 pot = vault;
vault = 0;
uint256 toCaller = (pot * OBSERVER_BPS) / BPS;
uint256 toPool = pot - toCaller;
if (totalStaked > 0) {
rewardIndex += (toPool * INDEX_SCALE) / totalStaked;
} else {
vault += toPool;
toPool = 0;
}
emit Rained(rains, shortfall, toCaller, toPool);
(bool ok, ) = msg.sender.call{value: toCaller}("");
require(ok);
}
// ---------------------------------------------------------------- pool
function stake(uint256 amount) external {
_settle(msg.sender);
staked[msg.sender] += amount;
totalStaked += amount;
require(TOKEN.transferFrom(msg.sender, address(this), amount));
}
/// no lockup, no exit penalty.
function unstake(uint256 amount) external {
_settle(msg.sender);
staked[msg.sender] -= amount;
totalStaked -= amount;
require(TOKEN.transfer(msg.sender, amount));
}
/// constant time regardless of how many dry observations were sat through.
function claim() external {
uint256 owed = _settle(msg.sender);
if (owed == 0) return;
(bool ok, ) = msg.sender.call{value: owed}("");
require(ok);
}
mapping(address => uint256) private accrued;
function _settle(address who) private returns (uint256) {
uint256 idx = rewardIndex;
uint256 delta = idx - rewardDebt[who];
if (delta > 0 && staked[who] > 0) {
accrued[who] += (staked[who] * delta) / INDEX_SCALE;
}
rewardDebt[who] = idx;
uint256 owed = accrued[who];
accrued[who] = 0;
return owed;
}
function pending(address who) external view returns (uint256) {
uint256 delta = rewardIndex - rewardDebt[who];
return accrued[who] + (staked[who] * delta) / INDEX_SCALE;
}
// ---------------------------------------------------------------- vault
/// anything sent here fills the vault and can leave only through a rain.
receive() external payable {
vault += msg.value;
}
// there is no setter, no reset, no pause, no migration, no withdrawal,
// no upgrade path and no admin. this is the whole of the contract.
}