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/ape-config.yaml b/ape-config.yaml index 9727fd79..d0ddbfd9 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,34 +39,38 @@ deployments: polygon: mainnet: - contract_type: DAI - address: '0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063' + address: "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063" + - contract_type: fx_child + address: "0x8397259c983751DAf40400790063935a11afa28a" mumbai: - contract_type: DAI - address: '0x001B3B4d0F3714Ca98ba10F6042DaEbF0B1B7b6F' + address: "0x001B3B4d0F3714Ca98ba10F6042DaEbF0B1B7b6F" - contract_type: StakeInfo - address: '0xC1379866Fb0c100DCBFAb7b470009C4827D47DD8' - - fx_child: '0xCf73231F28B7331BBe3124B907840A94851f9f11' - - verify: False + address: "0xC1379866Fb0c100DCBFAb7b470009C4827D47DD8" + - contract_type: fx_child + address: "0xCf73231F28B7331BBe3124B907840A94851f9f11" + - contract_type: TACoChildApplication + address: "0x68E95C2548363Bf5856667065Bc1B89CC498969F" 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: @@ -76,29 +84,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 + - contract_type: fx_root + address: "0x3d1d3E34f7fB6D26245E6640E1c50710eFFf15bA" + - contract_type: checkpoint_manager + 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" + - contract_type: checkpoint_manager + address: "0x86e4dc95c7fbdbf52e33d563bbdb00823894c287" test: mnemonic: test test test test test test test test test test test junk diff --git a/contracts/contracts/TACoApplication.sol b/contracts/contracts/TACoApplication.sol index bc4525b7..c1805f1f 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,12 @@ 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)) { - // trying to call contract to be sure that is correct address - _updatableStakeInfo.updateOperator(address(0), address(0)); - } - updatableStakeInfo = _updatableStakeInfo; + childApplication = _childApplication; } /** @@ -681,27 +671,34 @@ contract TACoApplication is IApplication, OwnableUpgradeable { info.operator = _operator; info.operatorStartTimestamp = uint64(block.timestamp); emit OperatorBonded(_stakingProvider, _operator, previousOperator, block.timestamp); - _releaseOperator(_stakingProvider); + + info.operatorConfirmed = false; + if (address(childApplication) != address(0)) { + childApplication.updateOperator(_stakingProvider, _operator); + } } /** * @notice Make a confirmation by operator */ - function confirmOperatorAddress() external { - address stakingProvider = _stakingProviderFromOperator[msg.sender]; - 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); + function confirmOperatorAddress(address _operator) external override { + require( + msg.sender == address(childApplication), + "Only child application allowed to confirm operator" + ); + address stakingProvider = _stakingProviderFromOperator[_operator]; + // 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; + } - if (address(updatableStakeInfo) != address(0)) { - updatableStakeInfo.updateOperator(stakingProvider, msg.sender); + StakingProviderInfo storage info = stakingProviderInfo[stakingProvider]; + if (!info.operatorConfirmed) { + updateRewardInternal(stakingProvider); + info.operatorConfirmed = true; + authorizedOverall += info.authorized; + emit OperatorConfirmed(stakingProvider, _operator); } } @@ -712,8 +709,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 +721,10 @@ contract TACoApplication is IApplication, OwnableUpgradeable { address _stakingProvider, StakingProviderInfo storage _info ) internal { - if (address(updatableStakeInfo) != address(0)) { - // TODO send both authorized and eligible amounts in case of slashing from StakeInfo + if (address(childApplication) != address(0)) { + // TODO send both authorized and eligible amounts in case of slashing from child app 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..1cbcf78d 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,18 @@ 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"); ParticipantKey memory newRecord = ParticipantKey(lastRitualId, _publicKey); - keysHistory[provider].push(newRecord); + 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"); + 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..9473fc3c --- /dev/null +++ b/contracts/contracts/coordination/ITACoRootToChild.sol @@ -0,0 +1,16 @@ +// 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; +} 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 250f3895..00000000 --- a/contracts/contracts/coordination/StakeInfo.sol +++ /dev/null @@ -1,91 +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) { - 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..721c2846 --- /dev/null +++ b/contracts/contracts/coordination/TACoChildApplication.sol @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +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"; +import "./Coordinator.sol"; + +/** + * @title TACoChildApplication + * @notice TACoChildApplication + */ +contract TACoChildApplication is ITACoRootToChild, ITACoChildApplication, Initializable { + 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; + + /** + * @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; + _disableInitializers(); + } + + /** + * @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( + address(Coordinator(_coordinator).application()) == address(this), + "Invalid coordinator" + ); + coordinator = _coordinator; + } + + function authorizedStake(address _stakingProvider) external view returns (uint96) { + return stakingProviderInfo[_stakingProvider].authorized; + } + + function updateOperator( + address stakingProvider, + address operator + ) external override onlyRootApplication { + _updateOperator(stakingProvider, operator); + } + + function updateAuthorization( + address stakingProvider, + uint96 amount + ) external override onlyRootApplication { + _updateAuthorization(stakingProvider, amount); + } + + function _updateOperator(address stakingProvider, address operator) internal { + StakingProviderInfo storage info = stakingProviderInfo[stakingProvider]; + address oldOperator = info.operator; + + if (stakingProvider != address(0) && operator != oldOperator) { + info.operator = operator; + // Update operator to provider mapping + stakingProviderFromOperator[oldOperator] = address(0); + stakingProviderFromOperator[operator] = stakingProvider; + info.operatorConfirmed = false; + // TODO placeholder to notify Coordinator + + emit OperatorUpdated(stakingProvider, operator); + } + } + + function _updateAuthorization(address stakingProvider, uint96 amount) internal { + StakingProviderInfo storage info = stakingProviderInfo[stakingProvider]; + uint96 fromAmount = info.authorized; + + if (stakingProvider != address(0) && amount != fromAmount) { + info.authorized = amount; + emit AuthorizationUpdated(stakingProvider, 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? + require(!info.operatorConfirmed, "Can't confirm same operator twice"); + info.operatorConfirmed = true; + emit OperatorConfirmed(stakingProvider, _operator); + 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/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/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 96941f50..ff95fc36 100644 --- a/contracts/test/TACoApplicationTestSet.sol +++ b/contracts/test/TACoApplicationTestSet.sol @@ -141,20 +141,31 @@ contract ThresholdStakingForTACoApplicationMock { } /** - * @notice Intermediary contract for testing operator + * @notice Contract for testing TACo application contract */ -contract Intermediary { - TACoApplication public immutable application; +contract ChildApplicationForTACoApplicationMock { + TACoApplication public immutable rootApplication; - constructor(TACoApplication _application) { - application = _application; + 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 bondOperator(address _operator) external { - application.bondOperator(address(this), _operator); + function updateAuthorization(address _stakingProvider, uint96 _amount) external { + authorizedStake[_stakingProvider] = _amount; } - function confirmOperatorAddress() external { - application.confirmOperatorAddress(); + function confirmOperatorAddress(address _operator) external { + rootApplication.confirmOperatorAddress(_operator); } } 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); + } +} 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 53415186..78010023 100644 --- a/contracts/xchain/PolygonChild.sol +++ b/contracts/xchain/PolygonChild.sol @@ -5,9 +5,7 @@ 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; + address public childApplication; constructor(address _fxChild) FxBaseChildTunnel(_fxChild) {} @@ -16,16 +14,17 @@ 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); + (bool success, ) = childApplication.call(data); + require(success, "Child tx failed"); } - function sendMessageToRoot(bytes memory message) public { - _sendMessageToRoot(message); + function setChildApplication(address _childApplication) public onlyOwner { + childApplication = _childApplication; } - function setStakeInfoAddress(address _stakeInfoAddress) public onlyOwner { - stakeInfoAddress = _stakeInfoAddress; + fallback() external { + require(msg.sender == childApplication, "Only child app can call this method"); + _sendMessageToRoot(msg.data); } } diff --git a/contracts/xchain/PolygonRoot.sol b/contracts/xchain/PolygonRoot.sol index 71def5e6..9ac3ebea 100644 --- a/contracts/xchain/PolygonRoot.sol +++ b/contracts/xchain/PolygonRoot.sol @@ -2,59 +2,29 @@ pragma solidity ^0.8.0; import "@fx-portal/contracts/tunnel/FxBaseRootTunnel.sol"; -import "../contracts/coordination/IUpdatableStakeInfo.sol"; -contract PolygonRoot is FxBaseRootTunnel, IUpdatableStakeInfo { - address public immutable source; - bytes public latestData; +contract PolygonRoot is FxBaseRootTunnel { + address public immutable rootApplication; constructor( address _checkpointManager, address _fxRoot, - address _source + address _rootApplication, + address _fxChildTunnel ) FxBaseRootTunnel(_checkpointManager, _fxRoot) { - require(_source != address(0), "Wrong input parameters"); - source = _source; - } - - /** - * @dev Checks caller is source of data - */ - modifier onlySource() { - require(msg.sender == source, "Caller must be the source"); - _; + require(_rootApplication != address(0), "Wrong input parameters"); + rootApplication = _rootApplication; + fxChildTunnel = _fxChildTunnel; } function _processMessageFromChild(bytes memory data) internal override { - latestData = data; - } - - function updateOperator( - address stakingProvider, - address operator - ) external override onlySource { - bytes memory message = abi.encodeWithSelector( - IUpdatableStakeInfo.updateOperator.selector, - stakingProvider, - operator - ); - _sendMessageToChild(message); - } - - function updateAmount(address stakingProvider, uint96 amount) external override onlySource { - bytes memory message = abi.encodeWithSelector( - IUpdatableStakeInfo.updateAmount.selector, - stakingProvider, - amount - ); - _sendMessageToChild(message); + // solhint-disable-next-line avoid-low-level-calls + (bool success, ) = rootApplication.call(data); + require(success, "Root tx failed"); } - function batchUpdate(bytes32[] calldata updateInfo) external override onlySource { - bytes memory message = abi.encodeWithSelector( - IUpdatableStakeInfo.batchUpdate.selector, - updateInfo - ); - _sendMessageToChild(message); + fallback() external { + require(msg.sender == rootApplication, "Caller must be the root app"); + _sendMessageToChild(msg.data); } } 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/check_xchain.py b/scripts/check_xchain.py index 0b1ea5a3..039e7c3b 100644 --- a/scripts/check_xchain.py +++ b/scripts/check_xchain.py @@ -1,20 +1,32 @@ 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("0xD2Cb2A8fbE29adBa1C287b2A0b49f5C4fDc1f5BE") 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"): + taco_child = project.TACoChildApplication.at( + "0x68E95C2548363Bf5856667065Bc1B89CC498969F" + ) + print( + taco_child.stakingProviderFromOperator("0xAe87D865F3A507185656aD0ef52a8E0B9f3d58f8") + ) + print( + 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 new file mode 100644 index 00000000..08a26f45 --- /dev/null +++ b/scripts/deploy_coordinator_with_fee_model.py @@ -0,0 +1,77 @@ +#!/usr/bin/python3 + +import os + +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 + + +@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"] == "TACoChildApplication")[ + "address" + ] + except StopIteration: + raise ValueError("TACoChildApplication 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) 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 diff --git a/scripts/deploy_xchain_test.py b/scripts/deploy_xchain_test.py index 06cc59b8..edb4c9d2 100644 --- a/scripts/deploy_xchain_test.py +++ b/scripts/deploy_xchain_test.py @@ -1,53 +1,135 @@ +import click from ape import accounts, config, networks, project +from ape.cli import NetworkBoundCommand, account_option +DEPENDENCY = project.dependencies["openzeppelin"]["4.9.1"] -def deploy_eth_contracts(deployer): + +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, child_address, config, eth_network): # Connect to the Ethereum network - with networks.ethereum.goerli.use_provider("infura"): - DEPLOYMENTS_CONFIG = config.get_config("deployments")["ethereum"]["goerli"][0] + with eth_network.use_provider("infura"): - # Deploy the FxStateRootTunnel contract - polygon_root = project.PolygonRoot.deploy( - DEPLOYMENTS_CONFIG.get("checkpoint_manager"), - DEPLOYMENTS_CONFIG.get("fx_root"), + token = project.TToken.deploy( + 100_000_000_000 * 10**18, sender=deployer, - publish=DEPLOYMENTS_CONFIG.get("verify"), ) - return polygon_root + 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, + ) -def deploy_polygon_contracts(deployer): - # Connect to the Polygon network - with networks.polygon.mumbai.use_provider("infura"): - DEPLOYMENTS_CONFIG = config.get_config("deployments")["polygon"]["mumbai"][0] + 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"], + proxy_contract.address, + child_address, + sender=deployer, + publish=False, + ) + proxy_contract.setChildApplication(root.address, sender=deployer) + + return root, proxy_contract, threshold_staking - # Deploy the FxStateChildTunnel contract + +def deploy_polygon_contracts(deployer, config, poly_network): + # Connect to the Polygon network + with poly_network.use_provider("infura"): polygon_child = project.PolygonChild.deploy( - DEPLOYMENTS_CONFIG.get("fx_child"), + config["fx_child"], + sender=deployer, + publish=False, + ) + + TACoChild = project.TestnetTACoChildApplication.deploy( + polygon_child.address, sender=deployer, - publish=DEPLOYMENTS_CONFIG.get("verify"), + publish=False, ) - stake_info = project.StakeInfo.deploy( - [deployer.address, polygon_child.address], + proxy_admin = DEPENDENCY.ProxyAdmin.deploy(sender=deployer) + proxy = DEPENDENCY.TransparentUpgradeableProxy.deploy( + TACoChild.address, + proxy_admin.address, + b"", sender=deployer, - publish=DEPLOYMENTS_CONFIG.get("verify"), ) + proxy_contract = project.TestnetTACoChildApplication.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, proxy_contract, coordinator - return polygon_child, stake_info +# 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)) -def main(account_id=None): - deployer = accounts.load("TGoerli") with accounts.use_sender(deployer): - root = deploy_eth_contracts(deployer) - child, stake_info = deploy_polygon_contracts(deployer) + poly_child, taco_child_app, coordinator = deploy_polygon_contracts( + deployer, convert_config(poly_config), poly_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 networks.polygon.mumbai.use_provider("infura"): - child.setFxRootTunnel(root.address) - child.setStakeInfoAddress(stake_info.address) + with poly_network.use_provider("infura"): + poly_child.setFxRootTunnel(root.address) - # Set the child contract address in the root contract - with networks.ethereum.goerli.use_provider("infura"): - root.setFxChildTunnel(child.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)) 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..c490b6f5 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, @@ -68,8 +66,8 @@ def test_bond_operator(accounts, threshold_staking, taco_application, stake_info 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(): - taco_application.confirmOperatorAddress(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(): @@ -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 == [ @@ -197,15 +195,15 @@ 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) + assert not taco_application.isOperatorConfirmed(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,41 @@ 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) - threshold_staking.setRoles(staking_provider, sender=creator) - - # Deploy intermediary contract - intermediary = creator.deploy(project.Intermediary, taco_application.address, sender=creator) + # 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) - # Bond contract as an operator + threshold_staking.setRoles(staking_provider, sender=creator) 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_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] diff --git a/tests/test_coordinator.py b/tests/test_coordinator.py index 956b4b55..36a42a08 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,11 @@ def treasury(accounts): @pytest.fixture() -def stake_info(project, deployer, nodes): - contract = project.StakeInfo.deploy([deployer], sender=deployer) +def application(project, deployer, nodes): + contract = project.ChildApplicationForCoordinatorMock.deploy(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 +84,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, @@ -161,7 +161,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 +181,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 +303,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 +324,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)