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

Rework ritual initiation script for use on mainnet #297

Merged
merged 11 commits into from
Aug 9, 2024
32 changes: 21 additions & 11 deletions deployment/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@

import deployment

#
# Filesystem
#

DEPLOYMENT_DIR = Path(deployment.__file__).parent
CONSTRUCTOR_PARAMS_DIR = DEPLOYMENT_DIR / "constructor_params"
ARTIFACTS_DIR = DEPLOYMENT_DIR / "artifacts"
OZ_DEPENDENCY = project.dependencies["openzeppelin"]["5.0.0"]

#
# Domains
Expand All @@ -20,7 +23,7 @@
SUPPORTED_TACO_DOMAINS = [LYNX, TAPIR, MAINNET]

#
# Nodes
# Testnet
#

LYNX_NODES = {
Expand All @@ -39,18 +42,25 @@
"0xcbE2F626d84c556AbA674FABBbBDdbED6B39d87b": "0xb057B982fB575509047e90cf5087c9B863a2022d",
}

# EIP1967
#
# Contracts
#

OZ_DEPENDENCY = project.dependencies["openzeppelin"]["5.0.0"]

# EIP1967 Admin slot - https://eips.ethereum.org/EIPS/eip-1967#admin-address
EIP1967_ADMIN_SLOT = 0xB53127684A568B3173AE13B9F8A6016E243E63B6E8EE1178D6A717850B5D6103

# Admin slot - https://eips.ethereum.org/EIPS/eip-1967#admin-address
EIP1967_ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103
ACCESS_CONTROLLERS = ["GlobalAllowList", "OpenAccessAuthorizer", "ManagedAllowList"]

FEE_MODELS = ["FreeFeeModel", "BqETHSubscription"]

#
# Contracts
# Sampling
#

ACCESS_CONTROLLERS = [
"GlobalAllowList",
"OpenAccessAuthorizer",
"ManagedAllowList"
]
PORTER_ENDPOINTS = {
KPrasch marked this conversation as resolved.
Show resolved Hide resolved
MAINNET: "https://porter.nucypher.io/bucket_sampling",
LYNX: "https://porter-lynx.nucypher.io/get_ursulas",
TAPIR: "https://porter-tapir.nucypher.io/get_ursulas",
}
32 changes: 32 additions & 0 deletions deployment/types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import click
from eth_utils import to_checksum_address


class MinInt(click.ParamType):
name = "minint"

def __init__(self, min_value):
self.min_value = min_value

def convert(self, value, param, ctx):
try:
ivalue = int(value)
except ValueError:
self.fail(f"{value} is not a valid integer", param, ctx)
if ivalue < self.min_value:
self.fail(
f"{value} is less than the minimum allowed value of {self.min_value}", param, ctx
)
return ivalue


class ChecksumAddress(click.ParamType):
name = "checksum_address"

def convert(self, value, param, ctx):
try:
value = to_checksum_address(value=value)
except ValueError:
self.fail("Invalid ethereum address")
else:
return value
31 changes: 28 additions & 3 deletions deployment/utils.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import json
import os
from pathlib import Path
from typing import Dict, List
from typing import Dict, List, Optional

import requests
import yaml
from ape import networks, project
from ape.contracts import ContractContainer, ContractInstance
from ape_etherscan.utils import API_KEY_ENV_KEY_MAP

from deployment.constants import ARTIFACTS_DIR
from deployment.constants import ARTIFACTS_DIR, LYNX, MAINNET, PORTER_ENDPOINTS, TAPIR
from deployment.networks import is_local_network


Expand Down Expand Up @@ -48,7 +49,7 @@ def validate_config(config: Dict) -> Path:
config_chain_id = deployment.get("chain_id")
if not config_chain_id:
raise ValueError("chain_id is not set in params file.")

contracts = config.get("contracts")
if not contracts:
raise ValueError("Constructor parameters file missing 'contracts' field.")
Expand Down Expand Up @@ -156,3 +157,27 @@ def registry_filepath_from_domain(domain: str) -> Path:
raise ValueError(f"No registry found for domain '{domain}'")

return p


def sample_nodes(
domain: str, num_nodes: int, random_seed: Optional[int] = None, duration: Optional[int] = None
):
porter_endpoint = PORTER_ENDPOINTS.get(domain)
if not porter_endpoint:
raise ValueError(f"Porter endpoint not found for domain '{domain}'")

params = {
"quantity": num_nodes,
}
if duration:
params["duration"] = duration
if random_seed:
if domain != MAINNET:
raise ValueError("'random_seed' is only a valid parameter for mainnet")
params["random_seed"] = random_seed

response = requests.get(porter_endpoint, params=params)
data = response.json()
result = sorted(data["result"]["ursulas"], key=lambda x: x.lower())

return result
127 changes: 90 additions & 37 deletions scripts/initiate_ritual.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
#!/usr/bin/python3

import click
from ape import project
from ape.cli import ConnectedProviderCommand, account_option, network_option

from deployment.constants import LYNX, LYNX_NODES, SUPPORTED_TACO_DOMAINS, TAPIR, TAPIR_NODES
from deployment import registry
from deployment.constants import ACCESS_CONTROLLERS, FEE_MODELS, SUPPORTED_TACO_DOMAINS
from deployment.params import Transactor
from deployment.registry import contracts_from_registry
from deployment.utils import check_plugins, registry_filepath_from_domain
from deployment.types import ChecksumAddress, MinInt
from deployment.utils import check_plugins, sample_nodes


@click.command(cls=ConnectedProviderCommand)
@network_option(required=True)
@click.command(cls=ConnectedProviderCommand, name="initiate-ritual")
@account_option()
@network_option(required=True)
@click.option(
"--domain",
"-d",
Expand All @@ -23,48 +23,101 @@
@click.option(
"--duration",
"-t",
help="Duration of the ritual",
type=int,
default=86400,
show_default=True,
help="Duration of the ritual in seconds. Must be at least 24h.",
type=MinInt(86400),
required=True,
)
@click.option(
"--access-controller",
"-c",
help="The registry name of an access controller contract.",
type=click.Choice(ACCESS_CONTROLLERS),
required=True,
)
@click.option(
"--fee-model",
"-f",
help="The name of a fee model contract.",
type=click.Choice(FEE_MODELS),
required=True,
)
@click.option(
"--authority",
"-a",
help="global allow list or open access authorizer.",
type=click.Choice(["GlobalAllowList", "OpenAccessAuthorizer", "ManagedAllowList"]),
help="The ethereum address of the ritual authority.",
required=True,
type=ChecksumAddress(),
)
def cli(domain, duration, network, account, access_controller):
@click.option(
"--num-nodes",
"-n",
help="Number of nodes to use for the ritual.",
type=int,
)
@click.option(
"--random-seed",
"-r",
help="Random seed integer for bucket sampling on mainnet.",
type=int,
)
@click.option(
"--handpicked",
help="The filepath of a file containing newline separated staking provider addresses.",
type=click.File("r"),
)
def cli(
domain,
account,
network,
duration,
access_controller,
fee_model,
authority,
num_nodes,
random_seed,
handpicked,
):
"""Initiate a ritual for a TACo domain."""

# Setup
check_plugins()
print(f"Using network: {network}")
print(f"Using domain: {domain}")
print(f"Using account: {account}")
transactor = Transactor(account=account)
click.echo(f"Connected to {network.name} network.")
if not (bool(handpicked) ^ (num_nodes is not None)):
raise click.BadOptionUsage(
option_name="--num-nodes",
message=f"Specify either --num-nodes or --handpicked; got {num_nodes} {handpicked}",
KPrasch marked this conversation as resolved.
Show resolved Hide resolved
)
if handpicked and random_seed:
raise click.BadOptionUsage(
option_name="--random-seed",
message="Cannot specify --random-seed when using --handpicked.",
)

if domain == LYNX:
providers = list(sorted(LYNX_NODES.keys()))
elif domain == TAPIR:
providers = list(sorted(TAPIR_NODES.keys()))
# Get the staking providers in the ritual cohort
if handpicked:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's cool!

providers = sorted(line.lower() for line in handpicked)
if not providers:
raise ValueError(f"No staking providers found in the handpicked file {handpicked.name}")
else:
# mainnet sampling not currently supported
raise ValueError(f"Sampling of providers not supported for domain '{domain}'")

registry_filepath = registry_filepath_from_domain(domain=domain)

chain_id = project.chain_manager.chain_id
deployments = contracts_from_registry(filepath=registry_filepath, chain_id=chain_id)
coordinator = deployments[project.Coordinator.contract_type.name]
providers = sample_nodes(
domain=domain, num_nodes=num_nodes, duration=duration, random_seed=random_seed
)

access_controller = deployments[getattr(project, access_controller).contract_type.name]
authority = transactor.get_account().address
# Get the contracts from the registry
coordinator = registry.get_contract(domain=domain, contract_name="Coordinator")
access_controller = registry.get_contract(domain=domain, contract_name=access_controller)
fee_model = registry.get_contract(domain=domain, contract_name=fee_model)

while True:
transactor.transact(
coordinator.initiateRitual, providers, authority, duration, access_controller.address
)
if not input("Another? [y/n] ").lower().startswith("y"):
break
# Initiate the ritual
transactor = Transactor(account=account)
transactor.transact(
coordinator.initiateRitual,
fee_model.address,
providers,
authority,
duration,
access_controller.address,
)


if __name__ == "__main__":
Expand Down
14 changes: 10 additions & 4 deletions scripts/manage_subscription.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import click
from ape import Contract
from ape.cli import account_option, ConnectedProviderCommand
from ape.cli import account_option, ConnectedProviderCommand, network_option

from deployment import registry
from deployment.options import (
Expand Down Expand Up @@ -51,6 +51,7 @@ def cli():

@cli.command(cls=ConnectedProviderCommand)
@account_option()
@network_option(required=True)
@domain_option
@subscription_contract_option
@encryptor_slots_option
Expand All @@ -59,9 +60,10 @@ def cli():
default=0,
help="Subscription billing period number to pay for.",
)
def pay_subscription(account, domain, subscription_contract, encryptor_slots, period):
def pay_subscription(account, network, domain, subscription_contract, encryptor_slots, period):
"""Pay for a new subscription period and initial encryptor slots."""
check_plugins()
click.echo(f"Connected to {network.name} network.")
transactor = Transactor(account=account)
subscription_contract = registry.get_contract(
contract_name=subscription_contract,
Expand Down Expand Up @@ -92,12 +94,14 @@ def pay_subscription(account, domain, subscription_contract, encryptor_slots, pe

@cli.command(cls=ConnectedProviderCommand)
@account_option()
@network_option(required=True)
@domain_option
@subscription_contract_option
@encryptor_slots_option
def pay_slots(account, domain, subscription_contract, encryptor_slots):
def pay_slots(account, network, domain, subscription_contract, encryptor_slots):
"""Pay for additional encryptor slots in the current billing period."""
check_plugins()
click.echo(f"Connected to {network.name} network.")
transactor = Transactor(account=account)
subscription_contract = registry.get_contract(
contract_name=subscription_contract,
Expand All @@ -123,12 +127,14 @@ def pay_slots(account, domain, subscription_contract, encryptor_slots):

@cli.command(cls=ConnectedProviderCommand)
@account_option()
@network_option(required=True)
@domain_option
@ritual_id_option
@access_controller_option
@encryptors_option
def add_encryptors(account, domain, ritual_id, access_controller, encryptors):
def add_encryptors(account, network, domain, ritual_id, access_controller, encryptors):
"""Authorize encryptors to the access control contract for a ritual."""
click.echo(f"Connected to {network.name} network.")
access_controller = registry.get_contract(
contract_name=access_controller,
domain=domain
Expand Down
Loading