Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
ba8cf88
Add first draft of InfractionCollector
theref May 15, 2024
293cde1
Replace reference to `adjudicator` in `TACoChildApplication` with `in…
theref May 15, 2024
3965b8d
Set Coordinator and TACoChild contracts to be public
theref May 24, 2024
a36c984
Add FAILING tests for infraction collector
theref May 27, 2024
60188bc
Get first InfractionCollector test passing
theref May 28, 2024
3624f86
Add genuine tests for InfractionCollector
theref May 30, 2024
b71f499
Add deployment script for InfractionCollector on lynx
theref Jun 5, 2024
3864018
Use interface for TACo Child Application in Infraction Collector
theref Jun 5, 2024
1f76754
Fix deployment typo
theref Jun 5, 2024
ef81c24
Add infraction deployment to child script
theref Jun 24, 2024
6c68878
Adding comment about transcript length
theref Jun 24, 2024
3960688
Add infraction type enum for granular punishment
theref Aug 8, 2024
783c917
Decouple TACoChildApplication and infraction collector
theref Aug 9, 2024
2a764ba
Move coordinator and taco application to contstructor
theref Jul 31, 2024
9f673a7
Fix bug with removing initialize
theref Aug 9, 2024
35bff3b
Make infractionCollector constructor more robust
theref Aug 9, 2024
7b43c21
Add InfractionReported event
theref Aug 9, 2024
a4974fc
Fix tests and contract constructor
theref Aug 9, 2024
2f52c15
Fix logic of tests to make code more efficient
theref Aug 9, 2024
9b71136
Fix yaml tab/spaces
theref Aug 9, 2024
3f2f133
Add new test where only half of nodes submit transcripts
theref Aug 9, 2024
911484f
Add PR suggestion on named mapping
theref Aug 13, 2024
866774f
Deploy InfractionCollector to Lynx
theref Aug 13, 2024
f23cad7
Deploy InfractionCollector to tapir
theref Aug 13, 2024
a455831
Add initialize function back into InfractionCollector
theref Aug 16, 2024
4310bd5
Remove duplicated IEncryptionAuthorizer
theref Aug 16, 2024
2ef8ff9
Apply PR suggestions
theref Aug 20, 2024
2f4e2af
Update Infraction tests to align with Coordinator after rebase
theref Aug 20, 2024
25dad92
Fix solhint errors
theref Aug 20, 2024
bd8b264
Add deployment script and params for InfractionCollector on mainnet
theref Aug 22, 2024
9fac617
Deploy InfractionCollector to mainnet
theref Aug 26, 2024
451464e
Fix outdated test_intraction after rebase
cygnusv Feb 20, 2026
93fc400
Define simple Periods contract to track period numbers
cygnusv Feb 20, 2026
bc097a8
Draft for PenaltyBoard contract
cygnusv Feb 20, 2026
de5bdca
Basic test showing naive integration of InfractionCollector and Penal…
cygnusv Feb 20, 2026
85f4f41
Appease solhint
cygnusv Feb 23, 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
67 changes: 67 additions & 0 deletions contracts/contracts/coordination/InfractionCollector.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// SPDX-License-Identifier: AGPL-3.0-or-later

pragma solidity ^0.8.0;

import "@openzeppelin-upgradeable/contracts/access/OwnableUpgradeable.sol";
import "./Coordinator.sol";
import "../../threshold/ITACoChildApplication.sol";

contract InfractionCollector is OwnableUpgradeable {
event InfractionReported(
uint32 indexed ritualId,
address indexed stakingProvider,
InfractionType infractionType
);
// infraction types
enum InfractionType {
MISSING_TRANSCRIPT
}
Coordinator public immutable coordinator;
// Reference to the TACoChildApplication contract
ITACoChildApplication public immutable tacoChildApplication;
// Mapping to keep track of reported infractions
mapping(uint32 ritualId => mapping(address stakingProvider => mapping(InfractionType => uint256)))
public infractionsForRitual;

constructor(Coordinator _coordinator) {
require(address(_coordinator) != address(0), "Contracts must be specified");
coordinator = _coordinator;
tacoChildApplication = coordinator.application();
_disableInitializers();
}

function initialize() external initializer {
__Ownable_init(msg.sender);
}

function reportMissingTranscript(uint32 ritualId, address[] calldata stakingProviders) public {
// Ritual must have failed
require(
coordinator.getRitualState(ritualId) == Coordinator.RitualState.DKG_TIMEOUT,
"Ritual must have failed"
);

for (uint256 i = 0; i < stakingProviders.length; i++) {
// Check if the infraction has already been reported
require(
infractionsForRitual[ritualId][stakingProviders[i]][
InfractionType.MISSING_TRANSCRIPT
] == 0,
"Infraction already reported"
);
Coordinator.Participant memory participant = coordinator.getParticipantFromProvider(
ritualId,
stakingProviders[i]
);
require(participant.transcript.length == 0, "Transcript is not missing");
infractionsForRitual[ritualId][stakingProviders[i]][
InfractionType.MISSING_TRANSCRIPT
] = 1;
emit InfractionReported(
ritualId,
stakingProviders[i],
InfractionType.MISSING_TRANSCRIPT
);
}
}
}
55 changes: 55 additions & 0 deletions contracts/contracts/coordination/PenaltyBoard.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// SPDX-License-Identifier: AGPL-3.0-or-later

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/access/AccessControl.sol";
import "./Periods.sol";

/**
* @title PenaltyBoard
* @notice Records which staking providers are penalized per period (period-oriented summary of infractions).
* Independent of InfractionCollector; may live on a different chain. An informer (trusted) sets
* the list of penalized providers for a given period.
*/
contract PenaltyBoard is Periods, AccessControl {
bytes32 public constant INFORMER_ROLE = keccak256("INFORMER_ROLE");

event PenalizedProvidersSet(uint256 indexed period, address[] providers);

mapping(uint256 period => address[]) private _penalizedProvidersByPeriod;

constructor(
uint256 genesisTime,
uint256 periodDuration,
address admin
) Periods(genesisTime, periodDuration) {
require(admin != address(0), "Admin required");
_grantRole(DEFAULT_ADMIN_ROLE, admin);
}

function getPenalizedProvidersForPeriod(
uint256 period
) external view returns (address[] memory) {
return _penalizedProvidersByPeriod[period];
}

/**
* @notice Set the list of penalized staking providers for a period.
* @param provs Staking provider addresses to record as penalized for the period.
* @param period Period index (must be current or previous period).
*/
function setPenalizedProvidersForPeriod(
address[] calldata provs,
uint256 period
) external onlyRole(INFORMER_ROLE) {
uint256 current = getCurrentPeriod();
require(period == current || (current > 0 && period == current - 1), "Invalid period");

delete _penalizedProvidersByPeriod[period];
for (uint256 i = 0; i < provs.length; i++) {
_penalizedProvidersByPeriod[period].push(provs[i]);
}

emit PenalizedProvidersSet(period, provs);
}
}
23 changes: 23 additions & 0 deletions contracts/contracts/coordination/Periods.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: AGPL-3.0-or-later

pragma solidity ^0.8.0;

contract Periods {
uint256 public immutable genesisTime;
uint256 public immutable periodDuration;

constructor(uint256 _genesisTime, uint256 _periodDuration) {
require(_periodDuration > 0, "Invalid period duration");
genesisTime = _genesisTime;
periodDuration = _periodDuration;
}

function getPeriodForTimestamp(uint256 timestamp) public view returns (uint256) {
require(timestamp >= genesisTime, "Timestamp is before genesis");
return (timestamp - genesisTime) / periodDuration;
}

function getCurrentPeriod() public view returns (uint256) {
return getPeriodForTimestamp(block.timestamp);
}
}
1 change: 0 additions & 1 deletion contracts/contracts/testnet/OpenAccessAuthorizer.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// SPDX-License-Identifier: AGPL-3.0-or-later

pragma solidity ^0.8.0;

import "../coordination/IEncryptionAuthorizer.sol";
Expand Down
239 changes: 239 additions & 0 deletions deployment/artifacts/lynx.json
Original file line number Diff line number Diff line change
Expand Up @@ -6178,6 +6178,245 @@
"block_number": 9101909,
"deployer": "0x3B42d26E19FF860bC4dEbB920DD8caA53F93c600"
},
"InfractionCollector": {
"address": "0xad8dADaB38eC94B8fe3c482f7550044201506369",
"abi": [
{
"type": "constructor",
"stateMutability": "nonpayable",
"inputs": [
{
"name": "_coordinator",
"type": "address",
"components": null,
"internal_type": "contract Coordinator"
},
{
"name": "_tacoChildApplication",
"type": "address",
"components": null,
"internal_type": "contract ITACoChildApplication"
}
]
},
{
"type": "error",
"name": "InvalidInitialization",
"inputs": []
},
{
"type": "error",
"name": "NotInitializing",
"inputs": []
},
{
"type": "error",
"name": "OwnableInvalidOwner",
"inputs": [
{
"name": "owner",
"type": "address",
"components": null,
"internal_type": "address"
}
]
},
{
"type": "error",
"name": "OwnableUnauthorizedAccount",
"inputs": [
{
"name": "account",
"type": "address",
"components": null,
"internal_type": "address"
}
]
},
{
"type": "event",
"name": "InfractionReported",
"inputs": [
{
"name": "ritualId",
"type": "uint32",
"components": null,
"internal_type": "uint32",
"indexed": true
},
{
"name": "stakingProvider",
"type": "address",
"components": null,
"internal_type": "address",
"indexed": true
},
{
"name": "infractionType",
"type": "uint8",
"components": null,
"internal_type": "enum InfractionCollector.InfractionType",
"indexed": false
}
],
"anonymous": false
},
{
"type": "event",
"name": "Initialized",
"inputs": [
{
"name": "version",
"type": "uint64",
"components": null,
"internal_type": "uint64",
"indexed": false
}
],
"anonymous": false
},
{
"type": "event",
"name": "OwnershipTransferred",
"inputs": [
{
"name": "previousOwner",
"type": "address",
"components": null,
"internal_type": "address",
"indexed": true
},
{
"name": "newOwner",
"type": "address",
"components": null,
"internal_type": "address",
"indexed": true
}
],
"anonymous": false
},
{
"type": "function",
"name": "coordinator",
"stateMutability": "view",
"inputs": [],
"outputs": [
{
"name": "",
"type": "address",
"components": null,
"internal_type": "contract Coordinator"
}
]
},
{
"type": "function",
"name": "infractions",
"stateMutability": "view",
"inputs": [
{
"name": "ritualId",
"type": "uint32",
"components": null,
"internal_type": "uint32"
},
{
"name": "stakingProvider",
"type": "address",
"components": null,
"internal_type": "address"
},
{
"name": "",
"type": "uint8",
"components": null,
"internal_type": "enum InfractionCollector.InfractionType"
}
],
"outputs": [
{
"name": "",
"type": "bool",
"components": null,
"internal_type": "bool"
}
]
},
{
"type": "function",
"name": "owner",
"stateMutability": "view",
"inputs": [],
"outputs": [
{
"name": "",
"type": "address",
"components": null,
"internal_type": "address"
}
]
},
{
"type": "function",
"name": "renounceOwnership",
"stateMutability": "nonpayable",
"inputs": [],
"outputs": []
},
{
"type": "function",
"name": "reportMissingTranscript",
"stateMutability": "nonpayable",
"inputs": [
{
"name": "ritualId",
"type": "uint32",
"components": null,
"internal_type": "uint32"
},
{
"name": "stakingProviders",
"type": "address[]",
"components": null,
"internal_type": "address[]"
}
],
"outputs": []
},
{
"type": "function",
"name": "tacoChildApplication",
"stateMutability": "view",
"inputs": [],
"outputs": [
{
"name": "",
"type": "address",
"components": null,
"internal_type": "contract ITACoChildApplication"
}
]
},
{
"type": "function",
"name": "transferOwnership",
"stateMutability": "nonpayable",
"inputs": [
{
"name": "newOwner",
"type": "address",
"components": null,
"internal_type": "address"
}
],
"outputs": []
}
],
"tx_hash": "0x36762db270b4706dfe829502e5b6469f783acb4bc74e9d21908ecdc88f150629",
"block_number": 10661923,
"deployer": "0x3B42d26E19FF860bC4dEbB920DD8caA53F93c600"
},
"LynxRitualToken": {
"address": "0x064Be2a9740e565729BC0d47bC616c5bb8Cc87B9",
"abi": [
Expand Down
Loading
Loading