Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
b34870f
feat(events): add canonical versioned event interface
Adedoyinjr Jul 28, 2026
15a535e
docs(events): define indexing and versioning standard
Adedoyinjr Jul 28, 2026
83924c0
fix(events): keep schema version interface-compatible
Adedoyinjr Jul 28, 2026
f81f744
fix(build): resolve compile blockers
Adedoyinjr Jul 28, 2026
1b3bbd2
test(events): add event architecture harness
Adedoyinjr Jul 28, 2026
b711ef4
test(events): cover indexed fields and schema version
Adedoyinjr Jul 28, 2026
550a9c2
docs(events): add event gas benchmark methodology
Adedoyinjr Jul 28, 2026
a662456
docs(events): add consumer compatibility checklist
Adedoyinjr Jul 28, 2026
17ec7fa
docs(events): catalog schema v1 event surface
Adedoyinjr Jul 28, 2026
fb5b242
docs(events): define deterministic log ordering
Adedoyinjr Jul 28, 2026
59274d4
feat(events): integrate versioned protocol events
Adedoyinjr Jul 28, 2026
85f7a6e
feat(events): emit canonical slashing events
Adedoyinjr Jul 29, 2026
65fbb79
feat(events): emit canonical reward calculation event
Adedoyinjr Jul 29, 2026
9f4f91a
feat(events): emit canonical governance events
Adedoyinjr Jul 29, 2026
2e6e290
feat(events): emit canonical emergency lifecycle events
Adedoyinjr Jul 29, 2026
f8089b1
merge upstream main and resolve event architecture conflict
Adedoyinjr Jul 29, 2026
9e63c86
chore: merge upstream main and preserve SC-022 changes
Adedoyinjr Jul 30, 2026
ab5dad7
chore: run one-time SC-022 merge repair
Adedoyinjr Jul 30, 2026
6d8710b
chore: trigger SC-022 repair
Adedoyinjr Jul 30, 2026
638abd1
chore: trigger SC-022 repair
Adedoyinjr Jul 30, 2026
f2c7b0b
fix: make SC-022 repair workflow push before validation
Adedoyinjr Jul 30, 2026
65ac6e0
fix: complete upstream merge repairs [sc022-repair]
github-actions[bot] Jul 30, 2026
be2d79b
chore: trigger repaired SC-022 workflow
Adedoyinjr Jul 30, 2026
9b41e86
chore: update SC-022 repair workflow for remaining compile errors
Adedoyinjr Jul 30, 2026
6eb7997
fix: resolve remaining merge compile errors [sc022-repair]
github-actions[bot] Jul 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions .github/workflows/fix-sc022-merge.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
name: One-time SC-022 merge repair

on:
push:
branches:
- feature/sc-022-event-architecture

permissions:
contents: write

jobs:
repair:
if: ${{ !contains(github.event.head_commit.message, '[sc022-repair]') }}
runs-on: ubuntu-latest
steps:
- name: Checkout branch
uses: actions/checkout@v4
with:
ref: feature/sc-022-event-architecture
fetch-depth: 0

- name: Apply deterministic repairs
shell: bash
run: |
python3 - <<'PY'
from pathlib import Path

treasury = Path('contracts/treasury/TreasuryAccounting.sol')
text = treasury.read_text()

old_enum = ''' ECOSYSTEM_FUND, // Future ecosystem development fund
SLASHED_TREASURY // Tokens slashed from verifiers
}'''
new_enum = ''' ECOSYSTEM_FUND, // Future ecosystem development fund
SLASHED_TREASURY, // Tokens slashed from verifiers
EXTERNAL // Off-ledger source or destination marker
}'''

if 'TreasuryAccount(99)' in text:
if old_enum not in text:
raise SystemExit('Could not locate TreasuryAccount enum for EXTERNAL marker')
text = text.replace(old_enum, new_enum, 1)
text = text.replace('TreasuryAccount(99)', 'TreasuryAccount.EXTERNAL')
treasury.write_text(text)
elif 'EXTERNAL' not in text:
raise SystemExit('Treasury external marker is neither patched nor patchable')

registry = Path('contracts/upgrade/VersionRegistry.sol')
text = registry.read_text()
old = 'function getVersion(string calldata contractName, string calldata semanticVersion)'
new = 'function getVersion(string memory contractName, string memory semanticVersion)'
if old in text:
registry.write_text(text.replace(old, new, 1))
elif new not in text:
raise SystemExit('Could not locate VersionRegistry.getVersion signature')
PY

- name: Commit and push repairs
shell: bash
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add contracts/treasury/TreasuryAccounting.sol contracts/upgrade/VersionRegistry.sol
if git diff --cached --quiet; then
echo "No source changes required"
else
git commit -m "fix: resolve remaining merge compile errors [sc022-repair]"
git push origin HEAD:feature/sc-022-event-architecture
fi

- name: Install dependencies
run: npm ci

- name: Compile
id: compile
continue-on-error: true
shell: bash
run: |
set -o pipefail
npx hardhat compile --force 2>&1 | tee compile-output.txt

- name: Upload compiler output
if: always()
uses: actions/upload-artifact@v4
with:
name: sc022-compile-output
path: compile-output.txt
if-no-files-found: ignore

- name: Report compile failure
if: steps.compile.outcome == 'failure'
run: exit 1
118 changes: 118 additions & 0 deletions compile-output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
Warning: This declaration has the same name as another declaration.
--> contracts/TruthBountyWeighted.sol:548:9:
|
548 | Vote storage vote = votes[claimId][msg.sender];
| ^^^^^^^^^^^^^^^^^
Note: The other declaration is here:
--> contracts/TruthBountyWeighted.sol:379:5:
|
379 | function vote(
| ^ (Relevant source part starts here and spans across multiple lines).


Warning: This declaration has the same name as another declaration.
--> contracts/TruthBountyWeighted.sol:631:9:
|
631 | Vote storage vote = votes[claimId][msg.sender];
| ^^^^^^^^^^^^^^^^^
Note: The other declaration is here:
--> contracts/TruthBountyWeighted.sol:379:5:
|
379 | function vote(
| ^ (Relevant source part starts here and spans across multiple lines).


Warning: This declaration has the same name as another declaration.
--> contracts/TruthBountyWeighted.sol:702:9:
|
702 | VerifierStake storage stake = verifierStakes[msg.sender];
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
Note: The other declaration is here:
--> contracts/TruthBountyWeighted.sol:354:5:
|
354 | function stake(uint256 amount) external nonReentrant whenNotPaused {
| ^ (Relevant source part starts here and spans across multiple lines).


Warning: This declaration has the same name as another declaration.
--> contracts/TruthBountyWeighted.sol:975:13:
|
975 | Vote storage vote = votes[claimId][voters[i]];
| ^^^^^^^^^^^^^^^^^
Note: The other declaration is here:
--> contracts/TruthBountyWeighted.sol:379:5:
|
379 | function vote(
| ^ (Relevant source part starts here and spans across multiple lines).


Warning: This declaration has the same name as another declaration.
--> contracts/TruthBountyWeighted.sol:1006:13:
|
1006 | Vote storage vote = votes[claimId][voter];
| ^^^^^^^^^^^^^^^^^
Note: The other declaration is here:
--> contracts/TruthBountyWeighted.sol:379:5:
|
379 | function vote(
| ^ (Relevant source part starts here and spans across multiple lines).


Warning: This declaration shadows an existing declaration.
--> contracts/TruthBountyWeighted.sol:1001:25:
|
1001 | ) internal returns (uint256 totalSlashed) {
| ^^^^^^^^^^^^^^^^^^^^
Note: The shadowed declaration is here:
--> contracts/TruthBountyWeighted.sol:211:5:
|
211 | uint256 public totalSlashed;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^


TypeError: Function has override specified but does not override anything.
--> contracts/TruthBounty.sol:281:44:
|
281 | function _resolverRole() internal pure override returns (bytes32) {
| ^^^^^^^^


TypeError: Invalid contract specified in override list: "ResolverRoleTimelock".
--> contracts/TruthBounty.sol:289:62:
|
289 | function grantRole(bytes32 role, address account) public override(AccessControl, ResolverRoleTimelock) {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Note: This contract:
--> contracts/utils/ResolverRoleTimelock.sol:12:1:
|
12 | abstract contract ResolverRoleTimelock is AccessControl {
| ^ (Relevant source part starts here and spans across multiple lines).


TypeError: Cannot call function via contract type name.
--> contracts/TruthBounty.sol:290:9:
|
290 | ResolverRoleTimelock.grantRole(role, account);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^


TypeError: Member "minStakeAmount" not found or not visible after argument-dependent lookup in struct IEconomicSimulation.SimulationConfig memory.
--> contracts/simulation/EconomicSimulation.sol:253:44:
|
253 | uint256 totalStakedEnd = stakers * config.minStakeAmount;
| ^^^^^^^^^^^^^^^^^^^^^


Error HH600: Compilation failed

HardhatError: HH600: Compilation failed
at SimpleTaskDefinition.action (/workspaces/truthbounty-contract/node_modules/hardhat/src/builtin-tasks/compile.ts:504:15)
at processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Environment._runTaskDefinition (/workspaces/truthbounty-contract/node_modules/hardhat/src/internal/core/runtime-environment.ts:351:14)
at async OverriddenTaskDefinition._action (/workspaces/truthbounty-contract/node_modules/@typechain/hardhat/src/index.ts:28:30)
at async Environment._runTaskDefinition (/workspaces/truthbounty-contract/node_modules/hardhat/src/internal/core/runtime-environment.ts:351:14)
at async Environment.run (/workspaces/truthbounty-contract/node_modules/hardhat/src/internal/core/runtime-environment.ts:184:14)
at async SimpleTaskDefinition.action (/workspaces/truthbounty-contract/node_modules/hardhat/src/builtin-tasks/compile.ts:1433:63)
at async Environment._runTaskDefinition (/workspaces/truthbounty-contract/node_modules/hardhat/src/internal/core/runtime-environment.ts:351:14)
at async Environment._runTaskDefinition (/workspaces/truthbounty-contract/node_modules/hardhat/src/internal/core/runtime-environment.ts:351:14)
at async Environment.run (/workspaces/truthbounty-contract/node_modules/hardhat/src/internal/core/runtime-environment.ts:184:14)
67 changes: 24 additions & 43 deletions contracts/TruthBounty.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "./utils/ResolverRoleTimelock.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
Expand All @@ -12,6 +11,7 @@ import "@openzeppelin/contracts/utils/math/Math.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";
import "./governance/GovernanceOwnable.sol";
import "./interfaces/ITruthBountyEvents.sol";
import "./governance/GovernanceHooks.sol";

/**
Expand Down Expand Up @@ -149,7 +149,8 @@ contract TruthBountyToken is ERC20, ResolverRoleTimelock, Initializable, UUPSUpg
* unchanged.
* ────────────────────────────────────────────────────────────────────────────
*/
contract TruthBounty is AccessControl, ReentrancyGuard, Pausable, GovernanceOwnable {
contract TruthBounty is AccessControl, ReentrancyGuard, Pausable, GovernanceOwnable, ITruthBountyEvents {
uint16 public constant EVENT_SCHEMA_VERSION = 1;

// ── Roles ──────────────────────────────────────────────────────────────

Expand Down Expand Up @@ -268,19 +269,18 @@ contract TruthBounty is AccessControl, ReentrancyGuard, Pausable, GovernanceOwna

_setRoleAdmin(RESOLVER_ROLE, ADMIN_ROLE);
_setRoleAdmin(TREASURY_ROLE, ADMIN_ROLE);
_setRoleAdmin(PAUSER_ROLE, ADMIN_ROLE);
_setRoleAdmin(PAUSER_ROLE, ADMIN_ROLE);

function _resolverRole() internal pure override returns (bytes32) {
return RESOLVER_ROLE;
_initializeGovernance(
_governanceController,
initialAdmin,
initialAdmin
);
}

function _percentOf(uint256 value, uint256 percent) internal pure returns (uint256) {
return Math.mulDiv(value, percent, 100);
}

function grantRole(bytes32 role, address account) public override(AccessControl, ResolverRoleTimelock) {
ResolverRoleTimelock.grantRole(role, account);
}
return Math.mulDiv(value, percent, 100);
}

// ── Core Functions ─────────────────────────────────────────────────────

Expand Down Expand Up @@ -422,10 +422,8 @@ contract TruthBounty is AccessControl, ReentrancyGuard, Pausable, GovernanceOwna
require(!isWinner, "Winners should use claimSettlementRewards");

// Calculate slashed portion
uint256 slashedAmount = _percentOf(vote.stakeAmount, slashPercent);
uint256 returnAmount = vote.stakeAmount - slashedAmount;

vote.stakeReturned = true;
uint256 slashedAmount = _percentOf(v.stakeAmount, slashPercent);
uint256 returnAmount = v.stakeAmount - slashedAmount;

v.stakeReturned = true;
verifierStakes[msg.sender].activeStakes -= v.stakeAmount;
Expand All @@ -446,32 +444,6 @@ contract TruthBounty is AccessControl, ReentrancyGuard, Pausable, GovernanceOwna

// ── Internal Helpers ───────────────────────────────────────────────────

function _determineOutcome(uint256 stakedFor, uint256 stakedAgainst) internal view returns (bool) {
uint256 total = stakedFor + stakedAgainst;
if (total == 0) return false;
return (stakedFor * 100) / total >= settlementThresholdPercent;
}

function _calculateSettlement(uint256 claimId, bool passed) internal returns (uint256 rewardAmount, uint256 slashedAmount) {
Claim storage claim = claims[claimId];
uint256 winnerStake = passed ? claim.totalStakedFor : claim.totalStakedAgainst;
uint256 loserStake = passed ? claim.totalStakedAgainst : claim.totalStakedFor;

slashedAmount = (loserStake * slashPercent) / 100;
rewardAmount = (slashedAmount * rewardPercent) / 100;

totalSlashed += slashedAmount;
totalRewarded += rewardAmount;

settlementResults[claimId] = SettlementResult({
passed: passed,
totalRewards: rewardAmount,
totalSlashed: slashedAmount,
winnerStake: winnerStake,
loserStake: loserStake
});
}

// ── View Functions ─────────────────────────────────────────────────────

function getClaim(uint256 claimId) external view returns (Claim memory) { return claims[claimId]; }
Expand Down Expand Up @@ -513,6 +485,15 @@ contract TruthBounty is AccessControl, ReentrancyGuard, Pausable, GovernanceOwna

// ── Pauser ─────────────────────────────────────────────────────────────

function pause() external onlyRole(PAUSER_ROLE) { _pause(); }
function unpause() external onlyRole(PAUSER_ROLE) { _unpause(); }
function pause() external onlyRole(PAUSER_ROLE) { _pause(); emit EmergencyPauseActivatedV1(
msg.sender,
keccak256("MANUAL_PAUSE"),
uint64(block.timestamp),
EVENT_SCHEMA_VERSION
); }
function unpause() external onlyRole(PAUSER_ROLE) { _unpause(); emit EmergencyPauseRecoveredV1(
msg.sender,
uint64(block.timestamp),
EVENT_SCHEMA_VERSION
); }
}
Loading