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.
130 lines
4.4 KiB
Solidity
130 lines
4.4 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity 0.8.23;
|
|
|
|
import "@openzeppelin/contracts/access/Ownable.sol";
|
|
import "./interfaces/ISeason1.sol";
|
|
|
|
/**
|
|
* @title AereQuestRegistry — AERE Season 1 "Altitude" quest catalogue
|
|
* @notice Foundation-owned registry describing every quest: its point weight,
|
|
* active window, per-user cap, active flag, and whether it requires a
|
|
* verified human. Fully TUNABLE without redeploying the ledger or the
|
|
* attestor — those read quest config live from here.
|
|
*
|
|
* A quest is keyed by a bytes32 questId (e.g. keccak256("create-passkey")).
|
|
*
|
|
* This contract holds NO funds and issues NO credits. It is pure config that
|
|
* the attestor and referral registry consult. Owner = Foundation account (single-key EOA today, not a multisig).
|
|
*/
|
|
contract AereQuestRegistry is Ownable, IAereQuestRegistry {
|
|
|
|
mapping(bytes32 => Quest) private _quests;
|
|
bytes32[] public questIds;
|
|
|
|
event QuestCreated(
|
|
bytes32 indexed questId,
|
|
uint256 weight,
|
|
uint64 startTime,
|
|
uint64 endTime,
|
|
uint256 perUserCap,
|
|
bool requiresVerifiedHuman
|
|
);
|
|
event QuestUpdated(
|
|
bytes32 indexed questId,
|
|
uint256 weight,
|
|
uint64 startTime,
|
|
uint64 endTime,
|
|
uint256 perUserCap,
|
|
bool requiresVerifiedHuman
|
|
);
|
|
event QuestActiveSet(bytes32 indexed questId, bool active);
|
|
|
|
error QuestExists();
|
|
error UnknownQuest();
|
|
error BadWindow();
|
|
error BadWeight();
|
|
|
|
/* ------------------------------- mutations ------------------------------ */
|
|
|
|
function createQuest(
|
|
bytes32 questId,
|
|
uint256 weight,
|
|
uint64 startTime,
|
|
uint64 endTime,
|
|
uint256 perUserCap,
|
|
bool requiresVerifiedHuman
|
|
) external onlyOwner {
|
|
if (questId == bytes32(0)) revert UnknownQuest();
|
|
if (_quests[questId].exists) revert QuestExists();
|
|
_validate(weight, startTime, endTime, perUserCap);
|
|
|
|
_quests[questId] = Quest({
|
|
weight: weight,
|
|
startTime: startTime,
|
|
endTime: endTime,
|
|
perUserCap: perUserCap,
|
|
active: true,
|
|
requiresVerifiedHuman: requiresVerifiedHuman,
|
|
exists: true
|
|
});
|
|
questIds.push(questId);
|
|
emit QuestCreated(questId, weight, startTime, endTime, perUserCap, requiresVerifiedHuman);
|
|
}
|
|
|
|
function updateQuest(
|
|
bytes32 questId,
|
|
uint256 weight,
|
|
uint64 startTime,
|
|
uint64 endTime,
|
|
uint256 perUserCap,
|
|
bool requiresVerifiedHuman
|
|
) external onlyOwner {
|
|
Quest storage q = _quests[questId];
|
|
if (!q.exists) revert UnknownQuest();
|
|
_validate(weight, startTime, endTime, perUserCap);
|
|
|
|
q.weight = weight;
|
|
q.startTime = startTime;
|
|
q.endTime = endTime;
|
|
q.perUserCap = perUserCap;
|
|
q.requiresVerifiedHuman = requiresVerifiedHuman;
|
|
emit QuestUpdated(questId, weight, startTime, endTime, perUserCap, requiresVerifiedHuman);
|
|
}
|
|
|
|
function setQuestActive(bytes32 questId, bool active) external onlyOwner {
|
|
if (!_quests[questId].exists) revert UnknownQuest();
|
|
_quests[questId].active = active;
|
|
emit QuestActiveSet(questId, active);
|
|
}
|
|
|
|
function _validate(uint256 weight, uint64 startTime, uint64 endTime, uint256 perUserCap) internal pure {
|
|
if (weight == 0) revert BadWeight();
|
|
// perUserCap must admit at least one completion.
|
|
if (perUserCap < weight) revert BadWeight();
|
|
// endTime 0 == open-ended; otherwise must be after start.
|
|
if (endTime != 0 && endTime <= startTime) revert BadWindow();
|
|
}
|
|
|
|
/* --------------------------------- views -------------------------------- */
|
|
|
|
function getQuest(bytes32 questId) external view returns (Quest memory) {
|
|
Quest memory q = _quests[questId];
|
|
if (!q.exists) revert UnknownQuest();
|
|
return q;
|
|
}
|
|
|
|
/// @notice True iff the quest exists, is active, and `atTime` is within its
|
|
/// window. endTime == 0 means open-ended.
|
|
function isOpen(bytes32 questId, uint256 atTime) external view returns (bool) {
|
|
Quest memory q = _quests[questId];
|
|
if (!q.exists || !q.active) return false;
|
|
if (atTime < q.startTime) return false;
|
|
if (q.endTime != 0 && atTime > q.endTime) return false;
|
|
return true;
|
|
}
|
|
|
|
function questCount() external view returns (uint256) {
|
|
return questIds.length;
|
|
}
|
|
}
|