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.
78 lines
3.0 KiB
Solidity
78 lines
3.0 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity 0.8.23;
|
|
|
|
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
|
|
|
interface IPayVault {
|
|
function pay(bytes32 findingId, address reporter, uint256 amount) external;
|
|
function fund(uint256 amount) external;
|
|
}
|
|
|
|
/// TEST-ONLY mock (local hardhat, NEVER deploy to chain 2800): an ERC20 whose
|
|
/// transfer() re-enters AereBugBountyVault.pay() (or fund()) exactly once, using
|
|
/// try/catch so the outer legitimate call can still complete. Lets the fuzz suite
|
|
/// prove the ReentrancyGuard on pay()/fund() blocks a token-callback re-entry
|
|
/// drain. A plain ERC20 can never exercise this path.
|
|
contract MockReentrantBountyToken is ERC20 {
|
|
address public vault; // AereBugBountyVault under test
|
|
bool public armed; // when true, transfer() re-enters once
|
|
bool public reenterFund; // re-enter fund() instead of pay() when true
|
|
bool public reentryAttempted;
|
|
bool public reentryReverted;
|
|
string public lastRevert;
|
|
|
|
constructor() ERC20("Reentrant WAERE", "rWAERE") {}
|
|
|
|
function mint(address to, uint256 amt) external { _mint(to, amt); }
|
|
|
|
function setVault(address v) external { vault = v; }
|
|
function arm(bool on, bool useFund) external { armed = on; reenterFund = useFund; }
|
|
|
|
/// Outer entrypoints so this token can be set AS the vault's triager, letting
|
|
/// the nested re-entry pass onlyTriager and actually reach the ReentrancyGuard.
|
|
function triggerPay(bytes32 findingId, address reporter, uint256 amount) external {
|
|
IPayVault(vault).pay(findingId, reporter, amount);
|
|
}
|
|
function triggerFund(uint256 amount) external {
|
|
// vault.fund does transferFrom(msg.sender=this, ...); ensure allowance is set by test.
|
|
IPayVault(vault).fund(amount);
|
|
}
|
|
|
|
function _maybeReenter() internal {
|
|
if (!armed || vault == address(0)) return;
|
|
armed = false; // one-shot so we do not recurse forever
|
|
reentryAttempted = true;
|
|
if (reenterFund) {
|
|
try IPayVault(vault).fund(1) {
|
|
reentryReverted = false;
|
|
} catch Error(string memory reason) {
|
|
reentryReverted = true;
|
|
lastRevert = reason;
|
|
} catch {
|
|
reentryReverted = true;
|
|
lastRevert = "unknown";
|
|
}
|
|
} else {
|
|
try IPayVault(vault).pay(bytes32("REENTRY"), address(0xBEEF), 1) {
|
|
reentryReverted = false;
|
|
} catch Error(string memory reason) {
|
|
reentryReverted = true;
|
|
lastRevert = reason;
|
|
} catch {
|
|
reentryReverted = true;
|
|
lastRevert = "unknown";
|
|
}
|
|
}
|
|
}
|
|
|
|
function transfer(address to, uint256 amount) public override returns (bool) {
|
|
_maybeReenter();
|
|
return super.transfer(to, amount);
|
|
}
|
|
|
|
function transferFrom(address from, address to, uint256 amount) public override returns (bool) {
|
|
_maybeReenter();
|
|
return super.transferFrom(from, to, amount);
|
|
}
|
|
}
|