Skip to content
This repository has been archived by the owner on May 22, 2023. It is now read-only.

Abstract bonding contract #525

Merged
merged 10 commits into from
Aug 12, 2020
357 changes: 357 additions & 0 deletions solidity/contracts/AbstractBonding.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,357 @@
/**
▓▓▌ ▓▓ ▐▓▓ ▓▓▓▓▓▓▓▓▓▓▌▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▄
▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▌▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓ ▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓ ▐▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓ ▐▓▓▓▓▓▌ ▐▓▓▓▓▓▓
▓▓▓▓▓▓▄▄▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓▄▄▄▄ ▓▓▓▓▓▓▄▄▄▄ ▐▓▓▓▓▓▌ ▐▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▌ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▀▀▓▓▓▓▓▓▄ ▐▓▓▓▓▓▓▀▀▀▀ ▓▓▓▓▓▓▀▀▀▀ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▀
▓▓▓▓▓▓ ▀▓▓▓▓▓▓▄ ▐▓▓▓▓▓▓ ▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓ ▐▓▓▓▓▓▌
▓▓▓▓▓▓▓▓▓▓ █▓▓▓▓▓▓▓▓▓ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓

Trust math, not hardware.
*/

pragma solidity 0.5.17;

import "@keep-network/keep-core/contracts/KeepRegistry.sol";
import "@keep-network/keep-core/contracts/Authorizations.sol";
import "@keep-network/keep-core/contracts/StakeDelegatable.sol";
import "@keep-network/sortition-pools/contracts/api/IBonding.sol";
import "openzeppelin-solidity/contracts/math/SafeMath.sol";


/// @title Keep Bonding
/// @notice Contract holding deposits from keeps' operators.
contract AbstractBonding is IBonding {
using SafeMath for uint256;

// Registry contract with a list of approved factories (operator contracts).
KeepRegistry internal registry;

// Staking Authorizations contract.
Authorizations internal authorizations;

// Stake Delegatable contract.
StakeDelegatable internal stakeDelegatable;
pdyraga marked this conversation as resolved.
Show resolved Hide resolved

// Unassigned value in wei deposited by operators.
mapping(address => uint256) public unbondedValue;

// References to created bonds. Bond identifier is built from operator's
// address, holder's address and reference ID assigned on bond creation.
mapping(bytes32 => uint256) internal lockedBonds;

// Sortition pools authorized by operator's authorizer.
// operator -> pool -> boolean
mapping(address => mapping(address => bool)) internal authorizedPools;

event UnbondedValueDeposited(
address indexed operator,
address indexed beneficiary,
uint256 amount
);
event UnbondedValueWithdrawn(
address indexed operator,
address indexed beneficiary,
uint256 amount
);
event BondCreated(
address indexed operator,
address indexed holder,
address indexed sortitionPool,
uint256 referenceID,
uint256 amount
);
event BondReassigned(
address indexed operator,
uint256 indexed referenceID,
address newHolder,
uint256 newReferenceID
);
event BondReleased(address indexed operator, uint256 indexed referenceID);
event BondSeized(
address indexed operator,
uint256 indexed referenceID,
address destination,
uint256 amount
);

/// @notice Initializes Keep Bonding contract.
/// @param registryAddress Keep registry contract address.
/// @param authorizationsAddress Staking Authorizations contract address.
/// @param stakeDelegatableAddress Stake Delegatable contract address.
pdyraga marked this conversation as resolved.
Show resolved Hide resolved
constructor(
address registryAddress,
address authorizationsAddress,
address stakeDelegatableAddress
) public {
registry = KeepRegistry(registryAddress);
authorizations = Authorizations(authorizationsAddress);
stakeDelegatable = StakeDelegatable(stakeDelegatableAddress);
}

/// @notice Add the provided value to operator's pool available for bonding.
/// @param operator Address of the operator.
function deposit(address operator) external payable {
address beneficiary = stakeDelegatable.beneficiaryOf(operator);
// Beneficiary has to be set (delegation exist) before an operator can
// deposit wei. It protects from a situation when an operator wants
// to withdraw funds which are transfered to beneficiary with zero
// address.
require(
beneficiary != address(0),
"Beneficiary not defined for the operator"
);
unbondedValue[operator] = unbondedValue[operator].add(msg.value);
emit UnbondedValueDeposited(operator, beneficiary, msg.value);
}

/// @notice Withdraws amount from operator's value available for bonding.
/// @param amount Value to withdraw in wei.
/// @param operator Address of the operator.
function withdraw(uint256 amount, address operator) public;

/// @notice Returns the amount of wei the operator has made available for
/// bonding and that is still unbounded. If the operator doesn't exist or
/// bond creator is not authorized as an operator contract or it is not
/// authorized by the operator or there is no secondary authorization for
/// the provided sortition pool, function returns 0.
/// @dev Implements function expected by sortition pools' IBonding interface.
/// @param operator Address of the operator.
/// @param bondCreator Address authorized to create a bond.
/// @param authorizedSortitionPool Address of authorized sortition pool.
/// @return Amount of authorized wei deposit available for bonding.
function availableUnbondedValue(
address operator,
address bondCreator,
address authorizedSortitionPool
) public view returns (uint256) {
// Sortition pools check this condition and skips operators that
// are no longer eligible. We cannot revert here.
if (
registry.isApprovedOperatorContract(bondCreator) &&
authorizations.isAuthorizedForOperator(operator, bondCreator) &&
hasSecondaryAuthorization(operator, authorizedSortitionPool)
) {
return unbondedValue[operator];
}

return 0;
}

/// @notice Create bond for the given operator, holder, reference and amount.
/// @dev Function can be executed only by authorized contract. Reference ID
/// should be unique for holder and operator.
/// @param operator Address of the operator to bond.
/// @param holder Address of the holder of the bond.
/// @param referenceID Reference ID used to track the bond by holder.
/// @param amount Value to bond in wei.
/// @param authorizedSortitionPool Address of authorized sortition pool.
function createBond(
address operator,
address holder,
uint256 referenceID,
uint256 amount,
address authorizedSortitionPool
) public {
require(
availableUnbondedValue(
operator,
msg.sender,
authorizedSortitionPool
) >= amount,
"Insufficient unbonded value"
);

bytes32 bondID = keccak256(
abi.encodePacked(operator, holder, referenceID)
);

require(
lockedBonds[bondID] == 0,
"Reference ID not unique for holder and operator"
);

unbondedValue[operator] = unbondedValue[operator].sub(amount);
lockedBonds[bondID] = lockedBonds[bondID].add(amount);

emit BondCreated(
operator,
holder,
authorizedSortitionPool,
referenceID,
amount
);
}

/// @notice Returns value of wei bonded for the operator.
/// @param operator Address of the operator.
/// @param holder Address of the holder of the bond.
/// @param referenceID Reference ID of the bond.
/// @return Amount of wei in the selected bond.
function bondAmount(address operator, address holder, uint256 referenceID)
public
view
returns (uint256)
{
bytes32 bondID = keccak256(
abi.encodePacked(operator, holder, referenceID)
);

return lockedBonds[bondID];
}

/// @notice Reassigns a bond to a new holder under a new reference.
/// @dev Function requires that a caller is the current holder of the bond
/// which is being reassigned.
/// @param operator Address of the bonded operator.
/// @param referenceID Reference ID of the bond.
/// @param newHolder Address of the new holder of the bond.
/// @param newReferenceID New reference ID to register the bond.
function reassignBond(
address operator,
uint256 referenceID,
address newHolder,
uint256 newReferenceID
) public {
address holder = msg.sender;
bytes32 bondID = keccak256(
abi.encodePacked(operator, holder, referenceID)
);

require(lockedBonds[bondID] > 0, "Bond not found");

bytes32 newBondID = keccak256(
abi.encodePacked(operator, newHolder, newReferenceID)
);

require(
lockedBonds[newBondID] == 0,
"Reference ID not unique for holder and operator"
);

lockedBonds[newBondID] = lockedBonds[bondID];
lockedBonds[bondID] = 0;

emit BondReassigned(operator, referenceID, newHolder, newReferenceID);
}

/// @notice Releases the bond and moves the bond value to the operator's
/// unbounded value pool.
/// @dev Function requires that caller is the holder of the bond which is
/// being released.
/// @param operator Address of the bonded operator.
/// @param referenceID Reference ID of the bond.
function freeBond(address operator, uint256 referenceID) public {
address holder = msg.sender;
bytes32 bondID = keccak256(
abi.encodePacked(operator, holder, referenceID)
);

require(lockedBonds[bondID] > 0, "Bond not found");

uint256 amount = lockedBonds[bondID];
lockedBonds[bondID] = 0;
unbondedValue[operator] = unbondedValue[operator].add(amount);

emit BondReleased(operator, referenceID);
}

/// @notice Seizes the bond by moving some or all of the locked bond to the
/// provided destination address.
/// @dev Function requires that a caller is the holder of the bond which is
/// being seized.
/// @param operator Address of the bonded operator.
/// @param referenceID Reference ID of the bond.
/// @param amount Amount to be seized.
/// @param destination Address to send the amount to.
function seizeBond(
address operator,
uint256 referenceID,
uint256 amount,
address payable destination
) public {
require(amount > 0, "Requested amount should be greater than zero");

address payable holder = msg.sender;
bytes32 bondID = keccak256(
abi.encodePacked(operator, holder, referenceID)
);

require(
lockedBonds[bondID] >= amount,
"Requested amount is greater than the bond"
);

lockedBonds[bondID] = lockedBonds[bondID].sub(amount);

(bool success, ) = destination.call.value(amount)("");
require(success, "Transfer failed");

emit BondSeized(operator, referenceID, destination, amount);
}

/// @notice Authorizes sortition pool for the provided operator.
/// Operator's authorizers need to authorize individual sortition pools
/// per application since they may be interested in participating only in
/// a subset of keep types used by the given application.
/// @dev Only operator's authorizer can call this function.
function authorizeSortitionPoolContract(
address _operator,
address _poolAddress
) public {
require(
stakeDelegatable.authorizerOf(_operator) == msg.sender,
"Not authorized"
);
authorizedPools[_operator][_poolAddress] = true;
}

/// @notice Deauthorizes sortition pool for the provided operator.
/// Authorizer may deauthorize individual sortition pool in case the
/// operator should no longer be eligible for work selection and the
/// application represented by the sortition pool should no longer be
/// eligible to create bonds for the operator.
/// @dev Only operator's authorizer can call this function.
function deauthorizeSortitionPoolContract(
address _operator,
address _poolAddress
) public {
require(
stakeDelegatable.authorizerOf(_operator) == msg.sender,
"Not authorized"
);
authorizedPools[_operator][_poolAddress] = false;
}

/// @notice Checks if the sortition pool has been authorized for the
/// provided operator by its authorizer.
/// @dev See authorizeSortitionPoolContract.
function hasSecondaryAuthorization(address _operator, address _poolAddress)
public
view
returns (bool)
{
return authorizedPools[_operator][_poolAddress];
}

/// @notice Withdraws the provided amount from unbonded value of the
/// provided operator to operator's beneficiary. If there is no enough
/// unbonded value or the transfer failed, function fails.
function withdrawBond(uint256 amount, address operator) internal {
require(
unbondedValue[operator] >= amount,
"Insufficient unbonded value"
);

unbondedValue[operator] = unbondedValue[operator].sub(amount);

address beneficiary = stakeDelegatable.beneficiaryOf(operator);

(bool success, ) = beneficiary.call.value(amount)("");
require(success, "Transfer failed");

emit UnbondedValueWithdrawn(operator, beneficiary, amount);
}
}
Loading