diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml
index 4a4bda64..8c2afdaa 100644
--- a/.github/workflows/main.yaml
+++ b/.github/workflows/main.yaml
@@ -47,8 +47,8 @@ jobs:
- name: Deploy NuCypher Token
run: ape run scripts/deploy_nucypher_token.py --network ethereum:local
- - name: Deploy Simple PRE
- run: ape run scripts/deploy_simple_pre.py --network ethereum:local
+ - name: Deploy TACo Application
+ run: ape run scripts/deploy_taco_application.py --network ethereum:local
- name: Deploy Staking Escrow
run: ape run scripts/deploy_staking_escrow.py --network ethereum:local
diff --git a/ape-config.yaml b/ape-config.yaml
index 5dd4d21c..2a9c00c2 100644
--- a/ape-config.yaml
+++ b/ape-config.yaml
@@ -18,6 +18,9 @@ dependencies:
- name: fx-portal
github: 0xPolygon/fx-portal
version: 1.0.5
+ - name: threshold
+ github: threshold-network/solidity-contracts
+ version: 1.2.1
solidity:
version: 0.8.20
@@ -26,6 +29,7 @@ solidity:
- "@openzeppelin/contracts=openzeppelin/v4.9.1"
- "@openzeppelin-upgradeable/contracts=openzeppelin-upgradeable/v4.9.1"
- "@fx-portal/contracts=fx-portal/v1.0.5"
+ - "@threshold/contracts=threshold/v1.2.1"
deployments:
polygon:
@@ -44,6 +48,14 @@ deployments:
- nu_token_supply: 1_000_000_000
pre_min_authorization: 40000000000000000000000
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
+ pre_min_authorization: 40000000000000000000000
+ pre_min_operator_seconds: 86400 # one day in seconds
+ reward_duration: 604800 # one week in seconds
+ deauthorization_duration: 5184000 # 60 days in seconds
verify: False
rinkeby:
- nu_token: '0x78D591D90a4a768B9D2790deA465D472b6Fe0f18'
diff --git a/contracts/contracts/Adjudicator.sol b/contracts/contracts/Adjudicator.sol
index 7acfe8cd..a2be80c9 100644
--- a/contracts/contracts/Adjudicator.sol
+++ b/contracts/contracts/Adjudicator.sol
@@ -4,19 +4,20 @@ pragma solidity ^0.8.0;
import "./lib/ReEncryptionValidator.sol";
import "./lib/SignatureVerifier.sol";
-import "./IStakingEscrow.sol";
-import "./proxy/Upgradeable.sol";
import "@openzeppelin/contracts/utils/math/Math.sol";
+import "@openzeppelin/contracts/utils/math/SafeCast.sol";
+import "./TACoApplication.sol";
/**
* @title Adjudicator
-* @notice Supervises stakers' behavior and punishes when something's wrong.
-* @dev |v2.1.2|
+* @notice Supervises operators' behavior and punishes when something's wrong.
+* @dev |v3.1.1|
*/
-contract Adjudicator is Upgradeable {
+contract Adjudicator {
using UmbralDeserializer for bytes;
+ using SafeCast for uint256;
event CFragEvaluated(
bytes32 indexed evaluationHash,
@@ -25,62 +26,57 @@ contract Adjudicator is Upgradeable {
);
event IncorrectCFragVerdict(
bytes32 indexed evaluationHash,
- address indexed worker,
- address indexed staker
+ address indexed operator,
+ address indexed stakingProvider
);
// used only for upgrading
bytes32 constant RESERVED_CAPSULE_AND_CFRAG_BYTES = bytes32(0);
address constant RESERVED_ADDRESS = address(0);
- IStakingEscrow public immutable escrow;
SignatureVerifier.HashAlgorithm public immutable hashAlgorithm;
uint256 public immutable basePenalty;
uint256 public immutable penaltyHistoryCoefficient;
uint256 public immutable percentagePenaltyCoefficient;
- uint256 public immutable rewardCoefficient;
+ TACoApplication public immutable application;
mapping (address => uint256) public penaltyHistory;
mapping (bytes32 => bool) public evaluatedCFrags;
/**
- * @param _escrow Escrow contract
* @param _hashAlgorithm Hashing algorithm
* @param _basePenalty Base for the penalty calculation
* @param _penaltyHistoryCoefficient Coefficient for calculating the penalty depending on the history
* @param _percentagePenaltyCoefficient Coefficient for calculating the percentage penalty
- * @param _rewardCoefficient Coefficient for calculating the reward
*/
constructor(
- IStakingEscrow _escrow,
+ TACoApplication _application,
SignatureVerifier.HashAlgorithm _hashAlgorithm,
uint256 _basePenalty,
uint256 _penaltyHistoryCoefficient,
- uint256 _percentagePenaltyCoefficient,
- uint256 _rewardCoefficient
+ uint256 _percentagePenaltyCoefficient
) {
- // Sanity checks.
- require(_escrow.secondsPerPeriod() > 0 && // This contract has an escrow, and it's not the null address.
- // The reward and penalty coefficients are set.
- _percentagePenaltyCoefficient != 0 &&
- _rewardCoefficient != 0);
- escrow = _escrow;
+ require(
+ _percentagePenaltyCoefficient != 0 &&
+ address(_application.token()) != address(0),
+ "Wrong input parameters"
+ );
hashAlgorithm = _hashAlgorithm;
basePenalty = _basePenalty;
percentagePenaltyCoefficient = _percentagePenaltyCoefficient;
penaltyHistoryCoefficient = _penaltyHistoryCoefficient;
- rewardCoefficient = _rewardCoefficient;
+ application = _application;
}
/**
- * @notice Submit proof that a worker created wrong CFrag
+ * @notice Submit proof that a operator created wrong CFrag
* @param _capsuleBytes Serialized capsule
* @param _cFragBytes Serialized CFrag
- * @param _cFragSignature Signature of CFrag by worker
+ * @param _cFragSignature Signature of CFrag by operator
* @param _taskSignature Signature of task specification by Bob
* @param _requesterPublicKey Bob's signing public key, also known as "stamp"
- * @param _workerPublicKey Worker's signing public key, also known as "stamp"
- * @param _workerIdentityEvidence Signature of worker's public key by worker's eth-key
+ * @param _operatorPublicKey Operator's signing public key, also known as "stamp"
+ * @param _operatorIdentityEvidence Signature of operator's public key by operator's eth-key
* @param _preComputedData Additional pre-computed data for CFrag correctness verification
*/
function evaluateCFrag(
@@ -89,8 +85,8 @@ contract Adjudicator is Upgradeable {
bytes memory _cFragSignature,
bytes memory _taskSignature,
bytes memory _requesterPublicKey,
- bytes memory _workerPublicKey,
- bytes memory _workerIdentityEvidence,
+ bytes memory _operatorPublicKey,
+ bytes memory _operatorIdentityEvidence,
bytes memory _preComputedData
)
public
@@ -106,28 +102,28 @@ contract Adjudicator is Upgradeable {
emit CFragEvaluated(evaluationHash, msg.sender, cFragIsCorrect);
// 3. Verify associated public keys and signatures
- require(ReEncryptionValidator.checkSerializedCoordinates(_workerPublicKey),
+ require(ReEncryptionValidator.checkSerializedCoordinates(_operatorPublicKey),
"Staker's public key is invalid");
require(ReEncryptionValidator.checkSerializedCoordinates(_requesterPublicKey),
"Requester's public key is invalid");
UmbralDeserializer.PreComputedData memory precomp = _preComputedData.toPreComputedData();
- // Verify worker's signature of CFrag
+ // Verify operator's signature of CFrag
require(SignatureVerifier.verify(
_cFragBytes,
abi.encodePacked(_cFragSignature, precomp.lostBytes[1]),
- _workerPublicKey,
+ _operatorPublicKey,
hashAlgorithm),
"CFrag signature is invalid"
);
- // Verify worker's signature of taskSignature and that it corresponds to cfrag.proof.metadata
+ // Verify operator's signature of taskSignature and that it corresponds to cfrag.proof.metadata
UmbralDeserializer.CapsuleFrag memory cFrag = _cFragBytes.toCapsuleFrag();
require(SignatureVerifier.verify(
_taskSignature,
abi.encodePacked(cFrag.proof.metadata, precomp.lostBytes[2]),
- _workerPublicKey,
+ _operatorPublicKey,
hashAlgorithm),
"Task signature is invalid"
);
@@ -136,14 +132,14 @@ contract Adjudicator is Upgradeable {
// A task specification is: capsule + ursula pubkey + alice address + blockhash
bytes32 stampXCoord;
assembly {
- stampXCoord := mload(add(_workerPublicKey, 32))
+ stampXCoord := mload(add(_operatorPublicKey, 32))
}
bytes memory stamp = abi.encodePacked(precomp.lostBytes[4], stampXCoord);
require(SignatureVerifier.verify(
abi.encodePacked(_capsuleBytes,
stamp,
- _workerIdentityEvidence,
+ _operatorIdentityEvidence,
precomp.alicesKeyAsAddress,
bytes32(0)),
abi.encodePacked(_taskSignature, precomp.lostBytes[3]),
@@ -152,58 +148,38 @@ contract Adjudicator is Upgradeable {
"Specification signature is invalid"
);
- // 4. Extract worker address from stamp signature.
- address worker = SignatureVerifier.recover(
+ // 4. Extract operator address from stamp signature.
+ address operator = SignatureVerifier.recover(
SignatureVerifier.hashEIP191(stamp, bytes1(0x45)), // Currently, we use version E (0x45) of EIP191 signatures
- _workerIdentityEvidence);
- address staker = escrow.stakerFromWorker(worker);
- require(staker != address(0), "Worker must be related to a staker");
+ _operatorIdentityEvidence);
+ address stakingProvider = application.stakingProviderFromOperator(operator);
+ require(stakingProvider != address(0), "Operator must be associated with a provider");
- // 5. Check that staker can be slashed
- uint256 stakerValue = escrow.getAllTokens(staker);
- require(stakerValue > 0, "Staker has no tokens");
+ // 5. Check that staking provider can be slashed
+ uint96 stakingProviderValue = application.authorizedStake(stakingProvider);
+ require(stakingProviderValue > 0, "Provider has no tokens");
- // 6. If CFrag was incorrect, slash staker
+ // 6. If CFrag was incorrect, slash staking provider
if (!cFragIsCorrect) {
- (uint256 penalty, uint256 reward) = calculatePenaltyAndReward(staker, stakerValue);
- escrow.slashStaker(staker, penalty, msg.sender, reward);
- emit IncorrectCFragVerdict(evaluationHash, worker, staker);
+ uint96 penalty = calculatePenalty(stakingProvider, stakingProviderValue);
+ application.slash(stakingProvider, penalty, msg.sender);
+ emit IncorrectCFragVerdict(evaluationHash, operator, stakingProvider);
}
}
/**
- * @notice Calculate penalty to the staker and reward to the investigator
- * @param _staker Staker's address
- * @param _stakerValue Amount of tokens that belong to the staker
+ * @notice Calculate penalty to the staking provider
+ * @param _stakingProvider Staking provider address
+ * @param _stakingProviderValue Amount of tokens that belong to the staking provider
*/
- function calculatePenaltyAndReward(address _staker, uint256 _stakerValue)
- internal returns (uint256 penalty, uint256 reward)
+ function calculatePenalty(address _stakingProvider, uint96 _stakingProviderValue)
+ internal returns (uint96)
{
- penalty = basePenalty + penaltyHistoryCoefficient * penaltyHistory[_staker];
- penalty = Math.min(penalty, _stakerValue / percentagePenaltyCoefficient);
- reward = penalty / rewardCoefficient;
+ uint256 penalty = basePenalty + penaltyHistoryCoefficient * penaltyHistory[_stakingProvider];
+ penalty = Math.min(penalty, _stakingProviderValue / percentagePenaltyCoefficient);
// TODO add maximum condition or other overflow protection or other penalty condition (#305?)
- penaltyHistory[_staker] = penaltyHistory[_staker] + 1;
- }
-
- /// @dev the `onlyWhileUpgrading` modifier works through a call to the parent `verifyState`
- function verifyState(address _testTarget) public override virtual {
- super.verifyState(_testTarget);
- bytes32 evaluationCFragHash = SignatureVerifier.hash(
- abi.encodePacked(RESERVED_CAPSULE_AND_CFRAG_BYTES), SignatureVerifier.HashAlgorithm.SHA256);
- require(delegateGet(_testTarget, this.evaluatedCFrags.selector, evaluationCFragHash) ==
- (evaluatedCFrags[evaluationCFragHash] ? 1 : 0));
- require(delegateGet(_testTarget, this.penaltyHistory.selector, bytes32(bytes20(RESERVED_ADDRESS))) ==
- penaltyHistory[RESERVED_ADDRESS]);
+ penaltyHistory[_stakingProvider] = penaltyHistory[_stakingProvider] + 1;
+ return penalty.toUint96();
}
- /// @dev the `onlyWhileUpgrading` modifier works through a call to the parent `finishUpgrade`
- function finishUpgrade(address _target) public override virtual {
- super.finishUpgrade(_target);
- // preparation for the verifyState method
- bytes32 evaluationCFragHash = SignatureVerifier.hash(
- abi.encodePacked(RESERVED_CAPSULE_AND_CFRAG_BYTES), SignatureVerifier.HashAlgorithm.SHA256);
- evaluatedCFrags[evaluationCFragHash] = true;
- penaltyHistory[RESERVED_ADDRESS] = 123;
- }
}
diff --git a/contracts/contracts/SimplePREApplication.sol b/contracts/contracts/SimplePREApplication.sol
deleted file mode 100644
index f43d901c..00000000
--- a/contracts/contracts/SimplePREApplication.sol
+++ /dev/null
@@ -1,226 +0,0 @@
-// SPDX-License-Identifier: AGPL-3.0-or-later
-
-pragma solidity ^0.8.0;
-
-
-import "../threshold/IStaking.sol";
-
-
-/**
-* @title PRE Application
-* @notice Contract handles PRE configuration
-*/
-contract SimplePREApplication {
-
- /**
- * @notice Signals that an operator was bonded to the staking provider
- * @param stakingProvider Staking provider address
- * @param operator Operator address
- * @param startTimestamp Timestamp bonding occurred
- */
- event OperatorBonded(address indexed stakingProvider, address indexed operator, 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;
- uint256 operatorStartTimestamp;
- }
-
- uint256 public immutable minAuthorization;
- uint256 public immutable minOperatorSeconds;
-
- IStaking public immutable tStaking;
-
- mapping (address => StakingProviderInfo) public stakingProviderInfo;
- address[] public stakingProviders;
- mapping(address => address) internal _stakingProviderFromOperator;
-
-
- /**
- * @notice Constructor sets address of token contract and parameters for staking
- * @param _tStaking T token staking contract
- * @param _minAuthorization Amount of minimum allowable authorization
- * @param _minOperatorSeconds Min amount of seconds while an operator can't be changed
- */
- constructor(
- IStaking _tStaking,
- uint256 _minAuthorization,
- uint256 _minOperatorSeconds
- ) {
- require(
- _tStaking.authorizedStake(address(this), address(this)) == 0,
- "Wrong input parameters"
- );
- minAuthorization = _minAuthorization;
- tStaking = _tStaking;
- minOperatorSeconds = _minOperatorSeconds;
- }
-
- /**
- * @dev Checks caller is a staking provider or stake owner
- */
- modifier onlyOwnerOrStakingProvider(address _stakingProvider)
- {
- require(isAuthorized(_stakingProvider), "Not owner or provider");
- if (_stakingProvider != msg.sender) {
- (address owner,,) = tStaking.rolesOf(_stakingProvider);
- require(owner == msg.sender, "Not owner or provider");
- }
- _;
- }
-
-
- //-------------------------Main-------------------------
- /**
- * @notice Returns staking provider for specified operator
- */
- function stakingProviderFromOperator(address _operator) public view returns (address) {
- return _stakingProviderFromOperator[_operator];
- }
-
- /**
- * @notice Returns operator for specified staking provider
- */
- function getOperatorFromStakingProvider(address _stakingProvider) public view returns (address) {
- return stakingProviderInfo[_stakingProvider].operator;
- }
-
- /**
- * @notice Get all tokens delegated to the staking provider
- */
- function authorizedStake(address _stakingProvider) public view returns (uint96) {
- (uint96 tStake, uint96 keepInTStake, uint96 nuInTStake) = tStaking.stakes(_stakingProvider);
- return tStake + keepInTStake + nuInTStake;
- }
-
- /**
- * @notice Get the value of authorized tokens for active providers as well as providers and their authorized tokens
- * @param _startIndex Start index for looking in providers array
- * @param _maxStakingProviders Max providers for looking, if set 0 then all will be used
- * @return allAuthorizedTokens Sum of authorized tokens for active providers
- * @return activeStakingProviders Array of providers and their authorized tokens.
- * Providers addresses stored as uint256
- * @dev Note that activeStakingProviders[0] is an array of uint256, but you want addresses.
- * Careful when used directly!
- */
- function getActiveStakingProviders(uint256 _startIndex, uint256 _maxStakingProviders)
- external view returns (uint256 allAuthorizedTokens, uint256[2][] memory activeStakingProviders)
- {
- uint256 endIndex = stakingProviders.length;
- require(_startIndex < endIndex, "Wrong start index");
- if (_maxStakingProviders != 0 && _startIndex + _maxStakingProviders < endIndex) {
- endIndex = _startIndex + _maxStakingProviders;
- }
- activeStakingProviders = new uint256[2][](endIndex - _startIndex);
- allAuthorizedTokens = 0;
-
- uint256 resultIndex = 0;
- for (uint256 i = _startIndex; i < endIndex; i++) {
- address stakingProvider = stakingProviders[i];
- StakingProviderInfo storage info = stakingProviderInfo[stakingProvider];
- uint256 eligibleAmount = authorizedStake(stakingProvider);
- if (eligibleAmount < minAuthorization || !info.operatorConfirmed) {
- continue;
- }
- activeStakingProviders[resultIndex][0] = uint256(uint160(stakingProvider));
- activeStakingProviders[resultIndex++][1] = eligibleAmount;
- allAuthorizedTokens += eligibleAmount;
- }
- assembly {
- mstore(activeStakingProviders, resultIndex)
- }
- }
-
- /**
- * @notice Returns beneficiary related to the staking provider
- */
- function getBeneficiary(address _stakingProvider) public view returns (address payable beneficiary) {
- (, beneficiary,) = tStaking.rolesOf(_stakingProvider);
- }
-
- /**
- * @notice Returns true if staking provider has authorized stake to this application
- */
- function isAuthorized(address _stakingProvider) public view returns (bool) {
- return authorizedStake(_stakingProvider) >= minAuthorization;
- }
-
- /**
- * @notice Returns true if operator has confirmed address
- */
- // TODO maybe _stakingProvider instead of _operator as input?
- function isOperatorConfirmed(address _operator) public view returns (bool) {
- address stakingProvider = _stakingProviderFromOperator[_operator];
- StakingProviderInfo storage info = stakingProviderInfo[stakingProvider];
- return info.operatorConfirmed;
- }
-
- /**
- * @notice Return the length of the array of staking providers
- */
- function getStakingProvidersLength() external view returns (uint256) {
- return stakingProviders.length;
- }
-
- /**
- * @notice Bond operator
- * @param _stakingProvider Staking provider address
- * @param _operator Operator address. Must be a real address, not a contract
- */
- function bondOperator(address _stakingProvider, address _operator)
- external onlyOwnerOrStakingProvider(_stakingProvider)
- {
- StakingProviderInfo storage info = stakingProviderInfo[_stakingProvider];
- require(_operator != info.operator, "Specified operator is already bonded with this provider");
- // If this staker had an operator ...
- if (info.operator != address(0)) {
- require(
- block.timestamp >= info.operatorStartTimestamp + minOperatorSeconds,
- "Not enough time passed to change operator"
- );
- // Remove the old relation "operator->stakingProvider"
- _stakingProviderFromOperator[info.operator] = address(0);
- }
-
- if (_operator != address(0)) {
- require(_stakingProviderFromOperator[_operator] == address(0), "Specified operator is already in use");
- require(
- _operator == _stakingProvider || getBeneficiary(_operator) == address(0),
- "Specified operator is a provider"
- );
- // Set new operator->stakingProvider relation
- _stakingProviderFromOperator[_operator] = _stakingProvider;
- }
-
- if (info.operatorStartTimestamp == 0) {
- stakingProviders.push(_stakingProvider);
- }
-
- // Bond new operator (or unbond if _operator == address(0))
- info.operator = _operator;
- info.operatorStartTimestamp = block.timestamp;
- info.operatorConfirmed = false;
- emit OperatorBonded(_stakingProvider, _operator, block.timestamp);
- }
-
- /**
- * @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");
- require(msg.sender == tx.origin, "Only operator with real address can make a confirmation");
- info.operatorConfirmed = true;
- emit OperatorConfirmed(stakingProvider, msg.sender);
- }
-
-}
diff --git a/contracts/contracts/StakingEscrow.sol b/contracts/contracts/StakingEscrow.sol
index 87d15ccd..aa9ec8c5 100644
--- a/contracts/contracts/StakingEscrow.sol
+++ b/contracts/contracts/StakingEscrow.sol
@@ -9,7 +9,7 @@ import "./lib/Bits.sol";
import "./proxy/Upgradeable.sol";
import "@openzeppelin/contracts/utils/math/Math.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
-import "../threshold/IStaking.sol";
+import "@threshold/contracts/staking/IStaking.sol";
/**
diff --git a/contracts/contracts/TACoApplication.sol b/contracts/contracts/TACoApplication.sol
new file mode 100644
index 00000000..952fde43
--- /dev/null
+++ b/contracts/contracts/TACoApplication.sol
@@ -0,0 +1,708 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+pragma solidity ^0.8.0;
+
+
+import "@openzeppelin/contracts/utils/math/Math.sol";
+import "@openzeppelin/contracts/utils/math/SafeCast.sol";
+import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
+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";
+
+
+/**
+* @title TACo Application
+* @notice Contract distributes rewards for participating in app and slashes for violating rules
+*/
+contract TACoApplication is IApplication, OwnableUpgradeable {
+
+ using SafeERC20 for IERC20;
+ using SafeCast for uint256;
+
+ /**
+ * @notice Signals that distributor role was set
+ * @param distributor Address of reward distributor
+ */
+ event RewardDistributorSet(address indexed distributor);
+
+ /**
+ * @notice Signals that reward was added
+ * @param reward Amount of reward
+ */
+ event RewardAdded(uint256 reward);
+
+ /**
+ * @notice Signals that the beneficiary related to the staking provider received reward
+ * @param stakingProvider Staking provider address
+ * @param beneficiary Beneficiary address
+ * @param reward Amount of reward
+ */
+ event RewardPaid(address indexed stakingProvider, address indexed beneficiary, uint256 reward);
+
+ /**
+ * @notice Signals that authorization was increased for the staking provider
+ * @param stakingProvider Staking provider address
+ * @param fromAmount Previous amount of increased authorization
+ * @param toAmount New amount of increased authorization
+ */
+ event AuthorizationIncreased(address indexed stakingProvider, uint96 fromAmount, uint96 toAmount);
+
+ /**
+ * @notice Signals that authorization was decreased involuntary
+ * @param stakingProvider Staking provider address
+ * @param fromAmount Previous amount of authorized tokens
+ * @param toAmount Amount of authorized tokens to decrease
+ */
+ event AuthorizationInvoluntaryDecreased(address indexed stakingProvider, uint96 fromAmount, uint96 toAmount);
+
+ /**
+ * @notice Signals that authorization decrease was requested for the staking provider
+ * @param stakingProvider Staking provider address
+ * @param fromAmount Current amount of authorized tokens
+ * @param toAmount Amount of authorization to decrease
+ */
+ event AuthorizationDecreaseRequested(address indexed stakingProvider, uint96 fromAmount, uint96 toAmount);
+
+ /**
+ * @notice Signals that authorization decrease was approved for the staking provider
+ * @param stakingProvider Staking provider address
+ * @param fromAmount Previous amount of authorized tokens
+ * @param toAmount Decreased amount of authorized tokens
+ */
+ event AuthorizationDecreaseApproved(address indexed stakingProvider, uint96 fromAmount, uint96 toAmount);
+
+ /**
+ * @notice Signals that authorization was resynchronized
+ * @param stakingProvider Staking provider address
+ * @param fromAmount Previous amount of authorized tokens
+ * @param toAmount Resynchronized amount of authorized tokens
+ */
+ event AuthorizationReSynchronized(address indexed stakingProvider, uint96 fromAmount, uint96 toAmount);
+
+ /**
+ * @notice Signals that the staking provider was slashed
+ * @param stakingProvider Staking provider address
+ * @param penalty Slashing penalty
+ * @param investigator Investigator address
+ * @param reward Value of reward provided to investigator (in units of T)
+ */
+ event Slashed(address indexed stakingProvider, uint256 penalty, address indexed investigator, uint256 reward);
+
+ /**
+ * @notice Signals that an operator was bonded to the staking provider
+ * @param stakingProvider Staking provider address
+ * @param operator Operator address
+ * @param previousOperator Previous operator address
+ * @param startTimestamp Timestamp bonding occurred
+ */
+ event OperatorBonded(
+ address indexed stakingProvider,
+ address indexed operator,
+ address indexed previousOperator,
+ 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;
+ uint64 operatorStartTimestamp;
+
+ uint96 authorized;
+ uint96 deauthorizing; // TODO real usage only in getActiveStakingProviders, maybe remove?
+ uint64 endDeauthorization;
+
+ uint96 tReward;
+ uint96 rewardPerTokenPaid;
+ }
+
+ uint96 public immutable minimumAuthorization;
+ uint256 public immutable minOperatorSeconds;
+ uint256 public immutable rewardDuration;
+ uint256 public immutable deauthorizationDuration;
+
+ IStaking public immutable tStaking;
+ IERC20 public immutable token;
+
+ IUpdatableStakeInfo public updatableStakeInfo;
+ address public adjudicator;
+
+ mapping (address => StakingProviderInfo) public stakingProviderInfo;
+ address[] public stakingProviders;
+ mapping(address => address) internal _stakingProviderFromOperator;
+
+ address public rewardDistributor;
+ uint256 public periodFinish;
+ uint256 public rewardRateDecimals;
+ uint256 public lastUpdateTime;
+ uint96 public rewardPerTokenStored;
+ uint96 public authorizedOverall;
+
+ /**
+ * @notice Constructor sets address of token contract and parameters for staking
+ * @param _token T token contract
+ * @param _tStaking T token staking contract
+ * @param _minimumAuthorization Amount of minimum allowable authorization
+ * @param _minOperatorSeconds Min amount of seconds while an operator can't be changed
+ * @param _rewardDuration Duration of one reward cycle in seconds
+ * @param _deauthorizationDuration Duration of decreasing authorization in seconds
+ */
+ constructor(
+ IERC20 _token,
+ IStaking _tStaking,
+ uint96 _minimumAuthorization,
+ uint256 _minOperatorSeconds,
+ uint256 _rewardDuration,
+ uint256 _deauthorizationDuration
+ ) {
+ require(
+ _rewardDuration != 0 &&
+ _tStaking.authorizedStake(address(this), address(this)) == 0 &&
+ _token.totalSupply() > 0,
+ "Wrong input parameters"
+ );
+ rewardDuration = _rewardDuration;
+ deauthorizationDuration = _deauthorizationDuration;
+ minimumAuthorization = _minimumAuthorization;
+ token = _token;
+ tStaking = _tStaking;
+ minOperatorSeconds = _minOperatorSeconds;
+ _disableInitializers();
+ }
+
+ /**
+ * @dev Update reward for the specified staking provider
+ */
+ modifier updateReward(address _stakingProvider) {
+ updateRewardInternal(_stakingProvider);
+ _;
+ }
+
+ /**
+ * @dev Checks caller is T staking contract
+ */
+ modifier onlyStakingContract()
+ {
+ require(msg.sender == address(tStaking), "Caller must be the T staking contract");
+ _;
+ }
+
+ /**
+ * @dev Checks caller is a staking provider or stake owner
+ */
+ modifier onlyOwnerOrStakingProvider(address _stakingProvider)
+ {
+ require(isAuthorized(_stakingProvider), "Not owner or provider");
+ if (_stakingProvider != msg.sender) {
+ (address owner,,) = tStaking.rolesOf(_stakingProvider);
+ require(owner == msg.sender, "Not owner or provider");
+ }
+ _;
+ }
+
+ /**
+ * @notice Initialize function for using with OpenZeppelin proxy
+ */
+ function initialize() external initializer {
+ __Ownable_init();
+ }
+
+ /**
+ * @notice Set contract for multi-chain interactions
+ */
+ function setUpdatableStakeInfo(IUpdatableStakeInfo _updatableStakeInfo) external onlyOwner {
+ require(address(_updatableStakeInfo) != address(updatableStakeInfo), "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;
+ }
+
+ /**
+ * @notice Set adjudicator contract. If zero then slashing is disabled
+ */
+ function setAdjudicator(address _adjudicator) external onlyOwner {
+ require(address(_adjudicator) != address(adjudicator), "New address must not be equal to the current one");
+ adjudicator = _adjudicator;
+ }
+
+ //------------------------Reward------------------------------
+
+ /**
+ * @notice Set reward distributor address
+ */
+ function setRewardDistributor(address _rewardDistributor)
+ external
+ onlyOwner
+ {
+ rewardDistributor = _rewardDistributor;
+ emit RewardDistributorSet(_rewardDistributor);
+ }
+
+ /**
+ * @notice Update reward for the specified staking provider
+ * @param _stakingProvider Staking provider address
+ */
+ function updateRewardInternal(address _stakingProvider) internal {
+ rewardPerTokenStored = rewardPerToken();
+ lastUpdateTime = lastTimeRewardApplicable();
+ if (_stakingProvider != address(0)) {
+ StakingProviderInfo storage info = stakingProviderInfo[_stakingProvider];
+ info.tReward = availableRewards(_stakingProvider);
+ info.rewardPerTokenPaid = rewardPerTokenStored;
+ }
+ }
+
+ /**
+ * @notice Returns last time when reward was applicable
+ */
+ function lastTimeRewardApplicable() public view returns (uint256) {
+ return Math.min(block.timestamp, periodFinish);
+ }
+
+ /**
+ * @notice Returns current value of reward per token
+ */
+ function rewardPerToken() public view returns (uint96) {
+ if (authorizedOverall == 0) {
+ return rewardPerTokenStored;
+ }
+ uint256 result = rewardPerTokenStored +
+ (lastTimeRewardApplicable() - lastUpdateTime)
+ * rewardRateDecimals
+ / authorizedOverall;
+ return result.toUint96();
+ }
+
+ /**
+ * @notice Returns amount of reward in T units for the staking provider
+ * @param _stakingProvider Staking provider address
+ */
+ function availableRewards(address _stakingProvider) public view returns (uint96) {
+ StakingProviderInfo storage info = stakingProviderInfo[_stakingProvider];
+ if (!info.operatorConfirmed) {
+ return info.tReward;
+ }
+ uint256 result = uint256(info.authorized) *
+ (rewardPerToken() - info.rewardPerTokenPaid)
+ / 1e18
+ + info.tReward;
+ return result.toUint96();
+ }
+
+ /**
+ * @notice Transfer reward for the next period. Can be called only by distributor
+ * @param _reward Amount of reward
+ */
+ function pushReward(uint96 _reward) external updateReward(address(0)) {
+ require(msg.sender == rewardDistributor, "Only distributor can push rewards");
+ require(_reward > 0, "Reward must be specified");
+ if (block.timestamp >= periodFinish) {
+ rewardRateDecimals = uint256(_reward) * 1e18 / rewardDuration;
+ } else {
+ uint256 remaining = periodFinish - block.timestamp;
+ uint256 leftover = remaining * rewardRateDecimals;
+ rewardRateDecimals = (uint256(_reward) * 1e18 + leftover) / rewardDuration;
+ }
+ lastUpdateTime = block.timestamp;
+ periodFinish = block.timestamp + rewardDuration;
+ emit RewardAdded(_reward);
+ token.safeTransferFrom(msg.sender, address(this), _reward);
+ }
+
+ /**
+ * @notice Withdraw available amount of T reward to beneficiary. Can be called only by beneficiary
+ * @param _stakingProvider Staking provider address
+ */
+ function withdrawRewards(address _stakingProvider) external updateReward(_stakingProvider) {
+ address beneficiary = getBeneficiary(_stakingProvider);
+ require(msg.sender == beneficiary, "Caller must be beneficiary");
+
+ StakingProviderInfo storage info = stakingProviderInfo[_stakingProvider];
+ uint96 value = info.tReward;
+ require(value > 0, "No reward to withdraw");
+ info.tReward = 0;
+ emit RewardPaid(_stakingProvider, beneficiary, value);
+ token.safeTransfer(beneficiary, value);
+ }
+
+ //------------------------Authorization------------------------------
+ /**
+ * @notice Recalculate `authorizedOverall` if desync happened
+ */
+ function resynchronizeAuthorizedOverall(StakingProviderInfo storage _info, uint96 _properAmount) internal {
+ if (_info.authorized != _properAmount) {
+ authorizedOverall -= _info.authorized - _properAmount;
+ }
+ }
+
+ /**
+ * @notice Recalculate reward and save increased authorization. Can be called only by staking contract
+ * @param _stakingProvider Address of staking provider
+ * @param _fromAmount Amount of previously authorized tokens to TACo application by staking provider
+ * @param _toAmount Amount of authorized tokens to TACo application by staking provider
+ */
+ function authorizationIncreased(
+ address _stakingProvider,
+ uint96 _fromAmount,
+ uint96 _toAmount
+ )
+ external override onlyStakingContract updateReward(_stakingProvider)
+ {
+ require(_stakingProvider != address(0) && _toAmount > 0, "Input parameters must be specified");
+ require(_toAmount >= minimumAuthorization, "Authorization must be greater than minimum");
+
+ StakingProviderInfo storage info = stakingProviderInfo[_stakingProvider];
+ require(
+ _stakingProviderFromOperator[_stakingProvider] == address(0) ||
+ _stakingProviderFromOperator[_stakingProvider] == _stakingProvider,
+ "A provider can't be an operator for another provider"
+ );
+
+ if (info.operatorConfirmed) {
+ resynchronizeAuthorizedOverall(info, _fromAmount);
+ authorizedOverall += _toAmount - _fromAmount;
+ }
+
+ info.authorized = _toAmount;
+ emit AuthorizationIncreased(_stakingProvider, _fromAmount, _toAmount);
+ _updateAuthorization(_stakingProvider, info);
+ }
+
+ /**
+ * @notice Immediately decrease authorization. Can be called only by staking contract
+ * @param _stakingProvider Address of staking provider
+ * @param _fromAmount Previous amount of authorized tokens
+ * @param _toAmount Amount of authorized tokens to decrease
+ */
+ function involuntaryAuthorizationDecrease(
+ address _stakingProvider,
+ uint96 _fromAmount,
+ uint96 _toAmount
+ )
+ external override onlyStakingContract updateReward(_stakingProvider)
+ {
+ StakingProviderInfo storage info = stakingProviderInfo[_stakingProvider];
+ if (info.operatorConfirmed) {
+ resynchronizeAuthorizedOverall(info, _fromAmount);
+ authorizedOverall -= _fromAmount - _toAmount;
+ }
+
+ info.authorized = _toAmount;
+ if (info.authorized < info.deauthorizing) {
+ info.deauthorizing = info.authorized;
+ }
+ emit AuthorizationInvoluntaryDecreased(_stakingProvider, _fromAmount, _toAmount);
+
+ if (info.authorized == 0) {
+ _stakingProviderFromOperator[info.operator] = address(0);
+ info.operator = address(0);
+ _releaseOperator(_stakingProvider);
+ }
+ _updateAuthorization(_stakingProvider, info);
+ }
+
+ /**
+ * @notice Register request of decreasing authorization. Can be called only by staking contract
+ * @param _stakingProvider Address of staking provider
+ * @param _fromAmount Current amount of authorized tokens
+ * @param _toAmount Amount of authorized tokens to decrease
+ */
+ function authorizationDecreaseRequested(
+ address _stakingProvider,
+ uint96 _fromAmount,
+ uint96 _toAmount
+ )
+ external override onlyStakingContract updateReward(_stakingProvider)
+ {
+ StakingProviderInfo storage info = stakingProviderInfo[_stakingProvider];
+ require(_toAmount <= info.authorized, "Amount to decrease greater than authorized");
+ require(
+ _toAmount == 0 || _toAmount >= minimumAuthorization,
+ "Resulting authorization will be less than minimum"
+ );
+ if (info.operatorConfirmed) {
+ resynchronizeAuthorizedOverall(info, _fromAmount);
+ }
+
+ info.authorized = _fromAmount;
+ info.deauthorizing = _fromAmount - _toAmount;
+ info.endDeauthorization = uint64(block.timestamp + deauthorizationDuration);
+ emit AuthorizationDecreaseRequested(_stakingProvider, _fromAmount, _toAmount);
+ _updateAuthorization(_stakingProvider, info);
+ }
+
+ /**
+ * @notice Approve request of decreasing authorization. Can be called by anyone
+ * @param _stakingProvider Address of staking provider
+ */
+ function finishAuthorizationDecrease(address _stakingProvider) external updateReward(_stakingProvider) {
+ StakingProviderInfo storage info = stakingProviderInfo[_stakingProvider];
+ require(info.deauthorizing > 0, "There is no deauthorizing in process");
+ require(info.endDeauthorization <= block.timestamp, "Authorization decrease has not finished yet");
+
+ uint96 toAmount = tStaking.approveAuthorizationDecrease(_stakingProvider);
+
+ if (info.operatorConfirmed) {
+ authorizedOverall -= info.authorized - toAmount;
+ }
+
+ emit AuthorizationDecreaseApproved(_stakingProvider, info.authorized, toAmount);
+ info.authorized = toAmount;
+ info.deauthorizing = 0;
+ info.endDeauthorization = 0;
+
+ if (info.authorized == 0) {
+ _stakingProviderFromOperator[info.operator] = address(0);
+ info.operator = address(0);
+ _releaseOperator(_stakingProvider);
+ }
+ _updateAuthorization(_stakingProvider, info);
+ }
+
+ /**
+ * @notice Read authorization from staking contract and store it. Can be called by anyone
+ * @param _stakingProvider Address of staking provider
+ */
+ function resynchronizeAuthorization(address _stakingProvider) external updateReward(_stakingProvider) {
+ StakingProviderInfo storage info = stakingProviderInfo[_stakingProvider];
+ uint96 newAuthorized = tStaking.authorizedStake(_stakingProvider, address(this));
+ require(info.authorized > newAuthorized, "Nothing to synchronize");
+
+ if (info.operatorConfirmed) {
+ authorizedOverall -= info.authorized - newAuthorized;
+ }
+ emit AuthorizationReSynchronized(_stakingProvider, info.authorized, newAuthorized);
+
+ info.authorized = newAuthorized;
+ if (info.authorized < info.deauthorizing) {
+ info.deauthorizing = info.authorized;
+ }
+
+ if (info.authorized == 0) {
+ _stakingProviderFromOperator[info.operator] = address(0);
+ info.operator = address(0);
+ _releaseOperator(_stakingProvider);
+ }
+ _updateAuthorization(_stakingProvider, info);
+ }
+
+ //-------------------------Main-------------------------
+ /**
+ * @notice Returns staking provider for specified operator
+ */
+ function stakingProviderFromOperator(address _operator) public view returns (address) {
+ return _stakingProviderFromOperator[_operator];
+ }
+
+ /**
+ * @notice Returns operator for specified staking provider
+ */
+ function getOperatorFromStakingProvider(address _stakingProvider) public view returns (address) {
+ return stakingProviderInfo[_stakingProvider].operator;
+ }
+
+ /**
+ * @notice Get all tokens delegated to the staking provider
+ */
+ function authorizedStake(address _stakingProvider) public view returns (uint96) {
+ return stakingProviderInfo[_stakingProvider].authorized;
+ }
+
+ /**
+ * @notice Get all tokens delegated to the staking provider
+ */
+ function getEligibleAmount(StakingProviderInfo storage _info) internal view returns (uint96) {
+ return _info.authorized - _info.deauthorizing;
+ }
+
+ /**
+ * @notice Get the value of authorized tokens for active providers as well as providers and their authorized tokens
+ * @param _startIndex Start index for looking in providers array
+ * @param _maxStakingProviders Max providers for looking, if set 0 then all will be used
+ * @return allAuthorizedTokens Sum of authorized tokens for active providers
+ * @return activeStakingProviders Array of providers and their authorized tokens.
+ * Providers addresses stored as uint256
+ * @dev Note that activeStakingProviders[0] is an array of uint256, but you want addresses.
+ * Careful when used directly!
+ */
+ function getActiveStakingProviders(uint256 _startIndex, uint256 _maxStakingProviders)
+ external view returns (uint256 allAuthorizedTokens, uint256[2][] memory activeStakingProviders)
+ {
+ uint256 endIndex = stakingProviders.length;
+ require(_startIndex < endIndex, "Wrong start index");
+ if (_maxStakingProviders != 0 && _startIndex + _maxStakingProviders < endIndex) {
+ endIndex = _startIndex + _maxStakingProviders;
+ }
+ activeStakingProviders = new uint256[2][](endIndex - _startIndex);
+ allAuthorizedTokens = 0;
+
+ uint256 resultIndex = 0;
+ for (uint256 i = _startIndex; i < endIndex; i++) {
+ address stakingProvider = stakingProviders[i];
+ StakingProviderInfo storage info = stakingProviderInfo[stakingProvider];
+ uint256 eligibleAmount = getEligibleAmount(info);
+ if (eligibleAmount < minimumAuthorization || !info.operatorConfirmed) {
+ continue;
+ }
+ activeStakingProviders[resultIndex][0] = uint256(uint160(stakingProvider));
+ activeStakingProviders[resultIndex++][1] = eligibleAmount;
+ allAuthorizedTokens += eligibleAmount;
+ }
+ assembly {
+ mstore(activeStakingProviders, resultIndex)
+ }
+ }
+
+ /**
+ * @notice Returns beneficiary related to the staking provider
+ */
+ function getBeneficiary(address _stakingProvider) public view returns (address payable beneficiary) {
+ (, beneficiary,) = tStaking.rolesOf(_stakingProvider);
+ }
+
+ /**
+ * @notice Returns true if staking provider has authorized stake to this application
+ */
+ function isAuthorized(address _stakingProvider) public view returns (bool) {
+ return stakingProviderInfo[_stakingProvider].authorized > 0;
+ }
+
+ /**
+ * @notice Returns true if operator has confirmed address
+ */
+ // TODO maybe _stakingProvider instead of _operator as input?
+ function isOperatorConfirmed(address _operator) public view returns (bool) {
+ address stakingProvider = _stakingProviderFromOperator[_operator];
+ StakingProviderInfo storage info = stakingProviderInfo[stakingProvider];
+ return info.operatorConfirmed;
+ }
+
+ /**
+ * @notice Return the length of the array of staking providers
+ */
+ function getStakingProvidersLength() external view returns (uint256) {
+ return stakingProviders.length;
+ }
+
+ /**
+ * @notice Bond operator
+ * @param _stakingProvider Staking provider address
+ * @param _operator Operator address. Must be an EOA, not a contract address
+ */
+ function bondOperator(address _stakingProvider, address _operator)
+ external onlyOwnerOrStakingProvider(_stakingProvider) updateReward(_stakingProvider)
+ {
+ StakingProviderInfo storage info = stakingProviderInfo[_stakingProvider];
+ address previousOperator = info.operator;
+ require(_operator != previousOperator, "Specified operator is already bonded with this provider");
+ // If this staker had a operator ...
+ if (previousOperator != address(0)) {
+ require(
+ !info.operatorConfirmed ||
+ block.timestamp >= uint256(info.operatorStartTimestamp) + minOperatorSeconds,
+ "Not enough time passed to change operator"
+ );
+ // Remove the old relation "operator->stakingProvider"
+ _stakingProviderFromOperator[previousOperator] = address(0);
+ }
+
+ if (_operator != address(0)) {
+ require(_stakingProviderFromOperator[_operator] == address(0), "Specified operator is already in use");
+ require(
+ _operator == _stakingProvider || getBeneficiary(_operator) == address(0),
+ "Specified operator is a provider"
+ );
+ // Set new operator->stakingProvider relation
+ _stakingProviderFromOperator[_operator] = _stakingProvider;
+ }
+
+ if (info.operatorStartTimestamp == 0) {
+ stakingProviders.push(_stakingProvider);
+ }
+
+ if (info.operatorConfirmed) {
+ authorizedOverall -= info.authorized;
+ }
+
+ // Bond new operator (or unbond if _operator == address(0))
+ info.operator = _operator;
+ info.operatorStartTimestamp = uint64(block.timestamp);
+ emit OperatorBonded(_stakingProvider, _operator, previousOperator, block.timestamp);
+ _releaseOperator(_stakingProvider);
+ }
+
+ /**
+ * @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");
+ require(msg.sender == tx.origin, "Only operator with real address can make a confirmation");
+
+ updateRewardInternal(stakingProvider);
+ info.operatorConfirmed = true;
+ authorizedOverall += info.authorized;
+ emit OperatorConfirmed(stakingProvider, msg.sender);
+
+ if (address(updatableStakeInfo) != address(0)) {
+ updatableStakeInfo.updateOperator(stakingProvider, msg.sender);
+ }
+ }
+
+ //-------------------------XChain-------------------------
+
+ /**
+ * @notice Resets operator confirmation
+ */
+ function _releaseOperator(address _stakingProvider) internal {
+ stakingProviderInfo[_stakingProvider].operatorConfirmed = false;
+ if (address(updatableStakeInfo) != address(0)) {
+ updatableStakeInfo.updateOperator(_stakingProvider, address(0));
+ }
+ }
+
+ /**
+ * @notice Send updated authorized amount to xchain contract
+ */
+ function _updateAuthorization(address _stakingProvider, StakingProviderInfo storage _info) internal {
+ if (address(updatableStakeInfo) != address(0)) {
+ // TODO send both authorized and eligible amounts in case of slashing from StakeInfo
+ uint96 eligibleAmount = getEligibleAmount(_info);
+ updatableStakeInfo.updateAmount(_stakingProvider, eligibleAmount);
+ }
+ }
+
+ //-------------------------Slashing-------------------------
+ /**
+ * @notice Slash the provider's stake and reward the investigator
+ * @param _stakingProvider Staking provider address
+ * @param _penalty Penalty
+ * @param _investigator Investigator
+ */
+ function slash(
+ address _stakingProvider,
+ uint96 _penalty,
+ address _investigator
+ )
+ external
+ {
+ require(msg.sender == adjudicator, "Only adjudicator allowed to slash");
+ address[] memory stakingProviderWrapper = new address[](1);
+ stakingProviderWrapper[0] = _stakingProvider;
+ tStaking.seize(_penalty, 100, _investigator, stakingProviderWrapper);
+ }
+
+}
diff --git a/contracts/contracts/TestnetThresholdStaking.sol b/contracts/contracts/TestnetThresholdStaking.sol
index dd3c60c1..22ac6c5f 100644
--- a/contracts/contracts/TestnetThresholdStaking.sol
+++ b/contracts/contracts/TestnetThresholdStaking.sol
@@ -3,7 +3,7 @@
pragma solidity ^0.8.0;
-import "./SimplePREApplication.sol";
+import "@threshold/contracts/staking/IApplication.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
@@ -18,12 +18,12 @@ contract TestnetThresholdStaking is Ownable {
uint96 nuInTStake;
}
- SimplePREApplication public preApplication;
+ IApplication public application;
mapping (address => StakingProviderInfo) public stakingProviderInfo;
- function setApplication(SimplePREApplication _preApplication) external onlyOwner {
- preApplication = _preApplication;
+ function setApplication(IApplication _application) external onlyOwner {
+ application = _application;
}
function stakedNu(address) external view returns (uint256) {
diff --git a/contracts/contracts/coordination/IUpdatableStakeInfo.sol b/contracts/contracts/coordination/IUpdatableStakeInfo.sol
index a3130d85..8410b08e 100644
--- a/contracts/contracts/coordination/IUpdatableStakeInfo.sol
+++ b/contracts/contracts/coordination/IUpdatableStakeInfo.sol
@@ -3,10 +3,10 @@
pragma solidity ^0.8.0;
/**
-* @title StakeInfo
-* @notice StakeInfo
+* @title IUpdatableStakeInfo
+* @notice Interface for x-chain interactions between application and coordinator
*/
-interface IUpdatableStakes {
+interface IUpdatableStakeInfo {
event UpdatedStakeOperator(address indexed stakingProvider, address indexed operator);
event UpdatedStakeAmount(address indexed stakingProvider, uint96 amount);
diff --git a/contracts/contracts/coordination/StakeInfo.sol b/contracts/contracts/coordination/StakeInfo.sol
index 5e764313..710e5b3c 100644
--- a/contracts/contracts/coordination/StakeInfo.sol
+++ b/contracts/contracts/coordination/StakeInfo.sol
@@ -10,7 +10,7 @@ import "../../threshold/IAccessControlApplication.sol";
* @title StakeInfo
* @notice StakeInfo
*/
-contract StakeInfo is AccessControl, IUpdatableStakes, IAccessControlApplication {
+contract StakeInfo is AccessControl, IUpdatableStakeInfo, IAccessControlApplication {
bytes32 public constant UPDATE_ROLE = keccak256("UPDATE_ROLE");
@@ -27,7 +27,7 @@ contract StakeInfo is AccessControl, IUpdatableStakes, IAccessControlApplication
}
mapping(address => Stake) public stakes;
- mapping(address => address) public operatorToProvider;
+ mapping(address => address) private operatorToProvider;
function stakingProviderFromOperator(address _operator) external view returns (address){
return operatorToProvider[_operator];
@@ -37,11 +37,11 @@ contract StakeInfo is AccessControl, IUpdatableStakes, IAccessControlApplication
return stakes[_stakingProvider].amount;
}
- function updateOperator(address stakingProvider, address operator) external onlyRole(UPDATE_ROLE) {
+ function updateOperator(address stakingProvider, address operator) external override onlyRole(UPDATE_ROLE) {
_updateOperator(stakingProvider, operator);
}
- function updateAmount(address stakingProvider, uint96 amount) external onlyRole(UPDATE_ROLE) {
+ function updateAmount(address stakingProvider, uint96 amount) external override onlyRole(UPDATE_ROLE) {
_updateAmount(stakingProvider, amount);
}
@@ -69,7 +69,7 @@ contract StakeInfo is AccessControl, IUpdatableStakes, IAccessControlApplication
}
}
- function batchUpdate(bytes32[] calldata updateInfo) external onlyRole(UPDATE_ROLE) {
+ function batchUpdate(bytes32[] calldata updateInfo) external override onlyRole(UPDATE_ROLE) {
require(updateInfo.length % 2 == 0, "bad length");
for(uint i = 0; i < updateInfo.length; i += 2){
bytes32 word0 = updateInfo[i];
diff --git a/contracts/test/AdjudicatorTestSet.sol b/contracts/test/AdjudicatorTestSet.sol
new file mode 100644
index 00000000..5e825455
--- /dev/null
+++ b/contracts/test/AdjudicatorTestSet.sol
@@ -0,0 +1,92 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+pragma solidity ^0.8.0;
+
+
+import "../contracts/Adjudicator.sol";
+import "../contracts/lib/SignatureVerifier.sol";
+//import "../contracts/proxy/Upgradeable.sol";
+
+
+/**
+* @notice Contract for testing the Adjudicator contract
+*/
+contract TACoApplicationForAdjudicatorMock {
+
+ uint32 public immutable secondsPerPeriod = 1;
+ mapping (address => uint96) public stakingProviderInfo;
+ mapping (address => uint256) public rewardInfo;
+ mapping (address => address) _stakingProviderFromOperator;
+
+ function stakingProviderFromOperator(address _operator) public view returns (address) {
+ return _stakingProviderFromOperator[_operator];
+ }
+
+ function setStakingProviderInfo(address _stakingProvider, uint96 _amount, address _operator) public {
+ stakingProviderInfo[_stakingProvider] = _amount;
+ if (_operator == address(0)) {
+ _operator = _stakingProvider;
+ }
+ _stakingProviderFromOperator[_operator] = _stakingProvider;
+ }
+
+ function authorizedStake(address _stakingProvider) public view returns (uint96) {
+ return stakingProviderInfo[_stakingProvider];
+ }
+
+ function slash(
+ address _stakingProvider,
+ uint96 _penalty,
+ address _investigator
+ )
+ external
+ {
+ stakingProviderInfo[_stakingProvider] -= _penalty;
+ rewardInfo[_investigator] += 1;
+ }
+
+}
+
+
+///**
+//* @notice Upgrade to this contract must lead to fail
+//*/
+//contract AdjudicatorBad is Upgradeable {
+//
+// mapping (bytes32 => bool) public evaluatedCFrags;
+// mapping (address => uint256) public penaltyHistory;
+//
+//}
+//
+//
+///**
+//* @notice Contract for testing upgrading the Adjudicator contract
+//*/
+//contract AdjudicatorV2Mock is Adjudicator {
+//
+// uint256 public valueToCheck;
+//
+// constructor(
+// SignatureVerifier.HashAlgorithm _hashAlgorithm,
+// uint256 _basePenalty,
+// uint256 _percentagePenalty,
+// uint256 _penaltyHistoryCoefficient
+// )
+// Adjudicator(
+// _hashAlgorithm,
+// _basePenalty,
+// _percentagePenalty,
+// _penaltyHistoryCoefficient
+// )
+// {
+// }
+//
+// function setValueToCheck(uint256 _valueToCheck) public {
+// valueToCheck = _valueToCheck;
+// }
+//
+// function verifyState(address _testTarget) override public {
+// super.verifyState(_testTarget);
+// require(uint256(delegateGet(_testTarget, this.valueToCheck.selector)) == valueToCheck);
+// }
+//}
diff --git a/contracts/test/PREApplicationTestSet.sol b/contracts/test/PREApplicationTestSet.sol
deleted file mode 100644
index 3ebd1b02..00000000
--- a/contracts/test/PREApplicationTestSet.sol
+++ /dev/null
@@ -1,153 +0,0 @@
-// SPDX-License-Identifier: AGPL-3.0-or-later
-
-pragma solidity ^0.8.0;
-
-
-import "../contracts/SimplePREApplication.sol";
-//import "zeppelin/token/ERC20/ERC20.sol";
-//import "zeppelin/token/ERC20/ERC20Detailed.sol";
-
-
-///**
-//* @notice Contract for testing PRE application contract
-//*/
-//contract TToken is ERC20, ERC20Detailed('T', 'T', 18) {
-//
-// constructor (uint256 _totalSupplyOfTokens) {
-// _mint(msg.sender, _totalSupplyOfTokens);
-// }
-//
-//}
-
-
-/**
-* @notice Contract for testing PRE application contract
-*/
-contract ThresholdStakingForPREApplicationMock {
-
- struct StakingProviderInfo {
- address owner;
- address payable beneficiary;
- address authorizer;
- uint96 tStake;
- uint96 keepInTStake;
- uint96 nuInTStake;
- }
-
- SimplePREApplication public preApplication;
-
- mapping (address => StakingProviderInfo) public stakingProviderInfo;
-
- function setApplication(SimplePREApplication _preApplication) external {
- preApplication = _preApplication;
- }
-
- function stakedNu(address) external view returns (uint256) {
- return 0;
- }
-
- function setRoles(
- address _stakingProvider,
- address _owner,
- address payable _beneficiary,
- address _authorizer
- )
- external
- {
- StakingProviderInfo storage info = stakingProviderInfo[_stakingProvider];
- info.owner = _owner;
- info.beneficiary = _beneficiary;
- info.authorizer = _authorizer;
- }
-
- /**
- * @dev If the function is called with only the _stakingProvider parameter,
- * we presume that the caller wants that address set for the other roles as well.
- */
- function setRoles(address _stakingProvider) external {
- StakingProviderInfo storage info = stakingProviderInfo[_stakingProvider];
- info.owner = _stakingProvider;
- info.beneficiary = payable(_stakingProvider);
- info.authorizer = _stakingProvider;
- }
-
- function setStakes(
- address _stakingProvider,
- uint96 _tStake,
- uint96 _keepInTStake,
- uint96 _nuInTStake
- )
- external
- {
- StakingProviderInfo storage info = stakingProviderInfo[_stakingProvider];
- info.tStake = _tStake;
- info.keepInTStake = _keepInTStake;
- info.nuInTStake = _nuInTStake;
- }
-
- function authorizedStake(address _stakingProvider, address _application) external view returns (uint96) {
- return 0;
- }
-
- function stakes(address _stakingProvider) external view returns (
- uint96 tStake,
- uint96 keepInTStake,
- uint96 nuInTStake
- ) {
- StakingProviderInfo storage info = stakingProviderInfo[_stakingProvider];
- tStake = info.tStake;
- keepInTStake = info.keepInTStake;
- nuInTStake = info.nuInTStake;
- }
-
- function rolesOf(address _stakingProvider) external view returns (
- address owner,
- address payable beneficiary,
- address authorizer
- ) {
- StakingProviderInfo storage info = stakingProviderInfo[_stakingProvider];
- owner = info.owner;
- beneficiary = info.beneficiary;
- authorizer = info.authorizer;
- }
-
-// function approveAuthorizationDecrease(address _stakingProvider) external returns (uint96) {
-//
-// }
-
-// function seize(
-// uint96 _amount,
-// uint256 _rewardMultipier,
-// address _notifier,
-// address[] memory _stakingProviders
-// ) external {
-//
-// }
-
-// function authorizationIncreased(address _stakingProvider, uint96 _fromAmount, uint96 _toAmount) external {
-// preApplication.authorizationIncreased(_stakingProvider, _fromAmount, _toAmount);
-// }
-
-}
-
-
-/**
-* @notice Intermediary contract for testing operator
-*/
-contract Intermediary {
-
- SimplePREApplication immutable preApplication;
-
- constructor(SimplePREApplication _preApplication) {
- preApplication = _preApplication;
- }
-
- function bondOperator(address _operator) external {
- preApplication.bondOperator(address(this), _operator);
- }
-
- function confirmOperatorAddress() external {
- preApplication.confirmOperatorAddress();
- }
-
-}
diff --git a/contracts/test/StakingEscrowTestSet.sol b/contracts/test/StakingEscrowTestSet.sol
index 09424a72..14ee50c2 100644
--- a/contracts/test/StakingEscrowTestSet.sol
+++ b/contracts/test/StakingEscrowTestSet.sol
@@ -5,7 +5,7 @@ pragma solidity ^0.8.0;
import "../contracts/StakingEscrow.sol";
import "../contracts/NuCypherToken.sol";
-import "../threshold/IStaking.sol";
+import "@threshold/contracts/staking/IStaking.sol";
/**
* @notice Enhanced version of StakingEscrow to use in tests
diff --git a/contracts/test/TACoApplicationTestSet.sol b/contracts/test/TACoApplicationTestSet.sol
new file mode 100644
index 00000000..3fbc6ac0
--- /dev/null
+++ b/contracts/test/TACoApplicationTestSet.sol
@@ -0,0 +1,170 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+pragma solidity ^0.8.0;
+
+
+import "../contracts/TACoApplication.sol";
+import "@threshold/contracts/staking/IApplication.sol";
+import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
+
+
+/**
+* @notice Contract for testing the application contract
+*/
+contract TToken is ERC20("T", "T") {
+
+ constructor (uint256 _totalSupplyOfTokens) {
+ _mint(msg.sender, _totalSupplyOfTokens);
+ }
+
+}
+
+
+/**
+* @notice Contract for testing TACo application contract
+*/
+contract ThresholdStakingForTACoApplicationMock {
+
+ struct StakingProviderInfo {
+ address owner;
+ address payable beneficiary;
+ address authorizer;
+ uint96 authorized;
+ uint96 decreaseRequestTo;
+ }
+
+ IApplication public application;
+
+ mapping (address => StakingProviderInfo) public stakingProviderInfo;
+
+ uint96 public amountToSeize;
+ uint256 public rewardMultiplier;
+ address public notifier;
+ address[] public stakingProvidersToSeize;
+
+ function setApplication(IApplication _application) external {
+ application = _application;
+ }
+
+ function stakedNu(address) external view returns (uint256) {
+ return 0;
+ }
+
+ function setRoles(
+ address _stakingProvider,
+ address _owner,
+ address payable _beneficiary,
+ address _authorizer
+ )
+ public
+ {
+ StakingProviderInfo storage info = stakingProviderInfo[_stakingProvider];
+ info.owner = _owner;
+ info.beneficiary = _beneficiary;
+ info.authorizer = _authorizer;
+ }
+
+ /**
+ * @dev If the function is called with only the _stakingProvider parameter,
+ * we presume that the caller wants that address set for the other roles as well.
+ */
+ function setRoles(address _stakingProvider) external {
+ setRoles(_stakingProvider, _stakingProvider, payable(_stakingProvider), _stakingProvider);
+ }
+
+ function setAuthorized(address _stakingProvider, uint96 _authorized) external {
+ stakingProviderInfo[_stakingProvider].authorized = _authorized;
+ }
+
+ function setDecreaseRequest(address _stakingProvider, uint96 _decreaseRequestTo) external {
+ stakingProviderInfo[_stakingProvider].decreaseRequestTo = _decreaseRequestTo;
+ }
+
+ function authorizedStake(address _stakingProvider, address _application) external view returns (uint96) {
+ require(_stakingProvider == _application || _application == address(application));
+ return stakingProviderInfo[_stakingProvider].authorized;
+ }
+
+ function rolesOf(address _stakingProvider) external view returns (
+ address owner,
+ address payable beneficiary,
+ address authorizer
+ ) {
+ StakingProviderInfo storage info = stakingProviderInfo[_stakingProvider];
+ owner = info.owner;
+ beneficiary = info.beneficiary;
+ authorizer = info.authorizer;
+ }
+
+ function approveAuthorizationDecrease(address _stakingProvider) external returns (uint96) {
+ StakingProviderInfo storage info = stakingProviderInfo[_stakingProvider];
+ info.authorized = info.decreaseRequestTo;
+ return info.authorized;
+ }
+
+ function seize(
+ uint96 _amount,
+ uint256 _rewardMultiplier,
+ address _notifier,
+ address[] memory _stakingProviders
+ ) external {
+ amountToSeize = _amount;
+ rewardMultiplier = _rewardMultiplier;
+ notifier = _notifier;
+ stakingProvidersToSeize = _stakingProviders;
+ }
+
+ function getLengthOfStakingProvidersToSeize() external view returns (uint256) {
+ return stakingProvidersToSeize.length;
+ }
+
+ function authorizationIncreased(address _stakingProvider, uint96 _fromAmount, uint96 _toAmount) external {
+ application.authorizationIncreased(_stakingProvider, _fromAmount, _toAmount);
+ stakingProviderInfo[_stakingProvider].authorized = _toAmount;
+ }
+
+ function involuntaryAuthorizationDecrease(
+ address _stakingProvider,
+ uint96 _fromAmount,
+ uint96 _toAmount
+ )
+ external
+ {
+ application.involuntaryAuthorizationDecrease(_stakingProvider, _fromAmount, _toAmount);
+ stakingProviderInfo[_stakingProvider].authorized = _toAmount;
+ }
+
+ function authorizationDecreaseRequested(
+ address _stakingProvider,
+ uint96 _fromAmount,
+ uint96 _toAmount
+ )
+ external
+ {
+ application.authorizationDecreaseRequested(_stakingProvider, _fromAmount, _toAmount);
+ stakingProviderInfo[_stakingProvider].decreaseRequestTo = _toAmount;
+ }
+
+}
+
+
+/**
+* @notice Intermediary contract for testing operator
+*/
+contract Intermediary {
+
+ TACoApplication immutable public application;
+
+ constructor(TACoApplication _application) {
+ application = _application;
+ }
+
+ function bondOperator(address _operator) external {
+ application.bondOperator(address(this), _operator);
+ }
+
+ function confirmOperatorAddress() external {
+ application.confirmOperatorAddress();
+ }
+
+}
diff --git a/contracts/threshold/IStaking.sol b/contracts/threshold/IStaking.sol
deleted file mode 100644
index 06421d81..00000000
--- a/contracts/threshold/IStaking.sol
+++ /dev/null
@@ -1,379 +0,0 @@
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-// ██████████████ ▐████▌ ██████████████
-// ██████████████ ▐████▌ ██████████████
-// ▐████▌ ▐████▌
-// ▐████▌ ▐████▌
-// ██████████████ ▐████▌ ██████████████
-// ██████████████ ▐████▌ ██████████████
-// ▐████▌ ▐████▌
-// ▐████▌ ▐████▌
-// ▐████▌ ▐████▌
-// ▐████▌ ▐████▌
-// ▐████▌ ▐████▌
-// ▐████▌ ▐████▌
-
-pragma solidity ^0.8.0;
-
-/// @title Interface of Threshold Network staking contract
-/// @notice The staking contract enables T owners to have their wallets offline
-/// and their stake managed by providers on their behalf. All off-chain
-/// client software should be able to run without exposing provider’s
-/// private key and should not require any owner’s keys at all.
-/// The stake delegation optimizes the network throughput without
-/// compromising the security of the owners’ stake.
-interface IStaking {
- enum StakeType {
- NU,
- KEEP,
- T
- }
-
- //
- //
- // Delegating a stake
- //
- //
-
- /// @notice Creates a delegation with `msg.sender` owner with the given
- /// provider, beneficiary, and authorizer. Transfers the given
- /// amount of T to the staking contract.
- /// @dev The owner of the delegation needs to have the amount approved to
- /// transfer to the staking contract.
- function stake(
- address stakingProvider,
- address payable beneficiary,
- address authorizer,
- uint96 amount
- ) external;
-
- /// @notice Copies delegation from the legacy KEEP staking contract to T
- /// staking contract. No tokens are transferred. Caches the active
- /// stake amount from KEEP staking contract. Can be called by
- /// anyone.
- function stakeKeep(address stakingProvider) external;
-
- /// @notice Copies delegation from the legacy NU staking contract to T
- /// staking contract, additionally appointing beneficiary and
- /// authorizer roles. Caches the amount staked in NU staking
- /// contract. Can be called only by the original delegation owner.
- function stakeNu(
- address stakingProvider,
- address payable beneficiary,
- address authorizer
- ) external;
-
- /// @notice Refresh Keep stake owner. Can be called only by the old owner.
- function refreshKeepStakeOwner(address stakingProvider) external;
-
- /// @notice Allows the Governance to set the minimum required stake amount.
- /// This amount is required to protect against griefing the staking
- /// contract and individual applications are allowed to require
- /// higher minimum stakes if necessary.
- function setMinimumStakeAmount(uint96 amount) external;
-
- //
- //
- // Authorizing an application
- //
- //
-
- /// @notice Allows the Governance to approve the particular application
- /// before individual stake authorizers are able to authorize it.
- function approveApplication(address application) external;
-
- /// @notice Increases the authorization of the given provider for the given
- /// application by the given amount. Can only be called by the given
- /// provider’s authorizer.
- /// @dev Calls `authorizationIncreased(address stakingProvider, uint256 amount)`
- /// on the given application to notify the application about
- /// authorization change. See `IApplication`.
- function increaseAuthorization(
- address stakingProvider,
- address application,
- uint96 amount
- ) external;
-
- /// @notice Requests decrease of the authorization for the given provider on
- /// the given application by the provided amount.
- /// It may not change the authorized amount immediatelly. When
- /// it happens depends on the application. Can only be called by the
- /// given provider’s authorizer. Overwrites pending authorization
- /// decrease for the given provider and application.
- /// @dev Calls `authorizationDecreaseRequested(address stakingProvider, uint256 amount)`
- /// on the given application. See `IApplication`.
- function requestAuthorizationDecrease(
- address stakingProvider,
- address application,
- uint96 amount
- ) external;
-
- /// @notice Requests decrease of all authorizations for the given provider on
- /// the applications by all authorized amount.
- /// It may not change the authorized amount immediatelly. When
- /// it happens depends on the application. Can only be called by the
- /// given provider’s authorizer. Overwrites pending authorization
- /// decrease for the given provider and application.
- /// @dev Calls `authorizationDecreaseRequested(address stakingProvider, uint256 amount)`
- /// for each authorized application. See `IApplication`.
- function requestAuthorizationDecrease(address stakingProvider) external;
-
- /// @notice Called by the application at its discretion to approve the
- /// previously requested authorization decrease request. Can only be
- /// called by the application that was previously requested to
- /// decrease the authorization for that provider.
- /// Returns resulting authorized amount for the application.
- function approveAuthorizationDecrease(address stakingProvider)
- external
- returns (uint96);
-
- /// @notice Decreases the authorization for the given `stakingProvider` on
- /// the given disabled `application`, for all authorized amount.
- /// Can be called by anyone.
- function forceDecreaseAuthorization(
- address stakingProvider,
- address application
- ) external;
-
- /// @notice Pauses the given application’s eligibility to slash stakes.
- /// Besides that stakers can't change authorization to the application.
- /// Can be called only by the Panic Button of the particular
- /// application. The paused application can not slash stakes until
- /// it is approved again by the Governance using `approveApplication`
- /// function. Should be used only in case of an emergency.
- function pauseApplication(address application) external;
-
- /// @notice Disables the given application. The disabled application can't
- /// slash stakers. Also stakers can't increase authorization to that
- /// application but can decrease without waiting by calling
- /// `requestAuthorizationDecrease` at any moment. Can be called only
- /// by the governance. The disabled application can't be approved
- /// again. Should be used only in case of an emergency.
- function disableApplication(address application) external;
-
- /// @notice Sets the Panic Button role for the given application to the
- /// provided address. Can only be called by the Governance. If the
- /// Panic Button for the given application should be disabled, the
- /// role address should be set to 0x0 address.
- function setPanicButton(address application, address panicButton) external;
-
- /// @notice Sets the maximum number of applications one provider can
- /// authorize. Used to protect against DoSing slashing queue.
- /// Can only be called by the Governance.
- function setAuthorizationCeiling(uint256 ceiling) external;
-
- //
- //
- // Stake top-up
- //
- //
-
- /// @notice Increases the amount of the stake for the given provider.
- /// Can be called only by the owner or provider.
- /// @dev The sender of this transaction needs to have the amount approved to
- /// transfer to the staking contract.
- function topUp(address stakingProvider, uint96 amount) external;
-
- /// @notice Propagates information about stake top-up from the legacy KEEP
- /// staking contract to T staking contract. Can be called only by
- /// the owner or provider.
- function topUpKeep(address stakingProvider) external;
-
- /// @notice Propagates information about stake top-up from the legacy NU
- /// staking contract to T staking contract. Can be called only by
- /// the owner or provider.
- function topUpNu(address stakingProvider) external;
-
- //
- //
- // Undelegating a stake (unstaking)
- //
- //
-
- /// @notice Reduces the liquid T stake amount by the provided amount and
- /// withdraws T to the owner. Reverts if there is at least one
- /// authorization higher than the sum of the legacy stake and
- /// remaining liquid T stake or if the unstake amount is higher than
- /// the liquid T stake amount. Can be called only by the owner or
- /// provider.
- function unstakeT(address stakingProvider, uint96 amount) external;
-
- /// @notice Sets the legacy KEEP staking contract active stake amount cached
- /// in T staking contract to 0. Reverts if the amount of liquid T
- /// staked in T staking contract is lower than the highest
- /// application authorization. This function allows to unstake from
- /// KEEP staking contract and still being able to operate in T
- /// network and earning rewards based on the liquid T staked. Can be
- /// called only by the delegation owner and provider.
- function unstakeKeep(address stakingProvider) external;
-
- /// @notice Reduces cached legacy NU stake amount by the provided amount.
- /// Reverts if there is at least one authorization higher than the
- /// sum of remaining legacy NU stake and liquid T stake for that
- /// provider or if the untaked amount is higher than the cached
- /// legacy stake amount. If succeeded, the legacy NU stake can be
- /// partially or fully undelegated on the legacy staking contract.
- /// This function allows to unstake from NU staking contract and
- /// still being able to operate in T network and earning rewards
- /// based on the liquid T staked. Can be called only by the
- /// delegation owner and provider.
- function unstakeNu(address stakingProvider, uint96 amount) external;
-
- /// @notice Sets cached legacy stake amount to 0, sets the liquid T stake
- /// amount to 0 and withdraws all liquid T from the stake to the
- /// owner. Reverts if there is at least one non-zero authorization.
- /// Can be called only by the delegation owner and provider.
- function unstakeAll(address stakingProvider) external;
-
- //
- //
- // Keeping information in sync
- //
- //
-
- /// @notice Notifies about the discrepancy between legacy KEEP active stake
- /// and the amount cached in T staking contract. Slashes the provider
- /// in case the amount cached is higher than the actual active stake
- /// amount in KEEP staking contract. Needs to update authorizations
- /// of all affected applications and execute an involuntary
- /// allocation decrease on all affected applications. Can be called
- /// by anyone, notifier receives a reward.
- function notifyKeepStakeDiscrepancy(address stakingProvider) external;
-
- /// @notice Notifies about the discrepancy between legacy NU active stake
- /// and the amount cached in T staking contract. Slashes the
- /// provider in case the amount cached is higher than the actual
- /// active stake amount in NU staking contract. Needs to update
- /// authorizations of all affected applications and execute an
- /// involuntary allocation decrease on all affected applications.
- /// Can be called by anyone, notifier receives a reward.
- function notifyNuStakeDiscrepancy(address stakingProvider) external;
-
- /// @notice Sets the penalty amount for stake discrepancy and reward
- /// multiplier for reporting it. The penalty is seized from the
- /// provider account, and 5% of the penalty, scaled by the
- /// multiplier, is given to the notifier. The rest of the tokens are
- /// burned. Can only be called by the Governance. See `seize` function.
- function setStakeDiscrepancyPenalty(
- uint96 penalty,
- uint256 rewardMultiplier
- ) external;
-
- /// @notice Sets reward in T tokens for notification of misbehaviour
- /// of one provider. Can only be called by the governance.
- function setNotificationReward(uint96 reward) external;
-
- /// @notice Transfer some amount of T tokens as reward for notifications
- /// of misbehaviour
- function pushNotificationReward(uint96 reward) external;
-
- /// @notice Withdraw some amount of T tokens from notifiers treasury.
- /// Can only be called by the governance.
- function withdrawNotificationReward(address recipient, uint96 amount)
- external;
-
- /// @notice Adds providers to the slashing queue along with the amount that
- /// should be slashed from each one of them. Can only be called by
- /// application authorized for all providers in the array.
- function slash(uint96 amount, address[] memory stakingProviders) external;
-
- /// @notice Adds providers to the slashing queue along with the amount.
- /// The notifier will receive reward per each provider from
- /// notifiers treasury. Can only be called by application
- /// authorized for all providers in the array.
- function seize(
- uint96 amount,
- uint256 rewardMultipier,
- address notifier,
- address[] memory stakingProviders
- ) external;
-
- /// @notice Takes the given number of queued slashing operations and
- /// processes them. Receives 5% of the slashed amount.
- /// Executes `involuntaryAllocationDecrease` function on each
- /// affected application.
- function processSlashing(uint256 count) external;
-
- //
- //
- // Auxiliary functions
- //
- //
-
- /// @notice Returns the authorized stake amount of the provider for the
- /// application.
- function authorizedStake(address stakingProvider, address application)
- external
- view
- returns (uint96);
-
- /// @notice Returns staked amount of T, Keep and Nu for the specified
- /// staking provider.
- /// @dev All values are in T denomination
- function stakes(address stakingProvider)
- external
- view
- returns (
- uint96 tStake,
- uint96 keepInTStake,
- uint96 nuInTStake
- );
-
- /// @notice Returns start staking timestamp for T/NU stake.
- /// @dev This value is set at most once, and only when a stake is created
- /// with T or NU tokens. If a stake is created from a legacy KEEP
- /// stake, this value will remain as zero
- function getStartStakingTimestamp(address stakingProvider)
- external
- view
- returns (uint256);
-
- /// @notice Returns staked amount of NU for the specified provider
- function stakedNu(address stakingProvider) external view returns (uint256);
-
- /// @notice Gets the stake owner, the beneficiary and the authorizer
- /// for the specified provider address.
- /// @return owner Stake owner address.
- /// @return beneficiary Beneficiary address.
- /// @return authorizer Authorizer address.
- function rolesOf(address stakingProvider)
- external
- view
- returns (
- address owner,
- address payable beneficiary,
- address authorizer
- );
-
- /// @notice Returns length of application array
- function getApplicationsLength() external view returns (uint256);
-
- /// @notice Returns length of slashing queue
- function getSlashingQueueLength() external view returns (uint256);
-
- /// @notice Returns minimum possible stake for T, KEEP or NU in T denomination
- /// @dev For example, suppose the given provider has 10 T, 20 T worth
- /// of KEEP, and 30 T worth of NU all staked, and the maximum
- /// application authorization is 40 T, then `getMinStaked` for
- /// that provider returns:
- /// * 0 T if KEEP stake type specified i.e.
- /// min = 40 T max - (10 T + 30 T worth of NU) = 0 T
- /// * 10 T if NU stake type specified i.e.
- /// min = 40 T max - (10 T + 20 T worth of KEEP) = 10 T
- /// * 0 T if T stake type specified i.e.
- /// min = 40 T max - (20 T worth of KEEP + 30 T worth of NU) < 0 T
- /// In other words, the minimum stake amount for the specified
- /// stake type is the minimum amount of stake of the given type
- /// needed to satisfy the maximum application authorization given
- /// the staked amounts of the other stake types for that provider.
- function getMinStaked(address stakingProvider, StakeType stakeTypes)
- external
- view
- returns (uint96);
-
- /// @notice Returns available amount to authorize for the specified application
- function getAvailableToAuthorize(
- address stakingProvider,
- address application
- ) external view returns (uint96);
-}
diff --git a/contracts/xchain/PolygonRoot.sol b/contracts/xchain/PolygonRoot.sol
index 50ce0ba7..0bd6f1a6 100644
--- a/contracts/xchain/PolygonRoot.sol
+++ b/contracts/xchain/PolygonRoot.sol
@@ -2,29 +2,50 @@
pragma solidity ^0.8.0;
import "@fx-portal/contracts/tunnel/FxBaseRootTunnel.sol";
+import "../contracts/coordination/IUpdatableStakeInfo.sol";
-contract PolygonRoot is FxBaseRootTunnel {
+contract PolygonRoot is FxBaseRootTunnel, IUpdatableStakeInfo {
+
+ address immutable public source;
bytes public latestData;
- constructor(address _checkpointManager, address _fxRoot) FxBaseRootTunnel(_checkpointManager, _fxRoot) {}
+ constructor(
+ address _checkpointManager,
+ address _fxRoot,
+ address _source
+ )
+ 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");
+ _;
+ }
function _processMessageFromChild(bytes memory data) internal override {
latestData = data;
}
- function updateOperator(address stakingProvider, address operator) public {
- bytes memory message = abi.encodeWithSignature("updateOperator(address,address)", stakingProvider, operator);
+ 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) public {
- bytes memory message = abi.encodeWithSignature("updateAmount(address,uint96)", stakingProvider, amount);
+ function updateAmount(address stakingProvider, uint96 amount) external override onlySource {
+ bytes memory message = abi.encodeWithSelector(IUpdatableStakeInfo.updateAmount.selector, stakingProvider, amount);
_sendMessageToChild(message);
}
- function batchUpdate(bytes32[] calldata updateInfo) public {
- bytes memory message = abi.encodeWithSignature("batchUpdate(bytes32[])", updateInfo);
+ function batchUpdate(bytes32[] calldata updateInfo) external override onlySource {
+ bytes memory message = abi.encodeWithSelector(IUpdatableStakeInfo.batchUpdate.selector, updateInfo);
_sendMessageToChild(message);
}
}
\ No newline at end of file
diff --git a/scripts/deploy_staking_escrow.py b/scripts/deploy_staking_escrow.py
index 6a18e77d..ef9f3841 100644
--- a/scripts/deploy_staking_escrow.py
+++ b/scripts/deploy_staking_escrow.py
@@ -13,7 +13,7 @@ def main(account_id=None):
deployer = get_account(account_id)
deployments_config = DEPLOYMENTS_CONFIG
if CURRENT_NETWORK in LOCAL_BLOCKCHAIN_ENVIRONMENTS:
- nucypher_token, t_staking, _, work_lock, _ = deploy_mocks(deployer)
+ nucypher_token, t_staking, _, work_lock, _, _ = deploy_mocks(deployer)
else:
nucypher_token = deployments_config.get("nu_token")
t_staking = deployments_config.get("t_staking")
diff --git a/scripts/deploy_simple_pre.py b/scripts/deploy_taco_application.py
similarity index 65%
rename from scripts/deploy_simple_pre.py
rename to scripts/deploy_taco_application.py
index f1d32820..b21d911d 100644
--- a/scripts/deploy_simple_pre.py
+++ b/scripts/deploy_taco_application.py
@@ -14,15 +14,20 @@ def main(account_id=None):
deployments_config = DEPLOYMENTS_CONFIG
if CURRENT_NETWORK in LOCAL_BLOCKCHAIN_ENVIRONMENTS:
- _, _, t_staking, _, _ = deploy_mocks(deployer)
+ _, _, t_staking, _, _, t_token = deploy_mocks(deployer)
else:
t_staking = deployments_config.get("t_staking")
+ t_token = deployments_config.get("t_token")
- simple_pre = project.SimplePREApplication.deploy(
+ # TODO deploy proxy
+ taco_app = project.TACoApplication.deploy(
+ t_token,
t_staking,
deployments_config.get("pre_min_authorization"),
deployments_config.get("pre_min_operator_seconds"),
+ deployments_config.get("reward_duration"),
+ deployments_config.get("deauthorization_duration"),
sender=deployer,
publish=deployments_config.get("verify"),
)
- return simple_pre
+ return taco_app
diff --git a/scripts/utils.py b/scripts/utils.py
index 37c96784..ff54f1e5 100644
--- a/scripts/utils.py
+++ b/scripts/utils.py
@@ -1,4 +1,5 @@
from ape import accounts, config, networks, project
+from web3 import Web3
LOCAL_BLOCKCHAIN_ENVIRONMENTS = ["local"]
PRODUCTION_ENVIRONMENTS = ["mainnet", "polygon-main"]
@@ -11,12 +12,21 @@ def deploy_mocks(deployer):
corresponding contract addresses"""
nucypher_token = project.NuCypherToken.deploy(1_000_000_000, sender=deployer)
t_staking_for_escrow = project.ThresholdStakingForStakingEscrowMock.deploy(sender=deployer)
- t_staking_for_pre = project.ThresholdStakingForPREApplicationMock.deploy(sender=deployer)
+ t_staking_for_taco = project.ThresholdStakingForTACoApplicationMock.deploy(sender=deployer)
+ total_supply = Web3.to_wei(10_000_000_000, "ether")
+ t_token = project.TToken.deploy(total_supply, sender=deployer)
work_lock = project.WorkLockForStakingEscrowMock.deploy(nucypher_token, sender=deployer)
staking_escrow = project.StakingEscrow.deploy(
nucypher_token, work_lock, t_staking_for_escrow, sender=deployer
)
- return nucypher_token, t_staking_for_escrow, t_staking_for_pre, work_lock, staking_escrow
+ return (
+ nucypher_token,
+ t_staking_for_escrow,
+ t_staking_for_taco,
+ work_lock,
+ staking_escrow,
+ t_token,
+ )
def get_account(id):
diff --git a/tests/application/conftest.py b/tests/application/conftest.py
index bdc6b003..59810084 100644
--- a/tests/application/conftest.py
+++ b/tests/application/conftest.py
@@ -20,18 +20,11 @@
from ape import project
from web3 import Web3
-
MIN_AUTHORIZATION = Web3.to_wei(40_000, "ether")
MIN_OPERATOR_SECONDS = 24 * 60 * 60
+TOTAL_SUPPLY = Web3.to_wei(10_000_000_000, "ether")
-HASH_ALGORITHM_KECCAK256 = 0
-HASH_ALGORITHM_SHA256 = 1
-HASH_ALGORITHM_RIPEMD160 = 2
-HASH_ALGORITHM = HASH_ALGORITHM_SHA256
-BASE_PENALTY = 2
-PENALTY_HISTORY_COEFFICIENT = 0
-PERCENTAGE_PENALTY_COEFFICIENT = 100000
REWARD_DURATION = 60 * 60 * 24 * 7 # one week in seconds
DEAUTHORIZATION_DURATION = 60 * 60 * 24 * 60 # 60 days in seconds
TOTAL_SUPPLY = Web3.to_wei(1_000_000_000, "ether") # TODO NU(1_000_000_000, 'NU').to_units()
@@ -48,16 +41,60 @@ def token(project, accounts):
@pytest.fixture()
def threshold_staking(project, accounts):
- threshold_staking = accounts[0].deploy(project.ThresholdStakingForPREApplicationMock)
+ threshold_staking = accounts[0].deploy(project.ThresholdStakingForTACoApplicationMock)
return threshold_staking
+def encode_function_data(initializer=None, *args):
+ """Encodes the function call so we can work with an initializer.
+ Args:
+ initializer ([ape.Contract.ContractMethodHandler], optional):
+ The initializer function we want to call. Example: `box.store`.
+ Defaults to None.
+ args (Any, optional):
+ The arguments to pass to the initializer function
+ Returns:
+ [bytes]: Return the encoded bytes.
+ """
+ if not len(args):
+ args = b""
+
+ if initializer:
+ return initializer.encode_input(*args)
+
+ return b""
+
+
@pytest.fixture()
-def pre_application(project, accounts, threshold_staking):
- contract = accounts[0].deploy(
- project.SimplePREApplication, threshold_staking.address, MIN_AUTHORIZATION, MIN_OPERATOR_SECONDS
+def taco_application(project, creator, token, threshold_staking):
+ contract = creator.deploy(
+ project.TACoApplication,
+ token.address,
+ threshold_staking.address,
+ MIN_AUTHORIZATION,
+ MIN_OPERATOR_SECONDS,
+ REWARD_DURATION,
+ DEAUTHORIZATION_DURATION,
)
- threshold_staking.setApplication(contract.address, sender=accounts[0])
+ proxy_admin = DEPENDENCY.ProxyAdmin.deploy(sender=creator)
+ encoded_initializer_function = encode_function_data()
+ proxy = DEPENDENCY.TransparentUpgradeableProxy.deploy(
+ contract.address,
+ proxy_admin.address,
+ encoded_initializer_function,
+ sender=creator,
+ )
+ proxy_contract = project.TACoApplication.at(proxy.address)
+
+ threshold_staking.setApplication(proxy_contract.address, sender=creator)
+ proxy_contract.initialize(sender=creator)
+
+ return proxy_contract
+
+@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)
return contract
diff --git a/tests/application/test_authorization.py b/tests/application/test_authorization.py
new file mode 100644
index 00000000..52422696
--- /dev/null
+++ b/tests/application/test_authorization.py
@@ -0,0 +1,667 @@
+"""
+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
+from ape.utils import ZERO_ADDRESS
+from web3 import Web3
+
+OPERATOR_CONFIRMED_SLOT = 1
+AUTHORIZATION_SLOT = 3
+DEAUTHORIZING_SLOT = 4
+END_DEAUTHORIZATION_SLOT = 5
+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):
+ """
+ Tests for authorization method: authorizationIncreased
+ """
+
+ creator, staking_provider = accounts[0:2]
+ minimum_authorization = MIN_AUTHORIZATION
+ value = minimum_authorization
+
+ # Can't call `authorizationIncreased` directly
+ with ape.reverts():
+ taco_application.authorizationIncreased(staking_provider, 0, value, sender=creator)
+
+ # Staking provider and toAmount must be specified
+ with ape.reverts():
+ threshold_staking.authorizationIncreased(ZERO_ADDRESS, 0, value, sender=creator)
+
+ with ape.reverts():
+ threshold_staking.authorizationIncreased(staking_provider, 0, 0, sender=creator)
+
+ # Authorization must be greater than minimum
+ with ape.reverts():
+ threshold_staking.authorizationIncreased(staking_provider, 0, value - 1, sender=creator)
+
+ # First authorization
+ tx = threshold_staking.authorizationIncreased(staking_provider, 0, value, sender=creator)
+ 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 taco_application.isAuthorized(staking_provider)
+
+ # Check that all events are emitted
+ events = taco_application.AuthorizationIncreased.from_receipt(tx)
+ assert len(events) == 1
+ event = events[0]
+ assert event["stakingProvider"] == staking_provider
+ assert event["fromAmount"] == 0
+ assert event["toAmount"] == value
+
+ # Decrease and try to increase again
+ threshold_staking.involuntaryAuthorizationDecrease(
+ staking_provider, value, value // 2, sender=creator
+ )
+
+ # Resulting authorization must be greater than minimum
+ with ape.reverts():
+ threshold_staking.authorizationIncreased(
+ staking_provider, value // 2, value - 1, sender=creator
+ )
+
+ tx = threshold_staking.authorizationIncreased(
+ staking_provider, value // 2, value, sender=creator
+ )
+ 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 taco_application.isAuthorized(staking_provider)
+
+ events = taco_application.AuthorizationIncreased.from_receipt(tx)
+ assert len(events) == 1
+ event = events[0]
+ assert event["stakingProvider"] == staking_provider
+ assert event["fromAmount"] == value // 2
+ assert event["toAmount"] == value
+
+ # 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)
+
+ authorization = 2 * value + 1
+ tx = threshold_staking.authorizationIncreased(
+ staking_provider, value, authorization, sender=creator
+ )
+ assert (
+ taco_application.stakingProviderInfo(staking_provider)[AUTHORIZATION_SLOT] == authorization
+ )
+ assert taco_application.authorizedOverall() == authorization
+ assert taco_application.authorizedStake(staking_provider) == authorization
+ assert stake_info.authorizedStake(staking_provider) == authorization
+ assert taco_application.isAuthorized(staking_provider)
+
+ events = taco_application.AuthorizationIncreased.from_receipt(tx)
+ assert len(events) == 1
+ event = events[0]
+ assert event["stakingProvider"] == staking_provider
+ assert event["fromAmount"] == value
+ assert event["toAmount"] == authorization
+
+ # Emulate slash and desync by sending smaller fromAmount
+ tx = threshold_staking.authorizationIncreased(
+ staking_provider, value // 2, value, sender=creator
+ )
+ 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 taco_application.isAuthorized(staking_provider)
+
+ events = taco_application.AuthorizationIncreased.from_receipt(tx)
+ assert len(events) == 1
+ event = events[0]
+ assert event["stakingProvider"] == staking_provider
+ assert event["fromAmount"] == value // 2
+ assert event["toAmount"] == value
+
+ # Increase again without syncing with StakeInfo
+ taco_application.setUpdatableStakeInfo(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
+
+
+def test_involuntary_authorization_decrease(
+ accounts, threshold_staking, taco_application, stake_info
+):
+ """
+ Tests for authorization method: involuntaryAuthorizationDecrease
+ """
+
+ creator, staking_provider = accounts[0:2]
+ minimum_authorization = MIN_AUTHORIZATION
+ value = minimum_authorization
+
+ # Prepare staking providers
+ threshold_staking.authorizationIncreased(staking_provider, 0, value, sender=creator)
+
+ # Can't call `involuntaryAuthorizationDecrease` directly
+ with ape.reverts():
+ taco_application.involuntaryAuthorizationDecrease(
+ staking_provider, value, 0, sender=creator
+ )
+
+ authorization = value // 2
+ tx = threshold_staking.involuntaryAuthorizationDecrease(
+ staking_provider, value, value // 2, sender=creator
+ )
+ assert (
+ taco_application.stakingProviderInfo(staking_provider)[AUTHORIZATION_SLOT] == authorization
+ )
+ assert taco_application.authorizedOverall() == 0
+ assert taco_application.authorizedStake(staking_provider) == authorization
+ assert stake_info.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
+
+ events = taco_application.AuthorizationInvoluntaryDecreased.from_receipt(tx)
+ assert len(events) == 1
+ event = events[0]
+ assert event["stakingProvider"] == staking_provider
+ assert event["fromAmount"] == value
+ assert event["toAmount"] == authorization
+
+ # Prepare request to decrease before involuntary decrease
+ threshold_staking.authorizationDecreaseRequested(
+ staking_provider, value // 2, 0, sender=creator
+ )
+ authorization = value // 4
+ tx = threshold_staking.involuntaryAuthorizationDecrease(
+ staking_provider, value // 2, authorization, sender=creator
+ )
+ assert (
+ taco_application.stakingProviderInfo(staking_provider)[AUTHORIZATION_SLOT] == authorization
+ )
+ assert (
+ taco_application.stakingProviderInfo(staking_provider)[DEAUTHORIZING_SLOT] == authorization
+ )
+ assert taco_application.authorizedOverall() == 0
+ assert taco_application.authorizedStake(staking_provider) == authorization
+ assert stake_info.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
+
+ events = taco_application.AuthorizationInvoluntaryDecreased.from_receipt(tx)
+ assert len(events) == 1
+ event = events[0]
+ assert event["stakingProvider"] == staking_provider
+ assert event["fromAmount"] == value // 2
+ assert event["toAmount"] == authorization
+
+ # Confirm operator address and decrease again
+ taco_application.bondOperator(staking_provider, staking_provider, sender=staking_provider)
+ taco_application.confirmOperatorAddress(sender=staking_provider)
+
+ authorization = value // 8
+ tx = threshold_staking.involuntaryAuthorizationDecrease(
+ staking_provider, value // 4, authorization, sender=creator
+ )
+ assert (
+ taco_application.stakingProviderInfo(staking_provider)[AUTHORIZATION_SLOT] == authorization
+ )
+ assert (
+ taco_application.stakingProviderInfo(staking_provider)[DEAUTHORIZING_SLOT] == authorization
+ )
+ assert taco_application.authorizedOverall() == authorization
+ assert taco_application.authorizedStake(staking_provider) == authorization
+ assert stake_info.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
+
+ events = taco_application.AuthorizationInvoluntaryDecreased.from_receipt(tx)
+ assert len(events) == 1
+ event = events[0]
+ assert event["stakingProvider"] == staking_provider
+ assert event["fromAmount"] == value // 4
+ assert event["toAmount"] == authorization
+
+ # Decrease everything
+ tx = threshold_staking.involuntaryAuthorizationDecrease(
+ staking_provider, authorization, 0, sender=creator
+ )
+ assert taco_application.stakingProviderInfo(staking_provider)[AUTHORIZATION_SLOT] == 0
+ 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 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
+
+ events = taco_application.AuthorizationInvoluntaryDecreased.from_receipt(tx)
+ assert len(events) == 1
+ event = events[0]
+ assert event["stakingProvider"] == staking_provider
+ assert event["fromAmount"] == authorization
+ assert event["toAmount"] == 0
+
+ # 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)
+
+ authorization = value // 2
+ tx = threshold_staking.involuntaryAuthorizationDecrease(
+ staking_provider, value, value // 2, sender=creator
+ )
+ assert (
+ taco_application.stakingProviderInfo(staking_provider)[AUTHORIZATION_SLOT] == authorization
+ )
+ assert taco_application.authorizedOverall() == authorization
+ assert taco_application.authorizedStake(staking_provider) == authorization
+ assert stake_info.authorizedStake(staking_provider) == authorization
+
+ events = taco_application.AuthorizationInvoluntaryDecreased.from_receipt(tx)
+ assert len(events) == 1
+ event = events[0]
+ assert event["stakingProvider"] == staking_provider
+ assert event["fromAmount"] == value
+ assert event["toAmount"] == authorization
+
+ # Decrease everything again without syncing with StakeInfo
+ taco_application.setUpdatableStakeInfo(ZERO_ADDRESS, sender=creator)
+ threshold_staking.involuntaryAuthorizationDecrease(
+ staking_provider, authorization, 0, sender=creator
+ )
+ assert stake_info.authorizedStake(staking_provider) == authorization
+ assert taco_application.getOperatorFromStakingProvider(staking_provider) == ZERO_ADDRESS
+ assert stake_info.stakes(staking_provider)[STAKE_INFO_OPERATOR_SLOT] == staking_provider
+
+
+def test_authorization_decrease_request(
+ accounts, threshold_staking, taco_application, stake_info, chain
+):
+ """
+ Tests for authorization method: authorizationDecreaseRequested
+ """
+
+ creator, staking_provider = accounts[0:2]
+ deauthorization_duration = DEAUTHORIZATION_DURATION
+ minimum_authorization = MIN_AUTHORIZATION
+ value = 2 * minimum_authorization + 1
+
+ # Prepare staking providers
+ threshold_staking.authorizationIncreased(staking_provider, 0, value, sender=creator)
+
+ # Can't call `involuntaryAuthorizationDecrease` directly
+ with ape.reverts():
+ taco_application.authorizationDecreaseRequested(staking_provider, value, 0, sender=creator)
+
+ # Can't increase amount using request
+ with ape.reverts():
+ threshold_staking.authorizationDecreaseRequested(
+ staking_provider, value, value + 1, sender=creator
+ )
+
+ # Resulting amount must be greater than minimum or 0
+ with ape.reverts():
+ threshold_staking.authorizationDecreaseRequested(staking_provider, value, 1, sender=creator)
+
+ # Request of partial decrease
+ tx = threshold_staking.authorizationDecreaseRequested(
+ staking_provider, value, minimum_authorization, sender=creator
+ )
+ timestamp = chain.pending_timestamp - 1
+ assert taco_application.stakingProviderInfo(staking_provider)[AUTHORIZATION_SLOT] == value
+ assert (
+ taco_application.stakingProviderInfo(staking_provider)[DEAUTHORIZING_SLOT]
+ == minimum_authorization + 1
+ )
+ end_deauthorization = timestamp + deauthorization_duration
+ assert (
+ taco_application.stakingProviderInfo(staking_provider)[END_DEAUTHORIZATION_SLOT]
+ == end_deauthorization
+ )
+ assert taco_application.authorizedOverall() == 0
+ assert taco_application.authorizedStake(staking_provider) == value
+ assert stake_info.authorizedStake(staking_provider) == minimum_authorization
+ assert taco_application.isAuthorized(staking_provider)
+
+ events = taco_application.AuthorizationDecreaseRequested.from_receipt(tx)
+ assert len(events) == 1
+ event = events[0]
+ assert event["stakingProvider"] == staking_provider
+ assert event["fromAmount"] == value
+ assert event["toAmount"] == minimum_authorization
+
+ # Confirm operator address and request full decrease
+ taco_application.bondOperator(staking_provider, staking_provider, sender=staking_provider)
+ taco_application.confirmOperatorAddress(sender=staking_provider)
+
+ tx = threshold_staking.authorizationDecreaseRequested(
+ staking_provider, value, 0, sender=creator
+ )
+ timestamp = chain.pending_timestamp - 1
+ assert taco_application.stakingProviderInfo(staking_provider)[AUTHORIZATION_SLOT] == value
+ assert taco_application.stakingProviderInfo(staking_provider)[DEAUTHORIZING_SLOT] == value
+ end_deauthorization = timestamp + deauthorization_duration
+ assert (
+ taco_application.stakingProviderInfo(staking_provider)[END_DEAUTHORIZATION_SLOT]
+ == end_deauthorization
+ )
+ assert taco_application.authorizedOverall() == value
+ assert taco_application.authorizedStake(staking_provider) == value
+ assert stake_info.authorizedStake(staking_provider) == 0
+ assert taco_application.isAuthorized(staking_provider)
+
+ events = taco_application.AuthorizationDecreaseRequested.from_receipt(tx)
+ assert len(events) == 1
+ event = events[0]
+ assert event["stakingProvider"] == staking_provider
+ assert event["fromAmount"] == value
+ assert event["toAmount"] == 0
+
+ # Emulate slash and desync by sending smaller fromAmount
+ tx = threshold_staking.authorizationDecreaseRequested(
+ staking_provider, value // 2, 0, sender=creator
+ )
+ assert taco_application.stakingProviderInfo(staking_provider)[AUTHORIZATION_SLOT] == value // 2
+ 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
+
+ events = taco_application.AuthorizationDecreaseRequested.from_receipt(tx)
+ assert len(events) == 1
+ event = events[0]
+ assert event["stakingProvider"] == staking_provider
+ assert event["fromAmount"] == value // 2
+ assert event["toAmount"] == 0
+
+ # Request decrease without syncing with StakeInfo
+ taco_application.setUpdatableStakeInfo(ZERO_ADDRESS, sender=creator)
+ threshold_staking.authorizationDecreaseRequested(
+ staking_provider, value // 2, value // 2, sender=creator
+ )
+ assert stake_info.authorizedStake(staking_provider) == 0
+
+
+def test_finish_authorization_decrease(
+ accounts, threshold_staking, taco_application, stake_info, chain
+):
+ """
+ Tests for authorization method: finishAuthorizationDecrease
+ """
+
+ creator, staking_provider = accounts[0:2]
+ deauthorization_duration = DEAUTHORIZATION_DURATION
+ minimum_authorization = MIN_AUTHORIZATION
+ value = 3 * minimum_authorization
+
+ # Prepare staking providers
+ threshold_staking.authorizationIncreased(staking_provider, 0, value, sender=creator)
+
+ # Can't approve decrease without request
+ with ape.reverts():
+ taco_application.finishAuthorizationDecrease(staking_provider, sender=creator)
+
+ new_value = 2 * minimum_authorization
+ threshold_staking.authorizationDecreaseRequested(
+ staking_provider, value, new_value, sender=creator
+ )
+
+ # Can't approve decrease before end timestamp
+ with ape.reverts():
+ taco_application.finishAuthorizationDecrease(staking_provider, sender=creator)
+
+ # Wait some time
+ chain.pending_timestamp += deauthorization_duration
+ tx = taco_application.finishAuthorizationDecrease(staking_provider, sender=creator)
+ assert taco_application.stakingProviderInfo(staking_provider)[AUTHORIZATION_SLOT] == new_value
+ assert taco_application.stakingProviderInfo(staking_provider)[DEAUTHORIZING_SLOT] == 0
+ 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 taco_application.isAuthorized(staking_provider)
+ assert (
+ threshold_staking.authorizedStake(staking_provider, taco_application.address) == new_value
+ )
+
+ events = taco_application.AuthorizationDecreaseApproved.from_receipt(tx)
+ assert len(events) == 1
+ event = events[0]
+ assert event["stakingProvider"] == staking_provider
+ assert event["fromAmount"] == value
+ assert event["toAmount"] == new_value
+
+ # 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)
+ threshold_staking.authorizationDecreaseRequested(
+ staking_provider, value, minimum_authorization, sender=creator
+ )
+
+ new_value = minimum_authorization // 2
+ threshold_staking.setDecreaseRequest(staking_provider, new_value, sender=creator)
+ chain.pending_timestamp += deauthorization_duration
+ tx = taco_application.finishAuthorizationDecrease(staking_provider, sender=creator)
+
+ assert taco_application.stakingProviderInfo(staking_provider)[AUTHORIZATION_SLOT] == new_value
+ assert taco_application.stakingProviderInfo(staking_provider)[DEAUTHORIZING_SLOT] == 0
+ assert taco_application.stakingProviderInfo(staking_provider)[END_DEAUTHORIZATION_SLOT] == 0
+ assert taco_application.getOperatorFromStakingProvider(staking_provider) == staking_provider
+ 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 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
+
+ events = taco_application.AuthorizationDecreaseApproved.from_receipt(tx)
+ assert len(events) == 1
+ event = events[0]
+ assert event["stakingProvider"] == staking_provider
+ assert event["fromAmount"] == value
+ assert event["toAmount"] == new_value
+
+ # Decrease everything
+ value = new_value
+ threshold_staking.authorizationDecreaseRequested(staking_provider, value, 0, sender=creator)
+ chain.pending_timestamp += deauthorization_duration
+ tx = taco_application.finishAuthorizationDecrease(staking_provider, sender=creator)
+
+ assert taco_application.stakingProviderInfo(staking_provider)[AUTHORIZATION_SLOT] == 0
+ assert taco_application.stakingProviderInfo(staking_provider)[DEAUTHORIZING_SLOT] == 0
+ assert taco_application.stakingProviderInfo(staking_provider)[END_DEAUTHORIZATION_SLOT] == 0
+ assert taco_application.getOperatorFromStakingProvider(staking_provider) == ZERO_ADDRESS
+ 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 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
+
+ events = taco_application.AuthorizationDecreaseApproved.from_receipt(tx)
+ assert len(events) == 1
+ event = events[0]
+ assert event["stakingProvider"] == staking_provider
+ assert event["fromAmount"] == value
+ assert event["toAmount"] == 0
+
+ # Decrease everything again without syncing with StakeInfo
+ 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)
+ threshold_staking.authorizationDecreaseRequested(
+ staking_provider, 2 * value, value, sender=creator
+ )
+ chain.pending_timestamp += deauthorization_duration
+ taco_application.setUpdatableStakeInfo(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 taco_application.getOperatorFromStakingProvider(staking_provider) == ZERO_ADDRESS
+ assert stake_info.stakes(staking_provider)[STAKE_INFO_OPERATOR_SLOT] == staking_provider
+
+
+def test_resync(accounts, threshold_staking, taco_application, stake_info):
+ """
+ Tests for authorization method: resynchronizeAuthorization
+ """
+
+ creator, staking_provider = accounts[0:2]
+ minimum_authorization = MIN_AUTHORIZATION
+ value = 3 * minimum_authorization
+
+ # Nothing sync for not staking provider
+ with ape.reverts():
+ taco_application.resynchronizeAuthorization(staking_provider, sender=creator)
+
+ # Prepare staking providers
+ threshold_staking.authorizationIncreased(staking_provider, 0, value, sender=creator)
+
+ # Nothing to resync
+ with ape.reverts():
+ taco_application.resynchronizeAuthorization(staking_provider, sender=creator)
+
+ # Change authorized amount and resync
+ new_value = 2 * minimum_authorization
+ threshold_staking.setAuthorized(staking_provider, new_value, sender=creator)
+ tx = taco_application.resynchronizeAuthorization(staking_provider, sender=creator)
+ 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 taco_application.isAuthorized(staking_provider)
+
+ events = taco_application.AuthorizationReSynchronized.from_receipt(tx)
+ assert len(events) == 1
+ event = events[0]
+ assert event["stakingProvider"] == staking_provider
+ assert event["fromAmount"] == value
+ assert event["toAmount"] == new_value
+
+ # 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)
+
+ new_value = minimum_authorization
+ threshold_staking.setAuthorized(staking_provider, new_value, sender=creator)
+ tx = taco_application.resynchronizeAuthorization(staking_provider, sender=creator)
+ assert taco_application.stakingProviderInfo(staking_provider)[AUTHORIZATION_SLOT] == new_value
+ assert taco_application.stakingProviderInfo(staking_provider)[DEAUTHORIZING_SLOT] == 0
+ assert taco_application.authorizedOverall() == new_value
+ assert taco_application.getOperatorFromStakingProvider(staking_provider) == staking_provider
+ 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 taco_application.isAuthorized(staking_provider)
+ assert taco_application.isOperatorConfirmed(staking_provider)
+ assert stake_info.stakes(staking_provider)[STAKE_INFO_OPERATOR_SLOT] == staking_provider
+
+ events = taco_application.AuthorizationReSynchronized.from_receipt(tx)
+ assert len(events) == 1
+ event = events[0]
+ assert event["stakingProvider"] == staking_provider
+ assert event["fromAmount"] == value
+ assert event["toAmount"] == new_value
+
+ # Request decrease and change authorized amount again
+ value = new_value
+ threshold_staking.authorizationDecreaseRequested(staking_provider, value, 0, sender=creator)
+ new_value = minimum_authorization // 2
+
+ threshold_staking.setAuthorized(staking_provider, new_value, sender=creator)
+ tx = taco_application.resynchronizeAuthorization(staking_provider, sender=creator)
+ assert taco_application.stakingProviderInfo(staking_provider)[AUTHORIZATION_SLOT] == new_value
+ assert taco_application.stakingProviderInfo(staking_provider)[DEAUTHORIZING_SLOT] == new_value
+ assert taco_application.authorizedOverall() == new_value
+ assert taco_application.getOperatorFromStakingProvider(staking_provider) == staking_provider
+ 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 taco_application.isAuthorized(staking_provider)
+ assert taco_application.isOperatorConfirmed(staking_provider)
+ assert stake_info.stakes(staking_provider)[STAKE_INFO_OPERATOR_SLOT] == staking_provider
+
+ events = taco_application.AuthorizationReSynchronized.from_receipt(tx)
+ assert len(events) == 1
+ event = events[0]
+ assert event["stakingProvider"] == staking_provider
+ assert event["fromAmount"] == value
+ assert event["toAmount"] == new_value
+
+ # Set authorized amount to zero and resync again
+ value = new_value
+ threshold_staking.setAuthorized(staking_provider, 0, sender=creator)
+ tx = taco_application.resynchronizeAuthorization(staking_provider, sender=creator)
+ assert taco_application.stakingProviderInfo(staking_provider)[AUTHORIZATION_SLOT] == 0
+ assert taco_application.stakingProviderInfo(staking_provider)[DEAUTHORIZING_SLOT] == 0
+ assert taco_application.authorizedOverall() == 0
+ assert taco_application.getOperatorFromStakingProvider(staking_provider) == ZERO_ADDRESS
+ 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 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
+
+ events = taco_application.AuthorizationReSynchronized.from_receipt(tx)
+ assert len(events) == 1
+ event = events[0]
+ assert event["stakingProvider"] == staking_provider
+ assert event["fromAmount"] == value
+ assert event["toAmount"] == 0
+
+ # Resync again without syncing with StakeInfo
+ 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)
+ threshold_staking.setAuthorized(staking_provider, 0, sender=creator)
+ taco_application.setUpdatableStakeInfo(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 taco_application.getOperatorFromStakingProvider(staking_provider) == ZERO_ADDRESS
+ assert stake_info.stakes(staking_provider)[STAKE_INFO_OPERATOR_SLOT] == staking_provider
diff --git a/tests/application/test_operator.py b/tests/application/test_operator.py
index c6fcc511..7e3affa4 100644
--- a/tests/application/test_operator.py
+++ b/tests/application/test_operator.py
@@ -23,8 +23,10 @@
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, pre_application, chain):
+
+def test_bond_operator(accounts, threshold_staking, taco_application, stake_info, chain):
(
creator,
staking_provider_1,
@@ -41,79 +43,83 @@ def test_bond_operator(accounts, threshold_staking, pre_application, chain):
min_authorization = MIN_AUTHORIZATION
min_operator_seconds = MIN_OPERATOR_SECONDS
- # Prepare staking providers: two with intermediary contract and two just a staking provider
+ # Prepare staking providers
threshold_staking.setRoles(staking_provider_1, sender=creator)
- threshold_staking.setStakes(staking_provider_1, min_authorization, 0, 0, sender=creator)
- threshold_staking.setRoles(staking_provider_2, sender=creator)
- threshold_staking.setStakes(
- staking_provider_2,
- min_authorization // 3,
- min_authorization // 3,
- min_authorization // 3 - 1,
- sender=creator,
+ threshold_staking.authorizationIncreased(
+ staking_provider_1, 0, min_authorization, sender=creator
)
+ threshold_staking.setRoles(staking_provider_2, sender=creator)
threshold_staking.setRoles(staking_provider_3, owner3, beneficiary, authorizer, sender=creator)
- threshold_staking.setStakes(staking_provider_3, 0, min_authorization, 0, sender=creator)
+ threshold_staking.authorizationIncreased(
+ staking_provider_3, 0, min_authorization, sender=creator
+ )
threshold_staking.setRoles(staking_provider_4, sender=creator)
- threshold_staking.setStakes(staking_provider_4, 0, 0, min_authorization, sender=creator)
+ threshold_staking.authorizationIncreased(
+ staking_provider_4, 0, min_authorization, sender=creator
+ )
- assert pre_application.getOperatorFromStakingProvider(staking_provider_1) == ZERO_ADDRESS
- assert pre_application.stakingProviderFromOperator(staking_provider_1) == ZERO_ADDRESS
- assert pre_application.getOperatorFromStakingProvider(staking_provider_2) == ZERO_ADDRESS
- assert pre_application.stakingProviderFromOperator(staking_provider_2) == ZERO_ADDRESS
- assert pre_application.getOperatorFromStakingProvider(staking_provider_3) == ZERO_ADDRESS
- assert pre_application.stakingProviderFromOperator(staking_provider_3) == ZERO_ADDRESS
- assert pre_application.getOperatorFromStakingProvider(staking_provider_4) == ZERO_ADDRESS
- assert pre_application.stakingProviderFromOperator(staking_provider_4) == ZERO_ADDRESS
+ assert taco_application.getOperatorFromStakingProvider(staking_provider_1) == ZERO_ADDRESS
+ assert taco_application.stakingProviderFromOperator(staking_provider_1) == ZERO_ADDRESS
+ assert taco_application.getOperatorFromStakingProvider(staking_provider_2) == ZERO_ADDRESS
+ assert taco_application.stakingProviderFromOperator(staking_provider_2) == ZERO_ADDRESS
+ assert taco_application.getOperatorFromStakingProvider(staking_provider_3) == ZERO_ADDRESS
+ assert taco_application.stakingProviderFromOperator(staking_provider_3) == ZERO_ADDRESS
+ assert taco_application.getOperatorFromStakingProvider(staking_provider_4) == ZERO_ADDRESS
+ 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():
- pre_application.confirmOperatorAddress(sender=staking_provider_1)
+ taco_application.confirmOperatorAddress(sender=staking_provider_1)
# Staking provider can't bond another staking provider as operator
with ape.reverts():
- pre_application.bondOperator(
+ taco_application.bondOperator(
staking_provider_1, staking_provider_2, sender=staking_provider_1
)
# Staking provider can't bond operator if stake is less than minimum
with ape.reverts():
- pre_application.bondOperator(staking_provider_2, operator1, sender=staking_provider_2)
+ taco_application.bondOperator(staking_provider_2, operator1, sender=staking_provider_2)
# Only staking provider or stake owner can bond operator
with ape.reverts():
- pre_application.bondOperator(staking_provider_3, operator1, sender=beneficiary)
+ taco_application.bondOperator(staking_provider_3, operator1, sender=beneficiary)
with ape.reverts():
- pre_application.bondOperator(staking_provider_3, operator1, sender=authorizer)
+ taco_application.bondOperator(staking_provider_3, operator1, sender=authorizer)
# Staking provider bonds operator and now operator can make a confirmation
- tx = pre_application.bondOperator(staking_provider_3, operator1, sender=owner3)
+ tx = taco_application.bondOperator(staking_provider_3, operator1, sender=owner3)
timestamp = tx.timestamp
- assert pre_application.getOperatorFromStakingProvider(staking_provider_3) == operator1
- assert pre_application.stakingProviderFromOperator(operator1) == staking_provider_3
- assert not pre_application.stakingProviderInfo(staking_provider_3)[CONFIRMATION_SLOT]
- assert not pre_application.isOperatorConfirmed(operator1)
- assert pre_application.getStakingProvidersLength() == 1
- assert pre_application.stakingProviders(0) == staking_provider_3
+ assert taco_application.getOperatorFromStakingProvider(staking_provider_3) == operator1
+ assert taco_application.stakingProviderFromOperator(operator1) == staking_provider_3
+ assert not taco_application.stakingProviderInfo(staking_provider_3)[CONFIRMATION_SLOT]
+ 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
# No active stakingProviders before confirmation
- all_locked, staking_providers = pre_application.getActiveStakingProviders(0, 0)
+ all_locked, staking_providers = taco_application.getActiveStakingProviders(0, 0)
assert all_locked == 0
assert len(staking_providers) == 0
- pre_application.confirmOperatorAddress(sender=operator1)
- assert pre_application.stakingProviderInfo(staking_provider_3)[CONFIRMATION_SLOT]
- assert pre_application.isOperatorConfirmed(operator1)
+ taco_application.confirmOperatorAddress(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
- events = pre_application.OperatorBonded.from_receipt(tx)
+ events = taco_application.OperatorBonded.from_receipt(tx)
assert len(events) == 1
event = events[0]
assert event["stakingProvider"] == staking_provider_3
assert event["operator"] == operator1
+ assert event["previousOperator"] == ZERO_ADDRESS
assert event["startTimestamp"] == timestamp
# After confirmation operator is becoming active
- all_locked, staking_providers = pre_application.getActiveStakingProviders(0, 0)
+ all_locked, staking_providers = taco_application.getActiveStakingProviders(0, 0)
assert all_locked == min_authorization
assert len(staking_providers) == 1
assert to_checksum_address(staking_providers[0][0]) == staking_provider_3
@@ -121,208 +127,234 @@ def test_bond_operator(accounts, threshold_staking, pre_application, chain):
# Operator is in use so other stakingProviders can't bond him
with ape.reverts():
- pre_application.bondOperator(staking_provider_4, operator1, sender=staking_provider_4)
+ taco_application.bondOperator(staking_provider_4, operator1, sender=staking_provider_4)
- # # Operator can't be a staking provider
- # threshold_staking.setRoles(operator1, sender=creator)
- # threshold_staking.setStakes(operator1, min_authorization, 0, 0, sender=creator)
- # with ape.reverts():
- # threshold_staking.increaseAuthorization(
- # operator1, min_authorization, pre_application.address, {'from': operator1})
+ # Operator can't be a staking provider
+ threshold_staking.setRoles(operator1, sender=creator)
+ with ape.reverts():
+ threshold_staking.authorizationIncreased(operator1, 0, min_authorization, sender=operator1)
+ threshold_staking.setRoles(operator1, ZERO_ADDRESS, ZERO_ADDRESS, ZERO_ADDRESS, sender=creator)
# Can't bond operator twice too soon
with ape.reverts():
- pre_application.bondOperator(staking_provider_3, operator2, sender=staking_provider_3)
+ taco_application.bondOperator(staking_provider_3, operator2, sender=staking_provider_3)
# She can't unbond her operator too, until enough time has passed
with ape.reverts():
- pre_application.bondOperator(staking_provider_3, ZERO_ADDRESS, sender=staking_provider_3)
+ taco_application.bondOperator(staking_provider_3, ZERO_ADDRESS, sender=staking_provider_3)
# Let's advance some time and unbond the operator
chain.pending_timestamp += min_operator_seconds
- tx = pre_application.bondOperator(staking_provider_3, ZERO_ADDRESS, sender=staking_provider_3)
+ tx = taco_application.bondOperator(staking_provider_3, ZERO_ADDRESS, sender=staking_provider_3)
timestamp = tx.timestamp
- assert pre_application.getOperatorFromStakingProvider(staking_provider_3) == ZERO_ADDRESS
- assert pre_application.stakingProviderFromOperator(staking_provider_3) == ZERO_ADDRESS
- assert pre_application.stakingProviderFromOperator(operator1) == ZERO_ADDRESS
- assert not pre_application.stakingProviderInfo(staking_provider_3)[CONFIRMATION_SLOT]
- assert not pre_application.isOperatorConfirmed(operator1)
- assert pre_application.getStakingProvidersLength() == 1
- assert pre_application.stakingProviders(0) == staking_provider_3
+ assert taco_application.getOperatorFromStakingProvider(staking_provider_3) == ZERO_ADDRESS
+ assert taco_application.stakingProviderFromOperator(staking_provider_3) == ZERO_ADDRESS
+ assert taco_application.stakingProviderFromOperator(operator1) == ZERO_ADDRESS
+ assert not taco_application.stakingProviderInfo(staking_provider_3)[CONFIRMATION_SLOT]
+ 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
# Resetting operator removes from active list before next confirmation
- all_locked, staking_providers = pre_application.getActiveStakingProviders(0, 0)
+ all_locked, staking_providers = taco_application.getActiveStakingProviders(0, 0)
assert all_locked == 0
assert len(staking_providers) == 0
- events = pre_application.OperatorBonded.from_receipt(tx)
+ events = taco_application.OperatorBonded.from_receipt(tx)
assert len(events) == 1
event = events[0]
assert event["stakingProvider"] == staking_provider_3
# Now the operator has been unbonded ...
assert event["operator"] == ZERO_ADDRESS
+ assert event["previousOperator"] == operator1
# ... with a new starting period.
assert event["startTimestamp"] == timestamp
# The staking provider can bond now a new operator, without waiting additional time.
- tx = pre_application.bondOperator(staking_provider_3, operator2, sender=staking_provider_3)
+ tx = taco_application.bondOperator(staking_provider_3, operator2, sender=staking_provider_3)
timestamp = tx.timestamp
- assert pre_application.getOperatorFromStakingProvider(staking_provider_3) == operator2
- assert pre_application.stakingProviderFromOperator(operator2) == staking_provider_3
- assert not pre_application.stakingProviderInfo(staking_provider_3)[CONFIRMATION_SLOT]
- assert not pre_application.isOperatorConfirmed(operator2)
- assert pre_application.getStakingProvidersLength() == 1
- assert pre_application.stakingProviders(0) == staking_provider_3
-
- events = pre_application.OperatorBonded.from_receipt(tx)
+ assert taco_application.getOperatorFromStakingProvider(staking_provider_3) == operator2
+ assert taco_application.stakingProviderFromOperator(operator2) == staking_provider_3
+ assert not taco_application.stakingProviderInfo(staking_provider_3)[CONFIRMATION_SLOT]
+ 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
+
+ events = taco_application.OperatorBonded.from_receipt(tx)
assert len(events) == 1
event = events[0]
assert event["stakingProvider"] == staking_provider_3
assert event["operator"] == operator2
+ assert event["previousOperator"] == ZERO_ADDRESS
assert event["startTimestamp"] == timestamp
# Now the previous operator can no longer make a confirmation
with ape.reverts():
- pre_application.confirmOperatorAddress(sender=operator1)
+ taco_application.confirmOperatorAddress(sender=operator1)
# Only new operator can
- pre_application.confirmOperatorAddress(sender=operator2)
- assert not pre_application.isOperatorConfirmed(operator1)
- assert pre_application.isOperatorConfirmed(operator2)
- assert pre_application.stakingProviderInfo(staking_provider_3)[CONFIRMATION_SLOT]
-
- # Another staker can bond a free operator
- tx = pre_application.bondOperator(staking_provider_4, operator1, sender=staking_provider_4)
+ taco_application.confirmOperatorAddress(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
+
+ # Another staking provider can bond a free operator
+ assert taco_application.authorizedOverall() == min_authorization
+ tx = taco_application.bondOperator(staking_provider_4, operator1, sender=staking_provider_4)
timestamp = tx.timestamp
- assert pre_application.getOperatorFromStakingProvider(staking_provider_4) == operator1
- assert pre_application.stakingProviderFromOperator(operator1) == staking_provider_4
- assert not pre_application.isOperatorConfirmed(operator1)
- assert not pre_application.stakingProviderInfo(staking_provider_4)[CONFIRMATION_SLOT]
- assert pre_application.getStakingProvidersLength() == 2
- assert pre_application.stakingProviders(1) == staking_provider_4
-
- events = pre_application.OperatorBonded.from_receipt(tx)
+ assert taco_application.getOperatorFromStakingProvider(staking_provider_4) == operator1
+ assert taco_application.stakingProviderFromOperator(operator1) == staking_provider_4
+ assert not taco_application.isOperatorConfirmed(operator1)
+ assert not taco_application.stakingProviderInfo(staking_provider_4)[CONFIRMATION_SLOT]
+ 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
+
+ events = taco_application.OperatorBonded.from_receipt(tx)
assert len(events) == 1
event = events[0]
assert event["stakingProvider"] == staking_provider_4
assert event["operator"] == operator1
+ assert event["previousOperator"] == ZERO_ADDRESS
assert event["startTimestamp"] == timestamp
- # # The first operator still can't be a staking provider
- # threshold_staking.setRoles(operator1, sender=creator)
- # threshold_staking.setStakes(operator1, min_authorization, 0, 0, sender=creator)
- # with ape.reverts():
- # threshold_staking.increaseAuthorization(
- # operator1, min_authorization, pre_application.address, {'from': operator1})
+ # The first operator still can't be a staking provider
+ threshold_staking.setRoles(operator1, sender=creator)
+ with ape.reverts():
+ threshold_staking.authorizationIncreased(operator1, 0, min_authorization, sender=operator1)
+ threshold_staking.setRoles(operator1, ZERO_ADDRESS, ZERO_ADDRESS, ZERO_ADDRESS, sender=creator)
# Bond operator again
- pre_application.confirmOperatorAddress(sender=operator1)
- assert pre_application.isOperatorConfirmed(operator1)
- assert pre_application.stakingProviderInfo(staking_provider_4)[CONFIRMATION_SLOT]
+ taco_application.confirmOperatorAddress(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
+
chain.pending_timestamp += min_operator_seconds
- tx = pre_application.bondOperator(staking_provider_4, operator3, sender=staking_provider_4)
+ tx = taco_application.bondOperator(staking_provider_4, operator3, sender=staking_provider_4)
timestamp = tx.timestamp
- assert pre_application.getOperatorFromStakingProvider(staking_provider_4) == operator3
- assert pre_application.stakingProviderFromOperator(operator3) == staking_provider_4
- assert pre_application.stakingProviderFromOperator(operator1) == ZERO_ADDRESS
- assert not pre_application.isOperatorConfirmed(operator3)
- assert not pre_application.isOperatorConfirmed(operator1)
- assert not pre_application.stakingProviderInfo(staking_provider_4)[CONFIRMATION_SLOT]
- assert pre_application.getStakingProvidersLength() == 2
- assert pre_application.stakingProviders(1) == staking_provider_4
+ assert taco_application.getOperatorFromStakingProvider(staking_provider_4) == operator3
+ assert taco_application.stakingProviderFromOperator(operator3) == staking_provider_4
+ assert taco_application.stakingProviderFromOperator(operator1) == ZERO_ADDRESS
+ assert not taco_application.isOperatorConfirmed(operator3)
+ assert not taco_application.isOperatorConfirmed(operator1)
+ assert not taco_application.stakingProviderInfo(staking_provider_4)[CONFIRMATION_SLOT]
+ 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
# Resetting operator removes from active list before next confirmation
- all_locked, staking_providers = pre_application.getActiveStakingProviders(1, 0)
+ all_locked, staking_providers = taco_application.getActiveStakingProviders(1, 0)
assert all_locked == 0
assert len(staking_providers) == 0
- events = pre_application.OperatorBonded.from_receipt(tx)
+ events = taco_application.OperatorBonded.from_receipt(tx)
assert len(events) == 1
event = events[0]
assert event["stakingProvider"] == staking_provider_4
assert event["operator"] == operator3
+ assert event["previousOperator"] == operator1
assert event["startTimestamp"] == timestamp
- # The first operator is free and can deposit tokens and become a staker
+ # The first operator is free and can deposit tokens and become a staking provider
threshold_staking.setRoles(operator1, sender=creator)
- threshold_staking.setStakes(
- operator1,
- min_authorization // 3,
- min_authorization // 3,
- min_authorization // 3,
- sender=creator,
- )
- # threshold_staking.increaseAuthorization(
- # operator1, min_authorization, pre_application.address, {'from': operator1})
- assert pre_application.getOperatorFromStakingProvider(operator1) == ZERO_ADDRESS
- assert pre_application.stakingProviderFromOperator(operator1) == ZERO_ADDRESS
+ threshold_staking.authorizationIncreased(operator1, 0, min_authorization, sender=operator1)
+ assert taco_application.getOperatorFromStakingProvider(operator1) == ZERO_ADDRESS
+ assert taco_application.stakingProviderFromOperator(operator1) == ZERO_ADDRESS
chain.pending_timestamp += min_operator_seconds
# Staking provider can't bond the first operator again because operator is a provider now
with ape.reverts():
- pre_application.bondOperator(staking_provider_4, operator1, sender=staking_provider_4)
+ taco_application.bondOperator(staking_provider_4, operator1, sender=staking_provider_4)
# Provider without intermediary contract can bond itself as operator
# (Probably not best idea, but whatever)
- tx = pre_application.bondOperator(
+ tx = taco_application.bondOperator(
staking_provider_1, staking_provider_1, sender=staking_provider_1
)
timestamp = tx.timestamp
- assert pre_application.getOperatorFromStakingProvider(staking_provider_1) == staking_provider_1
- assert pre_application.stakingProviderFromOperator(staking_provider_1) == staking_provider_1
- assert pre_application.getStakingProvidersLength() == 3
- assert pre_application.stakingProviders(2) == staking_provider_1
+ assert taco_application.getOperatorFromStakingProvider(staking_provider_1) == staking_provider_1
+ assert taco_application.stakingProviderFromOperator(staking_provider_1) == staking_provider_1
+ assert taco_application.getStakingProvidersLength() == 3
+ assert taco_application.stakingProviders(2) == staking_provider_1
- events = pre_application.OperatorBonded.from_receipt(tx)
+ events = taco_application.OperatorBonded.from_receipt(tx)
assert len(events) == 1
event = events[0]
assert event["stakingProvider"] == staking_provider_1
assert event["operator"] == staking_provider_1
+ assert event["previousOperator"] == ZERO_ADDRESS
assert event["startTimestamp"] == timestamp
- # If stake will be less than minimum then confirmation is not possible
- threshold_staking.setStakes(staking_provider_1, 0, min_authorization - 1, 0, sender=creator)
-
- with ape.reverts():
- pre_application.confirmOperatorAddress(sender=staking_provider_1)
-
- # Now provider can make a confirmation
- threshold_staking.setStakes(staking_provider_1, 0, 0, min_authorization, sender=creator)
- pre_application.confirmOperatorAddress(sender=staking_provider_1)
+ # If stake will be less than minimum then confirmation is still possible
+ 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
# If stake will be less than minimum then provider is not active
- all_locked, staking_providers = pre_application.getActiveStakingProviders(0, 0)
+ threshold_staking.authorizationIncreased(
+ staking_provider_1, min_authorization - 1, min_authorization, sender=creator
+ )
+ all_locked, staking_providers = taco_application.getActiveStakingProviders(0, 0)
assert all_locked == 2 * min_authorization
assert len(staking_providers) == 2
assert to_checksum_address(staking_providers[0][0]) == staking_provider_3
assert staking_providers[0][1] == min_authorization
assert to_checksum_address(staking_providers[1][0]) == staking_provider_1
assert staking_providers[1][1] == min_authorization
- threshold_staking.setStakes(staking_provider_1, 0, min_authorization - 1, 0, sender=creator)
- all_locked, staking_providers = pre_application.getActiveStakingProviders(1, 0)
+ threshold_staking.involuntaryAuthorizationDecrease(
+ staking_provider_1, min_authorization, min_authorization - 1, sender=creator
+ )
+ all_locked, staking_providers = taco_application.getActiveStakingProviders(1, 0)
assert all_locked == 0
assert len(staking_providers) == 0
+ # Reset xchain contract before next bonding
+ taco_application.setUpdatableStakeInfo(ZERO_ADDRESS, sender=creator)
+
+ # Unbond and rebond oeprator
+ taco_application.bondOperator(staking_provider_3, ZERO_ADDRESS, sender=staking_provider_3)
+ taco_application.bondOperator(staking_provider_3, operator2, sender=staking_provider_3)
+ assert not taco_application.isOperatorConfirmed(operator2)
-def test_confirm_address(accounts, threshold_staking, pre_application, chain, project):
+ # Operator can be unbonded before confirmation without restriction
+ taco_application.bondOperator(staking_provider_3, ZERO_ADDRESS, sender=staking_provider_3)
+ assert taco_application.getOperatorFromStakingProvider(staking_provider_3) == ZERO_ADDRESS
+ assert taco_application.stakingProviderFromOperator(staking_provider_3) == ZERO_ADDRESS
+ assert taco_application.stakingProviderFromOperator(operator2) == ZERO_ADDRESS
+
+
+def test_confirm_address(accounts, threshold_staking, taco_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 provider that has minimum amount of tokens
+ # Operator must be associated with staking provider
with ape.reverts():
- pre_application.confirmOperatorAddress(sender=staking_provider)
+ taco_application.confirmOperatorAddress(sender=staking_provider)
threshold_staking.setRoles(staking_provider, sender=creator)
- threshold_staking.setStakes(staking_provider, min_authorization - 1, 0, 0, sender=creator)
- with ape.reverts():
- pre_application.confirmOperatorAddress(sender=staking_provider)
# Deploy intermediary contract
- intermediary = creator.deploy(project.Intermediary, pre_application.address, sender=creator)
+ intermediary = creator.deploy(project.Intermediary, taco_application.address, sender=creator)
# Bond contract as an operator
- threshold_staking.setStakes(staking_provider, min_authorization, 0, 0, sender=creator)
- pre_application.bondOperator(staking_provider, intermediary.address, sender=staking_provider)
+ 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():
@@ -330,12 +362,14 @@ def test_confirm_address(accounts, threshold_staking, pre_application, chain, pr
# Bond operator again and make confirmation
chain.pending_timestamp += min_operator_seconds
- pre_application.bondOperator(staking_provider, operator, sender=staking_provider)
- tx = pre_application.confirmOperatorAddress(sender=operator)
- assert pre_application.isOperatorConfirmed(operator)
- assert pre_application.stakingProviderInfo(staking_provider)[CONFIRMATION_SLOT]
-
- events = pre_application.OperatorConfirmed.from_receipt(tx)
+ taco_application.bondOperator(staking_provider, operator, sender=staking_provider)
+ assert taco_application.authorizedOverall() == 0
+ tx = taco_application.confirmOperatorAddress(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
@@ -343,4 +377,18 @@ def test_confirm_address(accounts, threshold_staking, pre_application, chain, pr
# Can't confirm twice
with ape.reverts():
- pre_application.confirmOperatorAddress(sender=operator)
+ taco_application.confirmOperatorAddress(sender=operator)
+
+
+def test_slash(accounts, threshold_staking, taco_application):
+ creator, staking_provider, investigator, *everyone_else = accounts[0:]
+ min_authorization = MIN_AUTHORIZATION
+ penalty = min_authorization
+
+ taco_application.setAdjudicator(creator, sender=creator)
+ taco_application.slash(staking_provider, penalty, investigator, sender=creator)
+ assert threshold_staking.amountToSeize() == penalty
+ assert threshold_staking.rewardMultiplier() == 100
+ assert threshold_staking.notifier() == investigator
+ assert threshold_staking.stakingProvidersToSeize(0) == staking_provider
+ assert threshold_staking.getLengthOfStakingProvidersToSeize() == 1
diff --git a/tests/application/test_reward.py b/tests/application/test_reward.py
new file mode 100644
index 00000000..df2ad775
--- /dev/null
+++ b/tests/application/test_reward.py
@@ -0,0 +1,445 @@
+"""
+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
+from ape.utils import ZERO_ADDRESS
+from web3 import Web3
+
+REWARDS_SLOT = 6
+REWARDS_PAID_SLOT = 7
+ERROR = 1e5
+MIN_AUTHORIZATION = Web3.to_wei(40_000, "ether")
+MIN_OPERATOR_SECONDS = 24 * 60 * 60
+REWARD_DURATION = 60 * 60 * 24 * 7 # one week in seconds
+DEAUTHORIZATION_DURATION = 60 * 60 * 24 * 60 # 60 days in seconds
+
+
+def test_push_reward(accounts, token, threshold_staking, taco_application, chain):
+ creator, distributor, staking_provider_1, staking_provider_2, *everyone_else = accounts[0:]
+ min_authorization = MIN_AUTHORIZATION
+ reward_portion = min_authorization
+ reward_duration = REWARD_DURATION
+ value = int(1.5 * min_authorization)
+
+ # Can't push reward without distributor
+ token.approve(taco_application.address, reward_portion, sender=creator)
+ with ape.reverts():
+ taco_application.pushReward(reward_portion, sender=creator)
+
+ # Only owner can set distributor
+ with ape.reverts():
+ taco_application.setRewardDistributor(distributor, sender=distributor)
+
+ tx = taco_application.setRewardDistributor(distributor, sender=creator)
+ assert taco_application.rewardDistributor() == distributor
+
+ events = taco_application.RewardDistributorSet.from_receipt(tx)
+ assert len(events) == 1
+ event = events[0]
+ assert event["distributor"] == distributor
+
+ # Can't distribute zero rewards
+ with ape.reverts():
+ taco_application.pushReward(0, sender=distributor)
+
+ # Push reward without staking providers
+ token.transfer(distributor, 10 * reward_portion, sender=creator)
+ token.approve(taco_application.address, 10 * reward_portion, sender=distributor)
+ tx = taco_application.pushReward(reward_portion, sender=distributor)
+ timestamp = chain.pending_timestamp - 1
+ assert taco_application.rewardRateDecimals() == reward_portion * 10**18 // reward_duration
+ assert taco_application.lastUpdateTime() == timestamp
+ assert taco_application.periodFinish() == (timestamp + reward_duration)
+ assert token.balanceOf(taco_application.address) == reward_portion
+ assert token.balanceOf(distributor) == 9 * reward_portion
+ assert taco_application.lastTimeRewardApplicable() == timestamp
+ assert taco_application.rewardPerTokenStored() == 0
+ assert taco_application.rewardPerToken() == 0
+ assert taco_application.availableRewards(staking_provider_1) == 0
+
+ events = taco_application.RewardAdded.from_receipt(tx)
+ assert len(events) == 1
+ event = events[0]
+ assert event["reward"] == reward_portion
+
+ # Wait some time and push reward again (without staking providers)
+ chain.pending_timestamp += reward_duration // 2 - 1
+ tx = taco_application.pushReward(reward_portion, sender=distributor)
+ timestamp = chain.pending_timestamp - 1
+ expected_reward_rate = (reward_portion + reward_portion // 2) * 10**18 // reward_duration
+ # Could be some error during calculations
+ assert abs(taco_application.rewardRateDecimals() - expected_reward_rate) <= ERROR
+ assert taco_application.lastUpdateTime() == timestamp
+ assert taco_application.periodFinish() == (timestamp + reward_duration)
+ assert token.balanceOf(taco_application.address) == 2 * reward_portion
+ assert token.balanceOf(distributor) == 8 * reward_portion
+ assert taco_application.lastTimeRewardApplicable() == timestamp
+ assert taco_application.rewardPerTokenStored() == 0
+ assert taco_application.rewardPerToken() == 0
+ assert taco_application.availableRewards(staking_provider_1) == 0
+
+ events = taco_application.RewardAdded.from_receipt(tx)
+ assert len(events) == 1
+ event = events[0]
+ assert event["reward"] == reward_portion
+
+ # Wait, add one staking provider and push reward again
+ 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)
+
+ tx = taco_application.pushReward(reward_portion, sender=distributor)
+ timestamp = chain.pending_timestamp - 1
+ assert taco_application.rewardRateDecimals() == reward_portion * 10**18 // reward_duration
+ assert taco_application.lastUpdateTime() == timestamp
+ assert taco_application.periodFinish() == (timestamp + reward_duration)
+ assert token.balanceOf(taco_application.address) == 3 * reward_portion
+ assert token.balanceOf(distributor) == 7 * reward_portion
+ assert taco_application.lastTimeRewardApplicable() == timestamp
+ assert taco_application.rewardPerTokenStored() == 0
+ assert taco_application.rewardPerToken() == 0
+ assert taco_application.availableRewards(staking_provider_1) == 0
+
+ events = taco_application.RewardAdded.from_receipt(tx)
+ assert len(events) == 1
+ event = events[0]
+ assert event["reward"] == reward_portion
+
+ # Wait some time and check reward for staking provider
+ chain.pending_timestamp += reward_duration // 2
+ assert taco_application.rewardPerTokenStored() == 0
+ expected_reward_per_token = int(reward_portion * 1e18) // value // 2
+ assert abs(taco_application.rewardPerToken() - expected_reward_per_token) < ERROR
+ expected_reward = reward_portion // 2
+ assert abs(taco_application.availableRewards(staking_provider_1) - expected_reward) < ERROR
+
+ chain.pending_timestamp += reward_duration // 2
+ assert taco_application.rewardPerTokenStored() == 0
+ expected_reward_per_token = int(reward_portion * 1e18) // value
+ reward_per_token = taco_application.rewardPerToken()
+ assert abs(reward_per_token - expected_reward_per_token) <= 100
+ expected_reward = reward_portion
+ reward = taco_application.availableRewards(staking_provider_1)
+ assert abs(reward - expected_reward) <= ERROR
+
+ # Add another staking provider without confirmation and push reward again
+ threshold_staking.authorizationIncreased(staking_provider_2, 0, value, sender=creator)
+ tx = taco_application.pushReward(reward_portion, sender=distributor)
+ timestamp = chain.pending_timestamp - 1
+ assert taco_application.rewardRateDecimals() == reward_portion * 10**18 // reward_duration
+ assert taco_application.lastUpdateTime() == timestamp
+ assert taco_application.periodFinish() == (timestamp + reward_duration)
+ assert token.balanceOf(taco_application.address) == 4 * reward_portion
+ assert token.balanceOf(distributor) == 6 * reward_portion
+ assert taco_application.lastTimeRewardApplicable() == timestamp
+ assert taco_application.rewardPerTokenStored() == reward_per_token
+ assert taco_application.rewardPerToken() == reward_per_token
+ assert taco_application.availableRewards(staking_provider_1) == reward
+ assert taco_application.availableRewards(staking_provider_2) == 0
+
+ events = taco_application.RewardAdded.from_receipt(tx)
+ assert len(events) == 1
+ event = events[0]
+ assert event["reward"] == reward_portion
+
+ chain.pending_timestamp += reward_duration
+ assert (
+ abs(taco_application.availableRewards(staking_provider_1) - (reward + reward_portion))
+ < ERROR
+ )
+ assert taco_application.availableRewards(staking_provider_2) == 0
+
+
+def test_update_reward(accounts, token, threshold_staking, taco_application, chain):
+ creator, distributor, staking_provider_1, staking_provider_2, *everyone_else = accounts[0:]
+ min_authorization = MIN_AUTHORIZATION
+ reward_portion = min_authorization
+ reward_duration = REWARD_DURATION
+ deauthorization_duration = DEAUTHORIZATION_DURATION
+ min_operator_seconds = MIN_OPERATOR_SECONDS
+ value = int(1.5 * min_authorization)
+
+ reward_per_token = 0
+ new_reward_per_token = 0
+ staking_provider_1_reward = 0
+ staking_provider_1_new_reward = 0
+ staking_provider_2_reward = 0
+ staking_provider_2_new_reward = 0
+
+ def check_reward_no_confirmation():
+ nonlocal reward_per_token, new_reward_per_token
+ nonlocal staking_provider_1_reward, staking_provider_1_new_reward
+
+ new_reward_per_token = taco_application.rewardPerToken()
+ assert new_reward_per_token > reward_per_token
+ assert taco_application.rewardPerTokenStored() == new_reward_per_token
+ staking_provider_1_new_reward = taco_application.availableRewards(staking_provider_1)
+ assert staking_provider_1_new_reward > staking_provider_1_reward
+ assert taco_application.stakingProviderInfo(staking_provider_1)[REWARDS_SLOT] == 0
+ assert taco_application.stakingProviderInfo(staking_provider_1)[REWARDS_PAID_SLOT] == 0
+ assert taco_application.availableRewards(staking_provider_2) == 0
+ assert taco_application.stakingProviderInfo(staking_provider_2)[REWARDS_SLOT] == 0
+ assert (
+ taco_application.stakingProviderInfo(staking_provider_2)[REWARDS_PAID_SLOT]
+ == new_reward_per_token
+ )
+ reward_per_token = new_reward_per_token
+ staking_provider_1_reward = staking_provider_1_new_reward
+
+ def check_reward_with_confirmation():
+ nonlocal reward_per_token, new_reward_per_token, staking_provider_1_reward
+ nonlocal staking_provider_1_new_reward, staking_provider_2_reward
+ nonlocal staking_provider_2_new_reward
+
+ new_reward_per_token = taco_application.rewardPerToken()
+ assert new_reward_per_token > reward_per_token
+ assert taco_application.rewardPerTokenStored() == new_reward_per_token
+ staking_provider_1_new_reward = taco_application.availableRewards(staking_provider_1)
+ assert staking_provider_1_new_reward > staking_provider_1_reward
+ assert taco_application.stakingProviderInfo(staking_provider_1)[REWARDS_SLOT] == 0
+ assert taco_application.stakingProviderInfo(staking_provider_1)[REWARDS_PAID_SLOT] == 0
+ staking_provider_2_new_reward = taco_application.availableRewards(staking_provider_2)
+ assert staking_provider_2_new_reward > staking_provider_2_reward
+ assert (
+ taco_application.stakingProviderInfo(staking_provider_2)[REWARDS_SLOT]
+ == staking_provider_2_new_reward
+ )
+ assert (
+ taco_application.stakingProviderInfo(staking_provider_2)[REWARDS_PAID_SLOT]
+ == new_reward_per_token
+ )
+ reward_per_token = new_reward_per_token
+ staking_provider_1_reward = staking_provider_1_new_reward
+ staking_provider_2_reward = staking_provider_2_new_reward
+
+ # 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)
+
+ taco_application.setRewardDistributor(distributor, sender=creator)
+ token.transfer(distributor, 100 * reward_portion, sender=creator)
+ token.approve(taco_application.address, 100 * reward_portion, sender=distributor)
+ taco_application.pushReward(2 * reward_portion, sender=distributor)
+ assert taco_application.rewardPerTokenStored() == 0
+ assert taco_application.rewardPerToken() == 0
+ assert taco_application.availableRewards(staking_provider_1) == 0
+
+ chain.pending_timestamp += reward_duration // 2
+ # Reward per token will be updated but nothing earned yet
+ threshold_staking.authorizationIncreased(staking_provider_2, 0, 4 * value, sender=creator)
+ check_reward_no_confirmation()
+
+ # Add reward, wait and bond operator
+ taco_application.pushReward(reward_portion, sender=distributor)
+ chain.pending_timestamp += reward_duration // 2
+ # Reward per token will be updated but nothing earned yet (need confirmation)
+ taco_application.bondOperator(staking_provider_2, staking_provider_2, sender=staking_provider_2)
+ check_reward_no_confirmation()
+
+ # Involuntary decrease without confirmation
+ taco_application.pushReward(reward_portion, sender=distributor)
+ chain.pending_timestamp += reward_duration // 2
+ threshold_staking.involuntaryAuthorizationDecrease(
+ staking_provider_2, 4 * value, 3 * value, sender=creator
+ )
+ check_reward_no_confirmation()
+
+ # Request for decrease
+ taco_application.pushReward(reward_portion, sender=distributor)
+ chain.pending_timestamp += reward_duration // 2
+ threshold_staking.authorizationDecreaseRequested(
+ staking_provider_2, 3 * value, 2 * value, sender=creator
+ )
+ check_reward_no_confirmation()
+
+ # Finish decrease without confirmation
+ chain.pending_timestamp += deauthorization_duration
+ taco_application.finishAuthorizationDecrease(staking_provider_2, sender=creator)
+ check_reward_no_confirmation()
+
+ # Resync without confirmation
+ taco_application.pushReward(reward_portion, sender=distributor)
+ chain.pending_timestamp += reward_duration // 2
+ threshold_staking.setAuthorized(staking_provider_2, value, sender=creator)
+ taco_application.resynchronizeAuthorization(staking_provider_2, sender=creator)
+ check_reward_no_confirmation()
+
+ # Wait and confirm operator
+ 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)
+ check_reward_no_confirmation()
+
+ # Increase authorization with confirmation
+ taco_application.pushReward(reward_portion, sender=distributor)
+ chain.pending_timestamp += reward_duration // 2
+ threshold_staking.authorizationIncreased(staking_provider_2, value, 4 * value, sender=creator)
+ check_reward_with_confirmation()
+
+ # Involuntary decrease with confirmation
+ taco_application.pushReward(reward_portion, sender=distributor)
+ chain.pending_timestamp += reward_duration // 2
+ threshold_staking.involuntaryAuthorizationDecrease(
+ staking_provider_2, 4 * value, 3 * value, sender=creator
+ )
+ check_reward_with_confirmation()
+
+ # Request for decrease
+ taco_application.pushReward(reward_portion, sender=distributor)
+ chain.pending_timestamp += reward_duration // 2
+ threshold_staking.authorizationDecreaseRequested(
+ staking_provider_2, 3 * value, 2 * value, sender=creator
+ )
+ check_reward_with_confirmation()
+
+ # Finish decrease with confirmation
+ chain.pending_timestamp += deauthorization_duration
+ taco_application.finishAuthorizationDecrease(staking_provider_2, sender=creator)
+ check_reward_with_confirmation()
+
+ # Resync with confirmation
+ taco_application.pushReward(reward_portion, sender=distributor)
+ chain.pending_timestamp += reward_duration // 2
+ threshold_staking.setAuthorized(staking_provider_2, value, sender=creator)
+ taco_application.resynchronizeAuthorization(staking_provider_2, sender=creator)
+ check_reward_with_confirmation()
+
+ # Bond operator with confirmation (confirmation will be dropped)
+ taco_application.pushReward(reward_portion, sender=distributor)
+ chain.pending_timestamp += min_operator_seconds
+ # Reward per token will be updated but nothing earned yet (need confirmation)
+ taco_application.bondOperator(staking_provider_2, everyone_else[0], sender=staking_provider_2)
+ check_reward_with_confirmation()
+
+ # Push reward wait some time and check that no more reward
+ taco_application.pushReward(reward_portion, sender=distributor)
+ chain.pending_timestamp += reward_duration
+ assert taco_application.availableRewards(staking_provider_2) == staking_provider_2_reward
+ assert (
+ taco_application.stakingProviderInfo(staking_provider_2)[REWARDS_SLOT]
+ == staking_provider_2_reward
+ )
+ assert (
+ taco_application.stakingProviderInfo(staking_provider_2)[REWARDS_PAID_SLOT]
+ == reward_per_token
+ )
+
+
+def test_withdraw(accounts, token, threshold_staking, taco_application, chain):
+ (
+ creator,
+ distributor,
+ staking_provider,
+ owner,
+ beneficiary,
+ authorizer,
+ staking_provider_2,
+ *everyone_else,
+ ) = accounts[0:]
+ min_authorization = MIN_AUTHORIZATION
+ reward_portion = min_authorization
+ reward_duration = REWARD_DURATION
+ min_operator_seconds = MIN_OPERATOR_SECONDS
+ value = int(1.5 * min_authorization)
+
+ # No rewards, no staking providers
+ threshold_staking.setRoles(staking_provider, owner, beneficiary, authorizer, sender=creator)
+ with ape.reverts():
+ taco_application.withdrawRewards(staking_provider, sender=beneficiary)
+
+ # 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)
+
+ # Nothing earned yet
+ with ape.reverts():
+ taco_application.withdrawRewards(staking_provider, sender=beneficiary)
+
+ taco_application.setRewardDistributor(distributor, sender=creator)
+ token.transfer(distributor, 100 * reward_portion, sender=creator)
+ token.approve(taco_application.address, 100 * reward_portion, sender=distributor)
+ taco_application.pushReward(reward_portion, sender=distributor)
+ assert taco_application.rewardPerTokenStored() == 0
+ assert taco_application.rewardPerToken() == 0
+ assert taco_application.availableRewards(staking_provider) == 0
+
+ chain.pending_timestamp += reward_duration
+ # Only beneficiary can withdraw reward
+ with ape.reverts():
+ taco_application.withdrawRewards(staking_provider, sender=owner)
+ with ape.reverts():
+ taco_application.withdrawRewards(staking_provider, sender=authorizer)
+
+ reward_per_token = taco_application.rewardPerToken()
+ assert reward_per_token > 0
+ earned = taco_application.availableRewards(staking_provider)
+ assert earned > 0
+
+ tx = taco_application.withdrawRewards(staking_provider, sender=beneficiary)
+ assert taco_application.rewardPerTokenStored() == reward_per_token
+ assert taco_application.stakingProviderInfo(staking_provider)[REWARDS_SLOT] == 0
+ assert (
+ taco_application.stakingProviderInfo(staking_provider)[REWARDS_PAID_SLOT]
+ == reward_per_token
+ )
+ assert token.balanceOf(beneficiary) == earned
+ assert token.balanceOf(taco_application.address) == reward_portion - earned
+
+ events = taco_application.RewardPaid.from_receipt(tx)
+ assert len(events) == 1
+ event = events[0]
+ assert event["stakingProvider"] == staking_provider
+ assert event["beneficiary"] == beneficiary
+ assert event["reward"] == earned
+
+ # Add one more staking provider, push reward again and drop operator
+ chain.pending_timestamp += min_operator_seconds
+ 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)
+ taco_application.pushReward(reward_portion, sender=distributor)
+ chain.pending_timestamp += reward_duration // 2
+ taco_application.bondOperator(staking_provider, ZERO_ADDRESS, sender=staking_provider)
+
+ new_earned = taco_application.availableRewards(staking_provider)
+ assert taco_application.stakingProviderInfo(staking_provider)[REWARDS_SLOT] == new_earned
+
+ # Withdraw
+ chain.pending_timestamp += reward_duration // 2
+ assert taco_application.availableRewards(staking_provider) == new_earned
+ tx = taco_application.withdrawRewards(staking_provider, sender=beneficiary)
+ new_reward_per_token = taco_application.rewardPerToken()
+ assert taco_application.rewardPerTokenStored() == new_reward_per_token
+ assert taco_application.stakingProviderInfo(staking_provider)[REWARDS_SLOT] == 0
+ assert (
+ taco_application.stakingProviderInfo(staking_provider)[REWARDS_PAID_SLOT]
+ == new_reward_per_token
+ )
+ assert token.balanceOf(beneficiary) == earned + new_earned
+ assert token.balanceOf(taco_application.address) == 2 * reward_portion - earned - new_earned
+
+ events = taco_application.RewardPaid.from_receipt(tx)
+ assert len(events) == 1
+ event = events[0]
+ assert event["stakingProvider"] == staking_provider
+ assert event["beneficiary"] == beneficiary
+ assert event["reward"] == new_earned