Spaces:
Running
Running
File size: 10,491 Bytes
35a697b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 | // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract PureBi {
// --- 0. BRANDING ---
string public constant name = "PureBilingualVersation Research Registry";
string public constant symbol = "PURE BI-VERSATION";
// --- 1. IDENTITY & REPUTATION (PER DIALECT) ---
mapping(address => bool) public isRegistered;
// user -> dialect -> xp
mapping(address => mapping(string => uint256)) public userXP;
// user -> dialect -> role
mapping(address => mapping(string => string)) public userRole;
// Global admin checking
mapping(address => bool) public isAdmin;
address public labAdmin; // The Gasless Relayer (Python Backend)
// --- 2. REGISTRY & LICENSING ---
struct DialectEntry {
uint256 id;
string phrase;
string dialect;
string ipfsCid;
address contributor;
string licenseType;
uint256 verificationCount;
uint256 rejectionCount;
bool isVerified;
bool isBurned;
}
uint256 public entryCounter;
mapping(uint256 => DialectEntry) public entries;
// Consensus Tracking (entry_id -> user -> bool)
mapping(uint256 => mapping(address => bool)) public hasVoted;
// The Burn Ward Tracking
mapping(uint256 => string) public burnReasons;
mapping(uint256 => address[]) public rejectorsList;
mapping(uint256 => uint256) public appealCount;
mapping(uint256 => mapping(address => bool)) public hasAppealed;
// --- 3. BACKUP SYSTEM ---
struct DataSnapshot {
uint256 timestamp;
string dataType;
string ipfsCid;
string description;
}
mapping(string => DataSnapshot[]) public backups;
// --- 4. EVENTS ---
event EntryProposed(uint256 indexed id, string phrase, address indexed proposer);
event EntryVerified(uint256 indexed id, address indexed verifier);
event EntryRejected(uint256 indexed id, address indexed rejector, string reason);
event EntryBurned(uint256 indexed id, address indexed contributor, string reason);
event EntryAppealed(uint256 indexed id, address indexed appealer, bool success);
event ReputationMinted(address indexed researcher, string dialect, uint256 amount);
event RankUpgraded(address indexed researcher, string dialect, string newRole);
event BackupCreated(string indexed dataKey, string ipfsCid, uint256 timestamp);
constructor() {
labAdmin = msg.sender; // The Python Backend Wallet
isAdmin[msg.sender] = true;
}
// --- MODIFIERS ---
modifier onlyRelayer() {
require(msg.sender == labAdmin, "Only the Python Relayer can execute transactions");
_;
}
// --- CORE FUNCTIONS ---
function proposeEntry(string memory _phrase, string memory _dialect, string memory _ipfsCid, string memory _license, address _contributor) public onlyRelayer returns (uint256) {
entryCounter++;
entries[entryCounter] = DialectEntry(entryCounter, _phrase, _dialect, _ipfsCid, _contributor, _license, 0, 0, false, false);
emit EntryProposed(entryCounter, _phrase, _contributor);
if (!isRegistered[_contributor]) {
isRegistered[_contributor] = true;
}
// Auto-register brand new React users as Initiates in this dialect if they have no role
if (bytes(userRole[_contributor][_dialect]).length == 0 && !isAdmin[_contributor]) {
userRole[_contributor][_dialect] = "Initiate";
}
return entryCounter;
}
function verifyEntry(uint256 _id, address _verifier) public onlyRelayer {
DialectEntry storage entry = entries[_id];
require(!entry.isVerified && !entry.isBurned, "Entry already resolved");
require(entry.contributor != _verifier, "Cannot verify own entry");
require(!hasVoted[_id][_verifier], "User already voted");
string memory role = userRole[_verifier][entry.dialect];
bool isGod = isAdmin[_verifier];
bool isOracle = keccak256(bytes(role)) == keccak256(bytes("Oracle"));
require(isGod || isOracle, "Only Admins or Oracles can verify");
hasVoted[_id][_verifier] = true;
_mintReputation(_verifier, entry.dialect, 10); // +10 XP for doing the audit
if (isGod) {
_applyVerification(_id);
} else {
entry.verificationCount++;
if (entry.verificationCount >= 2) {
_applyVerification(_id);
}
}
}
function _applyVerification(uint256 _id) internal {
entries[_id].isVerified = true;
_mintReputation(entries[_id].contributor, entries[_id].dialect, 50); // +50 XP to the original submitter
emit EntryVerified(_id, msg.sender);
}
function rejectEntry(uint256 _id, address _rejector, string memory _reason) public onlyRelayer {
DialectEntry storage entry = entries[_id];
require(!entry.isVerified && !entry.isBurned, "Entry already resolved");
require(entry.contributor != _rejector, "Cannot reject own entry");
require(!hasVoted[_id][_rejector], "User already voted");
string memory role = userRole[_rejector][entry.dialect];
bool isGod = isAdmin[_rejector];
bool isOracle = keccak256(bytes(role)) == keccak256(bytes("Oracle"));
require(isGod || isOracle, "Only Admins or Oracles can reject");
hasVoted[_id][_rejector] = true;
rejectorsList[_id].push(_rejector); // Track for potential penalty
_mintReputation(_rejector, entry.dialect, 10); // +10 XP for doing the audit
emit EntryRejected(_id, _rejector, _reason);
if (isGod) {
_applyBurn(_id, _reason);
} else {
entry.rejectionCount++;
if (entry.rejectionCount >= 3) {
_applyBurn(_id, _reason);
}
}
}
function _applyBurn(uint256 _id, string memory _reason) internal {
entries[_id].isBurned = true;
burnReasons[_id] = _reason;
address badActor = entries[_id].contributor;
string memory dialect = entries[_id].dialect;
// Burn -10 XP from the contributor
if (userXP[badActor][dialect] >= 10) {
userXP[badActor][dialect] -= 10;
} else {
userXP[badActor][dialect] = 0;
}
_updateRank(badActor, dialect);
emit EntryBurned(_id, badActor, _reason);
}
function appealEntry(uint256 _id, address _appealer) public onlyRelayer {
DialectEntry storage entry = entries[_id];
require(entry.isBurned, "Entry is not burned");
require(!hasAppealed[_id][_appealer], "User already appealed");
string memory role = userRole[_appealer][entry.dialect];
bool isGod = isAdmin[_appealer];
bool isOracle = keccak256(bytes(role)) == keccak256(bytes("Oracle"));
require(isGod || isOracle, "Only Admins or Oracles can appeal");
hasAppealed[_id][_appealer] = true;
appealCount[_id]++;
// +10 XP for auditing the graveyard
_mintReputation(_appealer, entry.dialect, 10);
if (isGod || appealCount[_id] >= 2) {
// Overturn the rejection!
entry.isBurned = false;
entry.isVerified = true;
// Restore submitter's 50 XP + 10 XP penalty refund
_mintReputation(entry.contributor, entry.dialect, 60);
// Penalize the original rejectors -15 XP each
address[] memory rejectors = rejectorsList[_id];
for(uint i=0; i<rejectors.length; i++) {
address r = rejectors[i];
if (userXP[r][entry.dialect] >= 15) {
userXP[r][entry.dialect] -= 15;
} else {
userXP[r][entry.dialect] = 0;
}
_updateRank(r, entry.dialect);
}
emit EntryAppealed(_id, _appealer, true);
} else {
emit EntryAppealed(_id, _appealer, false);
}
}
function createBackup(string memory _dataKey, string memory _dataType, string memory _ipfsCid, string memory _description) public onlyRelayer {
DataSnapshot memory newSnap = DataSnapshot(block.timestamp, _dataType, _ipfsCid, _description);
backups[_dataKey].push(newSnap);
emit BackupCreated(_dataKey, _ipfsCid, block.timestamp);
}
// --- INTERNAL REPUTATION & RANKING LOGIC ---
function _mintReputation(address _user, string memory _dialect, uint256 _amount) internal {
userXP[_user][_dialect] += _amount;
emit ReputationMinted(_user, _dialect, _amount);
_updateRank(_user, _dialect);
}
function _updateRank(address _user, string memory _dialect) internal {
if (isAdmin[_user]) return;
uint256 score = userXP[_user][_dialect];
string memory newRole;
if (score >= 1500) { newRole = "Oracle"; }
else if (score >= 800) { newRole = "Archivist"; }
else if (score >= 300) { newRole = "Linguist"; }
else if (score >= 100) { newRole = "Scout"; }
else { newRole = "Initiate"; }
if (keccak256(abi.encodePacked(userRole[_user][_dialect])) != keccak256(abi.encodePacked(newRole))) {
userRole[_user][_dialect] = newRole;
emit RankUpgraded(_user, _dialect, newRole);
}
}
// --- MIGRATION QUERIES ---
function batchMigrateXP(address[] memory _users, string[] memory _dialects, uint256[] memory _xps) public onlyRelayer {
require(_users.length == _dialects.length && _users.length == _xps.length, "Array lengths mismatch");
for(uint i=0; i<_users.length; i++){
userXP[_users[i]][_dialects[i]] += _xps[i];
_updateRank(_users[i], _dialects[i]);
emit ReputationMinted(_users[i], _dialects[i], _xps[i]);
}
}
// --- HELPER QUERIES ---
function getRole(address _user, string memory _dialect) public view returns (string memory) {
if (isAdmin[_user]) return "Admin";
if (bytes(userRole[_user][_dialect]).length == 0) return "Initiate";
return userRole[_user][_dialect];
}
function getXP(address _user, string memory _dialect) public view returns (uint256) {
return userXP[_user][_dialect];
}
}
|