Some checks failed
contracts-ci / Install (lockfile) → compile → full test suite (push) Has been cancelled
contracts-ci / Ethereum interop (EIP-2537 BLS, prague hardfork) (push) Has been cancelled
contracts-ci / PQC known-answer tests (NIST vectors) (push) Has been cancelled
contracts-ci / Coverage (scoped, with artifacts) (push) Has been cancelled
The published line and the local line had no common ancestor: the public one carried the redaction pass, the local one carried three weeks of corrections that never shipped. This commit ports the local work onto the public line, keeps every public redaction, and extends the same discretion to seven client mentions that were still named in published comments. Carried: LICENSE year and LICENSING.md; the measured burn figures replacing the deflation claim (the vault holds ~0.137 AERE of 2.8 billion, and burn is a share of validator coinbase revenue, which is zero today); 'audited' removed from next to Bouncy Castle; citation paths rewritten to published form with CITATIONS-UNRESOLVED.md remeasured 2026-08-11; VERIFY-POLICY.md; slashing and ownership comments brought down to what the code does; the AerePyth repair; the shutter test helper the tests cite; runnable package.json entries; the CI file split into a GitHub/Gitea twin pair with a real measured test-run status; and the .gitignore hardening written after a compiled artifact leaked a local path in a sibling repository. A false '2-of-3 multisig' description of the owner account is corrected to what the chain measures: an externally owned account. The self-audit findings catalog stays unpublished pending an explicit decision.
104 lines
4.2 KiB
Solidity
104 lines
4.2 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity 0.8.23;
|
|
|
|
import "@openzeppelin/contracts/access/Ownable.sol";
|
|
|
|
/**
|
|
* @title AerePointsLedger — AERE Season 1 "Altitude" non-transferable points
|
|
* @notice A CREDIT-ONLY, NON-TRANSFERABLE ledger of "Altitude Points" (AP).
|
|
*
|
|
* HARD SAFETY DESIGN — AP is NOT a token and can never become one:
|
|
* - transfer / transferFrom / approve HARD-REVERT (NotTransferable). AP can
|
|
* never move between accounts, so it can never trade as a shadow token.
|
|
* - There is NO burn, NO redeem, NO conversion, NO "claim your tokens"
|
|
* path anywhere in this contract. AP is a scoreboard, nothing else.
|
|
* - Only Foundation-authorized attestors may `credit`. There is no mint()
|
|
* callable by users and no way for AP to leave an account.
|
|
*
|
|
* HONESTY / DISCLAIMER (must accompany any user-facing surfacing of AP):
|
|
* Altitude Points are NOT a token, carry NO guaranteed value, and confer
|
|
* NO right to returns. Any future reward depends on the network growing
|
|
* and is not promised. There is no listing.
|
|
*
|
|
* ERC-20 read shape (name/symbol/decimals/balanceOf/totalSupply) plus a
|
|
* mint-style Transfer(address(0), account, amount) event are emitted purely
|
|
* so block explorers / the AERE indexer can display balances. The WRITE side
|
|
* of ERC-20 is deliberately bricked.
|
|
*
|
|
* Owner = Foundation account (single-key EOA today, not a multisig). Ownable only manages the attestor allowlist;
|
|
* the owner cannot move, mint to itself for transfer, or reduce balances.
|
|
*/
|
|
contract AerePointsLedger is Ownable {
|
|
|
|
string public constant name = "AERE Altitude Points";
|
|
string public constant symbol = "AP";
|
|
uint8 public constant decimals = 0; // whole points, not a token
|
|
|
|
uint256 public totalSupply;
|
|
mapping(address => uint256) public balanceOf;
|
|
|
|
/// @notice Foundation-authorized credit sources (the quest attestor and the
|
|
/// referral registry are added here post-deploy).
|
|
mapping(address => bool) public isAttestor;
|
|
|
|
/* --------------------------------- events ------------------------------- */
|
|
|
|
/// @dev ERC-20 mint-style event for indexers. address(0) => account only.
|
|
event Transfer(address indexed from, address indexed to, uint256 value);
|
|
/// @dev Rich credit event carrying the quest attribution for the indexer.
|
|
event PointsCredited(address indexed account, uint256 amount, bytes32 indexed questId, uint256 newBalance);
|
|
event AttestorSet(address indexed attestor, bool allowed);
|
|
|
|
/* --------------------------------- errors ------------------------------- */
|
|
|
|
error NotTransferable();
|
|
error NotAttestor();
|
|
error ZeroAccount();
|
|
error ZeroAmount();
|
|
|
|
/* ------------------------------- attestors ------------------------------ */
|
|
|
|
function setAttestor(address attestor, bool allowed) external onlyOwner {
|
|
if (attestor == address(0)) revert ZeroAccount();
|
|
isAttestor[attestor] = allowed;
|
|
emit AttestorSet(attestor, allowed);
|
|
}
|
|
|
|
modifier onlyAttestor() {
|
|
if (!isAttestor[msg.sender]) revert NotAttestor();
|
|
_;
|
|
}
|
|
|
|
/* -------------------------------- credit -------------------------------- */
|
|
|
|
/// @notice Credit `amount` AP to `account`, attributed to `questId`.
|
|
/// Credit-only: balances can only ever go up.
|
|
function credit(address account, uint256 amount, bytes32 questId) external onlyAttestor {
|
|
if (account == address(0)) revert ZeroAccount();
|
|
if (amount == 0) revert ZeroAmount();
|
|
balanceOf[account] += amount;
|
|
totalSupply += amount;
|
|
emit Transfer(address(0), account, amount);
|
|
emit PointsCredited(account, amount, questId, balanceOf[account]);
|
|
}
|
|
|
|
/* --------------------------- bricked ERC-20 write ----------------------- */
|
|
|
|
function transfer(address, uint256) external pure returns (bool) {
|
|
revert NotTransferable();
|
|
}
|
|
|
|
function transferFrom(address, address, uint256) external pure returns (bool) {
|
|
revert NotTransferable();
|
|
}
|
|
|
|
function approve(address, uint256) external pure returns (bool) {
|
|
revert NotTransferable();
|
|
}
|
|
|
|
/// @notice Always zero — AP has no allowance concept.
|
|
function allowance(address, address) external pure returns (uint256) {
|
|
return 0;
|
|
}
|
|
}
|