PurePolyglot / src /PureBi.sol
github-actions[bot]
Automated deployment to Hugging Face
a2b450c
Raw
History Blame Contribute Delete
10.5 kB
// 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];
}
}