Aere Network public source. Everything here can be checked against the live chain (chain id 2800, https://rpc.aere.network). Scope note, stated up front rather than buried: consensus on chain 2800 is classical secp256k1 ECDSA QBFT. The post-quantum work in this repository is at the signature, precompile, account and transport layers. Nothing here makes the consensus post-quantum, and no document in it should be read as claiming so.
101 lines
4.4 KiB
Solidity
101 lines
4.4 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.23;
|
|
|
|
import "./paymaster/PaymasterBase.sol";
|
|
|
|
/**
|
|
* @title AereOnboardingPaymaster
|
|
* @notice Foundation-funded paymaster for first-time AERE users.
|
|
*
|
|
* Hard-capped onboarding paymaster:
|
|
* - Each address gets exactly N sponsored UserOps (lifetime — never resets)
|
|
* - Sitewide daily UserOp cap (Sybil-attack protection)
|
|
* - Optional whitelist of contract addresses we'll sponsor calls to
|
|
* - Total balance is whatever Foundation pre-funded. When drained, paymaster
|
|
* refuses all UserOps until Foundation tops up (which requires a governance
|
|
* decision, not an automated refill).
|
|
*
|
|
* Math example: with 3 sponsored txs/address and a 100-tx/day sitewide cap,
|
|
* 100,000 unique addresses cost ~300,000 gas-paid-by-paymaster txs. Even at
|
|
* 0.001 AERE/tx that's 300 AERE total to onboard 100k users.
|
|
*/
|
|
contract AereOnboardingPaymaster is PaymasterBase {
|
|
/// Lifetime sponsored UserOp cap per sender address.
|
|
uint256 public sponsoredOpsPerSender = 3;
|
|
|
|
/// Sitewide UserOp cap per UTC day (rolling: floor(block.timestamp / 1 days)).
|
|
uint256 public dailySitewideCap = 100;
|
|
|
|
/// Set to true to limit sponsorship to specific target contracts only.
|
|
bool public targetWhitelistEnabled = false;
|
|
|
|
/// Whitelist of target contracts whose function calls we will sponsor.
|
|
mapping(address => bool) public targetWhitelist;
|
|
|
|
/// Per-sender lifetime count of UserOps the paymaster has sponsored.
|
|
mapping(address => uint256) public sponsoredCount;
|
|
|
|
/// Per-day sitewide counter (key = unix day).
|
|
mapping(uint256 => uint256) public dailyCount;
|
|
|
|
event Sponsored(address indexed sender, uint256 newSenderCount, uint256 todayCount);
|
|
event ConfigChanged(uint256 perSender, uint256 dailyCap, bool whitelistEnabled);
|
|
event TargetWhitelist(address indexed target, bool allowed);
|
|
|
|
constructor(IEntryPoint _ep) PaymasterBase(_ep) {}
|
|
|
|
// ───────────────────── Admin ─────────────────────
|
|
|
|
function setLimits(uint256 _perSender, uint256 _dailyCap, bool _whitelist) external onlyOwner {
|
|
sponsoredOpsPerSender = _perSender;
|
|
dailySitewideCap = _dailyCap;
|
|
targetWhitelistEnabled = _whitelist;
|
|
emit ConfigChanged(_perSender, _dailyCap, _whitelist);
|
|
}
|
|
|
|
function setWhitelist(address target, bool allowed) external onlyOwner {
|
|
targetWhitelist[target] = allowed;
|
|
emit TargetWhitelist(target, allowed);
|
|
}
|
|
|
|
// ───────────────────── Paymaster hook ─────────────────────
|
|
|
|
function _validatePaymasterUserOp(
|
|
PackedUserOperation calldata userOp,
|
|
bytes32 /* userOpHash */,
|
|
uint256 /* maxCost */
|
|
) internal override returns (bytes memory context, uint256 validationData) {
|
|
address sender = userOp.sender;
|
|
|
|
// Per-sender lifetime cap
|
|
require(sponsoredCount[sender] < sponsoredOpsPerSender, "OnboardingPM: sender exhausted");
|
|
|
|
// Sitewide daily cap
|
|
uint256 today = block.timestamp / 1 days;
|
|
require(dailyCount[today] < dailySitewideCap, "OnboardingPM: daily cap reached");
|
|
|
|
// Optional target whitelist — first 4 bytes of callData are the function selector;
|
|
// the called contract is encoded in `userOp.callData` after the selector. We don't
|
|
// decode the inner call here for gas reasons; whitelist by target is enforced via
|
|
// the SmartAccount's `execute(target, value, data)` callData convention which
|
|
// places `target` at bytes 16..36 of callData (standard ERC-4337 SimpleAccount).
|
|
if (targetWhitelistEnabled) {
|
|
bytes calldata cd = userOp.callData;
|
|
require(cd.length >= 36, "OnboardingPM: bad callData");
|
|
address target;
|
|
// solhint-disable-next-line no-inline-assembly
|
|
assembly {
|
|
// callData layout: 4 bytes selector + 12 padding + 20 bytes target + ...
|
|
target := shr(96, calldataload(add(cd.offset, 16)))
|
|
}
|
|
require(targetWhitelist[target], "OnboardingPM: target not allowed");
|
|
}
|
|
|
|
sponsoredCount[sender] += 1;
|
|
dailyCount[today] += 1;
|
|
|
|
emit Sponsored(sender, sponsoredCount[sender], dailyCount[today]);
|
|
return ("", 0); // validationData=0 means: no signature, no time range, valid
|
|
}
|
|
}
|