mirror of https://github.com/nucypher/nucypher.git
Merge pull request #2860 from cygnusv/sm
Adapt "Hello Operator" to new interface of SubscriptionManagerpull/2869/head
commit
966d6c08c4
|
@ -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, ...]:
|
||||||
|
|
|
@ -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):
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue