Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TACo Threshold Application #99

Merged
merged 12 commits into from
Aug 1, 2023
4 changes: 2 additions & 2 deletions .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions ape-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand All @@ -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'
Expand Down
126 changes: 51 additions & 75 deletions contracts/contracts/Adjudicator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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(
Expand All @@ -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
Expand All @@ -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"
);
Expand All @@ -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]),
Expand All @@ -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;
}
}
Loading
Loading