From 0d05a3ce35a802ac879a6d0e756630f8b5e97e94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Tue, 25 Jul 2023 13:40:51 +0200 Subject: [PATCH 01/18] FxBaseChildTunnel can't emit events on _processMessageFromRoot callback See https://github.com/0xPolygon/fx-portal/blob/296ac8d41579f98d3a4dfb6d41737fae272a30ba/contracts/tunnel/FxBaseChildTunnel.sol#L58 --- contracts/xchain/PolygonChild.sol | 3 --- 1 file changed, 3 deletions(-) diff --git a/contracts/xchain/PolygonChild.sol b/contracts/xchain/PolygonChild.sol index 53415186..0a0fa040 100644 --- a/contracts/xchain/PolygonChild.sol +++ b/contracts/xchain/PolygonChild.sol @@ -5,8 +5,6 @@ import "@fx-portal/contracts/tunnel/FxBaseChildTunnel.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; contract PolygonChild is FxBaseChildTunnel, Ownable { - event MessageReceived(address indexed sender, bytes data); - address public stakeInfoAddress; constructor(address _fxChild) FxBaseChildTunnel(_fxChild) {} @@ -16,7 +14,6 @@ contract PolygonChild is FxBaseChildTunnel, Ownable { address sender, bytes memory data ) internal override validateSender(sender) { - emit MessageReceived(sender, data); // solhint-disable-next-line avoid-low-level-calls stakeInfoAddress.call(data); } From 38870ed8c946864b6bc6eea27310301baa434c3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Tue, 25 Jul 2023 16:29:58 +0200 Subject: [PATCH 02/18] FxPortal Deployment info: FxRoot, FxChild, CheckpointManager For both Ethereum Mainnet - Polygon Mainnet and Ethereum Goerli - Polygon Mumbai. See https://wiki.polygon.technology/docs/pos/design/bridge/l1-l2-communication/state-transfer/#prerequisites --- ape-config.yaml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/ape-config.yaml b/ape-config.yaml index 9727fd79..985f129d 100644 --- a/ape-config.yaml +++ b/ape-config.yaml @@ -36,12 +36,15 @@ deployments: mainnet: - contract_type: DAI address: '0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063' + - contract_type: fx_child + address: '0x8397259c983751DAf40400790063935a11afa28a' mumbai: - contract_type: DAI address: '0x001B3B4d0F3714Ca98ba10F6042DaEbF0B1B7b6F' - contract_type: StakeInfo address: '0xC1379866Fb0c100DCBFAb7b470009C4827D47DD8' - - fx_child: '0xCf73231F28B7331BBe3124B907840A94851f9f11' + - contract_type: fx_child + address: '0xCf73231F28B7331BBe3124B907840A94851f9f11' - verify: False ethereum: local: @@ -83,6 +86,10 @@ deployments: max_dkg_size: 8 pre_application: '0x685b8Fd02aB87d8FfFff7346cB101A5cE4185bf3' verify: True + - contract_type: fx_root + address: '0x3d1d3E34f7fB6D26245E6640E1c50710eFFf15bA' + - contract_type: checkpoint_manager + address: '0x2890bA17EfE978480615e330ecB65333b880928e' mumbai: - stake_info_contract: '0xC1379866Fb0c100DCBFAb7b470009C4827D47DD8' max_dkg_size: 16 @@ -98,6 +105,10 @@ deployments: pre_min_authorization: 40000000000000000000000 pre_min_operator_seconds: 86400 # one day in seconds verify: True + - contract_type: fx_root + address: '0xfe5e5D361b2ad62c541bAb87C45a0B9B018389a2' + - contract_type: checkpoint_manager + address: '0x86e4dc95c7fbdbf52e33d563bbdb00823894c287' test: From e54455beeb830840d1c4acc3a4024a8adff2a054 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Tue, 25 Jul 2023 16:33:59 +0200 Subject: [PATCH 03/18] Set FxChildTunnel on PolygonRoot constructor Instead of setting the child tunnel with setFxChildTunnel(), which could be prone to front-running. --- contracts/xchain/PolygonRoot.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/contracts/xchain/PolygonRoot.sol b/contracts/xchain/PolygonRoot.sol index 71def5e6..7c393e76 100644 --- a/contracts/xchain/PolygonRoot.sol +++ b/contracts/xchain/PolygonRoot.sol @@ -11,10 +11,12 @@ contract PolygonRoot is FxBaseRootTunnel, IUpdatableStakeInfo { constructor( address _checkpointManager, address _fxRoot, - address _source + address _source, + address _fxChildTunnel ) FxBaseRootTunnel(_checkpointManager, _fxRoot) { require(_source != address(0), "Wrong input parameters"); source = _source; + fxChildTunnel = _fxChildTunnel; } /** From f2953e716e94a32cdb76ba291f400df3fe666f70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Tue, 25 Jul 2023 16:35:48 +0200 Subject: [PATCH 04/18] Grant DEFAULT_ADMIN_ROLE on StakeInfo to the deployer This way they can grant/revoke updaters, which is useful for testnets, and the admin can revoke themselves if the updaters list is considered final. --- contracts/contracts/coordination/StakeInfo.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/contracts/coordination/StakeInfo.sol b/contracts/contracts/coordination/StakeInfo.sol index 250f3895..b4872f1a 100644 --- a/contracts/contracts/coordination/StakeInfo.sol +++ b/contracts/contracts/coordination/StakeInfo.sol @@ -20,6 +20,7 @@ contract StakeInfo is AccessControl, IUpdatableStakeInfo, IAccessControlApplicat } constructor(address[] memory updaters) { + _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); for (uint256 i = 0; i < updaters.length; i++) { _grantRole(UPDATE_ROLE, updaters[i]); } From 9551852c0b4f03f295dabf64ff75696d0ed3888f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Tue, 25 Jul 2023 17:09:36 +0200 Subject: [PATCH 05/18] Adapt xchain deployment script to new changes --- scripts/deploy_xchain_test.py | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/scripts/deploy_xchain_test.py b/scripts/deploy_xchain_test.py index 06cc59b8..83521571 100644 --- a/scripts/deploy_xchain_test.py +++ b/scripts/deploy_xchain_test.py @@ -1,15 +1,16 @@ from ape import accounts, config, networks, project -def deploy_eth_contracts(deployer): +def deploy_eth_contracts(deployer, source, child_address): # Connect to the Ethereum network with networks.ethereum.goerli.use_provider("infura"): DEPLOYMENTS_CONFIG = config.get_config("deployments")["ethereum"]["goerli"][0] - # Deploy the FxStateRootTunnel contract polygon_root = project.PolygonRoot.deploy( DEPLOYMENTS_CONFIG.get("checkpoint_manager"), DEPLOYMENTS_CONFIG.get("fx_root"), + source, + child_address, sender=deployer, publish=DEPLOYMENTS_CONFIG.get("verify"), ) @@ -22,32 +23,29 @@ def deploy_polygon_contracts(deployer): with networks.polygon.mumbai.use_provider("infura"): DEPLOYMENTS_CONFIG = config.get_config("deployments")["polygon"]["mumbai"][0] - # Deploy the FxStateChildTunnel contract - polygon_child = project.PolygonChild.deploy( - DEPLOYMENTS_CONFIG.get("fx_child"), + stake_info = project.StakeInfo.deploy( + [deployer.address, polygon_child.address], sender=deployer, publish=DEPLOYMENTS_CONFIG.get("verify"), ) - stake_info = project.StakeInfo.deploy( - [deployer.address, polygon_child.address], + + polygon_child = project.PolygonChild.deploy( + DEPLOYMENTS_CONFIG.get("fx_child"), + stake_info.address, sender=deployer, publish=DEPLOYMENTS_CONFIG.get("verify"), ) - return polygon_child, stake_info + return polygon_child, stake_info -def main(account_id=None): +# TODO: Figure out better way to retrieve the TACo app contract address +def main(taco_app, account_id=None): deployer = accounts.load("TGoerli") with accounts.use_sender(deployer): - root = deploy_eth_contracts(deployer) - child, stake_info = deploy_polygon_contracts(deployer) + child, _ = deploy_polygon_contracts(deployer) + root = deploy_eth_contracts(deployer, child.address, taco_app) # Set the root contract address in the child contract with networks.polygon.mumbai.use_provider("infura"): child.setFxRootTunnel(root.address) - child.setStakeInfoAddress(stake_info.address) - - # Set the child contract address in the root contract - with networks.ethereum.goerli.use_provider("infura"): - root.setFxChildTunnel(child.address) From ff108c7bf7f8a1409ee2348f2fdcc788dbd330f4 Mon Sep 17 00:00:00 2001 From: James Campbell Date: Tue, 1 Aug 2023 18:18:22 +0200 Subject: [PATCH 06/18] Improve logging of check_xchain script --- scripts/check_xchain.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/scripts/check_xchain.py b/scripts/check_xchain.py index 0b1ea5a3..06124cd1 100644 --- a/scripts/check_xchain.py +++ b/scripts/check_xchain.py @@ -1,20 +1,30 @@ import time -from ape import networks, project +from ape import accounts, networks, project def main(): + deployer = accounts.load("TGoerli") print("*******") print("WARNING: This script will take 40 mins to run to allow messages to sync from L1 to L2") print("*******") with networks.ethereum.goerli.use_provider("infura"): - root = project.PolygonRoot.at("0xdc90A337DF9561705EB85B92391ab8F55d114D53") + root = project.PolygonRoot.at("0x55D1E362b81FDC6BaA359630bf3Ffa5900F66777") root.updateOperator( "0x3B42d26E19FF860bC4dEbB920DD8caA53F93c600", "0x3B42d26E19FF860bC4dEbB920DD8caA53F93c600", + sender=deployer, ) - time.sleep(60 * 40) - with networks.polygon.mumbai.use_provider("infura"): - stake_info = project.StakeInfo.at("0x40D0107ACa3503CB345E4117a9F92E8220EEEb3C") - print(stake_info.operatorToProvider("0xAe87D865F3A507185656aD0ef52a8E0B9f3d58f8")) - print(stake_info.operatorToProvider("0x3B42d26E19FF860bC4dEbB920DD8caA53F93c600")) + # check every 5 minutes + print("Now: {}".format(time.time())) + for i in range(12): + time.sleep(60 * i * 5) + print("Now: {}".format(time.time())) + with networks.polygon.mumbai.use_provider("infura"): + stake_info = project.StakeInfo.at("0x96e7dBa88f79e5CCAEBf0c7678539F6C0d719c99") + print( + stake_info.stakingProviderFromOperator("0xAe87D865F3A507185656aD0ef52a8E0B9f3d58f8") + ) + print( + stake_info.stakingProviderFromOperator("0x3B42d26E19FF860bC4dEbB920DD8caA53F93c600") + ) From 1dfe46f162e7d3c5ce0ee3c1e76dda56a7c798d6 Mon Sep 17 00:00:00 2001 From: James Campbell Date: Tue, 1 Aug 2023 18:18:43 +0200 Subject: [PATCH 07/18] Fix bugs and improve config handling for deploying xchain scripts --- scripts/deploy_xchain_test.py | 79 +++++++++++++++++++++++++---------- 1 file changed, 57 insertions(+), 22 deletions(-) diff --git a/scripts/deploy_xchain_test.py b/scripts/deploy_xchain_test.py index 83521571..610b5b1a 100644 --- a/scripts/deploy_xchain_test.py +++ b/scripts/deploy_xchain_test.py @@ -1,51 +1,86 @@ +import click from ape import accounts, config, networks, project +from ape.cli import NetworkBoundCommand, account_option -def deploy_eth_contracts(deployer, source, child_address): - # Connect to the Ethereum network - with networks.ethereum.goerli.use_provider("infura"): - DEPLOYMENTS_CONFIG = config.get_config("deployments")["ethereum"]["goerli"][0] +def convert_config(config): + result = {} + for item in config: + if "contract_type" in item: + result[item["contract_type"]] = item["address"] + else: + result.update(item) + return result + +def deploy_eth_contracts(deployer, source, child_address, config, eth_network): + # Connect to the Ethereum network + with eth_network.use_provider("infura"): polygon_root = project.PolygonRoot.deploy( - DEPLOYMENTS_CONFIG.get("checkpoint_manager"), - DEPLOYMENTS_CONFIG.get("fx_root"), + config["checkpoint_manager"], + config["fx_root"], source, child_address, sender=deployer, - publish=DEPLOYMENTS_CONFIG.get("verify"), + publish=False, ) return polygon_root -def deploy_polygon_contracts(deployer): +def deploy_polygon_contracts(deployer, config, poly_network): # Connect to the Polygon network - with networks.polygon.mumbai.use_provider("infura"): - DEPLOYMENTS_CONFIG = config.get_config("deployments")["polygon"]["mumbai"][0] - + with poly_network.use_provider("infura"): stake_info = project.StakeInfo.deploy( - [deployer.address, polygon_child.address], + [deployer.address], sender=deployer, - publish=DEPLOYMENTS_CONFIG.get("verify"), + publish=False, ) polygon_child = project.PolygonChild.deploy( - DEPLOYMENTS_CONFIG.get("fx_child"), + config["fx_child"], stake_info.address, sender=deployer, - publish=DEPLOYMENTS_CONFIG.get("verify"), + publish=False, ) - return polygon_child, stake_info -# TODO: Figure out better way to retrieve the TACo app contract address -def main(taco_app, account_id=None): - deployer = accounts.load("TGoerli") + +# TODO: Figure out better way to retrieve the TACo app contract address +@click.command(cls=NetworkBoundCommand) +@click.option("--network_type", type=click.Choice(["mainnet", "testnet"])) +@account_option() +def cli(network_type, account): + deployer = account + if network_type == "mainnet": + eth_config = config.get_config("deployments")["ethereum"]["mainnet"] + poly_config = config.get_config("deployments")["polygon"]["mainnet"] + eth_network = networks.ethereum.mainnet + poly_network = networks.polygon.mainnet + elif network_type == "testnet": + eth_config = config.get_config("deployments")["ethereum"]["goerli"] + poly_config = config.get_config("deployments")["polygon"]["mumbai"] + eth_network = networks.ethereum.goerli + poly_network = networks.polygon.mumbai + + print("Deployer: {}".format(deployer)) + print("ETH CONFIG: {}".format(eth_config)) + print("POLYGON CONFIG: {}".format(poly_config)) + with accounts.use_sender(deployer): - child, _ = deploy_polygon_contracts(deployer) - root = deploy_eth_contracts(deployer, child.address, taco_app) + child, stake_info = deploy_polygon_contracts( + deployer, convert_config(poly_config), poly_network + ) + root = deploy_eth_contracts( + deployer, deployer.address, child.address, convert_config(eth_config), eth_network + ) # Set the root contract address in the child contract - with networks.polygon.mumbai.use_provider("infura"): + with poly_network.use_provider("infura"): child.setFxRootTunnel(root.address) + stake_info.addUpdaters([child.address]) + + print("CHILD: {}".format(child.address)) + print("STAKE INFO: {}".format(stake_info.address)) + print("ROOT: {}".format(root.address)) From a0acacd28ba66d2b36ee5b2630464363e0231990 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Tue, 29 Aug 2023 13:19:28 +0200 Subject: [PATCH 08/18] New coordinator deployment script Based on the FlatRateFeeModel script --- ape-config.yaml | 1 - scripts/deploy_coordinator_with_fee_model.py | 77 ++++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 scripts/deploy_coordinator_with_fee_model.py diff --git a/ape-config.yaml b/ape-config.yaml index 985f129d..1ffd5886 100644 --- a/ape-config.yaml +++ b/ape-config.yaml @@ -45,7 +45,6 @@ deployments: address: '0xC1379866Fb0c100DCBFAb7b470009C4827D47DD8' - contract_type: fx_child address: '0xCf73231F28B7331BBe3124B907840A94851f9f11' - - verify: False ethereum: local: - nu_token_supply: 1_000_000_000 diff --git a/scripts/deploy_coordinator_with_fee_model.py b/scripts/deploy_coordinator_with_fee_model.py new file mode 100644 index 00000000..5489d036 --- /dev/null +++ b/scripts/deploy_coordinator_with_fee_model.py @@ -0,0 +1,77 @@ +#!/usr/bin/python3 + +import os + +from ape import config, project, networks +from ape.cli import account_option, network_option, NetworkBoundCommand +from ape.utils import ZERO_ADDRESS + +from ape_etherscan.utils import API_KEY_ENV_KEY_MAP + +import click + + +@click.command(cls=NetworkBoundCommand) +@network_option() +@account_option() +@click.option('--currency', default=ZERO_ADDRESS) +@click.option('--rate', default=None) +@click.option('--timeout', default=None) +@click.option('--admin', default=None) +@click.option('--max_size', default=None) +@click.option('--verify/--no-verify', default=True) +def cli(network, account, currency, rate, timeout, admin, max_size, verify): + + deployer = account + click.echo(f"Deployer: {deployer}") + + if rate and currency == ZERO_ADDRESS: + raise ValueError("ERC20 contract address needed for currency") + + # Network + ecosystem_name = networks.provider.network.ecosystem.name + network_name = networks.provider.network.name + provider_name = networks.provider.name + click.echo(f"You are connected to network '{ecosystem_name}:{network_name}:{provider_name}'.") + + # TODO: Move this to a common deployment utilities module + # Validate Etherscan verification parameters. + # This import fails if called before the click network options are evaluated + from scripts.utils import LOCAL_BLOCKCHAIN_ENVIRONMENTS + is_public_deployment = network_name not in LOCAL_BLOCKCHAIN_ENVIRONMENTS + if not is_public_deployment: + verify = False + elif verify: + env_var_key = API_KEY_ENV_KEY_MAP.get(ecosystem_name) + api_key = os.environ.get(env_var_key) + print(api_key) + if not api_key: + raise ValueError(f"{env_var_key} is not set") + + # Use deployment information for currency, if possible + try: + deployments = config.deployments[ecosystem_name][network_name] + except KeyError: + pass # TODO: Further validate currency address? + else: + print(deployments) + try: + currency = next(d for d in deployments if d["contract_type"] == currency)["address"] + except StopIteration: + pass + + try: + stakes = next(d for d in deployments if d["contract_type"] == "StakeInfo")["address"] + except StopIteration: + raise ValueError("StakeInfo deployment needed") + + # Parameter defaults + admin = admin or deployer + rate = rate or 1 + timeout = timeout or 60*60 + max_size = max_size or 64 + + params = (stakes, timeout, max_size, admin, currency, rate) + print("Deployment parameters:", params) + return project.Coordinator.deploy(*params, sender=deployer, publish=verify) + \ No newline at end of file From 4edfe53b2236d7aeaddca807b685c4c2a14699b0 Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Thu, 24 Aug 2023 17:19:05 -0400 Subject: [PATCH 09/18] Rename StakeInfo and interfaces. Add confirmation from Polygon to Mainnet --- contracts/contracts/TACoApplication.sol | 53 ++++---- .../contracts/coordination/Coordinator.sol | 21 +-- .../coordination/ITACoChildToRoot.sol | 18 +++ .../coordination/ITACoRootToChild.sol | 18 +++ .../coordination/IUpdatableStakeInfo.sol | 18 --- .../contracts/coordination/StakeInfo.sol | 92 ------------- .../coordination/TACoChildApplication.sol | 122 ++++++++++++++++++ contracts/test/TACoApplicationTestSet.sol | 36 +++--- ...lication.sol => ITACoChildApplication.sol} | 4 +- contracts/xchain/PolygonChild.sol | 18 ++- contracts/xchain/PolygonRoot.sol | 40 +++--- 11 files changed, 253 insertions(+), 187 deletions(-) create mode 100644 contracts/contracts/coordination/ITACoChildToRoot.sol create mode 100644 contracts/contracts/coordination/ITACoRootToChild.sol delete mode 100644 contracts/contracts/coordination/IUpdatableStakeInfo.sol delete mode 100644 contracts/contracts/coordination/StakeInfo.sol create mode 100644 contracts/contracts/coordination/TACoChildApplication.sol rename contracts/threshold/{IAccessControlApplication.sol => ITACoChildApplication.sol} (73%) diff --git a/contracts/contracts/TACoApplication.sol b/contracts/contracts/TACoApplication.sol index bc4525b7..7d0b92e8 100644 --- a/contracts/contracts/TACoApplication.sol +++ b/contracts/contracts/TACoApplication.sol @@ -9,13 +9,14 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin-upgradeable/contracts/access/OwnableUpgradeable.sol"; import "@threshold/contracts/staking/IApplication.sol"; import "@threshold/contracts/staking/IStaking.sol"; -import "./coordination/IUpdatableStakeInfo.sol"; +import "./coordination/ITACoRootToChild.sol"; +import "./coordination/ITACoChildToRoot.sol"; /** * @title TACo Application * @notice Contract distributes rewards for participating in app and slashes for violating rules */ -contract TACoApplication is IApplication, OwnableUpgradeable { +contract TACoApplication is IApplication, ITACoChildToRoot, OwnableUpgradeable { using SafeERC20 for IERC20; using SafeCast for uint256; @@ -127,13 +128,6 @@ contract TACoApplication is IApplication, OwnableUpgradeable { uint256 startTimestamp ); - /** - * @notice Signals that an operator address is confirmed - * @param stakingProvider Staking provider address - * @param operator Operator address - */ - event OperatorConfirmed(address indexed stakingProvider, address indexed operator); - struct StakingProviderInfo { address operator; bool operatorConfirmed; @@ -153,7 +147,7 @@ contract TACoApplication is IApplication, OwnableUpgradeable { IStaking public immutable tStaking; IERC20 public immutable token; - IUpdatableStakeInfo public updatableStakeInfo; + ITACoRootToChild public childApplication; address public adjudicator; mapping(address => StakingProviderInfo) public stakingProviderInfo; @@ -237,16 +231,16 @@ contract TACoApplication is IApplication, OwnableUpgradeable { /** * @notice Set contract for multi-chain interactions */ - function setUpdatableStakeInfo(IUpdatableStakeInfo _updatableStakeInfo) external onlyOwner { + function setChildApplication(ITACoRootToChild _childApplication) external onlyOwner { require( - address(_updatableStakeInfo) != address(updatableStakeInfo), + address(_childApplication) != address(childApplication), "New address must not be equal to the current one" ); - if (address(_updatableStakeInfo) != address(0)) { + if (address(_childApplication) != address(0)) { // trying to call contract to be sure that is correct address - _updatableStakeInfo.updateOperator(address(0), address(0)); + _childApplication.updateOperator(address(0), address(0)); } - updatableStakeInfo = _updatableStakeInfo; + childApplication = _childApplication; } /** @@ -681,28 +675,29 @@ contract TACoApplication is IApplication, OwnableUpgradeable { info.operator = _operator; info.operatorStartTimestamp = uint64(block.timestamp); emit OperatorBonded(_stakingProvider, _operator, previousOperator, block.timestamp); - _releaseOperator(_stakingProvider); + + if (address(childApplication) != address(0)) { + childApplication.updateOperator(_stakingProvider, _operator); + } } /** * @notice Make a confirmation by operator */ - function confirmOperatorAddress() external { - address stakingProvider = _stakingProviderFromOperator[msg.sender]; + function confirmOperatorAddress(address _operator) external override { + require( + msg.sender == address(childApplication), + "Only StakeInfo contract allowed to confirm operator" + ); + address stakingProvider = _stakingProviderFromOperator[_operator]; require(isAuthorized(stakingProvider), "No stake associated with the operator"); StakingProviderInfo storage info = stakingProviderInfo[stakingProvider]; require(!info.operatorConfirmed, "Operator address is already confirmed"); - // solhint-disable-next-line avoid-tx-origin - require(msg.sender == tx.origin, "Only operator with real address can make a confirmation"); updateRewardInternal(stakingProvider); info.operatorConfirmed = true; authorizedOverall += info.authorized; - emit OperatorConfirmed(stakingProvider, msg.sender); - - if (address(updatableStakeInfo) != address(0)) { - updatableStakeInfo.updateOperator(stakingProvider, msg.sender); - } + emit OperatorConfirmed(stakingProvider, _operator); } //-------------------------XChain------------------------- @@ -712,8 +707,8 @@ contract TACoApplication is IApplication, OwnableUpgradeable { */ function _releaseOperator(address _stakingProvider) internal { stakingProviderInfo[_stakingProvider].operatorConfirmed = false; - if (address(updatableStakeInfo) != address(0)) { - updatableStakeInfo.updateOperator(_stakingProvider, address(0)); + if (address(childApplication) != address(0)) { + childApplication.updateOperator(_stakingProvider, address(0)); } } @@ -724,10 +719,10 @@ contract TACoApplication is IApplication, OwnableUpgradeable { address _stakingProvider, StakingProviderInfo storage _info ) internal { - if (address(updatableStakeInfo) != address(0)) { + if (address(childApplication) != address(0)) { // TODO send both authorized and eligible amounts in case of slashing from StakeInfo uint96 eligibleAmount = getEligibleAmount(_info); - updatableStakeInfo.updateAmount(_stakingProvider, eligibleAmount); + childApplication.updateAuthorization(_stakingProvider, eligibleAmount); } } diff --git a/contracts/contracts/coordination/Coordinator.sol b/contracts/contracts/coordination/Coordinator.sol index 95e4f309..4a566525 100644 --- a/contracts/contracts/coordination/Coordinator.sol +++ b/contracts/contracts/coordination/Coordinator.sol @@ -8,7 +8,7 @@ import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "./FlatRateFeeModel.sol"; import "./IReimbursementPool.sol"; import "../lib/BLS12381.sol"; -import "../../threshold/IAccessControlApplication.sol"; +import "../../threshold/ITACoChildApplication.sol"; import "./IEncryptionAuthorizer.sol"; /** @@ -83,7 +83,7 @@ contract Coordinator is AccessControlDefaultAdminRules, FlatRateFeeModel { bytes32 public constant INITIATOR_ROLE = keccak256("INITIATOR_ROLE"); bytes32 public constant TREASURY_ROLE = keccak256("TREASURY_ROLE"); - IAccessControlApplication public immutable application; + ITACoChildApplication public immutable application; Ritual[] public rituals; uint32 public timeout; @@ -97,14 +97,14 @@ contract Coordinator is AccessControlDefaultAdminRules, FlatRateFeeModel { mapping(bytes32 => uint32) internal ritualPublicKeyRegistry; constructor( - IAccessControlApplication _stakes, + ITACoChildApplication _application, uint32 _timeout, uint16 _maxDkgSize, address _admin, IERC20 _currency, uint256 _feeRatePerSecond ) AccessControlDefaultAdminRules(0, _admin) FlatRateFeeModel(_currency, _feeRatePerSecond) { - application = _stakes; + application = _application; timeout = _timeout; maxDkgSize = _maxDkgSize; } @@ -147,14 +147,19 @@ contract Coordinator is AccessControlDefaultAdminRules, FlatRateFeeModel { _setRoleAdmin(INITIATOR_ROLE, bytes32(0)); } - function setProviderPublicKey(BLS12381.G2Point calldata _publicKey) public { + function setProviderPublicKey(BLS12381.G2Point calldata _publicKey) external { uint32 lastRitualId = uint32(rituals.length); - address provider = application.stakingProviderFromOperator(msg.sender); + address stakingProvider = application.stakingProviderFromOperator(msg.sender); + require(stakingProvider != address(0), "Operator has no bond with staking provider"); // TODO ParticipantKey memory newRecord = ParticipantKey(lastRitualId, _publicKey); - keysHistory[provider].push(newRecord); + // keysHistory[stakingProvider][-1].publicKey != _publicKey; // TODO it's a question + keysHistory[stakingProvider].push(newRecord); - emit ParticipantPublicKeySet(lastRitualId, provider, _publicKey); + emit ParticipantPublicKeySet(lastRitualId, stakingProvider, _publicKey); + // solhint-disable-next-line avoid-tx-origin + require(msg.sender == tx.origin, "Only operator with real address can set public key"); // TODO + application.confirmOperatorAddress(msg.sender); } function getProviderPublicKey( diff --git a/contracts/contracts/coordination/ITACoChildToRoot.sol b/contracts/contracts/coordination/ITACoChildToRoot.sol new file mode 100644 index 00000000..2bb336dd --- /dev/null +++ b/contracts/contracts/coordination/ITACoChildToRoot.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.0; + +/** + * @title ITACoChildToRoot + * @notice Interface for x-chain interactions from coordinator to application + */ +interface ITACoChildToRoot { + /** + * @notice Signals that an operator address is confirmed + * @param stakingProvider Staking provider address + * @param operator Operator address + */ + event OperatorConfirmed(address indexed stakingProvider, address indexed operator); + + function confirmOperatorAddress(address operator) external; +} diff --git a/contracts/contracts/coordination/ITACoRootToChild.sol b/contracts/contracts/coordination/ITACoRootToChild.sol new file mode 100644 index 00000000..41d91077 --- /dev/null +++ b/contracts/contracts/coordination/ITACoRootToChild.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.0; + +/** + * @title ITACoRootToChild + * @notice Interface for x-chain interactions from application to coordinator + */ +interface ITACoRootToChild { + event OperatorUpdated(address indexed stakingProvider, address indexed operator); + event AuthorizationUpdated(address indexed stakingProvider, uint96 amount); + + function updateOperator(address stakingProvider, address operator) external; + + function updateAuthorization(address stakingProvider, uint96 amount) external; + + function batchUpdate(bytes32[] calldata updateInfo) external; +} diff --git a/contracts/contracts/coordination/IUpdatableStakeInfo.sol b/contracts/contracts/coordination/IUpdatableStakeInfo.sol deleted file mode 100644 index 59debac2..00000000 --- a/contracts/contracts/coordination/IUpdatableStakeInfo.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later - -pragma solidity ^0.8.0; - -/** - * @title IUpdatableStakeInfo - * @notice Interface for x-chain interactions between application and coordinator - */ -interface IUpdatableStakeInfo { - event UpdatedStakeOperator(address indexed stakingProvider, address indexed operator); - event UpdatedStakeAmount(address indexed stakingProvider, uint96 amount); - - function updateOperator(address stakingProvider, address operator) external; - - function updateAmount(address stakingProvider, uint96 amount) external; - - function batchUpdate(bytes32[] calldata updateInfo) external; -} diff --git a/contracts/contracts/coordination/StakeInfo.sol b/contracts/contracts/coordination/StakeInfo.sol deleted file mode 100644 index b4872f1a..00000000 --- a/contracts/contracts/coordination/StakeInfo.sol +++ /dev/null @@ -1,92 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/access/AccessControl.sol"; -import "./IUpdatableStakeInfo.sol"; -import "../../threshold/IAccessControlApplication.sol"; - -/** - * @title StakeInfo - * @notice StakeInfo - */ -contract StakeInfo is AccessControl, IUpdatableStakeInfo, IAccessControlApplication { - bytes32 public constant UPDATE_ROLE = keccak256("UPDATE_ROLE"); - - struct Stake { - address operator; - uint96 amount; - // TODO: what about undelegations etc? - } - - constructor(address[] memory updaters) { - _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); - for (uint256 i = 0; i < updaters.length; i++) { - _grantRole(UPDATE_ROLE, updaters[i]); - } - } - - mapping(address => Stake) public stakes; - mapping(address => address) private operatorToProvider; - - function stakingProviderFromOperator(address _operator) external view returns (address) { - return operatorToProvider[_operator]; - } - - function authorizedStake(address _stakingProvider) external view returns (uint96) { - return stakes[_stakingProvider].amount; - } - - function updateOperator( - address stakingProvider, - address operator - ) external override onlyRole(UPDATE_ROLE) { - _updateOperator(stakingProvider, operator); - } - - function updateAmount( - address stakingProvider, - uint96 amount - ) external override onlyRole(UPDATE_ROLE) { - _updateAmount(stakingProvider, amount); - } - - function _updateOperator(address stakingProvider, address operator) internal { - Stake storage stake = stakes[stakingProvider]; - address oldOperator = stake.operator; - - if (operator != oldOperator) { - stake.operator = operator; - // Update operator to provider mapping - operatorToProvider[oldOperator] = address(0); - operatorToProvider[operator] = stakingProvider; - - emit UpdatedStakeOperator(stakingProvider, operator); - } - } - - function _updateAmount(address stakingProvider, uint96 amount) internal { - Stake storage stake = stakes[stakingProvider]; - uint256 oldAmount = stake.amount; - - if (amount != oldAmount) { - stake.amount = amount; - emit UpdatedStakeAmount(stakingProvider, amount); - } - } - - function batchUpdate(bytes32[] calldata updateInfo) external override onlyRole(UPDATE_ROLE) { - require(updateInfo.length % 2 == 0, "bad length"); - for (uint256 i = 0; i < updateInfo.length; i += 2) { - bytes32 word0 = updateInfo[i]; - bytes32 word1 = updateInfo[i + 1]; - - address provider = address(bytes20(word0)); - uint96 amount = uint96(uint256(word0)); - address operator = address(bytes20(word1)); - - _updateOperator(provider, operator); - _updateAmount(provider, amount); - } - } -} diff --git a/contracts/contracts/coordination/TACoChildApplication.sol b/contracts/contracts/coordination/TACoChildApplication.sol new file mode 100644 index 00000000..d637611e --- /dev/null +++ b/contracts/contracts/coordination/TACoChildApplication.sol @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/access/AccessControl.sol"; +import "./ITACoRootToChild.sol"; +import "../../threshold/ITACoChildApplication.sol"; +import "./ITACoChildToRoot.sol"; + +/** + * @title TACoChildApplication + * @notice TACoChildApplication + */ +contract TACoChildApplication is AccessControl, ITACoRootToChild, ITACoChildApplication { + bytes32 public constant UPDATE_ROLE = keccak256("UPDATE_ROLE"); + bytes32 public constant DEPLOYER_ROLE = keccak256("DEPLOYER_ROLE"); + + struct StakingProviderInfo { + address operator; + bool operatorConfirmed; + uint96 authorized; + // TODO: what about undelegations etc? + } + + ITACoChildToRoot public immutable rootApplication; + address public coordinator; + + mapping(address => StakingProviderInfo) public stakingProviderInfo; + mapping(address => address) public stakingProviderFromOperator; + + constructor(ITACoChildToRoot _rootApplication, address[] memory updaters) { + require( + address(_rootApplication) != address(0), + "Address for root application must be specified" + ); + rootApplication = _rootApplication; + + _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); + for (uint256 i = 0; i < updaters.length; i++) { + _grantRole(UPDATE_ROLE, updaters[i]); + } + } + + function setCoordinator(address _coordinator) external onlyRole(DEPLOYER_ROLE) { + require(coordinator == address(0), "Coordinator already set"); + require(_coordinator != address(0), "Coordinator must be specified"); + // require(_coordinator.numberOfRituals() >= 0, "Invalid coordinator"); + coordinator = _coordinator; + + // TODO reset role? + } + + function authorizedStake(address _stakingProvider) external view returns (uint96) { + return stakingProviderInfo[_stakingProvider].authorized; + } + + function updateOperator( + address stakingProvider, + address operator + ) external override onlyRole(UPDATE_ROLE) { + _updateOperator(stakingProvider, operator); + } + + function updateAuthorization( + address stakingProvider, + uint96 amount + ) external override onlyRole(UPDATE_ROLE) { + _updateAuthorization(stakingProvider, amount); + } + + function _updateOperator(address stakingProvider, address operator) internal { + StakingProviderInfo storage info = stakingProviderInfo[stakingProvider]; + address oldOperator = info.operator; + + if (operator != oldOperator) { + info.operator = operator; + // Update operator to provider mapping + stakingProviderFromOperator[oldOperator] = address(0); + stakingProviderFromOperator[operator] = stakingProvider; + info.operatorConfirmed = false; + + emit OperatorUpdated(stakingProvider, operator); + } + } + + function _updateAuthorization(address stakingProvider, uint96 amount) internal { + StakingProviderInfo storage info = stakingProviderInfo[stakingProvider]; + uint96 fromAmount = info.authorized; + + if (amount != fromAmount) { + info.authorized = amount; + emit AuthorizationUpdated(stakingProvider, amount); + } + } + + function batchUpdate(bytes32[] calldata updateInfo) external override onlyRole(UPDATE_ROLE) { + require(updateInfo.length % 2 == 0, "bad length"); + for (uint256 i = 0; i < updateInfo.length; i += 2) { + bytes32 word0 = updateInfo[i]; + bytes32 word1 = updateInfo[i + 1]; + + address provider = address(bytes20(word0)); + uint96 amount = uint96(uint256(word0)); + address operator = address(bytes20(word1)); + + _updateOperator(provider, operator); + _updateAuthorization(provider, amount); + } + } + + function confirmOperatorAddress(address _operator) external override { + require(msg.sender == coordinator, "Only Coordinator allowed to confirm operator"); + address stakingProvider = stakingProviderFromOperator[_operator]; + StakingProviderInfo storage info = stakingProviderInfo[stakingProvider]; + if (info.operatorConfirmed) { + return; + } + require(info.authorized > 0, "No stake associated with the operator"); + info.operatorConfirmed = true; + rootApplication.confirmOperatorAddress(_operator); + } +} diff --git a/contracts/test/TACoApplicationTestSet.sol b/contracts/test/TACoApplicationTestSet.sol index 96941f50..982e8cca 100644 --- a/contracts/test/TACoApplicationTestSet.sol +++ b/contracts/test/TACoApplicationTestSet.sol @@ -140,21 +140,21 @@ contract ThresholdStakingForTACoApplicationMock { } } -/** - * @notice Intermediary contract for testing operator - */ -contract Intermediary { - TACoApplication public immutable application; - - constructor(TACoApplication _application) { - application = _application; - } - - function bondOperator(address _operator) external { - application.bondOperator(address(this), _operator); - } - - function confirmOperatorAddress() external { - application.confirmOperatorAddress(); - } -} +// /** +// * @notice Intermediary contract for testing operator +// */ +// contract Intermediary { +// TACoApplication public immutable application; + +// constructor(TACoApplication _application) { +// application = _application; +// } + +// function bondOperator(address _operator) external { +// application.bondOperator(address(this), _operator); +// } + +// function confirmOperatorAddress() external { +// application.confirmOperatorAddress(); +// } +// } diff --git a/contracts/threshold/IAccessControlApplication.sol b/contracts/threshold/ITACoChildApplication.sol similarity index 73% rename from contracts/threshold/IAccessControlApplication.sol rename to contracts/threshold/ITACoChildApplication.sol index 0320f147..a40490de 100644 --- a/contracts/threshold/IAccessControlApplication.sol +++ b/contracts/threshold/ITACoChildApplication.sol @@ -2,7 +2,9 @@ pragma solidity ^0.8.0; -interface IAccessControlApplication { +import "../contracts/coordination/ITACoChildToRoot.sol"; + +interface ITACoChildApplication is ITACoChildToRoot { function stakingProviderFromOperator(address _operator) external view returns (address); function authorizedStake(address _stakingProvider) external view returns (uint96); diff --git a/contracts/xchain/PolygonChild.sol b/contracts/xchain/PolygonChild.sol index 0a0fa040..99c55b7a 100644 --- a/contracts/xchain/PolygonChild.sol +++ b/contracts/xchain/PolygonChild.sol @@ -3,9 +3,10 @@ pragma solidity ^0.8.0; import "@fx-portal/contracts/tunnel/FxBaseChildTunnel.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; +import "../contracts/coordination/ITACoChildToRoot.sol"; -contract PolygonChild is FxBaseChildTunnel, Ownable { - address public stakeInfoAddress; +contract PolygonChild is ITACoChildToRoot, FxBaseChildTunnel, Ownable { + address public childApplication; constructor(address _fxChild) FxBaseChildTunnel(_fxChild) {} @@ -15,10 +16,19 @@ contract PolygonChild is FxBaseChildTunnel, Ownable { bytes memory data ) internal override validateSender(sender) { // solhint-disable-next-line avoid-low-level-calls - stakeInfoAddress.call(data); + childApplication.call(data); } - function sendMessageToRoot(bytes memory message) public { + function setChildApplication(address _childApplication) public onlyOwner { + childApplication = _childApplication; + } + + function confirmOperatorAddress(address operator) external override { + require(msg.sender == childApplication, "Only child app can call this method"); + bytes memory message = abi.encodeWithSelector( + ITACoChildToRoot.confirmOperatorAddress.selector, + operator + ); _sendMessageToRoot(message); } diff --git a/contracts/xchain/PolygonRoot.sol b/contracts/xchain/PolygonRoot.sol index 7c393e76..4a231ed4 100644 --- a/contracts/xchain/PolygonRoot.sol +++ b/contracts/xchain/PolygonRoot.sol @@ -2,59 +2,65 @@ pragma solidity ^0.8.0; import "@fx-portal/contracts/tunnel/FxBaseRootTunnel.sol"; -import "../contracts/coordination/IUpdatableStakeInfo.sol"; +import "../contracts/coordination/ITACoRootToChild.sol"; -contract PolygonRoot is FxBaseRootTunnel, IUpdatableStakeInfo { - address public immutable source; - bytes public latestData; +contract PolygonRoot is FxBaseRootTunnel, ITACoRootToChild { + address public immutable rootApplication; + + // bytes public latestData; constructor( address _checkpointManager, address _fxRoot, - address _source, + address _rootApplication, address _fxChildTunnel ) FxBaseRootTunnel(_checkpointManager, _fxRoot) { - require(_source != address(0), "Wrong input parameters"); - source = _source; + require(_rootApplication != address(0), "Wrong input parameters"); + rootApplication = _rootApplication; fxChildTunnel = _fxChildTunnel; } /** - * @dev Checks caller is source of data + * @dev Checks caller is the root application */ - modifier onlySource() { - require(msg.sender == source, "Caller must be the source"); + modifier onlyRootApplication() { + require(msg.sender == rootApplication, "Caller must be the root app"); _; } function _processMessageFromChild(bytes memory data) internal override { - latestData = data; + // latestData = data; + // solhint-disable-next-line avoid-low-level-calls + rootApplication.call(data); } function updateOperator( address stakingProvider, address operator - ) external override onlySource { + ) external override onlyRootApplication { bytes memory message = abi.encodeWithSelector( - IUpdatableStakeInfo.updateOperator.selector, + ITACoRootToChild.updateOperator.selector, stakingProvider, operator ); _sendMessageToChild(message); } - function updateAmount(address stakingProvider, uint96 amount) external override onlySource { + function updateAuthorization( + address stakingProvider, + uint96 amount + ) external override onlyRootApplication { bytes memory message = abi.encodeWithSelector( - IUpdatableStakeInfo.updateAmount.selector, + ITACoRootToChild.updateAuthorization.selector, stakingProvider, amount ); _sendMessageToChild(message); } - function batchUpdate(bytes32[] calldata updateInfo) external override onlySource { + function batchUpdate(bytes32[] calldata updateInfo) external override onlyRootApplication { bytes memory message = abi.encodeWithSelector( - IUpdatableStakeInfo.batchUpdate.selector, + ITACoRootToChild.batchUpdate.selector, updateInfo ); _sendMessageToChild(message); From cd5dc1425395034f44d73f035ed884e2c237a320 Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Fri, 25 Aug 2023 18:50:07 -0400 Subject: [PATCH 10/18] Fixes tests after renaming and adding confirmation through Coordinator --- contracts/contracts/TACoApplication.sol | 19 +-- .../coordination/TACoChildApplication.sol | 10 +- contracts/test/Dummy.sol | 11 ++ contracts/test/TACoApplicationTestSet.sol | 30 +++++ tests/application/conftest.py | 8 +- tests/application/test_authorization.py | 120 +++++++++--------- tests/application/test_operator.py | 89 ++++++------- tests/application/test_reward.py | 20 +-- tests/test_coordinator.py | 27 ++-- 9 files changed, 189 insertions(+), 145 deletions(-) create mode 100644 contracts/test/Dummy.sol diff --git a/contracts/contracts/TACoApplication.sol b/contracts/contracts/TACoApplication.sol index 7d0b92e8..3082d7c2 100644 --- a/contracts/contracts/TACoApplication.sol +++ b/contracts/contracts/TACoApplication.sol @@ -676,6 +676,7 @@ contract TACoApplication is IApplication, ITACoChildToRoot, OwnableUpgradeable { info.operatorStartTimestamp = uint64(block.timestamp); emit OperatorBonded(_stakingProvider, _operator, previousOperator, block.timestamp); + info.operatorConfirmed = false; if (address(childApplication) != address(0)) { childApplication.updateOperator(_stakingProvider, _operator); } @@ -687,17 +688,19 @@ contract TACoApplication is IApplication, ITACoChildToRoot, OwnableUpgradeable { function confirmOperatorAddress(address _operator) external override { require( msg.sender == address(childApplication), - "Only StakeInfo contract allowed to confirm operator" + "Only child application allowed to confirm operator" ); address stakingProvider = _stakingProviderFromOperator[_operator]; - require(isAuthorized(stakingProvider), "No stake associated with the operator"); + // TODO this case possible only in case of desync + require(stakingProvider != address(0), "Operator has no bond with staking provider"); StakingProviderInfo storage info = stakingProviderInfo[stakingProvider]; - require(!info.operatorConfirmed, "Operator address is already confirmed"); - updateRewardInternal(stakingProvider); - info.operatorConfirmed = true; - authorizedOverall += info.authorized; - emit OperatorConfirmed(stakingProvider, _operator); + if (!info.operatorConfirmed) { + updateRewardInternal(stakingProvider); + info.operatorConfirmed = true; + authorizedOverall += info.authorized; + emit OperatorConfirmed(stakingProvider, _operator); + } } //-------------------------XChain------------------------- @@ -720,7 +723,7 @@ contract TACoApplication is IApplication, ITACoChildToRoot, OwnableUpgradeable { StakingProviderInfo storage _info ) internal { if (address(childApplication) != address(0)) { - // TODO send both authorized and eligible amounts in case of slashing from StakeInfo + // TODO send both authorized and eligible amounts in case of slashing from child app uint96 eligibleAmount = getEligibleAmount(_info); childApplication.updateAuthorization(_stakingProvider, eligibleAmount); } diff --git a/contracts/contracts/coordination/TACoChildApplication.sol b/contracts/contracts/coordination/TACoChildApplication.sol index d637611e..9f45f395 100644 --- a/contracts/contracts/coordination/TACoChildApplication.sol +++ b/contracts/contracts/coordination/TACoChildApplication.sol @@ -13,7 +13,6 @@ import "./ITACoChildToRoot.sol"; */ contract TACoChildApplication is AccessControl, ITACoRootToChild, ITACoChildApplication { bytes32 public constant UPDATE_ROLE = keccak256("UPDATE_ROLE"); - bytes32 public constant DEPLOYER_ROLE = keccak256("DEPLOYER_ROLE"); struct StakingProviderInfo { address operator; @@ -35,13 +34,14 @@ contract TACoChildApplication is AccessControl, ITACoRootToChild, ITACoChildAppl ); rootApplication = _rootApplication; + // TODO Issue #112 _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); for (uint256 i = 0; i < updaters.length; i++) { _grantRole(UPDATE_ROLE, updaters[i]); } } - function setCoordinator(address _coordinator) external onlyRole(DEPLOYER_ROLE) { + function setCoordinator(address _coordinator) external onlyRole(DEFAULT_ADMIN_ROLE) { require(coordinator == address(0), "Coordinator already set"); require(_coordinator != address(0), "Coordinator must be specified"); // require(_coordinator.numberOfRituals() >= 0, "Invalid coordinator"); @@ -78,6 +78,7 @@ contract TACoChildApplication is AccessControl, ITACoRootToChild, ITACoChildAppl stakingProviderFromOperator[oldOperator] = address(0); stakingProviderFromOperator[operator] = stakingProvider; info.operatorConfirmed = false; + // TODO placeholder to notify Coordinator emit OperatorUpdated(stakingProvider, operator); } @@ -112,10 +113,9 @@ contract TACoChildApplication is AccessControl, ITACoRootToChild, ITACoChildAppl require(msg.sender == coordinator, "Only Coordinator allowed to confirm operator"); address stakingProvider = stakingProviderFromOperator[_operator]; StakingProviderInfo storage info = stakingProviderInfo[stakingProvider]; - if (info.operatorConfirmed) { - return; - } require(info.authorized > 0, "No stake associated with the operator"); + // TODO maybe allow second confirmation, just do not send root call + require(!info.operatorConfirmed, "Can't confirm same operator twice"); info.operatorConfirmed = true; rootApplication.confirmOperatorAddress(_operator); } diff --git a/contracts/test/Dummy.sol b/contracts/test/Dummy.sol new file mode 100644 index 00000000..6bd807d8 --- /dev/null +++ b/contracts/test/Dummy.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.0; + +contract Dummy { + // solhint-disable-next-line no-empty-blocks + receive() external payable {} + + // solhint-disable-next-line no-empty-blocks + fallback() external payable {} +} diff --git a/contracts/test/TACoApplicationTestSet.sol b/contracts/test/TACoApplicationTestSet.sol index 982e8cca..e5a7ce13 100644 --- a/contracts/test/TACoApplicationTestSet.sol +++ b/contracts/test/TACoApplicationTestSet.sol @@ -140,6 +140,36 @@ contract ThresholdStakingForTACoApplicationMock { } } +/** + * @notice Contract for testing TACo application contract + */ +contract ChildApplicationForTACoApplicationMock { + TACoApplication public immutable rootApplication; + + mapping(address => uint96) public authorizedStake; + mapping(address => address) public operatorFromStakingProvider; + mapping(address => address) public stakingProviderFromOperator; + + constructor(TACoApplication _rootApplication) { + rootApplication = _rootApplication; + } + + function updateOperator(address _stakingProvider, address _operator) external { + address oldOperator = operatorFromStakingProvider[_stakingProvider]; + stakingProviderFromOperator[oldOperator] = address(0); + operatorFromStakingProvider[_stakingProvider] = _operator; + stakingProviderFromOperator[_operator] = _stakingProvider; + } + + function updateAuthorization(address _stakingProvider, uint96 _amount) external { + authorizedStake[_stakingProvider] = _amount; + } + + function confirmOperatorAddress(address _operator) external { + rootApplication.confirmOperatorAddress(_operator); + } +} + // /** // * @notice Intermediary contract for testing operator // */ diff --git a/tests/application/conftest.py b/tests/application/conftest.py index 59810084..e2fee3c1 100644 --- a/tests/application/conftest.py +++ b/tests/application/conftest.py @@ -94,7 +94,9 @@ def taco_application(project, creator, token, threshold_staking): @pytest.fixture() -def stake_info(project, creator, taco_application): - contract = project.StakeInfo.deploy([taco_application.address], sender=creator) - taco_application.setUpdatableStakeInfo(contract.address, sender=creator) +def child_application(project, creator, taco_application): + contract = project.ChildApplicationForTACoApplicationMock.deploy( + taco_application.address, sender=creator + ) + taco_application.setChildApplication(contract.address, sender=creator) return contract diff --git a/tests/application/test_authorization.py b/tests/application/test_authorization.py index 33dae231..26e5ef4f 100644 --- a/tests/application/test_authorization.py +++ b/tests/application/test_authorization.py @@ -26,10 +26,8 @@ MIN_AUTHORIZATION = Web3.to_wei(40_000, "ether") DEAUTHORIZATION_DURATION = 60 * 60 * 24 * 60 # 60 days in seconds -STAKE_INFO_OPERATOR_SLOT = 0 - -def test_authorization_increase(accounts, threshold_staking, taco_application, stake_info): +def test_authorization_increase(accounts, threshold_staking, taco_application, child_application): """ Tests for authorization method: authorizationIncreased """ @@ -58,7 +56,7 @@ def test_authorization_increase(accounts, threshold_staking, taco_application, s assert taco_application.stakingProviderInfo(staking_provider)[AUTHORIZATION_SLOT] == value assert taco_application.authorizedOverall() == 0 assert taco_application.authorizedStake(staking_provider) == value - assert stake_info.authorizedStake(staking_provider) == value + assert child_application.authorizedStake(staking_provider) == value assert taco_application.isAuthorized(staking_provider) # Check that all events are emitted @@ -86,7 +84,7 @@ def test_authorization_increase(accounts, threshold_staking, taco_application, s assert taco_application.stakingProviderInfo(staking_provider)[AUTHORIZATION_SLOT] == value assert taco_application.authorizedOverall() == 0 assert taco_application.authorizedStake(staking_provider) == value - assert stake_info.authorizedStake(staking_provider) == value + assert child_application.authorizedStake(staking_provider) == value assert taco_application.isAuthorized(staking_provider) events = taco_application.AuthorizationIncreased.from_receipt(tx) @@ -98,7 +96,7 @@ def test_authorization_increase(accounts, threshold_staking, taco_application, s # Confirm operator address and try to increase authorization again taco_application.bondOperator(staking_provider, staking_provider, sender=staking_provider) - taco_application.confirmOperatorAddress(sender=staking_provider) + child_application.confirmOperatorAddress(staking_provider, sender=staking_provider) authorization = 2 * value + 1 tx = threshold_staking.authorizationIncreased( @@ -109,7 +107,7 @@ def test_authorization_increase(accounts, threshold_staking, taco_application, s ) assert taco_application.authorizedOverall() == authorization assert taco_application.authorizedStake(staking_provider) == authorization - assert stake_info.authorizedStake(staking_provider) == authorization + assert child_application.authorizedStake(staking_provider) == authorization assert taco_application.isAuthorized(staking_provider) events = taco_application.AuthorizationIncreased.from_receipt(tx) @@ -126,7 +124,7 @@ def test_authorization_increase(accounts, threshold_staking, taco_application, s assert taco_application.stakingProviderInfo(staking_provider)[AUTHORIZATION_SLOT] == value assert taco_application.authorizedOverall() == value assert taco_application.authorizedStake(staking_provider) == value - assert stake_info.authorizedStake(staking_provider) == value + assert child_application.authorizedStake(staking_provider) == value assert taco_application.isAuthorized(staking_provider) events = taco_application.AuthorizationIncreased.from_receipt(tx) @@ -136,17 +134,17 @@ def test_authorization_increase(accounts, threshold_staking, taco_application, s ) ] - # Increase again without syncing with StakeInfo - taco_application.setUpdatableStakeInfo(ZERO_ADDRESS, sender=creator) + # Increase again without syncing with child app + taco_application.setChildApplication(ZERO_ADDRESS, sender=creator) tx = threshold_staking.authorizationIncreased( staking_provider, value, 2 * value, sender=creator ) assert taco_application.authorizedStake(staking_provider) == 2 * value - assert stake_info.authorizedStake(staking_provider) == value + assert child_application.authorizedStake(staking_provider) == value def test_involuntary_authorization_decrease( - accounts, threshold_staking, taco_application, stake_info + accounts, threshold_staking, taco_application, child_application ): """ Tests for authorization method: involuntaryAuthorizationDecrease @@ -174,11 +172,11 @@ def test_involuntary_authorization_decrease( ) assert taco_application.authorizedOverall() == 0 assert taco_application.authorizedStake(staking_provider) == authorization - assert stake_info.authorizedStake(staking_provider) == authorization + assert child_application.authorizedStake(staking_provider) == authorization assert taco_application.isAuthorized(staking_provider) assert not taco_application.isOperatorConfirmed(staking_provider) assert not taco_application.stakingProviderInfo(staking_provider)[OPERATOR_CONFIRMED_SLOT] - assert stake_info.stakes(staking_provider)[STAKE_INFO_OPERATOR_SLOT] == ZERO_ADDRESS + assert child_application.operatorFromStakingProvider(staking_provider) == ZERO_ADDRESS events = taco_application.AuthorizationInvoluntaryDecreased.from_receipt(tx) assert events == [ @@ -203,11 +201,11 @@ def test_involuntary_authorization_decrease( ) assert taco_application.authorizedOverall() == 0 assert taco_application.authorizedStake(staking_provider) == authorization - assert stake_info.authorizedStake(staking_provider) == 0 + assert child_application.authorizedStake(staking_provider) == 0 assert taco_application.isAuthorized(staking_provider) assert not taco_application.isOperatorConfirmed(staking_provider) assert not taco_application.stakingProviderInfo(staking_provider)[OPERATOR_CONFIRMED_SLOT] - assert stake_info.stakes(staking_provider)[STAKE_INFO_OPERATOR_SLOT] == ZERO_ADDRESS + assert child_application.operatorFromStakingProvider(staking_provider) == ZERO_ADDRESS events = taco_application.AuthorizationInvoluntaryDecreased.from_receipt(tx) assert events == [ @@ -218,7 +216,7 @@ def test_involuntary_authorization_decrease( # Confirm operator address and decrease again taco_application.bondOperator(staking_provider, staking_provider, sender=staking_provider) - taco_application.confirmOperatorAddress(sender=staking_provider) + child_application.confirmOperatorAddress(staking_provider, sender=staking_provider) authorization = value // 8 tx = threshold_staking.involuntaryAuthorizationDecrease( @@ -232,12 +230,12 @@ def test_involuntary_authorization_decrease( ) assert taco_application.authorizedOverall() == authorization assert taco_application.authorizedStake(staking_provider) == authorization - assert stake_info.authorizedStake(staking_provider) == 0 + assert child_application.authorizedStake(staking_provider) == 0 assert taco_application.isAuthorized(staking_provider) assert taco_application.isOperatorConfirmed(staking_provider) assert taco_application.getOperatorFromStakingProvider(staking_provider) == staking_provider assert taco_application.stakingProviderFromOperator(staking_provider) == staking_provider - assert stake_info.stakes(staking_provider)[STAKE_INFO_OPERATOR_SLOT] == staking_provider + assert child_application.operatorFromStakingProvider(staking_provider) == staking_provider events = taco_application.AuthorizationInvoluntaryDecreased.from_receipt(tx) assert events == [ @@ -254,13 +252,13 @@ def test_involuntary_authorization_decrease( assert taco_application.stakingProviderInfo(staking_provider)[DEAUTHORIZING_SLOT] == 0 assert taco_application.authorizedOverall() == 0 assert taco_application.authorizedStake(staking_provider) == 0 - assert stake_info.authorizedStake(staking_provider) == 0 + assert child_application.authorizedStake(staking_provider) == 0 assert not taco_application.isAuthorized(staking_provider) assert not taco_application.isOperatorConfirmed(staking_provider) assert not taco_application.stakingProviderInfo(staking_provider)[OPERATOR_CONFIRMED_SLOT] assert taco_application.getOperatorFromStakingProvider(staking_provider) == ZERO_ADDRESS assert taco_application.stakingProviderFromOperator(staking_provider) == ZERO_ADDRESS - assert stake_info.stakes(staking_provider)[STAKE_INFO_OPERATOR_SLOT] == ZERO_ADDRESS + assert child_application.operatorFromStakingProvider(staking_provider) == ZERO_ADDRESS events = taco_application.AuthorizationInvoluntaryDecreased.from_receipt(tx) assert events == [ @@ -272,7 +270,7 @@ def test_involuntary_authorization_decrease( # Emulate slash and desync by sending smaller fromAmount threshold_staking.authorizationIncreased(staking_provider, 0, 2 * value, sender=creator) taco_application.bondOperator(staking_provider, staking_provider, sender=staking_provider) - taco_application.confirmOperatorAddress(sender=staking_provider) + child_application.confirmOperatorAddress(staking_provider, sender=staking_provider) authorization = value // 2 tx = threshold_staking.involuntaryAuthorizationDecrease( @@ -283,7 +281,7 @@ def test_involuntary_authorization_decrease( ) assert taco_application.authorizedOverall() == authorization assert taco_application.authorizedStake(staking_provider) == authorization - assert stake_info.authorizedStake(staking_provider) == authorization + assert child_application.authorizedStake(staking_provider) == authorization events = taco_application.AuthorizationInvoluntaryDecreased.from_receipt(tx) assert events == [ @@ -292,18 +290,18 @@ def test_involuntary_authorization_decrease( ) ] - # Decrease everything again without syncing with StakeInfo - taco_application.setUpdatableStakeInfo(ZERO_ADDRESS, sender=creator) + # Decrease everything again without syncing with child app + taco_application.setChildApplication(ZERO_ADDRESS, sender=creator) threshold_staking.involuntaryAuthorizationDecrease( staking_provider, authorization, 0, sender=creator ) - assert stake_info.authorizedStake(staking_provider) == authorization + assert child_application.authorizedStake(staking_provider) == authorization assert taco_application.getOperatorFromStakingProvider(staking_provider) == ZERO_ADDRESS - assert stake_info.stakes(staking_provider)[STAKE_INFO_OPERATOR_SLOT] == staking_provider + assert child_application.operatorFromStakingProvider(staking_provider) == staking_provider def test_authorization_decrease_request( - accounts, threshold_staking, taco_application, stake_info, chain + accounts, threshold_staking, taco_application, child_application, chain ): """ Tests for authorization method: authorizationDecreaseRequested @@ -348,7 +346,7 @@ def test_authorization_decrease_request( ) assert taco_application.authorizedOverall() == 0 assert taco_application.authorizedStake(staking_provider) == value - assert stake_info.authorizedStake(staking_provider) == minimum_authorization + assert child_application.authorizedStake(staking_provider) == minimum_authorization assert taco_application.isAuthorized(staking_provider) events = taco_application.AuthorizationDecreaseRequested.from_receipt(tx) @@ -360,7 +358,7 @@ def test_authorization_decrease_request( # Confirm operator address and request full decrease taco_application.bondOperator(staking_provider, staking_provider, sender=staking_provider) - taco_application.confirmOperatorAddress(sender=staking_provider) + child_application.confirmOperatorAddress(staking_provider, sender=staking_provider) tx = threshold_staking.authorizationDecreaseRequested( staking_provider, value, 0, sender=creator @@ -375,7 +373,7 @@ def test_authorization_decrease_request( ) assert taco_application.authorizedOverall() == value assert taco_application.authorizedStake(staking_provider) == value - assert stake_info.authorizedStake(staking_provider) == 0 + assert child_application.authorizedStake(staking_provider) == 0 assert taco_application.isAuthorized(staking_provider) events = taco_application.AuthorizationDecreaseRequested.from_receipt(tx) @@ -393,7 +391,7 @@ def test_authorization_decrease_request( assert taco_application.stakingProviderInfo(staking_provider)[DEAUTHORIZING_SLOT] == value // 2 assert taco_application.authorizedOverall() == value // 2 assert taco_application.authorizedStake(staking_provider) == value // 2 - assert stake_info.authorizedStake(staking_provider) == 0 + assert child_application.authorizedStake(staking_provider) == 0 events = taco_application.AuthorizationDecreaseRequested.from_receipt(tx) assert events == [ @@ -402,16 +400,16 @@ def test_authorization_decrease_request( ) ] - # Request decrease without syncing with StakeInfo - taco_application.setUpdatableStakeInfo(ZERO_ADDRESS, sender=creator) + # Request decrease without syncing with child app + taco_application.setChildApplication(ZERO_ADDRESS, sender=creator) threshold_staking.authorizationDecreaseRequested( staking_provider, value // 2, value // 2, sender=creator ) - assert stake_info.authorizedStake(staking_provider) == 0 + assert child_application.authorizedStake(staking_provider) == 0 def test_finish_authorization_decrease( - accounts, threshold_staking, taco_application, stake_info, chain + accounts, threshold_staking, taco_application, child_application, chain ): """ Tests for authorization method: finishAuthorizationDecrease @@ -446,7 +444,7 @@ def test_finish_authorization_decrease( assert taco_application.stakingProviderInfo(staking_provider)[END_DEAUTHORIZATION_SLOT] == 0 assert taco_application.authorizedOverall() == 0 assert taco_application.authorizedStake(staking_provider) == new_value - assert stake_info.authorizedStake(staking_provider) == new_value + assert child_application.authorizedStake(staking_provider) == new_value assert taco_application.isAuthorized(staking_provider) assert ( threshold_staking.authorizedStake(staking_provider, taco_application.address) == new_value @@ -462,7 +460,7 @@ def test_finish_authorization_decrease( # Confirm operator, request again then desync values and finish decrease value = new_value taco_application.bondOperator(staking_provider, staking_provider, sender=staking_provider) - taco_application.confirmOperatorAddress(sender=staking_provider) + child_application.confirmOperatorAddress(staking_provider, sender=staking_provider) threshold_staking.authorizationDecreaseRequested( staking_provider, value, minimum_authorization, sender=creator ) @@ -479,13 +477,13 @@ def test_finish_authorization_decrease( assert taco_application.stakingProviderFromOperator(staking_provider) == staking_provider assert taco_application.authorizedOverall() == new_value assert taco_application.authorizedStake(staking_provider) == new_value - assert stake_info.authorizedStake(staking_provider) == new_value + assert child_application.authorizedStake(staking_provider) == new_value assert taco_application.isAuthorized(staking_provider) assert taco_application.isOperatorConfirmed(staking_provider) assert ( threshold_staking.authorizedStake(staking_provider, taco_application.address) == new_value ) - assert stake_info.stakes(staking_provider)[STAKE_INFO_OPERATOR_SLOT] == staking_provider + assert child_application.operatorFromStakingProvider(staking_provider) == staking_provider events = taco_application.AuthorizationDecreaseApproved.from_receipt(tx) assert events == [ @@ -507,12 +505,12 @@ def test_finish_authorization_decrease( assert taco_application.stakingProviderFromOperator(staking_provider) == ZERO_ADDRESS assert taco_application.authorizedOverall() == 0 assert taco_application.authorizedStake(staking_provider) == 0 - assert stake_info.authorizedStake(staking_provider) == 0 + assert child_application.authorizedStake(staking_provider) == 0 assert not taco_application.isAuthorized(staking_provider) assert not taco_application.isOperatorConfirmed(staking_provider) assert not taco_application.stakingProviderInfo(staking_provider)[OPERATOR_CONFIRMED_SLOT] assert threshold_staking.authorizedStake(staking_provider, taco_application.address) == 0 - assert stake_info.stakes(staking_provider)[STAKE_INFO_OPERATOR_SLOT] == ZERO_ADDRESS + assert child_application.operatorFromStakingProvider(staking_provider) == ZERO_ADDRESS events = taco_application.AuthorizationDecreaseApproved.from_receipt(tx) assert events == [ @@ -521,25 +519,25 @@ def test_finish_authorization_decrease( ) ] - # Decrease everything again without syncing with StakeInfo + # Decrease everything again without syncing with child app value = minimum_authorization threshold_staking.authorizationIncreased(staking_provider, 0, 2 * value, sender=creator) taco_application.bondOperator(staking_provider, staking_provider, sender=staking_provider) - taco_application.confirmOperatorAddress(sender=staking_provider) + child_application.confirmOperatorAddress(staking_provider, sender=staking_provider) threshold_staking.authorizationDecreaseRequested( staking_provider, 2 * value, value, sender=creator ) chain.pending_timestamp += deauthorization_duration - taco_application.setUpdatableStakeInfo(ZERO_ADDRESS, sender=creator) + taco_application.setChildApplication(ZERO_ADDRESS, sender=creator) threshold_staking.setDecreaseRequest(staking_provider, 0, sender=creator) taco_application.finishAuthorizationDecrease(staking_provider, sender=creator) assert taco_application.authorizedStake(staking_provider) == 0 - assert stake_info.authorizedStake(staking_provider) == value + assert child_application.authorizedStake(staking_provider) == value assert taco_application.getOperatorFromStakingProvider(staking_provider) == ZERO_ADDRESS - assert stake_info.stakes(staking_provider)[STAKE_INFO_OPERATOR_SLOT] == staking_provider + assert child_application.operatorFromStakingProvider(staking_provider) == staking_provider -def test_resync(accounts, threshold_staking, taco_application, stake_info): +def test_resync(accounts, threshold_staking, taco_application, child_application): """ Tests for authorization method: resynchronizeAuthorization """ @@ -566,7 +564,7 @@ def test_resync(accounts, threshold_staking, taco_application, stake_info): assert taco_application.stakingProviderInfo(staking_provider)[AUTHORIZATION_SLOT] == new_value assert taco_application.authorizedOverall() == 0 assert taco_application.authorizedStake(staking_provider) == new_value - assert stake_info.authorizedStake(staking_provider) == new_value + assert child_application.authorizedStake(staking_provider) == new_value assert taco_application.isAuthorized(staking_provider) events = taco_application.AuthorizationReSynchronized.from_receipt(tx) @@ -579,7 +577,7 @@ def test_resync(accounts, threshold_staking, taco_application, stake_info): # Confirm operator and change authorized amount again value = new_value taco_application.bondOperator(staking_provider, staking_provider, sender=staking_provider) - taco_application.confirmOperatorAddress(sender=staking_provider) + child_application.confirmOperatorAddress(staking_provider, sender=staking_provider) new_value = minimum_authorization threshold_staking.setAuthorized(staking_provider, new_value, sender=creator) @@ -591,10 +589,10 @@ def test_resync(accounts, threshold_staking, taco_application, stake_info): assert taco_application.stakingProviderFromOperator(staking_provider) == staking_provider assert taco_application.authorizedOverall() == new_value assert taco_application.authorizedStake(staking_provider) == new_value - assert stake_info.authorizedStake(staking_provider) == new_value + assert child_application.authorizedStake(staking_provider) == new_value assert taco_application.isAuthorized(staking_provider) assert taco_application.isOperatorConfirmed(staking_provider) - assert stake_info.stakes(staking_provider)[STAKE_INFO_OPERATOR_SLOT] == staking_provider + assert child_application.operatorFromStakingProvider(staking_provider) == staking_provider events = taco_application.AuthorizationReSynchronized.from_receipt(tx) assert events == [ @@ -617,10 +615,10 @@ def test_resync(accounts, threshold_staking, taco_application, stake_info): assert taco_application.stakingProviderFromOperator(staking_provider) == staking_provider assert taco_application.authorizedOverall() == new_value assert taco_application.authorizedStake(staking_provider) == new_value - assert stake_info.authorizedStake(staking_provider) == 0 + assert child_application.authorizedStake(staking_provider) == 0 assert taco_application.isAuthorized(staking_provider) assert taco_application.isOperatorConfirmed(staking_provider) - assert stake_info.stakes(staking_provider)[STAKE_INFO_OPERATOR_SLOT] == staking_provider + assert child_application.operatorFromStakingProvider(staking_provider) == staking_provider events = taco_application.AuthorizationReSynchronized.from_receipt(tx) assert events == [ @@ -640,11 +638,11 @@ def test_resync(accounts, threshold_staking, taco_application, stake_info): assert taco_application.stakingProviderFromOperator(staking_provider) == ZERO_ADDRESS assert taco_application.authorizedOverall() == 0 assert taco_application.authorizedStake(staking_provider) == 0 - assert stake_info.authorizedStake(staking_provider) == 0 + assert child_application.authorizedStake(staking_provider) == 0 assert not taco_application.isAuthorized(staking_provider) assert not taco_application.isOperatorConfirmed(staking_provider) assert not taco_application.stakingProviderInfo(staking_provider)[OPERATOR_CONFIRMED_SLOT] - assert stake_info.stakes(staking_provider)[STAKE_INFO_OPERATOR_SLOT] == ZERO_ADDRESS + assert child_application.operatorFromStakingProvider(staking_provider) == ZERO_ADDRESS events = taco_application.AuthorizationReSynchronized.from_receipt(tx) assert events == [ @@ -653,15 +651,15 @@ def test_resync(accounts, threshold_staking, taco_application, stake_info): ) ] - # Resync again without syncing with StakeInfo + # Resync again without syncing with child app value = minimum_authorization threshold_staking.authorizationIncreased(staking_provider, 0, value, sender=creator) taco_application.bondOperator(staking_provider, staking_provider, sender=staking_provider) - taco_application.confirmOperatorAddress(sender=staking_provider) + child_application.confirmOperatorAddress(staking_provider, sender=staking_provider) threshold_staking.setAuthorized(staking_provider, 0, sender=creator) - taco_application.setUpdatableStakeInfo(ZERO_ADDRESS, sender=creator) + taco_application.setChildApplication(ZERO_ADDRESS, sender=creator) taco_application.resynchronizeAuthorization(staking_provider, sender=creator) assert taco_application.authorizedStake(staking_provider) == 0 - assert stake_info.authorizedStake(staking_provider) == value + assert child_application.authorizedStake(staking_provider) == value assert taco_application.getOperatorFromStakingProvider(staking_provider) == ZERO_ADDRESS - assert stake_info.stakes(staking_provider)[STAKE_INFO_OPERATOR_SLOT] == staking_provider + assert child_application.operatorFromStakingProvider(staking_provider) == staking_provider diff --git a/tests/application/test_operator.py b/tests/application/test_operator.py index 45d0c0bd..1774c079 100644 --- a/tests/application/test_operator.py +++ b/tests/application/test_operator.py @@ -23,10 +23,8 @@ MIN_AUTHORIZATION = Web3.to_wei(40_000, "ether") MIN_OPERATOR_SECONDS = 24 * 60 * 60 -STAKE_INFO_OPERATOR_SLOT = 0 - -def test_bond_operator(accounts, threshold_staking, taco_application, stake_info, chain): +def test_bond_operator(accounts, threshold_staking, taco_application, child_application, chain): ( creator, staking_provider_1, @@ -69,7 +67,7 @@ def test_bond_operator(accounts, threshold_staking, taco_application, stake_info # Staking provider can't confirm operator address because there is no operator by default with ape.reverts(): - taco_application.confirmOperatorAddress(sender=staking_provider_1) + child_application.confirmOperatorAddress(staking_provider_1, sender=staking_provider_1) # Staking provider can't bond another staking provider as operator with ape.reverts(): @@ -96,19 +94,19 @@ def test_bond_operator(accounts, threshold_staking, taco_application, stake_info assert not taco_application.isOperatorConfirmed(operator1) assert taco_application.getStakingProvidersLength() == 1 assert taco_application.stakingProviders(0) == staking_provider_3 - assert stake_info.stakes(staking_provider_3)[STAKE_INFO_OPERATOR_SLOT] == ZERO_ADDRESS - assert stake_info.stakingProviderFromOperator(operator1) == ZERO_ADDRESS + assert child_application.operatorFromStakingProvider(staking_provider_3) == operator1 + assert child_application.stakingProviderFromOperator(operator1) == staking_provider_3 # No active stakingProviders before confirmation all_locked, staking_providers = taco_application.getActiveStakingProviders(0, 0) assert all_locked == 0 assert len(staking_providers) == 0 - taco_application.confirmOperatorAddress(sender=operator1) + child_application.confirmOperatorAddress(operator1, sender=operator1) assert taco_application.stakingProviderInfo(staking_provider_3)[CONFIRMATION_SLOT] assert taco_application.isOperatorConfirmed(operator1) - assert stake_info.stakes(staking_provider_3)[STAKE_INFO_OPERATOR_SLOT] == operator1 - assert stake_info.stakingProviderFromOperator(operator1) == staking_provider_3 + assert child_application.operatorFromStakingProvider(staking_provider_3) == operator1 + assert child_application.stakingProviderFromOperator(operator1) == staking_provider_3 events = taco_application.OperatorBonded.from_receipt(tx) assert events == [ @@ -156,8 +154,8 @@ def test_bond_operator(accounts, threshold_staking, taco_application, stake_info assert not taco_application.isOperatorConfirmed(operator1) assert taco_application.getStakingProvidersLength() == 1 assert taco_application.stakingProviders(0) == staking_provider_3 - assert stake_info.stakes(staking_provider_3)[STAKE_INFO_OPERATOR_SLOT] == ZERO_ADDRESS - assert stake_info.stakingProviderFromOperator(operator1) == ZERO_ADDRESS + assert child_application.operatorFromStakingProvider(staking_provider_3) == ZERO_ADDRESS + assert child_application.stakingProviderFromOperator(operator1) == ZERO_ADDRESS # Resetting operator removes from active list before next confirmation all_locked, staking_providers = taco_application.getActiveStakingProviders(0, 0) @@ -183,8 +181,8 @@ def test_bond_operator(accounts, threshold_staking, taco_application, stake_info assert not taco_application.isOperatorConfirmed(operator2) assert taco_application.getStakingProvidersLength() == 1 assert taco_application.stakingProviders(0) == staking_provider_3 - assert stake_info.stakes(staking_provider_3)[STAKE_INFO_OPERATOR_SLOT] == ZERO_ADDRESS - assert stake_info.stakingProviderFromOperator(operator2) == ZERO_ADDRESS + assert child_application.operatorFromStakingProvider(staking_provider_3) == operator2 + assert child_application.stakingProviderFromOperator(operator2) == staking_provider_3 events = taco_application.OperatorBonded.from_receipt(tx) assert events == [ @@ -198,14 +196,14 @@ def test_bond_operator(accounts, threshold_staking, taco_application, stake_info # Now the previous operator can no longer make a confirmation with ape.reverts(): - taco_application.confirmOperatorAddress(sender=operator1) + child_application.confirmOperatorAddress(operator1, sender=operator1) # Only new operator can - taco_application.confirmOperatorAddress(sender=operator2) + child_application.confirmOperatorAddress(operator2, sender=operator2) assert not taco_application.isOperatorConfirmed(operator1) assert taco_application.isOperatorConfirmed(operator2) assert taco_application.stakingProviderInfo(staking_provider_3)[CONFIRMATION_SLOT] - assert stake_info.stakes(staking_provider_3)[STAKE_INFO_OPERATOR_SLOT] == operator2 - assert stake_info.stakingProviderFromOperator(operator2) == staking_provider_3 + assert child_application.operatorFromStakingProvider(staking_provider_3) == operator2 + assert child_application.stakingProviderFromOperator(operator2) == staking_provider_3 # Another staking provider can bond a free operator assert taco_application.authorizedOverall() == min_authorization @@ -218,8 +216,8 @@ def test_bond_operator(accounts, threshold_staking, taco_application, stake_info assert taco_application.getStakingProvidersLength() == 2 assert taco_application.stakingProviders(1) == staking_provider_4 assert taco_application.authorizedOverall() == min_authorization - assert stake_info.stakes(staking_provider_4)[STAKE_INFO_OPERATOR_SLOT] == ZERO_ADDRESS - assert stake_info.stakingProviderFromOperator(operator1) == ZERO_ADDRESS + assert child_application.operatorFromStakingProvider(staking_provider_4) == operator1 + assert child_application.stakingProviderFromOperator(operator1) == staking_provider_4 events = taco_application.OperatorBonded.from_receipt(tx) assert events == [ @@ -238,12 +236,12 @@ def test_bond_operator(accounts, threshold_staking, taco_application, stake_info threshold_staking.setRoles(operator1, ZERO_ADDRESS, ZERO_ADDRESS, ZERO_ADDRESS, sender=creator) # Bond operator again - taco_application.confirmOperatorAddress(sender=operator1) + child_application.confirmOperatorAddress(operator1, sender=operator1) assert taco_application.isOperatorConfirmed(operator1) assert taco_application.stakingProviderInfo(staking_provider_4)[CONFIRMATION_SLOT] assert taco_application.authorizedOverall() == 2 * min_authorization - assert stake_info.stakes(staking_provider_4)[STAKE_INFO_OPERATOR_SLOT] == operator1 - assert stake_info.stakingProviderFromOperator(operator1) == staking_provider_4 + assert child_application.operatorFromStakingProvider(staking_provider_4) == operator1 + assert child_application.stakingProviderFromOperator(operator1) == staking_provider_4 chain.pending_timestamp += min_operator_seconds tx = taco_application.bondOperator(staking_provider_4, operator3, sender=staking_provider_4) @@ -257,8 +255,9 @@ def test_bond_operator(accounts, threshold_staking, taco_application, stake_info assert taco_application.getStakingProvidersLength() == 2 assert taco_application.stakingProviders(1) == staking_provider_4 assert taco_application.authorizedOverall() == min_authorization - assert stake_info.stakes(staking_provider_4)[STAKE_INFO_OPERATOR_SLOT] == ZERO_ADDRESS - assert stake_info.stakingProviderFromOperator(operator1) == ZERO_ADDRESS + assert child_application.operatorFromStakingProvider(staking_provider_4) == operator3 + assert child_application.stakingProviderFromOperator(operator1) == ZERO_ADDRESS + assert child_application.stakingProviderFromOperator(operator3) == staking_provider_4 # Resetting operator removes from active list before next confirmation all_locked, staking_providers = taco_application.getActiveStakingProviders(1, 0) @@ -312,9 +311,9 @@ def test_bond_operator(accounts, threshold_staking, taco_application, stake_info threshold_staking.involuntaryAuthorizationDecrease( staking_provider_1, min_authorization, min_authorization - 1, sender=creator ) - taco_application.confirmOperatorAddress(sender=staking_provider_1) - assert stake_info.stakes(staking_provider_1)[STAKE_INFO_OPERATOR_SLOT] == staking_provider_1 - assert stake_info.stakingProviderFromOperator(staking_provider_1) == staking_provider_1 + child_application.confirmOperatorAddress(staking_provider_1, sender=staking_provider_1) + assert child_application.operatorFromStakingProvider(staking_provider_1) == staking_provider_1 + assert child_application.stakingProviderFromOperator(staking_provider_1) == staking_provider_1 # If stake will be less than minimum then provider is not active threshold_staking.authorizationIncreased( @@ -335,7 +334,7 @@ def test_bond_operator(accounts, threshold_staking, taco_application, stake_info assert len(staking_providers) == 0 # Reset xchain contract before next bonding - taco_application.setUpdatableStakeInfo(ZERO_ADDRESS, sender=creator) + taco_application.setChildApplication(ZERO_ADDRESS, sender=creator) # Unbond and rebond oeprator taco_application.bondOperator(staking_provider_3, ZERO_ADDRESS, sender=staking_provider_3) @@ -349,48 +348,40 @@ def test_bond_operator(accounts, threshold_staking, taco_application, stake_info assert taco_application.stakingProviderFromOperator(operator2) == ZERO_ADDRESS -def test_confirm_address(accounts, threshold_staking, taco_application, chain, project): +def test_confirm_address( + accounts, threshold_staking, taco_application, child_application, chain, project +): creator, staking_provider, operator, *everyone_else = accounts[0:] min_authorization = MIN_AUTHORIZATION min_operator_seconds = MIN_OPERATOR_SECONDS # Operator must be associated with staking provider with ape.reverts(): - taco_application.confirmOperatorAddress(sender=staking_provider) + child_application.confirmOperatorAddress(staking_provider, sender=staking_provider) threshold_staking.setRoles(staking_provider, sender=creator) - - # Deploy intermediary contract - intermediary = creator.deploy(project.Intermediary, taco_application.address, sender=creator) - - # Bond contract as an operator threshold_staking.authorizationIncreased(staking_provider, 0, min_authorization, sender=creator) - taco_application.bondOperator(staking_provider, intermediary.address, sender=staking_provider) - - # But can't make a confirmation using an intermediary contract - with ape.reverts(): - intermediary.confirmOperatorAddress(sender=staking_provider) - # Bond operator again and make confirmation + # Bond operator and make confirmation chain.pending_timestamp += min_operator_seconds taco_application.bondOperator(staking_provider, operator, sender=staking_provider) assert taco_application.authorizedOverall() == 0 - tx = taco_application.confirmOperatorAddress(sender=operator) + tx = child_application.confirmOperatorAddress(operator, sender=operator) assert taco_application.isOperatorConfirmed(operator) assert taco_application.stakingProviderInfo(staking_provider)[CONFIRMATION_SLOT] assert taco_application.authorizedOverall() == min_authorization events = taco_application.OperatorConfirmed.from_receipt(tx) - assert len(events) == 1 - event = events[0] - assert event["stakingProvider"] == staking_provider - assert event["operator"] == operator assert events == [ taco_application.OperatorConfirmed(stakingProvider=staking_provider, operator=operator) ] - # Can't confirm twice - with ape.reverts(): - taco_application.confirmOperatorAddress(sender=operator) + # Can confirm twice + earned = taco_application.availableRewards(staking_provider) + child_application.confirmOperatorAddress(operator, sender=operator) + assert taco_application.isOperatorConfirmed(operator) + assert taco_application.stakingProviderInfo(staking_provider)[CONFIRMATION_SLOT] + assert taco_application.authorizedOverall() == min_authorization + assert taco_application.availableRewards(staking_provider) == earned def test_slash(accounts, threshold_staking, taco_application): diff --git a/tests/application/test_reward.py b/tests/application/test_reward.py index ab0f067f..22196b52 100644 --- a/tests/application/test_reward.py +++ b/tests/application/test_reward.py @@ -28,7 +28,9 @@ DEAUTHORIZATION_DURATION = 60 * 60 * 24 * 60 # 60 days in seconds -def test_push_reward(accounts, token, threshold_staking, taco_application, chain): +def test_push_reward( + accounts, token, threshold_staking, taco_application, child_application, chain +): creator, distributor, staking_provider_1, staking_provider_2, *everyone_else = accounts[0:] min_authorization = MIN_AUTHORIZATION reward_portion = min_authorization @@ -93,7 +95,7 @@ def test_push_reward(accounts, token, threshold_staking, taco_application, chain chain.pending_timestamp += reward_duration threshold_staking.authorizationIncreased(staking_provider_1, 0, value, sender=creator) taco_application.bondOperator(staking_provider_1, staking_provider_1, sender=staking_provider_1) - taco_application.confirmOperatorAddress(sender=staking_provider_1) + child_application.confirmOperatorAddress(staking_provider_1, sender=staking_provider_1) tx = taco_application.pushReward(reward_portion, sender=distributor) timestamp = chain.pending_timestamp - 1 @@ -153,7 +155,9 @@ def test_push_reward(accounts, token, threshold_staking, taco_application, chain assert taco_application.availableRewards(staking_provider_2) == 0 -def test_update_reward(accounts, token, threshold_staking, taco_application, chain): +def test_update_reward( + accounts, token, threshold_staking, taco_application, child_application, chain +): creator, distributor, staking_provider_1, staking_provider_2, *everyone_else = accounts[0:] min_authorization = MIN_AUTHORIZATION reward_portion = min_authorization @@ -218,7 +222,7 @@ def check_reward_with_confirmation(): # Prepare one staking provider and reward threshold_staking.authorizationIncreased(staking_provider_1, 0, value, sender=creator) taco_application.bondOperator(staking_provider_1, staking_provider_1, sender=staking_provider_1) - taco_application.confirmOperatorAddress(sender=staking_provider_1) + child_application.confirmOperatorAddress(staking_provider_1, sender=staking_provider_1) taco_application.setRewardDistributor(distributor, sender=creator) token.transfer(distributor, 100 * reward_portion, sender=creator) @@ -272,7 +276,7 @@ def check_reward_with_confirmation(): taco_application.pushReward(reward_portion, sender=distributor) chain.pending_timestamp += reward_duration // 2 # Reward per token will be updated but nothing earned yet (just confirmed operator) - taco_application.confirmOperatorAddress(sender=staking_provider_2) + child_application.confirmOperatorAddress(staking_provider_2, sender=staking_provider_2) check_reward_no_confirmation() # Increase authorization with confirmation @@ -330,7 +334,7 @@ def check_reward_with_confirmation(): ) -def test_withdraw(accounts, token, threshold_staking, taco_application, chain): +def test_withdraw(accounts, token, threshold_staking, taco_application, child_application, chain): ( creator, distributor, @@ -355,7 +359,7 @@ def test_withdraw(accounts, token, threshold_staking, taco_application, chain): # Prepare one staking provider and reward threshold_staking.authorizationIncreased(staking_provider, 0, value, sender=creator) taco_application.bondOperator(staking_provider, staking_provider, sender=staking_provider) - taco_application.confirmOperatorAddress(sender=staking_provider) + child_application.confirmOperatorAddress(staking_provider, sender=staking_provider) # Nothing earned yet with ape.reverts(): @@ -403,7 +407,7 @@ def test_withdraw(accounts, token, threshold_staking, taco_application, chain): threshold_staking.setRoles(staking_provider_2, sender=creator) threshold_staking.authorizationIncreased(staking_provider_2, 0, value, sender=creator) taco_application.bondOperator(staking_provider_2, staking_provider_2, sender=staking_provider_2) - taco_application.confirmOperatorAddress(sender=staking_provider_2) + child_application.confirmOperatorAddress(staking_provider_2, sender=staking_provider_2) taco_application.pushReward(reward_portion, sender=distributor) chain.pending_timestamp += reward_duration // 2 taco_application.bondOperator(staking_provider, ZERO_ADDRESS, sender=staking_provider) diff --git a/tests/test_coordinator.py b/tests/test_coordinator.py index 956b4b55..229e66db 100644 --- a/tests/test_coordinator.py +++ b/tests/test_coordinator.py @@ -37,7 +37,7 @@ def gen_public_key(): def access_control_error_message(address, role=None): - role = Web3.to_hex(role or b'\x00'*32) + role = Web3.to_hex(role or b"\x00" * 32) return f"AccessControl: account {address.lower()} is missing role {role}" @@ -68,11 +68,12 @@ def treasury(accounts): @pytest.fixture() -def stake_info(project, deployer, nodes): - contract = project.StakeInfo.deploy([deployer], sender=deployer) +def application(project, deployer, nodes): + dummy = project.Dummy.deploy(sender=deployer) + contract = project.TACoChildApplication.deploy(dummy.address, [deployer], sender=deployer) for n in nodes: contract.updateOperator(n, n, sender=deployer) - contract.updateAmount(n, 42, sender=deployer) + contract.updateAuthorization(n, 42, sender=deployer) return contract @@ -84,10 +85,10 @@ def erc20(project, initiator): @pytest.fixture() -def coordinator(project, deployer, stake_info, erc20, initiator): +def coordinator(project, deployer, application, erc20, initiator): admin = deployer contract = project.Coordinator.deploy( - stake_info.address, + application.address, TIMEOUT, MAX_DKG_SIZE, admin, @@ -96,6 +97,7 @@ def coordinator(project, deployer, stake_info, erc20, initiator): sender=deployer, ) contract.grantRole(contract.INITIATOR_ROLE(), initiator, sender=admin) + application.setCoordinator(contract.address, sender=deployer) return contract @@ -161,7 +163,9 @@ def initiate_ritual(coordinator, erc20, allow_logic, authority, nodes): return authority, tx -def test_initiate_ritual(coordinator, nodes, initiator, erc20, global_allow_list, deployer, treasury): +def test_initiate_ritual( + coordinator, nodes, initiator, erc20, global_allow_list, deployer, treasury +): authority, tx = initiate_ritual( coordinator=coordinator, erc20=erc20, @@ -179,7 +183,7 @@ def test_initiate_ritual(coordinator, nodes, initiator, erc20, global_allow_list assert event["participants"] == tuple(n.address.lower() for n in nodes) assert coordinator.getRitualState(0) == RitualState.AWAITING_TRANSCRIPTS - + ritual_struct = coordinator.rituals(ritualID) assert ritual_struct[0] == initiator init, end = ritual_struct[1], ritual_struct[2] @@ -301,7 +305,9 @@ def test_post_transcript_but_not_waiting_for_transcripts( coordinator.postTranscript(0, transcript, sender=nodes[1]) -def test_post_aggregation(coordinator, nodes, initiator, erc20, global_allow_list, treasury, deployer): +def test_post_aggregation( + coordinator, nodes, initiator, erc20, global_allow_list, treasury, deployer +): initiate_ritual( coordinator=coordinator, erc20=erc20, @@ -320,8 +326,7 @@ def test_post_aggregation(coordinator, nodes, initiator, erc20, global_allow_lis for i, node in enumerate(nodes): assert coordinator.getRitualState(ritualID) == RitualState.AWAITING_AGGREGATIONS tx = coordinator.postAggregation( - ritualID, aggregated, dkg_public_key, decryption_request_static_keys[i], - sender=node + ritualID, aggregated, dkg_public_key, decryption_request_static_keys[i], sender=node ) events = coordinator.AggregationPosted.from_receipt(tx) From c74edc6fe108c15824011b3f6e02bd8fdb32c26a Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Tue, 29 Aug 2023 18:54:40 -0400 Subject: [PATCH 11/18] Removes `batchUpdate`, moves additional access in child app to extended contract, makes child app upgradeable, --- contracts/contracts/TACoApplication.sol | 9 ++- .../contracts/coordination/Coordinator.sol | 5 +- .../coordination/ITACoRootToChild.sol | 2 - .../coordination/TACoChildApplication.sol | 78 +++++++++++-------- contracts/test/TACoApplicationTestSet.sol | 19 ----- contracts/xchain/PolygonChild.sol | 4 - contracts/xchain/PolygonRoot.sol | 11 --- tests/application/test_operator.py | 15 ++-- tests/test_coordinator.py | 4 +- 9 files changed, 63 insertions(+), 84 deletions(-) diff --git a/contracts/contracts/TACoApplication.sol b/contracts/contracts/TACoApplication.sol index 3082d7c2..33113aae 100644 --- a/contracts/contracts/TACoApplication.sol +++ b/contracts/contracts/TACoApplication.sol @@ -691,10 +691,13 @@ contract TACoApplication is IApplication, ITACoChildToRoot, OwnableUpgradeable { "Only child application allowed to confirm operator" ); address stakingProvider = _stakingProviderFromOperator[_operator]; - // TODO this case possible only in case of desync - require(stakingProvider != address(0), "Operator has no bond with staking provider"); - StakingProviderInfo storage info = stakingProviderInfo[stakingProvider]; + // TODO only in case of desync, maybe just exit? + // require(stakingProvider != address(0), "Operator has no bond with staking provider"); + if (stakingProvider == address(0)) { + return; + } + StakingProviderInfo storage info = stakingProviderInfo[stakingProvider]; if (!info.operatorConfirmed) { updateRewardInternal(stakingProvider); info.operatorConfirmed = true; diff --git a/contracts/contracts/coordination/Coordinator.sol b/contracts/contracts/coordination/Coordinator.sol index 4a566525..1cbcf78d 100644 --- a/contracts/contracts/coordination/Coordinator.sol +++ b/contracts/contracts/coordination/Coordinator.sol @@ -150,15 +150,14 @@ contract Coordinator is AccessControlDefaultAdminRules, FlatRateFeeModel { function setProviderPublicKey(BLS12381.G2Point calldata _publicKey) external { uint32 lastRitualId = uint32(rituals.length); address stakingProvider = application.stakingProviderFromOperator(msg.sender); - require(stakingProvider != address(0), "Operator has no bond with staking provider"); // TODO + require(stakingProvider != address(0), "Operator has no bond with staking provider"); ParticipantKey memory newRecord = ParticipantKey(lastRitualId, _publicKey); - // keysHistory[stakingProvider][-1].publicKey != _publicKey; // TODO it's a question keysHistory[stakingProvider].push(newRecord); emit ParticipantPublicKeySet(lastRitualId, stakingProvider, _publicKey); // solhint-disable-next-line avoid-tx-origin - require(msg.sender == tx.origin, "Only operator with real address can set public key"); // TODO + require(msg.sender == tx.origin, "Only operator with real address can set public key"); application.confirmOperatorAddress(msg.sender); } diff --git a/contracts/contracts/coordination/ITACoRootToChild.sol b/contracts/contracts/coordination/ITACoRootToChild.sol index 41d91077..9473fc3c 100644 --- a/contracts/contracts/coordination/ITACoRootToChild.sol +++ b/contracts/contracts/coordination/ITACoRootToChild.sol @@ -13,6 +13,4 @@ interface ITACoRootToChild { function updateOperator(address stakingProvider, address operator) external; function updateAuthorization(address stakingProvider, uint96 amount) external; - - function batchUpdate(bytes32[] calldata updateInfo) external; } diff --git a/contracts/contracts/coordination/TACoChildApplication.sol b/contracts/contracts/coordination/TACoChildApplication.sol index 9f45f395..865ff484 100644 --- a/contracts/contracts/coordination/TACoChildApplication.sol +++ b/contracts/contracts/coordination/TACoChildApplication.sol @@ -2,7 +2,8 @@ pragma solidity ^0.8.0; -import "@openzeppelin/contracts/access/AccessControl.sol"; +import "@openzeppelin-upgradeable/contracts/access/AccessControlUpgradeable.sol"; +import "@openzeppelin-upgradeable/contracts/proxy/utils/Initializable.sol"; import "./ITACoRootToChild.sol"; import "../../threshold/ITACoChildApplication.sol"; import "./ITACoChildToRoot.sol"; @@ -11,9 +12,7 @@ import "./ITACoChildToRoot.sol"; * @title TACoChildApplication * @notice TACoChildApplication */ -contract TACoChildApplication is AccessControl, ITACoRootToChild, ITACoChildApplication { - bytes32 public constant UPDATE_ROLE = keccak256("UPDATE_ROLE"); - +contract TACoChildApplication is ITACoRootToChild, ITACoChildApplication, Initializable { struct StakingProviderInfo { address operator; bool operatorConfirmed; @@ -27,27 +26,30 @@ contract TACoChildApplication is AccessControl, ITACoRootToChild, ITACoChildAppl mapping(address => StakingProviderInfo) public stakingProviderInfo; mapping(address => address) public stakingProviderFromOperator; - constructor(ITACoChildToRoot _rootApplication, address[] memory updaters) { + /** + * @dev Checks caller is root application + */ + modifier onlyRootApplication() { + require(msg.sender == address(rootApplication), "Caller must be the root application"); + _; + } + + constructor(ITACoChildToRoot _rootApplication) { require( address(_rootApplication) != address(0), "Address for root application must be specified" ); rootApplication = _rootApplication; - - // TODO Issue #112 - _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); - for (uint256 i = 0; i < updaters.length; i++) { - _grantRole(UPDATE_ROLE, updaters[i]); - } } - function setCoordinator(address _coordinator) external onlyRole(DEFAULT_ADMIN_ROLE) { + /** + * @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"); // require(_coordinator.numberOfRituals() >= 0, "Invalid coordinator"); coordinator = _coordinator; - - // TODO reset role? } function authorizedStake(address _stakingProvider) external view returns (uint96) { @@ -57,14 +59,14 @@ contract TACoChildApplication is AccessControl, ITACoRootToChild, ITACoChildAppl function updateOperator( address stakingProvider, address operator - ) external override onlyRole(UPDATE_ROLE) { + ) external override onlyRootApplication { _updateOperator(stakingProvider, operator); } function updateAuthorization( address stakingProvider, uint96 amount - ) external override onlyRole(UPDATE_ROLE) { + ) external override onlyRootApplication { _updateAuthorization(stakingProvider, amount); } @@ -94,29 +96,41 @@ contract TACoChildApplication is AccessControl, ITACoRootToChild, ITACoChildAppl } } - function batchUpdate(bytes32[] calldata updateInfo) external override onlyRole(UPDATE_ROLE) { - require(updateInfo.length % 2 == 0, "bad length"); - for (uint256 i = 0; i < updateInfo.length; i += 2) { - bytes32 word0 = updateInfo[i]; - bytes32 word1 = updateInfo[i + 1]; - - address provider = address(bytes20(word0)); - uint96 amount = uint96(uint256(word0)); - address operator = address(bytes20(word1)); - - _updateOperator(provider, operator); - _updateAuthorization(provider, amount); - } - } - function confirmOperatorAddress(address _operator) external override { require(msg.sender == coordinator, "Only Coordinator allowed to confirm operator"); address stakingProvider = stakingProviderFromOperator[_operator]; StakingProviderInfo storage info = stakingProviderInfo[stakingProvider]; require(info.authorized > 0, "No stake associated with the operator"); - // TODO maybe allow second confirmation, just do not send root call + // TODO maybe allow second confirmation, just do not send root call? require(!info.operatorConfirmed, "Can't confirm same operator twice"); info.operatorConfirmed = true; rootApplication.confirmOperatorAddress(_operator); } } + +contract TestnetTACoChildApplication is AccessControlUpgradeable, TACoChildApplication { + bytes32 public constant UPDATE_ROLE = keccak256("UPDATE_ROLE"); + + constructor(ITACoChildToRoot _rootApplication) TACoChildApplication(_rootApplication) {} + + function initialize(address _coordinator, address[] memory updaters) external initializer { + coordinator = _coordinator; + for (uint256 i = 0; i < updaters.length; i++) { + _grantRole(UPDATE_ROLE, updaters[i]); + } + } + + function forceUpdateOperator( + address stakingProvider, + address operator + ) external onlyRole(UPDATE_ROLE) { + _updateOperator(stakingProvider, operator); + } + + function forceUpdateAuthorization( + address stakingProvider, + uint96 amount + ) external onlyRole(UPDATE_ROLE) { + _updateAuthorization(stakingProvider, amount); + } +} diff --git a/contracts/test/TACoApplicationTestSet.sol b/contracts/test/TACoApplicationTestSet.sol index e5a7ce13..ff95fc36 100644 --- a/contracts/test/TACoApplicationTestSet.sol +++ b/contracts/test/TACoApplicationTestSet.sol @@ -169,22 +169,3 @@ contract ChildApplicationForTACoApplicationMock { rootApplication.confirmOperatorAddress(_operator); } } - -// /** -// * @notice Intermediary contract for testing operator -// */ -// contract Intermediary { -// TACoApplication public immutable application; - -// constructor(TACoApplication _application) { -// application = _application; -// } - -// function bondOperator(address _operator) external { -// application.bondOperator(address(this), _operator); -// } - -// function confirmOperatorAddress() external { -// application.confirmOperatorAddress(); -// } -// } diff --git a/contracts/xchain/PolygonChild.sol b/contracts/xchain/PolygonChild.sol index 99c55b7a..942ff278 100644 --- a/contracts/xchain/PolygonChild.sol +++ b/contracts/xchain/PolygonChild.sol @@ -31,8 +31,4 @@ contract PolygonChild is ITACoChildToRoot, FxBaseChildTunnel, Ownable { ); _sendMessageToRoot(message); } - - function setStakeInfoAddress(address _stakeInfoAddress) public onlyOwner { - stakeInfoAddress = _stakeInfoAddress; - } } diff --git a/contracts/xchain/PolygonRoot.sol b/contracts/xchain/PolygonRoot.sol index 4a231ed4..0c675830 100644 --- a/contracts/xchain/PolygonRoot.sol +++ b/contracts/xchain/PolygonRoot.sol @@ -7,8 +7,6 @@ import "../contracts/coordination/ITACoRootToChild.sol"; contract PolygonRoot is FxBaseRootTunnel, ITACoRootToChild { address public immutable rootApplication; - // bytes public latestData; - constructor( address _checkpointManager, address _fxRoot, @@ -29,7 +27,6 @@ contract PolygonRoot is FxBaseRootTunnel, ITACoRootToChild { } function _processMessageFromChild(bytes memory data) internal override { - // latestData = data; // solhint-disable-next-line avoid-low-level-calls rootApplication.call(data); } @@ -57,12 +54,4 @@ contract PolygonRoot is FxBaseRootTunnel, ITACoRootToChild { ); _sendMessageToChild(message); } - - function batchUpdate(bytes32[] calldata updateInfo) external override onlyRootApplication { - bytes memory message = abi.encodeWithSelector( - ITACoRootToChild.batchUpdate.selector, - updateInfo - ); - _sendMessageToChild(message); - } } diff --git a/tests/application/test_operator.py b/tests/application/test_operator.py index 1774c079..c490b6f5 100644 --- a/tests/application/test_operator.py +++ b/tests/application/test_operator.py @@ -66,8 +66,8 @@ def test_bond_operator(accounts, threshold_staking, taco_application, child_appl assert taco_application.stakingProviderFromOperator(staking_provider_4) == ZERO_ADDRESS # Staking provider can't confirm operator address because there is no operator by default - with ape.reverts(): - child_application.confirmOperatorAddress(staking_provider_1, sender=staking_provider_1) + child_application.confirmOperatorAddress(staking_provider_1, sender=staking_provider_1) + assert not taco_application.isOperatorConfirmed(staking_provider_1) # Staking provider can't bond another staking provider as operator with ape.reverts(): @@ -195,8 +195,8 @@ def test_bond_operator(accounts, threshold_staking, taco_application, child_appl ] # Now the previous operator can no longer make a confirmation - with ape.reverts(): - child_application.confirmOperatorAddress(operator1, sender=operator1) + child_application.confirmOperatorAddress(operator1, sender=operator1) + assert not taco_application.isOperatorConfirmed(operator1) # Only new operator can child_application.confirmOperatorAddress(operator2, sender=operator2) assert not taco_application.isOperatorConfirmed(operator1) @@ -355,9 +355,10 @@ def test_confirm_address( min_authorization = MIN_AUTHORIZATION min_operator_seconds = MIN_OPERATOR_SECONDS - # Operator must be associated with staking provider - with ape.reverts(): - child_application.confirmOperatorAddress(staking_provider, sender=staking_provider) + # Skips confirmation if operator is not associated with staking provider + child_application.confirmOperatorAddress(staking_provider, sender=staking_provider) + assert not taco_application.isOperatorConfirmed(staking_provider) + threshold_staking.setRoles(staking_provider, sender=creator) threshold_staking.authorizationIncreased(staking_provider, 0, min_authorization, sender=creator) diff --git a/tests/test_coordinator.py b/tests/test_coordinator.py index 229e66db..36a42a08 100644 --- a/tests/test_coordinator.py +++ b/tests/test_coordinator.py @@ -69,8 +69,7 @@ def treasury(accounts): @pytest.fixture() def application(project, deployer, nodes): - dummy = project.Dummy.deploy(sender=deployer) - contract = project.TACoChildApplication.deploy(dummy.address, [deployer], sender=deployer) + contract = project.ChildApplicationForCoordinatorMock.deploy(sender=deployer) for n in nodes: contract.updateOperator(n, n, sender=deployer) contract.updateAuthorization(n, 42, sender=deployer) @@ -97,7 +96,6 @@ def coordinator(project, deployer, application, erc20, initiator): sender=deployer, ) contract.grantRole(contract.INITIATOR_ROLE(), initiator, sender=admin) - application.setCoordinator(contract.address, sender=deployer) return contract From 7b7899c20131db1fb5e0e3ec2df8db82befdcc7d Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Wed, 30 Aug 2023 09:29:55 -0400 Subject: [PATCH 12/18] Update contracts for coordinator test --- contracts/test/CoordinatorTestSet.sol | 49 +++++++++++++++++++ .../test/TACoChildApplicationTestSet.sol | 47 ++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 contracts/test/CoordinatorTestSet.sol create mode 100644 contracts/test/TACoChildApplicationTestSet.sol diff --git a/contracts/test/CoordinatorTestSet.sol b/contracts/test/CoordinatorTestSet.sol new file mode 100644 index 00000000..b36cc008 --- /dev/null +++ b/contracts/test/CoordinatorTestSet.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.0; + +import "../threshold/ITACoChildApplication.sol"; + +/** + * @notice Contract for testing Coordinator contract + */ +contract ChildApplicationForCoordinatorMock is ITACoChildApplication { + mapping(address => uint96) public authorizedStake; + mapping(address => address) public operatorFromStakingProvider; + mapping(address => address) public stakingProviderFromOperator; + mapping(address => bool) public confirmations; + + function updateOperator(address _stakingProvider, address _operator) external { + address oldOperator = operatorFromStakingProvider[_stakingProvider]; + stakingProviderFromOperator[oldOperator] = address(0); + operatorFromStakingProvider[_stakingProvider] = _operator; + stakingProviderFromOperator[_operator] = _stakingProvider; + } + + function updateAuthorization(address _stakingProvider, uint96 _amount) external { + authorizedStake[_stakingProvider] = _amount; + } + + function confirmOperatorAddress(address _operator) external { + confirmations[_operator] = true; + } +} + +// /** +// * @notice Intermediary contract for testing operator +// */ +// contract Intermediary { +// TACoApplication public immutable application; + +// constructor(TACoApplication _application) { +// application = _application; +// } + +// function bondOperator(address _operator) external { +// application.bondOperator(address(this), _operator); +// } + +// function confirmOperatorAddress() external { +// application.confirmOperatorAddress(); +// } +// } diff --git a/contracts/test/TACoChildApplicationTestSet.sol b/contracts/test/TACoChildApplicationTestSet.sol new file mode 100644 index 00000000..c6c21b76 --- /dev/null +++ b/contracts/test/TACoChildApplicationTestSet.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.0; + +import "../contracts/coordination/ITACoRootToChild.sol"; +import "../contracts/coordination/ITACoChildToRoot.sol"; + +/** + * @notice Contract for testing TACo child application contract + */ +contract RootApplicationForTACoChildApplicationMock { + ITACoRootToChild public childApplication; + + mapping(address => bool) public confirmations; + + function setChildApplication(ITACoRootToChild _childApplication) external { + childApplication = _childApplication; + } + + function updateOperator(address _stakingProvider, address _operator) external { + childApplication.updateOperator(_stakingProvider, _operator); + } + + function updateAuthorization(address _stakingProvider, uint96 _amount) external { + childApplication.updateAuthorization(_stakingProvider, _amount); + } + + function confirmOperatorAddress(address _operator) external { + confirmations[_operator] = true; + } + + function resetConfirmation(address _operator) external { + confirmations[_operator] = false; + } +} + +contract CoordinatorForTACoChildApplicationMock { + ITACoChildToRoot public immutable application; + + constructor(ITACoChildToRoot _application) { + application = _application; + } + + function confirmOperatorAddress(address _operator) external { + application.confirmOperatorAddress(_operator); + } +} From 19ca893e13c3059ebd0f0a5e110b2bc96d0061bf Mon Sep 17 00:00:00 2001 From: James Campbell Date: Wed, 30 Aug 2023 17:30:26 +0200 Subject: [PATCH 13/18] Update the x chain deploy script for full TACO test --- ape-config.yaml | 2 + scripts/deploy_xchain_test.py | 83 ++++++++++++++++++++++++++++------- 2 files changed, 68 insertions(+), 17 deletions(-) diff --git a/ape-config.yaml b/ape-config.yaml index 1ffd5886..1841bc8a 100644 --- a/ape-config.yaml +++ b/ape-config.yaml @@ -85,6 +85,8 @@ deployments: max_dkg_size: 8 pre_application: '0x685b8Fd02aB87d8FfFff7346cB101A5cE4185bf3' verify: True + reward_duration: 604800 # one week in seconds + deauthorization_duration: 5184000 # 60 days in seconds - contract_type: fx_root address: '0x3d1d3E34f7fB6D26245E6640E1c50710eFFf15bA' - contract_type: checkpoint_manager diff --git a/scripts/deploy_xchain_test.py b/scripts/deploy_xchain_test.py index 610b5b1a..8f06720c 100644 --- a/scripts/deploy_xchain_test.py +++ b/scripts/deploy_xchain_test.py @@ -2,6 +2,8 @@ from ape import accounts, config, networks, project from ape.cli import NetworkBoundCommand, account_option +DEPENDENCY = project.dependencies["openzeppelin"]["4.9.1"] + def convert_config(config): result = {} @@ -13,38 +15,82 @@ def convert_config(config): return result -def deploy_eth_contracts(deployer, source, child_address, config, eth_network): +def deploy_eth_contracts(deployer, child_address, config, eth_network): # Connect to the Ethereum network with eth_network.use_provider("infura"): - polygon_root = project.PolygonRoot.deploy( + + token = project.TToken.deploy( + 100_000_000_000 * 10**18, + sender=deployer, + ) + + threshold_staking = project.ThresholdStakingForTACoApplicationMock.deploy( + sender=deployer, + ) + + taco_app = project.TACoApplication.deploy( + token, + threshold_staking, + config["pre_min_authorization"], + config["pre_min_operator_seconds"], + config["reward_duration"], + config["deauthorization_duration"], + sender=deployer, + ) + + proxy_admin = DEPENDENCY.ProxyAdmin.deploy(sender=deployer) + proxy = DEPENDENCY.TransparentUpgradeableProxy.deploy( + taco_app.address, + proxy_admin.address, + taco_app.initialize.encode_input(), + sender=deployer, + ) + + proxy_contract = project.TACoApplication.at(proxy.address) + threshold_staking.setApplication(proxy_contract.address, sender=deployer) + + root = project.PolygonRoot.deploy( config["checkpoint_manager"], config["fx_root"], - source, + proxy_contract.address, child_address, sender=deployer, publish=False, ) - return polygon_root + return root, proxy_contract, threshold_staking def deploy_polygon_contracts(deployer, config, poly_network): # Connect to the Polygon network with poly_network.use_provider("infura"): - stake_info = project.StakeInfo.deploy( - [deployer.address], + polygon_child = project.PolygonChild.deploy( + config["fx_child"], sender=deployer, publish=False, ) - polygon_child = project.PolygonChild.deploy( - config["fx_child"], - stake_info.address, + TACoChild = project.TestnetTACoChildApplication.deploy( + polygon_child.address, sender=deployer, publish=False, ) + proxy_admin = DEPENDENCY.ProxyAdmin.deploy(sender=deployer) + proxy = DEPENDENCY.TransparentUpgradeableProxy.deploy( + TACoChild.address, + proxy_admin.address, + b"", + sender=deployer, + ) + proxy_contract = project.TACoChildApplication.at(proxy.address) + polygon_child.setChildApplication(proxy_contract.address, sender=deployer) + + coordinator = project.CoordinatorForTACoChildApplicationMock.deploy( + proxy_contract.address, sender=deployer + ) + proxy_contract.initialize(coordinator.address, [deployer], sender=deployer) - return polygon_child, stake_info + return polygon_child, proxy_contract, coordinator # TODO: Figure out better way to retrieve the TACo app contract address @@ -69,18 +115,21 @@ def cli(network_type, account): print("POLYGON CONFIG: {}".format(poly_config)) with accounts.use_sender(deployer): - child, stake_info = deploy_polygon_contracts( + poly_child, taco_child_app, coordinator = deploy_polygon_contracts( deployer, convert_config(poly_config), poly_network ) - root = deploy_eth_contracts( - deployer, deployer.address, child.address, convert_config(eth_config), eth_network + root, taco_root_app, threshold_staking = deploy_eth_contracts( + deployer, poly_child.address, convert_config(eth_config), eth_network ) # Set the root contract address in the child contract with poly_network.use_provider("infura"): - child.setFxRootTunnel(root.address) - stake_info.addUpdaters([child.address]) + poly_child.setFxRootTunnel(root.address) + taco_child_app.addUpdaters([poly_child.address]) - print("CHILD: {}".format(child.address)) - print("STAKE INFO: {}".format(stake_info.address)) + print("CHILD: {}".format(poly_child.address)) + print("TACo CHILD APP: {}".format(taco_child_app.address)) + print("COORDINATOR: {}".format(coordinator.address)) print("ROOT: {}".format(root.address)) + print("THRESHOLD STAKING: {}".format(threshold_staking.address)) + print("TACO ROOT APP: {}".format(taco_root_app.address)) From bef7fa1f7f1e4d3b8e11cda542a92fd6b73de416 Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Wed, 30 Aug 2023 16:51:55 -0400 Subject: [PATCH 14/18] Tests for child app --- .../coordination/TACoChildApplication.sol | 5 +- tests/test_child_application.py | 196 ++++++++++++++++++ 2 files changed, 199 insertions(+), 2 deletions(-) create mode 100644 tests/test_child_application.py diff --git a/contracts/contracts/coordination/TACoChildApplication.sol b/contracts/contracts/coordination/TACoChildApplication.sol index 865ff484..96cb428f 100644 --- a/contracts/contracts/coordination/TACoChildApplication.sol +++ b/contracts/contracts/coordination/TACoChildApplication.sol @@ -74,7 +74,7 @@ contract TACoChildApplication is ITACoRootToChild, ITACoChildApplication, Initia StakingProviderInfo storage info = stakingProviderInfo[stakingProvider]; address oldOperator = info.operator; - if (operator != oldOperator) { + if (stakingProvider != address(0) && operator != oldOperator) { info.operator = operator; // Update operator to provider mapping stakingProviderFromOperator[oldOperator] = address(0); @@ -90,7 +90,7 @@ contract TACoChildApplication is ITACoRootToChild, ITACoChildApplication, Initia StakingProviderInfo storage info = stakingProviderInfo[stakingProvider]; uint96 fromAmount = info.authorized; - if (amount != fromAmount) { + if (stakingProvider != address(0) && amount != fromAmount) { info.authorized = amount; emit AuthorizationUpdated(stakingProvider, amount); } @@ -105,6 +105,7 @@ contract TACoChildApplication is ITACoRootToChild, ITACoChildApplication, Initia require(!info.operatorConfirmed, "Can't confirm same operator twice"); info.operatorConfirmed = true; rootApplication.confirmOperatorAddress(_operator); + emit OperatorConfirmed(stakingProvider, _operator); } } diff --git a/tests/test_child_application.py b/tests/test_child_application.py new file mode 100644 index 00000000..0706447e --- /dev/null +++ b/tests/test_child_application.py @@ -0,0 +1,196 @@ +""" +This file is part of nucypher. + +nucypher is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +nucypher is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with nucypher. If not, see . +""" +import ape +import pytest +from ape import project +from ape.utils import ZERO_ADDRESS +from web3 import Web3 + +DEPENDENCY = project.dependencies["openzeppelin"]["4.9.1"] + +OPERATOR_SLOT = 0 +CONFIRMATION_SLOT = 1 + + +@pytest.fixture() +def root_application(project, creator): + contract = project.RootApplicationForTACoChildApplicationMock.deploy(sender=creator) + return contract + + +@pytest.fixture() +def child_application(project, creator, root_application): + contract = project.TACoChildApplication.deploy(root_application.address, sender=creator) + + proxy_admin = DEPENDENCY.ProxyAdmin.deploy(sender=creator) + proxy = DEPENDENCY.TransparentUpgradeableProxy.deploy( + contract.address, + proxy_admin.address, + b"", + sender=creator, + ) + proxy_contract = project.TACoChildApplication.at(proxy.address) + root_application.setChildApplication(proxy_contract.address, sender=creator) + + return proxy_contract + + +@pytest.fixture() +def coordinator(project, child_application, creator): + contract = project.CoordinatorForTACoChildApplicationMock.deploy( + child_application, sender=creator + ) + child_application.initialize(contract.address, sender=creator) + return contract + + +def test_update_operator(accounts, root_application, child_application): + ( + creator, + staking_provider_1, + staking_provider_2, + operator_1, + operator_2, + *everyone_else, + ) = accounts[0:] + + # Call to update operator can be done only from root app + with ape.reverts("Caller must be the root application"): + child_application.updateOperator(staking_provider_1, operator_1, sender=creator) + + # First bonding of operator + tx = root_application.updateOperator(staking_provider_1, operator_1, sender=creator) + assert child_application.stakingProviderFromOperator(operator_1) == staking_provider_1 + assert child_application.stakingProviderInfo(staking_provider_1)[OPERATOR_SLOT] == operator_1 + assert not child_application.stakingProviderInfo(staking_provider_1)[CONFIRMATION_SLOT] + + assert tx.events == [ + child_application.OperatorUpdated(stakingProvider=staking_provider_1, operator=operator_1) + ] + + # Rebond operator + tx = root_application.updateOperator(staking_provider_1, operator_2, sender=creator) + assert child_application.stakingProviderFromOperator(operator_2) == staking_provider_1 + assert child_application.stakingProviderFromOperator(operator_1) == ZERO_ADDRESS + assert child_application.stakingProviderInfo(staking_provider_1)[OPERATOR_SLOT] == operator_2 + assert not child_application.stakingProviderInfo(operator_2)[CONFIRMATION_SLOT] + assert not child_application.stakingProviderInfo(operator_1)[CONFIRMATION_SLOT] + + assert tx.events == [ + child_application.OperatorUpdated(stakingProvider=staking_provider_1, operator=operator_2) + ] + + # Unbond operator + tx = root_application.updateOperator(staking_provider_1, ZERO_ADDRESS, sender=creator) + assert child_application.stakingProviderFromOperator(operator_2) == ZERO_ADDRESS + assert child_application.stakingProviderInfo(staking_provider_1)[OPERATOR_SLOT] == ZERO_ADDRESS + assert not child_application.stakingProviderInfo(operator_2)[CONFIRMATION_SLOT] + + assert tx.events == [ + child_application.OperatorUpdated(stakingProvider=staking_provider_1, operator=ZERO_ADDRESS) + ] + + # Bonding from another address + tx = root_application.updateOperator(staking_provider_2, operator_1, sender=creator) + assert child_application.stakingProviderFromOperator(operator_1) == staking_provider_2 + assert child_application.stakingProviderInfo(staking_provider_2)[OPERATOR_SLOT] == operator_1 + assert not child_application.stakingProviderInfo(operator_1)[CONFIRMATION_SLOT] + + assert tx.events == [ + child_application.OperatorUpdated(stakingProvider=staking_provider_2, operator=operator_1) + ] + + +def test_update_authorization(accounts, root_application, child_application): + creator, staking_provider, *everyone_else = accounts[0:] + value = Web3.to_wei(40_000, "ether") + + # Call to update auhtorization can be done only from root app + with ape.reverts("Caller must be the root application"): + child_application.updateAuthorization(staking_provider, value, sender=creator) + + # First increazing of authorization + tx = root_application.updateAuthorization(staking_provider, value, sender=creator) + assert child_application.authorizedStake(staking_provider) == value + assert tx.events == [ + child_application.AuthorizationUpdated(stakingProvider=staking_provider, amount=value) + ] + + # Deauthorization imitation + tx = root_application.updateAuthorization(staking_provider, value // 2, sender=creator) + assert child_application.authorizedStake(staking_provider) == value // 2 + assert tx.events == [ + child_application.AuthorizationUpdated(stakingProvider=staking_provider, amount=value // 2) + ] + + # Increazing of authorization again + tx = root_application.updateAuthorization(staking_provider, value, sender=creator) + assert child_application.authorizedStake(staking_provider) == value + assert tx.events == [ + child_application.AuthorizationUpdated(stakingProvider=staking_provider, amount=value) + ] + + # Full deauthorization + tx = root_application.updateAuthorization(staking_provider, 0, sender=creator) + assert child_application.authorizedStake(staking_provider) == 0 + assert tx.events == [ + child_application.AuthorizationUpdated(stakingProvider=staking_provider, amount=0) + ] + + +def test_confirm_address(accounts, root_application, child_application, coordinator): + creator, staking_provider, operator, *everyone_else = accounts[0:] + value = Web3.to_wei(40_000, "ether") + + # Call to confirm operator address can be done only from coordinator + with ape.reverts("Only Coordinator allowed to confirm operator"): + child_application.confirmOperatorAddress(operator, sender=creator) + + # Can't confirm operator address without bonding with staking provider + with ape.reverts("No stake associated with the operator"): + coordinator.confirmOperatorAddress(operator, sender=creator) + + # First bonding of operator + root_application.updateOperator(staking_provider, operator, sender=creator) + assert child_application.stakingProviderFromOperator(operator) == staking_provider + assert child_application.stakingProviderInfo(staking_provider)[OPERATOR_SLOT] == operator + assert not child_application.stakingProviderInfo(staking_provider)[CONFIRMATION_SLOT] + + # Can't confirm operator address without authorized amount + with ape.reverts("No stake associated with the operator"): + coordinator.confirmOperatorAddress(operator, sender=creator) + + # Confirm operator address + root_application.updateAuthorization(staking_provider, value, sender=creator) + tx = coordinator.confirmOperatorAddress(operator, sender=creator) + assert child_application.stakingProviderInfo(staking_provider)[CONFIRMATION_SLOT] + assert root_application.confirmations(operator) + assert tx.events == [ + child_application.OperatorConfirmed(stakingProvider=staking_provider, operator=operator) + ] + + # Can't confirm twice + with ape.reverts("Can't confirm same operator twice"): + coordinator.confirmOperatorAddress(operator, sender=creator) + + # Changing operator resets confirmation + root_application.updateOperator(staking_provider, staking_provider, sender=creator) + assert child_application.stakingProviderFromOperator(staking_provider) == staking_provider + assert ( + child_application.stakingProviderInfo(staking_provider)[OPERATOR_SLOT] == staking_provider + ) + assert not child_application.stakingProviderInfo(staking_provider)[CONFIRMATION_SLOT] From 5bce8314eeac987389cde9ea3464d85e76fa57ba Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Thu, 31 Aug 2023 22:33:28 -0400 Subject: [PATCH 15/18] Updates dependencies and config to use deploy_xchain_test --- ape-config.yaml | 67 ++++++++++--------- .../coordination/TACoChildApplication.sol | 2 +- requirements.txt | 30 ++++----- scripts/deploy_xchain_test.py | 4 +- 4 files changed, 53 insertions(+), 50 deletions(-) diff --git a/ape-config.yaml b/ape-config.yaml index 1841bc8a..66fa695b 100644 --- a/ape-config.yaml +++ b/ape-config.yaml @@ -12,6 +12,10 @@ dependencies: - name: openzeppelin github: OpenZeppelin/openzeppelin-contracts version: 4.9.1 + config_override: + solidity: + version: 0.8.20 + evm_version: paris - name: openzeppelin-upgradeable github: OpenZeppelin/openzeppelin-contracts-upgradeable version: 4.9.1 @@ -35,36 +39,36 @@ deployments: polygon: mainnet: - contract_type: DAI - address: '0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063' + address: "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063" - contract_type: fx_child - address: '0x8397259c983751DAf40400790063935a11afa28a' + address: "0x8397259c983751DAf40400790063935a11afa28a" mumbai: - contract_type: DAI - address: '0x001B3B4d0F3714Ca98ba10F6042DaEbF0B1B7b6F' + address: "0x001B3B4d0F3714Ca98ba10F6042DaEbF0B1B7b6F" - contract_type: StakeInfo - address: '0xC1379866Fb0c100DCBFAb7b470009C4827D47DD8' + address: "0xC1379866Fb0c100DCBFAb7b470009C4827D47DD8" - contract_type: fx_child - address: '0xCf73231F28B7331BBe3124B907840A94851f9f11' + address: "0xCf73231F28B7331BBe3124B907840A94851f9f11" ethereum: local: - nu_token_supply: 1_000_000_000 pre_min_authorization: 40000000000000000000000 - pre_min_operator_seconds: 86400 # one day in seconds + pre_min_operator_seconds: 86400 # one day in seconds pre_hash_algorithm: 1 pre_base_penalty: 2 pre_penalty_history_coefficient: 0 pre_percentage_penalty_coefficient: 100000 - reward_duration: 604800 # one week in seconds - deauthorization_duration: 5184000 # 60 days in seconds + reward_duration: 604800 # one week in seconds + deauthorization_duration: 5184000 # 60 days in seconds verify: False rinkeby: - - nu_token: '0x78D591D90a4a768B9D2790deA465D472b6Fe0f18' + - nu_token: "0x78D591D90a4a768B9D2790deA465D472b6Fe0f18" nu_token_supply: 1_000_000_000 - t_staking: '0x18eFb520dA5D387982C860a64855C14C0AcADF3F' - work_lock: '0x0000000000000000000000000000000000000000' - staking_escrow: '0x6A6F917a3FF3d33d26BB4743140F205486cD6B4B' + t_staking: "0x18eFb520dA5D387982C860a64855C14C0AcADF3F" + work_lock: "0x0000000000000000000000000000000000000000" + staking_escrow: "0x6A6F917a3FF3d33d26BB4743140F205486cD6B4B" pre_min_authorization: 40000000000000000000000 - pre_min_operator_seconds: 86400 # one day in seconds + pre_min_operator_seconds: 86400 # one day in seconds verify: True # TODO: is there a batter way to manage multiple deployments on the same network? # goerli-tapir: @@ -78,39 +82,38 @@ deployments: # pre_min_authorization: 40000000000000000000000 # pre_min_operator_seconds: 86400 # one day in seconds goerli: # -lynx - - t_staking: '0x81eEefb7B1b3313C89976910096F8f1487301Bb1' + - t_staking: "0x81eEefb7B1b3313C89976910096F8f1487301Bb1" pre_min_authorization: 40000000000000000000000 - pre_min_operator_seconds: 86400 # one day in seconds + pre_min_operator_seconds: 86400 # one day in seconds ritual_timeout: 3600 max_dkg_size: 8 - pre_application: '0x685b8Fd02aB87d8FfFff7346cB101A5cE4185bf3' + pre_application: "0x685b8Fd02aB87d8FfFff7346cB101A5cE4185bf3" verify: True - reward_duration: 604800 # one week in seconds - deauthorization_duration: 5184000 # 60 days in seconds + reward_duration: 604800 # one week in seconds + deauthorization_duration: 5184000 # 60 days in seconds - contract_type: fx_root - address: '0x3d1d3E34f7fB6D26245E6640E1c50710eFFf15bA' + address: "0x3d1d3E34f7fB6D26245E6640E1c50710eFFf15bA" - contract_type: checkpoint_manager - address: '0x2890bA17EfE978480615e330ecB65333b880928e' + address: "0x2890bA17EfE978480615e330ecB65333b880928e" mumbai: - - stake_info_contract: '0xC1379866Fb0c100DCBFAb7b470009C4827D47DD8' + - stake_info_contract: "0xC1379866Fb0c100DCBFAb7b470009C4827D47DD8" max_dkg_size: 16 - ritual_timeout: 86400 # one day in seconds + ritual_timeout: 86400 # one day in seconds verify: True - checkpoint_manager: '0x2890bA17EfE978480615e330ecB65333b880928e' - fx_root: '0x3d1d3E34f7fB6D26245E6640E1c50710eFFf15bA' + checkpoint_manager: "0x2890bA17EfE978480615e330ecB65333b880928e" + fx_root: "0x3d1d3E34f7fB6D26245E6640E1c50710eFFf15bA" mainnet: - - nu_token: '0x4fE83213D56308330EC302a8BD641f1d0113A4Cc' - t_staking: '0x01B67b1194C75264d06F808A921228a95C765dd7' - work_lock: '0xe9778E69a961e64d3cdBB34CF6778281d34667c2' - staking_escrow: '0xbbD3C0C794F40c4f993B03F65343aCC6fcfCb2e2' + - nu_token: "0x4fE83213D56308330EC302a8BD641f1d0113A4Cc" + t_staking: "0x01B67b1194C75264d06F808A921228a95C765dd7" + work_lock: "0xe9778E69a961e64d3cdBB34CF6778281d34667c2" + staking_escrow: "0xbbD3C0C794F40c4f993B03F65343aCC6fcfCb2e2" pre_min_authorization: 40000000000000000000000 - pre_min_operator_seconds: 86400 # one day in seconds + pre_min_operator_seconds: 86400 # one day in seconds verify: True - contract_type: fx_root - address: '0xfe5e5D361b2ad62c541bAb87C45a0B9B018389a2' + address: "0xfe5e5D361b2ad62c541bAb87C45a0B9B018389a2" - contract_type: checkpoint_manager - address: '0x86e4dc95c7fbdbf52e33d563bbdb00823894c287' - + address: "0x86e4dc95c7fbdbf52e33d563bbdb00823894c287" test: mnemonic: test test test test test test test test test test test junk diff --git a/contracts/contracts/coordination/TACoChildApplication.sol b/contracts/contracts/coordination/TACoChildApplication.sol index 96cb428f..e898a5ef 100644 --- a/contracts/contracts/coordination/TACoChildApplication.sol +++ b/contracts/contracts/coordination/TACoChildApplication.sol @@ -104,8 +104,8 @@ contract TACoChildApplication is ITACoRootToChild, ITACoChildApplication, Initia // TODO maybe allow second confirmation, just do not send root call? require(!info.operatorConfirmed, "Can't confirm same operator twice"); info.operatorConfirmed = true; - rootApplication.confirmOperatorAddress(_operator); emit OperatorConfirmed(stakingProvider, _operator); + rootApplication.confirmOperatorAddress(_operator); } } diff --git a/requirements.txt b/requirements.txt index eec4de04..29f15ef8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ -i https://pypi.org/simple aiohttp==3.8.4 ; python_version >= '3.6' aiosignal==1.3.1 ; python_version >= '3.7' -ape-solidity==0.6.7 +ape-solidity==0.6.8 appnope==0.1.3 ; sys_platform == 'darwin' asn1crypto==1.5.1 asttokens==2.2.1 @@ -18,7 +18,7 @@ cffi==1.15.1 cfgv==3.3.1 ; python_full_version >= '3.6.1' chardet==5.1.0 ; python_version >= '3.7' charset-normalizer==3.1.0 ; python_full_version >= '3.7.0' -click==8.1.3 ; python_version >= '3.7' +click==8.1.6 ; python_version >= '3.7' coincurve==18.0.0 colorama==0.4.6 ; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6' commonmark==0.9.1 @@ -29,19 +29,19 @@ decorator==5.1.1 ; python_version >= '3.5' deprecated==1.2.13 ; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' distlib==0.3.6 eip712==0.2.1 ; python_version >= '3.8' and python_version < '4' -eth-abi==4.0.0 ; python_version >= '3.7' and python_version < '4' +eth-abi==4.1.0 ; python_version >= '3.7' and python_version < '4' eth-account==0.8.0 ; python_version >= '3.6' and python_version < '4' -eth-ape==0.6.13 +eth-ape==0.6.19 eth-bloom==2.0.0 ; python_version >= '3.7' and python_version < '4' eth-hash[pycryptodome]==0.5.1 ; python_version >= '3.7' and python_version < '4' eth-keyfile==0.6.1 eth-keys==0.4.0 eth-rlp==0.3.0 ; python_version >= '3.7' and python_version < '4' -eth-tester[py-evm]==0.9.0b1 -eth-typing==3.3.0 ; python_full_version >= '3.7.2' and python_version < '4' -eth-utils==2.1.0 ; python_version >= '3.7' and python_version < '4' -ethpm-types==0.5.3 ; python_version >= '3.8' and python_version < '4' -evm-trace==0.1.0a21 ; python_version >= '3.8' and python_version < '4' +eth-tester[py-evm]==0.9.1b1 +eth-typing==3.4.0 ; python_full_version >= '3.7.2' and python_version < '4' +eth-utils==2.2.0 ; python_version >= '3.7' and python_version < '4' +ethpm-types==0.5.4 ; python_version >= '3.8' and python_version < '4' +evm-trace==0.1.0a23 ; python_version >= '3.8' and python_version < '4' exceptiongroup==1.1.1 ; python_version < '3.11' executing==1.2.0 filelock==3.12.0 ; python_version >= '3.7' @@ -66,7 +66,7 @@ mccabe==0.7.0 ; python_version >= '3.6' morphys==1.0 msgspec==0.15.1 ; python_version >= '3.8' multidict==6.0.4 ; python_version >= '3.7' -mypy-extensions==0.4.4 ; python_version >= '2.7' +mypy-extensions==1.0.0 ; python_version >= '2.7' nodeenv==1.8.0 ; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6' nucypher-core==0.8.0 numpy==1.24.3 ; python_version < '3.10' @@ -79,7 +79,7 @@ pexpect==4.8.0 ; sys_platform != 'win32' pickleshare==0.7.5 pkgutil-resolve-name==1.3.10 ; python_version < '3.9' platformdirs==3.5.1 ; python_version >= '3.7' -pluggy==1.0.0 ; python_version >= '3.6' +pluggy==1.3.0 ; python_version >= '3.6' pre-commit==3.3.2 prompt-toolkit==3.0.38 ; python_full_version >= '3.7.0' protobuf==4.23.1 ; python_version >= '3.7' @@ -87,7 +87,7 @@ ptyprocess==0.7.0 pure-eval==0.2.2 py-cid==0.3.0 py-ecc==6.0.0 ; python_version >= '3.6' and python_version < '4' -py-evm==0.7.0a2 +py-evm==0.7.0a4 py-geth==3.13.0 ; python_version >= '3' py-multibase==1.0.3 py-multicodec==0.2.1 @@ -99,7 +99,7 @@ pycryptodome==3.18.0 pydantic==1.10.8 ; python_version >= '3.7' pyethash==0.1.27 pyflakes==3.0.1 ; python_version >= '3.6' -pygithub==1.58.2 ; python_version >= '3.7' +pygithub==1.59.0 ; python_version >= '3.7' pygments==2.15.1 ; python_version >= '3.7' pyjwt[crypto]==2.7.0 ; python_version >= '3.7' pynacl==1.5.0 ; python_version >= '3.6' @@ -132,9 +132,9 @@ typing-extensions==4.5.0 ; python_version < '3.10' urllib3==2.0.2 ; python_version >= '3.7' varint==1.0.2 virtualenv==20.23.0 ; python_version >= '3.7' -watchdog==2.3.1 ; python_version >= '3.6' +watchdog==3.0.0 ; python_version >= '3.6' wcwidth==0.2.6 -web3[tester]==6.5.0 ; python_full_version >= '3.7.2' +web3[tester]==6.7.0 ; python_full_version >= '3.7.2' websockets==11.0.3 ; python_version >= '3.7' wrapt==1.15.0 ; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' yarl==1.9.2 ; python_version >= '3.7' diff --git a/scripts/deploy_xchain_test.py b/scripts/deploy_xchain_test.py index 8f06720c..edb4c9d2 100644 --- a/scripts/deploy_xchain_test.py +++ b/scripts/deploy_xchain_test.py @@ -57,6 +57,7 @@ def deploy_eth_contracts(deployer, child_address, config, eth_network): sender=deployer, publish=False, ) + proxy_contract.setChildApplication(root.address, sender=deployer) return root, proxy_contract, threshold_staking @@ -82,7 +83,7 @@ def deploy_polygon_contracts(deployer, config, poly_network): b"", sender=deployer, ) - proxy_contract = project.TACoChildApplication.at(proxy.address) + proxy_contract = project.TestnetTACoChildApplication.at(proxy.address) polygon_child.setChildApplication(proxy_contract.address, sender=deployer) coordinator = project.CoordinatorForTACoChildApplicationMock.deploy( @@ -125,7 +126,6 @@ def cli(network_type, account): # Set the root contract address in the child contract with poly_network.use_provider("infura"): poly_child.setFxRootTunnel(root.address) - taco_child_app.addUpdaters([poly_child.address]) print("CHILD: {}".format(poly_child.address)) print("TACo CHILD APP: {}".format(taco_child_app.address)) From ff36f5bb6f89f6b9227d9d783399c18e2c714ea0 Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Fri, 1 Sep 2023 16:55:52 -0400 Subject: [PATCH 16/18] Final renames StakeInfo -> TACoChildApp --- ape-config.yaml | 2 ++ scripts/check_xchain.py | 10 +++--- scripts/deploy_coordinator.py | 5 ++- scripts/deploy_coordinator_with_fee_model.py | 34 ++++++++++---------- scripts/deploy_flat_rate_fee_model.py | 29 +++++++++-------- 5 files changed, 42 insertions(+), 38 deletions(-) diff --git a/ape-config.yaml b/ape-config.yaml index 66fa695b..d0ddbfd9 100644 --- a/ape-config.yaml +++ b/ape-config.yaml @@ -49,6 +49,8 @@ deployments: address: "0xC1379866Fb0c100DCBFAb7b470009C4827D47DD8" - contract_type: fx_child address: "0xCf73231F28B7331BBe3124B907840A94851f9f11" + - contract_type: TACoChildApplication + address: "0x68E95C2548363Bf5856667065Bc1B89CC498969F" ethereum: local: - nu_token_supply: 1_000_000_000 diff --git a/scripts/check_xchain.py b/scripts/check_xchain.py index 06124cd1..039e7c3b 100644 --- a/scripts/check_xchain.py +++ b/scripts/check_xchain.py @@ -9,7 +9,7 @@ def main(): print("WARNING: This script will take 40 mins to run to allow messages to sync from L1 to L2") print("*******") with networks.ethereum.goerli.use_provider("infura"): - root = project.PolygonRoot.at("0x55D1E362b81FDC6BaA359630bf3Ffa5900F66777") + root = project.PolygonRoot.at("0xD2Cb2A8fbE29adBa1C287b2A0b49f5C4fDc1f5BE") root.updateOperator( "0x3B42d26E19FF860bC4dEbB920DD8caA53F93c600", "0x3B42d26E19FF860bC4dEbB920DD8caA53F93c600", @@ -21,10 +21,12 @@ def main(): time.sleep(60 * i * 5) print("Now: {}".format(time.time())) with networks.polygon.mumbai.use_provider("infura"): - stake_info = project.StakeInfo.at("0x96e7dBa88f79e5CCAEBf0c7678539F6C0d719c99") + taco_child = project.TACoChildApplication.at( + "0x68E95C2548363Bf5856667065Bc1B89CC498969F" + ) print( - stake_info.stakingProviderFromOperator("0xAe87D865F3A507185656aD0ef52a8E0B9f3d58f8") + taco_child.stakingProviderFromOperator("0xAe87D865F3A507185656aD0ef52a8E0B9f3d58f8") ) print( - stake_info.stakingProviderFromOperator("0x3B42d26E19FF860bC4dEbB920DD8caA53F93c600") + taco_child.stakingProviderFromOperator("0x3B42d26E19FF860bC4dEbB920DD8caA53F93c600") ) diff --git a/scripts/deploy_coordinator.py b/scripts/deploy_coordinator.py index 0f066d27..991ce18a 100644 --- a/scripts/deploy_coordinator.py +++ b/scripts/deploy_coordinator.py @@ -1,8 +1,7 @@ #!/usr/bin/python3 from ape import project from ape.cli import get_user_selected_account - -from scripts.utils import DEPLOYMENTS_CONFIG, get_account +from scripts.utils import DEPLOYMENTS_CONFIG def main(account_id=None): @@ -10,7 +9,7 @@ def main(account_id=None): deployments_config = DEPLOYMENTS_CONFIG coordinator = project.Coordinator.deploy( - deployments_config.get("stake_info_contract"), + deployments_config.get("taco_child_contract"), deployments_config.get("ritual_timeout"), deployments_config.get("max_dkg_size"), sender=deployer, diff --git a/scripts/deploy_coordinator_with_fee_model.py b/scripts/deploy_coordinator_with_fee_model.py index 5489d036..08a26f45 100644 --- a/scripts/deploy_coordinator_with_fee_model.py +++ b/scripts/deploy_coordinator_with_fee_model.py @@ -2,24 +2,22 @@ import os -from ape import config, project, networks -from ape.cli import account_option, network_option, NetworkBoundCommand +import click +from ape import config, networks, project +from ape.cli import NetworkBoundCommand, account_option, network_option from ape.utils import ZERO_ADDRESS - from ape_etherscan.utils import API_KEY_ENV_KEY_MAP -import click - @click.command(cls=NetworkBoundCommand) @network_option() @account_option() -@click.option('--currency', default=ZERO_ADDRESS) -@click.option('--rate', default=None) -@click.option('--timeout', default=None) -@click.option('--admin', default=None) -@click.option('--max_size', default=None) -@click.option('--verify/--no-verify', default=True) +@click.option("--currency", default=ZERO_ADDRESS) +@click.option("--rate", default=None) +@click.option("--timeout", default=None) +@click.option("--admin", default=None) +@click.option("--max_size", default=None) +@click.option("--verify/--no-verify", default=True) def cli(network, account, currency, rate, timeout, admin, max_size, verify): deployer = account @@ -27,7 +25,7 @@ def cli(network, account, currency, rate, timeout, admin, max_size, verify): if rate and currency == ZERO_ADDRESS: raise ValueError("ERC20 contract address needed for currency") - + # Network ecosystem_name = networks.provider.network.ecosystem.name network_name = networks.provider.network.name @@ -38,6 +36,7 @@ def cli(network, account, currency, rate, timeout, admin, max_size, verify): # Validate Etherscan verification parameters. # This import fails if called before the click network options are evaluated from scripts.utils import LOCAL_BLOCKCHAIN_ENVIRONMENTS + is_public_deployment = network_name not in LOCAL_BLOCKCHAIN_ENVIRONMENTS if not is_public_deployment: verify = False @@ -59,19 +58,20 @@ def cli(network, account, currency, rate, timeout, admin, max_size, verify): currency = next(d for d in deployments if d["contract_type"] == currency)["address"] except StopIteration: pass - + try: - stakes = next(d for d in deployments if d["contract_type"] == "StakeInfo")["address"] + stakes = next(d for d in deployments if d["contract_type"] == "TACoChildApplication")[ + "address" + ] except StopIteration: - raise ValueError("StakeInfo deployment needed") + raise ValueError("TACoChildApplication deployment needed") # Parameter defaults admin = admin or deployer rate = rate or 1 - timeout = timeout or 60*60 + timeout = timeout or 60 * 60 max_size = max_size or 64 params = (stakes, timeout, max_size, admin, currency, rate) print("Deployment parameters:", params) return project.Coordinator.deploy(*params, sender=deployer, publish=verify) - \ No newline at end of file diff --git a/scripts/deploy_flat_rate_fee_model.py b/scripts/deploy_flat_rate_fee_model.py index 4669af85..8c4a43c3 100644 --- a/scripts/deploy_flat_rate_fee_model.py +++ b/scripts/deploy_flat_rate_fee_model.py @@ -2,28 +2,26 @@ import os -from ape import config, project, networks -from ape.cli import account_option, network_option, NetworkBoundCommand +import click +from ape import config, networks, project +from ape.cli import NetworkBoundCommand, account_option, network_option from ape.utils import ZERO_ADDRESS - from ape_etherscan.utils import API_KEY_ENV_KEY_MAP -import click - @click.command(cls=NetworkBoundCommand) @network_option() @account_option() -@click.option('--currency', default=ZERO_ADDRESS) -@click.option('--rate', default=0) -@click.option('--verify/--no-verify', default=True) +@click.option("--currency", default=ZERO_ADDRESS) +@click.option("--rate", default=0) +@click.option("--verify/--no-verify", default=True) def cli(network, account, currency, rate, verify): - deployer = account #get_account(account_id) + deployer = account # get_account(account_id) click.echo(f"Deployer: {deployer}") if rate and currency == ZERO_ADDRESS: raise ValueError("ERC20 contract address needed for currency") - + # Network ecosystem_name = networks.provider.network.ecosystem.name network_name = networks.provider.network.name @@ -34,6 +32,7 @@ def cli(network, account, currency, rate, verify): # Validate Etherscan verification parameters. # This import fails if called before the click network options are evaluated from scripts.utils import LOCAL_BLOCKCHAIN_ENVIRONMENTS + is_public_deployment = network_name not in LOCAL_BLOCKCHAIN_ENVIRONMENTS if not is_public_deployment: verify = False @@ -53,11 +52,13 @@ def cli(network, account, currency, rate, verify): currency = next(d for d in deployments if d["contract_type"] == currency)["address"] except StopIteration: pass - + try: - stakes = next(d for d in deployments if d["contract_type"] == "StakeInfo")["address"] + stakes = next(d for d in deployments if d["contract_type"] == "TACoChildApplication")[ + "address" + ] except StopIteration: - raise ValueError("StakeInfo deployment needed") + raise ValueError("TACoChildApplication deployment needed") flat_rate_fee_model = project.FlatRateFeeModel.deploy( currency, @@ -66,4 +67,4 @@ def cli(network, account, currency, rate, verify): sender=deployer, publish=verify, ) - return flat_rate_fee_model \ No newline at end of file + return flat_rate_fee_model From 34089fa11d87f275d3ec0fff6e03345a46deed5c Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Wed, 6 Sep 2023 21:16:26 -0400 Subject: [PATCH 17/18] Checks X chanin txs in bridge contracts --- contracts/contracts/coordination/TACoChildApplication.sol | 1 + contracts/xchain/PolygonChild.sol | 3 ++- contracts/xchain/PolygonRoot.sol | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/contracts/contracts/coordination/TACoChildApplication.sol b/contracts/contracts/coordination/TACoChildApplication.sol index e898a5ef..e4a5722e 100644 --- a/contracts/contracts/coordination/TACoChildApplication.sol +++ b/contracts/contracts/coordination/TACoChildApplication.sol @@ -40,6 +40,7 @@ contract TACoChildApplication is ITACoRootToChild, ITACoChildApplication, Initia "Address for root application must be specified" ); rootApplication = _rootApplication; + _disableInitializers(); } /** diff --git a/contracts/xchain/PolygonChild.sol b/contracts/xchain/PolygonChild.sol index 942ff278..92d3842d 100644 --- a/contracts/xchain/PolygonChild.sol +++ b/contracts/xchain/PolygonChild.sol @@ -16,7 +16,8 @@ contract PolygonChild is ITACoChildToRoot, FxBaseChildTunnel, Ownable { bytes memory data ) internal override validateSender(sender) { // solhint-disable-next-line avoid-low-level-calls - childApplication.call(data); + (bool success, ) = childApplication.call(data); + require(success, "Child tx failed"); } function setChildApplication(address _childApplication) public onlyOwner { diff --git a/contracts/xchain/PolygonRoot.sol b/contracts/xchain/PolygonRoot.sol index 0c675830..191ce5f0 100644 --- a/contracts/xchain/PolygonRoot.sol +++ b/contracts/xchain/PolygonRoot.sol @@ -28,7 +28,8 @@ contract PolygonRoot is FxBaseRootTunnel, ITACoRootToChild { function _processMessageFromChild(bytes memory data) internal override { // solhint-disable-next-line avoid-low-level-calls - rootApplication.call(data); + (bool success, ) = rootApplication.call(data); + require(success, "Root tx failed"); } function updateOperator( From b2efc21c425c35ec4faa6b609c89cb38e0a2919e Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Thu, 7 Sep 2023 14:10:44 -0400 Subject: [PATCH 18/18] Changes functions in polygon bridge to fallback functions, minor refinements --- .solhint.json | 3 +- contracts/contracts/TACoApplication.sol | 4 --- .../coordination/TACoChildApplication.sol | 6 +++- contracts/xchain/PolygonChild.sol | 11 ++---- contracts/xchain/PolygonRoot.sol | 36 +++---------------- 5 files changed, 14 insertions(+), 46 deletions(-) diff --git a/.solhint.json b/.solhint.json index 1f7b23cc..0bcafc18 100644 --- a/.solhint.json +++ b/.solhint.json @@ -41,6 +41,7 @@ "state-visibility": "error", "no-global-import": "off", "func-named-parameters": "off", - "prettier/prettier": "error" + "prettier/prettier": "error", + "no-complex-fallback": "warn" } } diff --git a/contracts/contracts/TACoApplication.sol b/contracts/contracts/TACoApplication.sol index 33113aae..c1805f1f 100644 --- a/contracts/contracts/TACoApplication.sol +++ b/contracts/contracts/TACoApplication.sol @@ -236,10 +236,6 @@ contract TACoApplication is IApplication, ITACoChildToRoot, OwnableUpgradeable { address(_childApplication) != address(childApplication), "New address must not be equal to the current one" ); - if (address(_childApplication) != address(0)) { - // trying to call contract to be sure that is correct address - _childApplication.updateOperator(address(0), address(0)); - } childApplication = _childApplication; } diff --git a/contracts/contracts/coordination/TACoChildApplication.sol b/contracts/contracts/coordination/TACoChildApplication.sol index e4a5722e..721c2846 100644 --- a/contracts/contracts/coordination/TACoChildApplication.sol +++ b/contracts/contracts/coordination/TACoChildApplication.sol @@ -7,6 +7,7 @@ import "@openzeppelin-upgradeable/contracts/proxy/utils/Initializable.sol"; import "./ITACoRootToChild.sol"; import "../../threshold/ITACoChildApplication.sol"; import "./ITACoChildToRoot.sol"; +import "./Coordinator.sol"; /** * @title TACoChildApplication @@ -49,7 +50,10 @@ contract TACoChildApplication is ITACoRootToChild, ITACoChildApplication, Initia function initialize(address _coordinator) external initializer { require(coordinator == address(0), "Coordinator already set"); require(_coordinator != address(0), "Coordinator must be specified"); - // require(_coordinator.numberOfRituals() >= 0, "Invalid coordinator"); + require( + address(Coordinator(_coordinator).application()) == address(this), + "Invalid coordinator" + ); coordinator = _coordinator; } diff --git a/contracts/xchain/PolygonChild.sol b/contracts/xchain/PolygonChild.sol index 92d3842d..78010023 100644 --- a/contracts/xchain/PolygonChild.sol +++ b/contracts/xchain/PolygonChild.sol @@ -3,9 +3,8 @@ pragma solidity ^0.8.0; import "@fx-portal/contracts/tunnel/FxBaseChildTunnel.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; -import "../contracts/coordination/ITACoChildToRoot.sol"; -contract PolygonChild is ITACoChildToRoot, FxBaseChildTunnel, Ownable { +contract PolygonChild is FxBaseChildTunnel, Ownable { address public childApplication; constructor(address _fxChild) FxBaseChildTunnel(_fxChild) {} @@ -24,12 +23,8 @@ contract PolygonChild is ITACoChildToRoot, FxBaseChildTunnel, Ownable { childApplication = _childApplication; } - function confirmOperatorAddress(address operator) external override { + fallback() external { require(msg.sender == childApplication, "Only child app can call this method"); - bytes memory message = abi.encodeWithSelector( - ITACoChildToRoot.confirmOperatorAddress.selector, - operator - ); - _sendMessageToRoot(message); + _sendMessageToRoot(msg.data); } } diff --git a/contracts/xchain/PolygonRoot.sol b/contracts/xchain/PolygonRoot.sol index 191ce5f0..9ac3ebea 100644 --- a/contracts/xchain/PolygonRoot.sol +++ b/contracts/xchain/PolygonRoot.sol @@ -2,9 +2,8 @@ pragma solidity ^0.8.0; import "@fx-portal/contracts/tunnel/FxBaseRootTunnel.sol"; -import "../contracts/coordination/ITACoRootToChild.sol"; -contract PolygonRoot is FxBaseRootTunnel, ITACoRootToChild { +contract PolygonRoot is FxBaseRootTunnel { address public immutable rootApplication; constructor( @@ -18,41 +17,14 @@ contract PolygonRoot is FxBaseRootTunnel, ITACoRootToChild { fxChildTunnel = _fxChildTunnel; } - /** - * @dev Checks caller is the root application - */ - modifier onlyRootApplication() { - require(msg.sender == rootApplication, "Caller must be the root app"); - _; - } - function _processMessageFromChild(bytes memory data) internal override { // solhint-disable-next-line avoid-low-level-calls (bool success, ) = rootApplication.call(data); require(success, "Root tx failed"); } - function updateOperator( - address stakingProvider, - address operator - ) external override onlyRootApplication { - bytes memory message = abi.encodeWithSelector( - ITACoRootToChild.updateOperator.selector, - stakingProvider, - operator - ); - _sendMessageToChild(message); - } - - function updateAuthorization( - address stakingProvider, - uint96 amount - ) external override onlyRootApplication { - bytes memory message = abi.encodeWithSelector( - ITACoRootToChild.updateAuthorization.selector, - stakingProvider, - amount - ); - _sendMessageToChild(message); + fallback() external { + require(msg.sender == rootApplication, "Caller must be the root app"); + _sendMessageToChild(msg.data); } }