From ecb747cb12fc4d6f1e8e2bf6ffc24908b0081511 Mon Sep 17 00:00:00 2001 From: James Campbell Date: Wed, 15 May 2024 14:29:47 +0200 Subject: [PATCH 01/31] Add first draft of InfractionCollector Fixes #266 --- .../coordination/InfractionCollector.sol | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 contracts/contracts/coordination/InfractionCollector.sol diff --git a/contracts/contracts/coordination/InfractionCollector.sol b/contracts/contracts/coordination/InfractionCollector.sol new file mode 100644 index 000000000..1c1d7e0d7 --- /dev/null +++ b/contracts/contracts/coordination/InfractionCollector.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin-upgradeable/contracts/access/OwnableUpgradeable.sol"; +import "./Coordinator.sol"; +import "./TACoChildApplication.sol"; + +contract InfractionCollector is OwnableUpgradeable { + // Reference to the Coordinator contract + Coordinator private coordinator; + + // Reference to the TACoChildApplication contract + TACoChildApplication private tacoChildApplication; + + // Mapping to keep track of reported infractions + mapping(bytes32 => mapping(address => bool)) private infractions; + + function initialize(Coordinator _coordinator, TACoChildApplication _tacoChildApplication) public initializer { + __Ownable_init(); + + coordinator = _coordinator; + tacoChildApplication = _tacoChildApplication; + } + + function reportMissingTranscript(bytes32 ritualId, address[] calldata stakingProviders) external { + // 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(!infractions[ritualId][stakingProviders[i]], "Infraction already reported"); + participant = coordinator.getParticipantFromProvider(ritualId, stakingProviders[i]); + if (participant.transcript.length == 0) { + // Penalize the staking provider + tacoChildApplication.penalize(stakingProviders[i]); + infractions[ritualId][stakingProviders[i]] = true; + } + } + } +} \ No newline at end of file From 9e7e0e971cd1e592728bcacefcc5038feed3d105 Mon Sep 17 00:00:00 2001 From: James Campbell Date: Wed, 15 May 2024 14:34:40 +0200 Subject: [PATCH 02/31] Replace reference to `adjudicator` in `TACoChildApplication` with `infractionCollector` Fix formatting errors and tests Include Participant struct Fix types Fix test reversion message --- .../coordination/InfractionCollector.sol | 28 +++++++++++++------ .../coordination/TACoChildApplication.sol | 18 ++++++++---- tests/test_child_application.py | 2 +- 3 files changed, 32 insertions(+), 16 deletions(-) diff --git a/contracts/contracts/coordination/InfractionCollector.sol b/contracts/contracts/coordination/InfractionCollector.sol index 1c1d7e0d7..6c9a43afb 100644 --- a/contracts/contracts/coordination/InfractionCollector.sol +++ b/contracts/contracts/coordination/InfractionCollector.sol @@ -6,30 +6,40 @@ import "./Coordinator.sol"; import "./TACoChildApplication.sol"; contract InfractionCollector is OwnableUpgradeable { - // Reference to the Coordinator contract Coordinator private coordinator; // Reference to the TACoChildApplication contract TACoChildApplication private tacoChildApplication; // Mapping to keep track of reported infractions - mapping(bytes32 => mapping(address => bool)) private infractions; - - function initialize(Coordinator _coordinator, TACoChildApplication _tacoChildApplication) public initializer { - __Ownable_init(); + mapping(uint32 => mapping(address => bool)) private infractions; + function initialize( + Coordinator _coordinator, + TACoChildApplication _tacoChildApplication + ) public initializer { + __Ownable_init(msg.sender); coordinator = _coordinator; tacoChildApplication = _tacoChildApplication; } - function reportMissingTranscript(bytes32 ritualId, address[] calldata stakingProviders) external { + function reportMissingTranscript( + uint32 ritualId, + address[] calldata stakingProviders + ) external { // Ritual must have failed - require(coordinator.getRitualState(ritualId) == Coordinator.RitualState.DKG_TIMEOUT, "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(!infractions[ritualId][stakingProviders[i]], "Infraction already reported"); - participant = coordinator.getParticipantFromProvider(ritualId, stakingProviders[i]); + Coordinator.Participant memory participant = coordinator.getParticipantFromProvider( + ritualId, + stakingProviders[i] + ); if (participant.transcript.length == 0) { // Penalize the staking provider tacoChildApplication.penalize(stakingProviders[i]); @@ -37,4 +47,4 @@ contract InfractionCollector is OwnableUpgradeable { } } } -} \ No newline at end of file +} diff --git a/contracts/contracts/coordination/TACoChildApplication.sol b/contracts/contracts/coordination/TACoChildApplication.sol index e829ddd03..d4f699a2c 100644 --- a/contracts/contracts/coordination/TACoChildApplication.sol +++ b/contracts/contracts/coordination/TACoChildApplication.sol @@ -31,7 +31,7 @@ contract TACoChildApplication is ITACoRootToChild, ITACoChildApplication, Initia ITACoChildToRoot public immutable rootApplication; address public coordinator; - address public adjudicator; + address public infractionCollector; uint96 public immutable minimumAuthorization; @@ -61,10 +61,13 @@ contract TACoChildApplication is ITACoRootToChild, ITACoChildApplication, Initia /** * @notice Initialize function for using with OpenZeppelin proxy */ - function initialize(address _coordinator, address _adjudicator) external initializer { - require(coordinator == address(0) || adjudicator == address(0), "Contracts already set"); + function initialize(address _coordinator, address _infractionCollector) external initializer { require( - _coordinator != address(0) && _adjudicator != address(0), + coordinator == address(0) || infractionCollector == address(0), + "Contracts already set" + ); + require( + _coordinator != address(0) && _infractionCollector != address(0), "Contracts must be specified" ); require( @@ -72,7 +75,7 @@ contract TACoChildApplication is ITACoRootToChild, ITACoChildApplication, Initia "Invalid coordinator" ); coordinator = _coordinator; - adjudicator = _adjudicator; + infractionCollector = _infractionCollector; } function authorizedStake(address _stakingProvider) external view returns (uint96) { @@ -200,7 +203,10 @@ contract TACoChildApplication is ITACoRootToChild, ITACoChildApplication, Initia * @param _stakingProvider Staking provider address */ function penalize(address _stakingProvider) external override { - require(msg.sender == address(adjudicator), "Only adjudicator allowed to penalize"); + require( + msg.sender == address(infractionCollector), + "Only infractionCollector allowed to penalize" + ); rootApplication.penalize(_stakingProvider); emit Penalized(_stakingProvider); } diff --git a/tests/test_child_application.py b/tests/test_child_application.py index f5e52b20f..8194fe3ca 100644 --- a/tests/test_child_application.py +++ b/tests/test_child_application.py @@ -331,7 +331,7 @@ def test_penalize(accounts, root_application, child_application, coordinator): ) = accounts[0:] # Penalize can be done only from adjudicator address - with ape.reverts("Only adjudicator allowed to penalize"): + with ape.reverts("Only infractionCollector allowed to penalize"): child_application.penalize(staking_provider, sender=staking_provider) tx = child_application.penalize(staking_provider, sender=creator) From db0d7cec6f1f500ffe88f3dfb3e825f19da3e947 Mon Sep 17 00:00:00 2001 From: James Campbell Date: Fri, 24 May 2024 13:21:22 +0200 Subject: [PATCH 03/31] Set Coordinator and TACoChild contracts to be public MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: David Núñez --- contracts/contracts/coordination/InfractionCollector.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/contracts/coordination/InfractionCollector.sol b/contracts/contracts/coordination/InfractionCollector.sol index 6c9a43afb..2c4125a33 100644 --- a/contracts/contracts/coordination/InfractionCollector.sol +++ b/contracts/contracts/coordination/InfractionCollector.sol @@ -6,10 +6,10 @@ import "./Coordinator.sol"; import "./TACoChildApplication.sol"; contract InfractionCollector is OwnableUpgradeable { - Coordinator private coordinator; + Coordinator public coordinator; // Reference to the TACoChildApplication contract - TACoChildApplication private tacoChildApplication; + TACoChildApplication public tacoChildApplication; // Mapping to keep track of reported infractions mapping(uint32 => mapping(address => bool)) private infractions; From bca6b8aa20a5c3cb76c10e6584b1f0d28cafbb4c Mon Sep 17 00:00:00 2001 From: James Campbell Date: Mon, 27 May 2024 15:53:14 +0200 Subject: [PATCH 04/31] Add FAILING tests for infraction collector --- .../coordination/InfractionCollector.sol | 3 +- tests/test_infraction.py | 174 ++++++++++++++++++ 2 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 tests/test_infraction.py diff --git a/contracts/contracts/coordination/InfractionCollector.sol b/contracts/contracts/coordination/InfractionCollector.sol index 2c4125a33..c9d85c4df 100644 --- a/contracts/contracts/coordination/InfractionCollector.sol +++ b/contracts/contracts/coordination/InfractionCollector.sol @@ -1,4 +1,5 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: AGPL-3.0-or-later + pragma solidity ^0.8.0; import "@openzeppelin-upgradeable/contracts/access/OwnableUpgradeable.sol"; diff --git a/tests/test_infraction.py b/tests/test_infraction.py new file mode 100644 index 000000000..ac6d5eb58 --- /dev/null +++ b/tests/test_infraction.py @@ -0,0 +1,174 @@ +import os +from enum import IntEnum + +import ape +import pytest +from eth_account import Account +from eth_account.messages import encode_defunct +from hexbytes import HexBytes +from web3 import Web3 + +TIMEOUT = 1000 +MAX_DKG_SIZE = 31 +FEE_RATE = 42 +ERC20_SUPPLY = 10**24 +DURATION = 48 * 60 * 60 +ONE_DAY = 24 * 60 * 60 + +RitualState = IntEnum( + "RitualState", + [ + "NON_INITIATED", + "DKG_AWAITING_TRANSCRIPTS", + "DKG_AWAITING_AGGREGATIONS", + "DKG_TIMEOUT", + "DKG_INVALID", + "ACTIVE", + "EXPIRED", + ], + start=0, +) + + +# This formula returns an approximated size +# To have a representative size, create transcripts with `nucypher-core` +def transcript_size(shares, threshold): + return int(424 + 240 * (shares / 2) + 50 * (threshold)) + + +def gen_public_key(): + return (os.urandom(32), os.urandom(32), os.urandom(32)) + + +def access_control_error_message(address, role=None): + role = role or b"\x00" * 32 + return f"account={address}, neededRole={role}" + + +@pytest.fixture(scope="module") +def nodes(accounts): + return sorted(accounts[:MAX_DKG_SIZE], key=lambda x: x.address.lower()) + + +@pytest.fixture(scope="module") +def initiator(accounts): + initiator_index = MAX_DKG_SIZE + 1 + assert len(accounts) >= initiator_index + return accounts[initiator_index] + + +@pytest.fixture(scope="module") +def deployer(accounts): + deployer_index = MAX_DKG_SIZE + 2 + assert len(accounts) >= deployer_index + return accounts[deployer_index] + + +@pytest.fixture(scope="module") +def treasury(accounts): + treasury_index = MAX_DKG_SIZE + 3 + assert len(accounts) >= treasury_index + return accounts[treasury_index] + + +@pytest.fixture() +def application(project, deployer, nodes): + contract = project.ChildApplicationForCoordinatorMock.deploy(sender=deployer) + for n in nodes: + contract.updateOperator(n, n, sender=deployer) + contract.updateAuthorization(n, 42, sender=deployer) + return contract + + +@pytest.fixture() +def erc20(project, initiator): + token = project.TestToken.deploy(ERC20_SUPPLY, sender=initiator) + return token + + +@pytest.fixture() +def coordinator(project, deployer, application, erc20, initiator, oz_dependency): + admin = deployer + contract = project.Coordinator.deploy( + application.address, + erc20.address, + FEE_RATE, + sender=deployer, + ) + + encoded_initializer_function = contract.initialize.encode_input(TIMEOUT, MAX_DKG_SIZE, admin) + proxy = oz_dependency.TransparentUpgradeableProxy.deploy( + contract.address, + deployer, + encoded_initializer_function, + sender=deployer, + ) + proxy_contract = project.Coordinator.at(proxy.address) + + proxy_contract.grantRole(contract.INITIATOR_ROLE(), initiator, sender=admin) + return proxy_contract + + +@pytest.fixture() +def global_allow_list(project, deployer, coordinator): + contract = project.GlobalAllowList.deploy(coordinator.address, sender=deployer) + return contract + + +@pytest.fixture +def infraction_collector(project, deployer, coordinator, application, oz_dependency): + contract = project.InfractionCollector.deploy(sender=deployer) + encoded_initializer_function = contract.initialize.encode_input(coordinator.address, application.address) + proxy = oz_dependency.TransparentUpgradeableProxy.deploy( + contract.address, + deployer, + encoded_initializer_function, + sender=deployer, + ) + proxy_contract = project.InfractionCollector.at(proxy.address) + return proxy_contract + +def test_report_missing_transcript(nodes, initiator, global_allow_list, infraction_collector, coordinator, accounts): + ritual_id = 1 + staking_providers = [accounts[0], accounts[1]] + for node in nodes: + public_key = gen_public_key() + coordinator.setProviderPublicKey(public_key, sender=node) + coordinator.initiateRitual( + nodes, initiator, DURATION, global_allow_list.address, sender=initiator + ) + + infraction_collector.reportMissingTranscript(ritual_id, staking_providers, sender=accounts[0]) + + for provider in staking_providers: + assert application.penalized(provider) == True + +def test_report_missing_transcript_already_reported(nodes, initiator, global_allow_list, infraction_collector, coordinator, accounts): + ritual_id = 1 + staking_providers = [accounts[0], accounts[1]] + for node in nodes: + public_key = gen_public_key() + coordinator.setProviderPublicKey(public_key, sender=node) + coordinator.initiateRitual( + nodes, initiator, DURATION, global_allow_list.address, sender=initiator + ) + coordinator.timeoutRitual(ritual_id, sender=accounts[0]) + + infraction_collector.reportMissingTranscript(ritual_id, staking_providers, sender=accounts[0]) + + with ape.reverts("Infraction already reported"): + infraction_collector.reportMissingTranscript(ritual_id, staking_providers, sender=accounts[0]) + +def test_report_missing_transcript_ritual_not_failed(nodes, initiator, global_allow_list, infraction_collector, coordinator, accounts): + ritual_id = 1 + staking_providers = [accounts[0], accounts[1]] + for node in nodes: + public_key = gen_public_key() + coordinator.setProviderPublicKey(public_key, sender=node) + coordinator.initiateRitual( + nodes, initiator, DURATION, global_allow_list.address, sender=initiator + ) + + with ape.reverts("Ritual must have failed"): + infraction_collector.reportMissingTranscript(ritual_id, staking_providers, sender=accounts[0]) + From 94b69efe994866d9a212f89562b5d42426b7e3e1 Mon Sep 17 00:00:00 2001 From: James Campbell Date: Tue, 28 May 2024 17:11:03 +0200 Subject: [PATCH 05/31] Get first InfractionCollector test passing --- .../coordination/InfractionCollector.sol | 2 +- tests/test_infraction.py | 72 ++++++++++--------- 2 files changed, 38 insertions(+), 36 deletions(-) diff --git a/contracts/contracts/coordination/InfractionCollector.sol b/contracts/contracts/coordination/InfractionCollector.sol index c9d85c4df..47725ba42 100644 --- a/contracts/contracts/coordination/InfractionCollector.sol +++ b/contracts/contracts/coordination/InfractionCollector.sol @@ -13,7 +13,7 @@ contract InfractionCollector is OwnableUpgradeable { TACoChildApplication public tacoChildApplication; // Mapping to keep track of reported infractions - mapping(uint32 => mapping(address => bool)) private infractions; + mapping(uint32 => mapping(address => bool)) public infractions; function initialize( Coordinator _coordinator, diff --git a/tests/test_infraction.py b/tests/test_infraction.py index ac6d5eb58..db1641629 100644 --- a/tests/test_infraction.py +++ b/tests/test_infraction.py @@ -128,47 +128,49 @@ def infraction_collector(project, deployer, coordinator, application, oz_depende proxy_contract = project.InfractionCollector.at(proxy.address) return proxy_contract -def test_report_missing_transcript(nodes, initiator, global_allow_list, infraction_collector, coordinator, accounts): - ritual_id = 1 +def test_report_missing_transcript(erc20, nodes, initiator, global_allow_list, infraction_collector, coordinator, accounts): + ritual_id = 0 staking_providers = [accounts[0], accounts[1]] for node in nodes: public_key = gen_public_key() coordinator.setProviderPublicKey(public_key, sender=node) - coordinator.initiateRitual( - nodes, initiator, DURATION, global_allow_list.address, sender=initiator - ) - - infraction_collector.reportMissingTranscript(ritual_id, staking_providers, sender=accounts[0]) - - for provider in staking_providers: - assert application.penalized(provider) == True - -def test_report_missing_transcript_already_reported(nodes, initiator, global_allow_list, infraction_collector, coordinator, accounts): - ritual_id = 1 - staking_providers = [accounts[0], accounts[1]] - for node in nodes: - public_key = gen_public_key() - coordinator.setProviderPublicKey(public_key, sender=node) - coordinator.initiateRitual( + cost = coordinator.getRitualInitiationCost(nodes, DURATION) + erc20.approve(coordinator.address, cost, sender=initiator) + tx = coordinator.initiateRitual( nodes, initiator, DURATION, global_allow_list.address, sender=initiator ) - coordinator.timeoutRitual(ritual_id, sender=accounts[0]) - - infraction_collector.reportMissingTranscript(ritual_id, staking_providers, sender=accounts[0]) - with ape.reverts("Infraction already reported"): - infraction_collector.reportMissingTranscript(ritual_id, staking_providers, sender=accounts[0]) + # tx1 = infraction_collector.reportMissingTranscript(ritual_id, staking_providers, sender=accounts[0]) -def test_report_missing_transcript_ritual_not_failed(nodes, initiator, global_allow_list, infraction_collector, coordinator, accounts): - ritual_id = 1 - staking_providers = [accounts[0], accounts[1]] - for node in nodes: - public_key = gen_public_key() - coordinator.setProviderPublicKey(public_key, sender=node) - coordinator.initiateRitual( - nodes, initiator, DURATION, global_allow_list.address, sender=initiator - ) - - with ape.reverts("Ritual must have failed"): - infraction_collector.reportMissingTranscript(ritual_id, staking_providers, sender=accounts[0]) + for provider in staking_providers: + assert infraction_collector.infractions(ritual_id, provider) == False + +# def test_report_missing_transcript_already_reported(nodes, initiator, global_allow_list, infraction_collector, coordinator, accounts): +# ritual_id = 1 +# staking_providers = [accounts[0], accounts[1]] +# for node in nodes: +# public_key = gen_public_key() +# coordinator.setProviderPublicKey(public_key, sender=node) +# coordinator.initiateRitual( +# nodes, initiator, DURATION, global_allow_list.address, sender=initiator +# ) +# coordinator.timeoutRitual(ritual_id, sender=accounts[0]) + +# infraction_collector.reportMissingTranscript(ritual_id, staking_providers, sender=accounts[0]) + +# with ape.reverts("Infraction already reported"): +# infraction_collector.reportMissingTranscript(ritual_id, staking_providers, sender=accounts[0]) + +# def test_report_missing_transcript_ritual_not_failed(nodes, initiator, global_allow_list, infraction_collector, coordinator, accounts): +# ritual_id = 1 +# staking_providers = [accounts[0], accounts[1]] +# for node in nodes: +# public_key = gen_public_key() +# coordinator.setProviderPublicKey(public_key, sender=node) +# coordinator.initiateRitual( +# nodes, initiator, DURATION, global_allow_list.address, sender=initiator +# ) + +# with ape.reverts("Ritual must have failed"): +# infraction_collector.reportMissingTranscript(ritual_id, staking_providers, sender=accounts[0]) From 01200226d877a6c71f7a81f3967aa0241dccaa23 Mon Sep 17 00:00:00 2001 From: James Campbell Date: Thu, 30 May 2024 10:55:35 +0200 Subject: [PATCH 06/31] Add genuine tests for InfractionCollector --- tests/test_infraction.py | 70 +++++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 36 deletions(-) diff --git a/tests/test_infraction.py b/tests/test_infraction.py index db1641629..fe4de5e75 100644 --- a/tests/test_infraction.py +++ b/tests/test_infraction.py @@ -128,49 +128,47 @@ def infraction_collector(project, deployer, coordinator, application, oz_depende proxy_contract = project.InfractionCollector.at(proxy.address) return proxy_contract -def test_report_missing_transcript(erc20, nodes, initiator, global_allow_list, infraction_collector, coordinator, accounts): - ritual_id = 0 - staking_providers = [accounts[0], accounts[1]] +def test_no_infractions(erc20, nodes, initiator, global_allow_list, infraction_collector, coordinator): for node in nodes: public_key = gen_public_key() coordinator.setProviderPublicKey(public_key, sender=node) cost = coordinator.getRitualInitiationCost(nodes, DURATION) erc20.approve(coordinator.address, cost, sender=initiator) - tx = coordinator.initiateRitual( + coordinator.initiateRitual( nodes, initiator, DURATION, global_allow_list.address, sender=initiator ) + transcript = os.urandom(transcript_size(len(nodes), len(nodes))) + for node in nodes: + coordinator.postTranscript(0, transcript, sender=node) - # tx1 = infraction_collector.reportMissingTranscript(ritual_id, staking_providers, sender=accounts[0]) - - for provider in staking_providers: - assert infraction_collector.infractions(ritual_id, provider) == False - -# def test_report_missing_transcript_already_reported(nodes, initiator, global_allow_list, infraction_collector, coordinator, accounts): -# ritual_id = 1 -# staking_providers = [accounts[0], accounts[1]] -# for node in nodes: -# public_key = gen_public_key() -# coordinator.setProviderPublicKey(public_key, sender=node) -# coordinator.initiateRitual( -# nodes, initiator, DURATION, global_allow_list.address, sender=initiator -# ) -# coordinator.timeoutRitual(ritual_id, sender=accounts[0]) - -# infraction_collector.reportMissingTranscript(ritual_id, staking_providers, sender=accounts[0]) - -# with ape.reverts("Infraction already reported"): -# infraction_collector.reportMissingTranscript(ritual_id, staking_providers, sender=accounts[0]) - -# def test_report_missing_transcript_ritual_not_failed(nodes, initiator, global_allow_list, infraction_collector, coordinator, accounts): -# ritual_id = 1 -# staking_providers = [accounts[0], accounts[1]] -# for node in nodes: -# public_key = gen_public_key() -# coordinator.setProviderPublicKey(public_key, sender=node) -# coordinator.initiateRitual( -# nodes, initiator, DURATION, global_allow_list.address, sender=initiator -# ) + with ape.reverts("Ritual must have failed"): + infraction_collector.reportMissingTranscript(0, nodes, sender=initiator) -# with ape.reverts("Ritual must have failed"): -# infraction_collector.reportMissingTranscript(ritual_id, staking_providers, sender=accounts[0]) +def test_report_infractions(erc20, nodes, initiator, global_allow_list, infraction_collector, coordinator, chain): + for node in nodes: + public_key = gen_public_key() + coordinator.setProviderPublicKey(public_key, sender=node) + cost = coordinator.getRitualInitiationCost(nodes, DURATION) + erc20.approve(coordinator.address, cost, sender=initiator) + coordinator.initiateRitual( + nodes, initiator, DURATION, global_allow_list.address, sender=initiator + ) + chain.pending_timestamp += TIMEOUT * 2 + infraction_collector.reportMissingTranscript(0, nodes, sender=initiator) + for node in nodes: + assert infraction_collector.infractions(0, node) == True +def test_cant_report_infractions_twice(erc20, nodes, initiator, global_allow_list, infraction_collector, coordinator, chain): + for node in nodes: + public_key = gen_public_key() + coordinator.setProviderPublicKey(public_key, sender=node) + cost = coordinator.getRitualInitiationCost(nodes, DURATION) + erc20.approve(coordinator.address, cost, sender=initiator) + coordinator.initiateRitual( + nodes, initiator, DURATION, global_allow_list.address, sender=initiator + ) + chain.pending_timestamp += TIMEOUT * 2 + infraction_collector.reportMissingTranscript(0, nodes, sender=initiator) + + with ape.reverts("Infraction already reported"): + infraction_collector.reportMissingTranscript(0, nodes, sender=initiator) \ No newline at end of file From c48f05a4130d42d737be493dd6c31c9b0ca8e23f Mon Sep 17 00:00:00 2001 From: James Campbell Date: Wed, 5 Jun 2024 10:35:50 +0200 Subject: [PATCH 07/31] Add deployment script for InfractionCollector on lynx --- .../constructor_params/lynx/infraction.yml | 18 +++++++++++++ scripts/lynx/deploy_infraction.py | 25 +++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 deployment/constructor_params/lynx/infraction.yml create mode 100644 scripts/lynx/deploy_infraction.py diff --git a/deployment/constructor_params/lynx/infraction.yml b/deployment/constructor_params/lynx/infraction.yml new file mode 100644 index 000000000..869163f3e --- /dev/null +++ b/deployment/constructor_params/lynx/infraction.yml @@ -0,0 +1,18 @@ +deployment: + name: infraction + chain_id: 80002 + +artifacts: + dir: ./deployment/artifacts/ + filename: infraction.json + +constants: + # See deployment/artifacts/lynx.json + COORDINATOR_PROXY: "0xE9e94499bB0f67b9DBD75506ec1735486DE57770" + TACO_CHILD_PROXY: "0x42F30AEc1A36995eEFaf9536Eb62BD751F982D32" + +contracts: + - InfractionCollector: + proxy: + constructor: + _data: $encode:initialize,$COORDINATOR_PROXY,$TACO_CHILD_PROX diff --git a/scripts/lynx/deploy_infraction.py b/scripts/lynx/deploy_infraction.py new file mode 100644 index 000000000..1ab64c8b2 --- /dev/null +++ b/scripts/lynx/deploy_infraction.py @@ -0,0 +1,25 @@ +#!/usr/bin/python3 + +from ape import project + +from deployment.constants import ( + CONSTRUCTOR_PARAMS_DIR, ARTIFACTS_DIR, +) +from deployment.params import Deployer +from deployment.registry import merge_registries + +VERIFY = False +CONSTRUCTOR_PARAMS_FILEPATH = CONSTRUCTOR_PARAMS_DIR / "lynx" / "infraction.yml" +LYNX_REGISTRY = ARTIFACTS_DIR / "lynx.json" + + +def main(): + deployer = Deployer.from_yaml(filepath=CONSTRUCTOR_PARAMS_FILEPATH, verify=VERIFY) + infraction = deployer.deploy(project.InfractionCollector) + deployments = [infraction] + deployer.finalize(deployments=deployments) + merge_registries( + registry_1_filepath=LYNX_REGISTRY, + registry_2_filepath=deployer.registry_filepath, + output_filepath=LYNX_REGISTRY, + ) From 8508fd6c6630da2a5b5dc9686a1b2ed675569646 Mon Sep 17 00:00:00 2001 From: James Campbell Date: Wed, 5 Jun 2024 13:48:58 +0200 Subject: [PATCH 08/31] Use interface for TACo Child Application in Infraction Collector --- contracts/contracts/coordination/InfractionCollector.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/contracts/coordination/InfractionCollector.sol b/contracts/contracts/coordination/InfractionCollector.sol index 47725ba42..2bc274b94 100644 --- a/contracts/contracts/coordination/InfractionCollector.sol +++ b/contracts/contracts/coordination/InfractionCollector.sol @@ -4,20 +4,20 @@ pragma solidity ^0.8.0; import "@openzeppelin-upgradeable/contracts/access/OwnableUpgradeable.sol"; import "./Coordinator.sol"; -import "./TACoChildApplication.sol"; +import "../../threshold/ITACoChildApplication.sol"; contract InfractionCollector is OwnableUpgradeable { Coordinator public coordinator; // Reference to the TACoChildApplication contract - TACoChildApplication public tacoChildApplication; + ITACoChildApplication public tacoChildApplication; // Mapping to keep track of reported infractions mapping(uint32 => mapping(address => bool)) public infractions; function initialize( Coordinator _coordinator, - TACoChildApplication _tacoChildApplication + ITACoChildApplication _tacoChildApplication ) public initializer { __Ownable_init(msg.sender); coordinator = _coordinator; From f870094ae85923c3183db212bfd6e29d687c99ad Mon Sep 17 00:00:00 2001 From: James Campbell Date: Wed, 5 Jun 2024 16:04:17 +0200 Subject: [PATCH 09/31] Fix deployment typo Co-authored-by: Derek Pierre --- deployment/constructor_params/lynx/infraction.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/constructor_params/lynx/infraction.yml b/deployment/constructor_params/lynx/infraction.yml index 869163f3e..55e0cddd1 100644 --- a/deployment/constructor_params/lynx/infraction.yml +++ b/deployment/constructor_params/lynx/infraction.yml @@ -15,4 +15,4 @@ contracts: - InfractionCollector: proxy: constructor: - _data: $encode:initialize,$COORDINATOR_PROXY,$TACO_CHILD_PROX + _data: $encode:initialize,$COORDINATOR_PROXY,$TACO_CHILD_PROXY From aab858210db785b84293c5340db1ce74e5b528fa Mon Sep 17 00:00:00 2001 From: James Campbell Date: Thu, 20 Jun 2024 15:27:44 +0200 Subject: [PATCH 10/31] Update README explaining how to test a deployment locally --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index f2acf6dfc..faf4ad94b 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,12 @@ def main(): deployer.finalize(deployments=deployments) ``` +To run a deployment locally: + +```bash +ape run lynx deploy_infraction --network ethereum:local:test +``` + ##### 3. Setup Deployment Account (production only) In order to deploy to **production** you will need to import an account into ape: From b44579db389b4c5d2aef21de558cddb22c98ce1e Mon Sep 17 00:00:00 2001 From: James Campbell Date: Mon, 24 Jun 2024 12:02:08 +0200 Subject: [PATCH 11/31] Add infraction deployment to child script --- deployment/constructor_params/lynx/child.yml | 4 ++++ scripts/lynx/deploy_child.py | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/deployment/constructor_params/lynx/child.yml b/deployment/constructor_params/lynx/child.yml index d158e7581..2102bc386 100644 --- a/deployment/constructor_params/lynx/child.yml +++ b/deployment/constructor_params/lynx/child.yml @@ -33,3 +33,7 @@ contracts: - GlobalAllowList: constructor: _coordinator: $Coordinator + - InfractionCollector: + proxy: + constructor: + _data: $encode:initialize,$Coordinator,$TACoChildApplication diff --git a/scripts/lynx/deploy_child.py b/scripts/lynx/deploy_child.py index a5a9b2ddb..c65d3f690 100644 --- a/scripts/lynx/deploy_child.py +++ b/scripts/lynx/deploy_child.py @@ -51,7 +51,8 @@ def main(): ritual_token = deployer.deploy(project.LynxRitualToken) coordinator = deployer.deploy(project.Coordinator) - deployer.transact(taco_child_application.initialize, coordinator.address) + infraction = deployer.deploy(project.InfractionCollector) + deployer.transact(taco_child_application.initialize, coordinator.address, infraction.address) global_allow_list = deployer.deploy(project.GlobalAllowList) @@ -61,6 +62,7 @@ def main(): ritual_token, coordinator, global_allow_list, + infraction, ] deployer.finalize(deployments=deployments) From 66bf2563d4d857b81b470888f5237e15fd6f85a4 Mon Sep 17 00:00:00 2001 From: James Campbell Date: Mon, 24 Jun 2024 14:03:27 +0200 Subject: [PATCH 12/31] Adding comment about transcript length MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: David Núñez --- contracts/contracts/coordination/InfractionCollector.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/contracts/coordination/InfractionCollector.sol b/contracts/contracts/coordination/InfractionCollector.sol index 2bc274b94..f66af146a 100644 --- a/contracts/contracts/coordination/InfractionCollector.sol +++ b/contracts/contracts/coordination/InfractionCollector.sol @@ -41,7 +41,7 @@ contract InfractionCollector is OwnableUpgradeable { ritualId, stakingProviders[i] ); - if (participant.transcript.length == 0) { + if (participant.transcript.length == 0) { // Transcript TX wasn't posted // Penalize the staking provider tacoChildApplication.penalize(stakingProviders[i]); infractions[ritualId][stakingProviders[i]] = true; From 9774912da0f1bd541ab5bad45f32dd4ff64e51c4 Mon Sep 17 00:00:00 2001 From: James Campbell Date: Thu, 8 Aug 2024 14:57:26 +0200 Subject: [PATCH 13/31] Add infraction type enum for granular punishment --- .../coordination/InfractionCollector.sol | 19 +++++++++++++++---- tests/test_infraction.py | 6 +----- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/contracts/contracts/coordination/InfractionCollector.sol b/contracts/contracts/coordination/InfractionCollector.sol index f66af146a..d34bb41e2 100644 --- a/contracts/contracts/coordination/InfractionCollector.sol +++ b/contracts/contracts/coordination/InfractionCollector.sol @@ -12,8 +12,13 @@ contract InfractionCollector is OwnableUpgradeable { // Reference to the TACoChildApplication contract ITACoChildApplication public tacoChildApplication; + // infraction types + enum InfractionType { + MISSING_TRANSCRIPT + } + // Mapping to keep track of reported infractions - mapping(uint32 => mapping(address => bool)) public infractions; + mapping(uint32 => mapping(address => mapping(InfractionType => bool))) public infractions; function initialize( Coordinator _coordinator, @@ -36,15 +41,21 @@ contract InfractionCollector is OwnableUpgradeable { for (uint256 i = 0; i < stakingProviders.length; i++) { // Check if the infraction has already been reported - require(!infractions[ritualId][stakingProviders[i]], "Infraction already reported"); + require( + !infractions[ritualId][stakingProviders[i]][InfractionType.MISSING_TRANSCRIPT], + "Infraction already reported" + ); Coordinator.Participant memory participant = coordinator.getParticipantFromProvider( ritualId, stakingProviders[i] ); - if (participant.transcript.length == 0) { // Transcript TX wasn't posted + if (participant.transcript.length == 0) { + // Transcript TX wasn't posted // Penalize the staking provider tacoChildApplication.penalize(stakingProviders[i]); - infractions[ritualId][stakingProviders[i]] = true; + infractions[ritualId][stakingProviders[i]][ + InfractionType.MISSING_TRANSCRIPT + ] = true; } } } diff --git a/tests/test_infraction.py b/tests/test_infraction.py index fe4de5e75..c9a166309 100644 --- a/tests/test_infraction.py +++ b/tests/test_infraction.py @@ -3,10 +3,6 @@ import ape import pytest -from eth_account import Account -from eth_account.messages import encode_defunct -from hexbytes import HexBytes -from web3 import Web3 TIMEOUT = 1000 MAX_DKG_SIZE = 31 @@ -156,7 +152,7 @@ def test_report_infractions(erc20, nodes, initiator, global_allow_list, infracti chain.pending_timestamp += TIMEOUT * 2 infraction_collector.reportMissingTranscript(0, nodes, sender=initiator) for node in nodes: - assert infraction_collector.infractions(0, node) == True + assert infraction_collector.infractions(0, node, 0) == True def test_cant_report_infractions_twice(erc20, nodes, initiator, global_allow_list, infraction_collector, coordinator, chain): for node in nodes: From e88941b197710d5c3b1f4951806455146efec20b Mon Sep 17 00:00:00 2001 From: James Campbell Date: Fri, 9 Aug 2024 15:56:52 +0200 Subject: [PATCH 14/31] Decouple TACoChildApplication and infraction collector --- .../coordination/InfractionCollector.sol | 2 +- .../coordination/TACoChildApplication.sol | 18 ++++++------------ deployment/constructor_params/lynx/child.yml | 4 ---- .../constructor_params/lynx/infraction.yml | 5 ++--- scripts/lynx/deploy_child.py | 4 +--- tests/test_child_application.py | 2 +- 6 files changed, 11 insertions(+), 24 deletions(-) diff --git a/contracts/contracts/coordination/InfractionCollector.sol b/contracts/contracts/coordination/InfractionCollector.sol index d34bb41e2..90834e03f 100644 --- a/contracts/contracts/coordination/InfractionCollector.sol +++ b/contracts/contracts/coordination/InfractionCollector.sol @@ -52,7 +52,7 @@ contract InfractionCollector is OwnableUpgradeable { if (participant.transcript.length == 0) { // Transcript TX wasn't posted // Penalize the staking provider - tacoChildApplication.penalize(stakingProviders[i]); + // tacoChildApplication.penalize(stakingProviders[i]); infractions[ritualId][stakingProviders[i]][ InfractionType.MISSING_TRANSCRIPT ] = true; diff --git a/contracts/contracts/coordination/TACoChildApplication.sol b/contracts/contracts/coordination/TACoChildApplication.sol index d4f699a2c..e829ddd03 100644 --- a/contracts/contracts/coordination/TACoChildApplication.sol +++ b/contracts/contracts/coordination/TACoChildApplication.sol @@ -31,7 +31,7 @@ contract TACoChildApplication is ITACoRootToChild, ITACoChildApplication, Initia ITACoChildToRoot public immutable rootApplication; address public coordinator; - address public infractionCollector; + address public adjudicator; uint96 public immutable minimumAuthorization; @@ -61,13 +61,10 @@ contract TACoChildApplication is ITACoRootToChild, ITACoChildApplication, Initia /** * @notice Initialize function for using with OpenZeppelin proxy */ - function initialize(address _coordinator, address _infractionCollector) external initializer { + function initialize(address _coordinator, address _adjudicator) external initializer { + require(coordinator == address(0) || adjudicator == address(0), "Contracts already set"); require( - coordinator == address(0) || infractionCollector == address(0), - "Contracts already set" - ); - require( - _coordinator != address(0) && _infractionCollector != address(0), + _coordinator != address(0) && _adjudicator != address(0), "Contracts must be specified" ); require( @@ -75,7 +72,7 @@ contract TACoChildApplication is ITACoRootToChild, ITACoChildApplication, Initia "Invalid coordinator" ); coordinator = _coordinator; - infractionCollector = _infractionCollector; + adjudicator = _adjudicator; } function authorizedStake(address _stakingProvider) external view returns (uint96) { @@ -203,10 +200,7 @@ contract TACoChildApplication is ITACoRootToChild, ITACoChildApplication, Initia * @param _stakingProvider Staking provider address */ function penalize(address _stakingProvider) external override { - require( - msg.sender == address(infractionCollector), - "Only infractionCollector allowed to penalize" - ); + require(msg.sender == address(adjudicator), "Only adjudicator allowed to penalize"); rootApplication.penalize(_stakingProvider); emit Penalized(_stakingProvider); } diff --git a/deployment/constructor_params/lynx/child.yml b/deployment/constructor_params/lynx/child.yml index 2102bc386..d158e7581 100644 --- a/deployment/constructor_params/lynx/child.yml +++ b/deployment/constructor_params/lynx/child.yml @@ -33,7 +33,3 @@ contracts: - GlobalAllowList: constructor: _coordinator: $Coordinator - - InfractionCollector: - proxy: - constructor: - _data: $encode:initialize,$Coordinator,$TACoChildApplication diff --git a/deployment/constructor_params/lynx/infraction.yml b/deployment/constructor_params/lynx/infraction.yml index 55e0cddd1..7ad4b720f 100644 --- a/deployment/constructor_params/lynx/infraction.yml +++ b/deployment/constructor_params/lynx/infraction.yml @@ -9,10 +9,9 @@ artifacts: constants: # See deployment/artifacts/lynx.json COORDINATOR_PROXY: "0xE9e94499bB0f67b9DBD75506ec1735486DE57770" - TACO_CHILD_PROXY: "0x42F30AEc1A36995eEFaf9536Eb62BD751F982D32" contracts: - InfractionCollector: proxy: - constructor: - _data: $encode:initialize,$COORDINATOR_PROXY,$TACO_CHILD_PROXY + constructor: + _coordinator: $COORDINATOR_PROXY diff --git a/scripts/lynx/deploy_child.py b/scripts/lynx/deploy_child.py index c65d3f690..a5a9b2ddb 100644 --- a/scripts/lynx/deploy_child.py +++ b/scripts/lynx/deploy_child.py @@ -51,8 +51,7 @@ def main(): ritual_token = deployer.deploy(project.LynxRitualToken) coordinator = deployer.deploy(project.Coordinator) - infraction = deployer.deploy(project.InfractionCollector) - deployer.transact(taco_child_application.initialize, coordinator.address, infraction.address) + deployer.transact(taco_child_application.initialize, coordinator.address) global_allow_list = deployer.deploy(project.GlobalAllowList) @@ -62,7 +61,6 @@ def main(): ritual_token, coordinator, global_allow_list, - infraction, ] deployer.finalize(deployments=deployments) diff --git a/tests/test_child_application.py b/tests/test_child_application.py index 8194fe3ca..f5e52b20f 100644 --- a/tests/test_child_application.py +++ b/tests/test_child_application.py @@ -331,7 +331,7 @@ def test_penalize(accounts, root_application, child_application, coordinator): ) = accounts[0:] # Penalize can be done only from adjudicator address - with ape.reverts("Only infractionCollector allowed to penalize"): + with ape.reverts("Only adjudicator allowed to penalize"): child_application.penalize(staking_provider, sender=staking_provider) tx = child_application.penalize(staking_provider, sender=creator) From ac4181fa9e897b19be0f5f015b974c9ed05e8201 Mon Sep 17 00:00:00 2001 From: James Campbell Date: Wed, 31 Jul 2024 13:54:03 +0200 Subject: [PATCH 15/31] Move coordinator and taco application to contstructor --- contracts/contracts/coordination/InfractionCollector.sol | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/contracts/contracts/coordination/InfractionCollector.sol b/contracts/contracts/coordination/InfractionCollector.sol index 90834e03f..1a4f312f9 100644 --- a/contracts/contracts/coordination/InfractionCollector.sol +++ b/contracts/contracts/coordination/InfractionCollector.sol @@ -7,10 +7,10 @@ import "./Coordinator.sol"; import "../../threshold/ITACoChildApplication.sol"; contract InfractionCollector is OwnableUpgradeable { - Coordinator public coordinator; + Coordinator public immutable coordinator; // Reference to the TACoChildApplication contract - ITACoChildApplication public tacoChildApplication; + ITACoChildApplication public immutable tacoChildApplication; // infraction types enum InfractionType { @@ -20,11 +20,10 @@ contract InfractionCollector is OwnableUpgradeable { // Mapping to keep track of reported infractions mapping(uint32 => mapping(address => mapping(InfractionType => bool))) public infractions; - function initialize( + constructor( Coordinator _coordinator, ITACoChildApplication _tacoChildApplication - ) public initializer { - __Ownable_init(msg.sender); + ) { coordinator = _coordinator; tacoChildApplication = _tacoChildApplication; } From a43a0e2ea1c32371e05d516b3a6c038a72256e27 Mon Sep 17 00:00:00 2001 From: James Campbell Date: Fri, 9 Aug 2024 16:16:46 +0200 Subject: [PATCH 16/31] Fix bug with removing initialize --- contracts/contracts/coordination/InfractionCollector.sol | 5 +---- deployment/constructor_params/lynx/infraction.yml | 2 ++ tests/test_infraction.py | 4 +--- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/contracts/contracts/coordination/InfractionCollector.sol b/contracts/contracts/coordination/InfractionCollector.sol index 1a4f312f9..18159ec8a 100644 --- a/contracts/contracts/coordination/InfractionCollector.sol +++ b/contracts/contracts/coordination/InfractionCollector.sol @@ -20,10 +20,7 @@ contract InfractionCollector is OwnableUpgradeable { // Mapping to keep track of reported infractions mapping(uint32 => mapping(address => mapping(InfractionType => bool))) public infractions; - constructor( - Coordinator _coordinator, - ITACoChildApplication _tacoChildApplication - ) { + constructor(Coordinator _coordinator, ITACoChildApplication _tacoChildApplication) { coordinator = _coordinator; tacoChildApplication = _tacoChildApplication; } diff --git a/deployment/constructor_params/lynx/infraction.yml b/deployment/constructor_params/lynx/infraction.yml index 7ad4b720f..1f82aa825 100644 --- a/deployment/constructor_params/lynx/infraction.yml +++ b/deployment/constructor_params/lynx/infraction.yml @@ -9,9 +9,11 @@ artifacts: constants: # See deployment/artifacts/lynx.json COORDINATOR_PROXY: "0xE9e94499bB0f67b9DBD75506ec1735486DE57770" + TACO_CHILD_PROXY: "0x42F30AEc1A36995eEFaf9536Eb62BD751F982D32" contracts: - InfractionCollector: proxy: constructor: _coordinator: $COORDINATOR_PROXY + _tacoChildApplication: $TACO_CHILD_PROXY diff --git a/tests/test_infraction.py b/tests/test_infraction.py index c9a166309..829619bf6 100644 --- a/tests/test_infraction.py +++ b/tests/test_infraction.py @@ -113,12 +113,10 @@ def global_allow_list(project, deployer, coordinator): @pytest.fixture def infraction_collector(project, deployer, coordinator, application, oz_dependency): - contract = project.InfractionCollector.deploy(sender=deployer) - encoded_initializer_function = contract.initialize.encode_input(coordinator.address, application.address) + contract = project.InfractionCollector.deploy(coordinator.address, application.address, sender=deployer) proxy = oz_dependency.TransparentUpgradeableProxy.deploy( contract.address, deployer, - encoded_initializer_function, sender=deployer, ) proxy_contract = project.InfractionCollector.at(proxy.address) From b99d3eaa28dcf0b13fe46603d6778c27958556ec Mon Sep 17 00:00:00 2001 From: James Campbell Date: Fri, 9 Aug 2024 16:23:00 +0200 Subject: [PATCH 17/31] Clean up README deployments --- README.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/README.md b/README.md index faf4ad94b..f2acf6dfc 100644 --- a/README.md +++ b/README.md @@ -117,12 +117,6 @@ def main(): deployer.finalize(deployments=deployments) ``` -To run a deployment locally: - -```bash -ape run lynx deploy_infraction --network ethereum:local:test -``` - ##### 3. Setup Deployment Account (production only) In order to deploy to **production** you will need to import an account into ape: From 8cb11f5e46006d92e9b0b4d5171ff4e0fb58353f Mon Sep 17 00:00:00 2001 From: James Campbell Date: Fri, 9 Aug 2024 16:24:49 +0200 Subject: [PATCH 18/31] Make infractionCollector constructor more robust --- contracts/contracts/coordination/InfractionCollector.sol | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/contracts/contracts/coordination/InfractionCollector.sol b/contracts/contracts/coordination/InfractionCollector.sol index 18159ec8a..c7319fb84 100644 --- a/contracts/contracts/coordination/InfractionCollector.sol +++ b/contracts/contracts/coordination/InfractionCollector.sol @@ -21,8 +21,13 @@ contract InfractionCollector is OwnableUpgradeable { mapping(uint32 => mapping(address => mapping(InfractionType => bool))) public infractions; constructor(Coordinator _coordinator, ITACoChildApplication _tacoChildApplication) { + require( + _coordinator != address(0) && _tacoChildApplication != address(0), + "Contracts must be specified" + ); coordinator = _coordinator; tacoChildApplication = _tacoChildApplication; + _disableInitializers(); } function reportMissingTranscript( From 988dc44abc40ca2d00ce3836e777258f2b42cfda Mon Sep 17 00:00:00 2001 From: James Campbell Date: Fri, 9 Aug 2024 16:26:46 +0200 Subject: [PATCH 19/31] Add InfractionReported event --- contracts/contracts/coordination/InfractionCollector.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contracts/contracts/coordination/InfractionCollector.sol b/contracts/contracts/coordination/InfractionCollector.sol index c7319fb84..c4916340c 100644 --- a/contracts/contracts/coordination/InfractionCollector.sol +++ b/contracts/contracts/coordination/InfractionCollector.sol @@ -7,6 +7,7 @@ import "./Coordinator.sol"; import "../../threshold/ITACoChildApplication.sol"; contract InfractionCollector is OwnableUpgradeable { + event InfractionReported(uint32 indexed ritualId, address indexed stakingProvider, InfractionType infractionType); Coordinator public immutable coordinator; // Reference to the TACoChildApplication contract @@ -57,6 +58,7 @@ contract InfractionCollector is OwnableUpgradeable { infractions[ritualId][stakingProviders[i]][ InfractionType.MISSING_TRANSCRIPT ] = true; + emit InfractionReported(ritualId, stakingProviders[i], InfractionType.MISSING_TRANSCRIPT); } } } From 2867808e74fff7f9c228d70cf8c26279f8a25a2e Mon Sep 17 00:00:00 2001 From: James Campbell Date: Fri, 9 Aug 2024 16:36:11 +0200 Subject: [PATCH 20/31] Fix tests and contract constructor --- .../contracts/coordination/InfractionCollector.sol | 2 +- tests/test_infraction.py | 10 ++-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/contracts/contracts/coordination/InfractionCollector.sol b/contracts/contracts/coordination/InfractionCollector.sol index c4916340c..3c49d6925 100644 --- a/contracts/contracts/coordination/InfractionCollector.sol +++ b/contracts/contracts/coordination/InfractionCollector.sol @@ -23,7 +23,7 @@ contract InfractionCollector is OwnableUpgradeable { constructor(Coordinator _coordinator, ITACoChildApplication _tacoChildApplication) { require( - _coordinator != address(0) && _tacoChildApplication != address(0), + address(_coordinator) != address(0) && address(_tacoChildApplication) != address(0), "Contracts must be specified" ); coordinator = _coordinator; diff --git a/tests/test_infraction.py b/tests/test_infraction.py index 829619bf6..9e06551e1 100644 --- a/tests/test_infraction.py +++ b/tests/test_infraction.py @@ -112,15 +112,9 @@ def global_allow_list(project, deployer, coordinator): @pytest.fixture -def infraction_collector(project, deployer, coordinator, application, oz_dependency): +def infraction_collector(project, deployer, coordinator, application): contract = project.InfractionCollector.deploy(coordinator.address, application.address, sender=deployer) - proxy = oz_dependency.TransparentUpgradeableProxy.deploy( - contract.address, - deployer, - sender=deployer, - ) - proxy_contract = project.InfractionCollector.at(proxy.address) - return proxy_contract + return contract def test_no_infractions(erc20, nodes, initiator, global_allow_list, infraction_collector, coordinator): for node in nodes: From bb59916f147f4eaffccc5b6449720033c248d573 Mon Sep 17 00:00:00 2001 From: James Campbell Date: Fri, 9 Aug 2024 16:39:34 +0200 Subject: [PATCH 21/31] Fix logic of tests to make code more efficient --- .../contracts/coordination/InfractionCollector.sol | 12 ++++++++++-- tests/test_infraction.py | 12 ++++++------ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/contracts/contracts/coordination/InfractionCollector.sol b/contracts/contracts/coordination/InfractionCollector.sol index 3c49d6925..7636c61c2 100644 --- a/contracts/contracts/coordination/InfractionCollector.sol +++ b/contracts/contracts/coordination/InfractionCollector.sol @@ -7,7 +7,11 @@ import "./Coordinator.sol"; import "../../threshold/ITACoChildApplication.sol"; contract InfractionCollector is OwnableUpgradeable { - event InfractionReported(uint32 indexed ritualId, address indexed stakingProvider, InfractionType infractionType); + event InfractionReported( + uint32 indexed ritualId, + address indexed stakingProvider, + InfractionType infractionType + ); Coordinator public immutable coordinator; // Reference to the TACoChildApplication contract @@ -58,7 +62,11 @@ contract InfractionCollector is OwnableUpgradeable { infractions[ritualId][stakingProviders[i]][ InfractionType.MISSING_TRANSCRIPT ] = true; - emit InfractionReported(ritualId, stakingProviders[i], InfractionType.MISSING_TRANSCRIPT); + emit InfractionReported( + ritualId, + stakingProviders[i], + InfractionType.MISSING_TRANSCRIPT + ); } } } diff --git a/tests/test_infraction.py b/tests/test_infraction.py index 9e06551e1..310ad3824 100644 --- a/tests/test_infraction.py +++ b/tests/test_infraction.py @@ -117,10 +117,10 @@ def infraction_collector(project, deployer, coordinator, application): return contract def test_no_infractions(erc20, nodes, initiator, global_allow_list, infraction_collector, coordinator): + cost = coordinator.getRitualInitiationCost(nodes, DURATION) for node in nodes: public_key = gen_public_key() - coordinator.setProviderPublicKey(public_key, sender=node) - cost = coordinator.getRitualInitiationCost(nodes, DURATION) + coordinator.setProviderPublicKey(public_key, sender=node) erc20.approve(coordinator.address, cost, sender=initiator) coordinator.initiateRitual( nodes, initiator, DURATION, global_allow_list.address, sender=initiator @@ -133,10 +133,10 @@ def test_no_infractions(erc20, nodes, initiator, global_allow_list, infraction_c infraction_collector.reportMissingTranscript(0, nodes, sender=initiator) def test_report_infractions(erc20, nodes, initiator, global_allow_list, infraction_collector, coordinator, chain): + cost = coordinator.getRitualInitiationCost(nodes, DURATION) for node in nodes: public_key = gen_public_key() - coordinator.setProviderPublicKey(public_key, sender=node) - cost = coordinator.getRitualInitiationCost(nodes, DURATION) + coordinator.setProviderPublicKey(public_key, sender=node) erc20.approve(coordinator.address, cost, sender=initiator) coordinator.initiateRitual( nodes, initiator, DURATION, global_allow_list.address, sender=initiator @@ -147,10 +147,10 @@ def test_report_infractions(erc20, nodes, initiator, global_allow_list, infracti assert infraction_collector.infractions(0, node, 0) == True def test_cant_report_infractions_twice(erc20, nodes, initiator, global_allow_list, infraction_collector, coordinator, chain): + cost = coordinator.getRitualInitiationCost(nodes, DURATION) for node in nodes: public_key = gen_public_key() - coordinator.setProviderPublicKey(public_key, sender=node) - cost = coordinator.getRitualInitiationCost(nodes, DURATION) + coordinator.setProviderPublicKey(public_key, sender=node) erc20.approve(coordinator.address, cost, sender=initiator) coordinator.initiateRitual( nodes, initiator, DURATION, global_allow_list.address, sender=initiator From 5de99469505e885193d8e659b66bb50d2c492f3d Mon Sep 17 00:00:00 2001 From: James Campbell Date: Fri, 9 Aug 2024 17:01:28 +0200 Subject: [PATCH 22/31] Fix yaml tab/spaces Co-authored-by: Derek Pierre --- deployment/constructor_params/lynx/infraction.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deployment/constructor_params/lynx/infraction.yml b/deployment/constructor_params/lynx/infraction.yml index 1f82aa825..c8d30ff01 100644 --- a/deployment/constructor_params/lynx/infraction.yml +++ b/deployment/constructor_params/lynx/infraction.yml @@ -15,5 +15,5 @@ contracts: - InfractionCollector: proxy: constructor: - _coordinator: $COORDINATOR_PROXY - _tacoChildApplication: $TACO_CHILD_PROXY + _coordinator: $COORDINATOR_PROXY + _tacoChildApplication: $TACO_CHILD_PROXY From d6047d78376cf46a1be1b8ac5a0d15e3a9951fe0 Mon Sep 17 00:00:00 2001 From: James Campbell Date: Fri, 9 Aug 2024 17:11:32 +0200 Subject: [PATCH 23/31] Add new test where only half of nodes submit transcripts --- tests/test_infraction.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/test_infraction.py b/tests/test_infraction.py index 310ad3824..c14454411 100644 --- a/tests/test_infraction.py +++ b/tests/test_infraction.py @@ -132,6 +132,27 @@ def test_no_infractions(erc20, nodes, initiator, global_allow_list, infraction_c with ape.reverts("Ritual must have failed"): infraction_collector.reportMissingTranscript(0, nodes, sender=initiator) +def test_partial_infractions(erc20, nodes, initiator, global_allow_list, infraction_collector, coordinator, chain): + cost = coordinator.getRitualInitiationCost(nodes, DURATION) + for node in nodes: + public_key = gen_public_key() + coordinator.setProviderPublicKey(public_key, sender=node) + erc20.approve(coordinator.address, cost, sender=initiator) + coordinator.initiateRitual( + nodes, initiator, DURATION, global_allow_list.address, sender=initiator + ) + transcript = os.urandom(transcript_size(len(nodes), len(nodes))) + # post transcript for half of nodes + for node in nodes[:len(nodes) // 2]: + coordinator.postTranscript(0, transcript, sender=node) + chain.pending_timestamp += TIMEOUT * 2 + infraction_collector.reportMissingTranscript(0, nodes, sender=initiator) + # first half of nodes should be fine, second half should be infracted + for node in nodes[:len(nodes) // 2]: + assert infraction_collector.infractions(0, node, 0) == False + for node in nodes[len(nodes) // 2:]: + assert infraction_collector.infractions(0, node, 0) == True + def test_report_infractions(erc20, nodes, initiator, global_allow_list, infraction_collector, coordinator, chain): cost = coordinator.getRitualInitiationCost(nodes, DURATION) for node in nodes: From ebd1e43e42de3b5533a66d2752b37eb299577717 Mon Sep 17 00:00:00 2001 From: James Campbell Date: Tue, 13 Aug 2024 10:17:14 +0200 Subject: [PATCH 24/31] Add PR suggestion on named mapping --- contracts/contracts/coordination/InfractionCollector.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/contracts/coordination/InfractionCollector.sol b/contracts/contracts/coordination/InfractionCollector.sol index 7636c61c2..654c62f48 100644 --- a/contracts/contracts/coordination/InfractionCollector.sol +++ b/contracts/contracts/coordination/InfractionCollector.sol @@ -23,7 +23,8 @@ contract InfractionCollector is OwnableUpgradeable { } // Mapping to keep track of reported infractions - mapping(uint32 => mapping(address => mapping(InfractionType => bool))) public infractions; + mapping(uint32 ritualId => mapping(address stakingProvider => mapping(InfractionType => bool))) + public infractions; constructor(Coordinator _coordinator, ITACoChildApplication _tacoChildApplication) { require( From 1b756eeac83d3ca5f0307bad4c3e6f9ae982b192 Mon Sep 17 00:00:00 2001 From: James Campbell Date: Tue, 13 Aug 2024 14:47:39 +0200 Subject: [PATCH 25/31] Deploy InfractionCollector to Lynx --- deployment/artifacts/lynx.json | 239 +++++++++++++++++++++++++++++++++ 1 file changed, 239 insertions(+) diff --git a/deployment/artifacts/lynx.json b/deployment/artifacts/lynx.json index 7d1f65d6c..7f34590dd 100644 --- a/deployment/artifacts/lynx.json +++ b/deployment/artifacts/lynx.json @@ -5476,6 +5476,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": [ From 5fff77bf58da1057b10909a6d66423ad85616a27 Mon Sep 17 00:00:00 2001 From: James Campbell Date: Tue, 13 Aug 2024 15:07:49 +0200 Subject: [PATCH 26/31] Deploy InfractionCollector to tapir --- deployment/artifacts/tapir.json | 241 +++++++++++++++++- .../constructor_params/tapir/infraction.yml | 19 ++ scripts/tapir/deploy_infraction.py | 20 ++ 3 files changed, 279 insertions(+), 1 deletion(-) create mode 100644 deployment/constructor_params/tapir/infraction.yml create mode 100644 scripts/tapir/deploy_infraction.py diff --git a/deployment/artifacts/tapir.json b/deployment/artifacts/tapir.json index af416637d..110c80cde 100644 --- a/deployment/artifacts/tapir.json +++ b/deployment/artifacts/tapir.json @@ -4288,6 +4288,245 @@ "block_number": 5393004, "deployer": "0x3B42d26E19FF860bC4dEbB920DD8caA53F93c600" }, + "InfractionCollector": { + "address": "0xb6400F55857716A3Ff863e6bE867F01F23C71793", + "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": "0x7c11685e52dca884556e225541211e3d747da2bd796fb88c75fdeed3910ba488", + "block_number": 10667701, + "deployer": "0x3B42d26E19FF860bC4dEbB920DD8caA53F93c600" + }, "MockPolygonChild": { "address": "0x970b5f6A299813cA9DC45Be8446929b6513903f9", "abi": [ @@ -5866,4 +6105,4 @@ "deployer": "0x3B42d26E19FF860bC4dEbB920DD8caA53F93c600" } } -} +} \ No newline at end of file diff --git a/deployment/constructor_params/tapir/infraction.yml b/deployment/constructor_params/tapir/infraction.yml new file mode 100644 index 000000000..8c2f03cf1 --- /dev/null +++ b/deployment/constructor_params/tapir/infraction.yml @@ -0,0 +1,19 @@ +deployment: + name: infraction + chain_id: 80002 + +artifacts: + dir: ./deployment/artifacts/ + filename: tapir.json + +constants: + # See deployment/artifacts/tapir.json + COORDINATOR_PROXY: "0xE690b6bCC0616Dc5294fF84ff4e00335cA52C388" + TACO_CHILD_PROXY: "0x489287Ed5BdF7a35fEE411FBdCc47331093D0769" + +contracts: + - InfractionCollector: + proxy: + constructor: + _coordinator: $COORDINATOR_PROXY + _tacoChildApplication: $TACO_CHILD_PROXY diff --git a/scripts/tapir/deploy_infraction.py b/scripts/tapir/deploy_infraction.py new file mode 100644 index 000000000..ec8944e70 --- /dev/null +++ b/scripts/tapir/deploy_infraction.py @@ -0,0 +1,20 @@ +#!/usr/bin/python3 + +from ape import project + +from deployment.constants import ( + CONSTRUCTOR_PARAMS_DIR, ARTIFACTS_DIR, +) +from deployment.params import Deployer + +VERIFY = False +CONSTRUCTOR_PARAMS_FILEPATH = CONSTRUCTOR_PARAMS_DIR / "tapir" / "infraction.yml" +TAPIR_REGISTRY = ARTIFACTS_DIR / "tapir.json" + + +def main(): + deployer = Deployer.from_yaml(filepath=CONSTRUCTOR_PARAMS_FILEPATH, verify=VERIFY) + infraction = deployer.deploy(project.InfractionCollector) + deployments = [infraction] + deployer.finalize(deployments=deployments) + From c822ab5ecaa568a5ea1c312485362214f48fbf1b Mon Sep 17 00:00:00 2001 From: James Campbell Date: Fri, 16 Aug 2024 13:44:16 +0100 Subject: [PATCH 27/31] Add initialize function back into InfractionCollector --- contracts/contracts/coordination/InfractionCollector.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/contracts/contracts/coordination/InfractionCollector.sol b/contracts/contracts/coordination/InfractionCollector.sol index 654c62f48..6788aecb2 100644 --- a/contracts/contracts/coordination/InfractionCollector.sol +++ b/contracts/contracts/coordination/InfractionCollector.sol @@ -36,6 +36,10 @@ contract InfractionCollector is OwnableUpgradeable { _disableInitializers(); } + function initialize() external initializer { + __Ownable_init(msg.sender); + } + function reportMissingTranscript( uint32 ritualId, address[] calldata stakingProviders From aa07f06fe9f1c6ec19eff9e8e8ec459eae2f6488 Mon Sep 17 00:00:00 2001 From: James Campbell Date: Fri, 16 Aug 2024 13:45:00 +0100 Subject: [PATCH 28/31] Remove duplicated IEncryptionAuthorizer --- contracts/contracts/testnet/OpenAccessAuthorizer.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/contracts/testnet/OpenAccessAuthorizer.sol b/contracts/contracts/testnet/OpenAccessAuthorizer.sol index 717133f76..dabda7463 100644 --- a/contracts/contracts/testnet/OpenAccessAuthorizer.sol +++ b/contracts/contracts/testnet/OpenAccessAuthorizer.sol @@ -1,5 +1,4 @@ // SPDX-License-Identifier: AGPL-3.0-or-later - pragma solidity ^0.8.0; import "../coordination/IEncryptionAuthorizer.sol"; From 837a3884f1859ccc0926fb9b3c969b47e5fc2cdf Mon Sep 17 00:00:00 2001 From: James Campbell Date: Tue, 20 Aug 2024 13:38:53 +0100 Subject: [PATCH 29/31] Apply PR suggestions - Move enum - get tacoChildApplication from Coordinator - infractions -> infractionsForRitual and value bool -> int256 - tests readability - replace `if` with `require` --- .../coordination/InfractionCollector.sol | 43 ++++++++----------- .../constructor_params/lynx/infraction.yml | 2 - .../constructor_params/tapir/infraction.yml | 2 - tests/test_infraction.py | 34 +++++++-------- 4 files changed, 33 insertions(+), 48 deletions(-) diff --git a/contracts/contracts/coordination/InfractionCollector.sol b/contracts/contracts/coordination/InfractionCollector.sol index 6788aecb2..0a5a536d2 100644 --- a/contracts/contracts/coordination/InfractionCollector.sol +++ b/contracts/contracts/coordination/InfractionCollector.sol @@ -12,27 +12,24 @@ contract InfractionCollector is OwnableUpgradeable { address indexed stakingProvider, InfractionType infractionType ); - Coordinator public immutable coordinator; - - // Reference to the TACoChildApplication contract - ITACoChildApplication public immutable tacoChildApplication; - // 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 => bool))) - public infractions; + mapping(uint32 ritualId => mapping(address stakingProvider => mapping(InfractionType => uint256))) + public infractionsForRitual; - constructor(Coordinator _coordinator, ITACoChildApplication _tacoChildApplication) { + constructor(Coordinator _coordinator) { require( - address(_coordinator) != address(0) && address(_tacoChildApplication) != address(0), + address(_coordinator) != address(0), "Contracts must be specified" ); coordinator = _coordinator; - tacoChildApplication = _tacoChildApplication; + tacoChildApplication = coordinator.application(); _disableInitializers(); } @@ -53,26 +50,22 @@ contract InfractionCollector is OwnableUpgradeable { for (uint256 i = 0; i < stakingProviders.length; i++) { // Check if the infraction has already been reported require( - !infractions[ritualId][stakingProviders[i]][InfractionType.MISSING_TRANSCRIPT], + infractionsForRitual[ritualId][stakingProviders[i]][InfractionType.MISSING_TRANSCRIPT] == 0, "Infraction already reported" ); Coordinator.Participant memory participant = coordinator.getParticipantFromProvider( ritualId, stakingProviders[i] ); - if (participant.transcript.length == 0) { - // Transcript TX wasn't posted - // Penalize the staking provider - // tacoChildApplication.penalize(stakingProviders[i]); - infractions[ritualId][stakingProviders[i]][ - InfractionType.MISSING_TRANSCRIPT - ] = true; - emit InfractionReported( - ritualId, - stakingProviders[i], - InfractionType.MISSING_TRANSCRIPT - ); - } + 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 + ); } } } diff --git a/deployment/constructor_params/lynx/infraction.yml b/deployment/constructor_params/lynx/infraction.yml index c8d30ff01..3b0ee3dd0 100644 --- a/deployment/constructor_params/lynx/infraction.yml +++ b/deployment/constructor_params/lynx/infraction.yml @@ -9,11 +9,9 @@ artifacts: constants: # See deployment/artifacts/lynx.json COORDINATOR_PROXY: "0xE9e94499bB0f67b9DBD75506ec1735486DE57770" - TACO_CHILD_PROXY: "0x42F30AEc1A36995eEFaf9536Eb62BD751F982D32" contracts: - InfractionCollector: proxy: constructor: _coordinator: $COORDINATOR_PROXY - _tacoChildApplication: $TACO_CHILD_PROXY diff --git a/deployment/constructor_params/tapir/infraction.yml b/deployment/constructor_params/tapir/infraction.yml index 8c2f03cf1..f0e589e1a 100644 --- a/deployment/constructor_params/tapir/infraction.yml +++ b/deployment/constructor_params/tapir/infraction.yml @@ -9,11 +9,9 @@ artifacts: constants: # See deployment/artifacts/tapir.json COORDINATOR_PROXY: "0xE690b6bCC0616Dc5294fF84ff4e00335cA52C388" - TACO_CHILD_PROXY: "0x489287Ed5BdF7a35fEE411FBdCc47331093D0769" contracts: - InfractionCollector: proxy: constructor: _coordinator: $COORDINATOR_PROXY - _tacoChildApplication: $TACO_CHILD_PROXY diff --git a/tests/test_infraction.py b/tests/test_infraction.py index c14454411..0918de764 100644 --- a/tests/test_infraction.py +++ b/tests/test_infraction.py @@ -11,16 +11,12 @@ DURATION = 48 * 60 * 60 ONE_DAY = 24 * 60 * 60 -RitualState = IntEnum( - "RitualState", +RITUAL_ID = 0 + +infraction_types = IntEnum( + "InfractionType", [ - "NON_INITIATED", - "DKG_AWAITING_TRANSCRIPTS", - "DKG_AWAITING_AGGREGATIONS", - "DKG_TIMEOUT", - "DKG_INVALID", - "ACTIVE", - "EXPIRED", + "MISSING_TRANSCRIPT", ], start=0, ) @@ -112,8 +108,8 @@ def global_allow_list(project, deployer, coordinator): @pytest.fixture -def infraction_collector(project, deployer, coordinator, application): - contract = project.InfractionCollector.deploy(coordinator.address, application.address, sender=deployer) +def infraction_collector(project, deployer, coordinator): + contract = project.InfractionCollector.deploy(coordinator.address, sender=deployer) return contract def test_no_infractions(erc20, nodes, initiator, global_allow_list, infraction_collector, coordinator): @@ -144,14 +140,14 @@ def test_partial_infractions(erc20, nodes, initiator, global_allow_list, infract transcript = os.urandom(transcript_size(len(nodes), len(nodes))) # post transcript for half of nodes for node in nodes[:len(nodes) // 2]: - coordinator.postTranscript(0, transcript, sender=node) + coordinator.postTranscript(RITUAL_ID, transcript, sender=node) chain.pending_timestamp += TIMEOUT * 2 - infraction_collector.reportMissingTranscript(0, nodes, sender=initiator) + infraction_collector.reportMissingTranscript(RITUAL_ID, nodes[len(nodes) // 2:], sender=initiator) # first half of nodes should be fine, second half should be infracted for node in nodes[:len(nodes) // 2]: - assert infraction_collector.infractions(0, node, 0) == False + assert not infraction_collector.infractionsForRitual(RITUAL_ID, node, infraction_types.MISSING_TRANSCRIPT) for node in nodes[len(nodes) // 2:]: - assert infraction_collector.infractions(0, node, 0) == True + assert infraction_collector.infractionsForRitual(RITUAL_ID, node, infraction_types.MISSING_TRANSCRIPT) def test_report_infractions(erc20, nodes, initiator, global_allow_list, infraction_collector, coordinator, chain): cost = coordinator.getRitualInitiationCost(nodes, DURATION) @@ -163,9 +159,9 @@ def test_report_infractions(erc20, nodes, initiator, global_allow_list, infracti nodes, initiator, DURATION, global_allow_list.address, sender=initiator ) chain.pending_timestamp += TIMEOUT * 2 - infraction_collector.reportMissingTranscript(0, nodes, sender=initiator) + infraction_collector.reportMissingTranscript(RITUAL_ID, nodes, sender=initiator) for node in nodes: - assert infraction_collector.infractions(0, node, 0) == True + assert infraction_collector.infractionsForRitual(RITUAL_ID, node, infraction_types.MISSING_TRANSCRIPT) def test_cant_report_infractions_twice(erc20, nodes, initiator, global_allow_list, infraction_collector, coordinator, chain): cost = coordinator.getRitualInitiationCost(nodes, DURATION) @@ -177,7 +173,7 @@ def test_cant_report_infractions_twice(erc20, nodes, initiator, global_allow_lis nodes, initiator, DURATION, global_allow_list.address, sender=initiator ) chain.pending_timestamp += TIMEOUT * 2 - infraction_collector.reportMissingTranscript(0, nodes, sender=initiator) + infraction_collector.reportMissingTranscript(RITUAL_ID, nodes, sender=initiator) with ape.reverts("Infraction already reported"): - infraction_collector.reportMissingTranscript(0, nodes, sender=initiator) \ No newline at end of file + infraction_collector.reportMissingTranscript(RITUAL_ID, nodes, sender=initiator) \ No newline at end of file From 0246332f7c78da7c77b91934d365bd08f87eee55 Mon Sep 17 00:00:00 2001 From: James Campbell Date: Tue, 20 Aug 2024 14:41:32 +0100 Subject: [PATCH 30/31] Update Infraction tests to align with Coordinator after rebase --- tests/test_infraction.py | 94 +++++++++++++++++++++++++--------------- 1 file changed, 60 insertions(+), 34 deletions(-) diff --git a/tests/test_infraction.py b/tests/test_infraction.py index 0918de764..f69721a46 100644 --- a/tests/test_infraction.py +++ b/tests/test_infraction.py @@ -79,12 +79,10 @@ def erc20(project, initiator): @pytest.fixture() -def coordinator(project, deployer, application, erc20, initiator, oz_dependency): +def coordinator(project, deployer, application, oz_dependency): admin = deployer contract = project.Coordinator.deploy( application.address, - erc20.address, - FEE_RATE, sender=deployer, ) @@ -96,8 +94,6 @@ def coordinator(project, deployer, application, erc20, initiator, oz_dependency) sender=deployer, ) proxy_contract = project.Coordinator.at(proxy.address) - - proxy_contract.grantRole(contract.INITIATOR_ROLE(), initiator, sender=admin) return proxy_contract @@ -107,19 +103,32 @@ def global_allow_list(project, deployer, coordinator): return contract +@pytest.fixture() +def fee_model(project, deployer, coordinator, erc20, treasury): + contract = project.FlatRateFeeModel.deploy( + coordinator.address, erc20.address, FEE_RATE, sender=deployer + ) + coordinator.grantRole(coordinator.TREASURY_ROLE(), treasury, sender=deployer) + coordinator.approveFeeModel(contract.address, sender=treasury) + return contract + + @pytest.fixture def infraction_collector(project, deployer, coordinator): contract = project.InfractionCollector.deploy(coordinator.address, sender=deployer) return contract -def test_no_infractions(erc20, nodes, initiator, global_allow_list, infraction_collector, coordinator): - cost = coordinator.getRitualInitiationCost(nodes, DURATION) + +def test_no_infractions( + erc20, nodes, initiator, global_allow_list, infraction_collector, coordinator, fee_model +): + cost = fee_model.getRitualCost(len(nodes), DURATION) for node in nodes: public_key = gen_public_key() - coordinator.setProviderPublicKey(public_key, sender=node) - erc20.approve(coordinator.address, cost, sender=initiator) + coordinator.setProviderPublicKey(public_key, sender=node) + erc20.approve(fee_model.address, cost, sender=initiator) coordinator.initiateRitual( - nodes, initiator, DURATION, global_allow_list.address, sender=initiator + fee_model, nodes, initiator, DURATION, global_allow_list.address, sender=initiator ) transcript = os.urandom(transcript_size(len(nodes), len(nodes))) for node in nodes: @@ -128,52 +137,69 @@ def test_no_infractions(erc20, nodes, initiator, global_allow_list, infraction_c with ape.reverts("Ritual must have failed"): infraction_collector.reportMissingTranscript(0, nodes, sender=initiator) -def test_partial_infractions(erc20, nodes, initiator, global_allow_list, infraction_collector, coordinator, chain): - cost = coordinator.getRitualInitiationCost(nodes, DURATION) + +def test_partial_infractions( + erc20, nodes, initiator, global_allow_list, infraction_collector, coordinator, chain, fee_model +): + cost = fee_model.getRitualCost(len(nodes), DURATION) for node in nodes: public_key = gen_public_key() coordinator.setProviderPublicKey(public_key, sender=node) - erc20.approve(coordinator.address, cost, sender=initiator) + erc20.approve(fee_model.address, cost, sender=initiator) coordinator.initiateRitual( - nodes, initiator, DURATION, global_allow_list.address, sender=initiator + fee_model, nodes, initiator, DURATION, global_allow_list.address, sender=initiator ) transcript = os.urandom(transcript_size(len(nodes), len(nodes))) # post transcript for half of nodes - for node in nodes[:len(nodes) // 2]: + for node in nodes[: len(nodes) // 2]: coordinator.postTranscript(RITUAL_ID, transcript, sender=node) chain.pending_timestamp += TIMEOUT * 2 - infraction_collector.reportMissingTranscript(RITUAL_ID, nodes[len(nodes) // 2:], sender=initiator) + infraction_collector.reportMissingTranscript( + RITUAL_ID, nodes[len(nodes) // 2 :], sender=initiator + ) # first half of nodes should be fine, second half should be infracted - for node in nodes[:len(nodes) // 2]: - assert not infraction_collector.infractionsForRitual(RITUAL_ID, node, infraction_types.MISSING_TRANSCRIPT) - for node in nodes[len(nodes) // 2:]: - assert infraction_collector.infractionsForRitual(RITUAL_ID, node, infraction_types.MISSING_TRANSCRIPT) - -def test_report_infractions(erc20, nodes, initiator, global_allow_list, infraction_collector, coordinator, chain): - cost = coordinator.getRitualInitiationCost(nodes, DURATION) + for node in nodes[: len(nodes) // 2]: + assert not infraction_collector.infractionsForRitual( + RITUAL_ID, node, infraction_types.MISSING_TRANSCRIPT + ) + for node in nodes[len(nodes) // 2 :]: + assert infraction_collector.infractionsForRitual( + RITUAL_ID, node, infraction_types.MISSING_TRANSCRIPT + ) + + +def test_report_infractions( + erc20, nodes, initiator, global_allow_list, infraction_collector, coordinator, chain, fee_model +): + cost = fee_model.getRitualCost(len(nodes), DURATION) for node in nodes: public_key = gen_public_key() - coordinator.setProviderPublicKey(public_key, sender=node) - erc20.approve(coordinator.address, cost, sender=initiator) + coordinator.setProviderPublicKey(public_key, sender=node) + erc20.approve(fee_model.address, cost, sender=initiator) coordinator.initiateRitual( - nodes, initiator, DURATION, global_allow_list.address, sender=initiator + fee_model, nodes, initiator, DURATION, global_allow_list.address, sender=initiator ) chain.pending_timestamp += TIMEOUT * 2 infraction_collector.reportMissingTranscript(RITUAL_ID, nodes, sender=initiator) for node in nodes: - assert infraction_collector.infractionsForRitual(RITUAL_ID, node, infraction_types.MISSING_TRANSCRIPT) + assert infraction_collector.infractionsForRitual( + RITUAL_ID, node, infraction_types.MISSING_TRANSCRIPT + ) -def test_cant_report_infractions_twice(erc20, nodes, initiator, global_allow_list, infraction_collector, coordinator, chain): - cost = coordinator.getRitualInitiationCost(nodes, DURATION) + +def test_cant_report_infractions_twice( + erc20, nodes, initiator, global_allow_list, infraction_collector, coordinator, chain, fee_model +): + cost = fee_model.getRitualCost(len(nodes), DURATION) for node in nodes: public_key = gen_public_key() - coordinator.setProviderPublicKey(public_key, sender=node) - erc20.approve(coordinator.address, cost, sender=initiator) + coordinator.setProviderPublicKey(public_key, sender=node) + erc20.approve(fee_model.address, cost, sender=initiator) coordinator.initiateRitual( - nodes, initiator, DURATION, global_allow_list.address, sender=initiator + fee_model, nodes, initiator, DURATION, global_allow_list.address, sender=initiator ) chain.pending_timestamp += TIMEOUT * 2 infraction_collector.reportMissingTranscript(RITUAL_ID, nodes, sender=initiator) - + with ape.reverts("Infraction already reported"): - infraction_collector.reportMissingTranscript(RITUAL_ID, nodes, sender=initiator) \ No newline at end of file + infraction_collector.reportMissingTranscript(RITUAL_ID, nodes, sender=initiator) From b97dd0339e71f70e0c432cfae63ae301f7669afa Mon Sep 17 00:00:00 2001 From: James Campbell Date: Tue, 20 Aug 2024 14:56:20 +0100 Subject: [PATCH 31/31] Fix solhint errors --- contracts/contracts/coordination/InfractionCollector.sol | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/contracts/contracts/coordination/InfractionCollector.sol b/contracts/contracts/coordination/InfractionCollector.sol index 0a5a536d2..6eeb5181a 100644 --- a/contracts/contracts/coordination/InfractionCollector.sol +++ b/contracts/contracts/coordination/InfractionCollector.sol @@ -24,10 +24,7 @@ contract InfractionCollector is OwnableUpgradeable { public infractionsForRitual; constructor(Coordinator _coordinator) { - require( - address(_coordinator) != address(0), - "Contracts must be specified" - ); + require(address(_coordinator) != address(0), "Contracts must be specified"); coordinator = _coordinator; tacoChildApplication = coordinator.application(); _disableInitializers(); @@ -50,7 +47,9 @@ contract InfractionCollector is OwnableUpgradeable { 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, + infractionsForRitual[ritualId][stakingProviders[i]][ + InfractionType.MISSING_TRANSCRIPT + ] == 0, "Infraction already reported" ); Coordinator.Participant memory participant = coordinator.getParticipantFromProvider(