From 53cb1196d4f418b12efe84eb50ad1b301f2545b7 Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Sat, 20 Apr 2024 19:50:13 -0400 Subject: [PATCH] TACoChildApplication: endpoint for sending penalty to the root app --- .../coordination/ITACoChildToRoot.sol | 2 ++ .../coordination/TACoChildApplication.sol | 27 ++++++++++++++++--- contracts/contracts/testnet/LynxSet.sol | 7 +++++ contracts/test/CoordinatorTestSet.sol | 3 +++ .../test/TACoChildApplicationTestSet.sol | 5 ++++ tests/test_child_application.py | 18 ++++++++++++- 6 files changed, 58 insertions(+), 4 deletions(-) diff --git a/contracts/contracts/coordination/ITACoChildToRoot.sol b/contracts/contracts/coordination/ITACoChildToRoot.sol index 2bb336dd..4bfb5e73 100644 --- a/contracts/contracts/coordination/ITACoChildToRoot.sol +++ b/contracts/contracts/coordination/ITACoChildToRoot.sol @@ -15,4 +15,6 @@ interface ITACoChildToRoot { event OperatorConfirmed(address indexed stakingProvider, address indexed operator); function confirmOperatorAddress(address operator) external; + + function penalize(address stakingProvider) external; } diff --git a/contracts/contracts/coordination/TACoChildApplication.sol b/contracts/contracts/coordination/TACoChildApplication.sol index a946452d..2d4f13c4 100644 --- a/contracts/contracts/coordination/TACoChildApplication.sol +++ b/contracts/contracts/coordination/TACoChildApplication.sol @@ -14,6 +14,12 @@ import "./Coordinator.sol"; * @notice TACoChildApplication */ contract TACoChildApplication is ITACoRootToChild, ITACoChildApplication, Initializable { + /** + * @notice Signals that the staking provider was penalized + * @param stakingProvider Staking provider address + */ + event Penalized(address indexed stakingProvider); + struct StakingProviderInfo { address operator; uint96 authorized; @@ -25,6 +31,7 @@ contract TACoChildApplication is ITACoRootToChild, ITACoChildApplication, Initia ITACoChildToRoot public immutable rootApplication; address public coordinator; + address public adjudicator; uint96 public immutable minimumAuthorization; @@ -54,14 +61,18 @@ contract TACoChildApplication is ITACoRootToChild, ITACoChildApplication, Initia /** * @notice Initialize function for using with OpenZeppelin proxy */ - function initialize(address _coordinator) external initializer { - require(coordinator == address(0), "Coordinator already set"); - require(_coordinator != address(0), "Coordinator must be specified"); + function initialize(address _coordinator, address _adjudicator) external initializer { + require(coordinator == address(0) || _adjudicator == address(0), "Contracts already set"); + require( + _coordinator != address(0) && _adjudicator != address(0), + "Contracts must be specified" + ); require( address(Coordinator(_coordinator).application()) == address(this), "Invalid coordinator" ); coordinator = _coordinator; + adjudicator = _adjudicator; } function authorizedStake(address _stakingProvider) external view returns (uint96) { @@ -184,6 +195,16 @@ contract TACoChildApplication is ITACoRootToChild, ITACoChildApplication, Initia rootApplication.confirmOperatorAddress(_operator); } + /** + * @notice Penalize the staking provider's future reward + * @param _stakingProvider Staking provider address + */ + function penalize(address _stakingProvider) external override { + require(msg.sender == address(adjudicator), "Only adjudicator allowed to penalize"); + rootApplication.penalize(_stakingProvider); + emit Penalized(_stakingProvider); + } + /** * @notice Return the length of the array of staking providers */ diff --git a/contracts/contracts/testnet/LynxSet.sol b/contracts/contracts/testnet/LynxSet.sol index 57f13029..c0ac099d 100644 --- a/contracts/contracts/testnet/LynxSet.sol +++ b/contracts/contracts/testnet/LynxSet.sol @@ -34,6 +34,10 @@ contract MockPolygonRoot is Ownable, ITACoChildToRoot, ITACoRootToChild { rootApplication.confirmOperatorAddress(operator); } + function penalize(address _stakingProvider) external override onlyOwner { + rootApplication.penalize(_stakingProvider); + } + // solhint-disable-next-line no-empty-blocks function updateOperator(address stakingProvider, address operator) external {} @@ -87,6 +91,9 @@ contract MockPolygonChild is Ownable, ITACoChildToRoot, ITACoRootToChild { // solhint-disable-next-line no-empty-blocks function confirmOperatorAddress(address _operator) external override {} + + // solhint-disable-next-line no-empty-blocks + function penalize(address _stakingProvider) external override {} } contract LynxRitualToken is ERC20("LynxRitualToken", "LRT") { diff --git a/contracts/test/CoordinatorTestSet.sol b/contracts/test/CoordinatorTestSet.sol index bbc21ff7..4d15b480 100644 --- a/contracts/test/CoordinatorTestSet.sol +++ b/contracts/test/CoordinatorTestSet.sol @@ -29,6 +29,9 @@ contract ChildApplicationForCoordinatorMock is ITACoChildApplication { function confirmOperatorAddress(address _operator) external { confirmations[_operator] = true; } + + // solhint-disable-next-line no-empty-blocks + function penalize(address _stakingProvider) external {} } // /** diff --git a/contracts/test/TACoChildApplicationTestSet.sol b/contracts/test/TACoChildApplicationTestSet.sol index 1d27b80a..05036907 100644 --- a/contracts/test/TACoChildApplicationTestSet.sol +++ b/contracts/test/TACoChildApplicationTestSet.sol @@ -12,6 +12,7 @@ contract RootApplicationForTACoChildApplicationMock { ITACoRootToChild public childApplication; mapping(address => bool) public confirmations; + mapping(address => bool) public penalties; function setChildApplication(ITACoRootToChild _childApplication) external { childApplication = _childApplication; @@ -43,6 +44,10 @@ contract RootApplicationForTACoChildApplicationMock { confirmations[_operator] = true; } + function penalize(address _stakingProvider) external { + penalties[_stakingProvider] = true; + } + function resetConfirmation(address _operator) external { confirmations[_operator] = false; } diff --git a/tests/test_child_application.py b/tests/test_child_application.py index 34c8645c..f5e52b20 100644 --- a/tests/test_child_application.py +++ b/tests/test_child_application.py @@ -56,7 +56,7 @@ def coordinator(project, child_application, creator): contract = project.CoordinatorForTACoChildApplicationMock.deploy( child_application, sender=creator ) - child_application.initialize(contract.address, sender=creator) + child_application.initialize(contract.address, creator, sender=creator) return contract @@ -321,3 +321,19 @@ def test_confirm_address(accounts, root_application, child_application, coordina all_locked, staking_providers = child_application.getActiveStakingProviders(0, 0) assert all_locked == 0 assert len(staking_providers) == 0 + + +def test_penalize(accounts, root_application, child_application, coordinator): + ( + creator, + staking_provider, + *everyone_else, + ) = accounts[0:] + + # Penalize can be done only from adjudicator address + with ape.reverts("Only adjudicator allowed to penalize"): + child_application.penalize(staking_provider, sender=staking_provider) + + tx = child_application.penalize(staking_provider, sender=creator) + assert root_application.penalties(staking_provider) + assert tx.events == [child_application.Penalized(stakingProvider=staking_provider)]