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.
230 lines
9.0 KiB
Solidity
230 lines
9.0 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity 0.8.23;
|
|
|
|
import "@openzeppelin/contracts/utils/introspection/IERC165.sol";
|
|
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
|
|
import "@openzeppelin/contracts/interfaces/IERC1271.sol";
|
|
import "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
|
|
|
|
/// @dev ERC-6551 account interface (ERC-165 id 0x6faff5f1).
|
|
interface IERC6551Account {
|
|
receive() external payable;
|
|
|
|
function token()
|
|
external
|
|
view
|
|
returns (uint256 chainId, address tokenContract, uint256 tokenId);
|
|
|
|
function state() external view returns (uint256);
|
|
|
|
function isValidSigner(address signer, bytes calldata context)
|
|
external
|
|
view
|
|
returns (bytes4 magicValue);
|
|
}
|
|
|
|
/// @dev ERC-6551 executable interface (ERC-165 id 0x51945447).
|
|
interface IERC6551Executable {
|
|
function execute(address to, uint256 value, bytes calldata data, uint8 operation)
|
|
external
|
|
payable
|
|
returns (bytes memory);
|
|
}
|
|
|
|
/**
|
|
* @title AereTokenBoundAccount — ERC-6551 token-bound smart account for AERE.
|
|
*
|
|
* @notice A wallet OWNED BY AN NFT. Deployed as a deterministic ERC-1167 proxy
|
|
* by the canonical ERC6551Registry and bound at creation to a specific
|
|
* (chainId, tokenContract, tokenId). Whoever currently holds that NFT
|
|
* controls this account: they can execute arbitrary calls, and the
|
|
* account produces EIP-1271 signatures on their behalf. The account can
|
|
* hold native AERE and any tokens/NFTs like any other address.
|
|
*
|
|
* This is an ACCOUNT PRIMITIVE, not a fungible/currency/governance token.
|
|
* It mints nothing, has no supply, and issues no transferable claim. Its
|
|
* only authority source is `ownerOf(tokenId)` on the bound ERC-721.
|
|
*
|
|
* EXECUTION + SIGNING (reuses AERE's 4337 / EIP-1271 pattern from
|
|
* AerePasskeyAccountV2): the same "owner authenticates, then call the
|
|
* target" shape, and the same EIP-1271 `isValidSignature` shape, but the
|
|
* owner set is derived from NFT ownership rather than a stored key set.
|
|
*
|
|
* Two authorization paths:
|
|
* 1. execute(...) — msg.sender must be the current NFT holder.
|
|
* 2. validateUserOp / executeFromEntryPoint — ERC-4337 path through the
|
|
* deployed AereEntryPointV2 (0x8D6f…aF770); the owner signs the
|
|
* userOpHash and the EntryPoint relays. Same 4337 shape as the
|
|
* passkey account, letting a bundler/paymaster sponsor the NFT
|
|
* wallet's gas.
|
|
*
|
|
* Only CALL (operation 0) is supported; DELEGATECALL/CREATE are rejected
|
|
* so the bound account can never be hijacked into changing its own logic.
|
|
*/
|
|
contract AereTokenBoundAccount is
|
|
IERC165,
|
|
IERC1271,
|
|
IERC6551Account,
|
|
IERC6551Executable
|
|
{
|
|
/// @notice Monotonic counter bumped on every successful execute — ERC-6551 `state`.
|
|
uint256 public state;
|
|
|
|
/// @notice Deployed AERE ERC-4337 EntryPoint (AereEntryPointV2). Fixed constant:
|
|
/// ERC-6551 proxies share one implementation and take no constructor args.
|
|
address public constant ENTRY_POINT = 0x8D6f40598d552fF0Cb358b6012cF4227B86aF770;
|
|
|
|
/// @notice EIP-1271 magic value on success.
|
|
bytes4 internal constant EIP1271_MAGIC = 0x1626ba7e;
|
|
|
|
/// @notice ERC-4337 v0.7 PackedUserOperation — identical shape to AereEntryPointV2.
|
|
struct PackedUserOperation {
|
|
address sender;
|
|
uint256 nonce;
|
|
bytes initCode;
|
|
bytes callData;
|
|
bytes32 accountGasLimits;
|
|
uint256 preVerificationGas;
|
|
bytes32 gasFees;
|
|
bytes paymasterAndData;
|
|
bytes signature;
|
|
}
|
|
|
|
event Executed(uint256 indexed state, address indexed to, uint256 value, bytes data);
|
|
|
|
error InvalidSigner();
|
|
error UnsupportedOperation(uint8 operation);
|
|
error NotFromEntryPoint();
|
|
|
|
receive() external payable {}
|
|
|
|
// ─── ERC-6551 execution (direct owner path) ───────────────────────────────
|
|
|
|
/// @notice Execute a call authorized by the current NFT holder.
|
|
/// @param operation must be 0 (CALL); anything else reverts.
|
|
function execute(address to, uint256 value, bytes calldata data, uint8 operation)
|
|
external
|
|
payable
|
|
override
|
|
returns (bytes memory result)
|
|
{
|
|
if (!_isValidSigner(msg.sender)) revert InvalidSigner();
|
|
if (operation != 0) revert UnsupportedOperation(operation);
|
|
|
|
++state;
|
|
|
|
bool success;
|
|
(success, result) = to.call{value: value}(data);
|
|
if (!success) {
|
|
// bubble up the revert reason
|
|
assembly {
|
|
revert(add(result, 32), mload(result))
|
|
}
|
|
}
|
|
emit Executed(state, to, value, data);
|
|
}
|
|
|
|
// ─── ERC-4337 path (through AereEntryPointV2) ─────────────────────────────
|
|
|
|
/// @notice ERC-4337 validation. Returns 0 on success, 1 on bad signature
|
|
/// (per spec — does not revert on signature failure).
|
|
function validateUserOp(
|
|
PackedUserOperation calldata userOp,
|
|
bytes32 userOpHash,
|
|
uint256 missingAccountFunds
|
|
) external returns (uint256 validationData) {
|
|
if (msg.sender != ENTRY_POINT) revert NotFromEntryPoint();
|
|
|
|
// Owner (the NFT holder) must have signed the userOpHash. SignatureChecker
|
|
// supports both EOA holders and smart-contract (EIP-1271) holders.
|
|
bool ok = SignatureChecker.isValidSignatureNow(owner(), userOpHash, userOp.signature);
|
|
if (!ok) return 1; // SIG_VALIDATION_FAILED
|
|
|
|
if (missingAccountFunds > 0) {
|
|
(bool sent, ) = payable(msg.sender).call{value: missingAccountFunds}("");
|
|
(sent); // EntryPoint reverts if underfunded
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/// @notice Post-validation execution dispatched by the EntryPoint.
|
|
function executeFromEntryPoint(address to, uint256 value, bytes calldata data)
|
|
external
|
|
returns (bytes memory result)
|
|
{
|
|
if (msg.sender != ENTRY_POINT) revert NotFromEntryPoint();
|
|
++state;
|
|
bool success;
|
|
(success, result) = to.call{value: value}(data);
|
|
if (!success) {
|
|
assembly {
|
|
revert(add(result, 32), mload(result))
|
|
}
|
|
}
|
|
emit Executed(state, to, value, data);
|
|
}
|
|
|
|
// ─── ERC-6551 signer / EIP-1271 ───────────────────────────────────────────
|
|
|
|
function isValidSigner(address signer, bytes calldata)
|
|
external
|
|
view
|
|
override
|
|
returns (bytes4)
|
|
{
|
|
if (_isValidSigner(signer)) return IERC6551Account.isValidSigner.selector;
|
|
return bytes4(0);
|
|
}
|
|
|
|
/// @notice EIP-1271: an off-chain signature is valid iff it is a valid
|
|
/// signature by the current NFT holder over `hash`.
|
|
function isValidSignature(bytes32 hash, bytes memory signature)
|
|
external
|
|
view
|
|
override
|
|
returns (bytes4 magicValue)
|
|
{
|
|
return SignatureChecker.isValidSignatureNow(owner(), hash, signature)
|
|
? EIP1271_MAGIC
|
|
: bytes4(0);
|
|
}
|
|
|
|
// ─── ERC-6551 binding / ownership ─────────────────────────────────────────
|
|
|
|
/// @notice The (chainId, tokenContract, tokenId) this account is bound to,
|
|
/// read from the ERC-1167 footer appended by the registry.
|
|
function token()
|
|
public
|
|
view
|
|
override
|
|
returns (uint256 chainId, address tokenContract, uint256 tokenId)
|
|
{
|
|
bytes memory footer = new bytes(0x60);
|
|
assembly {
|
|
// proxy runtime is 45 bytes; the 3 immutable words start at 0x4d.
|
|
extcodecopy(address(), add(footer, 0x20), 0x4d, 0x60)
|
|
}
|
|
return abi.decode(footer, (uint256, address, uint256));
|
|
}
|
|
|
|
/// @notice The current controller = holder of the bound NFT.
|
|
function owner() public view returns (address) {
|
|
(uint256 chainId, address tokenContract, uint256 tokenId) = token();
|
|
if (chainId != block.chainid) return address(0);
|
|
return IERC721(tokenContract).ownerOf(tokenId);
|
|
}
|
|
|
|
function _isValidSigner(address signer) internal view returns (bool) {
|
|
return signer != address(0) && signer == owner();
|
|
}
|
|
|
|
// ─── ERC-165 ──────────────────────────────────────────────────────────────
|
|
|
|
function supportsInterface(bytes4 interfaceId) external pure override returns (bool) {
|
|
return interfaceId == type(IERC165).interfaceId
|
|
|| interfaceId == type(IERC6551Account).interfaceId
|
|
|| interfaceId == type(IERC6551Executable).interfaceId
|
|
|| interfaceId == type(IERC1271).interfaceId;
|
|
}
|
|
}
|