Merge pull request #2860 from cygnusv/sm

Adapt "Hello Operator" to new interface of SubscriptionManager
pull/2869/head
KPrasch 2022-02-10 11:22:25 -08:00 committed by GitHub
commit 966d6c08c4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 91 additions and 48 deletions

View File

@ -17,6 +17,9 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
from typing import Tuple, Optional from typing import Tuple, Optional
from web3 import Web3
from web3.types import Wei
from nucypher.blockchain.eth.agents import ( from nucypher.blockchain.eth.agents import (
ContractAgency, ContractAgency,
PREApplicationAgent PREApplicationAgent
@ -29,6 +32,7 @@ class Economics:
_default_min_authorization = TToken(40_000, 'T').to_units() _default_min_authorization = TToken(40_000, 'T').to_units()
_default_min_operator_seconds = 60 * 60 * 24 # one day in seconds _default_min_operator_seconds = 60 * 60 * 24 # one day in seconds
_default_fee_rate = Wei(Web3.toWei(1, 'gwei'))
# TODO: Reintroduce Adjudicator # TODO: Reintroduce Adjudicator
# Slashing parameters # Slashing parameters
@ -44,6 +48,7 @@ class Economics:
def __init__(self, def __init__(self,
min_operator_seconds: int = _default_min_operator_seconds, min_operator_seconds: int = _default_min_operator_seconds,
min_authorization: int = _default_min_authorization, min_authorization: int = _default_min_authorization,
fee_rate: Wei = _default_fee_rate,
# Adjudicator # Adjudicator
# hash_algorithm: int = _default_hash_algorithm, # hash_algorithm: int = _default_hash_algorithm,
@ -73,6 +78,7 @@ class Economics:
self.min_operator_seconds = min_operator_seconds self.min_operator_seconds = min_operator_seconds
self.min_authorization = min_authorization self.min_authorization = min_authorization
self.fee_rate = fee_rate
@property @property
def pre_application_deployment_parameters(self) -> Tuple[int, ...]: def pre_application_deployment_parameters(self) -> Tuple[int, ...]:

View File

@ -825,8 +825,8 @@ class SubscriptionManagerAgent(EthereumContractAgent):
# #
@contract_api(CONTRACT_CALL) @contract_api(CONTRACT_CALL)
def rate_per_second(self) -> Wei: def fee_rate(self) -> Wei:
result = self.contract.functions.RATE_PER_SECOND().call() result = self.contract.functions.feeRate().call()
return Wei(result) return Wei(result)
@contract_api(CONTRACT_CALL) @contract_api(CONTRACT_CALL)
@ -839,10 +839,11 @@ class SubscriptionManagerAgent(EthereumContractAgent):
record = self.contract.functions.policies(policy_id).call() record = self.contract.functions.policies(policy_id).call()
policy_info = self.PolicyInfo( policy_info = self.PolicyInfo(
sponsor=record[0], sponsor=record[0],
start_timestamp=record[1],
end_timestamp=record[2],
size=record[3],
# If the policyOwner addr is null, we return the sponsor addr instead of the owner. # If the policyOwner addr is null, we return the sponsor addr instead of the owner.
owner=record[0] if record[1] == NULL_ADDRESS else record[1], owner=record[0] if record[4] == NULL_ADDRESS else record[4]
start_timestamp=record[2],
end_timestamp=record[3]
) )
return policy_info return policy_info
@ -854,6 +855,7 @@ class SubscriptionManagerAgent(EthereumContractAgent):
def create_policy(self, def create_policy(self,
policy_id: bytes, policy_id: bytes,
transacting_power: TransactingPower, transacting_power: TransactingPower,
size: int,
start_timestamp: Timestamp, start_timestamp: Timestamp,
end_timestamp: Timestamp, end_timestamp: Timestamp,
value: Wei, value: Wei,
@ -863,6 +865,7 @@ class SubscriptionManagerAgent(EthereumContractAgent):
contract_function: ContractFunction = self.contract.functions.createPolicy( contract_function: ContractFunction = self.contract.functions.createPolicy(
policy_id, policy_id,
owner_address, owner_address,
size,
start_timestamp, start_timestamp,
end_timestamp end_timestamp
) )
@ -873,13 +876,6 @@ class SubscriptionManagerAgent(EthereumContractAgent):
) )
return receipt return receipt
@contract_api(TRANSACTION)
def sweep(self, recipient: ChecksumAddress, transacting_power: TransactingPower) -> TxReceipt:
"""Collect fees (ETH) earned since last withdrawal"""
contract_function: ContractFunction = self.contract.functions.sweep(recipient)
receipt = self.blockchain.send_transaction(contract_function=contract_function, transacting_power=transacting_power)
return receipt
class AdjudicatorAgent(EthereumContractAgent): class AdjudicatorAgent(EthereumContractAgent):

View File

@ -721,7 +721,7 @@ class SubscriptionManagerDeployer(BaseContractDeployer, OwnableContractMixin):
agency = SubscriptionManagerAgent agency = SubscriptionManagerAgent
contract_name = agency.contract_name contract_name = agency.contract_name
deployment_steps = ('contract_deployment',) deployment_steps = ('contract_deployment', 'initialize')
_upgradeable = False _upgradeable = False
_ownable = True _ownable = True
@ -744,8 +744,19 @@ class SubscriptionManagerDeployer(BaseContractDeployer, OwnableContractMixin):
gas_limit=gas_limit, gas_limit=gas_limit,
confirmations=confirmations, confirmations=confirmations,
**constructor_kwargs) **constructor_kwargs)
self._contract = contract self._contract = contract
return {self.deployment_steps[0]: deployment_receipt}
tx_args = {}
if gas_limit:
tx_args.update({'gas': gas_limit}) # TODO: Gas management - 842
initialize_function = contract.functions.initialize(self.economics.fee_rate)
initialize_receipt = self.blockchain.send_transaction(contract_function=initialize_function,
transacting_power=transacting_power,
payload=tx_args,
confirmations=confirmations)
return {self.deployment_steps[0]: deployment_receipt,
self.deployment_steps[1]: initialize_receipt}
class AdjudicatorDeployer(BaseContractDeployer, UpgradeableContractMixin, OwnableContractMixin): class AdjudicatorDeployer(BaseContractDeployer, UpgradeableContractMixin, OwnableContractMixin):

View File

@ -2,38 +2,47 @@
pragma solidity ^0.8.0; pragma solidity ^0.8.0;
contract SubscriptionManager { import "../zeppelin/proxy/Initializable.sol";
uint256 private constant RATE_PER_DAY = 50 gwei; contract SubscriptionManager is Initializable {
uint256 public constant RATE_PER_SECOND = RATE_PER_DAY / 1 days;
struct Policy { // TODO: Optimize struct layout // The layout of policy struct is optimized, so sponsor, timestamps and size
// fit in a single 256-word.
struct Policy {
address payable sponsor; address payable sponsor;
uint32 startTimestamp;
uint32 endTimestamp;
uint16 size; // also known as `N`
address owner; address owner;
uint64 startTimestamp;
uint64 endTimestamp;
} }
event PolicyCreated( event PolicyCreated(
bytes16 indexed policyId, bytes16 indexed policyId,
address indexed sponsor, address indexed sponsor,
address indexed owner, address indexed owner,
uint64 startTimestamp, uint16 size,
uint64 endTimestamp uint32 startTimestamp,
uint32 endTimestamp
); );
address payable public owner;
mapping (bytes16 => Policy) public policies;
constructor(){ event FeeRateUpdated(uint256 oldFeeRate, uint256 newFeeRate);
owner = payable(msg.sender);
// Per-second service fee rate
uint256 public feeRate;
// Mapping that stores policy structs, keyed by policy ID
mapping (bytes16 => Policy) internal _policies;
function initialize(uint256 _feeRate) public initializer {
_setFeeRate(_feeRate);
} }
function createPolicy( function createPolicy(
bytes16 _policyId, bytes16 _policyId,
address _policyOwner, address _policyOwner,
uint64 _startTimestamp, uint16 _size,
uint64 _endTimestamp uint32 _startTimestamp,
uint32 _endTimestamp
) )
external payable external payable
{ {
@ -42,31 +51,33 @@ contract SubscriptionManager {
block.timestamp < _endTimestamp, block.timestamp < _endTimestamp,
"Invalid timestamps" "Invalid timestamps"
); );
uint64 duration = _endTimestamp - _startTimestamp; uint32 duration = _endTimestamp - _startTimestamp;
require( require(
duration > 0 && duration > 0 && _size > 0 &&
msg.value == RATE_PER_SECOND * uint64(duration) msg.value == feeRate * _size * uint32(duration)
); );
//Policy storage policy =
_createPolicy(_policyId, _policyOwner, _startTimestamp, _endTimestamp); _createPolicy(_policyId, _policyOwner, _size, _startTimestamp, _endTimestamp);
} }
/** /**
* @notice Create policy * @notice Create policy
* @param _policyId Policy id * @param _policyId Policy id
* @param _policyOwner Policy owner. Zero address means sender is owner * @param _policyOwner Policy owner. Zero address means sender is owner
* @param _size Number of nodes involved in the policy
* @param _startTimestamp Start timestamp of the policy in seconds * @param _startTimestamp Start timestamp of the policy in seconds
* @param _endTimestamp End timestamp of the policy in seconds * @param _endTimestamp End timestamp of the policy in seconds
*/ */
function _createPolicy( function _createPolicy(
bytes16 _policyId, bytes16 _policyId,
address _policyOwner, address _policyOwner,
uint64 _startTimestamp, uint16 _size,
uint64 _endTimestamp uint32 _startTimestamp,
uint32 _endTimestamp
) )
internal returns (Policy storage policy) internal returns (Policy storage policy)
{ {
policy = policies[_policyId]; policy = _policies[_policyId];
require( require(
policy.endTimestamp < block.timestamp, policy.endTimestamp < block.timestamp,
"Policy is currently active" "Policy is currently active"
@ -75,6 +86,7 @@ contract SubscriptionManager {
policy.sponsor = payable(msg.sender); policy.sponsor = payable(msg.sender);
policy.startTimestamp = _startTimestamp; policy.startTimestamp = _startTimestamp;
policy.endTimestamp = _endTimestamp; policy.endTimestamp = _endTimestamp;
policy.size = _size;
if (_policyOwner != msg.sender && _policyOwner != address(0)) { if (_policyOwner != msg.sender && _policyOwner != address(0)) {
policy.owner = _policyOwner; policy.owner = _policyOwner;
@ -84,20 +96,34 @@ contract SubscriptionManager {
_policyId, _policyId,
msg.sender, msg.sender,
_policyOwner == address(0) ? msg.sender : _policyOwner, _policyOwner == address(0) ? msg.sender : _policyOwner,
_size,
_startTimestamp, _startTimestamp,
_endTimestamp _endTimestamp
); );
} }
function getPolicy(bytes16 _policyID) public view returns(Policy memory){
return _policies[_policyID];
}
function isPolicyActive(bytes16 _policyID) public view returns(bool){ function isPolicyActive(bytes16 _policyID) public view returns(bool){
return policies[_policyID].endTimestamp > block.timestamp; return _policies[_policyID].endTimestamp > block.timestamp;
}
function _setFeeRate(uint256 newFee) internal {
uint256 oldFee = feeRate;
feeRate = newFee;
emit FeeRateUpdated(oldFee, newFee);
}
function setFeeRate(uint256 _rate_per_second) external {
_setFeeRate(_rate_per_second);
} }
function sweep(address payable recipient) external { function sweep(address payable recipient) external {
require(msg.sender == owner);
uint256 balance = address(this).balance; uint256 balance = address(this).balance;
(bool sent, ) = recipient.call{value: balance}(""); (bool sent, ) = recipient.call{value: balance}("");
require(sent, "Failed transfer"); require(sent, "Failed transfer");
} }
} }

View File

@ -48,6 +48,7 @@ class PaymentMethod(ReencryptionPrerequisite, ABC):
commencement: int # epoch commencement: int # epoch
expiration: int # epoch expiration: int # epoch
duration: int # seconds or periods duration: int # seconds or periods
shares: int
@abstractmethod @abstractmethod
def pay(self, policy: Policy) -> Dict: def pay(self, policy: Policy) -> Dict:
@ -125,6 +126,7 @@ class FreeReencryptions(PaymentMethod):
return 0 return 0
def quote(self, def quote(self,
shares: int,
commencement: Optional[Timestamp] = None, commencement: Optional[Timestamp] = None,
expiration: Optional[Timestamp] = None, expiration: Optional[Timestamp] = None,
duration: Optional[int] = None, duration: Optional[int] = None,
@ -133,6 +135,7 @@ class FreeReencryptions(PaymentMethod):
return self.Quote( return self.Quote(
value=0, value=0,
rate=0, rate=0,
shares=shares,
duration=duration, duration=duration,
commencement=commencement, commencement=commencement,
expiration=expiration expiration=expiration
@ -158,6 +161,7 @@ class SubscriptionManagerPayment(ContractPayment):
receipt = self.agent.create_policy( receipt = self.agent.create_policy(
value=policy.value, # wei value=policy.value, # wei
policy_id=bytes(policy.hrac), # bytes16 _policyID policy_id=bytes(policy.hrac), # bytes16 _policyID
size=len(policy.kfrags), # uint16
start_timestamp=policy.commencement, # uint16 start_timestamp=policy.commencement, # uint16
end_timestamp=policy.expiration, # uint16 end_timestamp=policy.expiration, # uint16
transacting_power=policy.publisher.transacting_power transacting_power=policy.publisher.transacting_power
@ -166,10 +170,11 @@ class SubscriptionManagerPayment(ContractPayment):
@property @property
def rate(self) -> Wei: def rate(self) -> Wei:
fixed_rate = self.agent.rate_per_second() fixed_rate = self.agent.fee_rate()
return Wei(fixed_rate) return Wei(fixed_rate)
def quote(self, def quote(self,
shares: int,
commencement: Optional[Timestamp] = None, commencement: Optional[Timestamp] = None,
expiration: Optional[Timestamp] = None, expiration: Optional[Timestamp] = None,
duration: Optional[int] = None, duration: Optional[int] = None,
@ -202,19 +207,18 @@ class SubscriptionManagerPayment(ContractPayment):
q = self.Quote( q = self.Quote(
rate=Wei(self.rate), rate=Wei(self.rate),
value=Wei(self.rate * duration), value=Wei(self.rate * duration * shares),
shares=shares,
commencement=Timestamp(commencement), commencement=Timestamp(commencement),
expiration=Timestamp(expiration), expiration=Timestamp(expiration),
duration=duration duration=duration
) )
return q return q
def validate_price(self, value: Wei, duration: Wei, *args, **kwargs) -> bool: def validate_price(self, value: Wei, duration: Wei, shares: int, *args, **kwargs) -> bool:
if value and duration: expected_price = Wei(shares * duration * self.rate)
if duration != value // self.rate: if value != expected_price:
raise ValueError(f"Invalid duration ({duration}) for value ({value}).") raise ValueError(f"Policy value ({value}) doesn't match expected value ({expected_price})")
if value != duration * self.rate:
raise ValueError(f"Invalid value ({value}) for duration ({duration}).")
return True return True