Compare commits
17 Commits
main
...
gw.dao_wal
Author | SHA1 | Date |
---|---|---|
Geoff Walmsley | e5ec333d62 | |
Geoff Walmsley | 6f5d997006 | |
Geoff Walmsley | f5d9ddc51f | |
Matthew Howard | b2f13b8d01 | |
Matthew Howard | ade81b18e3 | |
Matthew Howard | 2535a8b2d9 | |
matt-o-how | d3f61ba42b | |
Geoff Walmsley | 17727a4455 | |
Adam Kelly | ceb62d6a96 | |
Adam Kelly | ac6ff8b73a | |
Adam Kelly | 381260b3d1 | |
Adam Kelly | 41b4cd30f7 | |
Adam Kelly | d106a35c65 | |
Adam Kelly | 4ccaa22075 | |
Matthew Howard | e012ea7ecd | |
Adam Kelly | 413263cdad | |
Adam Kelly | e10960f132 |
4
.flake8
4
.flake8
|
@ -7,3 +7,7 @@ per-file-ignores =
|
|||
tests/util/test_network_protocol_files.py:F405
|
||||
tests/util/test_network_protocol_json.py:F405
|
||||
tests/util/protocol_messages_json.py:E501
|
||||
chia/wallet/dao_wallet/dao_utils.py:E501
|
||||
chia/wallet/dao_wallet/dao_wallet.py:E501
|
||||
chia/wallet/cat_wallet/dao_cat_wallet.py:E501
|
||||
tests/wallet/dao_wallet/test_dao_clvm.py:E501
|
||||
|
|
|
@ -9,6 +9,7 @@ from chia import __version__
|
|||
from chia.cmds.beta import beta_cmd
|
||||
from chia.cmds.completion import completion
|
||||
from chia.cmds.configure import configure_cmd
|
||||
from chia.cmds.dao import dao_cmd
|
||||
from chia.cmds.data import data_cmd
|
||||
from chia.cmds.db import db_cmd
|
||||
from chia.cmds.dev import dev_cmd
|
||||
|
@ -128,6 +129,7 @@ cli.add_command(data_cmd)
|
|||
cli.add_command(passphrase_cmd)
|
||||
cli.add_command(beta_cmd)
|
||||
cli.add_command(completion)
|
||||
cli.add_command(dao_cmd)
|
||||
cli.add_command(dev_cmd)
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,940 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
|
||||
import click
|
||||
|
||||
from chia.cmds.cmds_util import execute_with_wallet
|
||||
from chia.cmds.plotnft import validate_fee
|
||||
|
||||
|
||||
@click.group("dao", short_help="Create, manage or show state of DAOs", no_args_is_help=True)
|
||||
@click.pass_context
|
||||
def dao_cmd(ctx: click.Context) -> None:
|
||||
pass
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------------------
|
||||
# ADD
|
||||
|
||||
|
||||
@dao_cmd.command("add", short_help="Create a wallet for an existing DAO", no_args_is_help=True)
|
||||
@click.option(
|
||||
"-wp",
|
||||
"--wallet-rpc-port",
|
||||
help="Set the port where the Wallet is hosting the RPC interface. See the rpc_port under wallet in config.yaml",
|
||||
type=int,
|
||||
default=None,
|
||||
)
|
||||
@click.option("-f", "--fingerprint", help="Set the fingerprint to specify which key to use", type=int)
|
||||
@click.option("-n", "--name", help="Set the DAO wallet name", type=str)
|
||||
@click.option(
|
||||
"-t",
|
||||
"--treasury-id",
|
||||
help="The Treasury ID of the DAO you want to track",
|
||||
type=str,
|
||||
required=True,
|
||||
)
|
||||
@click.option(
|
||||
"-fa",
|
||||
"--filter-amount",
|
||||
help="The minimum number of votes a proposal needs before the wallet will recognise it",
|
||||
type=int,
|
||||
default=1,
|
||||
show_default=True,
|
||||
)
|
||||
def dao_add_cmd(
|
||||
wallet_rpc_port: Optional[int],
|
||||
fingerprint: int,
|
||||
treasury_id: str,
|
||||
filter_amount: int,
|
||||
name: Optional[str],
|
||||
) -> None:
|
||||
import asyncio
|
||||
|
||||
from .dao_funcs import add_dao_wallet
|
||||
|
||||
extra_params = {
|
||||
"name": name,
|
||||
"treasury_id": treasury_id,
|
||||
"filter_amount": filter_amount,
|
||||
}
|
||||
asyncio.run(execute_with_wallet(wallet_rpc_port, fingerprint, extra_params, add_dao_wallet))
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------------------
|
||||
# CREATE
|
||||
|
||||
|
||||
@dao_cmd.command("create", short_help="Create a new DAO wallet and treasury", no_args_is_help=True)
|
||||
@click.option(
|
||||
"-wp",
|
||||
"--wallet-rpc-port",
|
||||
help="Set the port where the Wallet is hosting the RPC interface. See the rpc_port under wallet in config.yaml",
|
||||
type=int,
|
||||
default=None,
|
||||
)
|
||||
@click.option("-f", "--fingerprint", help="Set the fingerprint to specify which key to use", type=int)
|
||||
@click.option("-n", "--name", help="Set the DAO wallet name", type=str)
|
||||
@click.option(
|
||||
"-pt",
|
||||
"--proposal-timelock",
|
||||
help="The minimum number of blocks before a proposal can close",
|
||||
type=int,
|
||||
default=1000,
|
||||
show_default=True,
|
||||
)
|
||||
@click.option(
|
||||
"-sc",
|
||||
"--soft-close",
|
||||
help="The number of blocks a proposal must remain unspent before closing",
|
||||
type=int,
|
||||
default=20,
|
||||
show_default=True,
|
||||
)
|
||||
@click.option(
|
||||
"-ar",
|
||||
"--attendance-required",
|
||||
help="The minimum number of votes a proposal must receive to be accepted",
|
||||
type=int,
|
||||
required=True,
|
||||
)
|
||||
@click.option(
|
||||
"-pp",
|
||||
"--pass-percentage",
|
||||
help="The percentage of 'yes' votes in basis points a proposal must receive to be accepted. 100% = 10000",
|
||||
type=int,
|
||||
default=5000,
|
||||
show_default=True,
|
||||
)
|
||||
@click.option(
|
||||
"-sd",
|
||||
"--self-destruct",
|
||||
help="The number of blocks required before a proposal can be automatically removed",
|
||||
type=int,
|
||||
default=10000,
|
||||
show_default=True,
|
||||
)
|
||||
@click.option(
|
||||
"-od",
|
||||
"--oracle-delay",
|
||||
help="The number of blocks required between oracle spends of the treasury",
|
||||
type=int,
|
||||
default=50,
|
||||
show_default=True,
|
||||
)
|
||||
@click.option(
|
||||
"-fa",
|
||||
"--filter-amount",
|
||||
help="The minimum number of votes a proposal needs before the wallet will recognise it",
|
||||
type=int,
|
||||
default=1,
|
||||
show_default=True,
|
||||
)
|
||||
@click.option(
|
||||
"-ca",
|
||||
"--cat-amount",
|
||||
help="The number of DAO CATs (in mojos) to create when initializing the DAO",
|
||||
type=int,
|
||||
required=True,
|
||||
)
|
||||
@click.option(
|
||||
"-m",
|
||||
"--fee",
|
||||
help="Set the fees per transaction, in XCH.",
|
||||
type=str,
|
||||
default="0",
|
||||
show_default=True,
|
||||
callback=validate_fee,
|
||||
)
|
||||
@click.option(
|
||||
"--reuse",
|
||||
help="Reuse existing address for the change.",
|
||||
is_flag=True,
|
||||
default=False,
|
||||
)
|
||||
def dao_create_cmd(
|
||||
wallet_rpc_port: Optional[int],
|
||||
fingerprint: int,
|
||||
proposal_timelock: int,
|
||||
soft_close: int,
|
||||
attendance_required: int,
|
||||
pass_percentage: int,
|
||||
self_destruct: int,
|
||||
oracle_delay: int,
|
||||
filter_amount: int,
|
||||
cat_amount: int,
|
||||
name: Optional[str],
|
||||
fee: str,
|
||||
reuse: bool,
|
||||
) -> None:
|
||||
import asyncio
|
||||
|
||||
from .dao_funcs import create_dao_wallet
|
||||
|
||||
print("Creating new DAO")
|
||||
|
||||
extra_params = {
|
||||
"fee": fee,
|
||||
"name": name,
|
||||
"proposal_timelock": proposal_timelock,
|
||||
"soft_close_length": soft_close,
|
||||
"attendance_required": attendance_required,
|
||||
"pass_percentage": pass_percentage,
|
||||
"self_destruct_length": self_destruct,
|
||||
"oracle_spend_delay": oracle_delay,
|
||||
"filter_amount": filter_amount,
|
||||
"amount_of_cats": cat_amount,
|
||||
"reuse_puzhash": True if reuse else None,
|
||||
}
|
||||
asyncio.run(execute_with_wallet(wallet_rpc_port, fingerprint, extra_params, create_dao_wallet))
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------------------
|
||||
# TREASURY FUNDS
|
||||
|
||||
|
||||
@dao_cmd.command("add-funds", short_help="Send funds to a DAO treasury", no_args_is_help=True)
|
||||
@click.option(
|
||||
"-wp",
|
||||
"--wallet-rpc-port",
|
||||
help="Set the port where the Wallet is hosting the RPC interface. See the rpc_port under wallet in config.yaml",
|
||||
type=int,
|
||||
default=None,
|
||||
)
|
||||
@click.option("-f", "--fingerprint", help="Set the fingerprint to specify which key to use", type=int)
|
||||
@click.option("-i", "--wallet-id", help="Id of the wallet to use", type=int, required=True)
|
||||
@click.option(
|
||||
"-f",
|
||||
"--funding-wallet-id",
|
||||
help="The ID of the wallet to send funds from",
|
||||
type=int,
|
||||
required=True,
|
||||
)
|
||||
@click.option(
|
||||
"-a",
|
||||
"--amount",
|
||||
help="The amount of funds to send",
|
||||
type=str,
|
||||
required=True,
|
||||
)
|
||||
@click.option(
|
||||
"-m",
|
||||
"--fee",
|
||||
help="Set the fees per transaction, in XCH.",
|
||||
type=str,
|
||||
default="0",
|
||||
show_default=True,
|
||||
callback=validate_fee,
|
||||
)
|
||||
@click.option(
|
||||
"--reuse",
|
||||
help="Reuse existing address for the change.",
|
||||
is_flag=True,
|
||||
default=False,
|
||||
)
|
||||
def dao_add_funds_cmd(
|
||||
wallet_rpc_port: Optional[int],
|
||||
fingerprint: int,
|
||||
wallet_id: int,
|
||||
funding_wallet_id: int,
|
||||
amount: str,
|
||||
fee: str,
|
||||
reuse: bool,
|
||||
) -> None:
|
||||
import asyncio
|
||||
|
||||
from .dao_funcs import add_funds_to_treasury
|
||||
|
||||
extra_params = {
|
||||
"wallet_id": wallet_id,
|
||||
"fee": fee,
|
||||
"funding_wallet_id": funding_wallet_id,
|
||||
"amount": amount,
|
||||
"reuse_puzhash": True if reuse else None,
|
||||
}
|
||||
asyncio.run(execute_with_wallet(wallet_rpc_port, fingerprint, extra_params, add_funds_to_treasury))
|
||||
|
||||
|
||||
@dao_cmd.command("get-balance", short_help="Get the asset balances for a DAO treasury", no_args_is_help=True)
|
||||
@click.option(
|
||||
"-wp",
|
||||
"--wallet-rpc-port",
|
||||
help="Set the port where the Wallet is hosting the RPC interface. See the rpc_port under wallet in config.yaml",
|
||||
type=int,
|
||||
default=None,
|
||||
)
|
||||
@click.option("-f", "--fingerprint", help="Set the fingerprint to specify which key to use", type=int)
|
||||
@click.option("-i", "--wallet-id", help="Id of the wallet to use", type=int, required=True)
|
||||
def dao_get_balance_cmd(
|
||||
wallet_rpc_port: Optional[int],
|
||||
fingerprint: int,
|
||||
wallet_id: int,
|
||||
) -> None:
|
||||
import asyncio
|
||||
|
||||
from .dao_funcs import get_treasury_balance
|
||||
|
||||
extra_params = {
|
||||
"wallet_id": wallet_id,
|
||||
}
|
||||
asyncio.run(execute_with_wallet(wallet_rpc_port, fingerprint, extra_params, get_treasury_balance))
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------------------
|
||||
# LIST/SHOW PROPOSALS
|
||||
|
||||
|
||||
@dao_cmd.command("list-proposals", short_help="List proposals for the DAO", no_args_is_help=True)
|
||||
@click.option(
|
||||
"-wp",
|
||||
"--wallet-rpc-port",
|
||||
help="Set the port where the Wallet is hosting the RPC interface. See the rpc_port under wallet in config.yaml",
|
||||
type=int,
|
||||
default=None,
|
||||
)
|
||||
@click.option("-f", "--fingerprint", help="Set the fingerprint to specify which key to use", type=int)
|
||||
@click.option("-i", "--wallet-id", help="Id of the wallet to use", type=int, required=True)
|
||||
@click.option(
|
||||
"-c",
|
||||
"--include-closed",
|
||||
help="Include previously closed proposals",
|
||||
is_flag=True,
|
||||
)
|
||||
def dao_list_proposals_cmd(
|
||||
wallet_rpc_port: Optional[int],
|
||||
fingerprint: int,
|
||||
wallet_id: int,
|
||||
include_closed: Optional[bool],
|
||||
) -> None:
|
||||
import asyncio
|
||||
|
||||
from .dao_funcs import list_proposals
|
||||
|
||||
if not include_closed:
|
||||
include_closed = False
|
||||
|
||||
extra_params = {
|
||||
"wallet_id": wallet_id,
|
||||
"include_closed": include_closed,
|
||||
}
|
||||
asyncio.run(execute_with_wallet(wallet_rpc_port, fingerprint, extra_params, list_proposals))
|
||||
|
||||
|
||||
@dao_cmd.command("show-proposal", short_help="Show the details of a specific proposal", no_args_is_help=True)
|
||||
@click.option(
|
||||
"-wp",
|
||||
"--wallet-rpc-port",
|
||||
help="Set the port where the Wallet is hosting the RPC interface. See the rpc_port under wallet in config.yaml",
|
||||
type=int,
|
||||
default=None,
|
||||
)
|
||||
@click.option("-f", "--fingerprint", help="Set the fingerprint to specify which key to use", type=int)
|
||||
@click.option("-i", "--wallet-id", help="Id of the wallet to use", type=int, required=True)
|
||||
@click.option(
|
||||
"-p",
|
||||
"--proposal_id",
|
||||
help="The ID of the proposal to fetch",
|
||||
type=str,
|
||||
required=True,
|
||||
)
|
||||
def dao_show_proposal_cmd(
|
||||
wallet_rpc_port: Optional[int],
|
||||
fingerprint: int,
|
||||
wallet_id: int,
|
||||
proposal_id: str,
|
||||
) -> None:
|
||||
import asyncio
|
||||
|
||||
from .dao_funcs import show_proposal
|
||||
|
||||
extra_params = {
|
||||
"wallet_id": wallet_id,
|
||||
"proposal_id": proposal_id,
|
||||
}
|
||||
asyncio.run(execute_with_wallet(wallet_rpc_port, fingerprint, extra_params, show_proposal))
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------------------
|
||||
# VOTE
|
||||
|
||||
|
||||
@dao_cmd.command("vote", short_help="Vote on a DAO proposal", no_args_is_help=True)
|
||||
@click.option(
|
||||
"-wp",
|
||||
"--wallet-rpc-port",
|
||||
help="Set the port where the Wallet is hosting the RPC interface. See the rpc_port under wallet in config.yaml",
|
||||
type=int,
|
||||
default=None,
|
||||
)
|
||||
@click.option("-f", "--fingerprint", help="Set the fingerprint to specify which key to use", type=int)
|
||||
@click.option("-i", "--wallet-id", help="Id of the wallet to use", type=int, required=True)
|
||||
@click.option(
|
||||
"-p",
|
||||
"--proposal-id",
|
||||
help="The ID of the proposal you are voting on",
|
||||
type=str,
|
||||
required=True,
|
||||
)
|
||||
@click.option(
|
||||
"-a",
|
||||
"--vote-amount",
|
||||
help="The number of votes you want to cast",
|
||||
type=int,
|
||||
required=True,
|
||||
)
|
||||
@click.option(
|
||||
"-n",
|
||||
"--vote-no",
|
||||
help="Use this option to vote against a proposal. If not present then the vote is for the proposal",
|
||||
is_flag=True,
|
||||
)
|
||||
@click.option(
|
||||
"-m",
|
||||
"--fee",
|
||||
help="Set the fees per transaction, in XCH.",
|
||||
type=str,
|
||||
default="0",
|
||||
show_default=True,
|
||||
callback=validate_fee,
|
||||
)
|
||||
@click.option(
|
||||
"--reuse",
|
||||
help="Reuse existing address for the change.",
|
||||
is_flag=True,
|
||||
default=False,
|
||||
)
|
||||
def dao_vote_cmd(
|
||||
wallet_rpc_port: Optional[int],
|
||||
fingerprint: int,
|
||||
wallet_id: int,
|
||||
proposal_id: str,
|
||||
vote_amount: int,
|
||||
vote_no: Optional[bool],
|
||||
fee: str,
|
||||
reuse: bool,
|
||||
) -> None:
|
||||
import asyncio
|
||||
|
||||
from .dao_funcs import vote_on_proposal
|
||||
|
||||
is_yes_vote = False if vote_no else True
|
||||
|
||||
extra_params = {
|
||||
"wallet_id": wallet_id,
|
||||
"fee": fee,
|
||||
"proposal_id": proposal_id,
|
||||
"vote_amount": vote_amount,
|
||||
"is_yes_vote": is_yes_vote,
|
||||
"reuse_puzhash": True if reuse else None,
|
||||
}
|
||||
asyncio.run(execute_with_wallet(wallet_rpc_port, fingerprint, extra_params, vote_on_proposal))
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------------------
|
||||
# CLOSE PROPOSALS
|
||||
|
||||
|
||||
@dao_cmd.command("close-proposal", short_help="Close a DAO proposal", no_args_is_help=True)
|
||||
@click.option(
|
||||
"-wp",
|
||||
"--wallet-rpc-port",
|
||||
help="Set the port where the Wallet is hosting the RPC interface. See the rpc_port under wallet in config.yaml",
|
||||
type=int,
|
||||
default=None,
|
||||
)
|
||||
@click.option("-f", "--fingerprint", help="Set the fingerprint to specify which key to use", type=int)
|
||||
@click.option("-i", "--wallet-id", help="Id of the wallet to use", type=int, required=True)
|
||||
@click.option(
|
||||
"-p",
|
||||
"--proposal-id",
|
||||
help="The ID of the proposal you are voting on",
|
||||
type=str,
|
||||
required=True,
|
||||
)
|
||||
@click.option(
|
||||
"-m",
|
||||
"--fee",
|
||||
help="Set the fees per transaction, in XCH.",
|
||||
type=str,
|
||||
default="0",
|
||||
show_default=True,
|
||||
callback=validate_fee,
|
||||
)
|
||||
@click.option(
|
||||
"--reuse",
|
||||
help="Reuse existing address for the change.",
|
||||
is_flag=True,
|
||||
default=False,
|
||||
)
|
||||
def dao_close_proposal_cmd(
|
||||
wallet_rpc_port: Optional[int],
|
||||
fingerprint: int,
|
||||
wallet_id: int,
|
||||
proposal_id: str,
|
||||
fee: str,
|
||||
reuse: bool,
|
||||
) -> None:
|
||||
import asyncio
|
||||
|
||||
from .dao_funcs import close_proposal
|
||||
|
||||
extra_params = {
|
||||
"wallet_id": wallet_id,
|
||||
"fee": fee,
|
||||
"proposal_id": proposal_id,
|
||||
"reuse_puzhash": True if reuse else None,
|
||||
}
|
||||
asyncio.run(execute_with_wallet(wallet_rpc_port, fingerprint, extra_params, close_proposal))
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------------------
|
||||
# LOCKUP COINS
|
||||
|
||||
|
||||
@dao_cmd.command("lockup-coins", short_help="Lock DAO CATs for voting", no_args_is_help=True)
|
||||
@click.option(
|
||||
"-wp",
|
||||
"--wallet-rpc-port",
|
||||
help="Set the port where the Wallet is hosting the RPC interface. See the rpc_port under wallet in config.yaml",
|
||||
type=int,
|
||||
default=None,
|
||||
)
|
||||
@click.option("-f", "--fingerprint", help="Set the fingerprint to specify which key to use", type=int)
|
||||
@click.option("-i", "--wallet-id", help="Id of the wallet to use", type=int, required=True)
|
||||
@click.option(
|
||||
"-a",
|
||||
"--amount",
|
||||
help="The amount of new cats the proposal will mint",
|
||||
type=str,
|
||||
required=True,
|
||||
)
|
||||
@click.option(
|
||||
"-m",
|
||||
"--fee",
|
||||
help="Set the fees per transaction, in XCH.",
|
||||
type=str,
|
||||
default="0",
|
||||
show_default=True,
|
||||
callback=validate_fee,
|
||||
)
|
||||
@click.option(
|
||||
"--reuse",
|
||||
help="Reuse existing address for the change.",
|
||||
is_flag=True,
|
||||
default=False,
|
||||
)
|
||||
def dao_lockup_coins_cmd(
|
||||
wallet_rpc_port: Optional[int],
|
||||
fingerprint: int,
|
||||
wallet_id: int,
|
||||
amount: str,
|
||||
fee: str,
|
||||
reuse: bool,
|
||||
) -> None:
|
||||
import asyncio
|
||||
|
||||
from .dao_funcs import lockup_coins
|
||||
|
||||
extra_params = {
|
||||
"wallet_id": wallet_id,
|
||||
"fee": fee,
|
||||
"amount": amount,
|
||||
"reuse_puzhash": True if reuse else None,
|
||||
}
|
||||
asyncio.run(execute_with_wallet(wallet_rpc_port, fingerprint, extra_params, lockup_coins))
|
||||
|
||||
|
||||
@dao_cmd.command("release-coins", short_help="Release closed proposals from DAO CATs", no_args_is_help=True)
|
||||
@click.option(
|
||||
"-wp",
|
||||
"--wallet-rpc-port",
|
||||
help="Set the port where the Wallet is hosting the RPC interface. See the rpc_port under wallet in config.yaml",
|
||||
type=int,
|
||||
default=None,
|
||||
)
|
||||
@click.option("-f", "--fingerprint", help="Set the fingerprint to specify which key to use", type=int)
|
||||
@click.option("-i", "--wallet-id", help="Id of the wallet to use", type=int, required=True)
|
||||
@click.option(
|
||||
"-m",
|
||||
"--fee",
|
||||
help="Set the fees per transaction, in XCH.",
|
||||
type=str,
|
||||
default="0",
|
||||
show_default=True,
|
||||
callback=validate_fee,
|
||||
)
|
||||
@click.option(
|
||||
"--reuse",
|
||||
help="Reuse existing address for the change.",
|
||||
is_flag=True,
|
||||
default=False,
|
||||
)
|
||||
def dao_release_coins_cmd(
|
||||
wallet_rpc_port: Optional[int],
|
||||
fingerprint: int,
|
||||
wallet_id: int,
|
||||
fee: str,
|
||||
reuse: bool,
|
||||
) -> None:
|
||||
import asyncio
|
||||
|
||||
from .dao_funcs import release_coins
|
||||
|
||||
extra_params = {
|
||||
"wallet_id": wallet_id,
|
||||
"fee": fee,
|
||||
"reuse_puzhash": True if reuse else None,
|
||||
}
|
||||
asyncio.run(execute_with_wallet(wallet_rpc_port, fingerprint, extra_params, release_coins))
|
||||
|
||||
|
||||
@dao_cmd.command("exit-lockup", short_help="Release DAO CATs from voting mode", no_args_is_help=True)
|
||||
@click.option(
|
||||
"-wp",
|
||||
"--wallet-rpc-port",
|
||||
help="Set the port where the Wallet is hosting the RPC interface. See the rpc_port under wallet in config.yaml",
|
||||
type=int,
|
||||
default=None,
|
||||
)
|
||||
@click.option("-f", "--fingerprint", help="Set the fingerprint to specify which key to use", type=int)
|
||||
@click.option("-i", "--wallet-id", help="Id of the wallet to use", type=int, required=True)
|
||||
@click.option(
|
||||
"-m",
|
||||
"--fee",
|
||||
help="Set the fees per transaction, in XCH.",
|
||||
type=str,
|
||||
default="0",
|
||||
show_default=True,
|
||||
callback=validate_fee,
|
||||
)
|
||||
@click.option(
|
||||
"--reuse",
|
||||
help="Reuse existing address for the change.",
|
||||
is_flag=True,
|
||||
default=False,
|
||||
)
|
||||
def dao_exit_lockup_cmd(
|
||||
wallet_rpc_port: Optional[int],
|
||||
fingerprint: int,
|
||||
wallet_id: int,
|
||||
fee: str,
|
||||
reuse: bool,
|
||||
) -> None:
|
||||
import asyncio
|
||||
|
||||
from .dao_funcs import exit_lockup
|
||||
|
||||
extra_params = {
|
||||
"wallet_id": wallet_id,
|
||||
"fee": fee,
|
||||
"reuse_puzhash": True if reuse else None,
|
||||
}
|
||||
asyncio.run(execute_with_wallet(wallet_rpc_port, fingerprint, extra_params, exit_lockup))
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------------------
|
||||
# CREATE PROPOSALS
|
||||
|
||||
|
||||
@dao_cmd.group("create-proposal", short_help="Create and add a proposal to a DAO", no_args_is_help=True)
|
||||
@click.pass_context
|
||||
def dao_proposal(ctx: click.Context) -> None:
|
||||
pass
|
||||
|
||||
|
||||
@dao_proposal.command("spend", short_help="Create a proposal to spend DAO funds", no_args_is_help=True)
|
||||
@click.option(
|
||||
"-wp",
|
||||
"--wallet-rpc-port",
|
||||
help="Set the port where the Wallet is hosting the RPC interface. See the rpc_port under wallet in config.yaml",
|
||||
type=int,
|
||||
default=None,
|
||||
)
|
||||
@click.option("-f", "--fingerprint", help="Set the fingerprint to specify which key to use", type=int)
|
||||
@click.option("-i", "--wallet-id", help="Id of the wallet to use", type=int, required=True)
|
||||
@click.option(
|
||||
"-t",
|
||||
"--to-address",
|
||||
help="The address the proposal will send funds to",
|
||||
type=str,
|
||||
required=False,
|
||||
default=None,
|
||||
)
|
||||
@click.option(
|
||||
"-a",
|
||||
"--amount",
|
||||
help="The amount of funds the proposal will send (in mojos)",
|
||||
type=float,
|
||||
required=False,
|
||||
default=None,
|
||||
)
|
||||
@click.option(
|
||||
"-v",
|
||||
"--vote-amount",
|
||||
help="The number of votes to add",
|
||||
type=int,
|
||||
required=True,
|
||||
)
|
||||
@click.option(
|
||||
"-id",
|
||||
"--asset-id",
|
||||
help="The asset id of the funds the proposal will send. Leave blank for xch",
|
||||
type=str,
|
||||
required=False,
|
||||
default=None,
|
||||
)
|
||||
@click.option(
|
||||
"-j",
|
||||
"--from-json",
|
||||
help="Path to a json file containing a list of additions, for use in proposals with multiple spends",
|
||||
type=str,
|
||||
required=False,
|
||||
default=None,
|
||||
)
|
||||
@click.option(
|
||||
"-m",
|
||||
"--fee",
|
||||
help="Set the fees per transaction, in XCH.",
|
||||
type=str,
|
||||
default="0",
|
||||
show_default=True,
|
||||
callback=validate_fee,
|
||||
)
|
||||
@click.option(
|
||||
"--reuse",
|
||||
help="Reuse existing address for the change.",
|
||||
is_flag=True,
|
||||
default=False,
|
||||
)
|
||||
def dao_create_spend_proposal_cmd(
|
||||
wallet_rpc_port: Optional[int],
|
||||
fingerprint: int,
|
||||
wallet_id: int,
|
||||
vote_amount: int,
|
||||
to_address: Optional[str],
|
||||
amount: Optional[float],
|
||||
asset_id: Optional[str],
|
||||
from_json: Optional[str],
|
||||
fee: str,
|
||||
reuse: bool,
|
||||
) -> None:
|
||||
import asyncio
|
||||
|
||||
from .dao_funcs import create_spend_proposal
|
||||
|
||||
extra_params = {
|
||||
"wallet_id": wallet_id,
|
||||
"fee": fee,
|
||||
"vote_amount": vote_amount,
|
||||
"to_address": to_address,
|
||||
"amount": amount,
|
||||
"asset_id": asset_id,
|
||||
"from_json": from_json,
|
||||
"reuse_puzhash": True if reuse else None,
|
||||
}
|
||||
asyncio.run(execute_with_wallet(wallet_rpc_port, fingerprint, extra_params, create_spend_proposal))
|
||||
|
||||
|
||||
@dao_proposal.command("update", short_help="Create a proposal to change the DAO rules", no_args_is_help=True)
|
||||
@click.option(
|
||||
"-wp",
|
||||
"--wallet-rpc-port",
|
||||
help="Set the port where the Wallet is hosting the RPC interface. See the rpc_port under wallet in config.yaml",
|
||||
type=int,
|
||||
default=None,
|
||||
)
|
||||
@click.option("-f", "--fingerprint", help="Set the fingerprint to specify which key to use", type=int)
|
||||
@click.option("-i", "--wallet-id", help="Id of the wallet to use", type=int, required=True)
|
||||
@click.option(
|
||||
"-v",
|
||||
"--vote-amount",
|
||||
help="The number of votes to add",
|
||||
type=int,
|
||||
required=True,
|
||||
)
|
||||
@click.option(
|
||||
"-pt",
|
||||
"--proposal-timelock",
|
||||
help="The new minimum number of blocks before a proposal can close",
|
||||
type=int,
|
||||
default=None,
|
||||
required=False,
|
||||
)
|
||||
@click.option(
|
||||
"-sc",
|
||||
"--soft-close",
|
||||
help="The number of blocks a proposal must remain unspent before closing",
|
||||
type=int,
|
||||
default=None,
|
||||
required=False,
|
||||
)
|
||||
@click.option(
|
||||
"-ar",
|
||||
"--attendance-required",
|
||||
help="The minimum number of votes a proposal must receive to be accepted",
|
||||
type=int,
|
||||
default=None,
|
||||
required=False,
|
||||
)
|
||||
@click.option(
|
||||
"-pp",
|
||||
"--pass-percentage",
|
||||
help="The percentage of 'yes' votes in basis points a proposal must receive to be accepted. 100% = 10000",
|
||||
type=int,
|
||||
default=None,
|
||||
required=False,
|
||||
)
|
||||
@click.option(
|
||||
"-sd",
|
||||
"--self-destruct",
|
||||
help="The number of blocks required before a proposal can be automatically removed",
|
||||
type=int,
|
||||
default=None,
|
||||
required=False,
|
||||
)
|
||||
@click.option(
|
||||
"-od",
|
||||
"--oracle-delay",
|
||||
help="The number of blocks required between oracle spends of the treasury",
|
||||
type=int,
|
||||
default=None,
|
||||
required=False,
|
||||
)
|
||||
@click.option(
|
||||
"-m",
|
||||
"--fee",
|
||||
help="Set the fees per transaction, in XCH.",
|
||||
type=str,
|
||||
default="0",
|
||||
show_default=True,
|
||||
callback=validate_fee,
|
||||
)
|
||||
@click.option(
|
||||
"--reuse",
|
||||
help="Reuse existing address for the change.",
|
||||
is_flag=True,
|
||||
default=False,
|
||||
)
|
||||
def dao_create_update_proposal_cmd(
|
||||
wallet_rpc_port: Optional[int],
|
||||
fingerprint: int,
|
||||
wallet_id: int,
|
||||
vote_amount: int,
|
||||
proposal_timelock: Optional[int],
|
||||
soft_close: Optional[int],
|
||||
attendance_required: Optional[int],
|
||||
pass_percentage: Optional[int],
|
||||
self_destruct: Optional[int],
|
||||
oracle_delay: Optional[int],
|
||||
fee: str,
|
||||
reuse: bool,
|
||||
) -> None:
|
||||
import asyncio
|
||||
|
||||
from .dao_funcs import create_update_proposal
|
||||
|
||||
extra_params = {
|
||||
"wallet_id": wallet_id,
|
||||
"fee": fee,
|
||||
"vote_amount": vote_amount,
|
||||
"proposal_timelock": proposal_timelock,
|
||||
"soft_close_length": soft_close,
|
||||
"attendance_required": attendance_required,
|
||||
"pass_percentage": pass_percentage,
|
||||
"self_destruct_length": self_destruct,
|
||||
"oracle_spend_delay": oracle_delay,
|
||||
"reuse_puzhash": True if reuse else None,
|
||||
}
|
||||
asyncio.run(execute_with_wallet(wallet_rpc_port, fingerprint, extra_params, create_update_proposal))
|
||||
|
||||
|
||||
@dao_proposal.command("mint", short_help="Create a proposal to mint new DAO CATs", no_args_is_help=True)
|
||||
@click.option(
|
||||
"-wp",
|
||||
"--wallet-rpc-port",
|
||||
help="Set the port where the Wallet is hosting the RPC interface. See the rpc_port under wallet in config.yaml",
|
||||
type=int,
|
||||
default=None,
|
||||
)
|
||||
@click.option("-f", "--fingerprint", help="Set the fingerprint to specify which key to use", type=int)
|
||||
@click.option("-i", "--wallet-id", help="Id of the wallet to use", type=int, required=True)
|
||||
@click.option(
|
||||
"-a",
|
||||
"--amount",
|
||||
help="The amount of new cats the proposal will mint (in mojos)",
|
||||
type=int,
|
||||
required=True,
|
||||
)
|
||||
@click.option(
|
||||
"-t",
|
||||
"--to-address",
|
||||
help="The address new cats will be minted to",
|
||||
type=str,
|
||||
required=True,
|
||||
default=None,
|
||||
)
|
||||
@click.option(
|
||||
"-v",
|
||||
"--vote-amount",
|
||||
help="The number of votes to add",
|
||||
type=int,
|
||||
required=True,
|
||||
)
|
||||
@click.option(
|
||||
"-m",
|
||||
"--fee",
|
||||
help="Set the fees per transaction, in XCH.",
|
||||
type=str,
|
||||
default="0",
|
||||
show_default=True,
|
||||
callback=validate_fee,
|
||||
)
|
||||
@click.option(
|
||||
"--reuse",
|
||||
help="Reuse existing address for the change.",
|
||||
is_flag=True,
|
||||
default=False,
|
||||
)
|
||||
def dao_create_mint_proposal_cmd(
|
||||
wallet_rpc_port: Optional[int],
|
||||
fingerprint: int,
|
||||
wallet_id: int,
|
||||
amount: int,
|
||||
to_address: int,
|
||||
vote_amount: int,
|
||||
fee: str,
|
||||
reuse: bool,
|
||||
) -> None:
|
||||
import asyncio
|
||||
|
||||
from .dao_funcs import create_mint_proposal
|
||||
|
||||
extra_params = {
|
||||
"wallet_id": wallet_id,
|
||||
"fee": fee,
|
||||
"amount": amount,
|
||||
"cat_target_address": to_address,
|
||||
"vote_amount": vote_amount,
|
||||
"reuse_puzhash": True if reuse else None,
|
||||
}
|
||||
asyncio.run(execute_with_wallet(wallet_rpc_port, fingerprint, extra_params, create_mint_proposal))
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------------------
|
||||
|
||||
dao_cmd.add_command(dao_add_cmd)
|
||||
dao_cmd.add_command(dao_create_cmd)
|
||||
dao_cmd.add_command(dao_add_funds_cmd)
|
||||
dao_cmd.add_command(dao_get_balance_cmd)
|
||||
dao_cmd.add_command(dao_list_proposals_cmd)
|
||||
dao_cmd.add_command(dao_show_proposal_cmd)
|
||||
dao_cmd.add_command(dao_vote_cmd)
|
||||
dao_cmd.add_command(dao_close_proposal_cmd)
|
||||
dao_cmd.add_command(dao_lockup_coins_cmd)
|
||||
dao_cmd.add_command(dao_exit_lockup_cmd)
|
||||
dao_cmd.add_command(dao_release_coins_cmd)
|
||||
dao_cmd.add_command(dao_proposal)
|
||||
|
||||
|
||||
# TODO: status: how many of your voting coins are locked away vs. spendable, etc.
|
|
@ -0,0 +1,442 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import time
|
||||
from decimal import Decimal
|
||||
from typing import Any, Dict
|
||||
|
||||
from chia.cmds.cmds_util import transaction_status_msg, transaction_submitted_msg
|
||||
from chia.cmds.units import units
|
||||
from chia.cmds.wallet_funcs import get_mojo_per_unit, get_wallet_type
|
||||
from chia.rpc.wallet_rpc_client import WalletRpcClient
|
||||
from chia.server.start_wallet import SERVICE_NAME
|
||||
from chia.types.blockchain_format.sized_bytes import bytes32
|
||||
from chia.util.bech32m import encode_puzzle_hash
|
||||
from chia.util.config import load_config, selected_network_address_prefix
|
||||
from chia.util.default_root import DEFAULT_ROOT_PATH
|
||||
from chia.util.ints import uint64
|
||||
|
||||
|
||||
async def add_dao_wallet(args: Dict[str, Any], wallet_client: WalletRpcClient, fingerprint: int) -> None:
|
||||
treasury_id = args["treasury_id"]
|
||||
filter_amount = args["filter_amount"]
|
||||
name = args["name"]
|
||||
|
||||
print(f"Adding wallet for DAO: {treasury_id}")
|
||||
print("This may take awhile.")
|
||||
|
||||
res = await wallet_client.create_new_dao_wallet(
|
||||
mode="existing",
|
||||
dao_rules=None,
|
||||
amount_of_cats=None,
|
||||
treasury_id=treasury_id,
|
||||
filter_amount=filter_amount,
|
||||
name=name,
|
||||
)
|
||||
|
||||
print("Successfully created DAO Wallet")
|
||||
print("DAO Treasury ID: {treasury_id}".format(**res))
|
||||
print("DAO Wallet ID: {wallet_id}".format(**res))
|
||||
print("CAT Wallet ID: {cat_wallet_id}".format(**res))
|
||||
print("DAOCAT Wallet ID: {dao_cat_wallet_id}".format(**res))
|
||||
|
||||
|
||||
async def create_dao_wallet(args: Dict[str, Any], wallet_client: WalletRpcClient, fingerprint: int) -> None:
|
||||
dao_rules = {
|
||||
"proposal_timelock": args["proposal_timelock"],
|
||||
"soft_close_length": args["soft_close_length"],
|
||||
"attendance_required": args["attendance_required"],
|
||||
"pass_percentage": args["pass_percentage"],
|
||||
"self_destruct_length": args["self_destruct_length"],
|
||||
"oracle_spend_delay": args["oracle_spend_delay"],
|
||||
}
|
||||
amount_of_cats = args["amount_of_cats"]
|
||||
filter_amount = args["filter_amount"]
|
||||
name = args["name"]
|
||||
reuse_puzhash = args["reuse_puzhash"]
|
||||
|
||||
fee = Decimal(args["fee"])
|
||||
final_fee: uint64 = uint64(int(fee * units["chia"]))
|
||||
|
||||
res = await wallet_client.create_new_dao_wallet(
|
||||
mode="new",
|
||||
dao_rules=dao_rules,
|
||||
amount_of_cats=amount_of_cats,
|
||||
treasury_id=None,
|
||||
filter_amount=filter_amount,
|
||||
name=name,
|
||||
fee=final_fee,
|
||||
reuse_puzhash=reuse_puzhash,
|
||||
)
|
||||
|
||||
print("Successfully created DAO Wallet")
|
||||
print("DAO Treasury ID: {treasury_id}".format(**res))
|
||||
print("DAO Wallet ID: {wallet_id}".format(**res))
|
||||
print("CAT Wallet ID: {cat_wallet_id}".format(**res))
|
||||
print("DAOCAT Wallet ID: {dao_cat_wallet_id}".format(**res))
|
||||
|
||||
|
||||
async def add_funds_to_treasury(args: Dict[str, Any], wallet_client: WalletRpcClient, fingerprint: int) -> None:
|
||||
wallet_id = args["wallet_id"]
|
||||
funding_wallet_id = args["funding_wallet_id"]
|
||||
amount = Decimal(args["amount"])
|
||||
reuse_puzhash = args["reuse_puzhash"]
|
||||
|
||||
try:
|
||||
typ = await get_wallet_type(wallet_id=funding_wallet_id, wallet_client=wallet_client)
|
||||
mojo_per_unit = get_mojo_per_unit(typ)
|
||||
except LookupError:
|
||||
print(f"Wallet id: {wallet_id} not found.")
|
||||
return
|
||||
|
||||
fee = Decimal(args["fee"])
|
||||
final_fee: uint64 = uint64(int(fee * units["chia"]))
|
||||
final_amount: uint64 = uint64(int(amount * mojo_per_unit))
|
||||
|
||||
res = await wallet_client.dao_add_funds_to_treasury(
|
||||
wallet_id=wallet_id,
|
||||
funding_wallet_id=funding_wallet_id,
|
||||
amount=final_amount,
|
||||
fee=final_fee,
|
||||
reuse_puzhash=reuse_puzhash,
|
||||
)
|
||||
|
||||
tx_id = res["tx_id"]
|
||||
start = time.time()
|
||||
while time.time() - start < 10:
|
||||
await asyncio.sleep(0.1)
|
||||
tx = await wallet_client.get_transaction(wallet_id, bytes32.from_hexstr(tx_id))
|
||||
if len(tx.sent_to) > 0:
|
||||
print(transaction_submitted_msg(tx))
|
||||
print(transaction_status_msg(fingerprint, tx_id))
|
||||
return None
|
||||
|
||||
print("Transaction not yet submitted to nodes")
|
||||
print(f"To get status, use command: chia wallet get_transaction -f {fingerprint} -tx 0x{tx_id}")
|
||||
|
||||
|
||||
async def get_treasury_balance(args: Dict[str, Any], wallet_client: WalletRpcClient, fingerprint: int) -> None:
|
||||
wallet_id = args["wallet_id"]
|
||||
|
||||
res = await wallet_client.dao_get_treasury_balance(wallet_id=wallet_id)
|
||||
balances = res["balances"]
|
||||
|
||||
if not balances:
|
||||
print("The DAO treasury currently has no funds")
|
||||
return None
|
||||
|
||||
for asset_id, balance in balances.items():
|
||||
if asset_id == "null":
|
||||
print(f"XCH: {balance}")
|
||||
else:
|
||||
print(f"{asset_id}: {balance}")
|
||||
|
||||
|
||||
async def list_proposals(args: Dict[str, Any], wallet_client: WalletRpcClient, fingerprint: int) -> None:
|
||||
wallet_id = args["wallet_id"]
|
||||
|
||||
res = await wallet_client.dao_get_proposals(wallet_id=wallet_id)
|
||||
proposals = res["proposals"]
|
||||
lockup_time = res["lockup_time"]
|
||||
soft_close_length = res["soft_close_length"]
|
||||
print("############################")
|
||||
for prop in proposals:
|
||||
print("Proposal ID: {proposal_id}".format(**prop))
|
||||
prop_status = "CLOSED" if prop["closed"] else "OPEN"
|
||||
print(f"Status: {prop_status}")
|
||||
print("Votes for: {yes_votes}".format(**prop))
|
||||
votes_against = prop["amount_voted"] - prop["yes_votes"]
|
||||
print(f"Votes against: {votes_against}")
|
||||
close_height = prop["singleton_block_height"] - lockup_time
|
||||
print(f"Closable at block height: {close_height}")
|
||||
print("------------------------")
|
||||
print(f"Proposals have {soft_close_length} blocks of soft close time.")
|
||||
print("############################")
|
||||
|
||||
|
||||
async def show_proposal(args: Dict[str, Any], wallet_client: WalletRpcClient, fingerprint: int) -> None:
|
||||
wallet_id = args["wallet_id"]
|
||||
proposal_id = args["proposal_id"]
|
||||
res = await wallet_client.dao_parse_proposal(wallet_id, proposal_id)
|
||||
pd = res["proposal_dictionary"]
|
||||
|
||||
blocks_needed = pd["state"]["blocks_needed"]
|
||||
passed = pd["state"]["passed"]
|
||||
closable = pd["state"]["closable"]
|
||||
status = "CLOSED" if pd["state"]["closed"] else "OPEN"
|
||||
votes_needed = pd["state"]["total_votes_needed"]
|
||||
yes_needed = pd["state"]["yes_votes_needed"]
|
||||
|
||||
ptype = pd["proposal_type"]
|
||||
if (ptype == "spend") and ("mint_amount" in pd):
|
||||
ptype = "mint"
|
||||
|
||||
print("")
|
||||
print(f"Details of Proposal: {proposal_id}")
|
||||
print("---------------------------")
|
||||
print("")
|
||||
print(f"Type: {ptype.upper()}")
|
||||
print(f"Status: {status}")
|
||||
print(f"Passed: {passed}")
|
||||
if not passed:
|
||||
print(f"Yes votes needed: {yes_needed}")
|
||||
|
||||
if not pd["state"]["closed"]:
|
||||
print(f"Closable: {closable}")
|
||||
if not closable:
|
||||
print(f"Total votes needed: {votes_needed}")
|
||||
print(f"Blocks remaining: {blocks_needed}")
|
||||
|
||||
if ptype == "spend":
|
||||
xch_conds = pd["xch_conditions"]
|
||||
asset_conds = pd["asset_conditions"]
|
||||
print("")
|
||||
if xch_conds:
|
||||
print("Proposal XCH Conditions")
|
||||
for pmt in xch_conds:
|
||||
print("{puzzle_hash} {amount}".format(**pmt))
|
||||
if asset_conds:
|
||||
print("Proposal asset Conditions")
|
||||
for asset_id, conds in asset_conds:
|
||||
print(f"Asset ID: {asset_id}")
|
||||
for pmt in conds:
|
||||
print("{puzzle_hash} {amount}".format(**pmt))
|
||||
|
||||
elif ptype == "update":
|
||||
print("")
|
||||
print("Proposed Rules:")
|
||||
for key, val in pd["dao_rules"].items():
|
||||
print(f"{key}: {val}")
|
||||
|
||||
elif ptype == "mint":
|
||||
mint_amount = pd["mint_amount"]
|
||||
config = load_config(DEFAULT_ROOT_PATH, "config.yaml", SERVICE_NAME)
|
||||
prefix = selected_network_address_prefix(config)
|
||||
address = encode_puzzle_hash(bytes32.from_hexstr(pd["new_cat_puzhash"]), prefix)
|
||||
print("")
|
||||
print(f"Amount of CAT to mint: {mint_amount}")
|
||||
print(f"Address: {address}")
|
||||
|
||||
|
||||
async def vote_on_proposal(args: Dict[str, Any], wallet_client: WalletRpcClient, fingerprint: int) -> None:
|
||||
wallet_id = args["wallet_id"]
|
||||
vote_amount = args["vote_amount"]
|
||||
fee = args["fee"]
|
||||
final_fee: uint64 = uint64(int(Decimal(fee) * units["chia"]))
|
||||
proposal_id = args["proposal_id"]
|
||||
is_yes_vote = args["is_yes_vote"]
|
||||
reuse_puzhash = args["reuse_puzhash"]
|
||||
# wallet_id: int, proposal_id: str, vote_amount: uint64, is_yes_vote: bool = True, fee: uint64 = uint64(0)
|
||||
res = await wallet_client.dao_vote_on_proposal(
|
||||
wallet_id=wallet_id,
|
||||
proposal_id=proposal_id,
|
||||
vote_amount=vote_amount,
|
||||
is_yes_vote=is_yes_vote,
|
||||
fee=final_fee,
|
||||
reuse_puzhash=reuse_puzhash,
|
||||
)
|
||||
spend_bundle = res["spend_bundle"]
|
||||
if res["success"]:
|
||||
print(f"Submitted spend bundle with name: {spend_bundle.name()}")
|
||||
else:
|
||||
print("Unable to generate vote transaction.")
|
||||
|
||||
|
||||
async def close_proposal(args: Dict[str, Any], wallet_client: WalletRpcClient, fingerprint: int) -> None:
|
||||
wallet_id = args["wallet_id"]
|
||||
fee = args["fee"]
|
||||
final_fee: uint64 = uint64(int(Decimal(fee) * units["chia"]))
|
||||
proposal_id = args["proposal_id"]
|
||||
reuse_puzhash = args["reuse_puzhash"]
|
||||
res = await wallet_client.dao_close_proposal(
|
||||
wallet_id=wallet_id,
|
||||
proposal_id=proposal_id,
|
||||
fee=final_fee,
|
||||
reuse_puzhash=reuse_puzhash,
|
||||
)
|
||||
# dao_close_proposal(self, wallet_id: int, proposal_id: str, fee: uint64 = uint64(0))
|
||||
if res["success"]:
|
||||
name = res["tx_id"]
|
||||
print(f"Submitted proposal close transaction with name: {name}")
|
||||
else:
|
||||
print("Unable to generate close transaction.")
|
||||
|
||||
|
||||
async def lockup_coins(args: Dict[str, Any], wallet_client: WalletRpcClient, fingerprint: int) -> None:
|
||||
wallet_id = args["wallet_id"]
|
||||
amount = args["amount"]
|
||||
final_amount: uint64 = uint64(int(Decimal(amount) * units["cat"]))
|
||||
fee = args["fee"]
|
||||
final_fee: uint64 = uint64(int(Decimal(fee) * units["chia"]))
|
||||
reuse_puzhash = args["reuse_puzhash"]
|
||||
# typ = await get_wallet_type(wallet_id=wallet_id, wallet_client=wallet_client)
|
||||
res = await wallet_client.dao_send_to_lockup(
|
||||
wallet_id=wallet_id,
|
||||
amount=final_amount,
|
||||
fee=final_fee,
|
||||
reuse_puzhash=reuse_puzhash,
|
||||
)
|
||||
tx_id = res["tx_id"]
|
||||
start = time.time()
|
||||
while time.time() - start < 10:
|
||||
await asyncio.sleep(0.1)
|
||||
tx = await wallet_client.get_transaction(wallet_id, bytes32.from_hexstr(tx_id))
|
||||
if len(tx.sent_to) > 0:
|
||||
print(transaction_submitted_msg(tx))
|
||||
print(transaction_status_msg(fingerprint, tx_id))
|
||||
return None
|
||||
|
||||
print("Transaction not yet submitted to nodes")
|
||||
|
||||
|
||||
async def release_coins(args: Dict[str, Any], wallet_client: WalletRpcClient, fingerprint: int) -> None:
|
||||
wallet_id = args["wallet_id"]
|
||||
fee = args["fee"]
|
||||
final_fee: uint64 = uint64(int(Decimal(fee) * units["chia"]))
|
||||
reuse_puzhash = args["reuse_puzhash"]
|
||||
res = await wallet_client.dao_free_coins_from_finished_proposals(
|
||||
wallet_id=wallet_id,
|
||||
fee=final_fee,
|
||||
reuse_puzhash=reuse_puzhash,
|
||||
)
|
||||
if res["success"]:
|
||||
print("Transaction submitted.")
|
||||
else:
|
||||
print("Transaction failed.")
|
||||
|
||||
|
||||
async def exit_lockup(args: Dict[str, Any], wallet_client: WalletRpcClient, fingerprint: int) -> None:
|
||||
wallet_id = args["wallet_id"]
|
||||
fee = args["fee"]
|
||||
final_fee: uint64 = uint64(int(Decimal(fee) * units["chia"]))
|
||||
reuse_puzhash = args["reuse_puzhash"]
|
||||
res = await wallet_client.dao_exit_lockup(
|
||||
wallet_id=wallet_id,
|
||||
coins=[],
|
||||
fee=final_fee,
|
||||
reuse_puzhash=reuse_puzhash,
|
||||
)
|
||||
if res["success"]:
|
||||
print("Transaction submitted.")
|
||||
else:
|
||||
print("Transaction failed.")
|
||||
|
||||
|
||||
async def create_spend_proposal(args: Dict[str, Any], wallet_client: WalletRpcClient, fingerprint: int) -> None:
|
||||
wallet_id = args["wallet_id"]
|
||||
fee = args["fee"]
|
||||
final_fee: uint64 = uint64(int(Decimal(fee) * units["chia"]))
|
||||
reuse_puzhash = args["reuse_puzhash"]
|
||||
|
||||
if "to_address" in args:
|
||||
address = args["to_address"]
|
||||
else:
|
||||
address = None
|
||||
if "amount" in args:
|
||||
amount = args["amount"]
|
||||
else:
|
||||
amount = None
|
||||
if "from_json" in args:
|
||||
additions = args["from_json"]
|
||||
else:
|
||||
additions = None
|
||||
if additions is None and (address is None or amount is None):
|
||||
print("ERROR: Must include a json specification or an address / amount pair.")
|
||||
if "vote_amount" in args:
|
||||
vote_amount = args["vote_amount"]
|
||||
else:
|
||||
vote_amount = None
|
||||
res = await wallet_client.dao_create_proposal(
|
||||
wallet_id=wallet_id,
|
||||
proposal_type="spend",
|
||||
additions=additions,
|
||||
amount=amount,
|
||||
inner_address=address,
|
||||
vote_amount=vote_amount,
|
||||
fee=final_fee,
|
||||
reuse_puzhash=reuse_puzhash,
|
||||
)
|
||||
if res["success"]:
|
||||
print("Successfully created proposal.")
|
||||
else:
|
||||
print("Failed to create proposal.")
|
||||
|
||||
|
||||
async def create_update_proposal(args: Dict[str, Any], wallet_client: WalletRpcClient, fingerprint: int) -> None:
|
||||
wallet_id = args["wallet_id"]
|
||||
fee = Decimal(args["fee"])
|
||||
final_fee: uint64 = uint64(int(fee * units["chia"]))
|
||||
reuse_puzhash = args["reuse_puzhash"]
|
||||
if "proposal_timelock" in args:
|
||||
proposal_timelock = args["proposal_timelock"]
|
||||
else:
|
||||
proposal_timelock = None
|
||||
if "soft_close_length" in args:
|
||||
soft_close_length = args["soft_close_length"]
|
||||
else:
|
||||
soft_close_length = None
|
||||
if "attendance_required" in args:
|
||||
attendance_required = args["attendance_required"]
|
||||
else:
|
||||
attendance_required = None
|
||||
if "pass_percentage" in args:
|
||||
pass_percentage = args["pass_percentage"]
|
||||
else:
|
||||
pass_percentage = None
|
||||
if "self_destruct_length" in args:
|
||||
self_destruct_length = args["self_destruct_length"]
|
||||
else:
|
||||
self_destruct_length = None
|
||||
if "oracle_spend_delay" in args:
|
||||
oracle_spend_delay = args["oracle_spend_delay"]
|
||||
else:
|
||||
oracle_spend_delay = None
|
||||
if "vote_amount" in args:
|
||||
vote_amount = args["vote_amount"]
|
||||
else:
|
||||
vote_amount = None
|
||||
new_dao_rules = {
|
||||
"proposal_timelock": proposal_timelock,
|
||||
"soft_close_length": soft_close_length,
|
||||
"attendance_required": attendance_required,
|
||||
"pass_percentage": pass_percentage,
|
||||
"self_destruct_length": self_destruct_length,
|
||||
"oracle_spend_delay": oracle_spend_delay,
|
||||
}
|
||||
res = await wallet_client.dao_create_proposal(
|
||||
wallet_id=wallet_id,
|
||||
proposal_type="update",
|
||||
new_dao_rules=new_dao_rules,
|
||||
vote_amount=vote_amount,
|
||||
fee=final_fee,
|
||||
reuse_puzhash=reuse_puzhash,
|
||||
)
|
||||
if res["success"]:
|
||||
print("Successfully created proposal.")
|
||||
else:
|
||||
print("Failed to create proposal.")
|
||||
|
||||
|
||||
async def create_mint_proposal(args: Dict[str, Any], wallet_client: WalletRpcClient, fingerprint: int) -> None:
|
||||
wallet_id = args["wallet_id"]
|
||||
fee = args["fee"]
|
||||
final_fee: uint64 = uint64(int(Decimal(fee) * units["chia"]))
|
||||
reuse_puzhash = args["reuse_puzhash"]
|
||||
cat_target_address = args["cat_target_address"]
|
||||
amount = args["amount"]
|
||||
vote_amount = None
|
||||
if "vote_amount" in args:
|
||||
vote_amount = args["vote_amount"]
|
||||
res = await wallet_client.dao_create_proposal(
|
||||
wallet_id=wallet_id,
|
||||
proposal_type="mint",
|
||||
cat_target_address=cat_target_address,
|
||||
amount=amount,
|
||||
vote_amount=vote_amount,
|
||||
fee=final_fee,
|
||||
reuse_puzhash=reuse_puzhash,
|
||||
)
|
||||
if res["success"]:
|
||||
print("Successfully created proposal.")
|
||||
else:
|
||||
print("Failed to create proposal.")
|
|
@ -14,10 +14,11 @@ from chia.types.blockchain_format.coin import Coin
|
|||
from chia.types.blockchain_format.program import Program
|
||||
from chia.types.blockchain_format.serialized_program import SerializedProgram
|
||||
from chia.types.blockchain_format.sized_bytes import bytes32
|
||||
from chia.types.coin_spend import CoinSpend, compute_additions
|
||||
from chia.types.coin_spend import CoinSpend
|
||||
from chia.util.ints import uint32, uint64
|
||||
from chia.wallet.puzzles.load_clvm import load_clvm_maybe_recompile
|
||||
from chia.wallet.puzzles.singleton_top_layer import puzzle_for_singleton
|
||||
from chia.wallet.singleton import get_most_recent_singleton_coin_from_coin_spend
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
# "Full" is the outer singleton, with the inner puzzle filled in
|
||||
|
@ -282,14 +283,6 @@ def create_absorb_spend(
|
|||
return coin_spends
|
||||
|
||||
|
||||
def get_most_recent_singleton_coin_from_coin_spend(coin_sol: CoinSpend) -> Optional[Coin]:
|
||||
additions: List[Coin] = compute_additions(coin_sol)
|
||||
for coin in additions:
|
||||
if coin.amount % 2 == 1:
|
||||
return coin
|
||||
return None
|
||||
|
||||
|
||||
def get_pubkey_from_member_inner_puzzle(inner_puzzle: Program) -> G1Element:
|
||||
args = uncurry_pool_member_inner_puzzle(inner_puzzle)
|
||||
if args is not None:
|
||||
|
|
|
@ -17,7 +17,6 @@ from chia.pools.pool_puzzles import (
|
|||
create_travel_spend,
|
||||
create_waiting_room_inner_puzzle,
|
||||
get_delayed_puz_info_from_launcher_spend,
|
||||
get_most_recent_singleton_coin_from_coin_spend,
|
||||
is_pool_member_inner_puzzle,
|
||||
is_pool_waitingroom_inner_puzzle,
|
||||
launcher_id_to_p2_puzzle_hash,
|
||||
|
@ -48,6 +47,7 @@ from chia.types.spend_bundle import SpendBundle
|
|||
from chia.util.ints import uint32, uint64, uint128
|
||||
from chia.wallet.derive_keys import find_owner_sk
|
||||
from chia.wallet.sign_coin_spends import sign_coin_spends
|
||||
from chia.wallet.singleton import get_most_recent_singleton_coin_from_coin_spend
|
||||
from chia.wallet.transaction_record import TransactionRecord
|
||||
from chia.wallet.util.transaction_type import TransactionType
|
||||
from chia.wallet.util.wallet_types import WalletType
|
||||
|
@ -263,8 +263,8 @@ class PoolWallet:
|
|||
async def apply_state_transition(self, new_state: CoinSpend, block_height: uint32) -> bool:
|
||||
"""
|
||||
Updates the Pool state (including DB) with new singleton spends.
|
||||
The DB must be committed after calling this method. All validation should be done here. Returns True iff
|
||||
the spend is a valid transition spend for the singleton, False otherwise.
|
||||
The DB must be committed after calling this method. All validation should be done here.
|
||||
Returns True iff the spend is a valid transition spend for the singleton, False otherwise.
|
||||
"""
|
||||
tip: Tuple[uint32, CoinSpend] = await self.get_tip()
|
||||
tip_spend = tip[1]
|
||||
|
|
|
@ -39,6 +39,11 @@ from chia.util.streamable import Streamable, streamable
|
|||
from chia.util.ws_message import WsRpcMessage, create_payload_dict
|
||||
from chia.wallet.cat_wallet.cat_constants import DEFAULT_CATS
|
||||
from chia.wallet.cat_wallet.cat_wallet import CATWallet
|
||||
from chia.wallet.cat_wallet.dao_cat_info import LockedCoinInfo
|
||||
from chia.wallet.cat_wallet.dao_cat_wallet import DAOCATWallet
|
||||
from chia.wallet.dao_wallet.dao_info import DAORules
|
||||
from chia.wallet.dao_wallet.dao_utils import get_treasury_rules_from_puzzle
|
||||
from chia.wallet.dao_wallet.dao_wallet import DAOWallet
|
||||
from chia.wallet.derive_keys import (
|
||||
MAX_POOL_WALLETS,
|
||||
master_sk_to_farmer_sk,
|
||||
|
@ -51,9 +56,9 @@ from chia.wallet.did_wallet.did_info import DIDInfo
|
|||
from chia.wallet.did_wallet.did_wallet import DIDWallet
|
||||
from chia.wallet.did_wallet.did_wallet_puzzles import (
|
||||
DID_INNERPUZ_MOD,
|
||||
did_program_to_metadata,
|
||||
match_did_puzzle,
|
||||
metadata_to_program,
|
||||
program_to_metadata,
|
||||
)
|
||||
from chia.wallet.nft_wallet import nft_puzzles
|
||||
from chia.wallet.nft_wallet.nft_info import NFTCoinInfo, NFTInfo
|
||||
|
@ -194,6 +199,19 @@ class WalletRpcApi:
|
|||
"/did_message_spend": self.did_message_spend,
|
||||
"/did_get_info": self.did_get_info,
|
||||
"/did_find_lost_did": self.did_find_lost_did,
|
||||
# DAO Wallets
|
||||
"/dao_get_proposals": self.dao_get_proposals,
|
||||
"/dao_create_proposal": self.dao_create_proposal,
|
||||
"/dao_parse_proposal": self.dao_parse_proposal,
|
||||
"/dao_vote_on_proposal": self.dao_vote_on_proposal,
|
||||
"/dao_get_treasury_balance": self.dao_get_treasury_balance,
|
||||
"/dao_close_proposal": self.dao_close_proposal,
|
||||
"/dao_exit_lockup": self.dao_exit_lockup,
|
||||
"/dao_adjust_filter_level": self.dao_adjust_filter_level,
|
||||
"/dao_add_funds_to_treasury": self.dao_add_funds_to_treasury,
|
||||
"/dao_send_to_lockup": self.dao_send_to_lockup,
|
||||
"/dao_get_proposal_state": self.dao_get_proposal_state,
|
||||
"/dao_free_coins_from_finished_proposals": self.dao_free_coins_from_finished_proposals,
|
||||
# NFT Wallet
|
||||
"/nft_mint_nft": self.nft_mint_nft,
|
||||
"/nft_count_nfts": self.nft_count_nfts,
|
||||
|
@ -713,6 +731,42 @@ class WalletRpcApi:
|
|||
}
|
||||
else: # undefined did_type
|
||||
pass
|
||||
elif request["wallet_type"] == "dao_wallet":
|
||||
name = None
|
||||
if request["name"]:
|
||||
name = request["name"]
|
||||
if request["mode"] == "new":
|
||||
if request["dao_rules"]:
|
||||
dao_rules = DAORules.from_json_dict(request["dao_rules"])
|
||||
else:
|
||||
raise ValueError("DAO rules must be specified for wallet creation")
|
||||
async with self.service.wallet_state_manager.lock:
|
||||
dao_wallet = await DAOWallet.create_new_dao_and_wallet(
|
||||
wallet_state_manager,
|
||||
main_wallet,
|
||||
uint64(request["amount_of_cats"]),
|
||||
dao_rules,
|
||||
uint64(request["filter_amount"]),
|
||||
name,
|
||||
uint64(request.get("fee", 0)),
|
||||
)
|
||||
elif request["mode"] == "existing":
|
||||
# async with self.service.wallet_state_manager.lock:
|
||||
dao_wallet = await DAOWallet.create_new_dao_wallet_for_existing_dao(
|
||||
wallet_state_manager,
|
||||
main_wallet,
|
||||
bytes32.from_hexstr(request["treasury_id"]),
|
||||
uint64(request["filter_amount"]),
|
||||
name,
|
||||
)
|
||||
return {
|
||||
"success": True,
|
||||
"type": dao_wallet.type(),
|
||||
"wallet_id": dao_wallet.id(),
|
||||
"treasury_id": dao_wallet.dao_info.treasury_id,
|
||||
"cat_wallet_id": dao_wallet.dao_info.cat_wallet_id,
|
||||
"dao_cat_wallet_id": dao_wallet.dao_info.dao_cat_wallet_id,
|
||||
}
|
||||
elif request["wallet_type"] == "nft_wallet":
|
||||
for wallet in self.service.wallet_state_manager.wallets.values():
|
||||
did_id: Optional[bytes32] = None
|
||||
|
@ -1565,7 +1619,7 @@ class WalletRpcApi:
|
|||
)
|
||||
if hold_lock:
|
||||
async with self.service.wallet_state_manager.lock:
|
||||
txs: List[TransactionRecord] = await wallet.generate_signed_transaction(
|
||||
txs: List[TransactionRecord] = await wallet.generate_signed_transactions(
|
||||
amounts,
|
||||
puzzle_hashes,
|
||||
fee,
|
||||
|
@ -1581,7 +1635,7 @@ class WalletRpcApi:
|
|||
for tx in txs:
|
||||
await wallet.standard_wallet.push_transaction(tx)
|
||||
else:
|
||||
txs = await wallet.generate_signed_transaction(
|
||||
txs = await wallet.generate_signed_transactions(
|
||||
amounts,
|
||||
puzzle_hashes,
|
||||
fee,
|
||||
|
@ -1949,7 +2003,7 @@ class WalletRpcApi:
|
|||
"public_key": public_key.atom.hex(),
|
||||
"recovery_list_hash": recovery_list_hash.atom.hex(),
|
||||
"num_verification": num_verification.as_int(),
|
||||
"metadata": program_to_metadata(metadata),
|
||||
"metadata": did_program_to_metadata(metadata),
|
||||
"launcher_id": singleton_struct.rest().first().atom.hex(),
|
||||
"full_puzzle": full_puzzle,
|
||||
"solution": Program.from_bytes(bytes(coin_spend.solution)).as_python(),
|
||||
|
@ -2120,7 +2174,7 @@ class WalletRpcApi:
|
|||
None,
|
||||
None,
|
||||
False,
|
||||
json.dumps(did_wallet_puzzles.program_to_metadata(metadata)),
|
||||
json.dumps(did_wallet_puzzles.did_program_to_metadata(metadata)),
|
||||
)
|
||||
await did_wallet.save_info(did_info)
|
||||
await self.service.wallet_state_manager.update_wallet_puzzle_hashes(did_wallet.wallet_info.id)
|
||||
|
@ -2326,6 +2380,241 @@ class WalletRpcApi:
|
|||
"transaction_id": txs.name,
|
||||
}
|
||||
|
||||
##########################################################################################
|
||||
# DAO Wallet
|
||||
##########################################################################################
|
||||
|
||||
async def dao_adjust_filter_level(self, request) -> EndpointResult:
|
||||
wallet_id = uint32(request["wallet_id"])
|
||||
dao_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DAOWallet)
|
||||
await dao_wallet.adjust_filter_level(uint64(request["filter_level"]))
|
||||
return {
|
||||
"success": True,
|
||||
"dao_info": dao_wallet.dao_info,
|
||||
}
|
||||
|
||||
async def dao_add_funds_to_treasury(self, request) -> EndpointResult:
|
||||
wallet_id = uint32(request["wallet_id"])
|
||||
dao_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DAOWallet)
|
||||
funding_wallet_id = uint32(request["funding_wallet_id"])
|
||||
wallet_type = self.service.wallet_state_manager.wallets[funding_wallet_id].type()
|
||||
if wallet_type not in [WalletType.STANDARD_WALLET, WalletType.CAT]:
|
||||
raise ValueError(f"Cannot fund a treasury with assets from a {wallet_type.name} wallet")
|
||||
funding_tx = await dao_wallet.create_add_money_to_treasury_spend(
|
||||
amount=uint64(request.get("amount")),
|
||||
fee=uint64(request.get("fee", 0)),
|
||||
funding_wallet_id=funding_wallet_id,
|
||||
reuse_puzhash=request.get("reuse_puzhash", None),
|
||||
)
|
||||
return {"success": True, "tx_id": funding_tx.name}
|
||||
|
||||
async def dao_get_treasury_balance(self, request) -> EndpointResult:
|
||||
wallet_id = uint32(request["wallet_id"])
|
||||
dao_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DAOWallet)
|
||||
assert dao_wallet is not None
|
||||
asset_list = dao_wallet.dao_info.assets
|
||||
balances = {}
|
||||
for asset_id in asset_list:
|
||||
balance = await dao_wallet.get_balance_by_asset_type(asset_id=asset_id)
|
||||
balances[asset_id] = balance
|
||||
return {"success": True, "balances": balances}
|
||||
|
||||
async def dao_send_to_lockup(self, request) -> EndpointResult:
|
||||
wallet_id = uint32(request["wallet_id"])
|
||||
dao_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DAOWallet)
|
||||
dao_cat_wallet = self.service.wallet_state_manager.get_wallet(
|
||||
id=dao_wallet.dao_info.dao_cat_wallet_id, required_type=DAOCATWallet
|
||||
)
|
||||
amount = uint64(request["amount"])
|
||||
fee = uint64(request.get("fee", 0))
|
||||
txs, _ = await dao_cat_wallet.create_new_dao_cats(
|
||||
amount,
|
||||
push=True,
|
||||
fee=fee,
|
||||
reuse_puzhash=request.get("reuse_puzhash", None),
|
||||
)
|
||||
return {
|
||||
"success": True,
|
||||
"tx_id": txs[0].name,
|
||||
}
|
||||
|
||||
async def dao_get_proposals(self, request) -> EndpointResult:
|
||||
wallet_id = uint32(request["wallet_id"])
|
||||
dao_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DAOWallet)
|
||||
assert dao_wallet is not None
|
||||
proposal_list = dao_wallet.dao_info.proposals_list
|
||||
dao_rules = get_treasury_rules_from_puzzle(dao_wallet.dao_info.current_treasury_innerpuz)
|
||||
return {
|
||||
"success": True,
|
||||
"proposals": proposal_list,
|
||||
"lockup_time": dao_rules.proposal_timelock,
|
||||
"soft_close_length": dao_rules.soft_close_length,
|
||||
}
|
||||
|
||||
async def dao_get_proposal_state(self, request) -> EndpointResult:
|
||||
wallet_id = uint32(request["wallet_id"])
|
||||
dao_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DAOWallet)
|
||||
assert dao_wallet is not None
|
||||
state = await dao_wallet.get_proposal_state(bytes32.from_hexstr(request["proposal_id"]))
|
||||
return {"success": True, "state": state}
|
||||
|
||||
async def dao_exit_lockup(self, request) -> EndpointResult:
|
||||
wallet_id = uint32(request["wallet_id"])
|
||||
dao_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DAOWallet)
|
||||
assert dao_wallet is not None
|
||||
dao_cat_wallet = self.service.wallet_state_manager.get_wallet(
|
||||
id=dao_wallet.dao_info.dao_cat_wallet_id, required_type=DAOCATWallet
|
||||
)
|
||||
assert dao_cat_wallet is not None
|
||||
if request["coins"]:
|
||||
coin_list = [Coin.from_json_dict(coin) for coin in request["coins"]]
|
||||
coins: List[LockedCoinInfo] = []
|
||||
for lci in dao_cat_wallet.dao_cat_info.locked_coins:
|
||||
if lci.coin in coin_list:
|
||||
coins.append(lci)
|
||||
else:
|
||||
coins = []
|
||||
for lci in dao_cat_wallet.dao_cat_info.locked_coins:
|
||||
if lci.active_votes == []:
|
||||
coins.append(lci)
|
||||
fee = uint64(request.get("fee", 0))
|
||||
tx = await dao_cat_wallet.exit_vote_state(
|
||||
coins,
|
||||
fee=fee,
|
||||
reuse_puzhash=request.get("reuse_puzhash", None),
|
||||
)
|
||||
return {"success": True, "tx_id": tx.name()}
|
||||
|
||||
async def dao_create_proposal(self, request) -> EndpointResult:
|
||||
wallet_id = uint32(request["wallet_id"])
|
||||
dao_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DAOWallet)
|
||||
assert dao_wallet is not None
|
||||
|
||||
if request["proposal_type"] == "spend":
|
||||
amounts: List[uint64] = []
|
||||
puzzle_hashes: List[bytes32] = []
|
||||
asset_types: List[Optional[bytes32]] = []
|
||||
additions: Optional[List[Dict]] = request.get("additions")
|
||||
if additions is not None:
|
||||
for addition in additions:
|
||||
if "asset_id" in addition:
|
||||
asset_id = bytes32.from_hexstr(addition["asset_id"])
|
||||
else:
|
||||
asset_id = None
|
||||
receiver_ph = bytes32.from_hexstr(addition["puzzle_hash"])
|
||||
amount = uint64(addition["amount"])
|
||||
amounts.append(amount)
|
||||
puzzle_hashes.append(receiver_ph)
|
||||
asset_types.append(asset_id)
|
||||
else:
|
||||
amounts.append(uint64(request["amount"]))
|
||||
puzzle_hashes.append(decode_puzzle_hash(request["inner_address"]))
|
||||
if request["asset_id"] is not None:
|
||||
asset_types.append(bytes32.from_hexstr(request["asset_id"]))
|
||||
else:
|
||||
asset_types.append(None)
|
||||
proposed_puzzle = dao_wallet.generate_simple_proposal_innerpuz(puzzle_hashes, amounts, asset_types)
|
||||
|
||||
elif request["proposal_type"] == "update":
|
||||
rules = dao_wallet.dao_rules
|
||||
prop = request["new_dao_rules"]
|
||||
new_rules = DAORules(
|
||||
proposal_timelock=prop.get("proposal_timelock") or rules.proposal_timelock,
|
||||
soft_close_length=prop.get("soft_close_length") or rules.soft_close_length,
|
||||
attendance_required=prop.get("attendance_required") or rules.attendance_required,
|
||||
pass_percentage=prop.get("pass_percentage") or rules.pass_percentage,
|
||||
self_destruct_length=prop.get("self_destruct_length") or rules.self_destruct_length,
|
||||
oracle_spend_delay=prop.get("oracle_spend_delay") or rules.oracle_spend_delay,
|
||||
)
|
||||
|
||||
proposed_puzzle = await dao_wallet.generate_update_proposal_innerpuz(new_rules)
|
||||
elif request["proposal_type"] == "mint":
|
||||
amount_of_cats = uint64(request["amount"])
|
||||
mint_address = decode_puzzle_hash(request["cat_target_address"])
|
||||
# amount_of_cats_to_create: uint64,
|
||||
# cats_new_innerpuzhash: bytes32,
|
||||
proposed_puzzle = await dao_wallet.generate_mint_proposal_innerpuz(amount_of_cats, mint_address)
|
||||
else:
|
||||
return {"success": False, "error": "Unknown proposal type."}
|
||||
|
||||
if "vote_amount" in request:
|
||||
vote_amount = uint64(request["vote_amount"])
|
||||
else:
|
||||
vote_amount = None
|
||||
fee = uint64(request.get("fee", 0))
|
||||
tx = await dao_wallet.generate_new_proposal(
|
||||
proposed_puzzle,
|
||||
vote_amount,
|
||||
fee,
|
||||
reuse_puzhash=request.get("reuse_puzhash", None),
|
||||
)
|
||||
assert tx is not None
|
||||
return {
|
||||
"success": True,
|
||||
"tx_id": tx.name().hex(),
|
||||
}
|
||||
|
||||
async def dao_vote_on_proposal(self, request) -> EndpointResult:
|
||||
wallet_id = uint32(request["wallet_id"])
|
||||
dao_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DAOWallet)
|
||||
assert dao_wallet is not None
|
||||
vote_amount = None
|
||||
if "vote_amount" in request:
|
||||
vote_amount = uint64(request["vote_amount"])
|
||||
fee = uint64(request.get("fee", 0))
|
||||
sb = await dao_wallet.generate_proposal_vote_spend(
|
||||
bytes32.from_hexstr(request["proposal_id"]),
|
||||
vote_amount,
|
||||
request["is_yes_vote"], # bool
|
||||
fee,
|
||||
push=True,
|
||||
reuse_puzhash=request.get("reuse_puzhash", None),
|
||||
)
|
||||
assert sb is not None
|
||||
return {"success": True, "spend_bundle": sb}
|
||||
|
||||
async def dao_parse_proposal(self, request) -> EndpointResult:
|
||||
wallet_id = uint32(request["wallet_id"])
|
||||
dao_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DAOWallet)
|
||||
assert dao_wallet is not None
|
||||
proposal_id = bytes32.from_hexstr(request["proposal_id"])
|
||||
proposal_dictionary = await dao_wallet.parse_proposal(proposal_id)
|
||||
assert proposal_dictionary is not None
|
||||
return {"success": True, "proposal_dictionary": proposal_dictionary}
|
||||
|
||||
async def dao_close_proposal(self, request) -> EndpointResult:
|
||||
wallet_id = uint32(request["wallet_id"])
|
||||
dao_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DAOWallet)
|
||||
assert dao_wallet is not None
|
||||
fee = uint64(request.get("fee", 0))
|
||||
if "genesis_id" in request:
|
||||
genesis_id = bytes32.from_hexstr(request["genesis_id"])
|
||||
else:
|
||||
genesis_id = None
|
||||
tx = await dao_wallet.create_proposal_close_spend(
|
||||
bytes32.from_hexstr(request["proposal_id"]),
|
||||
genesis_id,
|
||||
fee,
|
||||
# genesis_id,
|
||||
push=True,
|
||||
reuse_puzhash=request.get("reuse_puzhash", None),
|
||||
)
|
||||
assert tx is not None
|
||||
return {"success": True, "tx_id": tx.name()}
|
||||
|
||||
async def dao_free_coins_from_finished_proposals(self, request) -> EndpointResult:
|
||||
wallet_id = uint32(request["wallet_id"])
|
||||
fee = uint64(request.get("fee", 0))
|
||||
dao_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DAOWallet)
|
||||
assert dao_wallet is not None
|
||||
tx = await dao_wallet.free_coins_from_finished_proposals(
|
||||
fee=fee,
|
||||
reuse_puzhash=request.get("reuse_puzhash", None),
|
||||
)
|
||||
assert tx is not None
|
||||
|
||||
return {"success": True, "spend_name": tx.name()}
|
||||
|
||||
##########################################################################################
|
||||
# NFT Wallet
|
||||
##########################################################################################
|
||||
|
@ -3118,7 +3407,7 @@ class WalletRpcApi:
|
|||
else:
|
||||
assert isinstance(wallet, CATWallet)
|
||||
|
||||
txs = await wallet.generate_signed_transaction(
|
||||
txs = await wallet.generate_signed_transactions(
|
||||
[amount_0] + [output.amount for output in additional_outputs],
|
||||
[bytes32(puzzle_hash_0)] + [output.puzzle_hash for output in additional_outputs],
|
||||
fee,
|
||||
|
|
|
@ -1199,6 +1199,191 @@ class WalletRpcClient(RpcClient):
|
|||
response = await self.fetch("sign_message_by_id", {"id": id, "message": message})
|
||||
return response["pubkey"], response["signature"], response["signing_mode"]
|
||||
|
||||
# DAOs
|
||||
async def create_new_dao_wallet(
|
||||
self,
|
||||
mode: str,
|
||||
dao_rules: Optional[Dict[str, uint64]] = None,
|
||||
amount_of_cats: Optional[uint64] = None,
|
||||
treasury_id: Optional[bytes32] = None,
|
||||
filter_amount: uint64 = uint64(1),
|
||||
name: Optional[str] = None,
|
||||
fee: uint64 = uint64(0),
|
||||
reuse_puzhash: Optional[bool] = None,
|
||||
) -> Dict:
|
||||
"""
|
||||
TODO: Rearrange argument list order
|
||||
"""
|
||||
request: Dict[str, Any] = {
|
||||
"wallet_type": "dao_wallet",
|
||||
"mode": mode,
|
||||
"treasury_id": treasury_id,
|
||||
"dao_rules": dao_rules,
|
||||
"amount_of_cats": amount_of_cats,
|
||||
"filter_amount": filter_amount,
|
||||
"name": name,
|
||||
"fee": fee,
|
||||
"reuse_puzhash": reuse_puzhash,
|
||||
}
|
||||
response = await self.fetch("create_new_wallet", request)
|
||||
return response
|
||||
|
||||
async def dao_create_proposal(
|
||||
self,
|
||||
wallet_id: int,
|
||||
proposal_type: str,
|
||||
additions: Optional[List[Dict]] = None,
|
||||
amount: Optional[uint64] = None,
|
||||
inner_address: Optional[str] = None,
|
||||
asset_id: Optional[str] = None,
|
||||
cat_target_address: Optional[str] = None,
|
||||
vote_amount: Optional[int] = None,
|
||||
new_dao_rules: Optional[Dict[str, uint64]] = None,
|
||||
fee: uint64 = uint64(0),
|
||||
reuse_puzhash: Optional[bool] = None,
|
||||
) -> Dict:
|
||||
request: Dict[str, Any] = {
|
||||
"wallet_id": wallet_id,
|
||||
"proposal_type": proposal_type,
|
||||
"additions": additions,
|
||||
"amount": amount,
|
||||
"inner_address": inner_address,
|
||||
"asset_id": asset_id,
|
||||
"cat_target_address": cat_target_address,
|
||||
"vote_amount": vote_amount,
|
||||
"new_dao_rules": new_dao_rules,
|
||||
"fee": fee,
|
||||
"reuse_puzhash": reuse_puzhash,
|
||||
}
|
||||
|
||||
response = await self.fetch("dao_create_proposal", request)
|
||||
return response
|
||||
|
||||
async def dao_get_proposal_state(self, wallet_id: int, proposal_id: str):
|
||||
request: Dict[str, Any] = {"wallet_id": wallet_id, "proposal_id": proposal_id}
|
||||
response = await self.fetch("dao_get_proposal_state", request)
|
||||
return response
|
||||
|
||||
async def dao_parse_proposal(self, wallet_id: int, proposal_id: str):
|
||||
request: Dict[str, Any] = {"wallet_id": wallet_id, "proposal_id": proposal_id}
|
||||
response = await self.fetch("dao_parse_proposal", request)
|
||||
return response
|
||||
|
||||
async def dao_vote_on_proposal(
|
||||
self,
|
||||
wallet_id: int,
|
||||
proposal_id: str,
|
||||
vote_amount: uint64,
|
||||
is_yes_vote: bool = True,
|
||||
fee: uint64 = uint64(0),
|
||||
reuse_puzhash: Optional[bool] = None,
|
||||
):
|
||||
request: Dict[str, Any] = {
|
||||
"wallet_id": wallet_id,
|
||||
"proposal_id": proposal_id,
|
||||
"vote_amount": vote_amount,
|
||||
"is_yes_vote": is_yes_vote,
|
||||
"fee": fee,
|
||||
"reuse_puzhash": reuse_puzhash,
|
||||
}
|
||||
response = await self.fetch("dao_vote_on_proposal", request)
|
||||
return response
|
||||
|
||||
async def dao_get_proposals(self, wallet_id: int):
|
||||
request: Dict[str, Any] = {"wallet_id": wallet_id}
|
||||
response = await self.fetch("dao_get_proposals", request)
|
||||
return response
|
||||
|
||||
async def dao_close_proposal(
|
||||
self,
|
||||
wallet_id: int,
|
||||
proposal_id: str,
|
||||
fee: uint64 = uint64(0),
|
||||
reuse_puzhash: Optional[bool] = None,
|
||||
):
|
||||
request: Dict[str, Any] = {
|
||||
"wallet_id": wallet_id,
|
||||
"proposal_id": proposal_id,
|
||||
"fee": fee,
|
||||
"reuse_puzhash": reuse_puzhash,
|
||||
}
|
||||
response = await self.fetch("dao_close_proposal", request)
|
||||
return response
|
||||
|
||||
async def dao_free_coins_from_finished_proposals(
|
||||
self,
|
||||
wallet_id: int,
|
||||
fee: uint64 = uint64(0),
|
||||
reuse_puzhash: Optional[bool] = None,
|
||||
):
|
||||
request: Dict[str, Any] = {
|
||||
"wallet_id": wallet_id,
|
||||
"fee": fee,
|
||||
"reuse_puzhash": reuse_puzhash,
|
||||
}
|
||||
response = await self.fetch("dao_free_coins_from_finished_proposals", request)
|
||||
return response
|
||||
|
||||
async def dao_get_treasury_balance(self, wallet_id: int):
|
||||
request: Dict[str, Any] = {"wallet_id": wallet_id}
|
||||
response = await self.fetch("dao_get_treasury_balance", request)
|
||||
return response
|
||||
|
||||
async def dao_add_funds_to_treasury(
|
||||
self,
|
||||
wallet_id: int,
|
||||
funding_wallet_id: int,
|
||||
amount: uint64,
|
||||
fee: uint64 = uint64(0),
|
||||
reuse_puzhash: Optional[bool] = None,
|
||||
):
|
||||
request: Dict[str, Any] = {
|
||||
"wallet_id": wallet_id,
|
||||
"funding_wallet_id": funding_wallet_id,
|
||||
"amount": amount,
|
||||
"fee": fee,
|
||||
"reuse_puzhash": reuse_puzhash,
|
||||
}
|
||||
response = await self.fetch("dao_add_funds_to_treasury", request)
|
||||
return response
|
||||
|
||||
async def dao_send_to_lockup(
|
||||
self,
|
||||
wallet_id: int,
|
||||
amount: uint64,
|
||||
fee: uint64 = uint64(0),
|
||||
reuse_puzhash: Optional[bool] = None,
|
||||
):
|
||||
request: Dict[str, Any] = {
|
||||
"wallet_id": wallet_id,
|
||||
"amount": amount,
|
||||
"fee": fee,
|
||||
"reuse_puzhash": reuse_puzhash,
|
||||
}
|
||||
response = await self.fetch("dao_send_to_lockup", request)
|
||||
return response
|
||||
|
||||
async def dao_exit_lockup(
|
||||
self,
|
||||
wallet_id: int,
|
||||
coins: Optional[List[Dict]] = None,
|
||||
fee: uint64 = uint64(0),
|
||||
reuse_puzhash: Optional[bool] = None,
|
||||
):
|
||||
request: Dict[str, Any] = {
|
||||
"wallet_id": wallet_id,
|
||||
"coins": coins,
|
||||
"fee": fee,
|
||||
"reuse_puzhash": reuse_puzhash,
|
||||
}
|
||||
response = await self.fetch("dao_exit_lockup", request)
|
||||
return response
|
||||
|
||||
async def dao_adjust_filter_level(self, wallet_id: int, filter_level: int):
|
||||
request: Dict[str, Any] = {"wallet_id": wallet_id, "filter_level": filter_level}
|
||||
response = await self.fetch("dao_adjust_filter_level", request)
|
||||
return response
|
||||
|
||||
async def vc_mint(
|
||||
self, did_id: bytes32, target_address: Optional[bytes32] = None, fee: uint64 = uint64(0)
|
||||
) -> Tuple[VCRecord, List[TransactionRecord]]:
|
||||
|
|
|
@ -74,6 +74,10 @@ def construct_cat_puzzle(
|
|||
return mod_code.curry(mod_code_hash, limitations_program_hash, inner_puzzle)
|
||||
|
||||
|
||||
def construct_cat_puzzlehash_for_inner_puzzlehash(tail_hash: bytes32, inner_puzzle_hash: bytes32) -> bytes32:
|
||||
return CAT_MOD.curry(CAT_MOD_HASH, tail_hash, inner_puzzle_hash).get_tree_hash_precalc(inner_puzzle_hash)
|
||||
|
||||
|
||||
def subtotals_for_deltas(deltas: List[int]) -> List[int]:
|
||||
"""
|
||||
Given a list of deltas corresponding to input coins, create the "subtotals" list
|
||||
|
|
|
@ -176,6 +176,7 @@ class CATWallet:
|
|||
chia_tx = dataclasses.replace(chia_tx, spend_bundle=spend_bundle)
|
||||
await self.standard_wallet.push_transaction(chia_tx)
|
||||
await self.standard_wallet.push_transaction(cat_record)
|
||||
# breakpoint()
|
||||
return self
|
||||
|
||||
@staticmethod
|
||||
|
@ -304,7 +305,7 @@ class CATWallet:
|
|||
spendable.sort(reverse=True, key=lambda record: record.coin.amount)
|
||||
if self.cost_of_single_tx is None:
|
||||
coin = spendable[0].coin
|
||||
txs = await self.generate_signed_transaction(
|
||||
txs = await self.generate_signed_transactions(
|
||||
[uint64(coin.amount)], [coin.puzzle_hash], coins={coin}, ignore_max_send_amount=True
|
||||
)
|
||||
assert txs[0].spend_bundle
|
||||
|
@ -806,7 +807,7 @@ class CATWallet:
|
|||
chia_tx,
|
||||
)
|
||||
|
||||
async def generate_signed_transaction(
|
||||
async def generate_signed_transactions(
|
||||
self,
|
||||
amounts: List[uint64],
|
||||
puzzle_hashes: List[bytes32],
|
||||
|
@ -820,6 +821,7 @@ class CATWallet:
|
|||
max_coin_amount: Optional[uint64] = None,
|
||||
excluded_coin_amounts: Optional[List[uint64]] = None,
|
||||
reuse_puzhash: Optional[bool] = None,
|
||||
override_memos: Optional[bool] = None,
|
||||
**kwargs: Unpack[GSTOptionalArgs],
|
||||
) -> List[TransactionRecord]:
|
||||
excluded_cat_coins: Optional[Set[Coin]] = kwargs.get("excluded_cat_coins", None)
|
||||
|
@ -833,15 +835,18 @@ class CATWallet:
|
|||
|
||||
payments = []
|
||||
for amount, puzhash, memo_list in zip(amounts, puzzle_hashes, memos):
|
||||
memos_with_hint: List[bytes] = [puzhash]
|
||||
memos_with_hint.extend(memo_list)
|
||||
if override_memos:
|
||||
memos_with_hint: List[bytes] = memo_list
|
||||
else:
|
||||
memos_with_hint = [puzhash]
|
||||
memos_with_hint.extend(memo_list)
|
||||
payments.append(Payment(puzhash, amount, memos_with_hint))
|
||||
|
||||
payment_sum = sum([p.amount for p in payments])
|
||||
if not ignore_max_send_amount:
|
||||
max_send = await self.get_max_send_amount()
|
||||
if payment_sum > max_send:
|
||||
raise ValueError(f"Can't send more than {max_send} mojos in a single transaction")
|
||||
raise ValueError(f" Insufficient funds. Your max amount is {max_send} mojos in a single transaction.")
|
||||
unsigned_spend_bundle, chia_tx = await self.generate_unsigned_spendbundle(
|
||||
payments,
|
||||
fee,
|
||||
|
@ -899,7 +904,6 @@ class CATWallet:
|
|||
memos=[],
|
||||
)
|
||||
)
|
||||
|
||||
return tx_list
|
||||
|
||||
async def add_lineage(self, name: bytes32, lineage: Optional[LineageProof]) -> None:
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Optional
|
||||
|
||||
from chia.types.blockchain_format.coin import Coin
|
||||
from chia.types.blockchain_format.program import Program
|
||||
from chia.types.blockchain_format.sized_bytes import bytes32
|
||||
from chia.util.ints import uint64
|
||||
from chia.util.streamable import Streamable, streamable
|
||||
|
||||
|
||||
@streamable
|
||||
@dataclass(frozen=True)
|
||||
class LockedCoinInfo(Streamable):
|
||||
coin: Coin
|
||||
inner_puzzle: Program # This is the lockup puzzle, not the lockup_puzzle's inner_puzzle
|
||||
active_votes: List[Optional[bytes32]]
|
||||
|
||||
|
||||
@streamable
|
||||
@dataclass(frozen=True)
|
||||
class DAOCATInfo(Streamable):
|
||||
dao_wallet_id: uint64
|
||||
free_cat_wallet_id: uint64
|
||||
limitations_program_hash: bytes32
|
||||
my_tail: Optional[Program] # this is the program
|
||||
locked_coins: List[LockedCoinInfo]
|
|
@ -0,0 +1,846 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import time
|
||||
from secrets import token_bytes
|
||||
from typing import TYPE_CHECKING, Any, ClassVar, List, Optional, Set, Tuple, cast
|
||||
|
||||
from blspy import AugSchemeMPL, G1Element, G2Element
|
||||
|
||||
from chia.server.ws_connection import WSChiaConnection
|
||||
from chia.types.blockchain_format.coin import Coin
|
||||
from chia.types.blockchain_format.program import Program
|
||||
from chia.types.blockchain_format.sized_bytes import bytes32
|
||||
from chia.types.spend_bundle import SpendBundle
|
||||
from chia.util.byte_types import hexstr_to_bytes
|
||||
from chia.util.condition_tools import conditions_dict_for_solution, pkm_pairs_for_conditions_dict
|
||||
from chia.util.ints import uint32, uint64, uint128
|
||||
from chia.wallet.cat_wallet.cat_utils import (
|
||||
CAT_MOD,
|
||||
SpendableCAT,
|
||||
construct_cat_puzzle,
|
||||
match_cat_puzzle,
|
||||
unsigned_spend_bundle_for_spendable_cats,
|
||||
)
|
||||
from chia.wallet.cat_wallet.cat_wallet import CATWallet
|
||||
from chia.wallet.cat_wallet.dao_cat_info import DAOCATInfo, LockedCoinInfo
|
||||
from chia.wallet.cat_wallet.lineage_store import CATLineageStore
|
||||
from chia.wallet.dao_wallet.dao_utils import (
|
||||
DAO_FINISHED_STATE_HASH,
|
||||
DAO_PROPOSAL_TIMER_MOD_HASH,
|
||||
DAO_TREASURY_MOD_HASH,
|
||||
add_proposal_to_active_list,
|
||||
get_active_votes_from_lockup_puzzle,
|
||||
get_innerpuz_from_lockup_puzzle,
|
||||
get_lockup_puzzle,
|
||||
)
|
||||
from chia.wallet.derivation_record import DerivationRecord
|
||||
from chia.wallet.lineage_proof import LineageProof
|
||||
from chia.wallet.payment import Payment
|
||||
from chia.wallet.puzzles.p2_delegated_puzzle_or_hidden_puzzle import (
|
||||
DEFAULT_HIDDEN_PUZZLE_HASH,
|
||||
calculate_synthetic_secret_key,
|
||||
)
|
||||
from chia.wallet.transaction_record import TransactionRecord
|
||||
from chia.wallet.uncurried_puzzle import uncurry_puzzle
|
||||
from chia.wallet.util.curry_and_treehash import calculate_hash_of_quoted_mod_hash
|
||||
from chia.wallet.util.transaction_type import TransactionType
|
||||
from chia.wallet.util.wallet_sync_utils import fetch_coin_spend
|
||||
from chia.wallet.util.wallet_types import WalletType
|
||||
from chia.wallet.wallet import Wallet
|
||||
from chia.wallet.wallet_coin_record import WalletCoinRecord
|
||||
from chia.wallet.wallet_info import WalletInfo
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from chia.wallet.wallet_state_manager import WalletStateManager
|
||||
|
||||
CAT_MOD_HASH = CAT_MOD.get_tree_hash()
|
||||
CAT_MOD_HASH_HASH = Program.to(CAT_MOD_HASH).get_tree_hash()
|
||||
QUOTED_MOD_HASH = calculate_hash_of_quoted_mod_hash(CAT_MOD_HASH)
|
||||
|
||||
|
||||
class DAOCATWallet:
|
||||
if TYPE_CHECKING:
|
||||
from chia.wallet.wallet_protocol import WalletProtocol
|
||||
|
||||
_protocol_check: ClassVar[WalletProtocol] = cast("DAOCATWallet", None)
|
||||
|
||||
wallet_state_manager: Any
|
||||
log: logging.Logger
|
||||
wallet_info: WalletInfo
|
||||
dao_cat_info: DAOCATInfo
|
||||
standard_wallet: Wallet
|
||||
cost_of_single_tx: Optional[int]
|
||||
lineage_store: CATLineageStore
|
||||
|
||||
@classmethod
|
||||
def type(cls) -> WalletType:
|
||||
return WalletType.DAO_CAT
|
||||
|
||||
@staticmethod
|
||||
async def create(
|
||||
wallet_state_manager: WalletStateManager,
|
||||
wallet: Wallet,
|
||||
wallet_info: WalletInfo,
|
||||
) -> DAOCATWallet:
|
||||
self = DAOCATWallet()
|
||||
|
||||
self.log = logging.getLogger(__name__)
|
||||
|
||||
self.cost_of_single_tx = None
|
||||
self.wallet_state_manager = wallet_state_manager
|
||||
self.wallet_info = wallet_info
|
||||
self.standard_wallet = wallet
|
||||
try:
|
||||
self.dao_cat_info = DAOCATInfo.from_bytes(hexstr_to_bytes(self.wallet_info.data))
|
||||
self.lineage_store = await CATLineageStore.create(self.wallet_state_manager.db_wrapper, self.get_asset_id())
|
||||
except AssertionError as e:
|
||||
self.log.error(f"Error creating DAO CAT wallet: {e}")
|
||||
# Do a migration of the lineage proofs
|
||||
# cat_info = LegacyCATInfo.from_bytes(hexstr_to_bytes(self.wallet_info.data))
|
||||
# self.cat_info = DAOCATInfo(cat_info.limitations_program_hash, cat_info.my_tail)
|
||||
# self.lineage_store = await CATLineageStore.create(self.wallet_state_manager.db_wrapper, self.get_asset_id())
|
||||
# for coin_id, lineage in cat_info.lineage_proofs:
|
||||
# await self.add_lineage(coin_id, lineage)
|
||||
# await self.save_info(self.cat_info)
|
||||
|
||||
return self
|
||||
|
||||
@staticmethod
|
||||
async def get_or_create_wallet_for_cat(
|
||||
wallet_state_manager: Any,
|
||||
wallet: Wallet,
|
||||
limitations_program_hash_hex: str,
|
||||
name: Optional[str] = None,
|
||||
) -> DAOCATWallet:
|
||||
self = DAOCATWallet()
|
||||
self.cost_of_single_tx = None
|
||||
self.standard_wallet = wallet
|
||||
self.log = logging.getLogger(__name__)
|
||||
|
||||
limitations_program_hash_hex = bytes32.from_hexstr(limitations_program_hash_hex).hex() # Normalize the format
|
||||
|
||||
dao_wallet_id = None
|
||||
free_cat_wallet_id = None
|
||||
for id, w in wallet_state_manager.wallets.items():
|
||||
if w.type() == DAOCATWallet.type():
|
||||
assert isinstance(w, DAOCATWallet)
|
||||
if w.get_asset_id() == limitations_program_hash_hex:
|
||||
self.log.warning("Not creating wallet for already existing DAO CAT wallet")
|
||||
return w
|
||||
elif w.type() == CATWallet.type():
|
||||
assert isinstance(w, CATWallet)
|
||||
if w.get_asset_id() == limitations_program_hash_hex:
|
||||
free_cat_wallet_id = w.id()
|
||||
assert free_cat_wallet_id is not None
|
||||
for id, w in wallet_state_manager.wallets.items():
|
||||
if w.type() == WalletType.DAO:
|
||||
self.log.info(f"FOUND DAO WALLET: {w}")
|
||||
self.log.info(f"ALL WALLETS: {wallet_state_manager.wallets}")
|
||||
if w.get_cat_wallet_id() == free_cat_wallet_id:
|
||||
dao_wallet_id = w.id()
|
||||
assert dao_wallet_id is not None
|
||||
self.wallet_state_manager = wallet_state_manager
|
||||
if name is None:
|
||||
name = CATWallet.default_wallet_name_for_unknown_cat(limitations_program_hash_hex)
|
||||
|
||||
limitations_program_hash = bytes32(hexstr_to_bytes(limitations_program_hash_hex))
|
||||
|
||||
self.dao_cat_info = DAOCATInfo(
|
||||
dao_wallet_id,
|
||||
uint64(free_cat_wallet_id),
|
||||
limitations_program_hash,
|
||||
None,
|
||||
[],
|
||||
)
|
||||
info_as_string = bytes(self.dao_cat_info).hex()
|
||||
self.wallet_info = await wallet_state_manager.user_store.create_wallet(name, WalletType.DAO_CAT, info_as_string)
|
||||
|
||||
self.lineage_store = await CATLineageStore.create(self.wallet_state_manager.db_wrapper, self.get_asset_id())
|
||||
await self.wallet_state_manager.add_new_wallet(self)
|
||||
return self
|
||||
|
||||
async def inner_puzzle_for_cat_puzhash(self, cat_hash: bytes32) -> Program:
|
||||
record: Optional[
|
||||
DerivationRecord
|
||||
] = await self.wallet_state_manager.puzzle_store.get_derivation_record_for_puzzle_hash(cat_hash)
|
||||
if record is None:
|
||||
raise RuntimeError(f"Missing Derivation Record for CAT puzzle_hash {cat_hash}")
|
||||
inner_puzzle: Program = self.standard_wallet.puzzle_for_pk(record.pubkey)
|
||||
return inner_puzzle
|
||||
|
||||
async def coin_added(self, coin: Coin, height: uint32, peer: WSChiaConnection) -> None:
|
||||
"""Notification from wallet state manager that wallet has been received."""
|
||||
self.log.info(f"DAO CAT wallet has been notified that {coin} was added")
|
||||
# We can't get the inner puzzle for this coin's puzhash because it has the lockup layer.
|
||||
# So look for it's parent coin, and get the inner puzzle for it, which should be the same as
|
||||
# the one contained in the lockup.
|
||||
wallet_node: Any = self.wallet_state_manager.wallet_node
|
||||
parent_coin = (await wallet_node.get_coin_state([coin.parent_coin_info], peer, height))[0]
|
||||
parent_spend = await fetch_coin_spend(height, parent_coin.coin, peer)
|
||||
uncurried = parent_spend.puzzle_reveal.uncurry()
|
||||
cat_inner = uncurried[1].at("rrf")
|
||||
lockup_puz, lockup_args = cat_inner.uncurry()
|
||||
active_votes_list: List[Optional[bytes32]] = []
|
||||
|
||||
record = await self.wallet_state_manager.puzzle_store.get_derivation_record_for_puzzle_hash(coin.puzzle_hash)
|
||||
if record:
|
||||
inner_puzzle: Program = self.standard_wallet.puzzle_for_pk(record.pubkey)
|
||||
else:
|
||||
inner_puzzle = cat_inner.uncurry()[1].at("rrrrrrrrf")
|
||||
active_votes_list = [bytes32(prop.as_atom()) for prop in lockup_args.at("rrrrrrrf").as_iter()]
|
||||
|
||||
if parent_spend.coin.puzzle_hash == coin.puzzle_hash:
|
||||
# shortcut, works for change
|
||||
lockup_puz = cat_inner
|
||||
else:
|
||||
# TODO: Move this section to dao_utils once we've got the close spend sorted
|
||||
solution = parent_spend.solution.to_program().first()
|
||||
if solution.first() == Program.to(0):
|
||||
# No vote is being added so inner puz stays the same
|
||||
try:
|
||||
removals = solution.at("rrrf")
|
||||
if removals != Program.to(0):
|
||||
for removal in removals.as_iter():
|
||||
active_votes_list.remove(bytes32(removal.as_atom()))
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
new_vote = solution.at("rrrf")
|
||||
active_votes_list.insert(0, bytes32(new_vote.as_atom()))
|
||||
|
||||
lockup_puz = get_lockup_puzzle(
|
||||
self.dao_cat_info.limitations_program_hash,
|
||||
active_votes_list,
|
||||
inner_puzzle,
|
||||
)
|
||||
|
||||
new_cat_puzhash = construct_cat_puzzle(
|
||||
CAT_MOD, self.dao_cat_info.limitations_program_hash, lockup_puz
|
||||
).get_tree_hash()
|
||||
|
||||
if new_cat_puzhash != coin.puzzle_hash:
|
||||
raise ValueError(f"Cannot add coin - incorrect lockup puzzle: {coin}")
|
||||
|
||||
lineage_proof = LineageProof(coin.parent_coin_info, lockup_puz.get_tree_hash(), uint64(coin.amount))
|
||||
await self.add_lineage(coin.name(), lineage_proof)
|
||||
|
||||
# add the new coin to the list of locked coins and remove the spent coin
|
||||
locked_coins = [x for x in self.dao_cat_info.locked_coins if x.coin != parent_spend.coin]
|
||||
new_info = LockedCoinInfo(coin, lockup_puz, active_votes_list)
|
||||
if new_info not in locked_coins:
|
||||
locked_coins.append(LockedCoinInfo(coin, lockup_puz, active_votes_list))
|
||||
dao_cat_info: DAOCATInfo = DAOCATInfo(
|
||||
self.dao_cat_info.dao_wallet_id,
|
||||
self.dao_cat_info.free_cat_wallet_id,
|
||||
self.dao_cat_info.limitations_program_hash,
|
||||
self.dao_cat_info.my_tail,
|
||||
locked_coins,
|
||||
)
|
||||
await self.save_info(dao_cat_info)
|
||||
|
||||
async def add_lineage(self, name: bytes32, lineage: Optional[LineageProof]) -> None:
|
||||
"""
|
||||
Lineage proofs are stored as a list of parent coins and the lineage proof you will need if they are the
|
||||
parent of the coin you are trying to spend. 'If I'm your parent, here's the info you need to spend yourself'
|
||||
"""
|
||||
self.log.info(f"Adding parent {name.hex()}: {lineage}")
|
||||
if lineage is not None:
|
||||
await self.lineage_store.add_lineage_proof(name, lineage)
|
||||
|
||||
async def get_lineage_proof_for_coin(self, coin: Coin) -> Optional[LineageProof]:
|
||||
return await self.lineage_store.get_lineage_proof(coin.parent_coin_info)
|
||||
|
||||
async def remove_lineage(self, name: bytes32) -> None:
|
||||
self.log.info(f"Removing parent {name} (probably had a non-CAT parent)")
|
||||
await self.lineage_store.remove_lineage_proof(name)
|
||||
|
||||
async def advanced_select_coins(self, amount: uint64, proposal_id: bytes32) -> List[LockedCoinInfo]:
|
||||
coins = []
|
||||
s = 0
|
||||
for coin in self.dao_cat_info.locked_coins:
|
||||
compatible = True
|
||||
for active_vote in coin.active_votes:
|
||||
if active_vote == proposal_id:
|
||||
compatible = False
|
||||
break
|
||||
if compatible:
|
||||
coins.append(coin)
|
||||
s += coin.coin.amount
|
||||
if s >= amount:
|
||||
break
|
||||
if s < amount:
|
||||
raise ValueError(
|
||||
"We do not have enough CATs in Voting Mode right now. "
|
||||
"Please convert some more or try again with permission to convert."
|
||||
)
|
||||
return coins
|
||||
|
||||
def id(self) -> uint32:
|
||||
return self.wallet_info.id
|
||||
|
||||
async def create_vote_spend(
|
||||
self,
|
||||
amount: uint64,
|
||||
proposal_id: bytes32,
|
||||
is_yes_vote: bool,
|
||||
curry_vals: Optional[Tuple[Program, Program, Program]] = None,
|
||||
) -> SpendBundle:
|
||||
coins: List[LockedCoinInfo] = await self.advanced_select_coins(amount, proposal_id)
|
||||
running_sum = 0 # this will be used for change calculation
|
||||
change = sum(c.coin.amount for c in coins) - amount
|
||||
extra_delta, limitations_solution = 0, Program.to([])
|
||||
limitations_program_reveal = Program.to([])
|
||||
spendable_cat_list = []
|
||||
dao_wallet = self.wallet_state_manager.wallets[self.dao_cat_info.dao_wallet_id]
|
||||
treasury_id = dao_wallet.dao_info.treasury_id
|
||||
if curry_vals is None:
|
||||
YES_VOTES, TOTAL_VOTES, INNERPUZHASH = dao_wallet.get_proposal_curry_values(proposal_id)
|
||||
else:
|
||||
YES_VOTES, TOTAL_VOTES, INNERPUZHASH = curry_vals
|
||||
# proposal_curry_vals = [YES_VOTES, TOTAL_VOTES, INNERPUZ]
|
||||
for lci in coins:
|
||||
# my_id ; if my_id is 0 we do the return to return_address (exit voting mode) spend case
|
||||
# inner_solution
|
||||
# my_amount
|
||||
# new_proposal_vote_id_or_removal_id ; if we're exiting fully, set this to 0
|
||||
# proposal_curry_vals
|
||||
# vote_info
|
||||
# vote_amount
|
||||
# my_puzhash
|
||||
# new_innerpuzhash ; only include this if we're changing owners
|
||||
coin = lci.coin
|
||||
|
||||
vote_info = 0
|
||||
new_innerpuzzle = add_proposal_to_active_list(lci.inner_puzzle, proposal_id)
|
||||
standard_inner_puz = get_innerpuz_from_lockup_puzzle(new_innerpuzzle)
|
||||
# add_proposal_to_active_list also verifies that the lci.inner_puzzle is accurate
|
||||
# We must create either: one coin with the new puzzle and all our value
|
||||
# OR
|
||||
# a coin with the new puzzle and part of our amount AND a coin with our current puzzle and the change
|
||||
|
||||
# We must also create a puzzle announcement which announces the following:
|
||||
# message = (sha256tree (list new_proposal_vote_id_or_removal_id vote_amount vote_info my_id))
|
||||
message = Program.to([proposal_id, amount, is_yes_vote, coin.name()]).get_tree_hash()
|
||||
# We also collect 4 pieces of data for the DAOWallet in order to spend the Proposal properly
|
||||
|
||||
# vote_amount_or_solution ; The qty of "votes" to add or subtract. ALWAYS POSITIVE.
|
||||
# vote_info_or_p2_singleton_mod_hash ; vote_info is whether we are voting YES or NO. XXX rename vote_type?
|
||||
# vote_coin_id_or_current_cat_issuance ; this is either the coin ID we're taking a vote from OR...
|
||||
# ; the total number of CATs in circulation according to the treasury
|
||||
# previous_votes_or_pass_margin ; this is the active votes of the lockup we're communicating with
|
||||
# ; OR this is what percentage of the total votes must be YES - represented as an integer from 0 to 10,000 - typically this is set at 5100 (51%)
|
||||
# lockup_innerpuzhash_or_attendance_required ; this is either the innerpuz of the locked up CAT we're taking a vote from OR
|
||||
# ; the attendance required - the percentage of the current issuance which must have voted represented as 0 to 10,000 - this is announced by the treasury
|
||||
vote_amounts_list = []
|
||||
voting_coin_id_list = []
|
||||
previous_votes_list = []
|
||||
lockup_innerpuz_list = []
|
||||
if running_sum + coin.amount <= amount:
|
||||
vote_amount = coin.amount
|
||||
running_sum = running_sum + coin.amount
|
||||
# CREATE_COIN new_puzzle coin.amount
|
||||
# CREATE_PUZZLE_ANNOUNCEMENT (sha256tree (list new_proposal_vote_id_or_removal_id my_amount vote_info my_id))
|
||||
# Payment(change_puzhash, uint64(change), [change_puzhash])
|
||||
primaries = [
|
||||
Payment(
|
||||
new_innerpuzzle.get_tree_hash(),
|
||||
uint64(vote_amount),
|
||||
[standard_inner_puz.get_tree_hash()],
|
||||
)
|
||||
]
|
||||
puzzle_announcements = set([message])
|
||||
inner_solution = self.standard_wallet.make_solution(
|
||||
primaries=primaries, puzzle_announcements=puzzle_announcements
|
||||
)
|
||||
else:
|
||||
vote_amount = amount - running_sum
|
||||
# CREATE_COIN new_puzzle vote_amount
|
||||
# CREATE_COIN old_puzzle change
|
||||
# CREATE_PUZZLE_ANNOUNCEMENT (sha256tree (list new_proposal_vote_id_or_removal_id my_amount vote_info my_id))
|
||||
primaries = [
|
||||
Payment(
|
||||
new_innerpuzzle.get_tree_hash(),
|
||||
uint64(vote_amount),
|
||||
[new_innerpuzzle.get_tree_hash()],
|
||||
),
|
||||
]
|
||||
if change > 0:
|
||||
primaries.append(
|
||||
Payment(
|
||||
lci.inner_puzzle.get_tree_hash(),
|
||||
uint64(change),
|
||||
[lci.inner_puzzle.get_tree_hash()],
|
||||
)
|
||||
)
|
||||
puzzle_announcements = set([message])
|
||||
inner_solution = self.standard_wallet.make_solution(
|
||||
primaries=primaries, puzzle_announcements=puzzle_announcements
|
||||
)
|
||||
if is_yes_vote:
|
||||
vote_info = 1
|
||||
vote_amounts_list.append(vote_amount)
|
||||
voting_coin_id_list.append(coin.name())
|
||||
previous_votes_list.append(get_active_votes_from_lockup_puzzle(lci.inner_puzzle))
|
||||
lockup_innerpuz_list.append(get_innerpuz_from_lockup_puzzle(lci.inner_puzzle))
|
||||
# my_id ; if my_id is 0 we do the return to return_address (exit voting mode) spend case
|
||||
# inner_solution
|
||||
# my_amount
|
||||
# new_proposal_vote_id_or_removal_id ; if we're exiting fully, set this to 0
|
||||
# proposal_curry_vals
|
||||
# vote_info
|
||||
# vote_amount
|
||||
# my_inner_puzhash
|
||||
# new_innerpuzhash ; only include this if we're changing owners
|
||||
|
||||
# proposal_curry_vals is:
|
||||
# (
|
||||
# TREASURY_MOD_HASH
|
||||
# PROPOSAL_TIMER_MOD_HASH
|
||||
# TREASURY_ID
|
||||
# YES_VOTES
|
||||
# TOTAL_VOTES
|
||||
# INNERPUZHASH
|
||||
# )
|
||||
solution = Program.to(
|
||||
[
|
||||
coin.name(),
|
||||
inner_solution,
|
||||
coin.amount,
|
||||
proposal_id,
|
||||
[
|
||||
DAO_TREASURY_MOD_HASH,
|
||||
DAO_PROPOSAL_TIMER_MOD_HASH,
|
||||
treasury_id,
|
||||
YES_VOTES,
|
||||
TOTAL_VOTES,
|
||||
INNERPUZHASH,
|
||||
],
|
||||
vote_info,
|
||||
vote_amount,
|
||||
lci.inner_puzzle.get_tree_hash(),
|
||||
0,
|
||||
]
|
||||
)
|
||||
# breakpoint()
|
||||
lineage_proof = await self.get_lineage_proof_for_coin(coin)
|
||||
assert lineage_proof is not None
|
||||
new_spendable_cat = SpendableCAT(
|
||||
coin,
|
||||
self.dao_cat_info.limitations_program_hash,
|
||||
lci.inner_puzzle,
|
||||
solution,
|
||||
limitations_solution=limitations_solution,
|
||||
extra_delta=extra_delta,
|
||||
lineage_proof=lineage_proof,
|
||||
limitations_program_reveal=limitations_program_reveal,
|
||||
)
|
||||
spendable_cat_list.append(new_spendable_cat)
|
||||
running_sum += coin.amount
|
||||
|
||||
cat_spend_bundle = unsigned_spend_bundle_for_spendable_cats(CAT_MOD, spendable_cat_list)
|
||||
spend_bundle = await self.sign(cat_spend_bundle)
|
||||
|
||||
# breakpoint()
|
||||
return spend_bundle
|
||||
|
||||
async def get_new_vote_state_puzzle(self, coins: Optional[List[Coin]] = None) -> Program:
|
||||
innerpuz = await self.get_new_inner_puzzle()
|
||||
puzzle = get_lockup_puzzle(
|
||||
self.dao_cat_info.limitations_program_hash,
|
||||
[],
|
||||
innerpuz,
|
||||
)
|
||||
cat_puzzle: Program = construct_cat_puzzle(CAT_MOD, self.dao_cat_info.limitations_program_hash, puzzle)
|
||||
# breakpoint()
|
||||
await self.wallet_state_manager.add_interested_puzzle_hashes([puzzle.get_tree_hash()], [self.id()])
|
||||
await self.wallet_state_manager.add_interested_puzzle_hashes([cat_puzzle.get_tree_hash()], [self.id()])
|
||||
return puzzle
|
||||
|
||||
async def create_new_dao_cats(
|
||||
self,
|
||||
amount: uint64,
|
||||
push: bool = False,
|
||||
fee: uint64 = uint64(0),
|
||||
reuse_puzhash: Optional[bool] = None,
|
||||
) -> Tuple[List[TransactionRecord], Optional[List[Coin]]]:
|
||||
# check there are enough cats to convert
|
||||
cat_wallet = self.wallet_state_manager.wallets[self.dao_cat_info.free_cat_wallet_id]
|
||||
cat_balance = await cat_wallet.get_spendable_balance()
|
||||
if cat_balance < amount:
|
||||
raise ValueError(f"Insufficient CAT balance. Requested: {amount} Available: {cat_balance}")
|
||||
# get the lockup puzzle hash
|
||||
lockup_puzzle = await self.get_new_puzzle()
|
||||
# create the cat spend
|
||||
txs = await cat_wallet.generate_signed_transactions(
|
||||
[amount], [lockup_puzzle.get_tree_hash()], fee=fee, reuse_puzhash=reuse_puzhash
|
||||
)
|
||||
new_cats = []
|
||||
cat_puzzle_hash: bytes32 = construct_cat_puzzle(
|
||||
CAT_MOD, self.dao_cat_info.limitations_program_hash, lockup_puzzle
|
||||
).get_tree_hash()
|
||||
if push:
|
||||
for tx in txs:
|
||||
await self.wallet_state_manager.add_pending_transaction(tx)
|
||||
for coin in tx.spend_bundle.additions():
|
||||
if coin.puzzle_hash == cat_puzzle_hash:
|
||||
new_cats.append(coin)
|
||||
await self.wallet_state_manager.add_interested_puzzle_hashes([cat_puzzle_hash], [self.id()])
|
||||
|
||||
return txs, new_cats
|
||||
|
||||
async def exit_vote_state(
|
||||
self,
|
||||
coins: List[LockedCoinInfo],
|
||||
fee: uint64 = uint64(0),
|
||||
push: bool = True,
|
||||
reuse_puzhash: Optional[bool] = None,
|
||||
) -> SpendBundle:
|
||||
extra_delta, limitations_solution = 0, Program.to([])
|
||||
limitations_program_reveal = Program.to([])
|
||||
spendable_cat_list = []
|
||||
total_amt = 0
|
||||
spent_coins = []
|
||||
for lci in coins:
|
||||
coin = lci.coin
|
||||
if reuse_puzhash:
|
||||
new_inner_puzhash = await self.standard_wallet.get_puzzle_hash(new=False)
|
||||
else:
|
||||
new_inner_puzhash = await self.standard_wallet.get_puzzle_hash(new=True)
|
||||
|
||||
# CREATE_COIN new_puzzle coin.amount
|
||||
primaries = [
|
||||
Payment(
|
||||
new_inner_puzhash,
|
||||
uint64(coin.amount),
|
||||
[new_inner_puzhash],
|
||||
),
|
||||
]
|
||||
total_amt += coin.amount
|
||||
inner_solution = self.standard_wallet.make_solution(
|
||||
primaries=primaries,
|
||||
)
|
||||
# my_id ; if my_id is 0 we do the return to return_address (exit voting mode) spend case
|
||||
# inner_solution
|
||||
# my_amount
|
||||
# new_proposal_vote_id_or_removal_id ; if we're exiting fully, set this to 0
|
||||
# proposal_curry_vals
|
||||
# vote_info
|
||||
# vote_amount
|
||||
# my_puzhash
|
||||
solution = Program.to(
|
||||
[
|
||||
0,
|
||||
inner_solution,
|
||||
coin.amount,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
]
|
||||
)
|
||||
lineage_proof = await self.get_lineage_proof_for_coin(coin)
|
||||
assert lineage_proof is not None
|
||||
new_spendable_cat = SpendableCAT(
|
||||
coin,
|
||||
self.dao_cat_info.limitations_program_hash,
|
||||
lci.inner_puzzle,
|
||||
solution,
|
||||
limitations_solution=limitations_solution,
|
||||
extra_delta=extra_delta,
|
||||
lineage_proof=lineage_proof,
|
||||
limitations_program_reveal=limitations_program_reveal,
|
||||
)
|
||||
spendable_cat_list.append(new_spendable_cat)
|
||||
spent_coins.append(coin)
|
||||
|
||||
cat_spend_bundle = unsigned_spend_bundle_for_spendable_cats(CAT_MOD, spendable_cat_list)
|
||||
spend_bundle = await self.sign(cat_spend_bundle)
|
||||
|
||||
if fee > 0:
|
||||
dao_wallet = self.wallet_state_manager.wallets[self.dao_cat_info.dao_wallet_id]
|
||||
chia_tx = await dao_wallet.create_tandem_xch_tx(fee, reuse_puzhash=reuse_puzhash)
|
||||
assert chia_tx.spend_bundle is not None
|
||||
full_spend = SpendBundle.aggregate([spend_bundle, chia_tx.spend_bundle])
|
||||
else:
|
||||
full_spend = spend_bundle
|
||||
|
||||
if push:
|
||||
record = TransactionRecord(
|
||||
confirmed_at_height=uint32(0),
|
||||
created_at_time=uint64(int(time.time())),
|
||||
to_puzzle_hash=new_inner_puzhash,
|
||||
amount=uint64(total_amt),
|
||||
fee_amount=fee,
|
||||
confirmed=False,
|
||||
sent=uint32(10),
|
||||
spend_bundle=full_spend,
|
||||
additions=full_spend.additions(),
|
||||
removals=full_spend.removals(),
|
||||
wallet_id=self.id(),
|
||||
sent_to=[],
|
||||
trade_id=None,
|
||||
type=uint32(TransactionType.INCOMING_TX.value),
|
||||
name=bytes32(token_bytes()),
|
||||
memos=[],
|
||||
)
|
||||
await self.wallet_state_manager.add_pending_transaction(record)
|
||||
|
||||
# TODO: Hack to just drop coins from locked list. Need to catch this event in WSM to
|
||||
# check if we're adding CATs from our DAO CAT wallet and update the locked coin list
|
||||
# accordingly
|
||||
new_locked_coins = [x for x in self.dao_cat_info.locked_coins if x.coin not in spent_coins]
|
||||
dao_cat_info: DAOCATInfo = DAOCATInfo(
|
||||
self.dao_cat_info.dao_wallet_id,
|
||||
self.dao_cat_info.free_cat_wallet_id,
|
||||
self.dao_cat_info.limitations_program_hash,
|
||||
self.dao_cat_info.my_tail,
|
||||
new_locked_coins,
|
||||
)
|
||||
await self.save_info(dao_cat_info)
|
||||
|
||||
return spend_bundle
|
||||
|
||||
async def remove_active_proposal(
|
||||
self, proposal_id_list: List[bytes32], fee: uint64 = uint64(0), push: bool = True
|
||||
) -> SpendBundle:
|
||||
locked_coins: List[Tuple[LockedCoinInfo, List[bytes32]]] = []
|
||||
for lci in self.dao_cat_info.locked_coins:
|
||||
my_finished_proposals = []
|
||||
for active_vote in lci.active_votes:
|
||||
if active_vote in proposal_id_list:
|
||||
my_finished_proposals.append(active_vote)
|
||||
locked_coins.append((lci, my_finished_proposals))
|
||||
extra_delta, limitations_solution = 0, Program.to([])
|
||||
limitations_program_reveal = Program.to([])
|
||||
spendable_cat_list = []
|
||||
# cat_wallet = await self.wallet_state_manager.user_store.get_wallet_by_id(self.dao_cat_info.free_cat_wallet_id)
|
||||
dao_wallet = self.wallet_state_manager.wallets[self.dao_cat_info.dao_wallet_id]
|
||||
|
||||
for lci_proposals_tuple in locked_coins:
|
||||
proposal_curry_vals = []
|
||||
coin = lci_proposals_tuple[0].coin
|
||||
proposals = lci_proposals_tuple[1]
|
||||
|
||||
for proposal_id in proposals:
|
||||
YES_VOTES, TOTAL_VOTES, INNERPUZ = dao_wallet.get_proposal_curry_values(proposal_id)
|
||||
proposal_curry_vals.append([YES_VOTES, TOTAL_VOTES, INNERPUZ])
|
||||
# new_innerpuzzle = await cat_wallet.get_new_inner_puzzle()
|
||||
# my_id ; if my_id is 0 we do the return to return_address (exit voting mode) spend case
|
||||
# inner_solution
|
||||
# my_amount
|
||||
# new_proposal_vote_id_or_removal_id ; if we're exiting fully, set this to 0
|
||||
# proposal_curry_vals
|
||||
# vote_info
|
||||
# vote_amount
|
||||
# my_puzhash
|
||||
solution = Program.to(
|
||||
[
|
||||
0,
|
||||
0,
|
||||
coin.amount,
|
||||
proposals,
|
||||
proposal_curry_vals,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
]
|
||||
)
|
||||
lineage_proof = await self.get_lineage_proof_for_coin(coin)
|
||||
assert lineage_proof is not None
|
||||
new_spendable_cat = SpendableCAT(
|
||||
coin,
|
||||
self.dao_cat_info.limitations_program_hash,
|
||||
lci.inner_puzzle,
|
||||
solution,
|
||||
limitations_solution=limitations_solution,
|
||||
extra_delta=extra_delta,
|
||||
lineage_proof=lineage_proof,
|
||||
limitations_program_reveal=limitations_program_reveal,
|
||||
)
|
||||
spendable_cat_list.append(new_spendable_cat)
|
||||
|
||||
cat_spend_bundle = unsigned_spend_bundle_for_spendable_cats(CAT_MOD, spendable_cat_list)
|
||||
spend_bundle = await self.sign(cat_spend_bundle)
|
||||
|
||||
if fee > 0:
|
||||
dao_wallet = self.wallet_state_manager.wallets[self.dao_cat_info.dao_wallet_id]
|
||||
chia_tx = await dao_wallet.create_tandem_xch_tx(fee)
|
||||
assert chia_tx.spend_bundle is not None
|
||||
full_spend = SpendBundle.aggregate([spend_bundle, chia_tx.spend_bundle])
|
||||
else:
|
||||
full_spend = spend_bundle
|
||||
|
||||
if push:
|
||||
record = TransactionRecord(
|
||||
confirmed_at_height=uint32(0),
|
||||
created_at_time=uint64(int(time.time())),
|
||||
to_puzzle_hash=DAO_FINISHED_STATE_HASH,
|
||||
amount=uint64(1),
|
||||
fee_amount=fee,
|
||||
confirmed=False,
|
||||
sent=uint32(10),
|
||||
spend_bundle=full_spend,
|
||||
additions=full_spend.additions(),
|
||||
removals=full_spend.removals(),
|
||||
wallet_id=self.id(),
|
||||
sent_to=[],
|
||||
trade_id=None,
|
||||
type=uint32(TransactionType.INCOMING_TX.value),
|
||||
name=bytes32(token_bytes()),
|
||||
memos=[],
|
||||
)
|
||||
await self.wallet_state_manager.add_pending_transaction(record)
|
||||
|
||||
return full_spend
|
||||
|
||||
def get_asset_id(self) -> str:
|
||||
return bytes(self.dao_cat_info.limitations_program_hash).hex()
|
||||
|
||||
async def get_new_inner_hash(self) -> bytes32:
|
||||
puzzle = await self.get_new_inner_puzzle()
|
||||
return puzzle.get_tree_hash()
|
||||
|
||||
async def get_new_inner_puzzle(self) -> Program:
|
||||
return await self.standard_wallet.get_new_puzzle()
|
||||
|
||||
async def get_new_puzzle(self) -> Program:
|
||||
record = await self.wallet_state_manager.get_unused_derivation_record(self.id())
|
||||
inner_puzzle = self.standard_wallet.puzzle_for_pk(record.pubkey)
|
||||
puzzle = get_lockup_puzzle(
|
||||
self.dao_cat_info.limitations_program_hash,
|
||||
[],
|
||||
inner_puzzle,
|
||||
)
|
||||
cat_puzzle: Program = construct_cat_puzzle(CAT_MOD, self.dao_cat_info.limitations_program_hash, puzzle)
|
||||
await self.wallet_state_manager.add_interested_puzzle_hashes([puzzle.get_tree_hash()], [self.id()])
|
||||
await self.wallet_state_manager.add_interested_puzzle_hashes([cat_puzzle.get_tree_hash()], [self.id()])
|
||||
return puzzle
|
||||
|
||||
async def get_new_puzzlehash(self) -> bytes32:
|
||||
puzzle = await self.get_new_puzzle()
|
||||
return puzzle.get_tree_hash()
|
||||
|
||||
def puzzle_for_pk(self, pubkey: G1Element) -> Program:
|
||||
inner_puzzle = self.standard_wallet.puzzle_for_pk(pubkey)
|
||||
puzzle = get_lockup_puzzle(
|
||||
self.dao_cat_info.limitations_program_hash,
|
||||
[],
|
||||
inner_puzzle,
|
||||
)
|
||||
cat_puzzle: Program = construct_cat_puzzle(CAT_MOD, self.dao_cat_info.limitations_program_hash, puzzle)
|
||||
return cat_puzzle
|
||||
|
||||
def puzzle_hash_for_pk(self, pubkey: G1Element) -> bytes32:
|
||||
puzzle = self.puzzle_for_pk(pubkey)
|
||||
return puzzle.get_tree_hash()
|
||||
|
||||
def require_derivation_paths(self) -> bool:
|
||||
return True
|
||||
|
||||
async def get_cat_spendable_coins(self, records: Optional[Set[WalletCoinRecord]] = None) -> List[WalletCoinRecord]:
|
||||
result: List[WalletCoinRecord] = []
|
||||
|
||||
record_list: Set[WalletCoinRecord] = await self.wallet_state_manager.get_spendable_coins_for_wallet(
|
||||
self.id(), records
|
||||
)
|
||||
|
||||
for record in record_list:
|
||||
lineage = await self.get_lineage_proof_for_coin(record.coin)
|
||||
if lineage is not None and not lineage.is_none():
|
||||
result.append(record)
|
||||
|
||||
return result
|
||||
|
||||
async def get_spendable_balance(self, records: Optional[Set[WalletCoinRecord]] = None) -> uint128:
|
||||
return uint128(0)
|
||||
|
||||
async def get_confirmed_balance(self, record_list: Optional[Set[WalletCoinRecord]] = None) -> uint128:
|
||||
amount = 0
|
||||
for coin in self.dao_cat_info.locked_coins:
|
||||
amount += coin.coin.amount
|
||||
return uint128(amount)
|
||||
|
||||
async def get_unconfirmed_balance(self, unspent_records: Optional[Set[WalletCoinRecord]] = None) -> uint128:
|
||||
return uint128(0)
|
||||
|
||||
async def get_pending_change_balance(self) -> uint64:
|
||||
return uint64(0)
|
||||
|
||||
async def select_coins(
|
||||
self,
|
||||
amount: uint64,
|
||||
exclude: Optional[List[Coin]] = None,
|
||||
min_coin_amount: Optional[uint64] = None,
|
||||
max_coin_amount: Optional[uint64] = None,
|
||||
excluded_coin_amounts: Optional[List[uint64]] = None,
|
||||
) -> Set[Coin]:
|
||||
return set()
|
||||
|
||||
async def get_max_send_amount(self, unspent_records: Optional[Set[WalletCoinRecord]] = None) -> uint128:
|
||||
return uint128(0)
|
||||
|
||||
async def get_votable_balance(
|
||||
self,
|
||||
proposal_id: Optional[bytes32] = None,
|
||||
include_free_cats: bool = True,
|
||||
) -> uint64:
|
||||
balance = 0
|
||||
for coin in self.dao_cat_info.locked_coins:
|
||||
if proposal_id is not None:
|
||||
compatible = True
|
||||
for active_vote in coin.active_votes:
|
||||
if active_vote == proposal_id:
|
||||
compatible = False
|
||||
break
|
||||
if compatible:
|
||||
balance += coin.coin.amount
|
||||
else:
|
||||
balance += coin.coin.amount
|
||||
if include_free_cats:
|
||||
cat_wallet = self.wallet_state_manager.wallets[self.dao_cat_info.free_cat_wallet_id]
|
||||
cat_balance = await cat_wallet.get_spendable_balance()
|
||||
balance += cat_balance
|
||||
return uint64(balance)
|
||||
|
||||
async def sign(self, spend_bundle: SpendBundle) -> SpendBundle:
|
||||
sigs: List[G2Element] = []
|
||||
for spend in spend_bundle.coin_spends:
|
||||
args = match_cat_puzzle(uncurry_puzzle(spend.puzzle_reveal.to_program()))
|
||||
if args is not None:
|
||||
_, _, inner_puzzle = args
|
||||
inner_puzzle = get_innerpuz_from_lockup_puzzle(inner_puzzle)
|
||||
puzzle_hash = inner_puzzle.get_tree_hash()
|
||||
private = await self.wallet_state_manager.get_private_key(puzzle_hash)
|
||||
synthetic_secret_key = calculate_synthetic_secret_key(private, DEFAULT_HIDDEN_PUZZLE_HASH)
|
||||
conditions = conditions_dict_for_solution(
|
||||
spend.puzzle_reveal.to_program(),
|
||||
spend.solution.to_program(),
|
||||
self.wallet_state_manager.constants.MAX_BLOCK_COST_CLVM,
|
||||
)
|
||||
if conditions is not None:
|
||||
synthetic_pk = synthetic_secret_key.get_g1()
|
||||
for pk, msg in pkm_pairs_for_conditions_dict(
|
||||
conditions, spend.coin.name(), self.wallet_state_manager.constants.AGG_SIG_ME_ADDITIONAL_DATA
|
||||
):
|
||||
try:
|
||||
assert bytes(synthetic_pk) == pk
|
||||
sigs.append(AugSchemeMPL.sign(synthetic_secret_key, msg))
|
||||
except AssertionError:
|
||||
raise ValueError("This spend bundle cannot be signed by this DAO CAT wallet")
|
||||
|
||||
agg_sig = AugSchemeMPL.aggregate(sigs)
|
||||
return SpendBundle.aggregate([spend_bundle, SpendBundle([], agg_sig)])
|
||||
|
||||
async def save_info(self, dao_cat_info: DAOCATInfo) -> None:
|
||||
self.dao_cat_info = dao_cat_info
|
||||
current_info = self.wallet_info
|
||||
data_str = bytes(dao_cat_info).hex()
|
||||
wallet_info = WalletInfo(current_info.id, current_info.name, current_info.type, data_str)
|
||||
self.wallet_info = wallet_info
|
||||
await self.wallet_state_manager.user_store.update_wallet(wallet_info)
|
||||
|
||||
def get_name(self) -> str:
|
||||
return self.wallet_info.name
|
|
@ -0,0 +1,54 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
from chia.types.blockchain_format.coin import Coin
|
||||
from chia.types.blockchain_format.program import Program
|
||||
from chia.types.blockchain_format.sized_bytes import bytes32
|
||||
from chia.util.ints import uint32, uint64
|
||||
from chia.util.streamable import Streamable, streamable
|
||||
from chia.wallet.lineage_proof import LineageProof
|
||||
|
||||
|
||||
@streamable
|
||||
@dataclass(frozen=True)
|
||||
class ProposalInfo(Streamable):
|
||||
proposal_id: bytes32 # this is launcher_id
|
||||
inner_puzzle: Program
|
||||
amount_voted: uint64
|
||||
yes_votes: uint64
|
||||
current_coin: Coin
|
||||
current_innerpuz: Optional[Program]
|
||||
timer_coin: Optional[Coin] # if this is None then the proposal has finished
|
||||
singleton_block_height: uint32 # Block height that current proposal singleton coin was created in
|
||||
passed: Optional[bool]
|
||||
closed: Optional[bool]
|
||||
|
||||
|
||||
@streamable
|
||||
@dataclass(frozen=True)
|
||||
class DAOInfo(Streamable):
|
||||
treasury_id: bytes32
|
||||
cat_wallet_id: uint32
|
||||
dao_cat_wallet_id: uint32
|
||||
proposals_list: List[ProposalInfo]
|
||||
parent_info: List[Tuple[bytes32, Optional[LineageProof]]] # {coin.name(): LineageProof}
|
||||
current_treasury_coin: Optional[Coin]
|
||||
current_treasury_innerpuz: Optional[Program]
|
||||
singleton_block_height: uint32 # the block height that the current treasury singleton was created in
|
||||
filter_below_vote_amount: uint64 # we ignore proposals with fewer votes than this - defaults to 1
|
||||
assets: List[Optional[bytes32]]
|
||||
current_height: uint64
|
||||
|
||||
|
||||
@streamable
|
||||
@dataclass(frozen=True)
|
||||
class DAORules(Streamable):
|
||||
proposal_timelock: uint64
|
||||
soft_close_length: uint64
|
||||
attendance_required: uint64
|
||||
pass_percentage: uint64
|
||||
self_destruct_length: uint64
|
||||
oracle_spend_delay: uint64
|
||||
proposal_minimum_amount: uint64
|
|
@ -0,0 +1,706 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Iterator, List, Optional, Tuple
|
||||
|
||||
from clvm.casts import int_from_bytes
|
||||
from clvm.EvalError import EvalError
|
||||
|
||||
from chia.types.blockchain_format.program import Program
|
||||
from chia.types.blockchain_format.sized_bytes import bytes32
|
||||
from chia.types.coin_spend import CoinSpend
|
||||
from chia.util.ints import uint64
|
||||
from chia.wallet.cat_wallet.cat_utils import CAT_MOD, CAT_MOD_HASH, match_cat_puzzle
|
||||
from chia.wallet.dao_wallet.dao_info import DAORules
|
||||
from chia.wallet.puzzles.load_clvm import load_clvm
|
||||
from chia.wallet.puzzles.p2_delegated_puzzle_or_hidden_puzzle import MOD
|
||||
from chia.wallet.singleton import create_singleton_puzzle, get_inner_puzzle_from_singleton, get_singleton_struct_for_id
|
||||
from chia.wallet.uncurried_puzzle import UncurriedPuzzle
|
||||
|
||||
SINGLETON_MOD: Program = load_clvm("singleton_top_layer_v1_1.clsp")
|
||||
SINGLETON_MOD_HASH: bytes32 = SINGLETON_MOD.get_tree_hash()
|
||||
SINGLETON_LAUNCHER: Program = load_clvm("singleton_launcher.clsp")
|
||||
SINGLETON_LAUNCHER_HASH: bytes32 = SINGLETON_LAUNCHER.get_tree_hash()
|
||||
DAO_LOCKUP_MOD: Program = load_clvm("dao_lockup.clsp")
|
||||
DAO_LOCKUP_MOD_HASH: bytes32 = DAO_LOCKUP_MOD.get_tree_hash()
|
||||
DAO_PROPOSAL_TIMER_MOD: Program = load_clvm("dao_proposal_timer.clsp")
|
||||
DAO_PROPOSAL_TIMER_MOD_HASH: bytes32 = DAO_PROPOSAL_TIMER_MOD.get_tree_hash()
|
||||
DAO_PROPOSAL_MOD: Program = load_clvm("dao_proposal.clsp")
|
||||
DAO_PROPOSAL_MOD_HASH: bytes32 = DAO_PROPOSAL_MOD.get_tree_hash()
|
||||
DAO_PROPOSAL_VALIDATOR_MOD: Program = load_clvm("dao_proposal_validator.clsp")
|
||||
DAO_PROPOSAL_VALIDATOR_MOD_HASH: bytes32 = DAO_PROPOSAL_VALIDATOR_MOD.get_tree_hash()
|
||||
DAO_TREASURY_MOD: Program = load_clvm("dao_treasury.clsp")
|
||||
DAO_TREASURY_MOD_HASH: bytes32 = DAO_TREASURY_MOD.get_tree_hash()
|
||||
SPEND_P2_SINGLETON_MOD: Program = load_clvm("dao_spend_p2_singleton_v2.clsp")
|
||||
SPEND_P2_SINGLETON_MOD_HASH: bytes32 = SPEND_P2_SINGLETON_MOD.get_tree_hash()
|
||||
DAO_FINISHED_STATE: Program = load_clvm("dao_finished_state.clsp")
|
||||
DAO_FINISHED_STATE_HASH: bytes32 = DAO_FINISHED_STATE.get_tree_hash()
|
||||
DAO_RESALE_PREVENTION: Program = load_clvm("dao_resale_prevention_layer.clsp")
|
||||
DAO_RESALE_PREVENTION_HASH: bytes32 = DAO_RESALE_PREVENTION.get_tree_hash()
|
||||
DAO_CAT_TAIL: Program = load_clvm("genesis_by_coin_id_or_singleton.clsp")
|
||||
DAO_CAT_TAIL_HASH: bytes32 = DAO_CAT_TAIL.get_tree_hash()
|
||||
DAO_CAT_LAUNCHER: Program = load_clvm("dao_cat_launcher.clsp")
|
||||
P2_CONDITIONS_MOD: Program = load_clvm("p2_conditions_curryable.clsp")
|
||||
P2_CONDITIONS_MOD_HASH: bytes32 = P2_CONDITIONS_MOD.get_tree_hash()
|
||||
DAO_SAFE_PAYMENT_MOD: Program = load_clvm("dao_safe_payment.clsp")
|
||||
DAO_SAFE_PAYMENT_MOD_HASH: bytes32 = DAO_SAFE_PAYMENT_MOD.get_tree_hash()
|
||||
P2_SINGLETON_MOD: Program = load_clvm("p2_singleton_via_delegated_puzzle.clsp")
|
||||
P2_SINGLETON_MOD_HASH: bytes32 = P2_SINGLETON_MOD.get_tree_hash()
|
||||
DAO_UPDATE_PROPOSAL_MOD: Program = load_clvm("dao_update_proposal.clsp")
|
||||
DAO_UPDATE_PROPOSAL_MOD_HASH: bytes32 = DAO_UPDATE_PROPOSAL_MOD.get_tree_hash()
|
||||
DAO_CAT_EVE: Program = load_clvm("dao_cat_eve.clsp")
|
||||
P2_SINGLETON_AGGREGATOR_MOD: Program = load_clvm("p2_singleton_aggregator.clsp")
|
||||
|
||||
log = logging.Logger(__name__)
|
||||
|
||||
|
||||
def create_cat_launcher_for_singleton_id(id: bytes32) -> Program:
|
||||
singleton_struct = get_singleton_struct_for_id(id)
|
||||
return DAO_CAT_LAUNCHER.curry(singleton_struct)
|
||||
|
||||
|
||||
def curry_cat_eve(next_puzzle_hash: bytes32) -> Program:
|
||||
return DAO_CAT_EVE.curry(next_puzzle_hash)
|
||||
|
||||
|
||||
def get_treasury_puzzle(dao_rules: DAORules, treasury_id: bytes32, cat_tail_hash: bytes32) -> Program:
|
||||
# SINGLETON_STRUCT ; (SINGLETON_MOD_HASH (SINGLETON_ID . LAUNCHER_PUZZLE_HASH))
|
||||
# PROPOSAL_MOD_HASH
|
||||
# PROPOSAL_TIMER_MOD_HASH
|
||||
# CAT_MOD_HASH
|
||||
# LOCKUP_MOD_HASH
|
||||
# TREASURY_MOD_HASH
|
||||
# CAT_TAIL_HASH
|
||||
singleton_struct: Program = Program.to((SINGLETON_MOD_HASH, (treasury_id, SINGLETON_LAUNCHER_HASH)))
|
||||
proposal_validator = DAO_PROPOSAL_VALIDATOR_MOD.curry(
|
||||
singleton_struct,
|
||||
DAO_PROPOSAL_MOD_HASH,
|
||||
DAO_PROPOSAL_TIMER_MOD_HASH,
|
||||
CAT_MOD_HASH,
|
||||
DAO_FINISHED_STATE_HASH,
|
||||
DAO_LOCKUP_MOD_HASH,
|
||||
DAO_TREASURY_MOD_HASH,
|
||||
cat_tail_hash,
|
||||
dao_rules.proposal_minimum_amount,
|
||||
get_p2_singleton_puzzle(
|
||||
treasury_id
|
||||
).get_tree_hash(), # TODO: let people set this later - for now a hidden feature
|
||||
)
|
||||
# TREASURY_MOD_HASH
|
||||
# PROPOSAL_VALIDATOR ; this is the curryed proposal validator
|
||||
# PROPOSAL_LENGTH
|
||||
# PROPOSAL_SOFTCLOSE_LENGTH
|
||||
# ATTENDANCE_REQUIRED
|
||||
# PASS_MARGIN ; this is a percentage 0 - 10,000 - 51% would be 5100
|
||||
# PROPOSAL_SELF_DESTRUCT_TIME ; time in seconds after which proposals can be automatically closed
|
||||
# ORACLE_SPEND_DELAY ; timelock delay for oracle spend
|
||||
puzzle = DAO_TREASURY_MOD.curry(
|
||||
DAO_TREASURY_MOD_HASH,
|
||||
proposal_validator,
|
||||
dao_rules.proposal_timelock,
|
||||
dao_rules.soft_close_length,
|
||||
dao_rules.attendance_required,
|
||||
dao_rules.pass_percentage,
|
||||
dao_rules.self_destruct_length,
|
||||
dao_rules.oracle_spend_delay,
|
||||
)
|
||||
return puzzle
|
||||
|
||||
|
||||
def get_proposal_validator(treasury_puz: Program) -> Program:
|
||||
_, uncurried_args = treasury_puz.uncurry()
|
||||
validator: Program = uncurried_args.rest().first()
|
||||
return validator
|
||||
|
||||
|
||||
def get_update_proposal_puzzle(dao_rules: DAORules, proposal_validator: Program) -> Program:
|
||||
update_proposal = DAO_UPDATE_PROPOSAL_MOD.curry(
|
||||
DAO_TREASURY_MOD_HASH,
|
||||
proposal_validator,
|
||||
dao_rules.proposal_timelock,
|
||||
dao_rules.soft_close_length,
|
||||
dao_rules.attendance_required,
|
||||
dao_rules.pass_percentage,
|
||||
dao_rules.self_destruct_length,
|
||||
dao_rules.oracle_spend_delay,
|
||||
)
|
||||
return update_proposal
|
||||
|
||||
|
||||
def get_dao_rules_from_update_proposal(puzzle: Program) -> DAORules:
|
||||
mod, curried_args = puzzle.uncurry()
|
||||
if mod != DAO_UPDATE_PROPOSAL_MOD:
|
||||
raise ValueError("Not an update proposal.")
|
||||
(
|
||||
_,
|
||||
proposal_validator,
|
||||
proposal_timelock,
|
||||
soft_close_length,
|
||||
attendance_required,
|
||||
pass_percentage,
|
||||
self_destruct_length,
|
||||
oracle_spend_delay,
|
||||
) = curried_args.as_iter()
|
||||
# proposal_timelock: uint64
|
||||
# soft_close_length: uint64
|
||||
# attendance_required: uint64
|
||||
# pass_percentage: uint64
|
||||
# self_destruct_length: uint64
|
||||
# oracle_spend_delay: uint64
|
||||
curried_args = uncurry_proposal_validator(proposal_validator)
|
||||
(
|
||||
SINGLETON_STRUCT,
|
||||
PROPOSAL_MOD_HASH,
|
||||
PROPOSAL_TIMER_MOD_HASH,
|
||||
CAT_MOD_HASH,
|
||||
DAO_FINISHED_STATE_HASH,
|
||||
LOCKUP_MOD_HASH,
|
||||
TREASURY_MOD_HASH,
|
||||
CAT_TAIL_HASH,
|
||||
PROPOSAL_MINIMUM_AMOUNT,
|
||||
PAYOUT_PUZHASH,
|
||||
) = curried_args.as_iter()
|
||||
|
||||
dao_rules = DAORules(
|
||||
proposal_timelock.as_int(),
|
||||
soft_close_length.as_int(),
|
||||
attendance_required.as_int(),
|
||||
pass_percentage.as_int(),
|
||||
self_destruct_length.as_int(),
|
||||
oracle_spend_delay.as_int(),
|
||||
PROPOSAL_MINIMUM_AMOUNT.as_int(),
|
||||
)
|
||||
return dao_rules
|
||||
|
||||
|
||||
def get_spend_p2_singleton_puzzle(
|
||||
treasury_id: bytes32, xch_conditions: Optional[List], asset_conditions: Optional[List[Tuple]] # type: ignore
|
||||
) -> Program:
|
||||
# TODO: typecheck get_spend_p2_singleton_puzzle arguments
|
||||
# TODO: add tests for get_spend_p2_singleton_puzzle: pass xch_conditions as Puzzle, List and ConditionWithArgs
|
||||
#
|
||||
|
||||
# CAT_MOD_HASH
|
||||
# CONDITIONS ; XCH conditions, to be generated by the treasury
|
||||
# LIST_OF_TAILHASH_CONDITIONS ; the delegated puzzlehash must be curried in to the proposal.
|
||||
# ; Puzzlehash is only run in the last coin for that asset
|
||||
# ; ((TAIL_HASH CONDITIONS) (TAIL_HASH CONDITIONS)... )
|
||||
# P2_SINGLETON_VIA_DELEGATED_PUZZLE_PUZHASH
|
||||
treasury_struct = Program.to((SINGLETON_MOD_HASH, (treasury_id, SINGLETON_LAUNCHER_HASH)))
|
||||
puzzle: Program = SPEND_P2_SINGLETON_MOD.curry(
|
||||
treasury_struct,
|
||||
CAT_MOD_HASH,
|
||||
xch_conditions,
|
||||
asset_conditions,
|
||||
P2_SINGLETON_MOD.curry(treasury_struct, P2_SINGLETON_AGGREGATOR_MOD).get_tree_hash(),
|
||||
)
|
||||
return puzzle
|
||||
|
||||
|
||||
def get_p2_singleton_puzzle(treasury_id: bytes32, asset_id: Optional[bytes32] = None) -> Program:
|
||||
singleton_struct: Program = Program.to((SINGLETON_MOD_HASH, (treasury_id, SINGLETON_LAUNCHER_HASH)))
|
||||
inner_puzzle = P2_SINGLETON_MOD.curry(singleton_struct, P2_SINGLETON_AGGREGATOR_MOD)
|
||||
if asset_id:
|
||||
# CAT
|
||||
puzzle = CAT_MOD.curry(CAT_MOD_HASH, asset_id, inner_puzzle)
|
||||
return Program(puzzle)
|
||||
else:
|
||||
# XCH
|
||||
return inner_puzzle
|
||||
|
||||
|
||||
def get_p2_singleton_puzhash(treasury_id: bytes32, asset_id: Optional[bytes32] = None) -> bytes32:
|
||||
puz = get_p2_singleton_puzzle(treasury_id, asset_id)
|
||||
assert puz is not None
|
||||
return puz.get_tree_hash()
|
||||
|
||||
|
||||
def get_lockup_puzzle(
|
||||
cat_tail_hash: bytes32, previous_votes_list: List[Optional[bytes32]], innerpuz: Program
|
||||
) -> Program:
|
||||
puzzle: Program = DAO_LOCKUP_MOD.curry(
|
||||
DAO_PROPOSAL_MOD_HASH,
|
||||
SINGLETON_MOD_HASH,
|
||||
SINGLETON_LAUNCHER_HASH,
|
||||
DAO_LOCKUP_MOD_HASH,
|
||||
DAO_FINISHED_STATE_HASH,
|
||||
CAT_MOD_HASH,
|
||||
cat_tail_hash,
|
||||
previous_votes_list, # TODO: maybe format check this in this function
|
||||
innerpuz,
|
||||
)
|
||||
return puzzle
|
||||
|
||||
|
||||
def add_proposal_to_active_list(
|
||||
lockup_puzzle: Program, proposal_id: bytes32, inner_puzzle: Optional[Program] = None
|
||||
) -> Program:
|
||||
curried_args = uncurry_lockup(lockup_puzzle).as_iter()
|
||||
(
|
||||
PROPOSAL_MOD_HASH,
|
||||
SINGLETON_MOD_HASH,
|
||||
SINGLETON_LAUNCHER_PUZHASH,
|
||||
LOCKUP_MOD_HASH,
|
||||
DAO_FINISHED_STATE_HASH,
|
||||
CAT_MOD_HASH,
|
||||
CAT_TAIL_HASH,
|
||||
ACTIVE_VOTES,
|
||||
INNERPUZ,
|
||||
) = curried_args
|
||||
new_active_votes = Program.to(proposal_id).cons(ACTIVE_VOTES) # (c proposal_id ACTIVE_VOTES)
|
||||
if inner_puzzle is None:
|
||||
inner_puzzle = INNERPUZ
|
||||
return get_lockup_puzzle(CAT_TAIL_HASH, new_active_votes, inner_puzzle)
|
||||
|
||||
|
||||
def get_active_votes_from_lockup_puzzle(lockup_puzzle: Program) -> Program:
|
||||
curried_args = uncurry_lockup(lockup_puzzle)
|
||||
(
|
||||
_PROPOSAL_MOD_HASH,
|
||||
_SINGLETON_MOD_HASH,
|
||||
_SINGLETON_LAUNCHER_HASH,
|
||||
_LOCKUP_MOD_HASH,
|
||||
_DAO_FINISHED_STATE_HASH,
|
||||
_CAT_MOD_HASH,
|
||||
_CAT_TAIL_HASH,
|
||||
ACTIVE_VOTES,
|
||||
_INNERPUZ,
|
||||
) = list(curried_args.as_iter())
|
||||
return Program(ACTIVE_VOTES)
|
||||
|
||||
|
||||
def get_innerpuz_from_lockup_puzzle(lockup_puzzle: Program) -> Program:
|
||||
curried_args = uncurry_lockup(lockup_puzzle)
|
||||
(
|
||||
_PROPOSAL_MOD_HASH,
|
||||
_SINGLETON_MOD_HASH,
|
||||
_SINGLETON_LAUNCHER_HASH,
|
||||
_LOCKUP_MOD_HASH,
|
||||
_DAO_FINISHED_STATE_HASH,
|
||||
_CAT_MOD_HASH,
|
||||
_CAT_TAIL_HASH,
|
||||
_ACTIVE_VOTES,
|
||||
INNERPUZ,
|
||||
) = list(curried_args.as_iter())
|
||||
return Program(INNERPUZ)
|
||||
|
||||
|
||||
def get_proposal_puzzle(
|
||||
*,
|
||||
proposal_id: bytes32,
|
||||
cat_tail_hash: bytes32,
|
||||
treasury_id: bytes32,
|
||||
votes_sum: uint64,
|
||||
total_votes: uint64,
|
||||
proposed_puzzle_hash: bytes32,
|
||||
) -> Program:
|
||||
"""
|
||||
spend_or_update_flag can take on the following values, ranked from safest to most dangerous:
|
||||
s for spend only
|
||||
u for update only
|
||||
d for dangerous (can do anything)
|
||||
"""
|
||||
singleton_struct: Program = Program.to((SINGLETON_MOD_HASH, (proposal_id, SINGLETON_LAUNCHER_HASH)))
|
||||
puzzle = DAO_PROPOSAL_MOD.curry(
|
||||
singleton_struct,
|
||||
DAO_PROPOSAL_MOD_HASH,
|
||||
DAO_PROPOSAL_TIMER_MOD_HASH,
|
||||
CAT_MOD_HASH,
|
||||
DAO_FINISHED_STATE_HASH,
|
||||
DAO_TREASURY_MOD_HASH,
|
||||
DAO_LOCKUP_MOD_HASH,
|
||||
cat_tail_hash,
|
||||
treasury_id,
|
||||
votes_sum,
|
||||
total_votes,
|
||||
proposed_puzzle_hash,
|
||||
)
|
||||
return puzzle
|
||||
|
||||
|
||||
def get_proposal_timer_puzzle(
|
||||
cat_tail_hash: bytes32,
|
||||
proposal_id: bytes32,
|
||||
treasury_id: bytes32,
|
||||
) -> Program:
|
||||
parent_singleton_struct: Program = Program.to((SINGLETON_MOD_HASH, (proposal_id, SINGLETON_LAUNCHER_HASH)))
|
||||
puzzle: Program = DAO_PROPOSAL_TIMER_MOD.curry(
|
||||
DAO_PROPOSAL_MOD_HASH,
|
||||
DAO_PROPOSAL_TIMER_MOD_HASH,
|
||||
DAO_LOCKUP_MOD_HASH,
|
||||
DAO_FINISHED_STATE_HASH,
|
||||
CAT_MOD_HASH,
|
||||
cat_tail_hash,
|
||||
parent_singleton_struct,
|
||||
treasury_id,
|
||||
)
|
||||
return puzzle
|
||||
|
||||
|
||||
def get_treasury_rules_from_puzzle(puzzle_reveal: Optional[Program]) -> DAORules:
|
||||
assert isinstance(puzzle_reveal, Program)
|
||||
curried_args = uncurry_treasury(puzzle_reveal)
|
||||
(
|
||||
_DAO_TREASURY_MOD_HASH,
|
||||
proposal_validator,
|
||||
proposal_timelock,
|
||||
soft_close_length,
|
||||
attendance_required,
|
||||
pass_percentage,
|
||||
self_destruct_length,
|
||||
oracle_spend_delay,
|
||||
) = curried_args
|
||||
curried_args = uncurry_proposal_validator(proposal_validator)
|
||||
(
|
||||
SINGLETON_STRUCT,
|
||||
PROPOSAL_MOD_HASH,
|
||||
PROPOSAL_TIMER_MOD_HASH,
|
||||
CAT_MOD_HASH,
|
||||
DAO_FINISHED_STATE_HASH,
|
||||
LOCKUP_MOD_HASH,
|
||||
TREASURY_MOD_HASH,
|
||||
CAT_TAIL_HASH,
|
||||
PROPOSAL_MINIMUM_AMOUNT,
|
||||
PAYOUT_PUZHASH,
|
||||
) = curried_args.as_iter()
|
||||
return DAORules(
|
||||
uint64(int_from_bytes(proposal_timelock.as_atom())),
|
||||
uint64(int_from_bytes(soft_close_length.as_atom())),
|
||||
uint64(int_from_bytes(attendance_required.as_atom())),
|
||||
uint64(int_from_bytes(pass_percentage.as_atom())),
|
||||
uint64(int_from_bytes(self_destruct_length.as_atom())),
|
||||
uint64(int_from_bytes(oracle_spend_delay.as_atom())),
|
||||
uint64(PROPOSAL_MINIMUM_AMOUNT.as_int()),
|
||||
)
|
||||
|
||||
|
||||
# This takes the treasury puzzle and treasury solution, not the full puzzle and full solution
|
||||
# This also returns the treasury puzzle and not the full puzzle
|
||||
def get_new_puzzle_from_treasury_solution(puzzle_reveal: Program, solution: Program) -> Optional[Program]:
|
||||
# curried_args = uncurry_treasury(puzzle_reveal)
|
||||
# (
|
||||
# DAO_TREASURY_MOD_HASH,
|
||||
# DAO_PROPOSAL_VALIDATOR_MOD,
|
||||
# proposal_timelock,
|
||||
# soft_close_length,
|
||||
# attendance_required_percentage,
|
||||
# proposal_pass_percentage,
|
||||
# proposal_self_destruct_length,
|
||||
# oracle_spend_delay,
|
||||
# ) = curried_args
|
||||
if solution.rest().rest().first() != Program.to(0):
|
||||
# Proposal Spend
|
||||
mod, curried_args = solution.at("rrf").uncurry()
|
||||
if mod == DAO_UPDATE_PROPOSAL_MOD:
|
||||
(
|
||||
DAO_TREASURY_MOD_HASH,
|
||||
DAO_PROPOSAL_VALIDATOR,
|
||||
proposal_timelock,
|
||||
soft_close_length,
|
||||
attendance_required,
|
||||
pass_percentage,
|
||||
self_destruct_length,
|
||||
oracle_spend_delay,
|
||||
) = curried_args.as_iter()
|
||||
return DAO_TREASURY_MOD.curry(
|
||||
DAO_TREASURY_MOD_HASH,
|
||||
DAO_PROPOSAL_VALIDATOR,
|
||||
proposal_timelock,
|
||||
soft_close_length,
|
||||
attendance_required,
|
||||
pass_percentage,
|
||||
self_destruct_length,
|
||||
oracle_spend_delay,
|
||||
)
|
||||
else:
|
||||
return puzzle_reveal
|
||||
else:
|
||||
# Oracle Spend - treasury is unchanged
|
||||
return puzzle_reveal
|
||||
|
||||
|
||||
# This takes the proposal puzzle and proposal solution, not the full puzzle and full solution
|
||||
# This also returns the proposal puzzle and not the full puzzle
|
||||
def get_new_puzzle_from_proposal_solution(puzzle_reveal: Program, solution: Program) -> Optional[Program]:
|
||||
# Check if soft_close_length is in solution. If not, then add votes, otherwise close proposal
|
||||
if len(solution.as_python()) == 1:
|
||||
return puzzle_reveal # we're finished, shortcut this function
|
||||
|
||||
if solution.at("rrrrrrf") == Program.to(0):
|
||||
curried_args = uncurry_proposal(puzzle_reveal)
|
||||
(
|
||||
SINGLETON_STRUCT, # (SINGLETON_MOD_HASH, (SINGLETON_ID, LAUNCHER_PUZZLE_HASH))
|
||||
PROPOSAL_MOD_HASH,
|
||||
PROPOSAL_TIMER_MOD_HASH,
|
||||
CAT_MOD_HASH,
|
||||
DAO_FINISHED_STATE_HASH,
|
||||
TREASURY_MOD_HASH,
|
||||
LOCKUP_MOD_HASH,
|
||||
CAT_TAIL_HASH,
|
||||
TREASURY_ID,
|
||||
YES_VOTES, # yes votes are +1, no votes don't tally - we compare yes_votes/total_votes at the end
|
||||
TOTAL_VOTES, # how many people responded
|
||||
INNERPUZ_HASH,
|
||||
) = curried_args.as_iter()
|
||||
|
||||
added_votes = solution.at("ff").as_int()
|
||||
new_total_votes = TOTAL_VOTES.as_int() + added_votes
|
||||
|
||||
if solution.at("rf") == Program.to(0):
|
||||
# Vote Type: NO
|
||||
new_yes_votes = YES_VOTES
|
||||
else:
|
||||
# Vote Type: YES
|
||||
new_yes_votes = YES_VOTES.as_int() + added_votes
|
||||
return DAO_PROPOSAL_MOD.curry(
|
||||
SINGLETON_STRUCT,
|
||||
DAO_PROPOSAL_MOD_HASH,
|
||||
DAO_PROPOSAL_TIMER_MOD_HASH,
|
||||
CAT_MOD_HASH,
|
||||
DAO_FINISHED_STATE_HASH,
|
||||
DAO_TREASURY_MOD_HASH,
|
||||
DAO_LOCKUP_MOD_HASH,
|
||||
CAT_TAIL_HASH,
|
||||
TREASURY_ID,
|
||||
new_yes_votes,
|
||||
new_total_votes,
|
||||
INNERPUZ_HASH,
|
||||
)
|
||||
else:
|
||||
# we are in the finished state, puzzle is the same as ever
|
||||
mod, currieds = puzzle_reveal.uncurry() # singleton
|
||||
if mod == DAO_PROPOSAL_MOD:
|
||||
(
|
||||
SINGLETON_STRUCT, # (SINGLETON_MOD_HASH, (SINGLETON_ID, LAUNCHER_PUZZLE_HASH))
|
||||
PROPOSAL_MOD_HASH,
|
||||
PROPOSAL_TIMER_MOD_HASH,
|
||||
CAT_MOD_HASH,
|
||||
DAO_FINISHED_STATE_HASH,
|
||||
TREASURY_MOD_HASH,
|
||||
LOCKUP_MOD_HASH,
|
||||
CAT_TAIL_HASH,
|
||||
TREASURY_ID,
|
||||
YES_VOTES, # yes votes are +1, no votes don't tally - we compare yes_votes/total_votes at the end
|
||||
TOTAL_VOTES, # how many people responded
|
||||
INNERPUZ_HASH,
|
||||
) = currieds.as_iter()
|
||||
else:
|
||||
SINGLETON_STRUCT, dao_finished_hash = currieds.as_iter()
|
||||
|
||||
assert SINGLETON_STRUCT is not None
|
||||
return get_finished_state_puzzle(SINGLETON_STRUCT.rest().first().as_atom())
|
||||
|
||||
|
||||
def get_finished_state_puzzle(proposal_id: bytes32) -> Program:
|
||||
singleton_struct: Program = Program.to((SINGLETON_MOD_HASH, (proposal_id, SINGLETON_LAUNCHER_HASH)))
|
||||
finished_inner_puz: Program = DAO_FINISHED_STATE.curry(singleton_struct, DAO_FINISHED_STATE_HASH)
|
||||
return create_singleton_puzzle(finished_inner_puz, proposal_id)
|
||||
|
||||
|
||||
def get_proposed_puzzle_reveal_from_solution(solution: Program) -> Program:
|
||||
prog = Program.from_bytes(bytes(solution))
|
||||
return prog.at("rrfrrrrrf")
|
||||
|
||||
|
||||
def get_asset_id_from_puzzle(puzzle: Program) -> Optional[bytes32]:
|
||||
mod, curried_args = puzzle.uncurry()
|
||||
if mod == MOD:
|
||||
return None
|
||||
elif mod == CAT_MOD:
|
||||
return bytes32(curried_args.at("rf").as_atom())
|
||||
elif mod == SINGLETON_MOD:
|
||||
return bytes32(curried_args.at("frf").as_atom())
|
||||
else:
|
||||
raise ValueError("DAO received coin with unknown puzzle")
|
||||
|
||||
|
||||
def uncurry_proposal_validator(proposal_validator_program: Program) -> Program:
|
||||
try:
|
||||
mod, curried_args = proposal_validator_program.uncurry()
|
||||
except ValueError as e:
|
||||
log.debug("Cannot uncurry treasury puzzle: error: %s", e)
|
||||
raise e
|
||||
|
||||
if mod != DAO_PROPOSAL_VALIDATOR_MOD:
|
||||
raise ValueError("Not a Treasury mod.")
|
||||
return curried_args
|
||||
|
||||
|
||||
def uncurry_treasury(treasury_puzzle: Program) -> List[Program]:
|
||||
try:
|
||||
mod, curried_args = treasury_puzzle.uncurry()
|
||||
except ValueError as e:
|
||||
log.debug("Cannot uncurry treasury puzzle: error: %s", e)
|
||||
raise e
|
||||
|
||||
if mod != DAO_TREASURY_MOD:
|
||||
raise ValueError("Not a Treasury mod.")
|
||||
return list(curried_args.as_iter())
|
||||
|
||||
|
||||
def uncurry_proposal(proposal_puzzle: Program) -> Program:
|
||||
try:
|
||||
mod, curried_args = proposal_puzzle.uncurry()
|
||||
except ValueError as e:
|
||||
log.debug("Cannot uncurry proposal puzzle: error: %s", e)
|
||||
raise e
|
||||
|
||||
if mod != DAO_PROPOSAL_MOD:
|
||||
raise ValueError("Not a dao proposal mod.")
|
||||
return curried_args
|
||||
|
||||
|
||||
def uncurry_lockup(lockup_puzzle: Program) -> Program:
|
||||
try:
|
||||
mod, curried_args = lockup_puzzle.uncurry()
|
||||
except ValueError as e:
|
||||
log.debug("Cannot uncurry lockup puzzle: error: %s", e)
|
||||
raise e
|
||||
|
||||
if mod != DAO_LOCKUP_MOD:
|
||||
raise ValueError("Not a dao cat lockup mod.")
|
||||
return curried_args
|
||||
|
||||
|
||||
def get_proposal_args(puzzle: Program) -> Tuple[str, Program]:
|
||||
try:
|
||||
mod, curried_args = puzzle.uncurry()
|
||||
except ValueError as e:
|
||||
log.debug("Cannot uncurry spend puzzle: error: %s", e)
|
||||
raise e
|
||||
if mod == SPEND_P2_SINGLETON_MOD:
|
||||
return "spend", curried_args
|
||||
elif mod == DAO_UPDATE_PROPOSAL_MOD:
|
||||
return "update", curried_args
|
||||
else:
|
||||
raise ValueError("Unrecognised proposal type")
|
||||
|
||||
|
||||
def generate_cat_tail(genesis_coin_id: bytes32, treasury_id: bytes32) -> Program:
|
||||
dao_cat_launcher = create_cat_launcher_for_singleton_id(treasury_id).get_tree_hash()
|
||||
puzzle = DAO_CAT_TAIL.curry(genesis_coin_id, dao_cat_launcher)
|
||||
return puzzle
|
||||
|
||||
|
||||
# TODO: move curry_singleton to chia.wallet.singleton
|
||||
# TODO: Is this correct? See create_fullpuz
|
||||
# TODO: innerpuz type: is innerpuz a full reveal, or a hash?
|
||||
def curry_singleton(singleton_id: bytes32, innerpuz: Program) -> Program:
|
||||
singleton_struct = Program.to((SINGLETON_MOD_HASH, (singleton_id, SINGLETON_LAUNCHER_HASH)))
|
||||
return SINGLETON_MOD.curry(singleton_struct, innerpuz)
|
||||
|
||||
|
||||
def get_curry_vals_from_proposal_puzzle(proposal_puzzle: Program) -> Tuple[Program, Program, Program]:
|
||||
curried_args = uncurry_proposal(proposal_puzzle)
|
||||
(
|
||||
SINGLETON_STRUCT,
|
||||
PROPOSAL_MOD_HASH,
|
||||
PROPOSAL_TIMER_MOD_HASH,
|
||||
CAT_MOD_HASH,
|
||||
DAO_FINISHED_STATE_HASH,
|
||||
TREASURY_MOD_HASH,
|
||||
LOCKUP_MOD_HASH,
|
||||
CAT_TAIL_HASH,
|
||||
TREASURY_ID,
|
||||
YES_VOTES,
|
||||
TOTAL_VOTES,
|
||||
PROPOSED_PUZ_HASH,
|
||||
) = curried_args.as_iter()
|
||||
return YES_VOTES, TOTAL_VOTES, PROPOSED_PUZ_HASH
|
||||
|
||||
|
||||
# This is for use in the WalletStateManager to determine the type of coin received
|
||||
def match_treasury_puzzle(mod: Program, curried_args: Program) -> Optional[Iterator[Program]]:
|
||||
"""
|
||||
Given a puzzle test if it's a Treasury, if it is, return the curried arguments
|
||||
:param mod: Puzzle
|
||||
:param curried_args: Puzzle
|
||||
:return: Curried parameters
|
||||
"""
|
||||
try:
|
||||
if mod == SINGLETON_MOD:
|
||||
mod, curried_args = curried_args.rest().first().uncurry()
|
||||
if mod == DAO_TREASURY_MOD:
|
||||
return curried_args.first().as_iter() # type: ignore[no-any-return]
|
||||
except ValueError:
|
||||
import traceback
|
||||
|
||||
print(f"exception: {traceback.format_exc()}")
|
||||
|
||||
return None
|
||||
|
||||
|
||||
# This is for use in the WalletStateManager to determine the type of coin received
|
||||
def match_proposal_puzzle(mod: Program, curried_args: Program) -> Optional[Iterator[Program]]:
|
||||
"""
|
||||
Given a puzzle test if it's a Proposal, if it is, return the curried arguments
|
||||
:param curried_args: Puzzle
|
||||
:return: Curried parameters
|
||||
"""
|
||||
try:
|
||||
if mod == SINGLETON_MOD:
|
||||
mod, curried_args = curried_args.rest().first().uncurry()
|
||||
if mod == DAO_PROPOSAL_MOD:
|
||||
return curried_args.as_iter() # type: ignore[no-any-return]
|
||||
except ValueError:
|
||||
import traceback
|
||||
|
||||
print(f"exception: {traceback.format_exc()}")
|
||||
return None
|
||||
|
||||
|
||||
def match_finished_puzzle(mod: Program, curried_args: Program) -> Optional[Iterator[Program]]:
|
||||
"""
|
||||
Given a puzzle test if it's a Proposal, if it is, return the curried arguments
|
||||
:param curried_args: Puzzle
|
||||
:return: Curried parameters
|
||||
"""
|
||||
try:
|
||||
if mod == SINGLETON_MOD:
|
||||
mod, curried_args = curried_args.rest().first().uncurry()
|
||||
if mod == DAO_FINISHED_STATE:
|
||||
return curried_args.as_iter() # type: ignore[no-any-return]
|
||||
except ValueError:
|
||||
import traceback
|
||||
|
||||
print(f"exception: {traceback.format_exc()}")
|
||||
return None
|
||||
|
||||
|
||||
# This is used in WSM to determine whether we have a dao funding spend
|
||||
def match_funding_puzzle(uncurried: UncurriedPuzzle, solution: Program) -> Optional[bool]:
|
||||
# TODO: handle case where solution is for existing p2_singleton
|
||||
try:
|
||||
if match_cat_puzzle(uncurried):
|
||||
conditions = solution.at("frfr").as_iter()
|
||||
elif uncurried.mod == MOD:
|
||||
conditions = solution.at("rfr").as_iter()
|
||||
else:
|
||||
return None
|
||||
for cond in conditions:
|
||||
if (cond.list_len() == 4) and (cond.first().as_int() == 51):
|
||||
maybe_treasury_id = cond.at("rrrff")
|
||||
if cond.at("rf") == get_p2_singleton_puzhash(maybe_treasury_id):
|
||||
return True
|
||||
except (ValueError, EvalError):
|
||||
import traceback
|
||||
|
||||
print(f"exception: {traceback.format_exc()}")
|
||||
return None
|
||||
|
||||
|
||||
def match_dao_cat_puzzle(uncurried: UncurriedPuzzle) -> Optional[Iterator[Program]]:
|
||||
try:
|
||||
if uncurried.mod == CAT_MOD:
|
||||
arg_list = list(uncurried.args.as_iter())
|
||||
maybe_dao_lockup = uncurried.args.at("rrf").uncurry()
|
||||
if maybe_dao_lockup[0] == DAO_LOCKUP_MOD:
|
||||
innerpuz = maybe_dao_lockup[1].at("rrrrrrrf").uncurry()[0]
|
||||
arg_list[2] = innerpuz
|
||||
dao_cat_args: Iterator[Program] = Program.to(arg_list).as_iter()
|
||||
return dao_cat_args
|
||||
except ValueError:
|
||||
import traceback
|
||||
|
||||
print(f"exception: {traceback.format_exc()}")
|
||||
return None
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,33 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from chia.types.blockchain_format.program import Program
|
||||
from chia.types.blockchain_format.sized_bytes import bytes32
|
||||
|
||||
|
||||
def get_dao_inner_puzhash_by_p2(
|
||||
p2_puzzle_hash: bytes32,
|
||||
launcher_id: bytes32,
|
||||
metadata: Program = Program.to([]),
|
||||
) -> bytes32:
|
||||
"""
|
||||
Calculate DAO inner puzzle hash from a P2 puzzle hash
|
||||
:param p2_puzzle_hash: P2 puzzle hash
|
||||
:param launcher_id: ID of the launch coin
|
||||
:param metadata: DAO metadata
|
||||
:return: DAO inner puzzle hash
|
||||
"""
|
||||
|
||||
# backup_ids_hash = Program(Program.to(recovery_list)).get_tree_hash()
|
||||
# singleton_struct = Program.to((SINGLETON_TOP_LAYER_MOD_HASH, (launcher_id, LAUNCHER_PUZZLE_HASH)))
|
||||
#
|
||||
# quoted_mod_hash = calculate_hash_of_quoted_mod_hash(TREASURY_INNERPUZ_MOD_HASH)
|
||||
#
|
||||
# return curry_and_treehash(
|
||||
# quoted_mod_hash,
|
||||
# p2_puzzle_hash,
|
||||
# Program.to(backup_ids_hash).get_tree_hash(),
|
||||
# Program.to(num_of_backup_ids_needed).get_tree_hash(),
|
||||
# Program.to(singleton_struct).get_tree_hash(),
|
||||
# metadata.get_tree_hash(),
|
||||
# )
|
||||
return bytes32(b"0" * 32)
|
|
@ -233,7 +233,7 @@ class DIDWallet:
|
|||
None,
|
||||
None,
|
||||
False,
|
||||
json.dumps(did_wallet_puzzles.program_to_metadata(metadata)),
|
||||
json.dumps(did_wallet_puzzles.did_program_to_metadata(metadata)),
|
||||
)
|
||||
self.check_existed_did()
|
||||
info_as_string = json.dumps(self.did_info.to_json_dict())
|
||||
|
|
|
@ -187,7 +187,7 @@ def metadata_to_program(metadata: Dict) -> Program:
|
|||
return Program.to(kv_list)
|
||||
|
||||
|
||||
def program_to_metadata(program: Program) -> Dict:
|
||||
def did_program_to_metadata(program: Program) -> Dict:
|
||||
"""
|
||||
Convert a program to a metadata dict
|
||||
:param program: Chialisp program contains the metadata
|
||||
|
|
|
@ -146,7 +146,7 @@ def metadata_to_program(metadata: Dict[bytes, Any]) -> Program:
|
|||
return program
|
||||
|
||||
|
||||
def program_to_metadata(program: Program) -> Dict[bytes, Any]:
|
||||
def nft_program_to_metadata(program: Program) -> Dict[bytes, Any]:
|
||||
"""
|
||||
Convert a program to a metadata dict
|
||||
:param program: Chialisp program contains the metadata
|
||||
|
@ -181,7 +181,7 @@ def update_metadata(metadata: Program, update_condition: Program) -> Program:
|
|||
:param update_condition: Update metadata conditions
|
||||
:return: Updated metadata
|
||||
"""
|
||||
new_metadata: Dict[bytes, Any] = program_to_metadata(metadata)
|
||||
new_metadata: Dict[bytes, Any] = nft_program_to_metadata(metadata)
|
||||
uri: Program = update_condition.rest().rest().first()
|
||||
prepend_value(uri.first().as_python(), uri.rest(), new_metadata)
|
||||
return metadata_to_program(new_metadata)
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
(
|
||||
(defun recurry_by_index_ordered (
|
||||
index_value_pairs_list ; MUST BE ORDERED
|
||||
current_position ; must be 0 on initial call
|
||||
CURRENT_PARAMS ; current list of curry params
|
||||
)
|
||||
(if index_value_pairs_list
|
||||
(if (= (f (f index_value_pairs_list)) current_position)
|
||||
(c (r (f index_value_pairs_list)) (recurry_by_index_ordered (r index_value_pairs_list) (+ current_position 1) (r CURRENT_PARAMS)))
|
||||
(c (f CURRENT_PARAMS) (recurry_by_index_ordered index_value_pairs_list (+ current_position 1) (r CURRENT_PARAMS)))
|
||||
)
|
||||
()
|
||||
)
|
||||
)
|
||||
|
||||
)
|
|
@ -0,0 +1,71 @@
|
|||
; This is an innerpuz inside a DAO CAT, or any CAT, or even any coin, which locks it up until a payment gets made
|
||||
|
||||
(mod (
|
||||
INPUT_RATIO_INT ; the default here is 1 - meaning for one chia input you get exactly the OUTPUT_RATIO_INT output
|
||||
OUTPUT_RATIO_INT ; the default here is 1 - meaning for one output you must pay exactly the INPUT_RATIO_INT as input
|
||||
; IMPORTANT NOTE: this does not implicitly take into account the 1:1000 Chia/CAT conversation that the UI uses
|
||||
SETTLEMENT_PAYMENTS_PUZHASH ; a notarized coin payment is `(nonce . ((puzzle_hash amount ...) (puzzle_hash amount ...) ...))`
|
||||
; here we will use this coin's ID as the nonce to prevent someone from claiming multiple buy ins at once
|
||||
BUY_IN_PAYMENT_ADDRESS ; this is the address we check that a payment has been made to before paying out
|
||||
; for the DAO we curry the p2_singleton with the DAO Treasury ID in the wallet and then curry that here
|
||||
new_puzhash ; this is secured by creating an announcement here which must be asserted elsewhere
|
||||
; ^ probably by the puzzle that creates the settlement. Where that, the settlement, and this are spent together.
|
||||
payment_amount ; secured in the same announcement as above
|
||||
my_coin_id
|
||||
my_puzhash
|
||||
my_amount
|
||||
)
|
||||
(include condition_codes.clib)
|
||||
(include curry-and-treehash.clib)
|
||||
(include *standard-cl-21*)
|
||||
|
||||
(defconstant TEN_THOUSAND 10000)
|
||||
|
||||
; TODO: optimise out the repeated divmod - apparently it costs 1116 (third most expensive op)
|
||||
(defun calculate_output (INPUT_RATIO_INT OUTPUT_RATIO_INT payment_amount)
|
||||
(+
|
||||
(* (f (divmod payment_amount INPUT_RATIO_INT)) OUTPUT_RATIO_INT)
|
||||
(calculate_percentage output (f (divmod (* (r (divmod payment_amount INPUT_RATIO_INT)) TEN_THOUSAND) INPUT_RATIO_INT)))
|
||||
)
|
||||
)
|
||||
|
||||
(defun-inline calculate_percentage (amount percentage)
|
||||
(f (divmod (* amount percentage) TEN_THOUSAND))
|
||||
)
|
||||
|
||||
(defun create_outputs (my_puzhash new_puzhash my_amount calculated_output)
|
||||
(list
|
||||
(list CREATE_COIN my_puzhash (- my_amount calculate_output))
|
||||
(list CREATE_COIN new_puzhash calculated_output)
|
||||
)
|
||||
)
|
||||
|
||||
(c
|
||||
(list
|
||||
ASSERT_PUZZLE_ANNOUNCEMENT
|
||||
(sha256
|
||||
SETTLEMENT_PAYMENTS_PUZHASH
|
||||
(sha256tree
|
||||
(c
|
||||
my_coin_id
|
||||
(list (list BUY_IN_PAYMENT_ADDRESS payment_amount))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(c
|
||||
(list CREATE_COIN_ANNOUNCEMENT (sha256tree (c new_puzhash payment_amount)))
|
||||
(c
|
||||
(list ASSERT_MY_ID my_coin_id)
|
||||
(c
|
||||
(list ASSERT_MY_PUZZLEHASH my_puzhash)
|
||||
(c
|
||||
(list ASSERT_MY_AMOUNT my_amount)
|
||||
(create_outputs my_puzhash new_puzhash my_amount (calculate_output INPUT_RATIO_INT OUTPUT_RATIO_INT payment_amount))
|
||||
)
|
||||
)
|
||||
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
|
@ -0,0 +1 @@
|
|||
ff02ffff01ff04ffff04ffff013fffff04ffff0bff17ffff02ff04ffff04ff02ffff04ffff04ff82017fffff04ffff04ff2fffff04ff8200bfffff01808080ffff01808080ff8080808080ffff01808080ffff04ffff04ffff013cffff04ffff02ff04ffff04ff02ffff04ffff04ff5fff8200bf80ff80808080ffff01808080ffff04ffff04ffff018c4153534552545f4d595f4944ffff04ff82017fffff01808080ffff04ffff04ffff0148ffff04ff8202ffffff01808080ffff04ffff04ffff0149ffff04ff8205ffffff01808080ffff02ff0effff04ff02ffff04ff8202ffffff04ff5fffff04ff8205ffffff04ffff02ff0affff04ff02ffff04ff05ffff04ff0bffff04ff8200bfff808080808080ff808080808080808080808080ffff04ffff01ffff02ffff03ffff07ff0580ffff01ff02ffff01ff0bffff0102ffff02ff04ffff04ff02ffff04ffff05ff0580ff80808080ffff02ff04ffff04ff02ffff04ffff06ff0580ff8080808080ff0180ffff01ff02ffff01ff0bffff0101ff0580ff018080ff0180ffff10ffff12ffff05ffff14ff17ff058080ff0b80ffff05ffff14ffff12ffff01866f7574707574ffff05ffff14ffff12ffff06ffff14ff17ff058080ffff0182271080ff05808080ffff01822710808080ff04ffff04ffff0133ffff04ff05ffff04ffff11ff17ffff04ffff0102ffff04ffff04ffff0101ff0a80ffff04ffff04ffff0104ffff04ffff04ffff0101ff0280ffff01ff01808080ff8080808080ff80808080ffff04ffff04ffff0133ffff04ff0bffff04ff2fff80808080ff808080ff018080
|
|
@ -0,0 +1,17 @@
|
|||
; This file is what the first form the CAT takes, and then it gets immediately eve spent out of here.
|
||||
; This allows the coin to move into its real state already having been eve spent and validated to not be a fake CAT.
|
||||
; The trick is that we won't know what the real state puzzlehash reveal is, but we will know what this is.
|
||||
; Mint into this, eve spend out of this
|
||||
(mod (
|
||||
NEW_PUZZLE_HASH ; this is the CAT inner_puzzle
|
||||
my_amount
|
||||
tail_reveal
|
||||
tail_solution
|
||||
)
|
||||
(include condition_codes.clib)
|
||||
(list
|
||||
(list CREATE_COIN NEW_PUZZLE_HASH my_amount (list NEW_PUZZLE_HASH))
|
||||
(list ASSERT_MY_AMOUNT my_amount)
|
||||
(list CREATE_COIN 0 -113 tail_reveal tail_solution) ; this is secure because anything but the real values won't work
|
||||
)
|
||||
)
|
|
@ -0,0 +1 @@
|
|||
ff02ffff01ff04ffff04ff06ffff04ff05ffff04ff0bffff04ffff04ff05ff8080ff8080808080ffff04ffff04ff04ffff04ff0bff808080ffff04ffff04ff06ffff04ff80ffff04ffff01818fffff04ff17ffff04ff2fff808080808080ff80808080ffff04ffff01ff4933ff018080
|
|
@ -0,0 +1,36 @@
|
|||
(mod (
|
||||
TREASURY_SINGLETON_STRUCT
|
||||
treasury_inner_puz_hash
|
||||
parent_parent
|
||||
new_puzzle_hash ; the full CAT puzzle
|
||||
amount
|
||||
)
|
||||
(include condition_codes.clib)
|
||||
(include curry-and-treehash.clib)
|
||||
|
||||
(defun calculate_singleton_puzzle_hash (PROPOSAL_SINGLETON_STRUCT inner_puzzle_hash)
|
||||
(puzzle-hash-of-curried-function (f PROPOSAL_SINGLETON_STRUCT)
|
||||
inner_puzzle_hash
|
||||
(sha256tree PROPOSAL_SINGLETON_STRUCT)
|
||||
)
|
||||
)
|
||||
|
||||
(defun create_parent_conditions (parent_id new_puzzle_hash amount)
|
||||
(list
|
||||
(list ASSERT_COIN_ANNOUNCEMENT (sha256 parent_id (sha256tree (list 'm' new_puzzle_hash))))
|
||||
(list ASSERT_MY_PARENT_ID parent_id)
|
||||
)
|
||||
)
|
||||
|
||||
(c
|
||||
(list CREATE_COIN new_puzzle_hash amount (list new_puzzle_hash))
|
||||
(c
|
||||
(list ASSERT_MY_AMOUNT amount)
|
||||
(create_parent_conditions
|
||||
(sha256 parent_parent (calculate_singleton_puzzle_hash TREASURY_SINGLETON_STRUCT treasury_inner_puz_hash) ONE)
|
||||
new_puzzle_hash
|
||||
amount
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
|
@ -0,0 +1 @@
|
|||
ff02ffff01ff04ffff04ff34ffff04ff2fffff04ff5fffff04ffff04ff2fff8080ff8080808080ffff04ffff04ff28ffff04ff5fff808080ffff02ff36ffff04ff02ffff04ffff0bff17ffff02ff26ffff04ff02ffff04ff05ffff04ff0bff8080808080ff3c80ffff04ff2fffff04ff5fff8080808080808080ffff04ffff01ffffff3dff4947ffff0233ff0401ffff01ff02ff02ffff03ff05ffff01ff02ff3affff04ff02ffff04ff0dffff04ffff0bff2affff0bff3cff2c80ffff0bff2affff0bff2affff0bff3cff1280ff0980ffff0bff2aff0bffff0bff3cff8080808080ff8080808080ffff010b80ff0180ffffff02ff2effff04ff02ffff04ff09ffff04ff0bffff04ffff02ff3effff04ff02ffff04ff05ff80808080ff808080808080ff04ffff04ff10ffff04ffff0bff05ffff02ff3effff04ff02ffff04ffff04ffff016dffff04ff0bff808080ff8080808080ff808080ffff04ffff04ff38ffff04ff05ff808080ff808080ffff0bff2affff0bff3cff2480ffff0bff2affff0bff2affff0bff3cff1280ff0580ffff0bff2affff02ff3affff04ff02ffff04ff07ffff04ffff0bff3cff3c80ff8080808080ffff0bff3cff8080808080ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff3effff04ff02ffff04ff09ff80808080ffff02ff3effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080
|
|
@ -0,0 +1,57 @@
|
|||
(mod
|
||||
(
|
||||
SINGLETON_STRUCT ; ((SINGLETON_MOD_HASH, (SINGLETON_ID, LAUNCHER_PUZZLE_HASH)))
|
||||
DAO_DIVIDEND_MOD
|
||||
DAO_DIVIDEND_TIMER_MOD
|
||||
CAT_MOD_HASH
|
||||
TREASURY_MOD_HASH
|
||||
LOCKUP_MOD_HASH
|
||||
STARTING_CHIA_AMOUNT
|
||||
CURRENT_CAT_ISSUANCE
|
||||
TIMELOCK
|
||||
spend_type
|
||||
my_amount
|
||||
)
|
||||
|
||||
(include condition_codes.clib)
|
||||
(include curry-and-treehash.clib)
|
||||
(defconstant dao_finished_state 0xfb015415f2e6a09c1141f880fc2135beec6adf2a19e4d02a191432846db16559)
|
||||
|
||||
; TODO: THIS FILE IS NOT FINISHED
|
||||
|
||||
(defun-inline calculate_percentage (amount percentage)
|
||||
(f (divmod (* amount percentage) TEN_THOUSAND))
|
||||
)
|
||||
|
||||
(defun calculate_dividend_timer_puzzlehash (DAO_DIVIDEND_TIMER_MOD)
|
||||
()
|
||||
)
|
||||
|
||||
(if spend_type
|
||||
; claim
|
||||
(c
|
||||
(list ASSERT_MY_AMOUNT my_amount)
|
||||
(if (> my_amount STARTING_CHIA_AMOUNT)
|
||||
(if (= my_amount STARTING_CHIA_AMOUNT)
|
||||
(list CREATE_COIN (calculate_dividend_timer_puzzlehash ) 0) ; create proposal timer - this prevents the dividend from starting with inaccurate amounts
|
||||
()
|
||||
)
|
||||
(x)
|
||||
)
|
||||
)
|
||||
|
||||
; move to finished state
|
||||
(list
|
||||
(list CREATE_COIN dao_finished_state ONE)
|
||||
(list ASSERT_MY_AMOUNT my_amount)
|
||||
(list ASSERT_PUZZLE_ANNOUNCEMENT ; assert that the treasury has actually taken the payment
|
||||
(sha256
|
||||
;; Commented out to pass clvm compile checks
|
||||
;; (calculate_treasury_puzzlehash)
|
||||
(sha256tree (list (- my_amount ONE) (f (r SINGLETON_STRUCT))))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
)
|
|
@ -0,0 +1 @@
|
|||
ff02ffff01ff02ffff03ff820bffffff01ff04ffff04ff08ffff04ff8217ffff808080ffff02ffff03ffff15ff8217ffff82017f80ffff01ff02ffff03ffff09ff8217ffff82017f80ffff01ff04ff1cffff04ffff02ff1affff04ff02ff808080ffff01ff80808080ff8080ff0180ffff01ff088080ff018080ffff01ff04ffff04ff1cffff04ff16ffff04ff12ff80808080ffff04ffff04ff08ffff04ff8217ffff808080ffff04ffff04ff14ffff04ffff0bffff02ff1effff04ff02ffff04ffff04ffff11ff8217ffff1280ffff04ff15ff808080ff8080808080ff808080ff8080808080ff0180ffff04ffff01ffff49ff3f33ffff0180ffa0fb015415f2e6a09c1141f880fc2135beec6adf2a19e4d02a191432846db16559ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff1effff04ff02ffff04ff09ff80808080ffff02ff1effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080
|
|
@ -0,0 +1,96 @@
|
|||
; This is a persistent timer for a proposal which allows it to have a relative time that survives despite it being recreated.
|
||||
; It has a curried TIMELOCK, which is applied as a ASSERT_HEIGHT_RELATIVE
|
||||
; It creates/asserts announcements to pair it with the finishing spend of a proposal
|
||||
|
||||
(mod (
|
||||
DIVIDEND_MOD_HASH
|
||||
DIVIDEND_TIMER_MOD_HASH
|
||||
CAT_MOD_HASH
|
||||
CAT_TAIL
|
||||
CURRENT_CAT_ISSUANCE
|
||||
TIMELOCK
|
||||
MY_PARENT_SINGLETON_STRUCT
|
||||
TREASURY_ID
|
||||
|
||||
)
|
||||
(include condition_codes.clib)
|
||||
(include curry-and-treehash.clib)
|
||||
(include *standard-cl-21*)
|
||||
|
||||
; TODO: THIS FILE IS NOT FINISHED
|
||||
|
||||
(defun calculate_singleton_puzzle_hash (PROPOSAL_SINGLETON_STRUCT inner_puzzle_hash)
|
||||
(puzzle-hash-of-curried-function (f PROPOSAL_SINGLETON_STRUCT)
|
||||
inner_puzzle_hash
|
||||
(sha256tree PROPOSAL_SINGLETON_STRUCT)
|
||||
)
|
||||
)
|
||||
|
||||
(defun calculate_dividend_puzzlehash (
|
||||
PROPOSAL_SINGLETON_STRUCT
|
||||
PROPOSAL_MOD_HASH
|
||||
PROPOSAL_TIMER_MOD_HASH
|
||||
CAT_MOD_HASH
|
||||
CAT_TAIL
|
||||
CURRENT_CAT_ISSUANCE
|
||||
PROPOSAL_PASS_PERCENTAGE
|
||||
TREASURY_ID
|
||||
PROPOSAL_TIMELOCK
|
||||
proposal_current_votes
|
||||
proposal_total_votes
|
||||
proposal_innerpuzhash
|
||||
)
|
||||
(puzzle-hash-of-curried-function PROPOSAL_MOD_HASH
|
||||
proposal_innerpuzhash
|
||||
(sha256 ONE proposal_total_votes)
|
||||
(sha256 ONE proposal_current_votes)
|
||||
(sha256 ONE PROPOSAL_TIMELOCK)
|
||||
(sha256 ONE TREASURY_ID)
|
||||
(sha256 ONE PROPOSAL_PASS_PERCENTAGE)
|
||||
(sha256 ONE CURRENT_CAT_ISSUANCE)
|
||||
(sha256 ONE CAT_TAIL)
|
||||
(sha256 ONE CAT_MOD_HASH)
|
||||
(sha256 ONE PROPOSAL_TIMER_MOD_HASH)
|
||||
(sha256 ONE PROPOSAL_MOD_HASH)
|
||||
(sha256tree PROPOSAL_SINGLETON_STRUCT)
|
||||
)
|
||||
)
|
||||
|
||||
; saves the calculated proposal puzzlehash to use in two conditions rather than calculating it twice
|
||||
(defun generate_proposal_conditions (TREASURY_ID dividend_puzzlehash proposal_parent_id proposal_amount)
|
||||
(list
|
||||
(list ASSERT_PUZZLE_ANNOUNCEMENT (sha256 dividend_puzzlehash TREASURY_ID))
|
||||
(list ASSERT_MY_PARENT_ID (calculate_coin_id proposal_parent_id dividend_puzzlehash proposal_amount))
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
; main
|
||||
(c
|
||||
(list ASSERT_HEIGHT_RELATIVE PROPOSAL_TIMELOCK)
|
||||
(c
|
||||
(list CREATE_PUZZLE_ANNOUNCEMENT (f (r MY_PARENT_SINGLETON_STRUCT)))
|
||||
(generate_proposal_conditions
|
||||
TREASURY_ID
|
||||
(calculate_singleton_puzzle_hash MY_PARENT_SINGLETON_STRUCT
|
||||
(calculate_dividend_puzzlehash
|
||||
MY_PARENT_SINGLETON_STRUCT
|
||||
PROPOSAL_MOD_HASH
|
||||
PROPOSAL_TIMER_MOD_HASH
|
||||
CAT_MOD_HASH
|
||||
CAT_TAIL
|
||||
CURRENT_CAT_ISSUANCE
|
||||
PROPOSAL_PASS_PERCENTAGE
|
||||
TREASURY_ID
|
||||
PROPOSAL_TIMELOCK
|
||||
proposal_current_votes
|
||||
proposal_total_votes
|
||||
proposal_innerpuzhash
|
||||
)
|
||||
)
|
||||
proposal_parent_id
|
||||
proposal_amount
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
|
@ -0,0 +1 @@
|
|||
ff02ffff01ff04ffff04ffff0152ffff04ffff019150524f504f53414c5f54494d454c4f434bffff01808080ffff04ffff04ffff013effff04ffff05ffff06ff82017f8080ffff01808080ffff02ff1effff04ff02ffff04ff8202ffffff04ffff02ff1affff04ff02ffff04ff82017fffff04ffff02ff16ffff04ff02ffff04ff82017fffff04ffff019150524f504f53414c5f4d4f445f48415348ffff04ffff019750524f504f53414c5f54494d45525f4d4f445f48415348ffff04ff17ffff04ff2fffff04ff5fffff04ffff019850524f504f53414c5f504153535f50455243454e54414745ffff04ff8202ffffff04ffff019150524f504f53414c5f54494d454c4f434bffff04ffff019670726f706f73616c5f63757272656e745f766f746573ffff04ffff019470726f706f73616c5f746f74616c5f766f746573ffff04ffff019570726f706f73616c5f696e6e657270757a68617368ff808080808080808080808080808080ff8080808080ffff04ffff019270726f706f73616c5f706172656e745f6964ffff04ffff018f70726f706f73616c5f616d6f756e74ff808080808080808080ffff04ffff01ffffff02ffff03ff05ffff01ff02ffff01ff02ff08ffff04ff02ffff04ffff06ff0580ffff04ffff0bffff0102ffff0bffff0101ffff010480ffff0bffff0102ffff0bffff0102ffff0bffff0101ffff010180ffff05ff058080ffff0bffff0102ff0bffff0bffff0101ffff018080808080ff8080808080ff0180ffff01ff02ffff010bff018080ff0180ffff0bffff0102ffff01a0a12871fee210fb8619291eaea194581cbd2531e4b23759d225f6806923f63222ffff0bffff0102ffff0bffff0102ffff01a09dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2ff0580ffff0bffff0102ffff02ff08ffff04ff02ffff04ff07ffff01ffa09dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b280808080ffff01a04bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a808080ff02ffff03ffff22ffff09ffff0dff0580ffff012080ffff09ffff0dff0b80ffff012080ffff15ff17ffff0181ff8080ffff01ff02ffff01ff0bff05ff0bff1780ff0180ffff01ff02ffff01ff0880ff018080ff0180ffffff02ffff03ffff07ff0580ffff01ff02ffff01ff0bffff0102ffff02ff12ffff04ff02ffff04ffff05ff0580ff80808080ffff02ff12ffff04ff02ffff04ffff06ff0580ff8080808080ff0180ffff01ff02ffff01ff0bffff0101ff0580ff018080ff0180ff02ff14ffff04ff02ffff04ff09ffff04ff0bffff04ffff02ff12ffff04ff02ffff04ff05ff80808080ff808080808080ffff02ff14ffff04ff02ffff04ff0bffff04ff822fffffff04ffff0bffff0101ff8217ff80ffff04ffff0bffff0101ff820bff80ffff04ffff0bffff0101ff8205ff80ffff04ffff0bffff0101ff8202ff80ffff04ffff0bffff0101ff82017f80ffff04ffff0bffff0101ff8200bf80ffff04ffff0bffff0101ff5f80ffff04ffff0bffff0101ff2f80ffff04ffff0bffff0101ff1780ffff04ffff0bffff0101ff0b80ffff04ffff02ff12ffff04ff02ffff04ff05ff80808080ff80808080808080808080808080808080ff04ffff04ffff013fffff04ffff0bff0bff0580ff808080ffff04ffff04ffff0147ffff04ffff02ff1cffff04ff02ffff04ff17ffff04ff0bffff04ff2fff808080808080ff808080ff808080ff018080
|
|
@ -0,0 +1,35 @@
|
|||
; This code is the end state of a proposal or a dividend.
|
||||
; It is an oracle which simply recreates itself and emits an announcement that it has concluded operation
|
||||
|
||||
(mod (SINGLETON_STRUCT DAO_FINISHED_STATE_MOD_HASH my_amount)
|
||||
(include condition_codes.clib)
|
||||
(include curry-and-treehash.clib)
|
||||
(include *standard-cl-21*)
|
||||
|
||||
(defun wrap_in_singleton (SINGLETON_STRUCT my_inner_puzhash)
|
||||
(puzzle-hash-of-curried-function (f SINGLETON_STRUCT)
|
||||
my_inner_puzhash
|
||||
(sha256tree SINGLETON_STRUCT)
|
||||
)
|
||||
)
|
||||
|
||||
(defun recreate_self (SINGLETON_STRUCT DAO_FINISHED_STATE_MOD_HASH)
|
||||
(puzzle-hash-of-curried-function DAO_FINISHED_STATE_MOD_HASH
|
||||
(sha256 ONE DAO_FINISHED_STATE_MOD_HASH)
|
||||
(sha256tree SINGLETON_STRUCT)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
(let
|
||||
(
|
||||
(my_inner_puzhash (recreate_self SINGLETON_STRUCT DAO_FINISHED_STATE_MOD_HASH))
|
||||
)
|
||||
(list
|
||||
(list ASSERT_MY_PUZZLEHASH (wrap_in_singleton SINGLETON_STRUCT my_inner_puzhash))
|
||||
(list ASSERT_MY_AMOUNT my_amount)
|
||||
(list CREATE_COIN my_inner_puzhash my_amount)
|
||||
(list CREATE_PUZZLE_ANNOUNCEMENT 0)
|
||||
)
|
||||
)
|
||||
)
|
|
@ -0,0 +1 @@
|
|||
ff02ffff01ff04ffff04ffff0148ffff04ffff02ff16ffff04ff02ffff04ffff05ffff06ff018080ffff04ffff02ff1effff04ff02ffff04ff05ffff04ff0bff8080808080ff8080808080ffff01808080ffff04ffff04ffff0149ffff04ffff05ffff06ffff06ffff06ff0180808080ffff01808080ffff04ffff04ffff0133ffff04ffff02ff1effff04ff02ffff04ff05ffff04ff0bff8080808080ffff04ffff05ffff06ffff06ffff06ff0180808080ffff0180808080ffff04ffff04ffff013effff04ffff0180ffff01808080ffff018080808080ffff04ffff01ffffff02ffff03ff05ffff01ff02ffff01ff02ff08ffff04ff02ffff04ffff06ff0580ffff04ffff0bffff0102ffff0bffff0101ffff010480ffff0bffff0102ffff0bffff0102ffff0bffff0101ffff010180ffff05ff058080ffff0bffff0102ff0bffff0bffff0101ffff018080808080ff8080808080ff0180ffff01ff02ffff010bff018080ff0180ff0bffff0102ffff01a0a12871fee210fb8619291eaea194581cbd2531e4b23759d225f6806923f63222ffff0bffff0102ffff0bffff0102ffff01a09dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2ff0580ffff0bffff0102ffff02ff08ffff04ff02ffff04ff07ffff01ffa09dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b280808080ffff01a04bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a808080ffff02ffff03ffff07ff0580ffff01ff02ffff01ff0bffff0102ffff02ff0affff04ff02ffff04ffff05ff0580ff80808080ffff02ff0affff04ff02ffff04ffff06ff0580ff8080808080ff0180ffff01ff02ffff01ff0bffff0101ff0580ff018080ff0180ffff02ff0cffff04ff02ffff04ff09ffff04ff0bffff04ffff02ff0affff04ff02ffff04ff05ff80808080ff808080808080ff02ff0cffff04ff02ffff04ff0bffff04ffff0bffff0101ff0b80ffff04ffff02ff0affff04ff02ffff04ff05ff80808080ff808080808080ff018080
|
|
@ -0,0 +1 @@
|
|||
7f3cc356732907933a8f9b1ccf16f71735d07340eb38c847aa402e97d75eb40b
|
|
@ -0,0 +1,379 @@
|
|||
; This code is the "voting mode" for a DAO CAT.
|
||||
; The coin can be spent from this state to vote on a proposal or claim a dividend.
|
||||
; It locks the CAT in while it has active votes/dividends going on.
|
||||
; Once a vote or dividend closes, then the coin can spend itself to remove that coin from the "active list"
|
||||
; If the "active list" is empty the coin can leave the voting mode
|
||||
|
||||
(mod (
|
||||
PROPOSAL_MOD_HASH
|
||||
SINGLETON_MOD_HASH
|
||||
SINGLETON_LAUNCHER_PUZHASH
|
||||
LOCKUP_MOD_HASH
|
||||
DAO_FINISHED_STATE_MOD_HASH
|
||||
CAT_MOD_HASH
|
||||
CAT_TAIL_HASH
|
||||
ACTIVE_VOTES ; "active votes" list
|
||||
INNERPUZ
|
||||
my_id ; if my_id is 0 we do the return to return_address (exit voting mode) spend case
|
||||
inner_solution
|
||||
my_amount
|
||||
new_proposal_vote_id_or_removal_id ; removal_id is a list of removal_ids
|
||||
proposal_curry_vals ; list of curry_vals which should match the order of removal_id_list
|
||||
vote_info
|
||||
vote_amount
|
||||
my_inner_puzhash
|
||||
new_innerpuzhash ; only include this if we're changing owners - secured because coin is still made from inner_puz
|
||||
)
|
||||
(include condition_codes.clib)
|
||||
(include curry-and-treehash.clib)
|
||||
(include *standard-cl-21*)
|
||||
|
||||
(defun calculate_finished_state (singleton_struct dao_finished_state)
|
||||
(puzzle-hash-of-curried-function dao_finished_state
|
||||
(sha256 ONE dao_finished_state)
|
||||
(sha256tree singleton_struct)
|
||||
)
|
||||
)
|
||||
|
||||
; take two lists and merge them into one
|
||||
(defun merge_list (list_a list_b)
|
||||
(if list_a
|
||||
(c (f list_a) (merge_list (r list_a) list_b))
|
||||
list_b
|
||||
)
|
||||
)
|
||||
|
||||
(defun wrap_in_cat_layer (CAT_MOD_HASH CAT_TAIL_HASH INNERPUZHASH)
|
||||
(puzzle-hash-of-curried-function CAT_MOD_HASH
|
||||
INNERPUZHASH
|
||||
(sha256 ONE CAT_TAIL_HASH)
|
||||
(sha256 ONE CAT_MOD_HASH)
|
||||
)
|
||||
)
|
||||
|
||||
; loop through conditions and check that they aren't trying to create anything they shouldn't
|
||||
(defun check_conditions (conditions vote_added_puzhash my_amount message vote_amount my_inner_puzhash seen_vote seen_change)
|
||||
(if conditions
|
||||
(if (= (f (f conditions)) CREATE_COIN) ; this guarantees that the new coin is obeying the rules - other coins are banned to avoid re-voting
|
||||
(if (= (f (r (f conditions))) vote_added_puzhash)
|
||||
(if seen_vote ; assert we haven't already made a coin with the new vote included
|
||||
(x "wut")
|
||||
(if (= (f (r (r (f conditions)))) my_amount) ; we vote with all our value
|
||||
(if seen_change ; assert that we haven't already recreated ourself in some fashion
|
||||
(x "butt")
|
||||
(c (f conditions) (check_conditions (r conditions) vote_added_puzhash my_amount message vote_amount my_inner_puzhash 1 1))
|
||||
)
|
||||
(if (= (f (r (r (f conditions)))) vote_amount) ; we vote with part of our power
|
||||
(c (f conditions) (check_conditions (r conditions) vote_added_puzhash my_amount message vote_amount my_inner_puzhash 1 seen_change))
|
||||
(x "cut")
|
||||
)
|
||||
)
|
||||
)
|
||||
(if (all
|
||||
(= (f (r (f conditions))) my_inner_puzhash)
|
||||
(not seen_change)
|
||||
(= (f (r (r (f conditions)))) (- my_amount vote_amount))
|
||||
) ; we recreate ourselves with unused voting power
|
||||
(c (f conditions) (check_conditions (r conditions) vote_added_puzhash my_amount message vote_amount my_inner_puzhash seen_vote 1))
|
||||
(x "hut" (f (r (f conditions))) my_inner_puzhash vote_added_puzhash)
|
||||
)
|
||||
)
|
||||
(if (= (f (f conditions)) CREATE_PUZZLE_ANNOUNCEMENT) ; this secures the values used to generate message - other messages are banned in case of LIES
|
||||
(if (= (f (r (f conditions))) message)
|
||||
(c (f conditions) (check_conditions (r conditions) vote_added_puzhash my_amount message vote_amount my_inner_puzhash seen_vote seen_change))
|
||||
(x "jut")
|
||||
)
|
||||
(c (f conditions) (check_conditions (r conditions) vote_added_puzhash my_amount message vote_amount my_inner_puzhash seen_vote seen_change))
|
||||
)
|
||||
)
|
||||
(if (all seen_vote seen_change) ; check all value is accounted for
|
||||
()
|
||||
(x "jart")
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
; go through our list of active votes and check that we aren't revoting
|
||||
(defun check_not_previously_voted (
|
||||
SINGLETON_MOD_HASH
|
||||
SINGLETON_LAUNCHER_PUZHASH
|
||||
LOCKUP_MOD_HASH
|
||||
DAO_FINISHED_STATE_MOD_HASH
|
||||
CAT_MOD_HASH
|
||||
CAT_TAIL_HASH
|
||||
PROPOSAL_MOD_HASH
|
||||
INNERPUZ
|
||||
my_id
|
||||
new_vote_id
|
||||
active_votes
|
||||
proposal_curry_vals
|
||||
)
|
||||
(if active_votes
|
||||
(if (= new_vote_id (f active_votes)) ; check new vote id is not equal to an existent vote id
|
||||
(x "fart")
|
||||
(check_not_previously_voted
|
||||
SINGLETON_MOD_HASH
|
||||
SINGLETON_LAUNCHER_PUZHASH
|
||||
LOCKUP_MOD_HASH
|
||||
DAO_FINISHED_STATE_MOD_HASH
|
||||
CAT_MOD_HASH
|
||||
CAT_TAIL_HASH
|
||||
PROPOSAL_MOD_HASH
|
||||
INNERPUZ
|
||||
my_id
|
||||
new_vote_id
|
||||
(r active_votes)
|
||||
proposal_curry_vals
|
||||
)
|
||||
)
|
||||
(list ASSERT_PUZZLE_ANNOUNCEMENT (sha256 ; the problem might be here
|
||||
(calculate_proposal_puzzlehash
|
||||
PROPOSAL_MOD_HASH
|
||||
CAT_MOD_HASH
|
||||
DAO_FINISHED_STATE_MOD_HASH
|
||||
LOCKUP_MOD_HASH
|
||||
CAT_TAIL_HASH
|
||||
(c SINGLETON_MOD_HASH (c new_vote_id SINGLETON_LAUNCHER_PUZHASH))
|
||||
proposal_curry_vals
|
||||
)
|
||||
my_id
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
(defun calculate_singleton_puzzle_hash (PROPOSAL_SINGLETON_STRUCT inner_puzzle_hash)
|
||||
(puzzle-hash-of-curried-function (f PROPOSAL_SINGLETON_STRUCT)
|
||||
inner_puzzle_hash
|
||||
(sha256tree PROPOSAL_SINGLETON_STRUCT)
|
||||
)
|
||||
)
|
||||
|
||||
(defun calculate_proposal_puzzlehash (
|
||||
PROPOSAL_MOD_HASH
|
||||
CAT_MOD_HASH
|
||||
DAO_FINISHED_STATE_MOD_HASH
|
||||
LOCKUP_MOD_HASH
|
||||
CAT_TAIL_HASH
|
||||
proposal_singleton_struct
|
||||
(
|
||||
TREASURY_MOD_HASH
|
||||
PROPOSAL_TIMER_MOD_HASH
|
||||
TREASURY_ID
|
||||
YES_VOTES
|
||||
TOTAL_VOTES
|
||||
INNERPUZHASH
|
||||
)
|
||||
)
|
||||
(calculate_singleton_puzzle_hash
|
||||
proposal_singleton_struct
|
||||
(puzzle-hash-of-curried-function PROPOSAL_MOD_HASH
|
||||
(sha256 ONE INNERPUZHASH)
|
||||
(sha256 ONE TOTAL_VOTES)
|
||||
(sha256 ONE YES_VOTES)
|
||||
(sha256 ONE TREASURY_ID)
|
||||
(sha256 ONE CAT_TAIL_HASH)
|
||||
(sha256 ONE LOCKUP_MOD_HASH)
|
||||
(sha256 ONE TREASURY_MOD_HASH)
|
||||
(sha256 ONE DAO_FINISHED_STATE_MOD_HASH)
|
||||
(sha256 ONE CAT_MOD_HASH)
|
||||
(sha256 ONE PROPOSAL_TIMER_MOD_HASH)
|
||||
(sha256 ONE PROPOSAL_MOD_HASH)
|
||||
(sha256tree proposal_singleton_struct)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
; PROPOSAL_MOD_HASH
|
||||
; SINGLETON_MOD_HASH
|
||||
; SINGLETON_LAUNCHER_PUZHASH
|
||||
; LOCKUP_MOD_HASH
|
||||
; DAO_FINISHED_STATE_MOD_HASH
|
||||
; CAT_MOD_HASH
|
||||
; CAT_TAIL_HASH
|
||||
; ACTIVE_VOTES ; "active votes" list
|
||||
; INNERPUZ
|
||||
(defun calculate_lockup_puzzlehash (
|
||||
PROPOSAL_MOD_HASH
|
||||
SINGLETON_MOD_HASH
|
||||
SINGLETON_LAUNCHER_PUZHASH
|
||||
LOCKUP_MOD_HASH
|
||||
DAO_FINISHED_STATE_MOD_HASH
|
||||
CAT_MOD_HASH
|
||||
CAT_TAIL_HASH
|
||||
active_votes
|
||||
innerpuzhash
|
||||
)
|
||||
(puzzle-hash-of-curried-function LOCKUP_MOD_HASH
|
||||
innerpuzhash
|
||||
(sha256tree active_votes)
|
||||
(sha256 ONE CAT_TAIL_HASH)
|
||||
(sha256 ONE CAT_MOD_HASH)
|
||||
(sha256 ONE DAO_FINISHED_STATE_MOD_HASH)
|
||||
(sha256 ONE LOCKUP_MOD_HASH)
|
||||
(sha256 ONE SINGLETON_LAUNCHER_PUZHASH)
|
||||
(sha256 ONE SINGLETON_MOD_HASH)
|
||||
(sha256 ONE PROPOSAL_MOD_HASH)
|
||||
)
|
||||
)
|
||||
|
||||
(defun for_every_removal_id (
|
||||
PROPOSAL_MOD_HASH
|
||||
SINGLETON_MOD_HASH
|
||||
SINGLETON_LAUNCHER_PUZHASH
|
||||
LOCKUP_MOD_HASH
|
||||
DAO_FINISHED_STATE_MOD_HASH
|
||||
CAT_MOD_HASH
|
||||
CAT_TAIL_HASH
|
||||
ACTIVE_VOTES
|
||||
INNERPUZ
|
||||
removal_ids
|
||||
my_amount
|
||||
proposal_curry_vals
|
||||
unused_votes
|
||||
)
|
||||
(if removal_ids
|
||||
(c
|
||||
(list
|
||||
ASSERT_PUZZLE_ANNOUNCEMENT ; check proposal is actually finished
|
||||
(sha256
|
||||
(calculate_singleton_puzzle_hash
|
||||
(c SINGLETON_MOD_HASH (c (f removal_ids) SINGLETON_LAUNCHER_PUZHASH))
|
||||
(calculate_finished_state
|
||||
(c SINGLETON_MOD_HASH (c (f removal_ids) SINGLETON_LAUNCHER_PUZHASH))
|
||||
DAO_FINISHED_STATE_MOD_HASH
|
||||
)
|
||||
)
|
||||
0
|
||||
)
|
||||
)
|
||||
(for_every_removal_id
|
||||
PROPOSAL_MOD_HASH
|
||||
SINGLETON_MOD_HASH
|
||||
SINGLETON_LAUNCHER_PUZHASH
|
||||
LOCKUP_MOD_HASH
|
||||
DAO_FINISHED_STATE_MOD_HASH
|
||||
CAT_MOD_HASH
|
||||
CAT_TAIL_HASH
|
||||
ACTIVE_VOTES
|
||||
INNERPUZ
|
||||
(r removal_ids)
|
||||
my_amount
|
||||
(r proposal_curry_vals)
|
||||
(c (f removal_ids) unused_votes)
|
||||
)
|
||||
)
|
||||
(list
|
||||
(list ASSERT_MY_AMOUNT my_amount) ; assert that we aren't lying about our amount to free up money and re-vote
|
||||
(list
|
||||
CREATE_COIN ; recreate self with the finished proposal ID removed
|
||||
(calculate_lockup_puzzlehash
|
||||
PROPOSAL_MOD_HASH
|
||||
SINGLETON_MOD_HASH
|
||||
SINGLETON_LAUNCHER_PUZHASH
|
||||
LOCKUP_MOD_HASH
|
||||
DAO_FINISHED_STATE_MOD_HASH
|
||||
CAT_MOD_HASH
|
||||
CAT_TAIL_HASH
|
||||
(remove_list_one_entries_from_list_two unused_votes ACTIVE_VOTES)
|
||||
(sha256tree INNERPUZ)
|
||||
)
|
||||
my_amount
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(defun remove_list_one_entries_from_list_two (list_one list_two)
|
||||
(if list_one
|
||||
(remove_item_from_list (f list_one) (remove_list_one_entries_from_list_two (r list_one) list_two))
|
||||
list_two
|
||||
)
|
||||
)
|
||||
|
||||
(defun remove_item_from_list (item list_one)
|
||||
(if list_one
|
||||
(if (= (f list_one) item)
|
||||
(r list_one) ; assuming there are no duplicates
|
||||
(c (f list_one) (remove_item_from_list item (r list_one)))
|
||||
)
|
||||
() ; item was never in list_one, return list_two entirely
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
; main
|
||||
(if my_id
|
||||
(c (list ASSERT_MY_PUZZLEHASH (wrap_in_cat_layer CAT_MOD_HASH CAT_TAIL_HASH my_inner_puzhash))
|
||||
(c
|
||||
(list ASSERT_MY_AMOUNT my_amount)
|
||||
(c
|
||||
(list ASSERT_MY_COIN_ID my_id)
|
||||
(c
|
||||
(if new_proposal_vote_id_or_removal_id
|
||||
(check_not_previously_voted ; this returns a single condition asserting announcement from vote singleton
|
||||
SINGLETON_MOD_HASH
|
||||
SINGLETON_LAUNCHER_PUZHASH
|
||||
LOCKUP_MOD_HASH
|
||||
DAO_FINISHED_STATE_MOD_HASH
|
||||
CAT_MOD_HASH
|
||||
CAT_TAIL_HASH
|
||||
PROPOSAL_MOD_HASH
|
||||
INNERPUZ
|
||||
my_id
|
||||
new_proposal_vote_id_or_removal_id
|
||||
ACTIVE_VOTES
|
||||
proposal_curry_vals
|
||||
)
|
||||
(list REMARK)
|
||||
)
|
||||
|
||||
; loop over conditions and check that we aren't trying to leave voting state
|
||||
(check_conditions
|
||||
(a INNERPUZ inner_solution)
|
||||
(calculate_lockup_puzzlehash ; compare created coin to our own calculation on what the next puzzle should be
|
||||
PROPOSAL_MOD_HASH
|
||||
SINGLETON_MOD_HASH
|
||||
SINGLETON_LAUNCHER_PUZHASH
|
||||
LOCKUP_MOD_HASH
|
||||
DAO_FINISHED_STATE_MOD_HASH
|
||||
CAT_MOD_HASH
|
||||
CAT_TAIL_HASH
|
||||
(if new_proposal_vote_id_or_removal_id (c new_proposal_vote_id_or_removal_id ACTIVE_VOTES) ACTIVE_VOTES)
|
||||
(if new_innerpuzhash new_innerpuzhash (sha256tree INNERPUZ))
|
||||
)
|
||||
my_amount
|
||||
; TODO: add namespace to this announcement to allow announcements from the innerpuz
|
||||
(sha256tree (list new_proposal_vote_id_or_removal_id vote_amount vote_info my_id)) ; (sha256tree (list (f (r SINGLETON_STRUCT)) (f vote_amount_list) vote_info (f coin_id_list)))
|
||||
vote_amount
|
||||
my_inner_puzhash
|
||||
0
|
||||
0
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
; return to return_address or remove something from active list - check if our locked list is empty
|
||||
(if ACTIVE_VOTES
|
||||
(for_every_removal_id ; locked list is not empty, so we must be trying to remove something from it
|
||||
PROPOSAL_MOD_HASH
|
||||
SINGLETON_MOD_HASH
|
||||
SINGLETON_LAUNCHER_PUZHASH
|
||||
LOCKUP_MOD_HASH
|
||||
DAO_FINISHED_STATE_MOD_HASH
|
||||
CAT_MOD_HASH
|
||||
CAT_TAIL_HASH
|
||||
ACTIVE_VOTES
|
||||
INNERPUZ
|
||||
new_proposal_vote_id_or_removal_id
|
||||
my_amount
|
||||
proposal_curry_vals
|
||||
()
|
||||
)
|
||||
(a INNERPUZ inner_solution)
|
||||
)
|
||||
)
|
||||
)
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1 @@
|
|||
9fa63e652e131f89a9f8bb6f7abb5ffc6ac485a78dcfb8710cd9df5c368774d9
|
|
@ -0,0 +1,439 @@
|
|||
(mod (
|
||||
SINGLETON_STRUCT ; (SINGLETON_MOD_HASH (SINGLETON_ID . LAUNCHER_PUZZLE_HASH))
|
||||
PROPOSAL_MOD_HASH
|
||||
PROPOSAL_TIMER_MOD_HASH ; proposal timer needs to know which proposal created it, AND
|
||||
CAT_MOD_HASH
|
||||
DAO_FINISHED_STATE_MOD_HASH
|
||||
TREASURY_MOD_HASH
|
||||
LOCKUP_MOD_HASH
|
||||
CAT_TAIL_HASH
|
||||
TREASURY_ID
|
||||
YES_VOTES ; yes votes are +1, no votes don't tally - we compare yes_votes/total_votes at the end
|
||||
TOTAL_VOTES ; how many people responded
|
||||
PROPOSED_PUZ_HASH ; this is what runs if this proposal is successful - the inner puzzle of this proposal
|
||||
vote_amounts_or_proposal_validator_hash ; The qty of "votes" to add or subtract. ALWAYS POSITIVE.
|
||||
vote_info ; vote_info is whether we are voting YES or NO. XXX rename vote_type?
|
||||
vote_coin_ids_or_proposal_timelock_length ; this is either the coin ID we're taking a vote from
|
||||
previous_votes_or_pass_margin ; this is the active votes of the lockup we're communicating with
|
||||
; OR this is what percentage of the total votes must be YES - represented as an integer from 0 to 10,000 - typically this is set at 5100 (51%)
|
||||
lockup_innerpuzhashes_or_attendance_required ; this is either the innerpuz of the locked up CAT we're taking a vote from OR
|
||||
; the attendance required - the percentage of the current issuance which must have voted represented as 0 to 10,000 - this is announced by the treasury
|
||||
innerpuz_reveal ; this is only added during the first vote
|
||||
soft_close_length ; revealed by the treasury
|
||||
self_destruct_time ; revealed by the treasury
|
||||
oracle_spend_delay ; used to recreate the treasury
|
||||
self_destruct_flag ; if not 0, do the self-destruct spend
|
||||
my_amount
|
||||
)
|
||||
(include condition_codes.clib)
|
||||
(include utility_macros.clib)
|
||||
(include curry-and-treehash.clib)
|
||||
(include *standard-cl-21*)
|
||||
|
||||
(defconstant TEN_THOUSAND 10000)
|
||||
|
||||
(defun-inline calculate_win_percentage (TOTAL PERCENTAGE)
|
||||
(f (divmod (* TOTAL PERCENTAGE) TEN_THOUSAND))
|
||||
)
|
||||
|
||||
(defun calculate_finished_state (singleton_struct dao_finished_state)
|
||||
(puzzle-hash-of-curried-function dao_finished_state
|
||||
(sha256 ONE dao_finished_state)
|
||||
(sha256tree singleton_struct)
|
||||
)
|
||||
)
|
||||
|
||||
(defun calculate_timer_puzhash (
|
||||
PROPOSAL_MOD_HASH
|
||||
PROPOSAL_TIMER_MOD_HASH
|
||||
LOCKUP_MOD_HASH
|
||||
DAO_FINISHED_STATE_MOD_HASH
|
||||
CAT_MOD_HASH
|
||||
CAT_TAIL_HASH
|
||||
MY_SINGLETON_STRUCT
|
||||
TREASURY_ID
|
||||
)
|
||||
(puzzle-hash-of-curried-function PROPOSAL_TIMER_MOD_HASH
|
||||
(sha256 ONE TREASURY_ID)
|
||||
(sha256tree MY_SINGLETON_STRUCT)
|
||||
(sha256 ONE CAT_TAIL_HASH)
|
||||
(sha256 ONE CAT_MOD_HASH)
|
||||
(sha256 ONE DAO_FINISHED_STATE_MOD_HASH)
|
||||
(sha256 ONE LOCKUP_MOD_HASH)
|
||||
(sha256 ONE PROPOSAL_TIMER_MOD_HASH)
|
||||
(sha256 ONE PROPOSAL_MOD_HASH)
|
||||
)
|
||||
)
|
||||
|
||||
(defun calculate_lockup_puzzlehash (
|
||||
PROPOSAL_MOD_HASH
|
||||
SINGLETON_MOD_HASH
|
||||
SINGLETON_LAUNCHER_PUZHASH
|
||||
LOCKUP_MOD_HASH
|
||||
DAO_FINISHED_STATE_MOD_HASH
|
||||
CAT_MOD_HASH
|
||||
CAT_TAIL_HASH
|
||||
previous_votes
|
||||
lockup_innerpuzhash
|
||||
)
|
||||
(puzzle-hash-of-curried-function LOCKUP_MOD_HASH
|
||||
lockup_innerpuzhash
|
||||
(sha256tree previous_votes)
|
||||
(sha256 ONE CAT_TAIL_HASH)
|
||||
(sha256 ONE CAT_MOD_HASH)
|
||||
(sha256 ONE DAO_FINISHED_STATE_MOD_HASH)
|
||||
(sha256 ONE LOCKUP_MOD_HASH)
|
||||
(sha256 ONE SINGLETON_LAUNCHER_PUZHASH)
|
||||
(sha256 ONE SINGLETON_MOD_HASH)
|
||||
(sha256 ONE PROPOSAL_MOD_HASH)
|
||||
)
|
||||
)
|
||||
|
||||
(defun recreate_self (
|
||||
SINGLETON_STRUCT
|
||||
PROPOSAL_MOD_HASH
|
||||
PROPOSAL_TIMER_MOD_HASH
|
||||
CAT_MOD_HASH
|
||||
DAO_FINISHED_STATE_MOD_HASH
|
||||
TREASURY_MOD_HASH
|
||||
LOCKUP_MOD_HASH
|
||||
CAT_TAIL_HASH
|
||||
TREASURY_ID
|
||||
YES_VOTES
|
||||
TOTAL_VOTES
|
||||
PROPOSED_PUZ_HASH
|
||||
)
|
||||
(puzzle-hash-of-curried-function PROPOSAL_MOD_HASH
|
||||
(sha256 ONE PROPOSED_PUZ_HASH)
|
||||
(sha256 ONE TOTAL_VOTES)
|
||||
(sha256 ONE YES_VOTES)
|
||||
(sha256 ONE TREASURY_ID)
|
||||
(sha256 ONE CAT_TAIL_HASH)
|
||||
(sha256 ONE LOCKUP_MOD_HASH)
|
||||
(sha256 ONE TREASURY_MOD_HASH)
|
||||
(sha256 ONE DAO_FINISHED_STATE_MOD_HASH)
|
||||
(sha256 ONE CAT_MOD_HASH)
|
||||
(sha256 ONE PROPOSAL_TIMER_MOD_HASH)
|
||||
(sha256 ONE PROPOSAL_MOD_HASH)
|
||||
(sha256tree SINGLETON_STRUCT)
|
||||
)
|
||||
)
|
||||
|
||||
(defun wrap_in_cat_layer (CAT_MOD_HASH CAT_TAIL_HASH INNERPUZHASH)
|
||||
(puzzle-hash-of-curried-function CAT_MOD_HASH
|
||||
INNERPUZHASH
|
||||
(sha256 ONE CAT_TAIL_HASH)
|
||||
(sha256 ONE CAT_MOD_HASH)
|
||||
)
|
||||
)
|
||||
|
||||
(defun calculate_singleton_puzzle_hash (PROPOSAL_SINGLETON_STRUCT inner_puzzle_hash)
|
||||
(puzzle-hash-of-curried-function (f PROPOSAL_SINGLETON_STRUCT)
|
||||
inner_puzzle_hash
|
||||
(sha256tree PROPOSAL_SINGLETON_STRUCT)
|
||||
)
|
||||
)
|
||||
|
||||
(defun calculate_treasury_puzzlehash (
|
||||
treasury_singleton_struct
|
||||
TREASURY_MOD_HASH
|
||||
PROPOSAL_VALIDATOR_HASH
|
||||
PROPOSAL_LENGTH
|
||||
PROPOSAL_SOFTCLOSE_LENGTH
|
||||
attendance_required
|
||||
pass_percentage
|
||||
self_destruct_time
|
||||
oracle_spend_delay
|
||||
)
|
||||
|
||||
(calculate_singleton_puzzle_hash treasury_singleton_struct
|
||||
(puzzle-hash-of-curried-function TREASURY_MOD_HASH
|
||||
(sha256 ONE oracle_spend_delay)
|
||||
(sha256 ONE self_destruct_time)
|
||||
(sha256 ONE pass_percentage)
|
||||
(sha256 ONE attendance_required)
|
||||
(sha256 ONE PROPOSAL_SOFTCLOSE_LENGTH)
|
||||
(sha256 ONE PROPOSAL_LENGTH)
|
||||
PROPOSAL_VALIDATOR_HASH
|
||||
(sha256 ONE TREASURY_MOD_HASH)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(defun loop_over_vote_coins (
|
||||
SINGLETON_STRUCT
|
||||
CAT_MOD_HASH
|
||||
CAT_TAIL_HASH
|
||||
PROPOSAL_MOD_HASH
|
||||
PROPOSAL_TIMER_MOD_HASH
|
||||
TREASURY_MOD_HASH
|
||||
LOCKUP_MOD_HASH
|
||||
DAO_FINISHED_STATE_MOD_HASH
|
||||
TREASURY_ID
|
||||
YES_VOTES
|
||||
TOTAL_VOTES
|
||||
PROPOSED_PUZ_HASH
|
||||
coin_id_list
|
||||
vote_amount_list
|
||||
previous_votes
|
||||
lockup_innerpuzhashes
|
||||
vote_info
|
||||
sum
|
||||
output
|
||||
my_amount
|
||||
)
|
||||
(if coin_id_list
|
||||
(assert (> (f vote_amount_list) 0)
|
||||
(c
|
||||
(list CREATE_PUZZLE_ANNOUNCEMENT (f coin_id_list))
|
||||
(c
|
||||
(list
|
||||
ASSERT_PUZZLE_ANNOUNCEMENT ; take the vote
|
||||
(sha256
|
||||
(wrap_in_cat_layer
|
||||
CAT_MOD_HASH
|
||||
CAT_TAIL_HASH
|
||||
(calculate_lockup_puzzlehash ; because the message comes from
|
||||
PROPOSAL_MOD_HASH
|
||||
(f SINGLETON_STRUCT)
|
||||
(r (r SINGLETON_STRUCT))
|
||||
LOCKUP_MOD_HASH
|
||||
DAO_FINISHED_STATE_MOD_HASH
|
||||
CAT_MOD_HASH
|
||||
CAT_TAIL_HASH
|
||||
(f previous_votes)
|
||||
(f lockup_innerpuzhashes)
|
||||
)
|
||||
)
|
||||
(sha256tree (list (f (r SINGLETON_STRUCT)) (f vote_amount_list) vote_info (f coin_id_list)))
|
||||
)
|
||||
)
|
||||
(loop_over_vote_coins
|
||||
SINGLETON_STRUCT
|
||||
CAT_MOD_HASH
|
||||
CAT_TAIL_HASH
|
||||
PROPOSAL_MOD_HASH
|
||||
PROPOSAL_TIMER_MOD_HASH
|
||||
TREASURY_MOD_HASH
|
||||
LOCKUP_MOD_HASH
|
||||
DAO_FINISHED_STATE_MOD_HASH
|
||||
TREASURY_ID
|
||||
YES_VOTES
|
||||
TOTAL_VOTES
|
||||
PROPOSED_PUZ_HASH
|
||||
(r coin_id_list)
|
||||
(r vote_amount_list)
|
||||
(r previous_votes)
|
||||
(r lockup_innerpuzhashes)
|
||||
vote_info
|
||||
(+ (f vote_amount_list) sum)
|
||||
output
|
||||
my_amount
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(c
|
||||
(list
|
||||
CREATE_COIN ; recreate self with vote information added
|
||||
(recreate_self
|
||||
SINGLETON_STRUCT
|
||||
PROPOSAL_MOD_HASH
|
||||
PROPOSAL_TIMER_MOD_HASH
|
||||
CAT_MOD_HASH
|
||||
DAO_FINISHED_STATE_MOD_HASH
|
||||
TREASURY_MOD_HASH
|
||||
LOCKUP_MOD_HASH
|
||||
CAT_TAIL_HASH
|
||||
TREASURY_ID
|
||||
(if vote_info (+ YES_VOTES sum) YES_VOTES)
|
||||
(+ TOTAL_VOTES sum)
|
||||
PROPOSED_PUZ_HASH
|
||||
)
|
||||
my_amount
|
||||
(list TREASURY_ID) ; hint to Treasury ID so people can find it
|
||||
)
|
||||
(c
|
||||
(list ASSERT_MY_AMOUNT my_amount)
|
||||
output
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
)
|
||||
|
||||
|
||||
(if self_destruct_flag
|
||||
; assert self_destruct_time > proposal_timelock_length
|
||||
; this is the code path for if we've not been accepted by the treasury for a long time, and we're "bad" for some reason
|
||||
(assert (> self_destruct_time vote_coin_ids_or_proposal_timelock_length)
|
||||
(list
|
||||
(list CREATE_COIN (calculate_finished_state SINGLETON_STRUCT DAO_FINISHED_STATE_MOD_HASH) ONE (list TREASURY_ID))
|
||||
(list ASSERT_HEIGHT_RELATIVE self_destruct_time)
|
||||
(list ASSERT_PUZZLE_ANNOUNCEMENT ; make sure that we have a matching treasury oracle spend
|
||||
(sha256
|
||||
(calculate_treasury_puzzlehash
|
||||
(c (f SINGLETON_STRUCT) (c TREASURY_ID (r (r SINGLETON_STRUCT))))
|
||||
TREASURY_MOD_HASH
|
||||
vote_amounts_or_proposal_validator_hash
|
||||
vote_coin_ids_or_proposal_timelock_length ; check the veracity of these values by if the treasury uses them
|
||||
soft_close_length
|
||||
lockup_innerpuzhashes_or_attendance_required
|
||||
previous_votes_or_pass_margin
|
||||
self_destruct_time
|
||||
oracle_spend_delay
|
||||
)
|
||||
0 ; the arguments are secured implicitly in the puzzle of the treasury
|
||||
)
|
||||
)
|
||||
(list
|
||||
ASSERT_PUZZLE_ANNOUNCEMENT
|
||||
(sha256 ; external timer
|
||||
(calculate_timer_puzhash
|
||||
PROPOSAL_MOD_HASH
|
||||
PROPOSAL_TIMER_MOD_HASH
|
||||
LOCKUP_MOD_HASH
|
||||
DAO_FINISHED_STATE_MOD_HASH
|
||||
CAT_MOD_HASH
|
||||
CAT_TAIL_HASH
|
||||
SINGLETON_STRUCT
|
||||
TREASURY_ID
|
||||
)
|
||||
(f (r SINGLETON_STRUCT))
|
||||
)
|
||||
)
|
||||
(list CREATE_PUZZLE_ANNOUNCEMENT vote_coin_ids_or_proposal_timelock_length)
|
||||
)
|
||||
)
|
||||
; We're not trying to self destruct
|
||||
; Check whether we have a soft close to either try closing the proposal or adding votes
|
||||
; soft_close_length is used to prevent people from spamming the proposal and preventing others from being able to vote.
|
||||
; Someone could add 1 'no' vote to the proposal in every block until the proposal timelock has passed and then close the proposal as failed.
|
||||
; soft_close_length imposes some fixed number of blocks have passed without the proposal being spent before it can be closed.
|
||||
; This means there will always be some time for people to vote if they want before a proposal is closed.
|
||||
(if soft_close_length
|
||||
; Add the conditions which apply in both passed and failed cases
|
||||
(c
|
||||
(list ASSERT_HEIGHT_RELATIVE soft_close_length)
|
||||
(c
|
||||
(list CREATE_COIN (calculate_finished_state SINGLETON_STRUCT DAO_FINISHED_STATE_MOD_HASH) ONE (list TREASURY_ID))
|
||||
(c
|
||||
(list
|
||||
ASSERT_PUZZLE_ANNOUNCEMENT
|
||||
(sha256 ; external timer
|
||||
(calculate_timer_puzhash
|
||||
PROPOSAL_MOD_HASH
|
||||
PROPOSAL_TIMER_MOD_HASH
|
||||
LOCKUP_MOD_HASH
|
||||
DAO_FINISHED_STATE_MOD_HASH
|
||||
CAT_MOD_HASH
|
||||
CAT_TAIL_HASH
|
||||
SINGLETON_STRUCT
|
||||
TREASURY_ID
|
||||
)
|
||||
(f (r SINGLETON_STRUCT))
|
||||
)
|
||||
)
|
||||
(c
|
||||
(list CREATE_PUZZLE_ANNOUNCEMENT vote_coin_ids_or_proposal_timelock_length)
|
||||
; We are trying to close the proposal, so check whether it passed or failed
|
||||
(if
|
||||
(all
|
||||
(> TOTAL_VOTES lockup_innerpuzhashes_or_attendance_required)
|
||||
(> YES_VOTES (calculate_win_percentage TOTAL_VOTES previous_votes_or_pass_margin))
|
||||
)
|
||||
; Passed
|
||||
(list
|
||||
(list CREATE_COIN_ANNOUNCEMENT (sha256tree (list PROPOSED_PUZ_HASH 0))) ; the 0 at the end is announcement_args in proposal_validators
|
||||
; the above coin annnouncement lets us validate this coin in the proposal validator
|
||||
(list ASSERT_PUZZLE_ANNOUNCEMENT ; make sure that we actually have a matching treasury spend
|
||||
(sha256
|
||||
(calculate_treasury_puzzlehash
|
||||
(c (f SINGLETON_STRUCT) (c TREASURY_ID (r (r SINGLETON_STRUCT))))
|
||||
TREASURY_MOD_HASH
|
||||
vote_amounts_or_proposal_validator_hash
|
||||
vote_coin_ids_or_proposal_timelock_length ; check the veracity of these values by if the treasury uses them
|
||||
soft_close_length
|
||||
lockup_innerpuzhashes_or_attendance_required
|
||||
previous_votes_or_pass_margin
|
||||
self_destruct_time
|
||||
oracle_spend_delay
|
||||
)
|
||||
(f (r SINGLETON_STRUCT)) ; directed at singleton, but most values are implicitly announced in the puzzle
|
||||
)
|
||||
)
|
||||
)
|
||||
; Failed
|
||||
(list
|
||||
(list ASSERT_PUZZLE_ANNOUNCEMENT ; make sure that we verify solution values against the treasury's oracle spend
|
||||
(sha256
|
||||
(calculate_treasury_puzzlehash
|
||||
(c (f SINGLETON_STRUCT) (c TREASURY_ID (r (r SINGLETON_STRUCT))))
|
||||
TREASURY_MOD_HASH
|
||||
vote_amounts_or_proposal_validator_hash
|
||||
vote_coin_ids_or_proposal_timelock_length ; check the veracity of these values by if the treasury uses them
|
||||
soft_close_length
|
||||
lockup_innerpuzhashes_or_attendance_required
|
||||
previous_votes_or_pass_margin
|
||||
self_destruct_time
|
||||
oracle_spend_delay
|
||||
)
|
||||
0 ; the arguments are secured implicitly in the puzzle of the treasury
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
; no soft_close_length so run the add votes path
|
||||
(loop_over_vote_coins
|
||||
SINGLETON_STRUCT
|
||||
CAT_MOD_HASH
|
||||
CAT_TAIL_HASH
|
||||
PROPOSAL_MOD_HASH
|
||||
PROPOSAL_TIMER_MOD_HASH
|
||||
TREASURY_MOD_HASH
|
||||
LOCKUP_MOD_HASH
|
||||
DAO_FINISHED_STATE_MOD_HASH
|
||||
TREASURY_ID
|
||||
YES_VOTES
|
||||
TOTAL_VOTES
|
||||
PROPOSED_PUZ_HASH
|
||||
vote_coin_ids_or_proposal_timelock_length
|
||||
vote_amounts_or_proposal_validator_hash
|
||||
previous_votes_or_pass_margin
|
||||
lockup_innerpuzhashes_or_attendance_required
|
||||
vote_info
|
||||
0
|
||||
(if (any YES_VOTES TOTAL_VOTES) ; this prevents the timer from being created if the coin has been created with fake votes
|
||||
()
|
||||
(c
|
||||
(list
|
||||
CREATE_COIN
|
||||
(calculate_timer_puzhash
|
||||
PROPOSAL_MOD_HASH
|
||||
PROPOSAL_TIMER_MOD_HASH
|
||||
LOCKUP_MOD_HASH
|
||||
DAO_FINISHED_STATE_MOD_HASH
|
||||
CAT_MOD_HASH
|
||||
CAT_TAIL_HASH
|
||||
SINGLETON_STRUCT
|
||||
TREASURY_ID
|
||||
)
|
||||
0
|
||||
)
|
||||
(if (= (sha256tree innerpuz_reveal) PROPOSED_PUZ_HASH) ; reveal the proposed code on chain with the first vote
|
||||
()
|
||||
(x)
|
||||
)
|
||||
)
|
||||
)
|
||||
my_amount
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1 @@
|
|||
a27440cdee44f910e80225592e51dc03721a9d819cc358165587fa2b34eef4cd
|
|
@ -0,0 +1,124 @@
|
|||
; This is a persistent timer for a proposal which allows it to have a relative time that survives despite it being recreated.
|
||||
; The closing time is contained in the timelock and passed in to the solution, and confirmed via an announcement from the Proposal
|
||||
; It creates/asserts announcements to pair it with the finishing spend of a proposal
|
||||
|
||||
(mod (
|
||||
PROPOSAL_MOD_HASH
|
||||
PROPOSAL_TIMER_MOD_HASH
|
||||
LOCKUP_MOD_HASH
|
||||
DAO_FINISHED_STATE_HASH
|
||||
CAT_MOD_HASH
|
||||
CAT_TAIL_HASH
|
||||
(@ MY_PARENT_SINGLETON_STRUCT (SINGLETON_MOD_HASH SINGLETON_ID . LAUNCHER_PUZZLE_HASH))
|
||||
TREASURY_ID
|
||||
treasury_mod_hash
|
||||
proposal_yes_votes
|
||||
proposal_total_votes
|
||||
proposal_innerpuzhash
|
||||
proposal_timelock
|
||||
parent_parent
|
||||
parent_amount
|
||||
)
|
||||
(include condition_codes.clib)
|
||||
(include curry-and-treehash.clib)
|
||||
(include *standard-cl-21*)
|
||||
|
||||
(defun calculate_singleton_puzzle_hash (PROPOSAL_SINGLETON_STRUCT inner_puzzle_hash)
|
||||
(puzzle-hash-of-curried-function (f PROPOSAL_SINGLETON_STRUCT)
|
||||
inner_puzzle_hash
|
||||
(sha256tree PROPOSAL_SINGLETON_STRUCT)
|
||||
)
|
||||
)
|
||||
|
||||
; PROPOSAL_MOD_HASH
|
||||
; CAT_MOD_HASH
|
||||
; LOCKUP_MOD_HASH
|
||||
; CAT_TAIL_HASH
|
||||
; MY_PARENT_SINGLETON_STRUCT
|
||||
; treasury_mod_hash
|
||||
; PROPOSAL_TIMER_MOD_HASH
|
||||
; TREASURY_ID
|
||||
; proposal_yes_votes
|
||||
; proposal_total_votes
|
||||
; proposal_innerpuzhash
|
||||
(defun calculate_proposal_puzzlehash (
|
||||
PROPOSAL_MOD_HASH
|
||||
CAT_MOD_HASH
|
||||
LOCKUP_MOD_HASH
|
||||
DAO_FINISHED_STATE_HASH
|
||||
CAT_TAIL_HASH
|
||||
proposal_singleton_struct
|
||||
TREASURY_MOD_HASH
|
||||
PROPOSAL_TIMER_MOD_HASH
|
||||
TREASURY_ID
|
||||
YES_VOTES
|
||||
TOTAL_VOTES
|
||||
INNERPUZHASH
|
||||
)
|
||||
(calculate_singleton_puzzle_hash
|
||||
proposal_singleton_struct
|
||||
(puzzle-hash-of-curried-function PROPOSAL_MOD_HASH
|
||||
(sha256 ONE INNERPUZHASH)
|
||||
(sha256 ONE TOTAL_VOTES)
|
||||
(sha256 ONE YES_VOTES)
|
||||
(sha256 ONE TREASURY_ID)
|
||||
(sha256 ONE CAT_TAIL_HASH)
|
||||
(sha256 ONE LOCKUP_MOD_HASH)
|
||||
(sha256 ONE TREASURY_MOD_HASH)
|
||||
(sha256 ONE DAO_FINISHED_STATE_HASH)
|
||||
(sha256 ONE CAT_MOD_HASH)
|
||||
(sha256 ONE PROPOSAL_TIMER_MOD_HASH)
|
||||
(sha256 ONE PROPOSAL_MOD_HASH)
|
||||
(sha256tree proposal_singleton_struct)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
; main
|
||||
(list
|
||||
(list ASSERT_HEIGHT_RELATIVE proposal_timelock)
|
||||
(list CREATE_PUZZLE_ANNOUNCEMENT (f (r MY_PARENT_SINGLETON_STRUCT)))
|
||||
(list
|
||||
ASSERT_PUZZLE_ANNOUNCEMENT
|
||||
(sha256
|
||||
(calculate_proposal_puzzlehash
|
||||
PROPOSAL_MOD_HASH
|
||||
CAT_MOD_HASH
|
||||
LOCKUP_MOD_HASH
|
||||
DAO_FINISHED_STATE_HASH
|
||||
CAT_TAIL_HASH
|
||||
MY_PARENT_SINGLETON_STRUCT
|
||||
treasury_mod_hash
|
||||
PROPOSAL_TIMER_MOD_HASH
|
||||
TREASURY_ID
|
||||
proposal_yes_votes
|
||||
proposal_total_votes
|
||||
proposal_innerpuzhash
|
||||
)
|
||||
proposal_timelock
|
||||
)
|
||||
)
|
||||
(list
|
||||
ASSERT_MY_PARENT_ID
|
||||
(sha256
|
||||
parent_parent
|
||||
(calculate_proposal_puzzlehash
|
||||
PROPOSAL_MOD_HASH
|
||||
CAT_MOD_HASH
|
||||
LOCKUP_MOD_HASH
|
||||
DAO_FINISHED_STATE_HASH
|
||||
CAT_TAIL_HASH
|
||||
MY_PARENT_SINGLETON_STRUCT
|
||||
treasury_mod_hash
|
||||
PROPOSAL_TIMER_MOD_HASH
|
||||
TREASURY_ID
|
||||
0
|
||||
0
|
||||
proposal_innerpuzhash
|
||||
)
|
||||
parent_amount
|
||||
)
|
||||
|
||||
)
|
||||
)
|
||||
)
|
|
@ -0,0 +1 @@
|
|||
ff02ffff01ff04ffff04ffff0152ffff04ff825fffffff01808080ffff04ffff04ffff013effff04ffff05ffff06ff82017f8080ffff01808080ffff04ffff04ffff013fffff04ffff0bffff02ff1effff04ff02ffff04ff05ffff04ff5fffff04ff17ffff04ff2fffff04ff8200bfffff04ff82017fffff04ff8205ffffff04ff0bffff04ff8202ffffff04ff820bffffff04ff8217ffffff04ff822fffff808080808080808080808080808080ff825fff80ffff01808080ffff04ffff04ffff0147ffff04ffff0bff8300bfffffff02ff1effff04ff02ffff04ff05ffff04ff5fffff04ff17ffff04ff2fffff04ff8200bfffff04ff82017fffff04ff8205ffffff04ff0bffff04ff8202ffffff04ffff0180ffff04ffff0180ffff04ff822fffff808080808080808080808080808080ff83017fff80ffff01808080ffff018080808080ffff04ffff01ffffff02ffff03ff05ffff01ff02ffff01ff02ff08ffff04ff02ffff04ffff06ff0580ffff04ffff0bffff0102ffff0bffff0101ffff010480ffff0bffff0102ffff0bffff0102ffff0bffff0101ffff010180ffff05ff058080ffff0bffff0102ff0bffff0bffff0101ffff018080808080ff8080808080ff0180ffff01ff02ffff010bff018080ff0180ff0bffff0102ffff01a0a12871fee210fb8619291eaea194581cbd2531e4b23759d225f6806923f63222ffff0bffff0102ffff0bffff0102ffff01a09dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2ff0580ffff0bffff0102ffff02ff08ffff04ff02ffff04ff07ffff01ffa09dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b280808080ffff01a04bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a808080ffff02ffff03ffff07ff0580ffff01ff02ffff01ff0bffff0102ffff02ff0affff04ff02ffff04ffff05ff0580ff80808080ffff02ff0affff04ff02ffff04ffff06ff0580ff8080808080ff0180ffff01ff02ffff01ff0bffff0101ff0580ff018080ff0180ffff02ff0cffff04ff02ffff04ff09ffff04ff0bffff04ffff02ff0affff04ff02ffff04ff05ff80808080ff808080808080ff02ff16ffff04ff02ffff04ff8200bfffff04ffff02ff0cffff04ff02ffff04ff05ffff04ffff0bffff0101ff822fff80ffff04ffff0bffff0101ff8217ff80ffff04ffff0bffff0101ff820bff80ffff04ffff0bffff0101ff8205ff80ffff04ffff0bffff0101ff5f80ffff04ffff0bffff0101ff1780ffff04ffff0bffff0101ff82017f80ffff04ffff0bffff0101ff2f80ffff04ffff0bffff0101ff0b80ffff04ffff0bffff0101ff8202ff80ffff04ffff0bffff0101ff0580ffff04ffff02ff0affff04ff02ffff04ff8200bfff80808080ff80808080808080808080808080808080ff8080808080ff018080
|
|
@ -0,0 +1 @@
|
|||
5526d8dc33b60a23c86ac7184e8f7051515af16dbb7489555f389b84a5313c84
|
|
@ -0,0 +1,111 @@
|
|||
(mod
|
||||
(
|
||||
SINGLETON_STRUCT ; (SINGLETON_MOD_HASH (SINGLETON_ID . LAUNCHER_PUZZLE_HASH))
|
||||
PROPOSAL_MOD_HASH
|
||||
PROPOSAL_TIMER_MOD_HASH
|
||||
CAT_MOD_HASH
|
||||
DAO_FINISHED_STATE_MOD_HASH
|
||||
LOCKUP_MOD_HASH
|
||||
TREASURY_MOD_HASH
|
||||
CAT_TAIL_HASH
|
||||
PROPOSAL_MINIMUM_AMOUNT
|
||||
PROPOSAL_EXCESS_PAYOUT_PUZ_HASH ; this is where the excess money gets paid out to
|
||||
Attendance_Required ; this is passed in as a Truth from above
|
||||
Pass_Margin ; this is a pass in as a Truth from above
|
||||
(announcement_source delegated_puzzle_hash announcement_args)
|
||||
(
|
||||
proposal_id
|
||||
total_votes
|
||||
yes_votes
|
||||
coin_parent
|
||||
coin_amount
|
||||
)
|
||||
conditions
|
||||
)
|
||||
|
||||
(include condition_codes.clib)
|
||||
(include curry-and-treehash.clib)
|
||||
(include utility_macros.clib)
|
||||
(include *standard-cl-21*)
|
||||
|
||||
(defconstant TEN_THOUSAND 10000)
|
||||
|
||||
(defun-inline calculate_win_percentage (TOTAL PERCENTAGE)
|
||||
(f (divmod (* TOTAL PERCENTAGE) TEN_THOUSAND))
|
||||
)
|
||||
|
||||
(defun-inline calculate_full_puzzle_hash (SINGLETON_STRUCT inner_puzzle_hash)
|
||||
(puzzle-hash-of-curried-function (f SINGLETON_STRUCT)
|
||||
inner_puzzle_hash
|
||||
(sha256tree SINGLETON_STRUCT)
|
||||
)
|
||||
)
|
||||
|
||||
(defun-inline calculate_proposal_puzzle (
|
||||
PROPOSAL_MOD_HASH
|
||||
PROPOSAL_SINGLETON_STRUCT
|
||||
PROPOSAL_TIMER_MOD_HASH
|
||||
CAT_MOD_HASH
|
||||
DAO_FINISHED_STATE_MOD_HASH
|
||||
TREASURY_MOD_HASH
|
||||
LOCKUP_MOD_HASH
|
||||
CAT_TAIL_HASH
|
||||
TREASURY_ID
|
||||
proposal_yes_votes
|
||||
proposal_total_votes
|
||||
proposal_innerpuz_hash
|
||||
)
|
||||
(puzzle-hash-of-curried-function PROPOSAL_MOD_HASH
|
||||
(sha256 ONE proposal_innerpuz_hash)
|
||||
(sha256 ONE proposal_total_votes)
|
||||
(sha256 ONE proposal_yes_votes)
|
||||
(sha256 ONE TREASURY_ID)
|
||||
(sha256 ONE CAT_TAIL_HASH)
|
||||
(sha256 ONE LOCKUP_MOD_HASH)
|
||||
(sha256 ONE TREASURY_MOD_HASH)
|
||||
(sha256 ONE DAO_FINISHED_STATE_MOD_HASH)
|
||||
(sha256 ONE CAT_MOD_HASH)
|
||||
(sha256 ONE PROPOSAL_TIMER_MOD_HASH)
|
||||
(sha256 ONE PROPOSAL_MOD_HASH)
|
||||
(sha256tree PROPOSAL_SINGLETON_STRUCT)
|
||||
)
|
||||
)
|
||||
|
||||
(assert
|
||||
; (= (sha256tree my_solution) announcement_args) - quex suggested this. We don't need to check it now. Can be used for future functionality.
|
||||
(> (+ coin_amount ONE) PROPOSAL_MINIMUM_AMOUNT) ; >=
|
||||
(> total_votes Attendance_Required) ; TODO: we might want to change this to storing total cats and calculating like with yes votes
|
||||
(> yes_votes (calculate_win_percentage total_votes Pass_Margin))
|
||||
(=
|
||||
announcement_source
|
||||
(calculate_coin_id
|
||||
coin_parent
|
||||
(calculate_full_puzzle_hash
|
||||
(c (f SINGLETON_STRUCT) (c proposal_id (r (r SINGLETON_STRUCT))))
|
||||
(calculate_proposal_puzzle
|
||||
PROPOSAL_MOD_HASH
|
||||
(c (f SINGLETON_STRUCT) (c proposal_id (r (r SINGLETON_STRUCT))))
|
||||
PROPOSAL_TIMER_MOD_HASH
|
||||
CAT_MOD_HASH
|
||||
DAO_FINISHED_STATE_MOD_HASH
|
||||
TREASURY_MOD_HASH
|
||||
LOCKUP_MOD_HASH
|
||||
CAT_TAIL_HASH
|
||||
(f (r SINGLETON_STRUCT))
|
||||
yes_votes ; this is where we validate the yes votes and total votes
|
||||
total_votes
|
||||
delegated_puzzle_hash
|
||||
)
|
||||
)
|
||||
coin_amount
|
||||
)
|
||||
)
|
||||
(c
|
||||
(list CREATE_COIN PROPOSAL_EXCESS_PAYOUT_PUZ_HASH (- coin_amount 1))
|
||||
(c
|
||||
(list CREATE_PUZZLE_ANNOUNCEMENT proposal_id) ; specify the proposal we're talking about
|
||||
conditions
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
|
@ -0,0 +1 @@
|
|||
ff02ffff01ff02ffff03ffff15ffff10ff8317bfffffff010180ff8205ff80ffff01ff02ffff01ff02ffff03ffff15ff8302bfffff8217ff80ffff01ff02ffff01ff02ffff03ffff15ff8305bfffffff05ffff14ffff12ff8302bfffff822fff80ffff01822710808080ffff01ff02ffff01ff02ffff03ffff09ff83009fffffff02ff0affff04ff02ffff04ff830bbfffffff04ffff02ff0cffff04ff02ffff04ffff05ffff04ffff05ff0580ffff04ff83013fffffff06ffff06ff058080808080ffff04ffff02ff0cffff04ff02ffff04ff0bffff04ffff0bffff0101ff83015fff80ffff04ffff0bffff0101ff8302bfff80ffff04ffff0bffff0101ff8305bfff80ffff04ffff0bffff0101ffff05ffff06ff05808080ffff04ffff0bffff0101ff8202ff80ffff04ffff0bffff0101ff8200bf80ffff04ffff0bffff0101ff82017f80ffff04ffff0bffff0101ff5f80ffff04ffff0bffff0101ff2f80ffff04ffff0bffff0101ff1780ffff04ffff0bffff0101ff0b80ffff04ffff02ff0effff04ff02ffff04ffff04ffff05ff0580ffff04ff83013fffffff06ffff06ff0580808080ff80808080ff80808080808080808080808080808080ffff04ffff02ff0effff04ff02ffff04ffff04ffff05ff0580ffff04ff83013fffffff06ffff06ff0580808080ff80808080ff808080808080ffff04ff8317bfffff80808080808080ffff01ff02ffff01ff04ffff04ffff0133ffff04ff820bffffff04ffff11ff8317bfffffff010180ffff0180808080ffff04ffff04ffff013effff04ff83013fffffff01808080ff83017fff8080ff0180ffff01ff02ffff01ff0880ff018080ff0180ff0180ffff01ff02ffff01ff0880ff018080ff0180ff0180ffff01ff02ffff01ff0880ff018080ff0180ff0180ffff01ff02ffff01ff0880ff018080ff0180ffff04ffff01ffffff02ffff03ff05ffff01ff02ffff01ff02ff08ffff04ff02ffff04ffff06ff0580ffff04ffff0bffff0102ffff0bffff0101ffff010480ffff0bffff0102ffff0bffff0102ffff0bffff0101ffff010180ffff05ff058080ffff0bffff0102ff0bffff0bffff0101ffff018080808080ff8080808080ff0180ffff01ff02ffff010bff018080ff0180ff0bffff0102ffff01a0a12871fee210fb8619291eaea194581cbd2531e4b23759d225f6806923f63222ffff0bffff0102ffff0bffff0102ffff01a09dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2ff0580ffff0bffff0102ffff02ff08ffff04ff02ffff04ff07ffff01ffa09dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b280808080ffff01a04bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a808080ffff02ffff03ffff22ffff09ffff0dff0580ffff012080ffff09ffff0dff0b80ffff012080ffff15ff17ffff0181ff8080ffff01ff02ffff01ff0bff05ff0bff1780ff0180ffff01ff02ffff01ff0880ff018080ff0180ff02ffff03ffff07ff0580ffff01ff02ffff01ff0bffff0102ffff02ff0effff04ff02ffff04ffff05ff0580ff80808080ffff02ff0effff04ff02ffff04ffff06ff0580ff8080808080ff0180ffff01ff02ffff01ff0bffff0101ff0580ff018080ff0180ff018080
|
|
@ -0,0 +1 @@
|
|||
edff0f36ca097ea55c867f8700cc4d48d267b91fd00ccee2db0fab6fe0645c67
|
|
@ -0,0 +1,27 @@
|
|||
; The function of this file is to force the DAO CAT underneath it to stay in locked voting mode for TIMELOCK period of time
|
||||
|
||||
(mod (
|
||||
TIMELOCK ; absolute block height for when this coin should unlock
|
||||
INNERPUZ
|
||||
(@ inner_sol
|
||||
(
|
||||
my_id
|
||||
inner_solution
|
||||
my_amount
|
||||
new_proposal_vote_id_or_removal_id ; this is enforced as being empty if we're trying to leave locked state
|
||||
proposal_curry_vals
|
||||
vote_info
|
||||
)
|
||||
)
|
||||
)
|
||||
(include condition_codes.clib)
|
||||
(include curry-and-treehash.clib)
|
||||
(include *standard-cl-21*)
|
||||
|
||||
|
||||
; uncurry innerpuz and add the new vote procedure
|
||||
(if new_proposal_vote_id_or_removal_id
|
||||
(a INNERPUZ inner_sol) ; not trying to exit
|
||||
(c (list ASSERT_HEIGHT_ABSOLUTE TIMELOCK) (a INNERPUZ inner_sol)) ; trying to exit
|
||||
)
|
||||
)
|
|
@ -0,0 +1 @@
|
|||
ff02ffff01ff02ffff03ff820177ffff01ff02ffff01ff02ff0bff1780ff0180ffff01ff02ffff01ff04ffff04ffff0153ffff04ff05ffff01808080ffff02ff0bff178080ff018080ff0180ffff04ff80ff018080
|
|
@ -0,0 +1,24 @@
|
|||
(mod (SINGLETON_STRUCT CONDITIONS SPEND_AMOUNT treasury_puzzle_hash treasury_amount)
|
||||
|
||||
(include condition_codes.clib)
|
||||
(include curry-and-treehash.clib)
|
||||
(include *standard-cl-21*)
|
||||
|
||||
(defun-inline calculate_full_puzzle_hash (SINGLETON_STRUCT inner_puzzle_hash)
|
||||
(puzzle-hash-of-curried-function (f SINGLETON_STRUCT)
|
||||
inner_puzzle_hash
|
||||
(sha256tree SINGLETON_STRUCT)
|
||||
)
|
||||
)
|
||||
|
||||
(c
|
||||
(list ASSERT_MY_AMOUNT treasury_amount)
|
||||
(c
|
||||
(list CREATE_COIN treasury_puzzle_hash (- treasury_amount SPEND_AMOUNT))
|
||||
(c
|
||||
(list ASSERT_MY_PUZZLEHASH (calculate_full_puzzle_hash SINGLETON_STRUCT treasury_puzzle_hash))
|
||||
CONDITIONS
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
|
@ -0,0 +1 @@
|
|||
ff02ffff01ff04ffff04ffff0149ffff04ff5fffff01808080ffff04ffff04ffff0133ffff04ff2fffff04ffff11ff5fff1780ffff0180808080ffff04ffff04ffff0148ffff04ffff02ff0affff04ff02ffff04ffff05ff0580ffff04ff2fffff04ffff02ff0effff04ff02ffff04ff05ff80808080ff808080808080ffff01808080ff0b808080ffff04ffff01ffff02ffff03ff05ffff01ff02ffff01ff02ff04ffff04ff02ffff04ffff06ff0580ffff04ffff0bffff0102ffff0bffff0101ffff010480ffff0bffff0102ffff0bffff0102ffff0bffff0101ffff010180ffff05ff058080ffff0bffff0102ff0bffff0bffff0101ffff018080808080ff8080808080ff0180ffff01ff02ffff010bff018080ff0180ffff0bffff0102ffff01a0a12871fee210fb8619291eaea194581cbd2531e4b23759d225f6806923f63222ffff0bffff0102ffff0bffff0102ffff01a09dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2ff0580ffff0bffff0102ffff02ff04ffff04ff02ffff04ff07ffff01ffa09dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b280808080ffff01a04bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a808080ff02ffff03ffff07ff0580ffff01ff02ffff01ff0bffff0102ffff02ff0effff04ff02ffff04ffff05ff0580ff80808080ffff02ff0effff04ff02ffff04ffff06ff0580ff8080808080ff0180ffff01ff02ffff01ff0bffff0101ff0580ff018080ff0180ff018080
|
|
@ -0,0 +1,53 @@
|
|||
(mod (
|
||||
CONDITIONS
|
||||
P2_SINGLETON_PUZHASH
|
||||
p2_singleton_parent_amount_list
|
||||
)
|
||||
; NOTE: this code will NOT work for assets which have special layers like CATs and NFTs
|
||||
; For those you'll need to make a new proposal type
|
||||
|
||||
; If you're writing a proposal you'll want to use this layer
|
||||
; if you don't, your proposal might be invalidated if the p2_singleton coins get spent
|
||||
|
||||
(include condition_codes.clib)
|
||||
|
||||
(defun loop_through_list (SPEND_AMOUNT P2_SINGLETON_PUZHASH p2_calculated p2_singleton_list total output)
|
||||
(c
|
||||
(list CREATE_PUZZLE_ANNOUNCEMENT p2_calculated)
|
||||
(c
|
||||
(list ASSERT_COIN_ANNOUNCEMENT (sha256 p2_calculated '$'))
|
||||
(if p2_singleton_list
|
||||
(loop_through_list
|
||||
SPEND_AMOUNT
|
||||
P2_SINGLETON_PUZHASH
|
||||
(sha256 (f (f p2_singleton_list)) P2_SINGLETON_PUZHASH (f (r (f p2_singleton_list))))
|
||||
(r p2_singleton_list)
|
||||
(+ total (f (r (f p2_singleton_list))))
|
||||
)
|
||||
(if (> total SPEND_AMOUNT)
|
||||
(c (list CREATE_COIN P2_SINGLETON_PUZHASH (- total SPEND_AMOUNT)) output)
|
||||
(x)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(defun sum_create_coins (conditions)
|
||||
(if conditions
|
||||
(+ (if (= (f (f conditions)) CREATE_COIN) (f (r (r (f conditions)))) 0) (sum_create_coins (r conditions)))
|
||||
0
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
; main
|
||||
(loop_through_list
|
||||
(sum_create_coins CONDITIONS)
|
||||
P2_SINGLETON_PUZHASH
|
||||
(sha256 (f (f p2_singleton_parent_amount_list)) P2_SINGLETON_PUZHASH (f (r (f p2_singleton_parent_amount_list))))
|
||||
(r p2_singleton_parent_amount_list)
|
||||
(f (r (f p2_singleton_parent_amount_list)))
|
||||
CONDITIONS
|
||||
)
|
||||
)
|
|
@ -0,0 +1 @@
|
|||
ff02ffff01ff02ff16ffff04ff02ffff04ffff02ff1effff04ff02ffff04ff05ff80808080ffff04ff0bffff04ffff0bff47ff0bff81a780ffff04ff37ffff04ff81a7ffff04ff05ff808080808080808080ffff04ffff01ffff3d33ff3effff04ffff04ff0affff04ff17ff808080ffff04ffff04ff08ffff04ffff0bff17ffff012480ff808080ffff02ffff03ff2fffff01ff02ff16ffff04ff02ffff04ff05ffff04ff0bffff04ffff0bff818fff0bff82014f80ffff04ff6fffff04ffff10ff5fff82014f80ff8080808080808080ffff01ff02ffff03ffff15ff5fff0580ffff01ff04ffff04ff0cffff04ff0bffff04ffff11ff5fff0580ff80808080ff81bf80ffff01ff088080ff018080ff01808080ff02ffff03ff05ffff01ff10ffff02ffff03ffff09ff11ff0c80ffff0159ff8080ff0180ffff02ff1effff04ff02ffff04ff0dff8080808080ff8080ff0180ff018080
|
|
@ -0,0 +1 @@
|
|||
54964584da94f665a970e4d8b6e2091c4f2abe447886db82f19583f1a7e43ceb
|
|
@ -0,0 +1,198 @@
|
|||
(mod (
|
||||
TREASURY_SINGLETON_STRUCT
|
||||
CAT_MOD_HASH
|
||||
CONDITIONS ; XCH conditions, to be generated by the treasury
|
||||
LIST_OF_TAILHASH_CONDITIONS ; the delegated puzzlehash must be curried in to the proposal.
|
||||
; Puzzlehash is only run in the last coin for that asset
|
||||
; ((TAIL_HASH CONDITIONS) (TAIL_HASH CONDITIONS)... )
|
||||
P2_SINGLETON_VIA_DELEGATED_PUZZLE_PUZHASH
|
||||
p2_singleton_parent_amount_list ; for xch this is just a list of (coin_parent coin_amount)
|
||||
p2_singleton_tailhash_parent_amount_list ; list of ((asset (parent amount) (parent amount)... ) (asset (parent amount)... )... ),
|
||||
; must match order of curryed asset list
|
||||
; the last (parent amount) gets given the puzzlehash, the rest get given 0
|
||||
treasury_inner_puzhash
|
||||
)
|
||||
; we need to track CAT_TYPE and DELEGATED_PUZZLE
|
||||
; list of (asset_type (parent amount))
|
||||
|
||||
; If you're writing a proposal you'll want to use this layer
|
||||
; if you don't, your proposal might be invalidated if the p2_singleton coins get spent
|
||||
|
||||
(include condition_codes.clib)
|
||||
(include curry-and-treehash.clib)
|
||||
|
||||
(defun-inline calculate_singleton_puzzle_hash (PROPOSAL_SINGLETON_STRUCT inner_puzzle_hash)
|
||||
(puzzle-hash-of-curried-function (f PROPOSAL_SINGLETON_STRUCT)
|
||||
inner_puzzle_hash
|
||||
(sha256tree PROPOSAL_SINGLETON_STRUCT)
|
||||
)
|
||||
)
|
||||
|
||||
(defun loop_through_list (
|
||||
SPEND_AMOUNT
|
||||
P2_SINGLETON_PUZHASH
|
||||
p2_calculated
|
||||
p2_singleton_list
|
||||
total
|
||||
output
|
||||
)
|
||||
(c
|
||||
(list CREATE_PUZZLE_ANNOUNCEMENT (sha256tree (list p2_calculated (sha256tree 0))))
|
||||
(c
|
||||
(list ASSERT_COIN_ANNOUNCEMENT (sha256 p2_calculated '$'))
|
||||
(if p2_singleton_list
|
||||
(loop_through_list
|
||||
SPEND_AMOUNT
|
||||
P2_SINGLETON_PUZHASH
|
||||
(calculate_coin_id (f (f p2_singleton_list)) P2_SINGLETON_PUZHASH (f (r (f p2_singleton_list))))
|
||||
(r p2_singleton_list)
|
||||
(+ total (f (r (f p2_singleton_list))))
|
||||
)
|
||||
(c
|
||||
(list CREATE_COIN P2_SINGLETON_PUZHASH (- total SPEND_AMOUNT))
|
||||
output
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(defun add_announcements_to_result (p2_calculated delegated_puzhash output)
|
||||
(c
|
||||
(list CREATE_PUZZLE_ANNOUNCEMENT (sha256tree (list p2_calculated delegated_puzhash)))
|
||||
(c
|
||||
(list ASSERT_COIN_ANNOUNCEMENT (sha256 p2_calculated '$'))
|
||||
output
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(defun sum_create_coins (conditions)
|
||||
(if conditions
|
||||
(+
|
||||
(if
|
||||
(= (f (f conditions)) CREATE_COIN)
|
||||
(if
|
||||
(> (f (r (r (f conditions)))) 0) ; make an exception for -113 and other magic conditions
|
||||
(f (r (r (f conditions))))
|
||||
0
|
||||
)
|
||||
0
|
||||
)
|
||||
(sum_create_coins (r conditions))
|
||||
)
|
||||
0
|
||||
)
|
||||
)
|
||||
|
||||
(defun-inline calculate_delegated_puzzlehash (CONDITIONS)
|
||||
(sha256tree (c ONE CONDITIONS)) ; this makes (q . CONDITIONS)
|
||||
)
|
||||
|
||||
(defun wrap_in_cat_layer (CAT_MOD_HASH CAT_TAIL_HASH INNERPUZHASH)
|
||||
(puzzle-hash-of-curried-function CAT_MOD_HASH
|
||||
INNERPUZHASH
|
||||
(sha256 ONE CAT_TAIL_HASH)
|
||||
(sha256 ONE CAT_MOD_HASH)
|
||||
)
|
||||
)
|
||||
|
||||
; for a given asset type, loop through the cat coins and generate the announcements required for each
|
||||
(defun for_each_asset (
|
||||
CAT_MOD_HASH
|
||||
CONDITIONS_FOR_THIS_ASSET_TYPE
|
||||
P2_SINGLETON_PUZHASH
|
||||
p2_singleton_puzzle_hash
|
||||
parent_amount_list
|
||||
total
|
||||
create_coin_sum
|
||||
output
|
||||
)
|
||||
(if parent_amount_list
|
||||
(add_announcements_to_result
|
||||
(sha256 (f (f parent_amount_list)) p2_singleton_puzzle_hash (f (r (f parent_amount_list))))
|
||||
(if
|
||||
(r parent_amount_list) ; this is the delegated_puzhash
|
||||
(sha256tree 0) ; most coins destroy themselves
|
||||
(calculate_delegated_puzzlehash ; the last coin creates the conditions
|
||||
(c
|
||||
(list CREATE_COIN P2_SINGLETON_PUZHASH (- (+ total (f (r (f parent_amount_list)))) create_coin_sum))
|
||||
CONDITIONS_FOR_THIS_ASSET_TYPE
|
||||
)
|
||||
)
|
||||
)
|
||||
(for_each_asset
|
||||
CAT_MOD_HASH
|
||||
CONDITIONS_FOR_THIS_ASSET_TYPE
|
||||
P2_SINGLETON_PUZHASH
|
||||
p2_singleton_puzzle_hash
|
||||
parent_amount_list
|
||||
(+ total (f (r (f parent_amount_list))))
|
||||
create_coin_sum
|
||||
output
|
||||
)
|
||||
)
|
||||
()
|
||||
)
|
||||
)
|
||||
|
||||
; loops through the list of ((tailhash conditions))
|
||||
(defun for_each_asset_type (
|
||||
CAT_MOD_HASH
|
||||
P2_SINGLETON_PUZHASH
|
||||
LIST_OF_TAILHASH_CONDITIONS
|
||||
p2_singleton_tailhash_parent_amount_list ; ((tailhash ((parent amount) (parent_amount)... ) (tailhash (parent amount))..)
|
||||
output
|
||||
)
|
||||
(if p2_singleton_tailhash_parent_amount_list
|
||||
(for_each_asset_type
|
||||
CAT_MOD_HASH
|
||||
P2_SINGLETON_PUZHASH
|
||||
(r LIST_OF_TAILHASH_CONDITIONS)
|
||||
(r p2_singleton_tailhash_parent_amount_list)
|
||||
(for_each_asset
|
||||
CAT_MOD_HASH
|
||||
(if
|
||||
(=
|
||||
(f (f LIST_OF_TAILHASH_CONDITIONS))
|
||||
(f (f p2_singleton_tailhash_parent_amount_list))
|
||||
)
|
||||
(f (r (f LIST_OF_TAILHASH_CONDITIONS)))
|
||||
(x) ; bad solution format
|
||||
)
|
||||
P2_SINGLETON_PUZHASH
|
||||
(wrap_in_cat_layer CAT_MOD_HASH (f (f p2_singleton_tailhash_parent_amount_list)) P2_SINGLETON_PUZHASH) ; p2_singleton_puzzle_hash
|
||||
(r (f p2_singleton_tailhash_parent_amount_list)) ; list of ((parent amount) (parent amount)...)
|
||||
0 ; current total - initialise as 0
|
||||
(sum_create_coins (f (r (f LIST_OF_TAILHASH_CONDITIONS))))
|
||||
output ; add new conditions to previous calculated output conditions
|
||||
)
|
||||
)
|
||||
output ; at the end of the loop output our calculated conditions
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
; main
|
||||
(c
|
||||
(list ASSERT_MY_PUZZLEHASH (calculate_singleton_puzzle_hash TREASURY_SINGLETON_STRUCT treasury_inner_puzhash))
|
||||
(c
|
||||
(list CREATE_COIN treasury_inner_puzhash ONE (list (f (r TREASURY_SINGLETON_STRUCT))))
|
||||
(loop_through_list
|
||||
(sum_create_coins CONDITIONS)
|
||||
P2_SINGLETON_VIA_DELEGATED_PUZZLE_PUZHASH
|
||||
(sha256 (f (f p2_singleton_parent_amount_list)) P2_SINGLETON_VIA_DELEGATED_PUZZLE_PUZHASH (f (r (f p2_singleton_parent_amount_list))))
|
||||
(r p2_singleton_parent_amount_list)
|
||||
(f (r (f p2_singleton_parent_amount_list)))
|
||||
(for_each_asset_type
|
||||
CAT_MOD_HASH
|
||||
P2_SINGLETON_VIA_DELEGATED_PUZZLE_PUZHASH
|
||||
LIST_OF_TAILHASH_CONDITIONS
|
||||
p2_singleton_tailhash_parent_amount_list ; ((tailhash ((parent amount) (parent_amount)... ) (tailhash (parent amount))..)
|
||||
CONDITIONS
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
)
|
|
@ -0,0 +1 @@
|
|||
ff02ffff01ff04ffff04ff30ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff8202ffffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff808080ffff04ffff04ff58ffff04ff8202ffffff04ff34ffff04ffff04ff15ff8080ff8080808080ffff02ff26ffff04ff02ffff04ffff02ff5effff04ff02ffff04ff17ff80808080ffff04ff5fffff04ffff0bff82023fff5fff82053f80ffff04ff8201bfffff04ff82053fffff04ffff02ff7affff04ff02ffff04ff0bffff04ff5fffff04ff2fffff04ff82017fffff04ff17ff8080808080808080ff8080808080808080808080ffff04ffff01ffffffff3d48ff02ff333effff0401ff01ff02ff04ffff04ff78ffff04ffff02ff2effff04ff02ffff04ffff04ff05ffff04ff0bff808080ff80808080ff808080ffff04ffff04ff20ffff04ffff0bff05ffff012480ff808080ff178080ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff5cffff0bff34ff2480ffff0bff5cffff0bff5cffff0bff34ff2c80ff0980ffff0bff5cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ffff02ffff03ff5fffff01ff02ff7cffff04ff02ffff04ffff0bff82011fff2fff82029f80ffff04ffff02ffff03ff81dfffff01ff02ff2effff04ff02ffff01ff80808080ffff01ff02ff2effff04ff02ffff04ffff04ff34ffff04ffff04ff58ffff04ff17ffff04ffff11ffff10ff81bfff82029f80ff82017f80ff80808080ff0b8080ff8080808080ff0180ffff04ffff02ff5affff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ffff10ff81bfff82029f80ffff04ff82017fffff04ff8202ffff8080808080808080808080ff808080808080ff8080ff0180ff02ffff03ff2fffff01ff02ff7affff04ff02ffff04ff05ffff04ff0bffff04ff37ffff04ff6fffff04ffff02ff5affff04ff02ffff04ff05ffff04ffff02ffff03ffff09ff47ff818f80ffff0181a7ffff01ff088080ff0180ffff04ff0bffff04ffff02ff7effff04ff02ffff04ff05ffff04ff818fffff04ff0bff808080808080ffff04ff81cfffff04ff80ffff04ffff02ff5effff04ff02ffff04ff81a7ff80808080ffff04ff5fff8080808080808080808080ff8080808080808080ffff015f80ff0180ffffff04ffff04ff78ffff04ffff02ff2effff04ff02ffff04ffff04ff17ffff04ffff02ff2effff04ff02ffff01ff80808080ff808080ff80808080ff808080ffff04ffff04ff20ffff04ffff0bff17ffff012480ff808080ffff02ffff03ff2fffff01ff02ff26ffff04ff02ffff04ff05ffff04ff0bffff04ffff02ff2affff04ff02ffff04ff818fffff04ff0bffff04ff82014fff808080808080ffff04ff6fffff04ffff10ff5fff82014f80ff8080808080808080ffff01ff04ffff04ff58ffff04ff0bffff04ffff11ff5fff0580ff80808080ff81bf8080ff01808080ff0bff5cffff0bff34ff2880ffff0bff5cffff0bff5cffff0bff34ff2c80ff0580ffff0bff5cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ffff02ffff03ff05ffff01ff10ffff02ffff03ffff09ff11ff5880ffff01ff02ffff03ffff15ff59ff8080ffff0159ff8080ff0180ff8080ff0180ffff02ff5effff04ff02ffff04ff0dff8080808080ff8080ff0180ff02ff36ffff04ff02ffff04ff05ffff04ff17ffff04ffff0bff34ff0b80ffff04ffff0bff34ff0580ff80808080808080ff018080
|
|
@ -0,0 +1 @@
|
|||
fb3890f672c9df3cc69699e21446b89b55b65071d35bf5c80a49d11c9b79a68f
|
|
@ -0,0 +1,109 @@
|
|||
(mod
|
||||
(
|
||||
TREASURY_MOD_HASH
|
||||
PROPOSAL_VALIDATOR ; this is the curryed proposal validator
|
||||
PROPOSAL_LENGTH
|
||||
PROPOSAL_SOFTCLOSE_LENGTH
|
||||
ATTENDANCE_REQUIRED
|
||||
PASS_MARGIN ; this is a percentage 0 - 10,000 - 51% would be 5100
|
||||
PROPOSAL_SELF_DESTRUCT_TIME ; time in seconds after which proposals can be automatically closed
|
||||
ORACLE_SPEND_DELAY ; timelock delay for oracle spend
|
||||
(@ proposal_announcement (announcement_source delegated_puzzle_hash announcement_args))
|
||||
proposal_validator_solution
|
||||
delegated_puzzle_reveal ; this is the reveal of the puzzle announced by the proposal
|
||||
delegated_solution ; this is not secure unless the delegated puzzle secures it
|
||||
my_singleton_struct
|
||||
)
|
||||
(include utility_macros.clib)
|
||||
(include condition_codes.clib)
|
||||
(include curry-and-treehash.clib)
|
||||
(include *standard-cl-21*)
|
||||
|
||||
(defun-inline recreate_self (
|
||||
TREASURY_MOD_HASH
|
||||
PROPOSAL_VALIDATOR
|
||||
PROPOSAL_LENGTH
|
||||
PROPOSAL_SOFTCLOSE_LENGTH
|
||||
ATTENDANCE_REQUIRED
|
||||
PASS_MARGIN
|
||||
PROPOSAL_SELF_DESTRUCT_TIME
|
||||
ORACLE_SPEND_DELAY
|
||||
)
|
||||
(puzzle-hash-of-curried-function TREASURY_MOD_HASH
|
||||
(sha256 ONE ORACLE_SPEND_DELAY)
|
||||
(sha256 ONE PROPOSAL_SELF_DESTRUCT_TIME)
|
||||
(sha256 ONE PASS_MARGIN)
|
||||
(sha256 ONE ATTENDANCE_REQUIRED)
|
||||
(sha256 ONE PROPOSAL_SOFTCLOSE_LENGTH)
|
||||
(sha256 ONE PROPOSAL_LENGTH)
|
||||
(sha256tree PROPOSAL_VALIDATOR)
|
||||
(sha256 ONE TREASURY_MOD_HASH)
|
||||
)
|
||||
)
|
||||
|
||||
(defun calculate_singleton_puzzle_hash (SINGLETON_STRUCT inner_puzzle_hash)
|
||||
(puzzle-hash-of-curried-function (f SINGLETON_STRUCT)
|
||||
inner_puzzle_hash
|
||||
(sha256tree SINGLETON_STRUCT)
|
||||
)
|
||||
)
|
||||
|
||||
(defun stager (ORACLE_SPEND_DELAY my_inner_puzhash singleton_struct)
|
||||
(c
|
||||
(if singleton_struct
|
||||
(list ASSERT_MY_COIN_ID
|
||||
(sha256
|
||||
(f (r singleton_struct))
|
||||
(calculate_singleton_puzzle_hash singleton_struct my_inner_puzhash)
|
||||
ONE
|
||||
)
|
||||
)
|
||||
(list ASSERT_HEIGHT_RELATIVE ORACLE_SPEND_DELAY)
|
||||
)
|
||||
(list (list CREATE_COIN my_inner_puzhash ONE))
|
||||
)
|
||||
)
|
||||
|
||||
(c
|
||||
(list CREATE_PUZZLE_ANNOUNCEMENT 0) ; the arguments are secured implicitly in the puzzle of the treasury
|
||||
(if delegated_puzzle_reveal
|
||||
; if we're checking a proposal (testing if it has passed)
|
||||
(if (= (sha256tree delegated_puzzle_reveal) delegated_puzzle_hash)
|
||||
; Merge the treasury conditions with the proposal validator conditions
|
||||
; If the update case then the validator returns the new treasury create coin
|
||||
; If the spend case then we need to recreate the treasury outselves
|
||||
; treasury specific conditions
|
||||
|
||||
(c
|
||||
(list ASSERT_COIN_ANNOUNCEMENT (sha256 announcement_source (sha256tree (list delegated_puzzle_hash announcement_args)))) ; announcement source is validated inside the ProposalValidator
|
||||
(a
|
||||
PROPOSAL_VALIDATOR
|
||||
(list
|
||||
ATTENDANCE_REQUIRED
|
||||
PASS_MARGIN
|
||||
proposal_announcement
|
||||
proposal_validator_solution
|
||||
(a delegated_puzzle_reveal delegated_solution)
|
||||
)
|
||||
)
|
||||
)
|
||||
(x)
|
||||
)
|
||||
; no proposal_flag so create the oracle announcement
|
||||
(stager
|
||||
ORACLE_SPEND_DELAY
|
||||
(recreate_self
|
||||
TREASURY_MOD_HASH
|
||||
PROPOSAL_VALIDATOR
|
||||
PROPOSAL_LENGTH
|
||||
PROPOSAL_SOFTCLOSE_LENGTH
|
||||
ATTENDANCE_REQUIRED
|
||||
PASS_MARGIN
|
||||
PROPOSAL_SELF_DESTRUCT_TIME
|
||||
ORACLE_SPEND_DELAY
|
||||
)
|
||||
my_singleton_struct
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
|
@ -0,0 +1 @@
|
|||
ff02ffff01ff04ffff04ffff013effff04ffff0180ffff01808080ffff02ffff03ff8217ffffff01ff02ffff01ff02ffff03ffff09ffff02ff0affff04ff02ffff04ff8217ffff80808080ff8215ff80ffff01ff02ffff01ff04ffff04ffff013dffff04ffff0bff8209ffffff02ff0affff04ff02ffff04ffff04ff8215ffffff04ff822dffffff01808080ff8080808080ffff01808080ffff02ff0bffff04ff5fffff04ff8200bfffff04ff8205ffffff04ff820bffffff04ffff02ff8217ffff822fff80ffff018080808080808080ff0180ffff01ff02ffff01ff0880ff018080ff0180ff0180ffff01ff02ffff01ff02ff1effff04ff02ffff04ff8202ffffff04ffff02ff0cffff04ff02ffff04ff05ffff04ffff0bffff0101ff8202ff80ffff04ffff0bffff0101ff82017f80ffff04ffff0bffff0101ff8200bf80ffff04ffff0bffff0101ff5f80ffff04ffff0bffff0101ff2f80ffff04ffff0bffff0101ff1780ffff04ffff02ff0affff04ff02ffff04ff0bff80808080ffff04ffff0bffff0101ff0580ff808080808080808080808080ffff04ff825fffff808080808080ff018080ff018080ffff04ffff01ffffff02ffff03ff05ffff01ff02ffff01ff02ff08ffff04ff02ffff04ffff06ff0580ffff04ffff0bffff0102ffff0bffff0101ffff010480ffff0bffff0102ffff0bffff0102ffff0bffff0101ffff010180ffff05ff058080ffff0bffff0102ff0bffff0bffff0101ffff018080808080ff8080808080ff0180ffff01ff02ffff010bff018080ff0180ff0bffff0102ffff01a0a12871fee210fb8619291eaea194581cbd2531e4b23759d225f6806923f63222ffff0bffff0102ffff0bffff0102ffff01a09dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2ff0580ffff0bffff0102ffff02ff08ffff04ff02ffff04ff07ffff01ffa09dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b280808080ffff01a04bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a808080ffff02ffff03ffff07ff0580ffff01ff02ffff01ff0bffff0102ffff02ff0affff04ff02ffff04ffff05ff0580ff80808080ffff02ff0affff04ff02ffff04ffff06ff0580ff8080808080ff0180ffff01ff02ffff01ff0bffff0101ff0580ff018080ff0180ffff02ff0cffff04ff02ffff04ff09ffff04ff0bffff04ffff02ff0affff04ff02ffff04ff05ff80808080ff808080808080ff04ffff02ffff03ff17ffff01ff02ffff01ff04ffff0146ffff04ffff0bffff05ffff06ff178080ffff02ff16ffff04ff02ffff04ff17ffff04ff0bff8080808080ffff010180ffff01808080ff0180ffff01ff02ffff01ff04ffff0152ffff04ff05ffff01808080ff018080ff0180ffff04ffff04ffff0133ffff04ff0bffff01ff01808080ff808080ff018080
|
|
@ -0,0 +1 @@
|
|||
ff02ffff01ff02ffff03ff8205ffffff01ff02ffff03ffff09ffff02ff2effff04ff02ffff04ff822fffff80808080ff822bff80ffff01ff04ffff04ff2cffff04ff8227ffff808080ffff04ffff04ff2cffff01ff808080ffff04ffff04ff38ffff04ffff0bff8213ffffff02ff2effff04ff02ffff04ffff04ff822bffffff04ff825bffff808080ff8080808080ff808080ffff02ff0bffff04ff5fffff04ff81bfffff04ff820bffffff04ff8217ffffff04ffff02ff822fffff825fff80ff80808080808080808080ffff01ff088080ff0180ffff01ff04ffff04ff2cffff01ff808080ffff02ff3effff04ff02ffff04ff8202ffffff04ffff02ff36ffff04ff02ffff04ff05ffff04ffff0bff22ff8202ff80ffff04ffff0bff22ff82017f80ffff04ffff0bff22ff81bf80ffff04ffff0bff22ff5f80ffff04ffff0bff22ff2f80ffff04ffff0bff22ff1780ffff04ffff02ff2effff04ff02ffff04ff0bff80808080ffff04ffff0bff22ff0580ff808080808080808080808080ffff04ff82bfffff8080808080808080ff0180ffff04ffff01ffffff52ff463fffff0233ff3e04ffffff0101ff02ff02ffff03ff05ffff01ff02ff3affff04ff02ffff04ff0dffff04ffff0bff2affff0bff22ff3c80ffff0bff2affff0bff2affff0bff22ff3280ff0980ffff0bff2aff0bffff0bff22ff8080808080ff8080808080ffff010b80ff0180ffffff02ff36ffff04ff02ffff04ff09ffff04ff0bffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff0bff2affff0bff22ff2480ffff0bff2affff0bff2affff0bff22ff3280ff0580ffff0bff2affff02ff3affff04ff02ffff04ff07ffff04ffff0bff22ff2280ff8080808080ffff0bff22ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff04ffff02ffff03ff17ffff01ff04ff28ffff04ffff0bff57ffff02ff26ffff04ff02ffff04ff17ffff04ff0bff8080808080ff2280ff808080ffff01ff04ff10ffff04ff05ff80808080ff0180ffff04ffff04ff34ffff04ff0bffff04ff22ff80808080ff808080ff018080
|
|
@ -0,0 +1,36 @@
|
|||
(mod
|
||||
(
|
||||
TREASURY_MOD_HASH
|
||||
PROPOSAL_VALIDATOR
|
||||
PROPOSAL_LENGTH
|
||||
PROPOSAL_SOFTCLOSE_LENGTH
|
||||
ATTENDANCE_REQUIRED
|
||||
PASS_MARGIN
|
||||
PROPOSAL_SELF_DESTRUCT_TIME
|
||||
ORACLE_SPEND_DELAY
|
||||
)
|
||||
;; This is a proposal to update treasury conditions for a DAO
|
||||
|
||||
|
||||
(include condition_codes.clib)
|
||||
(include curry-and-treehash.clib)
|
||||
(include *standard-cl-21*)
|
||||
(include utility_macros.clib)
|
||||
|
||||
|
||||
(list
|
||||
(list CREATE_COIN
|
||||
(puzzle-hash-of-curried-function TREASURY_MOD_HASH
|
||||
(sha256 ONE ORACLE_SPEND_DELAY)
|
||||
(sha256 ONE PROPOSAL_SELF_DESTRUCT_TIME)
|
||||
(sha256 ONE PASS_MARGIN)
|
||||
(sha256 ONE ATTENDANCE_REQUIRED)
|
||||
(sha256 ONE PROPOSAL_SOFTCLOSE_LENGTH)
|
||||
(sha256 ONE PROPOSAL_LENGTH)
|
||||
(sha256tree PROPOSAL_VALIDATOR)
|
||||
(sha256 ONE TREASURY_MOD_HASH)
|
||||
)
|
||||
ONE
|
||||
)
|
||||
)
|
||||
)
|
|
@ -0,0 +1 @@
|
|||
ff02ffff01ff04ffff04ffff0133ffff04ffff02ff0affff04ff02ffff04ff05ffff04ffff0bffff0101ff8202ff80ffff04ffff0bffff0101ff82017f80ffff04ffff0bffff0101ff8200bf80ffff04ffff0bffff0101ff5f80ffff04ffff0bffff0101ff2f80ffff04ffff0bffff0101ff1780ffff04ffff02ff0effff04ff02ffff04ff0bff80808080ffff04ffff0bffff0101ff0580ff808080808080808080808080ffff04ffff0101ffff0180808080ffff018080ffff04ffff01ffff02ffff03ff05ffff01ff02ffff01ff02ff04ffff04ff02ffff04ffff06ff0580ffff04ffff0bffff0102ffff0bffff0101ffff010480ffff0bffff0102ffff0bffff0102ffff0bffff0101ffff010180ffff05ff058080ffff0bffff0102ff0bffff0bffff0101ffff018080808080ff8080808080ff0180ffff01ff02ffff010bff018080ff0180ffff0bffff0102ffff01a0a12871fee210fb8619291eaea194581cbd2531e4b23759d225f6806923f63222ffff0bffff0102ffff0bffff0102ffff01a09dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2ff0580ffff0bffff0102ffff02ff04ffff04ff02ffff04ff07ffff01ffa09dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b280808080ffff01a04bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a808080ff02ffff03ffff07ff0580ffff01ff02ffff01ff0bffff0102ffff02ff0effff04ff02ffff04ffff05ff0580ff80808080ffff02ff0effff04ff02ffff04ffff06ff0580ff8080808080ff0180ffff01ff02ffff01ff0bffff0101ff0580ff018080ff0180ff018080
|
|
@ -7,6 +7,24 @@
|
|||
"covenant_layer": "b982796850336aabf9ab17c3f21e299f0c633444117ab5e9ebeafadf1860d9fc",
|
||||
"create_nft_launcher_from_did": "7a32d2d9571d3436791c0ad3d7fcfdb9c43ace2b0f0ff13f98d29f0cc093f445",
|
||||
"credential_restriction": "2fdfc1f058cfd65e7ec4e253bfeb394da163ecd0036f508df8629b0a2b8fde96",
|
||||
"dao_cat_buy_in": "2421f1375866bd9e27ed35179003018e1e44cbdfee96592f5c6eb62c7ababd8a",
|
||||
"dao_cat_eve": "488f55bedaca5a599544dfd5ab341e2e5c7e6fca67d9b98a3d856f876c52f53e",
|
||||
"dao_cat_launcher": "15eb3382528a5d25af7f2af3942cd31e71dacf41320910698a753179684d970d",
|
||||
"dao_dividend": "e3e4e15e7dd1694d776ba716c21dfff181530852682498220002ae72a8a261b2",
|
||||
"dao_dividend_timer": "f85df70f39c65b836cccfa5c93939452319ab42136f653ffee3bbd795eb022dc",
|
||||
"dao_finished_state": "7f3cc356732907933a8f9b1ccf16f71735d07340eb38c847aa402e97d75eb40b",
|
||||
"dao_lockup": "9fa63e652e131f89a9f8bb6f7abb5ffc6ac485a78dcfb8710cd9df5c368774d9",
|
||||
"dao_proposal": "a27440cdee44f910e80225592e51dc03721a9d819cc358165587fa2b34eef4cd",
|
||||
"dao_proposal_timer": "87f2808340dab6a5fb8194c501b3133f1f850c5a8bf422e7b049762486819813",
|
||||
"dao_proposal_validator": "0737418c68d175fcd5bb7584a6c736f8daca184f69f50bf8ad021592b5e8b538",
|
||||
"dao_resale_prevention_layer": "5940dea172de20b34149f709931057eb27cd0d1e769830102345bb7b1e4751c5",
|
||||
"dao_safe_payment": "3a86a13d78980b4bed1c709834630b47e4d3204125f1e1341be4ca40978649dd",
|
||||
"dao_spend_p2_singleton": "54964584da94f665a970e4d8b6e2091c4f2abe447886db82f19583f1a7e43ceb",
|
||||
"dao_spend_p2_singleton_v2": "5163024ea180c9ee3949da9db3105a0e3b706a1248ae4138ecd7078d77d5b7c9",
|
||||
"dao_treasury": "8db946dbea7db3c6e99f45225b4159bd5f2ed0b53576a5c80aca2fe8ffdd99b6",
|
||||
"dao_treasury_with_before_cond": "f1b1ae5386e9668a4ac2cd3bb34d9e610f0d324189619f5eb0c3c50437bb4d0f",
|
||||
"dao_treasury_with_hack": "32865fb271c24f6092085fecaa3a4fa6d32d7436773be06700a0aa47539ebdcc",
|
||||
"dao_update_proposal": "70567fde4ec4b6ed83a62bc0e92c3a391203b0d6f7a4d4bf3516c95662ba316c",
|
||||
"decompress_coin_spend_entry": "9d98ed08770d31be4bd1bde4705dab388db5e7e9c349f5a76fc3c347aa3a0b79",
|
||||
"decompress_coin_spend_entry_with_prefix": "92aa4bc8060a8836355a1884075141b4791ce1b67ae6092bb166b2845954bc89",
|
||||
"decompress_puzzle": "fe94c58f1117afe315e0450daca1c62460ec1a1c439cd4018d79967a5d7d1370",
|
||||
|
@ -19,6 +37,7 @@
|
|||
"exigent_metadata_layer": "d5fd32e069fda83e230ccd8f6a7c4f652231aed5c755514b3d996cbeff4182b8",
|
||||
"flag_proofs_checker": "fe2e3c631562fbb9be095297f762bf573705a0197164e9361ad5d50e045ba241",
|
||||
"genesis_by_coin_id": "493afb89eed93ab86741b2aa61b8f5de495d33ff9b781dfc8919e602b2afa150",
|
||||
"genesis_by_coin_id_or_singleton": "3bb7d336e4aaee5cd18bb3d8a01e15acfd56c80ff6801261783596c88a16603b",
|
||||
"genesis_by_puzzle_hash": "de5a6e06d41518be97ff6365694f4f89475dda773dede267caa33da63b434e36",
|
||||
"graftroot_dl_offers": "0893e36a88c064fddfa6f8abdb42c044584a98cb4273b80cccc83b4867b701a1",
|
||||
"nft_intermediate_launcher": "7a32d2d9571d3436791c0ad3d7fcfdb9c43ace2b0f0ff13f98d29f0cc093f445",
|
||||
|
@ -31,6 +50,7 @@
|
|||
"p2_1_of_n": "46b29fd87fbeb6737600c4543931222a6c1ed3db6fa5601a3ca284a9f4efe780",
|
||||
"p2_announced_delegated_puzzle": "c4d24c3c5349376f3e8f3aba202972091713b4ec4915f0f26192ae4ace0bd04d",
|
||||
"p2_conditions": "1c77d7d5efde60a7a1d2d27db6d746bc8e568aea1ef8586ca967a0d60b83cc36",
|
||||
"p2_conditions_curryable": "a12871fee210fb8619291eaea194581cbd2531e4b23759d225f6806923f63222",
|
||||
"p2_delegated_conditions": "0ff94726f1a8dea5c3f70d3121945190778d3b2b3fcda3735a1f290977e98341",
|
||||
"p2_delegated_puzzle": "542cde70d1102cd1b763220990873efc8ab15625ded7eae22cc11e21ef2e2f7c",
|
||||
"p2_delegated_puzzle_or_hidden_puzzle": "e9aaa49f45bad5c889b86ee3341550c155cfdd10c3a6757de618d20612fffd52",
|
||||
|
@ -39,6 +59,7 @@
|
|||
"p2_puzzle_hash": "13e29a62b42cd2ef72a79e4bacdc59733ca6310d65af83d349360d36ec622363",
|
||||
"p2_singleton": "40f828d8dd55603f4ff9fbf6b73271e904e69406982f4fbefae2c8dcceaf9834",
|
||||
"p2_singleton_or_delayed_puzhash": "adb656e0211e2ab4f42069a4c5efc80dc907e7062be08bf1628c8e5b6d94d25b",
|
||||
"p2_singleton_via_delegated_puzzle": "231334e31ed4e60867abb640eb904931a4ce248e68091442e3b0b47a2af77d06",
|
||||
"pool_member_innerpuz": "a8490702e333ddd831a3ac9c22d0fa26d2bfeaf2d33608deb22f0e0123eb0494",
|
||||
"pool_waitingroom_innerpuz": "a317541a765bf8375e1c6e7c13503d0d2cbf56cacad5182befe947e78e2c0307",
|
||||
"rom_bootstrap_generator": "161bade1f822dcd62ab712ebaf30f3922a301e48a639e4295c5685f8bece7bd9",
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
; This is a TAIL for use with cat.clvm.
|
||||
;
|
||||
; This checker allows new CATs to be created if they have a particular coin id as parent
|
||||
;
|
||||
; The genesis_id is curried in, making this lineage_check program unique and giving the CAT it's uniqueness
|
||||
(mod (
|
||||
GENESIS_ID
|
||||
MINT_LAUNCHER_PUZZLE_HASH
|
||||
Truths
|
||||
parent_is_cat
|
||||
lineage_proof
|
||||
delta
|
||||
inner_conditions
|
||||
( ; solution
|
||||
singleton_inner_puzhash
|
||||
parent_parent_id
|
||||
parent_amount
|
||||
)
|
||||
)
|
||||
|
||||
(include cat_truths.clib)
|
||||
(include curry-and-treehash.clib)
|
||||
|
||||
(if delta
|
||||
(x)
|
||||
(if (= (my_parent_cat_truth Truths) GENESIS_ID)
|
||||
()
|
||||
(if
|
||||
(=
|
||||
(my_parent_cat_truth Truths)
|
||||
(sha256
|
||||
parent_parent_id
|
||||
MINT_LAUNCHER_PUZZLE_HASH
|
||||
parent_amount
|
||||
)
|
||||
)
|
||||
()
|
||||
(x)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
)
|
|
@ -0,0 +1 @@
|
|||
ff02ffff03ff5fffff01ff0880ffff01ff02ffff03ffff09ff5bff0280ff80ffff01ff02ffff03ffff09ff5bffff0bff82057fff05ff820b7f8080ff80ffff01ff088080ff018080ff018080ff0180
|
|
@ -0,0 +1 @@
|
|||
140c74d3e8c2b66cae5dca30d03cd532df12f71e9fc17f565e5a973930d11b1f
|
|
@ -0,0 +1,3 @@
|
|||
(mod (CONDITIONS_LIST)
|
||||
CONDITIONS_LIST
|
||||
)
|
|
@ -0,0 +1 @@
|
|||
02
|
|
@ -0,0 +1,60 @@
|
|||
;; Works with p2_singleton_via_delegated_puzzle
|
||||
;; When we have many p2_singleton coins and want to aggregate them together
|
||||
|
||||
(mod
|
||||
(
|
||||
my_id
|
||||
my_puzhash
|
||||
my_amount
|
||||
list_of_parent_puzhash_amounts ; (parent_id puzhash amount)
|
||||
)
|
||||
|
||||
(include condition_codes.clib)
|
||||
(include curry-and-treehash.clib)
|
||||
(include *standard-cl-21*)
|
||||
|
||||
(defun cons_announcements_to_output (coin_id output)
|
||||
(c
|
||||
(list ASSERT_COIN_ANNOUNCEMENT (sha256tree (list coin_id 0)))
|
||||
output
|
||||
)
|
||||
)
|
||||
|
||||
(defun for_parent_puzhash_amounts
|
||||
(
|
||||
my_puzhash
|
||||
(@ coin_info_list ((@ first (parent puzhash amount)) . rest))
|
||||
total
|
||||
)
|
||||
(if coin_info_list
|
||||
(cons_announcements_to_output
|
||||
(calculate_coin_id parent puzhash amount)
|
||||
(for_parent_puzhash_amounts my_puzhash rest (+ total amount))
|
||||
)
|
||||
(list
|
||||
(list ASSERT_HEIGHT_RELATIVE 5) ; TODO: should this be higher or lower?
|
||||
(list CREATE_COIN my_puzhash total)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(defun-inline give_self_to_merge ()
|
||||
(list
|
||||
(list CREATE_COIN_ANNOUNCEMENT 0)
|
||||
)
|
||||
)
|
||||
|
||||
(c
|
||||
(list ASSERT_MY_PUZZLEHASH my_puzhash)
|
||||
(if list_of_parent_puzhash_amounts
|
||||
; we are making the output
|
||||
(c
|
||||
(list ASSERT_MY_COIN_ID my_id)
|
||||
(for_parent_puzhash_amounts my_puzhash list_of_parent_puzhash_amounts my_amount)
|
||||
)
|
||||
; we are letting another coin make the output
|
||||
(give_self_to_merge)
|
||||
)
|
||||
)
|
||||
|
||||
)
|
|
@ -0,0 +1 @@
|
|||
ff02ffff01ff04ffff04ffff0148ffff04ff0bffff01808080ffff02ffff03ff2fffff01ff02ffff01ff04ffff04ffff0146ffff04ff05ffff01808080ffff02ff0effff04ff02ffff04ff0bffff04ff2fffff04ff17ff80808080808080ff0180ffff01ff02ffff01ff04ffff04ffff013cffff04ffff0180ffff01808080ffff018080ff018080ff018080ffff04ffff01ffffff02ffff03ffff22ffff09ffff0dff0580ffff012080ffff09ffff0dff0b80ffff012080ffff15ff17ffff0181ff8080ffff01ff02ffff01ff0bff05ff0bff1780ff0180ffff01ff02ffff01ff0880ff018080ff0180ff02ffff03ffff07ff0580ffff01ff02ffff01ff0bffff0102ffff02ff0cffff04ff02ffff04ffff05ff0580ff80808080ffff02ff0cffff04ff02ffff04ffff06ff0580ff8080808080ff0180ffff01ff02ffff01ff0bffff0101ff0580ff018080ff0180ffff04ffff04ffff013dffff04ffff02ff0cffff04ff02ffff04ffff04ff05ffff01ff808080ff80808080ff808080ff0b80ff02ffff03ff0bffff01ff02ffff01ff02ff0affff04ff02ffff04ffff02ff08ffff04ff02ffff04ff23ffff04ff53ffff04ff8200b3ff808080808080ffff04ffff02ff0effff04ff02ffff04ff05ffff04ff1bffff04ffff10ff17ff8200b380ff808080808080ff8080808080ff0180ffff01ff02ffff01ff04ffff04ffff0152ffff04ffff0105ffff01808080ffff04ffff04ffff0133ffff04ff05ffff04ff17ffff0180808080ffff01808080ff018080ff0180ff018080
|
|
@ -0,0 +1,47 @@
|
|||
;; This puzzle holds an amount which can be spent via two spend paths:
|
||||
;; 1. to a delegated puzzle provided our owner singleton creates a puzzle announcement of this coin's id and the delegated puzzle.
|
||||
;; 2. coins of this puzzle type can be merged together without the owner singleton's permission. This spend type is useful for DAOs which use this puzzle to custody funds and want to keep a reasonable limit on the number of coins tracked by DAO wallets.
|
||||
;; The AGGREGATOR_PUZZLE is curried in to preserve generality and so its logic can be updated without requiring any change to the spend to delegated path. Optionally the Aggregator puzzle can be `(x)` to close off this spend path
|
||||
|
||||
(mod (
|
||||
SINGLETON_STRUCT
|
||||
AGGREGATOR_PUZZLE
|
||||
aggregator_solution ; (my_id my_puzhash list_of_parent_puzhash_amounts my_amount)
|
||||
singleton_inner_puzhash
|
||||
delegated_puzzle
|
||||
delegated_solution
|
||||
my_id
|
||||
)
|
||||
|
||||
(include condition_codes.clib)
|
||||
(include curry-and-treehash.clib)
|
||||
|
||||
(defun-inline calculate_full_puzzle_hash (SINGLETON_STRUCT singleton_inner_puzhash)
|
||||
(puzzle-hash-of-curried-function (f SINGLETON_STRUCT)
|
||||
singleton_inner_puzhash
|
||||
(sha256tree SINGLETON_STRUCT)
|
||||
)
|
||||
)
|
||||
|
||||
(if aggregator_solution
|
||||
; we are merging coins to make a larger coin
|
||||
(a AGGREGATOR_PUZZLE aggregator_solution)
|
||||
; we are being spent by our singleton
|
||||
(c
|
||||
(list
|
||||
ASSERT_PUZZLE_ANNOUNCEMENT
|
||||
(sha256
|
||||
(calculate_full_puzzle_hash SINGLETON_STRUCT singleton_inner_puzhash)
|
||||
(sha256tree (list my_id (sha256tree delegated_puzzle)))
|
||||
)
|
||||
)
|
||||
(c
|
||||
(list CREATE_COIN_ANNOUNCEMENT '$')
|
||||
(c
|
||||
(list ASSERT_MY_COIN_ID my_id)
|
||||
(a delegated_puzzle delegated_solution)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
|
@ -0,0 +1 @@
|
|||
ff02ffff01ff02ffff03ff17ffff01ff02ff0bff1780ffff01ff04ffff04ff18ffff04ffff0bffff02ff2effff04ff02ffff04ff09ffff04ff2fffff04ffff02ff3effff04ff02ffff04ff05ff80808080ff808080808080ffff02ff3effff04ff02ffff04ffff04ff82017fffff04ffff02ff3effff04ff02ffff04ff5fff80808080ff808080ff8080808080ff808080ffff04ffff04ff2cffff01ff248080ffff04ffff04ff10ffff04ff82017fff808080ffff02ff5fff81bf8080808080ff0180ffff04ffff01ffffff463fff02ff3c04ffff01ff0102ffff02ffff03ff05ffff01ff02ff16ffff04ff02ffff04ff0dffff04ffff0bff3affff0bff12ff3c80ffff0bff3affff0bff3affff0bff12ff2a80ff0980ffff0bff3aff0bffff0bff12ff8080808080ff8080808080ffff010b80ff0180ffff0bff3affff0bff12ff1480ffff0bff3affff0bff3affff0bff12ff2a80ff0580ffff0bff3affff02ff16ffff04ff02ffff04ff07ffff04ffff0bff12ff1280ff8080808080ffff0bff12ff8080808080ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff3effff04ff02ffff04ff09ff80808080ffff02ff3effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080
|
|
@ -2,6 +2,8 @@ from __future__ import annotations
|
|||
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
from chia_rs import Coin
|
||||
|
||||
from chia.types.blockchain_format.program import Program
|
||||
from chia.types.blockchain_format.sized_bytes import bytes32
|
||||
from chia.types.spend_bundle import SpendBundle
|
||||
|
@ -15,6 +17,7 @@ from chia.wallet.cat_wallet.cat_utils import (
|
|||
unsigned_spend_bundle_for_spendable_cats,
|
||||
)
|
||||
from chia.wallet.cat_wallet.lineage_store import CATLineageStore
|
||||
from chia.wallet.dao_wallet.dao_utils import create_cat_launcher_for_singleton_id
|
||||
from chia.wallet.lineage_proof import LineageProof
|
||||
from chia.wallet.payment import Payment
|
||||
from chia.wallet.puzzles.load_clvm import load_clvm_maybe_recompile
|
||||
|
@ -24,6 +27,7 @@ GENESIS_BY_ID_MOD = load_clvm_maybe_recompile("genesis_by_coin_id.clsp")
|
|||
GENESIS_BY_PUZHASH_MOD = load_clvm_maybe_recompile("genesis_by_puzzle_hash.clsp")
|
||||
EVERYTHING_WITH_SIG_MOD = load_clvm_maybe_recompile("everything_with_signature.clsp")
|
||||
DELEGATED_LIMITATIONS_MOD = load_clvm_maybe_recompile("delegated_tail.clsp")
|
||||
GENESIS_BY_ID_OR_SINGLETON_MOD = load_clvm_maybe_recompile("genesis_by_coin_id_or_singleton.clsp")
|
||||
|
||||
|
||||
class LimitationsProgram:
|
||||
|
@ -191,6 +195,93 @@ class DelegatedLimitations(LimitationsProgram):
|
|||
)
|
||||
|
||||
|
||||
class GenesisByIdOrSingleton(LimitationsProgram):
|
||||
"""
|
||||
This TAIL allows for another TAIL to be used, as long as a signature of that TAIL's puzzlehash is included.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def match(uncurried_mod: Program, curried_args: Program) -> Tuple[bool, List[Program]]:
|
||||
if uncurried_mod == GENESIS_BY_ID_OR_SINGLETON_MOD:
|
||||
genesis_id = curried_args.first()
|
||||
return True, [genesis_id.as_atom()]
|
||||
else:
|
||||
return False, []
|
||||
|
||||
@staticmethod
|
||||
def construct(args: List[Program]) -> Program:
|
||||
return GENESIS_BY_ID_OR_SINGLETON_MOD.curry(
|
||||
args[0],
|
||||
args[1],
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def solve(args: List[Program], solution_dict: Dict) -> Program:
|
||||
pid = hexstr_to_bytes(solution_dict["parent_coin_info"])
|
||||
return Program.to([pid, solution_dict["amount"]])
|
||||
|
||||
@classmethod
|
||||
async def generate_issuance_bundle(
|
||||
cls, wallet, tail_info: Dict, amount: uint64
|
||||
) -> Tuple[TransactionRecord, SpendBundle]:
|
||||
if "coins" in tail_info:
|
||||
coins: List[Coin] = tail_info["coins"]
|
||||
origin_id = coins.copy().pop().name()
|
||||
else:
|
||||
coins = await wallet.standard_wallet.select_coins(amount)
|
||||
|
||||
origin = coins.copy().pop()
|
||||
origin_id = origin.name()
|
||||
|
||||
cat_inner: Program = await wallet.get_new_inner_puzzle()
|
||||
# GENESIS_ID
|
||||
# TREASURY_SINGLETON_STRUCT ; (SINGLETON_MOD_HASH, (LAUNCHER_ID, LAUNCHER_PUZZLE_HASH))
|
||||
launcher_puzhash = create_cat_launcher_for_singleton_id(tail_info["treasury_id"]).get_tree_hash()
|
||||
tail: Program = cls.construct(
|
||||
[
|
||||
Program.to(origin_id),
|
||||
Program.to(launcher_puzhash),
|
||||
]
|
||||
)
|
||||
|
||||
wallet.lineage_store = await CATLineageStore.create(
|
||||
wallet.wallet_state_manager.db_wrapper, tail.get_tree_hash().hex()
|
||||
)
|
||||
await wallet.add_lineage(origin_id, LineageProof())
|
||||
|
||||
minted_cat_puzzle_hash: bytes32 = construct_cat_puzzle(CAT_MOD, tail.get_tree_hash(), cat_inner).get_tree_hash()
|
||||
|
||||
tx_record: TransactionRecord = await wallet.standard_wallet.generate_signed_transaction(
|
||||
amount, minted_cat_puzzle_hash, uint64(0), origin_id=origin_id, coins=set(coins)
|
||||
)
|
||||
assert tx_record.spend_bundle is not None
|
||||
payment = Payment(cat_inner.get_tree_hash(), amount)
|
||||
inner_solution = wallet.standard_wallet.add_condition_to_solution(
|
||||
Program.to([51, 0, -113, tail, []]),
|
||||
wallet.standard_wallet.make_solution(
|
||||
primaries=[payment],
|
||||
),
|
||||
)
|
||||
eve_spend = unsigned_spend_bundle_for_spendable_cats(
|
||||
CAT_MOD,
|
||||
[
|
||||
SpendableCAT(
|
||||
list(filter(lambda a: a.amount == amount, tx_record.additions))[0],
|
||||
tail.get_tree_hash(),
|
||||
cat_inner,
|
||||
inner_solution,
|
||||
limitations_program_reveal=tail,
|
||||
)
|
||||
],
|
||||
)
|
||||
signed_eve_spend = await wallet.sign(eve_spend)
|
||||
|
||||
if wallet.cat_info.my_tail is None:
|
||||
await wallet.save_info(CATInfo(tail.get_tree_hash(), tail))
|
||||
|
||||
return tx_record, SpendBundle.aggregate([tx_record.spend_bundle, signed_eve_spend])
|
||||
|
||||
|
||||
# This should probably be much more elegant than just a dictionary with strings as identifiers
|
||||
# Right now this is small and experimental so it can stay like this
|
||||
ALL_LIMITATIONS_PROGRAMS: Dict[str, Any] = {
|
||||
|
@ -198,6 +289,7 @@ ALL_LIMITATIONS_PROGRAMS: Dict[str, Any] = {
|
|||
"genesis_by_puzhash": GenesisByPuzhash,
|
||||
"everything_with_signature": EverythingWithSig,
|
||||
"delegated_limitations": DelegatedLimitations,
|
||||
"genesis_by_id_or_singleton": GenesisByIdOrSingleton,
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
from typing import List, Optional, Union
|
||||
|
||||
from chia.types.blockchain_format.coin import Coin
|
||||
from chia.types.blockchain_format.program import Program
|
||||
from chia.types.blockchain_format.serialized_program import SerializedProgram
|
||||
from chia.types.blockchain_format.sized_bytes import bytes32
|
||||
from chia.types.coin_spend import CoinSpend, compute_additions
|
||||
from chia.wallet.puzzles.load_clvm import load_clvm_maybe_recompile
|
||||
from chia.wallet.util.curry_and_treehash import calculate_hash_of_quoted_mod_hash, curry_and_treehash
|
||||
|
||||
|
@ -14,7 +17,7 @@ SINGLETON_LAUNCHER_PUZZLE = load_clvm_maybe_recompile("singleton_launcher.clsp")
|
|||
SINGLETON_LAUNCHER_PUZZLE_HASH = SINGLETON_LAUNCHER_PUZZLE.get_tree_hash()
|
||||
|
||||
|
||||
def get_inner_puzzle_from_singleton(puzzle: Program) -> Optional[Program]:
|
||||
def get_inner_puzzle_from_singleton(puzzle: Union[Program, SerializedProgram]) -> Optional[Program]:
|
||||
"""
|
||||
Extract the inner puzzle of a singleton
|
||||
:param puzzle: Singleton puzzle
|
||||
|
@ -30,7 +33,23 @@ def get_inner_puzzle_from_singleton(puzzle: Program) -> Optional[Program]:
|
|||
return Program(INNER_PUZZLE)
|
||||
|
||||
|
||||
def is_singleton(inner_f: Program) -> bool:
|
||||
def get_singleton_id_from_puzzle(puzzle: Union[Program, SerializedProgram]) -> Optional[bytes32]:
|
||||
"""
|
||||
Extract the singleton ID from a singleton puzzle
|
||||
:param puzzle: Singleton puzzle
|
||||
:return: Inner puzzle
|
||||
"""
|
||||
r = puzzle.uncurry()
|
||||
if r is None:
|
||||
return None
|
||||
inner_f, args = r
|
||||
if not is_singleton(inner_f):
|
||||
return None
|
||||
SINGLETON_STRUCT, INNER_PUZZLE = list(args.as_iter())
|
||||
return bytes32(Program(SINGLETON_STRUCT).rest().first().as_atom())
|
||||
|
||||
|
||||
def is_singleton(inner_f: Union[Program, SerializedProgram]) -> bool:
|
||||
"""
|
||||
Check if a puzzle is a singleton mod
|
||||
:param inner_f: puzzle
|
||||
|
@ -52,7 +71,7 @@ def create_singleton_puzzle_hash(innerpuz_hash: bytes32, launcher_id: bytes32) -
|
|||
return curry_and_treehash(SINGLETON_TOP_LAYER_MOD_HASH_QUOTED, singleton_struct.get_tree_hash(), innerpuz_hash)
|
||||
|
||||
|
||||
def create_singleton_puzzle(innerpuz: Program, launcher_id: bytes32) -> Program:
|
||||
def create_singleton_puzzle(innerpuz: Union[Program, SerializedProgram], launcher_id: bytes32) -> Program:
|
||||
"""
|
||||
Create a full Singleton puzzle
|
||||
:param innerpuz: Singleton inner puzzle
|
||||
|
@ -62,3 +81,16 @@ def create_singleton_puzzle(innerpuz: Program, launcher_id: bytes32) -> Program:
|
|||
# singleton_struct = (MOD_HASH . (LAUNCHER_ID . LAUNCHER_PUZZLE_HASH))
|
||||
singleton_struct = Program.to((SINGLETON_TOP_LAYER_MOD_HASH, (launcher_id, SINGLETON_LAUNCHER_PUZZLE_HASH)))
|
||||
return SINGLETON_TOP_LAYER_MOD.curry(singleton_struct, innerpuz)
|
||||
|
||||
|
||||
def get_most_recent_singleton_coin_from_coin_spend(coin_sol: CoinSpend) -> Optional[Coin]:
|
||||
additions: List[Coin] = compute_additions(coin_sol)
|
||||
for coin in additions:
|
||||
if coin.amount % 2 == 1:
|
||||
return coin
|
||||
return None
|
||||
|
||||
|
||||
def get_singleton_struct_for_id(id: bytes32) -> Program:
|
||||
singleton_struct: Program = Program.to((SINGLETON_TOP_LAYER_MOD_HASH, (id, SINGLETON_LAUNCHER_PUZZLE_HASH)))
|
||||
return singleton_struct
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Optional
|
||||
|
||||
from chia.types.blockchain_format.coin import Coin
|
||||
from chia.types.blockchain_format.sized_bytes import bytes32
|
||||
from chia.types.coin_spend import CoinSpend
|
||||
from chia.util.ints import uint32
|
||||
from chia.wallet.lineage_proof import LineageProof
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SingletonRecord:
|
||||
"""
|
||||
These are values that correspond to a singleton in the WalletSingletonStore
|
||||
"""
|
||||
|
||||
coin: Coin
|
||||
singleton_id: bytes32
|
||||
wallet_id: uint32
|
||||
parent_coinspend: CoinSpend
|
||||
inner_puzzle_hash: Optional[bytes32]
|
||||
pending: bool
|
||||
removed_height: int
|
||||
lineage_proof: LineageProof
|
||||
custom_data: Optional[Any]
|
||||
|
||||
def name(self) -> bytes32:
|
||||
return self.coin.name()
|
|
@ -259,6 +259,12 @@ class TradeManager:
|
|||
ignore_max_send_amount=True,
|
||||
)
|
||||
all_txs.append(tx)
|
||||
elif wallet.type() == WalletType.CAT:
|
||||
# ATTENTION: new_wallets
|
||||
txs = await wallet.generate_signed_transactions(
|
||||
[coin.amount], [new_ph], fee=fee_to_pay, coins={coin}, ignore_max_send_amount=True
|
||||
)
|
||||
all_txs.extend(txs)
|
||||
else:
|
||||
# ATTENTION: new_wallets
|
||||
txs = await wallet.generate_signed_transaction(
|
||||
|
@ -340,6 +346,15 @@ class TradeManager:
|
|||
if tx is not None and tx.spend_bundle is not None:
|
||||
bundles.append(tx.spend_bundle)
|
||||
all_txs.append(dataclasses.replace(tx, spend_bundle=None))
|
||||
elif wallet.type() == WalletType.CAT:
|
||||
# ATTENTION: new_wallets
|
||||
txs = await wallet.generate_signed_transactions(
|
||||
[coin.amount], [new_ph], fee=fee_to_pay, coins={coin}, ignore_max_send_amount=True
|
||||
)
|
||||
for tx in txs:
|
||||
if tx is not None and tx.spend_bundle is not None:
|
||||
bundles.append(tx.spend_bundle)
|
||||
all_txs.append(dataclasses.replace(tx, spend_bundle=None))
|
||||
else:
|
||||
# ATTENTION: new_wallets
|
||||
txs = await wallet.generate_signed_transaction(
|
||||
|
@ -601,6 +616,17 @@ class TradeManager:
|
|||
reuse_puzhash=reuse_puzhash,
|
||||
)
|
||||
all_transactions.extend(txs)
|
||||
elif wallet.type() == WalletType.CAT:
|
||||
# ATTENTION: new_wallets
|
||||
txs = await wallet.generate_signed_transactions(
|
||||
[abs(offer_dict[id])],
|
||||
[OFFER_MOD_OLD_HASH if old else Offer.ph()],
|
||||
fee=fee_left_to_pay,
|
||||
coins=set(selected_coins),
|
||||
puzzle_announcements_to_consume=announcements_to_assert,
|
||||
reuse_puzhash=reuse_puzhash,
|
||||
)
|
||||
all_transactions.extend(txs)
|
||||
else:
|
||||
# ATTENTION: new_wallets
|
||||
txs = await wallet.generate_signed_transaction(
|
||||
|
|
|
@ -70,7 +70,8 @@ def debug_spend_bundle(spend_bundle, agg_sig_additional_data=DEFAULT_CONSTANTS.A
|
|||
continue
|
||||
|
||||
print(f"consuming coin {dump_coin(coin)}")
|
||||
print(f" with id {coin_name.hex()}")
|
||||
print(f" with id {coin_name}")
|
||||
print(f" ID in hex is {coin_name.hex()}")
|
||||
print()
|
||||
print(f"\nbrun -y main.sym '{bu_disassemble(puzzle_reveal)}' '{bu_disassemble(solution)}'")
|
||||
conditions = conditions_dict_for_solution(puzzle_reveal, solution, INFINITE_COST)
|
||||
|
|
|
@ -26,6 +26,8 @@ class WalletType(IntEnum):
|
|||
DATA_LAYER = 11
|
||||
DATA_LAYER_OFFER = 12
|
||||
VC = 13
|
||||
DAO = 14
|
||||
DAO_CAT = 15
|
||||
|
||||
|
||||
class CoinType(IntEnum):
|
||||
|
|
|
@ -0,0 +1,230 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
from sqlite3 import Row
|
||||
from typing import List, Type, TypeVar
|
||||
|
||||
from clvm.casts import int_from_bytes
|
||||
|
||||
from chia.consensus.default_constants import DEFAULT_CONSTANTS
|
||||
from chia.types.blockchain_format.coin import Coin
|
||||
from chia.types.blockchain_format.sized_bytes import bytes32
|
||||
from chia.types.coin_spend import CoinSpend
|
||||
from chia.types.condition_opcodes import ConditionOpcode
|
||||
from chia.util.condition_tools import conditions_dict_for_solution
|
||||
from chia.util.db_wrapper import DBWrapper2
|
||||
from chia.util.ints import uint32
|
||||
from chia.wallet import singleton
|
||||
from chia.wallet.lineage_proof import LineageProof
|
||||
from chia.wallet.singleton import get_inner_puzzle_from_singleton, get_singleton_id_from_puzzle
|
||||
from chia.wallet.singleton_record import SingletonRecord
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
_T_WalletSingletonStore = TypeVar("_T_WalletSingletonStore", bound="WalletSingletonStore")
|
||||
|
||||
|
||||
class WalletSingletonStore:
|
||||
db_wrapper: DBWrapper2
|
||||
|
||||
@classmethod
|
||||
async def create(cls: Type[_T_WalletSingletonStore], wrapper: DBWrapper2) -> _T_WalletSingletonStore:
|
||||
self = cls()
|
||||
self.db_wrapper = wrapper
|
||||
|
||||
async with self.db_wrapper.writer_maybe_transaction() as conn:
|
||||
await conn.execute(
|
||||
(
|
||||
"CREATE TABLE IF NOT EXISTS singletons("
|
||||
"coin_id blob PRIMARY KEY,"
|
||||
" coin text,"
|
||||
" singleton_id blob,"
|
||||
" wallet_id int,"
|
||||
" parent_coin_spend blob,"
|
||||
" inner_puzzle_hash blob,"
|
||||
" pending tinyint,"
|
||||
" removed_height int,"
|
||||
" lineage_proof blob,"
|
||||
" custom_data blob)"
|
||||
)
|
||||
)
|
||||
|
||||
await conn.execute("CREATE INDEX IF NOT EXISTS removed_height_index on singletons(removed_height)")
|
||||
|
||||
return self
|
||||
|
||||
async def save_singleton(self, record: SingletonRecord) -> None:
|
||||
singleton_id = singleton.get_singleton_id_from_puzzle(record.parent_coinspend.puzzle_reveal)
|
||||
if singleton_id is None:
|
||||
raise RuntimeError(
|
||||
"Failed to derive Singleton ID from puzzle reveal in parent spend %s", record.parent_coinspend
|
||||
)
|
||||
pending_int = 0
|
||||
if record.pending:
|
||||
pending_int = 1
|
||||
async with self.db_wrapper.writer_maybe_transaction() as conn:
|
||||
columns = (
|
||||
"coin_id, coin, singleton_id, wallet_id, parent_coin_spend, inner_puzzle_hash, "
|
||||
"pending, removed_height, lineage_proof, custom_data"
|
||||
)
|
||||
await conn.execute(
|
||||
f"INSERT or REPLACE INTO singletons ({columns}) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
(
|
||||
record.coin.name().hex(),
|
||||
json.dumps(record.coin.to_json_dict()),
|
||||
singleton_id.hex(),
|
||||
record.wallet_id,
|
||||
bytes(record.parent_coinspend),
|
||||
record.inner_puzzle_hash,
|
||||
pending_int,
|
||||
record.removed_height,
|
||||
bytes(record.lineage_proof),
|
||||
record.custom_data,
|
||||
),
|
||||
)
|
||||
|
||||
async def add_spend(
|
||||
self,
|
||||
wallet_id: uint32,
|
||||
coin_state: CoinSpend,
|
||||
block_height: uint32 = uint32(0),
|
||||
pending: bool = True,
|
||||
) -> None:
|
||||
"""Given a coin spend of a singleton, attempt to calculate the child coin and details
|
||||
for the new singleton record. Add the new record to the store and remove the old record
|
||||
if it exists
|
||||
"""
|
||||
# get singleton_id from puzzle_reveal
|
||||
singleton_id = get_singleton_id_from_puzzle(coin_state.puzzle_reveal)
|
||||
if not singleton_id:
|
||||
raise RuntimeError("Coin to add is not a valid singleton")
|
||||
|
||||
# get details for singleton record
|
||||
conditions = conditions_dict_for_solution(
|
||||
coin_state.puzzle_reveal.to_program(),
|
||||
coin_state.solution.to_program(),
|
||||
DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM,
|
||||
)
|
||||
if conditions is None:
|
||||
raise RuntimeError("Failed to add spend for coin: %s ", coin_state.coin.name())
|
||||
|
||||
cc_cond = [cond for cond in conditions[ConditionOpcode.CREATE_COIN] if int_from_bytes(cond.vars[1]) % 2 == 1][0]
|
||||
|
||||
coin = Coin(coin_state.coin.name(), cc_cond.vars[0], int_from_bytes(cc_cond.vars[1]))
|
||||
inner_puz = get_inner_puzzle_from_singleton(coin_state.puzzle_reveal)
|
||||
if inner_puz is None:
|
||||
raise RuntimeError("Could not get inner puzzle from puzzle reveal in coin spend %s", coin_state)
|
||||
|
||||
lineage_bytes = [x.as_atom() for x in coin_state.solution.to_program().first().as_iter()]
|
||||
if len(lineage_bytes) == 2:
|
||||
lineage_proof = LineageProof(lineage_bytes[0], None, int_from_bytes(lineage_bytes[1]))
|
||||
else:
|
||||
lineage_proof = LineageProof(lineage_bytes[0], lineage_bytes[1], int_from_bytes(lineage_bytes[2]))
|
||||
# Create and save the new singleton record
|
||||
new_record = SingletonRecord(
|
||||
coin, singleton_id, wallet_id, coin_state, inner_puz.get_tree_hash(), pending, 0, lineage_proof, None
|
||||
)
|
||||
await self.save_singleton(new_record)
|
||||
# check if coin is in DB and mark deleted if found
|
||||
current_records = await self.get_records_by_coin_id(coin_state.coin.name())
|
||||
if len(current_records) > 0:
|
||||
await self.delete_singleton_by_coin_id(coin_state.coin.name(), block_height)
|
||||
return
|
||||
|
||||
def _to_singleton_record(self, row: Row) -> SingletonRecord:
|
||||
return SingletonRecord(
|
||||
coin=Coin.from_json_dict(json.loads(row[1])),
|
||||
singleton_id=bytes32.from_hexstr(row[2]),
|
||||
wallet_id=uint32(row[3]),
|
||||
parent_coinspend=CoinSpend.from_bytes(row[4]),
|
||||
inner_puzzle_hash=bytes32.from_bytes(row[5]), # inner puz hash
|
||||
pending=True if row[6] == 1 else False,
|
||||
removed_height=uint32(row[7]),
|
||||
lineage_proof=LineageProof.from_bytes(row[8]),
|
||||
custom_data=row[9],
|
||||
)
|
||||
|
||||
async def delete_singleton_by_singleton_id(self, singleton_id: bytes32, height: uint32) -> bool:
|
||||
"""Tries to mark a given singleton as deleted at specific height
|
||||
|
||||
This is due to how re-org works
|
||||
Returns `True` if singleton was found and marked deleted or `False` if not."""
|
||||
async with self.db_wrapper.writer_maybe_transaction() as conn:
|
||||
# Remove NFT in the users_nfts table
|
||||
cursor = await conn.execute(
|
||||
"UPDATE singletons SET removed_height=? WHERE singleton_id=?", (int(height), singleton_id.hex())
|
||||
)
|
||||
return cursor.rowcount > 0
|
||||
|
||||
async def delete_singleton_by_coin_id(self, coin_id: bytes32, height: uint32) -> bool:
|
||||
"""Tries to mark a given singleton as deleted at specific height
|
||||
|
||||
This is due to how re-org works
|
||||
Returns `True` if singleton was found and marked deleted or `False` if not."""
|
||||
async with self.db_wrapper.writer_maybe_transaction() as conn:
|
||||
# Remove NFT in the users_nfts table
|
||||
cursor = await conn.execute(
|
||||
"UPDATE singletons SET removed_height=? WHERE coin_id=?", (int(height), coin_id.hex())
|
||||
)
|
||||
if cursor.rowcount > 0:
|
||||
log.info("Deleted singleton with coin id: %s", coin_id.hex())
|
||||
return True
|
||||
log.warning("Couldn't find singleton with coin id to delete: %s", coin_id)
|
||||
return False
|
||||
|
||||
async def update_pending_transaction(self, coin_id: bytes32, pending: bool) -> bool:
|
||||
async with self.db_wrapper.writer_maybe_transaction() as conn:
|
||||
c = await conn.execute(
|
||||
"UPDATE singletons SET pending=? WHERE coin_id = ?",
|
||||
(pending, coin_id.hex()),
|
||||
)
|
||||
return c.rowcount > 0
|
||||
|
||||
async def get_records_by_wallet_id(self, wallet_id: int) -> List[SingletonRecord]:
|
||||
"""
|
||||
Retrieves all entries for a wallet ID.
|
||||
"""
|
||||
|
||||
async with self.db_wrapper.reader_no_transaction() as conn:
|
||||
rows = await conn.execute_fetchall(
|
||||
"SELECT * FROM singletons WHERE wallet_id = ? ORDER BY removed_height",
|
||||
(wallet_id,),
|
||||
)
|
||||
return [self._to_singleton_record(row) for row in rows]
|
||||
|
||||
async def get_records_by_coin_id(self, coin_id: bytes32) -> List[SingletonRecord]:
|
||||
"""
|
||||
Retrieves all entries for a coin ID.
|
||||
"""
|
||||
|
||||
async with self.db_wrapper.reader_no_transaction() as conn:
|
||||
rows = await conn.execute_fetchall(
|
||||
"SELECT * FROM singletons WHERE coin_id = ?",
|
||||
(coin_id.hex(),),
|
||||
)
|
||||
return [self._to_singleton_record(row) for row in rows]
|
||||
|
||||
async def get_records_by_singleton_id(self, singleton_id: bytes32) -> List[SingletonRecord]:
|
||||
"""
|
||||
Retrieves all entries for a singleton ID.
|
||||
"""
|
||||
|
||||
async with self.db_wrapper.reader_no_transaction() as conn:
|
||||
rows = await conn.execute_fetchall(
|
||||
"SELECT * FROM singletons WHERE singleton_id = ? ORDER BY removed_height",
|
||||
(singleton_id.hex(),),
|
||||
)
|
||||
return [self._to_singleton_record(row) for row in rows]
|
||||
|
||||
async def rollback(self, height: int, wallet_id_arg: int) -> None:
|
||||
"""
|
||||
Rollback removes all entries which have entry_height > height passed in. Note that this is not committed to the
|
||||
DB until db_wrapper.commit() is called. However, it is written to the cache, so it can be fetched with
|
||||
get_all_state_transitions.
|
||||
"""
|
||||
|
||||
async with self.db_wrapper.writer_maybe_transaction() as conn:
|
||||
cursor = await conn.execute(
|
||||
"DELETE FROM singletons WHERE removed_height>? AND wallet_id=?", (height, wallet_id_arg)
|
||||
)
|
||||
await cursor.close()
|
|
@ -8,7 +8,20 @@ import traceback
|
|||
from contextlib import asynccontextmanager
|
||||
from pathlib import Path
|
||||
from secrets import token_bytes
|
||||
from typing import TYPE_CHECKING, Any, AsyncIterator, Callable, Dict, Iterator, List, Optional, Set, Type, TypeVar
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
AsyncIterator,
|
||||
Callable,
|
||||
Dict,
|
||||
Iterator,
|
||||
List,
|
||||
Optional,
|
||||
Set,
|
||||
Type,
|
||||
TypeVar,
|
||||
Union,
|
||||
)
|
||||
|
||||
import aiosqlite
|
||||
from blspy import G1Element, G2Element, PrivateKey
|
||||
|
@ -49,6 +62,16 @@ from chia.util.path import path_from_root
|
|||
from chia.wallet.cat_wallet.cat_constants import DEFAULT_CATS
|
||||
from chia.wallet.cat_wallet.cat_utils import CAT_MOD, CAT_MOD_HASH, construct_cat_puzzle, match_cat_puzzle
|
||||
from chia.wallet.cat_wallet.cat_wallet import CATWallet
|
||||
from chia.wallet.cat_wallet.dao_cat_wallet import DAOCATWallet
|
||||
from chia.wallet.dao_wallet.dao_utils import (
|
||||
get_new_puzzle_from_treasury_solution,
|
||||
match_dao_cat_puzzle,
|
||||
match_finished_puzzle,
|
||||
match_funding_puzzle,
|
||||
match_proposal_puzzle,
|
||||
match_treasury_puzzle,
|
||||
)
|
||||
from chia.wallet.dao_wallet.dao_wallet import DAOWallet
|
||||
from chia.wallet.db_wallet.db_wallet_puzzles import MIRROR_PUZZLE_HASH
|
||||
from chia.wallet.derivation_record import DerivationRecord
|
||||
from chia.wallet.derive_keys import (
|
||||
|
@ -71,7 +94,7 @@ from chia.wallet.payment import Payment
|
|||
from chia.wallet.puzzle_drivers import PuzzleInfo
|
||||
from chia.wallet.puzzles.clawback.drivers import generate_clawback_spend_bundle, match_clawback_puzzle
|
||||
from chia.wallet.puzzles.clawback.metadata import ClawbackMetadata, ClawbackVersion
|
||||
from chia.wallet.singleton import create_singleton_puzzle
|
||||
from chia.wallet.singleton import create_singleton_puzzle, get_singleton_id_from_puzzle
|
||||
from chia.wallet.trade_manager import TradeManager
|
||||
from chia.wallet.trading.trade_status import TradeStatus
|
||||
from chia.wallet.transaction_record import TransactionRecord
|
||||
|
@ -102,6 +125,7 @@ from chia.wallet.wallet_pool_store import WalletPoolStore
|
|||
from chia.wallet.wallet_protocol import WalletProtocol
|
||||
from chia.wallet.wallet_puzzle_store import WalletPuzzleStore
|
||||
from chia.wallet.wallet_retry_store import WalletRetryStore
|
||||
from chia.wallet.wallet_singleton_store import WalletSingletonStore
|
||||
from chia.wallet.wallet_transaction_store import WalletTransactionStore
|
||||
from chia.wallet.wallet_user_store import WalletUserStore
|
||||
|
||||
|
@ -115,6 +139,8 @@ PendingTxCallback = Callable[[], None]
|
|||
|
||||
|
||||
class WalletStateManager:
|
||||
interested_ph_cache: Dict[bytes32, List[int]] = {}
|
||||
interested_coin_cache: Dict[bytes32, List[int]] = {}
|
||||
constants: ConsensusConstants
|
||||
config: Dict[str, Any]
|
||||
tx_store: WalletTransactionStore
|
||||
|
@ -153,6 +179,7 @@ class WalletStateManager:
|
|||
wallet_node: WalletNode
|
||||
pool_store: WalletPoolStore
|
||||
dl_store: DataLayerStore
|
||||
singleton_store: WalletSingletonStore
|
||||
default_cats: Dict[str, Any]
|
||||
asset_to_wallet_map: Dict[AssetType, Any]
|
||||
initial_num_public_keys: int
|
||||
|
@ -169,6 +196,7 @@ class WalletStateManager:
|
|||
wallet_node: WalletNode,
|
||||
) -> WalletStateManager:
|
||||
self = WalletStateManager()
|
||||
|
||||
self.config = config
|
||||
self.constants = constants
|
||||
self.server = server
|
||||
|
@ -207,6 +235,7 @@ class WalletStateManager:
|
|||
self.dl_store = await DataLayerStore.create(self.db_wrapper)
|
||||
self.interested_store = await WalletInterestedStore.create(self.db_wrapper)
|
||||
self.retry_store = await WalletRetryStore.create(self.db_wrapper)
|
||||
self.singleton_store = await WalletSingletonStore.create(self.db_wrapper)
|
||||
self.default_cats = DEFAULT_CATS
|
||||
|
||||
self.wallet_node = wallet_node
|
||||
|
@ -262,7 +291,22 @@ class WalletStateManager:
|
|||
wallet_info,
|
||||
)
|
||||
elif wallet_type == WalletType.DATA_LAYER:
|
||||
wallet = await DataLayerWallet.create(self, wallet_info)
|
||||
wallet = await DataLayerWallet.create(
|
||||
self,
|
||||
wallet_info,
|
||||
)
|
||||
elif wallet_type == WalletType.DAO:
|
||||
wallet = await DAOWallet.create(
|
||||
self,
|
||||
self.main_wallet,
|
||||
wallet_info,
|
||||
)
|
||||
elif wallet_type == WalletType.DAO_CAT:
|
||||
wallet = await DAOCATWallet.create(
|
||||
self,
|
||||
self.main_wallet,
|
||||
wallet_info,
|
||||
)
|
||||
elif wallet_type == WalletType.VC: # pragma: no cover
|
||||
wallet = await VCWallet.create(
|
||||
self,
|
||||
|
@ -647,7 +691,6 @@ class WalletStateManager:
|
|||
or self.is_farmer_reward(uint32(coin_state.created_height), coin_state.coin)
|
||||
):
|
||||
return None
|
||||
|
||||
response: List[CoinState] = await self.wallet_node.get_coin_state(
|
||||
[coin_state.coin.parent_coin_info], peer=peer, fork_height=fork_height
|
||||
)
|
||||
|
@ -662,9 +705,33 @@ class WalletStateManager:
|
|||
return None
|
||||
|
||||
puzzle = Program.from_bytes(bytes(coin_spend.puzzle_reveal))
|
||||
solution = Program.from_bytes(bytes(coin_spend.solution))
|
||||
|
||||
uncurried = uncurry_puzzle(puzzle)
|
||||
|
||||
# Check if the coin is a DAO Treasury
|
||||
dao_curried_args = match_treasury_puzzle(uncurried.mod, uncurried.args)
|
||||
if dao_curried_args is not None:
|
||||
return await self.handle_dao_treasury(dao_curried_args, parent_coin_state, coin_state, coin_spend)
|
||||
# Check if the coin is a Proposal and that it isn't the timer coin (amount == 0)
|
||||
dao_curried_args = match_proposal_puzzle(uncurried.mod, uncurried.args)
|
||||
if (dao_curried_args is not None) and (coin_state.coin.amount != 0):
|
||||
return await self.handle_dao_proposal(dao_curried_args, parent_coin_state, coin_state, coin_spend)
|
||||
|
||||
# Check if the coin is a finished proposal
|
||||
dao_curried_args = match_finished_puzzle(uncurried.mod, uncurried.args)
|
||||
if dao_curried_args is not None:
|
||||
return await self.handle_dao_finished_proposals(dao_curried_args, parent_coin_state, coin_state, coin_spend)
|
||||
|
||||
# Check if the coin is a DAO CAT
|
||||
dao_cat_args = match_dao_cat_puzzle(uncurried)
|
||||
if dao_cat_args:
|
||||
return await self.handle_dao_cat(dao_cat_args, parent_coin_state, coin_state, coin_spend)
|
||||
|
||||
funding_puzzle_check = match_funding_puzzle(uncurried, solution)
|
||||
if funding_puzzle_check:
|
||||
return await self.get_dao_wallet_from_coinspend_hint(coin_spend)
|
||||
|
||||
# Check if the coin is a CAT
|
||||
cat_curried_args = match_cat_puzzle(uncurried)
|
||||
if cat_curried_args is not None:
|
||||
|
@ -776,6 +843,8 @@ class WalletStateManager:
|
|||
)
|
||||
coin_spend: CoinSpend = generate_clawback_spend_bundle(coin, metadata, inner_puzzle, inner_solution)
|
||||
coin_spends.append(coin_spend)
|
||||
# Update incoming tx to prevent double spend and mark it is pending
|
||||
await self.tx_store.increment_sent(incoming_tx.name, "", MempoolInclusionStatus.PENDING, None)
|
||||
except Exception as e:
|
||||
self.log.error(f"Failed to create clawback spend bundle for {coin.name().hex()}: {e}")
|
||||
if len(coin_spends) == 0:
|
||||
|
@ -807,9 +876,6 @@ class WalletStateManager:
|
|||
memos=list(compute_memos(spend_bundle).items()),
|
||||
)
|
||||
await self.add_pending_transaction(tx_record)
|
||||
# Update incoming tx to prevent double spend and mark it is pending
|
||||
for coin_spend in coin_spends:
|
||||
await self.tx_store.increment_sent(coin_spend.coin.name(), "", MempoolInclusionStatus.PENDING, None)
|
||||
return [tx_record.name]
|
||||
|
||||
async def filter_spam(self, new_coin_state: List[CoinState]) -> List[CoinState]:
|
||||
|
@ -846,6 +912,25 @@ class WalletStateManager:
|
|||
wallet_identifier = await self.get_wallet_identifier_for_puzzle_hash(coin_state.coin.puzzle_hash)
|
||||
return wallet_identifier is not None and wallet_identifier.type == WalletType.STANDARD_WALLET
|
||||
|
||||
async def handle_dao_cat(
|
||||
self,
|
||||
curried_args: Iterator[Program],
|
||||
parent_coin_state: CoinState,
|
||||
coin_state: CoinState,
|
||||
coin_spend: CoinSpend,
|
||||
) -> Optional[WalletIdentifier]:
|
||||
"""
|
||||
Handle the new coin when it is a DAO CAT
|
||||
"""
|
||||
mod_hash, tail_hash, inner_puzzle = curried_args
|
||||
asset_id: bytes32 = bytes32(bytes(tail_hash)[1:])
|
||||
for wallet in self.wallets.values():
|
||||
if wallet.type() == WalletType.DAO_CAT:
|
||||
assert isinstance(wallet, DAOCATWallet)
|
||||
if wallet.dao_cat_info.limitations_program_hash == asset_id:
|
||||
return WalletIdentifier.create(wallet)
|
||||
return None
|
||||
|
||||
async def handle_cat(
|
||||
self,
|
||||
curried_args: Iterator[Program],
|
||||
|
@ -871,7 +956,6 @@ class WalletStateManager:
|
|||
derivation_record = await self.puzzle_store.get_derivation_record_for_puzzle_hash(bytes32(hint))
|
||||
if derivation_record is not None:
|
||||
break
|
||||
|
||||
if derivation_record is None:
|
||||
self.log.info(f"Received state for the coin that doesn't belong to us {coin_state}")
|
||||
return None
|
||||
|
@ -936,6 +1020,7 @@ class WalletStateManager:
|
|||
if derivation_record is None:
|
||||
self.log.info(f"Received state for the coin that doesn't belong to us {coin_state}")
|
||||
# Check if it was owned by us
|
||||
# If the puzzle inside is no longer recognised then delete the wallet associated
|
||||
removed_wallet_ids = []
|
||||
for wallet in self.wallets.values():
|
||||
if not isinstance(wallet, DIDWallet):
|
||||
|
@ -1033,6 +1118,98 @@ class WalletStateManager:
|
|||
minter_did = bytes32(bytes(singleton_struct.rest().first())[1:])
|
||||
return minter_did
|
||||
|
||||
async def handle_dao_treasury(
|
||||
self,
|
||||
uncurried_args: Iterator[Program],
|
||||
parent_coin_state: CoinState,
|
||||
coin_state: CoinState,
|
||||
coin_spend: CoinSpend,
|
||||
) -> Optional[WalletIdentifier]:
|
||||
self.log.info("Entering dao_treasury handling in WalletStateManager")
|
||||
singleton_id = get_singleton_id_from_puzzle(coin_spend.puzzle_reveal)
|
||||
for wallet in self.wallets.values():
|
||||
if wallet.type() == WalletType.DAO:
|
||||
assert isinstance(wallet, DAOWallet)
|
||||
if wallet.dao_info.treasury_id == singleton_id:
|
||||
return WalletIdentifier.create(wallet)
|
||||
|
||||
inner_puzzle = get_new_puzzle_from_treasury_solution(
|
||||
coin_spend.puzzle_reveal.to_program(), coin_spend.solution.to_program()
|
||||
)
|
||||
# If we can't find the wallet for this DAO but we've got here because we're subscribed, then create the wallet
|
||||
assert isinstance(inner_puzzle, Program)
|
||||
assert isinstance(singleton_id, bytes32)
|
||||
dao_wallet = await DAOWallet.create_new_dao_wallet_for_existing_dao(
|
||||
self,
|
||||
self.main_wallet,
|
||||
singleton_id,
|
||||
)
|
||||
assert dao_wallet is not None
|
||||
return None
|
||||
|
||||
async def handle_dao_proposal(
|
||||
self,
|
||||
uncurried_args: Iterator[Program],
|
||||
parent_coin_state: CoinState,
|
||||
coin_state: CoinState,
|
||||
coin_spend: CoinSpend,
|
||||
) -> Optional[WalletIdentifier]:
|
||||
(
|
||||
SINGLETON_STRUCT, # (SINGLETON_MOD_HASH, (SINGLETON_ID, LAUNCHER_PUZZLE_HASH))
|
||||
PROPOSAL_MOD_HASH,
|
||||
PROPOSAL_TIMER_MOD_HASH,
|
||||
CAT_MOD_HASH,
|
||||
DAO_FINISHED_STATE_MOD_HASH,
|
||||
TREASURY_MOD_HASH,
|
||||
LOCKUP_MOD_HASH,
|
||||
CAT_TAIL_HASH,
|
||||
TREASURY_ID,
|
||||
YES_VOTES, # yes votes are +1, no votes don't tally - we compare yes_votes/total_votes at the end
|
||||
TOTAL_VOTES, # how many people responded
|
||||
INNERPUZHASH,
|
||||
) = uncurried_args
|
||||
for wallet in self.wallets.values():
|
||||
if wallet.type() == WalletType.DAO:
|
||||
assert isinstance(wallet, DAOWallet)
|
||||
if wallet.dao_info.treasury_id == TREASURY_ID.as_atom():
|
||||
assert isinstance(coin_state.created_height, int)
|
||||
await wallet.add_or_update_proposal_info(coin_spend, uint32(coin_state.created_height))
|
||||
return WalletIdentifier.create(wallet)
|
||||
return None
|
||||
|
||||
async def handle_dao_finished_proposals(
|
||||
self,
|
||||
uncurried_args: Iterator[Program],
|
||||
parent_coin_state: CoinState,
|
||||
coin_state: CoinState,
|
||||
coin_spend: CoinSpend,
|
||||
) -> Optional[WalletIdentifier]:
|
||||
if coin_state.created_height is None:
|
||||
raise ValueError("coin_state argument to handle_dao_finished_proposals cannot have created_height of None")
|
||||
(
|
||||
SINGLETON_STRUCT, # (SINGLETON_MOD_HASH, (SINGLETON_ID, LAUNCHER_PUZZLE_HASH))
|
||||
FINISHED_STATE_MOD_HASH,
|
||||
) = uncurried_args
|
||||
proposal_id = SINGLETON_STRUCT.rest().first().as_atom()
|
||||
for wallet in self.wallets.values():
|
||||
if wallet.type() == WalletType.DAO:
|
||||
assert isinstance(wallet, DAOWallet)
|
||||
for proposal_info in wallet.dao_info.proposals_list:
|
||||
if proposal_info.proposal_id == proposal_id:
|
||||
await wallet.add_or_update_proposal_info(coin_spend, uint32(coin_state.created_height))
|
||||
return WalletIdentifier.create(wallet)
|
||||
return None
|
||||
|
||||
async def get_dao_wallet_from_coinspend_hint(self, coin_spend: CoinSpend) -> Optional[WalletIdentifier]:
|
||||
hint_list = compute_coin_hints(coin_spend)
|
||||
if hint_list:
|
||||
for wallet in self.wallets.values():
|
||||
if wallet.type() == WalletType.DAO.value:
|
||||
assert isinstance(wallet, DAOWallet)
|
||||
if wallet.dao_info.treasury_id in hint_list:
|
||||
return WalletIdentifier.create(wallet)
|
||||
return None
|
||||
|
||||
async def handle_nft(
|
||||
self, coin_spend: CoinSpend, uncurried_nft: UncurriedNFT, parent_coin_state: CoinState, coin_state: CoinState
|
||||
) -> Optional[WalletIdentifier]:
|
||||
|
@ -1455,6 +1632,7 @@ class WalletStateManager:
|
|||
if record.coin_type == CoinType.CLAWBACK:
|
||||
await self.interested_store.remove_interested_coin_id(coin_state.coin.name())
|
||||
confirmed_tx_records: List[TransactionRecord] = []
|
||||
|
||||
for tx_record in all_unconfirmed:
|
||||
if tx_record.type in CLAWBACK_TRANSACTION_TYPES:
|
||||
for add_coin in tx_record.additions:
|
||||
|
@ -1475,14 +1653,17 @@ class WalletStateManager:
|
|||
unconfirmed_record.name, uint32(coin_state.spent_height)
|
||||
)
|
||||
|
||||
if record.wallet_type == WalletType.POOLING_WALLET:
|
||||
if record.wallet_type in [WalletType.POOLING_WALLET, WalletType.DAO]:
|
||||
wallet_type_to_class = {WalletType.POOLING_WALLET: PoolWallet, WalletType.DAO: DAOWallet}
|
||||
if coin_state.spent_height is not None and coin_state.coin.amount == uint64(1):
|
||||
pool_wallet = self.get_wallet(id=uint32(record.wallet_id), required_type=PoolWallet)
|
||||
singleton_wallet: Union[PoolWallet, DAOWallet] = self.get_wallet(
|
||||
id=uint32(record.wallet_id), required_type=wallet_type_to_class[record.wallet_type]
|
||||
)
|
||||
curr_coin_state: CoinState = coin_state
|
||||
|
||||
while curr_coin_state.spent_height is not None:
|
||||
cs = await fetch_coin_spend_for_coin_state(curr_coin_state, peer)
|
||||
success = await pool_wallet.apply_state_transition(
|
||||
cs: CoinSpend = await fetch_coin_spend_for_coin_state(curr_coin_state, peer)
|
||||
success = await singleton_wallet.apply_state_transition(
|
||||
cs, uint32(curr_coin_state.spent_height)
|
||||
)
|
||||
if not success:
|
||||
|
@ -1744,10 +1925,14 @@ class WalletStateManager:
|
|||
coin_record: WalletCoinRecord = WalletCoinRecord(
|
||||
coin, height, uint32(0), False, coinbase, wallet_type, wallet_id
|
||||
)
|
||||
|
||||
await self.coin_store.add_coin_record(coin_record, coin_name)
|
||||
|
||||
await self.wallets[wallet_id].coin_added(coin, height, peer)
|
||||
|
||||
if wallet_type == WalletType.DAO:
|
||||
return
|
||||
|
||||
await self.create_more_puzzle_hashes()
|
||||
|
||||
async def add_pending_transaction(self, tx_record: TransactionRecord) -> None:
|
||||
|
@ -1971,22 +2156,43 @@ class WalletStateManager:
|
|||
return filtered
|
||||
|
||||
async def new_peak(self, peak: NewPeakWallet) -> None:
|
||||
valid_list = [WalletType.POOLING_WALLET, WalletType.DAO]
|
||||
for wallet_id, wallet in self.wallets.items():
|
||||
if wallet.type() == WalletType.POOLING_WALLET:
|
||||
assert isinstance(wallet, PoolWallet)
|
||||
await wallet.new_peak(uint64(peak.height))
|
||||
if wallet.type() in valid_list:
|
||||
valid = isinstance(wallet, PoolWallet) or isinstance(wallet, DAOWallet)
|
||||
assert valid
|
||||
await wallet.new_peak(uint64(peak.height)) # type: ignore[attr-defined]
|
||||
current_time = int(time.time())
|
||||
|
||||
if self.wallet_node.last_wallet_tx_resend_time < current_time - self.wallet_node.wallet_tx_resend_timeout_secs:
|
||||
self.tx_pending_changed()
|
||||
|
||||
async def add_interested_puzzle_hashes(self, puzzle_hashes: List[bytes32], wallet_ids: List[int]) -> None:
|
||||
# TODO: It's unclear if the intended use for this is that each puzzle hash should store all
|
||||
# the elements of wallet_ids. It only stores one wallet_id per puzzle hash in the interested_store
|
||||
# but the coin_cache keeps all wallet_ids for each puzzle hash
|
||||
for puzzle_hash in puzzle_hashes:
|
||||
if puzzle_hash in self.interested_coin_cache:
|
||||
wallet_ids_to_add = list(
|
||||
set([w for w in wallet_ids if w not in self.interested_coin_cache[puzzle_hash]])
|
||||
)
|
||||
self.interested_coin_cache[puzzle_hash].extend(wallet_ids_to_add)
|
||||
else:
|
||||
self.interested_coin_cache[puzzle_hash] = list(set(wallet_ids))
|
||||
for puzzle_hash, wallet_id in zip(puzzle_hashes, wallet_ids):
|
||||
await self.interested_store.add_interested_puzzle_hash(puzzle_hash, wallet_id)
|
||||
if len(puzzle_hashes) > 0:
|
||||
await self.wallet_node.new_peak_queue.subscribe_to_puzzle_hashes(puzzle_hashes)
|
||||
|
||||
async def add_interested_coin_ids(self, coin_ids: List[bytes32]) -> None:
|
||||
async def add_interested_coin_ids(self, coin_ids: List[bytes32], wallet_ids: List[int] = []) -> None:
|
||||
# TODO: FIX: wallet_ids is sometimes populated unexpectedly when called from add_pending_transaction
|
||||
for coin_id in coin_ids:
|
||||
if coin_id in self.interested_coin_cache:
|
||||
# prevent repeated wallet_ids from appearing in the coin cache
|
||||
wallet_ids_to_add = list(set([w for w in wallet_ids if w not in self.interested_coin_cache[coin_id]]))
|
||||
self.interested_coin_cache[coin_id].extend(wallet_ids_to_add)
|
||||
else:
|
||||
self.interested_coin_cache[coin_id] = list(set(wallet_ids))
|
||||
for coin_id in coin_ids:
|
||||
await self.interested_store.add_interested_coin_id(coin_id)
|
||||
if len(coin_ids) > 0:
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
#!/bin/bash
|
||||
|
||||
clear
|
||||
|
||||
python3 -c "import sys; sys.exit(sys.prefix == sys.base_prefix)" || { echo "Not in a Python venv. Run '. ./activate'"; exit 1; }
|
||||
venv_dir=$(python3 -c "import sys; print(sys.prefix);")
|
||||
|
||||
python3 -m pip install --upgrade pip mypy black flake8 isort | grep -v 'Requirement already satisfied:'
|
||||
|
||||
paths=$(git diff --name-only main | egrep 'py$')
|
||||
|
||||
${venv_dir}/bin/isort $paths
|
||||
${venv_dir}/bin/black $paths
|
||||
${venv_dir}/bin/flake8 --exclude venv $paths
|
||||
${venv_dir}/bin/mypy $paths
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
; logging options
|
||||
log_cli = False
|
||||
addopts = --verbose --tb=short -n auto -p no:monitor
|
||||
log_level = WARNING
|
||||
log_level = INFO
|
||||
console_output_style = count
|
||||
log_format = %(asctime)s %(name)s: %(levelname)s %(message)s
|
||||
asyncio_mode = strict
|
||||
|
|
|
@ -16,7 +16,6 @@ from chia.pools.pool_puzzles import (
|
|||
create_travel_spend,
|
||||
create_waiting_room_inner_puzzle,
|
||||
get_delayed_puz_info_from_launcher_spend,
|
||||
get_most_recent_singleton_coin_from_coin_spend,
|
||||
get_pubkey_from_member_inner_puzzle,
|
||||
get_seconds_and_delayed_puzhash_from_p2_singleton_puzzle,
|
||||
is_pool_singleton_inner_puzzle,
|
||||
|
@ -39,6 +38,7 @@ from chia.wallet.puzzles.p2_delegated_puzzle_or_hidden_puzzle import (
|
|||
puzzle_for_pk,
|
||||
solution_for_conditions,
|
||||
)
|
||||
from chia.wallet.singleton import get_most_recent_singleton_coin_from_coin_spend
|
||||
from tests.clvm.coin_store import BadSpendBundleError, CoinStore, CoinTimestamp
|
||||
from tests.clvm.test_puzzles import public_key_for_index, secret_exponent_for_index
|
||||
from tests.util.key_tool import KeyTool
|
||||
|
|
|
@ -208,7 +208,7 @@ class TestCATWallet:
|
|||
assert cat_wallet.cat_info.limitations_program_hash == cat_wallet_2.cat_info.limitations_program_hash
|
||||
|
||||
cat_2_hash = await cat_wallet_2.get_new_inner_hash()
|
||||
tx_records = await cat_wallet.generate_signed_transaction([uint64(60)], [cat_2_hash], fee=uint64(1))
|
||||
tx_records = await cat_wallet.generate_signed_transactions([uint64(60)], [cat_2_hash], fee=uint64(1))
|
||||
tx_id = None
|
||||
for tx_record in tx_records:
|
||||
await wallet.wallet_state_manager.add_pending_transaction(tx_record)
|
||||
|
@ -241,7 +241,7 @@ class TestCATWallet:
|
|||
assert len(memos[tx_id]) == 2
|
||||
assert list(memos[tx_id].values())[0][0] == cat_2_hash.hex()
|
||||
cat_hash = await cat_wallet.get_new_inner_hash()
|
||||
tx_records = await cat_wallet_2.generate_signed_transaction([uint64(15)], [cat_hash])
|
||||
tx_records = await cat_wallet_2.generate_signed_transactions([uint64(15)], [cat_hash])
|
||||
for tx_record in tx_records:
|
||||
await wallet.wallet_state_manager.add_pending_transaction(tx_record)
|
||||
|
||||
|
@ -315,7 +315,7 @@ class TestCATWallet:
|
|||
assert cat_wallet.cat_info.limitations_program_hash == cat_wallet_2.cat_info.limitations_program_hash
|
||||
|
||||
cat_2_hash = await cat_wallet_2.get_new_inner_hash()
|
||||
tx_records = await cat_wallet.generate_signed_transaction(
|
||||
tx_records = await cat_wallet.generate_signed_transactions(
|
||||
[uint64(60)], [cat_2_hash], fee=uint64(1), reuse_puzhash=True
|
||||
)
|
||||
for tx_record in tx_records:
|
||||
|
@ -345,7 +345,7 @@ class TestCATWallet:
|
|||
await time_out_assert(30, cat_wallet_2.get_unconfirmed_balance, 60)
|
||||
|
||||
cat_hash = await cat_wallet.get_new_inner_hash()
|
||||
tx_records = await cat_wallet_2.generate_signed_transaction([uint64(15)], [cat_hash])
|
||||
tx_records = await cat_wallet_2.generate_signed_transactions([uint64(15)], [cat_hash])
|
||||
for tx_record in tx_records:
|
||||
await wallet.wallet_state_manager.add_pending_transaction(tx_record)
|
||||
|
||||
|
@ -471,7 +471,7 @@ class TestCATWallet:
|
|||
assert cat_wallet.cat_info.limitations_program_hash == cat_wallet_2.cat_info.limitations_program_hash
|
||||
|
||||
cat_2_hash = await cat_wallet_2.get_new_inner_hash()
|
||||
tx_records = await cat_wallet.generate_signed_transaction([uint64(60)], [cat_2_hash], fee=uint64(1))
|
||||
tx_records = await cat_wallet.generate_signed_transactions([uint64(60)], [cat_2_hash], fee=uint64(1))
|
||||
for tx_record in tx_records:
|
||||
await wallet.wallet_state_manager.add_pending_transaction(tx_record)
|
||||
await full_node_api.process_transaction_records(records=tx_records)
|
||||
|
@ -568,7 +568,7 @@ class TestCATWallet:
|
|||
cat_1_hash = await cat_wallet_1.get_new_inner_hash()
|
||||
cat_2_hash = await cat_wallet_2.get_new_inner_hash()
|
||||
|
||||
tx_records = await cat_wallet_0.generate_signed_transaction([uint64(60), uint64(20)], [cat_1_hash, cat_2_hash])
|
||||
tx_records = await cat_wallet_0.generate_signed_transactions([uint64(60), uint64(20)], [cat_1_hash, cat_2_hash])
|
||||
for tx_record in tx_records:
|
||||
await wallet_0.wallet_state_manager.add_pending_transaction(tx_record)
|
||||
await full_node_api.process_transaction_records(records=tx_records)
|
||||
|
@ -584,11 +584,11 @@ class TestCATWallet:
|
|||
|
||||
cat_hash = await cat_wallet_0.get_new_inner_hash()
|
||||
|
||||
tx_records = await cat_wallet_1.generate_signed_transaction([uint64(15)], [cat_hash])
|
||||
tx_records = await cat_wallet_1.generate_signed_transactions([uint64(15)], [cat_hash])
|
||||
for tx_record in tx_records:
|
||||
await wallet_1.wallet_state_manager.add_pending_transaction(tx_record)
|
||||
|
||||
tx_records_2 = await cat_wallet_2.generate_signed_transaction([uint64(20)], [cat_hash])
|
||||
tx_records_2 = await cat_wallet_2.generate_signed_transactions([uint64(20)], [cat_hash])
|
||||
for tx_record in tx_records_2:
|
||||
await wallet_2.wallet_state_manager.add_pending_transaction(tx_record)
|
||||
|
||||
|
@ -606,11 +606,11 @@ class TestCATWallet:
|
|||
txs = await wallet_1.wallet_state_manager.tx_store.get_transactions_between(cat_wallet_1.id(), 0, 100000)
|
||||
print(len(txs))
|
||||
# Test with Memo
|
||||
tx_records_3: TransactionRecord = await cat_wallet_1.generate_signed_transaction(
|
||||
tx_records_3: TransactionRecord = await cat_wallet_1.generate_signed_transactions(
|
||||
[uint64(30)], [cat_hash], memos=[[b"Markus Walburg"]]
|
||||
)
|
||||
with pytest.raises(ValueError):
|
||||
await cat_wallet_1.generate_signed_transaction(
|
||||
await cat_wallet_1.generate_signed_transactions(
|
||||
[uint64(30)], [cat_hash], memos=[[b"too"], [b"many"], [b"memos"]]
|
||||
)
|
||||
|
||||
|
@ -682,7 +682,7 @@ class TestCATWallet:
|
|||
amounts.append(uint64(i))
|
||||
puzzle_hashes.append(cat_2_hash)
|
||||
spent_coint = (await cat_wallet.get_cat_spendable_coins())[0].coin
|
||||
tx_records = await cat_wallet.generate_signed_transaction(amounts, puzzle_hashes, coins={spent_coint})
|
||||
tx_records = await cat_wallet.generate_signed_transactions(amounts, puzzle_hashes, coins={spent_coint})
|
||||
for tx_record in tx_records:
|
||||
await wallet.wallet_state_manager.add_pending_transaction(tx_record)
|
||||
await full_node_api.process_transaction_records(records=tx_records)
|
||||
|
@ -708,7 +708,7 @@ class TestCATWallet:
|
|||
max_sent_amount = await cat_wallet.get_max_send_amount()
|
||||
|
||||
# 1) Generate transaction that is under the limit
|
||||
[transaction_record] = await cat_wallet.generate_signed_transaction(
|
||||
[transaction_record] = await cat_wallet.generate_signed_transactions(
|
||||
[max_sent_amount - 1],
|
||||
[ph],
|
||||
)
|
||||
|
@ -716,7 +716,7 @@ class TestCATWallet:
|
|||
assert transaction_record.amount == uint64(max_sent_amount - 1)
|
||||
|
||||
# 2) Generate transaction that is equal to limit
|
||||
[transaction_record] = await cat_wallet.generate_signed_transaction(
|
||||
[transaction_record] = await cat_wallet.generate_signed_transactions(
|
||||
[max_sent_amount],
|
||||
[ph],
|
||||
)
|
||||
|
@ -725,7 +725,7 @@ class TestCATWallet:
|
|||
|
||||
# 3) Generate transaction that is greater than limit
|
||||
with pytest.raises(ValueError):
|
||||
await cat_wallet.generate_signed_transaction(
|
||||
await cat_wallet.generate_signed_transactions(
|
||||
[max_sent_amount + 1],
|
||||
[ph],
|
||||
)
|
||||
|
@ -786,7 +786,7 @@ class TestCATWallet:
|
|||
assert cat_wallet.cat_info.limitations_program_hash is not None
|
||||
|
||||
cat_2_hash = await wallet2.get_new_puzzlehash()
|
||||
tx_records = await cat_wallet.generate_signed_transaction([uint64(60)], [cat_2_hash], memos=[[cat_2_hash]])
|
||||
tx_records = await cat_wallet.generate_signed_transactions([uint64(60)], [cat_2_hash], memos=[[cat_2_hash]])
|
||||
|
||||
for tx_record in tx_records:
|
||||
await wallet.wallet_state_manager.add_pending_transaction(tx_record)
|
||||
|
@ -816,7 +816,7 @@ class TestCATWallet:
|
|||
}
|
||||
|
||||
# Then we send another transaction
|
||||
tx_records = await cat_wallet.generate_signed_transaction([uint64(10)], [cat_2_hash], memos=[[cat_2_hash]])
|
||||
tx_records = await cat_wallet.generate_signed_transactions([uint64(10)], [cat_2_hash], memos=[[cat_2_hash]])
|
||||
|
||||
for tx_record in tx_records:
|
||||
await wallet.wallet_state_manager.add_pending_transaction(tx_record)
|
||||
|
@ -835,7 +835,7 @@ class TestCATWallet:
|
|||
await time_out_assert(30, cat_wallet_2.get_unconfirmed_balance, 70)
|
||||
|
||||
cat_hash = await cat_wallet.get_new_inner_hash()
|
||||
tx_records = await cat_wallet_2.generate_signed_transaction([uint64(5)], [cat_hash])
|
||||
tx_records = await cat_wallet_2.generate_signed_transactions([uint64(5)], [cat_hash])
|
||||
for tx_record in tx_records:
|
||||
await wallet.wallet_state_manager.add_pending_transaction(tx_record)
|
||||
|
||||
|
|
|
@ -0,0 +1,991 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
# mypy: ignore-errors
|
||||
from clvm.casts import int_to_bytes
|
||||
|
||||
from chia.types.blockchain_format.coin import Coin
|
||||
|
||||
# from chia.types.blockchain_format.coin import Coin
|
||||
from chia.types.blockchain_format.program import INFINITE_COST, Program
|
||||
from chia.types.blockchain_format.sized_bytes import bytes32
|
||||
from chia.types.condition_opcodes import ConditionOpcode
|
||||
from chia.util.condition_tools import conditions_dict_for_solution
|
||||
from chia.util.hash import std_hash
|
||||
from chia.util.ints import uint64
|
||||
from chia.wallet.cat_wallet.cat_utils import CAT_MOD
|
||||
from chia.wallet.puzzles.load_clvm import load_clvm
|
||||
|
||||
CAT_MOD_HASH: bytes32 = CAT_MOD.get_tree_hash()
|
||||
SINGLETON_MOD: Program = load_clvm("singleton_top_layer_v1_1.clsp")
|
||||
SINGLETON_MOD_HASH: bytes32 = SINGLETON_MOD.get_tree_hash()
|
||||
SINGLETON_LAUNCHER: Program = load_clvm("singleton_launcher.clsp")
|
||||
SINGLETON_LAUNCHER_HASH: bytes32 = SINGLETON_LAUNCHER.get_tree_hash()
|
||||
DAO_LOCKUP_MOD: Program = load_clvm("dao_lockup.clsp")
|
||||
DAO_LOCKUP_MOD_HASH: bytes32 = DAO_LOCKUP_MOD.get_tree_hash()
|
||||
DAO_PROPOSAL_TIMER_MOD: Program = load_clvm("dao_proposal_timer.clsp")
|
||||
DAO_PROPOSAL_TIMER_MOD_HASH: bytes32 = DAO_PROPOSAL_TIMER_MOD.get_tree_hash()
|
||||
DAO_PROPOSAL_MOD: Program = load_clvm("dao_proposal.clsp")
|
||||
DAO_PROPOSAL_MOD_HASH: bytes32 = DAO_PROPOSAL_MOD.get_tree_hash()
|
||||
DAO_PROPOSAL_VALIDATOR_MOD: Program = load_clvm("dao_proposal_validator.clsp")
|
||||
DAO_PROPOSAL_VALIDATOR_MOD_HASH: bytes32 = DAO_PROPOSAL_VALIDATOR_MOD.get_tree_hash()
|
||||
DAO_TREASURY_MOD: Program = load_clvm("dao_treasury.clsp")
|
||||
DAO_TREASURY_MOD_HASH: bytes32 = DAO_TREASURY_MOD.get_tree_hash()
|
||||
SPEND_P2_SINGLETON_MOD: Program = load_clvm("dao_spend_p2_singleton_v2.clsp")
|
||||
SPEND_P2_SINGLETON_MOD_HASH: bytes32 = SPEND_P2_SINGLETON_MOD.get_tree_hash()
|
||||
DAO_FINISHED_STATE: Program = load_clvm("dao_finished_state.clsp")
|
||||
DAO_FINISHED_STATE_HASH: bytes32 = DAO_FINISHED_STATE.get_tree_hash()
|
||||
DAO_RESALE_PREVENTION: Program = load_clvm("dao_resale_prevention_layer.clsp")
|
||||
DAO_RESALE_PREVENTION_HASH: bytes32 = DAO_RESALE_PREVENTION.get_tree_hash()
|
||||
DAO_CAT_TAIL: Program = load_clvm("genesis_by_coin_id_or_singleton.clsp")
|
||||
DAO_CAT_TAIL_HASH: bytes32 = DAO_CAT_TAIL.get_tree_hash()
|
||||
P2_CONDITIONS_MOD: Program = load_clvm("p2_conditions_curryable.clsp")
|
||||
P2_CONDITIONS_MOD_HASH: bytes32 = P2_CONDITIONS_MOD.get_tree_hash()
|
||||
DAO_SAFE_PAYMENT_MOD: Program = load_clvm("dao_safe_payment.clsp")
|
||||
DAO_SAFE_PAYMENT_MOD_HASH: bytes32 = DAO_SAFE_PAYMENT_MOD.get_tree_hash()
|
||||
P2_SINGLETON_MOD: Program = load_clvm("p2_singleton_via_delegated_puzzle.clsp")
|
||||
P2_SINGLETON_MOD_HASH: bytes32 = P2_SINGLETON_MOD.get_tree_hash()
|
||||
P2_SINGLETON_AGGREGATOR_MOD: Program = load_clvm("p2_singleton_aggregator.clsp")
|
||||
P2_SINGLETON_AGGREGATOR_MOD_HASH: bytes32 = P2_SINGLETON_AGGREGATOR_MOD.get_tree_hash()
|
||||
DAO_UPDATE_MOD: Program = load_clvm("dao_update_proposal.clsp")
|
||||
DAO_UPDATE_MOD_HASH: bytes32 = DAO_UPDATE_MOD.get_tree_hash()
|
||||
|
||||
|
||||
def test_finished_state() -> None:
|
||||
proposal_id: Program = Program.to("proposal_id").get_tree_hash()
|
||||
singleton_struct: Program = Program.to(
|
||||
(SINGLETON_MOD.get_tree_hash(), (proposal_id, SINGLETON_LAUNCHER.get_tree_hash()))
|
||||
)
|
||||
finished_inner_puz = DAO_FINISHED_STATE.curry(singleton_struct, DAO_FINISHED_STATE_HASH)
|
||||
finished_full_puz = SINGLETON_MOD.curry(singleton_struct, finished_inner_puz)
|
||||
inner_sol = Program.to([1])
|
||||
|
||||
conds = finished_inner_puz.run(inner_sol).as_python()
|
||||
assert conds[0][1] == finished_full_puz.get_tree_hash()
|
||||
assert conds[2][1] == finished_inner_puz.get_tree_hash()
|
||||
|
||||
lineage = Program.to([proposal_id, finished_inner_puz.get_tree_hash(), 1])
|
||||
full_sol = Program.to([lineage, 1, inner_sol])
|
||||
|
||||
conds = conditions_dict_for_solution(finished_full_puz, full_sol, INFINITE_COST)
|
||||
assert conds[ConditionOpcode.ASSERT_MY_PUZZLEHASH][0].vars[0] == finished_full_puz.get_tree_hash()
|
||||
assert conds[ConditionOpcode.CREATE_COIN][0].vars[0] == finished_full_puz.get_tree_hash()
|
||||
|
||||
|
||||
def test_proposal() -> None:
|
||||
proposal_pass_percentage: uint64 = uint64(5100)
|
||||
CAT_TAIL_HASH: Program = Program.to("tail").get_tree_hash()
|
||||
treasury_id: Program = Program.to("treasury").get_tree_hash()
|
||||
singleton_id: Program = Program.to("singleton_id").get_tree_hash()
|
||||
singleton_struct: Program = Program.to(
|
||||
(SINGLETON_MOD.get_tree_hash(), (singleton_id, SINGLETON_LAUNCHER.get_tree_hash()))
|
||||
)
|
||||
self_destruct_time = 1000 # number of blocks
|
||||
oracle_spend_delay = 10
|
||||
active_votes_list = [0xFADEDDAB] # are the the ids of previously voted on proposals?
|
||||
acs: Program = Program.to(1)
|
||||
acs_ph: bytes32 = acs.get_tree_hash()
|
||||
|
||||
# make a lockup puz for the dao cat
|
||||
lockup_puz = DAO_LOCKUP_MOD.curry(
|
||||
DAO_PROPOSAL_MOD_HASH,
|
||||
SINGLETON_MOD_HASH,
|
||||
SINGLETON_LAUNCHER_HASH,
|
||||
DAO_LOCKUP_MOD_HASH,
|
||||
DAO_FINISHED_STATE_HASH,
|
||||
CAT_MOD_HASH,
|
||||
CAT_TAIL_HASH,
|
||||
active_votes_list,
|
||||
acs, # innerpuz
|
||||
)
|
||||
|
||||
dao_cat_puz: Program = CAT_MOD.curry(CAT_MOD_HASH, CAT_TAIL_HASH, lockup_puz)
|
||||
dao_cat_puzhash: bytes32 = dao_cat_puz.get_tree_hash()
|
||||
|
||||
# Test Voting
|
||||
current_yes_votes = 20
|
||||
current_total_votes = 100
|
||||
full_proposal: Program = DAO_PROPOSAL_MOD.curry(
|
||||
singleton_struct,
|
||||
DAO_PROPOSAL_MOD_HASH,
|
||||
DAO_PROPOSAL_TIMER_MOD_HASH,
|
||||
CAT_MOD_HASH,
|
||||
DAO_FINISHED_STATE_HASH,
|
||||
DAO_TREASURY_MOD_HASH,
|
||||
DAO_LOCKUP_MOD_HASH,
|
||||
CAT_TAIL_HASH,
|
||||
treasury_id,
|
||||
current_yes_votes,
|
||||
current_total_votes,
|
||||
acs_ph,
|
||||
)
|
||||
|
||||
vote_amount = 10
|
||||
vote_type = 1 # yes vote
|
||||
vote_coin_id = Program.to("vote_coin").get_tree_hash()
|
||||
solution: Program = Program.to(
|
||||
[
|
||||
[vote_amount], # vote amounts
|
||||
vote_type, # vote type (yes)
|
||||
[vote_coin_id], # vote coin ids
|
||||
[active_votes_list], # previous votes
|
||||
[acs_ph], # lockup inner puz hash
|
||||
0, # inner puz reveal
|
||||
0, # soft close len
|
||||
self_destruct_time,
|
||||
oracle_spend_delay,
|
||||
0,
|
||||
1,
|
||||
]
|
||||
)
|
||||
|
||||
# Run the proposal and check its conditions
|
||||
conditions = conditions_dict_for_solution(full_proposal, solution, INFINITE_COST)
|
||||
|
||||
# Puzzle Announcement of vote_coin_ids
|
||||
assert conditions[ConditionOpcode.CREATE_PUZZLE_ANNOUNCEMENT][0].vars[0] == vote_coin_id
|
||||
|
||||
# Assert puzzle announcement from dao_cat of proposal_id and all vote details
|
||||
apa_msg = Program.to([singleton_id, vote_amount, vote_type, vote_coin_id]).get_tree_hash()
|
||||
assert conditions[ConditionOpcode.ASSERT_PUZZLE_ANNOUNCEMENT][0].vars[0] == std_hash(dao_cat_puzhash + apa_msg)
|
||||
|
||||
# Check that the proposal recreates itself with updated vote amounts
|
||||
next_proposal: Program = DAO_PROPOSAL_MOD.curry(
|
||||
singleton_struct,
|
||||
DAO_PROPOSAL_MOD_HASH,
|
||||
DAO_PROPOSAL_TIMER_MOD_HASH,
|
||||
CAT_MOD_HASH,
|
||||
DAO_FINISHED_STATE_HASH,
|
||||
DAO_TREASURY_MOD_HASH,
|
||||
DAO_LOCKUP_MOD_HASH,
|
||||
CAT_TAIL_HASH,
|
||||
treasury_id,
|
||||
current_yes_votes + vote_amount,
|
||||
current_total_votes + vote_amount,
|
||||
acs_ph,
|
||||
)
|
||||
assert conditions[ConditionOpcode.CREATE_COIN][0].vars[0] == next_proposal.get_tree_hash()
|
||||
assert conditions[ConditionOpcode.CREATE_COIN][0].vars[1] == int_to_bytes(1)
|
||||
|
||||
# Test Launch
|
||||
current_yes_votes = 0
|
||||
current_total_votes = 0
|
||||
launch_proposal: Program = DAO_PROPOSAL_MOD.curry(
|
||||
singleton_struct,
|
||||
DAO_PROPOSAL_MOD_HASH,
|
||||
DAO_PROPOSAL_TIMER_MOD_HASH,
|
||||
CAT_MOD_HASH,
|
||||
DAO_FINISHED_STATE_HASH,
|
||||
DAO_TREASURY_MOD_HASH,
|
||||
DAO_LOCKUP_MOD_HASH,
|
||||
CAT_TAIL_HASH,
|
||||
treasury_id,
|
||||
current_yes_votes,
|
||||
current_total_votes,
|
||||
acs_ph,
|
||||
)
|
||||
vote_amount = 10
|
||||
vote_type = 1 # yes vote
|
||||
vote_coin_id = Program.to("vote_coin").get_tree_hash()
|
||||
solution: Program = Program.to(
|
||||
[
|
||||
[vote_amount], # vote amounts
|
||||
vote_type, # vote type (yes)
|
||||
[vote_coin_id], # vote coin ids
|
||||
# TODO: Check whether previous votes should be 0 in the first spend since
|
||||
# proposal looks at (f previous_votes) during loop_over_vote_coins
|
||||
[0], # previous votes
|
||||
[acs_ph], # lockup inner puz hash
|
||||
acs, # inner puz reveal
|
||||
0, # soft close len
|
||||
self_destruct_time,
|
||||
oracle_spend_delay,
|
||||
0,
|
||||
1,
|
||||
]
|
||||
)
|
||||
# Run the proposal and check its conditions
|
||||
conditions = conditions_dict_for_solution(launch_proposal, solution, INFINITE_COST)
|
||||
# check that the timer is created
|
||||
timer_puz = DAO_PROPOSAL_TIMER_MOD.curry(
|
||||
DAO_PROPOSAL_MOD_HASH,
|
||||
DAO_PROPOSAL_TIMER_MOD_HASH,
|
||||
DAO_LOCKUP_MOD_HASH,
|
||||
DAO_FINISHED_STATE_HASH,
|
||||
CAT_MOD_HASH,
|
||||
CAT_TAIL_HASH,
|
||||
singleton_struct,
|
||||
treasury_id,
|
||||
)
|
||||
timer_puzhash = timer_puz.get_tree_hash()
|
||||
assert conditions[ConditionOpcode.CREATE_COIN][1].vars[0] == timer_puzhash
|
||||
|
||||
# Test exits
|
||||
|
||||
# Test attempt to close a passing proposal
|
||||
current_yes_votes = 200
|
||||
current_total_votes = 350
|
||||
full_proposal: Program = DAO_PROPOSAL_MOD.curry(
|
||||
singleton_struct,
|
||||
DAO_PROPOSAL_MOD_HASH,
|
||||
DAO_PROPOSAL_TIMER_MOD_HASH,
|
||||
CAT_MOD_HASH,
|
||||
DAO_FINISHED_STATE_HASH,
|
||||
DAO_TREASURY_MOD_HASH,
|
||||
DAO_LOCKUP_MOD_HASH,
|
||||
CAT_TAIL_HASH,
|
||||
treasury_id,
|
||||
current_yes_votes,
|
||||
current_total_votes,
|
||||
acs_ph,
|
||||
)
|
||||
attendance_required = 200
|
||||
proposal_timelock = 20
|
||||
soft_close_length = 5
|
||||
solution = Program.to(
|
||||
[
|
||||
Program.to("validator_hash").get_tree_hash(),
|
||||
0,
|
||||
# Program.to("receiver_hash").get_tree_hash(), # not needed anymore?
|
||||
proposal_timelock,
|
||||
proposal_pass_percentage,
|
||||
attendance_required,
|
||||
0,
|
||||
soft_close_length,
|
||||
self_destruct_time,
|
||||
oracle_spend_delay,
|
||||
0,
|
||||
1,
|
||||
]
|
||||
)
|
||||
|
||||
conds = conditions_dict_for_solution(full_proposal, solution, INFINITE_COST)
|
||||
|
||||
# make a matching treasury puzzle for the APA
|
||||
treasury_inner: Program = DAO_TREASURY_MOD.curry(
|
||||
DAO_TREASURY_MOD_HASH,
|
||||
Program.to("validator_hash"),
|
||||
proposal_timelock,
|
||||
soft_close_length,
|
||||
attendance_required,
|
||||
proposal_pass_percentage,
|
||||
self_destruct_time,
|
||||
oracle_spend_delay,
|
||||
)
|
||||
treasury: Program = SINGLETON_MOD.curry(
|
||||
Program.to((SINGLETON_MOD_HASH, (treasury_id, SINGLETON_LAUNCHER_HASH))),
|
||||
treasury_inner,
|
||||
)
|
||||
treasury_puzhash = treasury.get_tree_hash()
|
||||
apa_msg = singleton_id
|
||||
|
||||
timer_apa = std_hash(timer_puzhash + singleton_id)
|
||||
assert conds[ConditionOpcode.ASSERT_PUZZLE_ANNOUNCEMENT][0].vars[0] == timer_apa
|
||||
assert conds[ConditionOpcode.ASSERT_PUZZLE_ANNOUNCEMENT][1].vars[0] == std_hash(treasury_puzhash + apa_msg)
|
||||
|
||||
# close a failed proposal
|
||||
full_proposal: Program = DAO_PROPOSAL_MOD.curry(
|
||||
singleton_struct,
|
||||
DAO_PROPOSAL_MOD_HASH,
|
||||
DAO_PROPOSAL_TIMER_MOD_HASH,
|
||||
CAT_MOD_HASH,
|
||||
DAO_FINISHED_STATE_HASH,
|
||||
DAO_TREASURY_MOD_HASH,
|
||||
DAO_LOCKUP_MOD_HASH,
|
||||
CAT_TAIL_HASH,
|
||||
treasury_id,
|
||||
20, # failing number of yes votes
|
||||
current_total_votes,
|
||||
acs_ph,
|
||||
)
|
||||
solution = Program.to(
|
||||
[
|
||||
Program.to("validator_hash").get_tree_hash(),
|
||||
0,
|
||||
# Program.to("receiver_hash").get_tree_hash(), # not needed anymore?
|
||||
proposal_timelock,
|
||||
proposal_pass_percentage,
|
||||
attendance_required,
|
||||
0,
|
||||
soft_close_length,
|
||||
self_destruct_time,
|
||||
oracle_spend_delay,
|
||||
0,
|
||||
1,
|
||||
]
|
||||
)
|
||||
conds = conditions_dict_for_solution(full_proposal, solution, INFINITE_COST)
|
||||
apa_msg = int_to_bytes(0)
|
||||
assert conds[ConditionOpcode.ASSERT_PUZZLE_ANNOUNCEMENT][1].vars[0] == std_hash(treasury_puzhash + apa_msg)
|
||||
assert conds[ConditionOpcode.ASSERT_PUZZLE_ANNOUNCEMENT][0].vars[0] == timer_apa
|
||||
|
||||
finished_puz = DAO_FINISHED_STATE.curry(singleton_struct, DAO_FINISHED_STATE_HASH)
|
||||
assert conds[ConditionOpcode.CREATE_COIN][0].vars[0] == finished_puz.get_tree_hash()
|
||||
|
||||
# self destruct a proposal
|
||||
attendance_required = 200
|
||||
solution = Program.to(
|
||||
[
|
||||
Program.to("validator_hash").get_tree_hash(),
|
||||
0,
|
||||
# Program.to("receiver_hash").get_tree_hash(), # not needed anymore?
|
||||
proposal_timelock,
|
||||
proposal_pass_percentage,
|
||||
attendance_required,
|
||||
0,
|
||||
soft_close_length,
|
||||
self_destruct_time,
|
||||
oracle_spend_delay,
|
||||
1,
|
||||
1,
|
||||
]
|
||||
)
|
||||
conds = conditions_dict_for_solution(full_proposal, solution, INFINITE_COST)
|
||||
assert conds[ConditionOpcode.ASSERT_PUZZLE_ANNOUNCEMENT][0].vars[0] == std_hash(treasury_puzhash + apa_msg)
|
||||
assert conds[ConditionOpcode.CREATE_COIN][0].vars[0] == finished_puz.get_tree_hash()
|
||||
|
||||
|
||||
def test_proposal_timer() -> None:
|
||||
CAT_TAIL: Program = Program.to("tail").get_tree_hash()
|
||||
treasury_id: Program = Program.to("treasury").get_tree_hash()
|
||||
singleton_id: Program = Program.to("singleton_id").get_tree_hash()
|
||||
singleton_struct: Program = Program.to(
|
||||
(SINGLETON_MOD.get_tree_hash(), (singleton_id, SINGLETON_LAUNCHER.get_tree_hash()))
|
||||
)
|
||||
proposal_timer_full: Program = DAO_PROPOSAL_TIMER_MOD.curry(
|
||||
DAO_PROPOSAL_MOD_HASH,
|
||||
DAO_PROPOSAL_TIMER_MOD_HASH,
|
||||
DAO_LOCKUP_MOD_HASH,
|
||||
DAO_FINISHED_STATE_HASH,
|
||||
CAT_MOD_HASH,
|
||||
CAT_TAIL,
|
||||
singleton_struct,
|
||||
treasury_id,
|
||||
)
|
||||
|
||||
solution: Program = Program.to(
|
||||
[
|
||||
140,
|
||||
180,
|
||||
Program.to(1).get_tree_hash(),
|
||||
Program.to("parent").get_tree_hash(),
|
||||
23,
|
||||
200,
|
||||
Program.to("parent_parent").get_tree_hash(),
|
||||
]
|
||||
)
|
||||
conds: Program = proposal_timer_full.run(solution)
|
||||
assert len(conds.as_python()) == 4
|
||||
|
||||
|
||||
def test_validator() -> None:
|
||||
# This test covers proposal_validator and spend_p2_singleton
|
||||
# Setup the treasury
|
||||
treasury_id: Program = Program.to("treasury_id").get_tree_hash()
|
||||
treasury_struct: Program = Program.to((SINGLETON_MOD_HASH, (treasury_id, SINGLETON_LAUNCHER_HASH)))
|
||||
|
||||
# Setup the proposal
|
||||
proposal_id: Program = Program.to("proposal_id").get_tree_hash()
|
||||
proposal_struct: Program = Program.to((SINGLETON_MOD.get_tree_hash(), (proposal_id, SINGLETON_LAUNCHER_HASH)))
|
||||
CAT_TAIL_HASH: Program = Program.to("tail").get_tree_hash()
|
||||
acs: Program = Program.to(1)
|
||||
acs_ph: bytes32 = acs.get_tree_hash()
|
||||
|
||||
p2_singleton = P2_SINGLETON_MOD.curry(treasury_struct, P2_SINGLETON_AGGREGATOR_MOD)
|
||||
p2_singleton_puzhash = p2_singleton.get_tree_hash()
|
||||
parent_id = Program.to("parent").get_tree_hash()
|
||||
locked_amount = 100000
|
||||
spend_amount = 1100
|
||||
conditions = [[51, 0xDABBAD00, 1000], [51, 0xCAFEF00D, 100]]
|
||||
|
||||
# Setup the validator
|
||||
minimum_amt = 1
|
||||
excess_puzhash = bytes32(b"1" * 32)
|
||||
proposal_validator = DAO_PROPOSAL_VALIDATOR_MOD.curry(
|
||||
treasury_struct,
|
||||
DAO_PROPOSAL_MOD_HASH,
|
||||
DAO_PROPOSAL_TIMER_MOD_HASH,
|
||||
CAT_MOD_HASH,
|
||||
DAO_FINISHED_STATE_HASH,
|
||||
DAO_LOCKUP_MOD_HASH,
|
||||
DAO_TREASURY_MOD_HASH,
|
||||
CAT_TAIL_HASH,
|
||||
minimum_amt,
|
||||
excess_puzhash,
|
||||
)
|
||||
|
||||
# Can now create the treasury inner puz
|
||||
treasury_inner = DAO_TREASURY_MOD.curry(
|
||||
DAO_TREASURY_MOD_HASH,
|
||||
proposal_validator,
|
||||
10, # proposal len
|
||||
5, # soft close
|
||||
1000, # attendance
|
||||
5100, # pass margin
|
||||
20, # self_destruct len
|
||||
3, # oracle delay
|
||||
)
|
||||
|
||||
# Setup the spend_p2_singleton (proposal inner puz)
|
||||
spend_p2_singleton = SPEND_P2_SINGLETON_MOD.curry(
|
||||
treasury_struct, CAT_MOD_HASH, conditions, [], p2_singleton_puzhash # tailhash conds
|
||||
)
|
||||
spend_p2_singleton_puzhash = spend_p2_singleton.get_tree_hash()
|
||||
|
||||
parent_amt_list = [[parent_id, locked_amount]]
|
||||
cat_parent_amt_list = []
|
||||
spend_p2_singleton_solution = Program.to([parent_amt_list, cat_parent_amt_list, treasury_inner.get_tree_hash()])
|
||||
|
||||
output_conds = spend_p2_singleton.run(spend_p2_singleton_solution)
|
||||
|
||||
proposal: Program = DAO_PROPOSAL_MOD.curry(
|
||||
proposal_struct,
|
||||
DAO_PROPOSAL_MOD_HASH,
|
||||
DAO_PROPOSAL_TIMER_MOD_HASH,
|
||||
CAT_MOD_HASH,
|
||||
DAO_FINISHED_STATE_HASH,
|
||||
DAO_TREASURY_MOD_HASH,
|
||||
DAO_LOCKUP_MOD_HASH,
|
||||
CAT_TAIL_HASH,
|
||||
treasury_id,
|
||||
950,
|
||||
1200,
|
||||
spend_p2_singleton_puzhash,
|
||||
)
|
||||
full_proposal = SINGLETON_MOD.curry(proposal_struct, proposal)
|
||||
proposal_amt = 10
|
||||
proposal_coin_id = Coin(parent_id, full_proposal.get_tree_hash(), proposal_amt).name()
|
||||
solution = Program.to(
|
||||
[
|
||||
1000,
|
||||
5100,
|
||||
[proposal_coin_id, spend_p2_singleton_puzhash, 0],
|
||||
[proposal_id, 1200, 950, parent_id, proposal_amt],
|
||||
output_conds,
|
||||
]
|
||||
)
|
||||
|
||||
conds: Program = proposal_validator.run(solution)
|
||||
assert len(conds.as_python()) == 7 + len(conditions)
|
||||
|
||||
# test update
|
||||
proposal: Program = DAO_PROPOSAL_MOD.curry(
|
||||
proposal_struct,
|
||||
DAO_PROPOSAL_MOD_HASH,
|
||||
DAO_PROPOSAL_TIMER_MOD_HASH,
|
||||
CAT_MOD_HASH,
|
||||
DAO_FINISHED_STATE_HASH,
|
||||
DAO_TREASURY_MOD_HASH,
|
||||
DAO_LOCKUP_MOD_HASH,
|
||||
CAT_TAIL_HASH,
|
||||
treasury_id,
|
||||
950,
|
||||
1200,
|
||||
acs_ph,
|
||||
)
|
||||
full_proposal = SINGLETON_MOD.curry(proposal_struct, proposal)
|
||||
proposal_coin_id = Coin(parent_id, full_proposal.get_tree_hash(), proposal_amt).name()
|
||||
solution = Program.to(
|
||||
[
|
||||
1000,
|
||||
5100,
|
||||
[proposal_coin_id, acs_ph, 0],
|
||||
[proposal_id, 1200, 950, parent_id, proposal_amt],
|
||||
[[51, 0xCAFE00D, spend_amount]],
|
||||
]
|
||||
)
|
||||
conds: Program = proposal_validator.run(solution)
|
||||
assert len(conds.as_python()) == 3
|
||||
|
||||
return
|
||||
|
||||
|
||||
def test_merge_p2_singleton() -> None:
|
||||
# Setup a singleton struct
|
||||
singleton_inner: Program = Program.to(1)
|
||||
singleton_id: Program = Program.to("singleton_id").get_tree_hash()
|
||||
singleton_struct: Program = Program.to((SINGLETON_MOD_HASH, (singleton_id, SINGLETON_LAUNCHER_HASH)))
|
||||
|
||||
# Setup p2_singleton_via_delegated puz
|
||||
my_id = Program.to("my_id").get_tree_hash()
|
||||
p2_singleton = P2_SINGLETON_MOD.curry(singleton_struct, P2_SINGLETON_AGGREGATOR_MOD)
|
||||
my_puzhash = p2_singleton.get_tree_hash()
|
||||
|
||||
# Spend to delegated puz
|
||||
delegated_puz = Program.to(1)
|
||||
delegated_sol = Program.to([[51, 0xCAFEF00D, 300]])
|
||||
solution = Program.to([0, singleton_inner.get_tree_hash(), delegated_puz, delegated_sol, my_id])
|
||||
conds = conditions_dict_for_solution(p2_singleton, solution, INFINITE_COST)
|
||||
apa = std_hash(
|
||||
SINGLETON_MOD.curry(singleton_struct, singleton_inner).get_tree_hash()
|
||||
+ Program.to([my_id, delegated_puz.get_tree_hash()]).get_tree_hash()
|
||||
)
|
||||
assert len(conds) == 4
|
||||
assert conds[ConditionOpcode.ASSERT_PUZZLE_ANNOUNCEMENT][0].vars[0] == apa
|
||||
assert conds[ConditionOpcode.CREATE_COIN][0].vars[1] == int_to_bytes(300)
|
||||
|
||||
# Merge Spend (not output creator)
|
||||
merge_sol = Program.to([[my_id, my_puzhash, 300, 0]])
|
||||
conds = conditions_dict_for_solution(p2_singleton, merge_sol, INFINITE_COST)
|
||||
assert len(conds) == 2
|
||||
assert conds[ConditionOpcode.ASSERT_MY_PUZZLEHASH][0].vars[0] == my_puzhash
|
||||
assert conds[ConditionOpcode.CREATE_COIN_ANNOUNCEMENT][0].vars[0] == int_to_bytes(0)
|
||||
|
||||
# Merge Spend (output creator)
|
||||
fake_parent_id = Program.to("fake_parent").get_tree_hash()
|
||||
merged_coin_id = Coin(fake_parent_id, my_puzhash, 200).name()
|
||||
merge_sol = Program.to([[my_id, my_puzhash, 100, [[fake_parent_id, my_puzhash, 200]]]])
|
||||
conds = conditions_dict_for_solution(p2_singleton, merge_sol, INFINITE_COST)
|
||||
assert len(conds) == 5
|
||||
assert conds[ConditionOpcode.ASSERT_COIN_ANNOUNCEMENT][0].vars[0] == Program.to([merged_coin_id, 0]).get_tree_hash()
|
||||
assert conds[ConditionOpcode.CREATE_COIN][0].vars[1] == int_to_bytes(300)
|
||||
return
|
||||
|
||||
|
||||
def test_treasury() -> None:
|
||||
# Setup the treasury
|
||||
treasury_id: Program = Program.to("treasury_id").get_tree_hash()
|
||||
treasury_struct: Program = Program.to((SINGLETON_MOD_HASH, (treasury_id, SINGLETON_LAUNCHER_HASH)))
|
||||
CAT_TAIL_HASH: Program = Program.to("tail").get_tree_hash()
|
||||
|
||||
proposal_id: Program = Program.to("singleton_id").get_tree_hash()
|
||||
proposal_struct: Program = Program.to((SINGLETON_MOD_HASH, (proposal_id, SINGLETON_LAUNCHER_HASH)))
|
||||
p2_singleton = P2_SINGLETON_MOD.curry(treasury_struct, P2_SINGLETON_AGGREGATOR_MOD)
|
||||
p2_singleton_puzhash = p2_singleton.get_tree_hash()
|
||||
parent_id = Program.to("parent").get_tree_hash()
|
||||
locked_amount = 100000
|
||||
oracle_spend_delay = 10
|
||||
self_destruct_time = 1000
|
||||
proposal_length = 40
|
||||
soft_close_length = 5
|
||||
attendance = 1000
|
||||
pass_margin = 5100
|
||||
conditions = [[51, 0xDABBAD00, 1000], [51, 0xCAFEF00D, 100]]
|
||||
|
||||
# Setup the validator
|
||||
minimum_amt = 1
|
||||
excess_puzhash = bytes32(b"1" * 32)
|
||||
proposal_validator = DAO_PROPOSAL_VALIDATOR_MOD.curry(
|
||||
treasury_struct,
|
||||
DAO_PROPOSAL_MOD_HASH,
|
||||
DAO_PROPOSAL_TIMER_MOD_HASH,
|
||||
CAT_MOD_HASH,
|
||||
DAO_FINISHED_STATE_HASH,
|
||||
DAO_LOCKUP_MOD_HASH,
|
||||
DAO_TREASURY_MOD_HASH,
|
||||
CAT_TAIL_HASH,
|
||||
minimum_amt,
|
||||
excess_puzhash,
|
||||
)
|
||||
|
||||
# Can now create the treasury inner puz
|
||||
treasury_inner = DAO_TREASURY_MOD.curry(
|
||||
DAO_TREASURY_MOD_HASH,
|
||||
proposal_validator,
|
||||
proposal_length,
|
||||
soft_close_length,
|
||||
attendance,
|
||||
pass_margin,
|
||||
self_destruct_time,
|
||||
oracle_spend_delay,
|
||||
)
|
||||
|
||||
# Setup the spend_p2_singleton (proposal inner puz)
|
||||
spend_p2_singleton = SPEND_P2_SINGLETON_MOD.curry(
|
||||
treasury_struct, CAT_MOD_HASH, conditions, [], p2_singleton_puzhash # tailhash conds
|
||||
)
|
||||
spend_p2_singleton_puzhash = spend_p2_singleton.get_tree_hash()
|
||||
|
||||
parent_amt_list = [[parent_id, locked_amount]]
|
||||
cat_parent_amt_list = []
|
||||
spend_p2_singleton_solution = Program.to([parent_amt_list, cat_parent_amt_list, treasury_inner.get_tree_hash()])
|
||||
|
||||
proposal: Program = DAO_PROPOSAL_MOD.curry(
|
||||
proposal_struct,
|
||||
DAO_PROPOSAL_MOD_HASH,
|
||||
DAO_PROPOSAL_TIMER_MOD_HASH,
|
||||
CAT_MOD_HASH,
|
||||
DAO_FINISHED_STATE_HASH,
|
||||
DAO_TREASURY_MOD_HASH,
|
||||
DAO_LOCKUP_MOD_HASH,
|
||||
CAT_TAIL_HASH,
|
||||
treasury_id,
|
||||
950,
|
||||
1200,
|
||||
spend_p2_singleton_puzhash,
|
||||
)
|
||||
full_proposal = SINGLETON_MOD.curry(proposal_struct, proposal)
|
||||
|
||||
# Oracle spend
|
||||
solution: Program = Program.to([0, 0, 0, 0, 0, treasury_struct])
|
||||
conds: Program = treasury_inner.run(solution)
|
||||
assert len(conds.as_python()) == 3
|
||||
|
||||
# Proposal Spend
|
||||
proposal_amt = 10
|
||||
proposal_coin_id = Coin(parent_id, full_proposal.get_tree_hash(), proposal_amt).name()
|
||||
solution: Program = Program.to(
|
||||
[
|
||||
[proposal_coin_id, spend_p2_singleton_puzhash, 0, "s"],
|
||||
[proposal_id, 1200, 950, parent_id, proposal_amt],
|
||||
spend_p2_singleton,
|
||||
spend_p2_singleton_solution,
|
||||
]
|
||||
)
|
||||
conds = treasury_inner.run(solution)
|
||||
assert len(conds.as_python()) == 9 + len(conditions)
|
||||
|
||||
|
||||
def test_lockup() -> None:
|
||||
CAT_TAIL: Program = Program.to("tail").get_tree_hash()
|
||||
|
||||
INNERPUZ = Program.to(1)
|
||||
previous_votes = [0xFADEDDAB]
|
||||
|
||||
full_lockup_puz: Program = DAO_LOCKUP_MOD.curry(
|
||||
DAO_PROPOSAL_MOD_HASH,
|
||||
SINGLETON_MOD_HASH,
|
||||
SINGLETON_LAUNCHER_HASH,
|
||||
DAO_LOCKUP_MOD_HASH,
|
||||
DAO_FINISHED_STATE_HASH,
|
||||
CAT_MOD_HASH,
|
||||
CAT_TAIL,
|
||||
previous_votes,
|
||||
INNERPUZ,
|
||||
)
|
||||
my_id = Program.to("my_id").get_tree_hash()
|
||||
lockup_coin_amount = 20
|
||||
|
||||
# Test adding vote
|
||||
new_proposal = 0xBADDADAB
|
||||
new_vote_list = [new_proposal, 0xFADEDDAB]
|
||||
child_puzhash = DAO_LOCKUP_MOD.curry(
|
||||
DAO_PROPOSAL_MOD_HASH,
|
||||
SINGLETON_MOD_HASH,
|
||||
SINGLETON_LAUNCHER_HASH,
|
||||
DAO_LOCKUP_MOD_HASH,
|
||||
DAO_FINISHED_STATE_HASH,
|
||||
CAT_MOD_HASH,
|
||||
CAT_TAIL,
|
||||
new_vote_list,
|
||||
INNERPUZ,
|
||||
).get_tree_hash()
|
||||
message = Program.to([new_proposal, lockup_coin_amount, 1, my_id]).get_tree_hash()
|
||||
generated_conditions = [[51, child_puzhash, lockup_coin_amount], [62, message]]
|
||||
solution: Program = Program.to(
|
||||
[
|
||||
my_id,
|
||||
generated_conditions,
|
||||
20,
|
||||
new_proposal,
|
||||
[1, 2, 3, 4, 5, 6, 7, 8, 9, 0], # fake proposal curry vals
|
||||
1,
|
||||
20,
|
||||
child_puzhash,
|
||||
0,
|
||||
]
|
||||
)
|
||||
conds: Program = full_lockup_puz.run(solution)
|
||||
assert len(conds.as_python()) == 6
|
||||
|
||||
# Test Re-voting on same proposal fails
|
||||
new_proposal = 0xBADDADAB
|
||||
new_vote_list = [new_proposal, 0xBADDADAB]
|
||||
child_puzhash = DAO_LOCKUP_MOD.curry(
|
||||
DAO_PROPOSAL_MOD_HASH,
|
||||
SINGLETON_MOD_HASH,
|
||||
SINGLETON_LAUNCHER_HASH,
|
||||
DAO_LOCKUP_MOD_HASH,
|
||||
DAO_FINISHED_STATE_HASH,
|
||||
CAT_MOD_HASH,
|
||||
CAT_TAIL,
|
||||
new_vote_list,
|
||||
INNERPUZ,
|
||||
).get_tree_hash()
|
||||
message = Program.to([new_proposal, lockup_coin_amount, 1, my_id]).get_tree_hash()
|
||||
generated_conditions = [[51, child_puzhash, lockup_coin_amount], [62, message]]
|
||||
revote_solution: Program = Program.to(
|
||||
[
|
||||
my_id,
|
||||
generated_conditions,
|
||||
20,
|
||||
new_proposal,
|
||||
[1, 2, 3, 4, 5, 6, 7, 8, 9, 0], # fake proposal curry vals
|
||||
1,
|
||||
20,
|
||||
child_puzhash,
|
||||
0,
|
||||
]
|
||||
)
|
||||
with pytest.raises(ValueError) as e_info:
|
||||
conds: Program = full_lockup_puz.run(revote_solution)
|
||||
assert e_info.value.args[0] == "clvm raise"
|
||||
|
||||
# Test vote removal
|
||||
solution = Program.to(
|
||||
[
|
||||
0,
|
||||
generated_conditions,
|
||||
20,
|
||||
[0xFADEDDAB],
|
||||
[1, 2, 3, 4, 5, 6, 7, 8, 9, 0],
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
]
|
||||
)
|
||||
conds = full_lockup_puz.run(solution)
|
||||
assert len(conds.as_python()) == 3
|
||||
|
||||
new_innerpuz = Program.to("new_inner")
|
||||
new_innerpuzhash = new_innerpuz.get_tree_hash()
|
||||
child_lockup = DAO_LOCKUP_MOD.curry(
|
||||
DAO_PROPOSAL_MOD_HASH,
|
||||
SINGLETON_MOD_HASH,
|
||||
SINGLETON_LAUNCHER_HASH,
|
||||
DAO_LOCKUP_MOD_HASH,
|
||||
DAO_FINISHED_STATE_HASH,
|
||||
CAT_MOD_HASH,
|
||||
CAT_TAIL,
|
||||
previous_votes,
|
||||
new_innerpuz,
|
||||
).get_tree_hash()
|
||||
message = Program.to([0, 0, 0, my_id]).get_tree_hash()
|
||||
spend_conds = [[51, child_lockup, lockup_coin_amount], [62, message]]
|
||||
transfer_sol = Program.to(
|
||||
[
|
||||
my_id,
|
||||
spend_conds,
|
||||
lockup_coin_amount,
|
||||
0,
|
||||
[1, 2, 3, 4, 5, 6, 7, 8, 9, 0], # fake proposal curry vals
|
||||
0,
|
||||
0,
|
||||
INNERPUZ.get_tree_hash(),
|
||||
new_innerpuzhash,
|
||||
]
|
||||
)
|
||||
conds = full_lockup_puz.run(transfer_sol)
|
||||
assert conds.at("rrrrfrf").as_atom() == child_lockup
|
||||
|
||||
|
||||
def test_proposal_innerpuz() -> None:
|
||||
proposal_pass_percentage: uint64 = uint64(5100)
|
||||
attendance_required: uint64 = uint64(1000)
|
||||
proposal_timelock = 40
|
||||
soft_close_length = 5
|
||||
self_destruct_time = 1000
|
||||
CAT_TAIL_HASH: Program = Program.to("tail").get_tree_hash()
|
||||
oracle_spend_delay = 10
|
||||
|
||||
# Setup the treasury
|
||||
treasury_id: Program = Program.to("treasury_id").get_tree_hash()
|
||||
treasury_singleton_struct: Program = Program.to((SINGLETON_MOD_HASH, (treasury_id, SINGLETON_LAUNCHER_HASH)))
|
||||
treasury_amount = 1
|
||||
|
||||
# setup the p2_singleton
|
||||
p2_singleton = P2_SINGLETON_MOD.curry(treasury_singleton_struct, P2_SINGLETON_AGGREGATOR_MOD)
|
||||
p2_singleton_puzhash = p2_singleton.get_tree_hash()
|
||||
parent_id = Program.to("parent").get_tree_hash()
|
||||
locked_amount = 100000
|
||||
conditions = [[51, 0xDABBAD00, 1000], [51, 0xCAFEF00D, 100]]
|
||||
|
||||
min_amt = 1
|
||||
excess_puzhash = bytes32(b"1" * 32)
|
||||
proposal_validator = DAO_PROPOSAL_VALIDATOR_MOD.curry(
|
||||
treasury_singleton_struct,
|
||||
DAO_PROPOSAL_MOD_HASH,
|
||||
DAO_PROPOSAL_TIMER_MOD_HASH,
|
||||
CAT_MOD_HASH,
|
||||
DAO_FINISHED_STATE_HASH,
|
||||
DAO_LOCKUP_MOD_HASH,
|
||||
DAO_TREASURY_MOD_HASH,
|
||||
CAT_TAIL_HASH,
|
||||
min_amt,
|
||||
excess_puzhash,
|
||||
)
|
||||
|
||||
treasury_inner_puz: Program = DAO_TREASURY_MOD.curry(
|
||||
DAO_TREASURY_MOD_HASH,
|
||||
proposal_validator,
|
||||
proposal_timelock,
|
||||
soft_close_length,
|
||||
attendance_required,
|
||||
proposal_pass_percentage,
|
||||
self_destruct_time,
|
||||
oracle_spend_delay,
|
||||
)
|
||||
treasury_inner_puzhash = treasury_inner_puz.get_tree_hash()
|
||||
|
||||
full_treasury_puz = SINGLETON_MOD.curry(treasury_singleton_struct, treasury_inner_puz)
|
||||
full_treasury_puzhash = full_treasury_puz.get_tree_hash()
|
||||
|
||||
# Setup the spend_p2_singleton (proposal inner puz)
|
||||
spend_p2_singleton = SPEND_P2_SINGLETON_MOD.curry(
|
||||
treasury_singleton_struct, CAT_MOD_HASH, conditions, [], p2_singleton_puzhash # tailhash conds
|
||||
)
|
||||
spend_p2_singleton_puzhash = spend_p2_singleton.get_tree_hash()
|
||||
|
||||
parent_amt_list = [[parent_id, locked_amount]]
|
||||
cat_parent_amt_list = []
|
||||
spend_p2_singleton_solution = Program.to([parent_amt_list, cat_parent_amt_list, treasury_inner_puzhash])
|
||||
|
||||
# Setup Proposal
|
||||
proposal_id: Program = Program.to("proposal_id").get_tree_hash()
|
||||
proposal_singleton_struct: Program = Program.to((SINGLETON_MOD_HASH, (proposal_id, SINGLETON_LAUNCHER_HASH)))
|
||||
|
||||
current_votes = 1200
|
||||
yes_votes = 950
|
||||
proposal: Program = DAO_PROPOSAL_MOD.curry(
|
||||
proposal_singleton_struct,
|
||||
DAO_PROPOSAL_MOD_HASH,
|
||||
DAO_PROPOSAL_TIMER_MOD_HASH,
|
||||
CAT_MOD_HASH,
|
||||
DAO_FINISHED_STATE_HASH,
|
||||
DAO_TREASURY_MOD_HASH,
|
||||
DAO_LOCKUP_MOD_HASH,
|
||||
CAT_TAIL_HASH,
|
||||
treasury_id,
|
||||
yes_votes,
|
||||
current_votes,
|
||||
spend_p2_singleton_puzhash,
|
||||
)
|
||||
full_proposal: Program = SINGLETON_MOD.curry(proposal_singleton_struct, proposal)
|
||||
full_proposal_puzhash: bytes32 = full_proposal.get_tree_hash()
|
||||
proposal_amt = 11
|
||||
proposal_coin_id = Coin(parent_id, full_proposal_puzhash, proposal_amt).name()
|
||||
|
||||
treasury_solution: Program = Program.to(
|
||||
[
|
||||
[proposal_coin_id, spend_p2_singleton_puzhash, 0, "s"],
|
||||
[proposal_id, current_votes, yes_votes, parent_id, proposal_amt],
|
||||
spend_p2_singleton,
|
||||
spend_p2_singleton_solution,
|
||||
]
|
||||
)
|
||||
|
||||
proposal_solution = Program.to(
|
||||
[
|
||||
proposal_validator.get_tree_hash(),
|
||||
0,
|
||||
proposal_timelock,
|
||||
proposal_pass_percentage,
|
||||
attendance_required,
|
||||
0,
|
||||
soft_close_length,
|
||||
self_destruct_time,
|
||||
oracle_spend_delay,
|
||||
0,
|
||||
proposal_amt,
|
||||
]
|
||||
)
|
||||
|
||||
# lineage_proof my_amount inner_solution
|
||||
lineage_proof = [treasury_id, treasury_inner_puzhash, treasury_amount]
|
||||
full_treasury_solution = Program.to([lineage_proof, treasury_amount, treasury_solution])
|
||||
full_proposal_solution = Program.to([lineage_proof, proposal_amt, proposal_solution])
|
||||
|
||||
# Run the puzzles
|
||||
# from clvm.casts import int_from_bytes
|
||||
# cds = conditions_dict_for_solution(full_treasury_puz, full_treasury_solution, INFINITE_COST)[ConditionOpcode.CREATE_COIN]
|
||||
# amts = [int_from_bytes(x.vars[1]) for x in cds]
|
||||
# breakpoint()
|
||||
treasury_conds: Program = conditions_dict_for_solution(full_treasury_puz, full_treasury_solution, INFINITE_COST)
|
||||
proposal_conds: Program = conditions_dict_for_solution(full_proposal, full_proposal_solution, INFINITE_COST)
|
||||
|
||||
# Announcements
|
||||
treasury_aca = treasury_conds[ConditionOpcode.ASSERT_COIN_ANNOUNCEMENT][0].vars[0]
|
||||
proposal_cca = proposal_conds[ConditionOpcode.CREATE_COIN_ANNOUNCEMENT][0].vars[0]
|
||||
assert std_hash(proposal_coin_id + proposal_cca) == treasury_aca
|
||||
|
||||
treasury_cpas = [
|
||||
std_hash(full_treasury_puzhash + cond.vars[0])
|
||||
for cond in treasury_conds[ConditionOpcode.CREATE_PUZZLE_ANNOUNCEMENT]
|
||||
]
|
||||
proposal_apas = [cond.vars[0] for cond in proposal_conds[ConditionOpcode.ASSERT_PUZZLE_ANNOUNCEMENT]]
|
||||
assert treasury_cpas[1] == proposal_apas[1]
|
||||
|
||||
# Test Proposal to update treasury
|
||||
# Set up new treasury params
|
||||
new_proposal_pass_percentage: uint64 = uint64(2500)
|
||||
new_attendance_required: uint64 = uint64(500)
|
||||
new_proposal_timelock = 900
|
||||
new_soft_close_length = 10
|
||||
new_self_destruct_time = 1000
|
||||
new_oracle_spend_delay = 20
|
||||
|
||||
update_proposal = DAO_UPDATE_MOD.curry(
|
||||
DAO_TREASURY_MOD_HASH,
|
||||
proposal_validator,
|
||||
new_proposal_timelock,
|
||||
new_soft_close_length,
|
||||
new_attendance_required,
|
||||
new_proposal_pass_percentage,
|
||||
new_self_destruct_time,
|
||||
new_oracle_spend_delay,
|
||||
)
|
||||
update_proposal_puzhash = update_proposal.get_tree_hash()
|
||||
update_proposal_sol = Program.to([])
|
||||
|
||||
proposal: Program = DAO_PROPOSAL_MOD.curry(
|
||||
proposal_singleton_struct,
|
||||
DAO_PROPOSAL_MOD_HASH,
|
||||
DAO_PROPOSAL_TIMER_MOD_HASH,
|
||||
CAT_MOD_HASH,
|
||||
DAO_FINISHED_STATE_HASH,
|
||||
DAO_TREASURY_MOD_HASH,
|
||||
DAO_LOCKUP_MOD_HASH,
|
||||
CAT_TAIL_HASH,
|
||||
treasury_id,
|
||||
yes_votes,
|
||||
current_votes,
|
||||
update_proposal_puzhash,
|
||||
)
|
||||
full_proposal: Program = SINGLETON_MOD.curry(proposal_singleton_struct, proposal)
|
||||
full_proposal_puzhash: bytes32 = full_proposal.get_tree_hash()
|
||||
proposal_coin_id = Coin(parent_id, full_proposal_puzhash, proposal_amt).name()
|
||||
|
||||
treasury_solution: Program = Program.to(
|
||||
[
|
||||
[proposal_coin_id, update_proposal_puzhash, 0, "u"],
|
||||
[proposal_id, current_votes, yes_votes, parent_id, proposal_amt],
|
||||
update_proposal,
|
||||
update_proposal_sol,
|
||||
]
|
||||
)
|
||||
|
||||
proposal_solution = Program.to(
|
||||
[
|
||||
proposal_validator.get_tree_hash(),
|
||||
0,
|
||||
proposal_timelock,
|
||||
proposal_pass_percentage,
|
||||
attendance_required,
|
||||
0,
|
||||
soft_close_length,
|
||||
self_destruct_time,
|
||||
oracle_spend_delay,
|
||||
0,
|
||||
proposal_amt,
|
||||
]
|
||||
)
|
||||
|
||||
lineage_proof = [treasury_id, treasury_inner_puzhash, treasury_amount]
|
||||
full_treasury_solution = Program.to([lineage_proof, treasury_amount, treasury_solution])
|
||||
full_proposal_solution = Program.to([lineage_proof, proposal_amt, proposal_solution])
|
||||
|
||||
treasury_conds: Program = conditions_dict_for_solution(full_treasury_puz, full_treasury_solution, INFINITE_COST)
|
||||
proposal_conds: Program = conditions_dict_for_solution(full_proposal, full_proposal_solution, INFINITE_COST)
|
||||
|
||||
treasury_aca = treasury_conds[ConditionOpcode.ASSERT_COIN_ANNOUNCEMENT][0].vars[0]
|
||||
proposal_cca = proposal_conds[ConditionOpcode.CREATE_COIN_ANNOUNCEMENT][0].vars[0]
|
||||
assert std_hash(proposal_coin_id + proposal_cca) == treasury_aca
|
||||
|
||||
treasury_cpas = [
|
||||
std_hash(full_treasury_puzhash + cond.vars[0])
|
||||
for cond in treasury_conds[ConditionOpcode.CREATE_PUZZLE_ANNOUNCEMENT]
|
||||
]
|
||||
proposal_apas = [cond.vars[0] for cond in proposal_conds[ConditionOpcode.ASSERT_PUZZLE_ANNOUNCEMENT]]
|
||||
assert treasury_cpas[1] == proposal_apas[1]
|
File diff suppressed because it is too large
Load Diff
|
@ -727,7 +727,7 @@ async def test_nft_offer_sell_nft_for_cat(
|
|||
|
||||
ph_taker_cat_1 = await wallet_taker.get_new_puzzlehash()
|
||||
ph_taker_cat_2 = await wallet_taker.get_new_puzzlehash()
|
||||
cat_tx_records = await cat_wallet_maker.generate_signed_transaction(
|
||||
cat_tx_records = await cat_wallet_maker.generate_signed_transactions(
|
||||
[cats_to_trade, cats_to_trade], [ph_taker_cat_1, ph_taker_cat_2], memos=[[ph_taker_cat_1], [ph_taker_cat_2]]
|
||||
)
|
||||
for tx_record in cat_tx_records:
|
||||
|
@ -943,7 +943,7 @@ async def test_nft_offer_request_nft_for_cat(
|
|||
extra_change = cats_to_mint - (2 * cats_to_trade)
|
||||
amounts.append(uint64(extra_change))
|
||||
puzzle_hashes.append(ph_taker_cat_1)
|
||||
cat_tx_records = await cat_wallet_maker.generate_signed_transaction(amounts, puzzle_hashes)
|
||||
cat_tx_records = await cat_wallet_maker.generate_signed_transactions(amounts, puzzle_hashes)
|
||||
for tx_record in cat_tx_records:
|
||||
await wallet_maker.wallet_state_manager.add_pending_transaction(tx_record)
|
||||
await full_node_api.process_transaction_records(records=cat_tx_records)
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
from __future__ import annotations
|
||||
|
||||
# import dataclasses
|
||||
from secrets import token_bytes
|
||||
|
||||
import pytest
|
||||
|
||||
from chia.types.blockchain_format.coin import Coin
|
||||
from chia.types.blockchain_format.program import Program
|
||||
from chia.types.blockchain_format.sized_bytes import bytes32
|
||||
from chia.types.coin_spend import CoinSpend
|
||||
from chia.util.ints import uint32, uint64
|
||||
|
||||
# from chia.wallet.dao_wallet.dao_wallet import DAOInfo, DAOWallet
|
||||
from chia.wallet.lineage_proof import LineageProof
|
||||
from chia.wallet.singleton import create_singleton_puzzle
|
||||
from chia.wallet.singleton_record import SingletonRecord
|
||||
from chia.wallet.wallet_singleton_store import WalletSingletonStore
|
||||
from tests.util.db_connection import DBConnection
|
||||
|
||||
|
||||
def get_record() -> SingletonRecord:
|
||||
launcher_id = bytes32(token_bytes(32))
|
||||
inner_puz = Program.to(1)
|
||||
inner_puz_hash = inner_puz.get_tree_hash()
|
||||
parent_puz = create_singleton_puzzle(inner_puz, launcher_id)
|
||||
parent_puz_hash = parent_puz.get_tree_hash()
|
||||
parent_coin = Coin(launcher_id, parent_puz_hash, 1)
|
||||
inner_sol = Program.to([[51, parent_puz_hash, 1]])
|
||||
lineage_proof = LineageProof(launcher_id, inner_puz.get_tree_hash(), uint64(1))
|
||||
parent_sol = Program.to([lineage_proof.to_program(), 1, inner_sol])
|
||||
parent_coinspend = CoinSpend(parent_coin, parent_puz, parent_sol)
|
||||
# child_coin = Coin(parent_coin.name(), parent_puz_hash, 1)
|
||||
wallet_id = uint32(2)
|
||||
pending = True
|
||||
removed_height = 0
|
||||
custom_data = "{'key': 'value'}"
|
||||
record = SingletonRecord(
|
||||
coin=parent_coin,
|
||||
singleton_id=launcher_id,
|
||||
wallet_id=wallet_id,
|
||||
parent_coinspend=parent_coinspend,
|
||||
inner_puzzle_hash=inner_puz_hash,
|
||||
pending=pending,
|
||||
removed_height=removed_height,
|
||||
lineage_proof=lineage_proof,
|
||||
custom_data=custom_data,
|
||||
)
|
||||
return record
|
||||
|
||||
|
||||
class TestSingletonStore:
|
||||
@pytest.mark.asyncio
|
||||
async def test_singleton_insert(self) -> None:
|
||||
async with DBConnection(1) as wrapper:
|
||||
db = await WalletSingletonStore.create(wrapper)
|
||||
record = get_record()
|
||||
await db.save_singleton(record)
|
||||
records_by_wallet = await db.get_records_by_wallet_id(record.wallet_id)
|
||||
assert records_by_wallet[0] == record
|
||||
record_by_coin_id = await db.get_records_by_coin_id(record.coin.name())
|
||||
assert record_by_coin_id[0] == record
|
||||
records_by_singleton_id = await db.get_records_by_singleton_id(record.singleton_id)
|
||||
assert records_by_singleton_id[0] == record
|
||||
# modify pending
|
||||
await db.update_pending_transaction(record.coin.name(), False)
|
||||
record_to_check = await db.get_records_by_coin_id(record.coin.name())
|
||||
assert record_to_check[0].pending is False
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_singleton_remove(self) -> None:
|
||||
async with DBConnection(1) as wrapper:
|
||||
db = await WalletSingletonStore.create(wrapper)
|
||||
record_1 = get_record()
|
||||
record_2 = get_record()
|
||||
await db.save_singleton(record_1)
|
||||
await db.save_singleton(record_2)
|
||||
resp_1 = await db.delete_singleton_by_coin_id(record_1.coin.name(), uint32(1))
|
||||
assert resp_1
|
||||
resp_2 = await db.delete_singleton_by_singleton_id(record_2.singleton_id, uint32(1))
|
||||
assert resp_2
|
||||
record = (await db.get_records_by_coin_id(record_1.coin.name()))[0]
|
||||
assert record.removed_height == 1
|
||||
record = (await db.get_records_by_coin_id(record_2.coin.name()))[0]
|
||||
assert record.removed_height == 1
|
|
@ -153,6 +153,9 @@ class ClvmBytes:
|
|||
hash=generate_hash_bytes(hex_bytes=hex_bytes),
|
||||
)
|
||||
|
||||
def strip(self) -> ClvmBytes:
|
||||
return ClvmBytes(hex=self.hex.strip(b"\n"), hash=self.hash.strip(b"\n"))
|
||||
|
||||
|
||||
# These files have the wrong extension for now so we'll just manually exclude them
|
||||
excludes: typing.Set[str] = set()
|
||||
|
@ -297,7 +300,7 @@ def check(use_cache: bool) -> int:
|
|||
|
||||
generated_bytes = ClvmBytes.from_hex_bytes(hex_bytes=generated_paths.hex.read_bytes())
|
||||
|
||||
if generated_bytes != reference_bytes:
|
||||
if generated_bytes.strip() != reference_bytes.strip():
|
||||
file_fail = True
|
||||
error = f" reference: {reference_bytes!r}\n"
|
||||
error += f" generated: {generated_bytes!r}"
|
||||
|
|
Loading…
Reference in New Issue