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.
151 lines
5.4 KiB
Solidity
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)
|
|
}
|
|
}
|
|
}
|