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];
    }
}