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.
139 lines
6.1 KiB
Solidity
139 lines
6.1 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.23;
|
|
|
|
import "./paymaster/PaymasterBase.sol";
|
|
|
|
interface IERC20 {
|
|
function transferFrom(address from, address to, uint256 amount) external returns (bool);
|
|
function approve(address spender, uint256 amount) external returns (bool);
|
|
function balanceOf(address) external view returns (uint256);
|
|
function decimals() external view returns (uint8);
|
|
}
|
|
|
|
interface IAereOracle {
|
|
/// Returns (price scaled 1e8, ts, contributors). Same interface as AereLending used.
|
|
function getPrice(bytes32 symbol) external view returns (uint128 price, uint64 timestamp, uint256 contributors);
|
|
}
|
|
|
|
/**
|
|
* @title AereTokenPaymaster
|
|
* @notice Pay AERE gas with any whitelisted ERC-20.
|
|
*
|
|
* How it works:
|
|
* 1. User submits a UserOperation with paymasterData = (token, maxTokenCost).
|
|
* 2. We read the token's USD price from AereOracle and AERE/USD price.
|
|
* 3. We compute the AERE cost of the op, convert to token, add markup, charge.
|
|
* 4. We pull tokens from the user via transferFrom (requires prior approve).
|
|
*
|
|
* Markup: configurable bps (default 500 = 5%). Markup keeps the paymaster
|
|
* profitable over time without active management.
|
|
*
|
|
* Refunds: if actual gas spent < maxCost, the unused portion is NOT refunded
|
|
* (gas-saving design; the markup absorbs the difference).
|
|
*
|
|
* Foundation pre-funds the paymaster with a small AERE float. The paymaster
|
|
* accumulates tokens (USDT, USDC, etc.) and the Foundation periodically swaps
|
|
* them on AereSwap to top up the AERE float.
|
|
*/
|
|
contract AereTokenPaymaster is PaymasterBase {
|
|
IAereOracle public oracle;
|
|
bytes32 public constant AERE_USD = keccak256("AERE/USD");
|
|
|
|
struct TokenConfig {
|
|
bool enabled;
|
|
bytes32 priceKey; // e.g. keccak256("USDT/USD") - returned by AereOracle
|
|
uint8 decimals;
|
|
}
|
|
mapping(address => TokenConfig) public tokens;
|
|
|
|
/// Markup over fair price, in basis points. 500 = 5%.
|
|
uint256 public markupBps = 500;
|
|
|
|
/// Fallback AERE/USD price (1e8 scale). Used while oracle has no AERE/USD feed.
|
|
uint256 public fallbackAereUsd1e8 = 10_000_000; // $0.10
|
|
|
|
event TokenSet(address indexed token, bool enabled, bytes32 priceKey, uint8 decimals);
|
|
event ConfigChanged(uint256 markupBps);
|
|
event Paid(address indexed sender, address indexed token, uint256 tokenAmount);
|
|
|
|
constructor(IEntryPoint _ep, IAereOracle _oracle) PaymasterBase(_ep) {
|
|
oracle = _oracle;
|
|
}
|
|
|
|
// ───────────────────── Admin ─────────────────────
|
|
|
|
function setToken(address token, bytes32 priceKey, uint8 dec, bool enabled) external onlyOwner {
|
|
tokens[token] = TokenConfig(enabled, priceKey, dec);
|
|
emit TokenSet(token, enabled, priceKey, dec);
|
|
}
|
|
|
|
function setMarkup(uint256 _markupBps) external onlyOwner {
|
|
require(_markupBps <= 3000, "TokenPM: markup too high");
|
|
markupBps = _markupBps;
|
|
emit ConfigChanged(_markupBps);
|
|
}
|
|
|
|
function setFallbackAereUsd(uint256 price1e8) external onlyOwner {
|
|
fallbackAereUsd1e8 = price1e8;
|
|
}
|
|
|
|
function withdrawToken(address token, address to, uint256 amount) external onlyOwner {
|
|
IERC20(token).transferFrom(address(this), to, amount);
|
|
}
|
|
|
|
// ───────────────────── Pricing ─────────────────────
|
|
|
|
function _aereUsd1e8() internal view returns (uint256) {
|
|
try oracle.getPrice(AERE_USD) returns (uint128 p, uint64, uint256) {
|
|
if (p > 0) return uint256(p);
|
|
} catch {}
|
|
return fallbackAereUsd1e8;
|
|
}
|
|
|
|
function quoteTokenAmount(address token, uint256 aereWei) public view returns (uint256 tokenAmount) {
|
|
TokenConfig memory cfg = tokens[token];
|
|
require(cfg.enabled, "TokenPM: token disabled");
|
|
(uint128 tokPrice, , ) = oracle.getPrice(cfg.priceKey);
|
|
require(tokPrice > 0, "TokenPM: oracle missing");
|
|
// USD value of the AERE: aereWei * AERE/USD (1e8) / 1e18
|
|
// Token amount = USD / (token/USD * 10^(18 - tokenDecimals))
|
|
// Combine: tokenAmount = aereWei * aereUsd / tokPrice (both prices share 1e8 scale → cancels)
|
|
// Adjust to token decimals: result is in 1e18 units of USD-AERE; divide by 10^(18-dec) to get token native units.
|
|
uint256 usdAmount = (aereWei * _aereUsd1e8()) / 1e8; // 1e18 USD-scaled
|
|
uint256 amount1e18 = (usdAmount * 1e8) / uint256(tokPrice); // 1e18 token-scaled
|
|
if (cfg.decimals < 18) {
|
|
tokenAmount = amount1e18 / (10 ** (18 - cfg.decimals));
|
|
} else {
|
|
tokenAmount = amount1e18 * (10 ** (cfg.decimals - 18));
|
|
}
|
|
// Apply markup
|
|
tokenAmount = (tokenAmount * (10_000 + markupBps)) / 10_000;
|
|
}
|
|
|
|
// ───────────────────── Paymaster hook ─────────────────────
|
|
|
|
function _validatePaymasterUserOp(
|
|
PackedUserOperation calldata userOp,
|
|
bytes32 /*userOpHash*/,
|
|
uint256 maxCost
|
|
) internal override returns (bytes memory context, uint256 validationData) {
|
|
// paymasterAndData layout:
|
|
// bytes 0..20 - paymaster address
|
|
// bytes 20..36 - paymaster verification gas limit + post-op gas limit (32 bytes packed)
|
|
// bytes 52..72 - token address (20 bytes)
|
|
// bytes 72..104 - maxTokenCost (32 bytes)
|
|
require(userOp.paymasterAndData.length >= 104, "TokenPM: bad paymasterData");
|
|
address token = address(bytes20(userOp.paymasterAndData[52:72]));
|
|
uint256 maxTokenCost = uint256(bytes32(userOp.paymasterAndData[72:104]));
|
|
|
|
uint256 tokenCost = quoteTokenAmount(token, maxCost);
|
|
require(tokenCost <= maxTokenCost, "TokenPM: token cost exceeds max");
|
|
|
|
// Pull tokens up-front. User must have pre-approved this contract.
|
|
require(IERC20(token).transferFrom(userOp.sender, address(this), tokenCost), "TokenPM: pull failed");
|
|
|
|
emit Paid(userOp.sender, token, tokenCost);
|
|
return ("", 0);
|
|
}
|
|
}
|