aere-contracts/contracts/mocks/PaymasterTestMocks.sol
Aere Network a13a649b77
Some checks are pending
contracts-ci / Install (lockfile) → compile → full test suite (push) Waiting to run
contracts-ci / PQC known-answer tests (NIST vectors) (push) Waiting to run
contracts-ci / Coverage (scoped, with artifacts) (push) Waiting to run
Initial public release
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.
2026-07-20 01:02:37 +03:00

151 lines
5.4 KiB
Solidity

// 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)
}
}
}