// SPDX-License-Identifier: MIT pragma solidity 0.8.23; // TEST-ONLY mocks for the paymaster-stack audit suite. // NEVER deploy to chain 2800. These exist only so the hardhat suite can drive // the real paymaster sources through accept/reject/pricing/quota/gas paths. import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "../AereStakeQuotaPaymaster.sol"; import "../paymaster/AereEntryPoint.sol"; /// AereOracle-shaped mock: getPrice(bytes32) -> (uint128 price1e8, uint64 ts, uint256 contributors). contract MockAereOracle { mapping(bytes32 => uint128) public px; mapping(bytes32 => bool) public boom; function set(bytes32 k, uint128 p) external { px[k] = p; } function setRevert(bytes32 k, bool b) external { boom[k] = b; } function getPrice(bytes32 k) external view returns (uint128, uint64, uint256) { require(!boom[k], "oracle: revert"); return (px[k], uint64(block.timestamp), 1); } } /// AereStaking-shaped mock that DOES implement balanceOf (unlike the real one), /// so the quota-math tests can drive stakedOf via the delegated path. contract MockStaking { mapping(address => uint256) public bal; bool public boom; function setBal(address u, uint256 v) external { bal[u] = v; } function setBoom(bool b) external { boom = b; } function balanceOf(address u) external view returns (uint256) { require(!boom, "staking: boom"); return bal[u]; } } /// Faithful clone of AereLockedStaking's storage layout + getLock signature /// (returns the Lock struct in the SAME field order as the real contract), /// plus a batch `seed` so the OOG gas test can create many locks cheaply. contract MockLockedStaking { struct Lock { uint256 amount; uint64 startedAt; uint64 unlockAt; uint8 tier; bool withdrawn; } mapping(address => Lock[]) private _locks; bool public boom; function setBoom(bool b) external { boom = b; } function seed(address u, uint256 n, uint256 amount, uint64 unlockAt) external { for (uint256 i = 0; i < n; i++) { _locks[u].push(Lock(amount, uint64(block.timestamp), unlockAt, 0, false)); } } function lockCount(address u) external view returns (uint256) { require(!boom, "locked: boom"); return _locks[u].length; } function getLock(address u, uint256 id) external view returns (Lock memory) { return _locks[u][id]; } } /// Locked-staking mock whose getLock returns the fields in the ORDER the /// paymaster's IAereLockedStaking interface EXPECTS: (tier, amount, startTime, /// unlockTime, withdrawn). Used to (a) prove the field order is the root cause of /// the revert, and (b) measure the loop gas in the hypothetical "interface fixed" /// world where the loop actually executes. contract MockLockedStakingMatched { struct L { uint8 tier; uint256 amount; uint64 startTime; uint64 unlockTime; bool withdrawn; } mapping(address => L[]) private _locks; bool public boom; function setBoom(bool b) external { boom = b; } function seed(address u, uint256 n, uint256 amount, uint64 unlockTime) external { for (uint256 i = 0; i < n; i++) { _locks[u].push(L(0, amount, uint64(block.timestamp), unlockTime, false)); } } function lockCount(address u) external view returns (uint256) { require(!boom, "matched: boom"); return _locks[u].length; } function getLock(address u, uint256 id) external view returns (uint8 tier, uint256 amount, uint64 startTime, uint64 unlockTime, bool withdrawn) { L memory l = _locks[u][id]; return (l.tier, l.amount, l.startTime, l.unlockTime, l.withdrawn); } } /// ERC-20 whose transferFrom returns false (no revert) when armed, to exercise /// AereTokenPaymaster's `require(...transferFrom..., "TokenPM: pull failed")`. contract MockERC20FalseTransferFrom is ERC20 { uint8 private immutable _dec; bool public lie; constructor(string memory n, string memory s, uint8 d) ERC20(n, s) { _dec = d; } function decimals() public view override returns (uint8) { return _dec; } function mint(address to, uint256 amt) external { _mint(to, amt); } function setLie(bool b) external { lie = b; } function transferFrom(address from, address to, uint256 amount) public override returns (bool) { if (lie) return false; return super.transferFrom(from, to, amount); } } /// Measures the gas consumed by a staticcall to stakedOf(), read via callStatic. contract StakeGasProbe { function measure(address pm, address user) external view returns (uint256 gasUsed, uint256 staked) { uint256 g0 = gasleft(); staked = AereStakeQuotaPaymaster(payable(pm)).stakedOf(user); gasUsed = g0 - gasleft(); } } /// Attempts to re-enter AereEntryPoint.withdrawTo during the value callback, /// to prove the nonReentrant guard blocks reentrancy. contract ReentrantWithdrawer { AereEntryPoint public immutable ep; bool public tried; constructor(address _ep) { ep = AereEntryPoint(payable(_ep)); } function fund() external payable { ep.depositTo{ value: msg.value }(address(this)); } function attack(uint256 amount) external { ep.withdrawTo(payable(address(this)), amount); } receive() external payable { if (!tried && address(ep).balance >= 1) { tried = true; ep.withdrawTo(payable(address(this)), 1); // must revert (nonReentrant) } } }