kopia lustrzana https://github.com/bugout-dev/dao
Added ERC20 initialization when facet is cut onto diamond
[WIP] ERC20 deployment checklist template [WIP] clean up code a bitpull/7/head
rodzic
3ac420c409
commit
7b832c785b
|
@ -0,0 +1,28 @@
|
||||||
|
# Deploy a diamond proxy
|
||||||
|
|
||||||
|
Moonstream DAO uses the EIP2535 Diamond proxy to manage each of its smart contracts.
|
||||||
|
|
||||||
|
This checklist describes how to deploy the proxy contract.
|
||||||
|
|
||||||
|
## Environment variables
|
||||||
|
|
||||||
|
1. `export DAO_NETWORK=<desired brownie network>`
|
||||||
|
2. `export DAO_OWNER=<path to keystore file for owner account>`
|
||||||
|
3. `export DAO_OWNER_ADDRESS=$(jq -r .address $DAO_OWNER)`
|
||||||
|
4. `export GAS_PRICE="<N> gwei"`
|
||||||
|
5. `export CONFIRMATIONS=<M>`
|
||||||
|
6. `export OUTPUT_FILE=<path to JSON file in which to store diamond addresses>`
|
||||||
|
|
||||||
|
## Deploy diamond proxy
|
||||||
|
|
||||||
|
- [ ] Deploy diamond with all core facets
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dao core gogogo \
|
||||||
|
--network $DAO_NETWORK \
|
||||||
|
--sender $DAO_OWNER \
|
||||||
|
--gas-price "$GAS_PRICE" \
|
||||||
|
--confirmations $CONFIRMATIONS \
|
||||||
|
--owner $DAO_OWNER_ADDRESS \
|
||||||
|
--outfile $OUTPUT_FILE
|
||||||
|
```
|
|
@ -0,0 +1,78 @@
|
||||||
|
# Deploy the Moonstream governance token
|
||||||
|
|
||||||
|
The Moonstream DAO governance token is deployed as an EIP2535 Diamond proxy contract with an ERC20
|
||||||
|
facet attached to it.
|
||||||
|
|
||||||
|
This checklist describes how to deploy the token.
|
||||||
|
|
||||||
|
## Deployed addresses
|
||||||
|
|
||||||
|
You will modify this section as you go through the checklist
|
||||||
|
|
||||||
|
### Diamond addresses
|
||||||
|
|
||||||
|
```json
|
||||||
|
```
|
||||||
|
|
||||||
|
### ERC20Facet address
|
||||||
|
|
||||||
|
``
|
||||||
|
|
||||||
|
## Environment variables
|
||||||
|
|
||||||
|
1. `export DAO_NETWORK=<desired brownie network>`
|
||||||
|
2. `export DAO_OWNER=<path to keystore file for owner account>`
|
||||||
|
3. `export DAO_OWNER_ADDRESS=$(jq -r .address $DAO_OWNER)`
|
||||||
|
4. `export GAS_PRICE="<N> gwei"`
|
||||||
|
5. `export CONFIRMATIONS=<M>`
|
||||||
|
6. `export MOONSTREAM_ADDRESSES=<path to JSON file in which to store diamond addresses>`
|
||||||
|
|
||||||
|
## Deploy diamond proxy
|
||||||
|
|
||||||
|
- [ ] Deploy diamond with all core facets
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dao core gogogo \
|
||||||
|
--network $DAO_NETWORK \
|
||||||
|
--sender $DAO_OWNER \
|
||||||
|
--gas-price "$GAS_PRICE" \
|
||||||
|
--confirmations $CONFIRMATIONS \
|
||||||
|
--owner $DAO_OWNER_ADDRESS \
|
||||||
|
--outfile $MOONSTREAM_ADDRESSES
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] Store JSON output under `Deployed addresses / Diamond addresses` above.
|
||||||
|
|
||||||
|
## Attach ERC20 functionality
|
||||||
|
|
||||||
|
- [ ] Export diamond proxy address: `export MOONSTREAM_DIAMOND="$(jq -r .Diamond $MOONSTREAM_ADDRESSES)"`
|
||||||
|
|
||||||
|
- [ ] Deploy `ERC20Facet` contract.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dao moonstream deploy \
|
||||||
|
--network $DAO_NETWORK \
|
||||||
|
--sender $DAO_OWNER \
|
||||||
|
--gas-price "$GAS_PRICE" \
|
||||||
|
--confirmations $CONFIRMATIONS
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] Export address of deployed contract as `export ERC20FACET_ADDRESS=<address>`
|
||||||
|
|
||||||
|
- [ ] Store address of deployed contract under `Deployed addresses / ERC20Facet address` above
|
||||||
|
|
||||||
|
- [ ] Attach `ERC20Facet` to diamond:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dao core facet-cut \
|
||||||
|
--address $MOONSTREAM_DIAMOND \
|
||||||
|
--network $DAO_NETWORK \
|
||||||
|
--sender $DAO_OWNER \
|
||||||
|
--gas-price "$GAS_PRICE" \
|
||||||
|
--confirmations $CONFIRMATIONS \
|
||||||
|
--facet-name ERC20Facet \
|
||||||
|
--facet-address $ERC20FACET_ADDRESS \
|
||||||
|
--action add
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] Check the ERC20 name of the diamond contract
|
|
@ -11,9 +11,10 @@ pragma solidity ^0.8.0;
|
||||||
|
|
||||||
import "./ERC20WithCommonStorage.sol";
|
import "./ERC20WithCommonStorage.sol";
|
||||||
import "./LibERC20.sol";
|
import "./LibERC20.sol";
|
||||||
|
import "../diamond/libraries/LibDiamond.sol";
|
||||||
|
|
||||||
contract ERC20Facet is ERC20WithCommonStorage {
|
contract ERC20Facet is ERC20WithCommonStorage {
|
||||||
constructor() ERC20WithCommonStorage("Moonstream", "MNSTR") {}
|
constructor() {}
|
||||||
|
|
||||||
function mint(address account, uint256 amount) external {
|
function mint(address account, uint256 amount) external {
|
||||||
LibERC20.enforceIsController();
|
LibERC20.enforceIsController();
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authors: Moonstream Engineering (engineering@moonstream.to)
|
||||||
|
* GitHub: https://github.com/bugout-dev/dao
|
||||||
|
*
|
||||||
|
* Initializer for Moonstream DAO governance token. Used when mounting a new ERC20Facet onto its
|
||||||
|
* diamond proxy.
|
||||||
|
*/
|
||||||
|
|
||||||
|
pragma solidity ^0.8.0;
|
||||||
|
|
||||||
|
import "@openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
|
||||||
|
import "../diamond/libraries/LibDiamond.sol";
|
||||||
|
import "./LibERC20.sol";
|
||||||
|
|
||||||
|
contract ERC20Initializer {
|
||||||
|
function init() external {
|
||||||
|
LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage();
|
||||||
|
ds.supportedInterfaces[type(IERC20).interfaceId] = true;
|
||||||
|
|
||||||
|
LibERC20.ERC20Storage storage es = LibERC20.erc20Storage();
|
||||||
|
es.controller = msg.sender;
|
||||||
|
es.name = "Moonstream DAO";
|
||||||
|
es.symbol = "MNSTR";
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,7 +32,10 @@ contract ERC20WithCommonStorage is Context, IERC20, IERC20Metadata {
|
||||||
* All two of these values are immutable: they can only be set once during
|
* All two of these values are immutable: they can only be set once during
|
||||||
* construction.
|
* construction.
|
||||||
*/
|
*/
|
||||||
constructor(string memory name_, string memory symbol_) {
|
function setERC20Metadata(string memory name_, string memory symbol_)
|
||||||
|
external
|
||||||
|
{
|
||||||
|
LibERC20.enforceIsController();
|
||||||
LibERC20.ERC20Storage storage es = LibERC20.erc20Storage();
|
LibERC20.ERC20Storage storage es = LibERC20.erc20Storage();
|
||||||
es.name = name_;
|
es.name = name_;
|
||||||
es.symbol = symbol_;
|
es.symbol = symbol_;
|
||||||
|
|
|
@ -128,6 +128,10 @@ class ERC20Facet:
|
||||||
self.assert_contract_is_instantiated()
|
self.assert_contract_is_instantiated()
|
||||||
return self.contract.name.call()
|
return self.contract.name.call()
|
||||||
|
|
||||||
|
def set_erc20_metadata(self, name_: str, symbol_: str, transaction_config) -> Any:
|
||||||
|
self.assert_contract_is_instantiated()
|
||||||
|
return self.contract.setERC20Metadata(name_, symbol_, transaction_config)
|
||||||
|
|
||||||
def symbol(self) -> Any:
|
def symbol(self) -> Any:
|
||||||
self.assert_contract_is_instantiated()
|
self.assert_contract_is_instantiated()
|
||||||
return self.contract.symbol.call()
|
return self.contract.symbol.call()
|
||||||
|
@ -271,6 +275,18 @@ def handle_name(args: argparse.Namespace) -> None:
|
||||||
print(result)
|
print(result)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_set_erc20_metadata(args: argparse.Namespace) -> None:
|
||||||
|
network.connect(args.network)
|
||||||
|
contract = ERC20Facet(args.address)
|
||||||
|
transaction_config = get_transaction_config(args)
|
||||||
|
result = contract.set_erc20_metadata(
|
||||||
|
name_=args.name_arg,
|
||||||
|
symbol_=args.symbol_arg,
|
||||||
|
transaction_config=transaction_config,
|
||||||
|
)
|
||||||
|
print(result)
|
||||||
|
|
||||||
|
|
||||||
def handle_symbol(args: argparse.Namespace) -> None:
|
def handle_symbol(args: argparse.Namespace) -> None:
|
||||||
network.connect(args.network)
|
network.connect(args.network)
|
||||||
contract = ERC20Facet(args.address)
|
contract = ERC20Facet(args.address)
|
||||||
|
@ -372,6 +388,16 @@ def generate_cli() -> argparse.ArgumentParser:
|
||||||
add_default_arguments(name_parser, False)
|
add_default_arguments(name_parser, False)
|
||||||
name_parser.set_defaults(func=handle_name)
|
name_parser.set_defaults(func=handle_name)
|
||||||
|
|
||||||
|
set_erc20_metadata_parser = subcommands.add_parser("set-erc20-metadata")
|
||||||
|
add_default_arguments(set_erc20_metadata_parser, True)
|
||||||
|
set_erc20_metadata_parser.add_argument(
|
||||||
|
"--name-arg", required=True, help="Type: string", type=str
|
||||||
|
)
|
||||||
|
set_erc20_metadata_parser.add_argument(
|
||||||
|
"--symbol-arg", required=True, help="Type: string", type=str
|
||||||
|
)
|
||||||
|
set_erc20_metadata_parser.set_defaults(func=handle_set_erc20_metadata)
|
||||||
|
|
||||||
symbol_parser = subcommands.add_parser("symbol")
|
symbol_parser = subcommands.add_parser("symbol")
|
||||||
add_default_arguments(symbol_parser, False)
|
add_default_arguments(symbol_parser, False)
|
||||||
symbol_parser.set_defaults(func=handle_symbol)
|
symbol_parser.set_defaults(func=handle_symbol)
|
||||||
|
|
|
@ -0,0 +1,175 @@
|
||||||
|
# Code generated by moonworm : https://github.com/bugout-dev/moonworm
|
||||||
|
# Moonworm version : 0.1.8
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Dict, List, Optional, Union
|
||||||
|
|
||||||
|
from brownie import Contract, network, project
|
||||||
|
from brownie.network.contract import ContractContainer
|
||||||
|
from eth_typing.evm import ChecksumAddress
|
||||||
|
|
||||||
|
|
||||||
|
PROJECT_DIRECTORY = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||||||
|
BUILD_DIRECTORY = os.path.join(PROJECT_DIRECTORY, "build", "contracts")
|
||||||
|
|
||||||
|
|
||||||
|
def boolean_argument_type(raw_value: str) -> bool:
|
||||||
|
TRUE_VALUES = ["1", "t", "y", "true", "yes"]
|
||||||
|
FALSE_VALUES = ["0", "f", "n", "false", "no"]
|
||||||
|
|
||||||
|
if raw_value.lower() in TRUE_VALUES:
|
||||||
|
return True
|
||||||
|
elif raw_value.lower() in FALSE_VALUES:
|
||||||
|
return False
|
||||||
|
|
||||||
|
raise ValueError(
|
||||||
|
f"Invalid boolean argument: {raw_value}. Value must be one of: {','.join(TRUE_VALUES + FALSE_VALUES)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def bytes_argument_type(raw_value: str) -> bytes:
|
||||||
|
return raw_value.encode()
|
||||||
|
|
||||||
|
|
||||||
|
def get_abi_json(abi_name: str) -> List[Dict[str, Any]]:
|
||||||
|
abi_full_path = os.path.join(BUILD_DIRECTORY, f"{abi_name}.json")
|
||||||
|
if not os.path.isfile(abi_full_path):
|
||||||
|
raise IOError(
|
||||||
|
f"File does not exist: {abi_full_path}. Maybe you have to compile the smart contracts?"
|
||||||
|
)
|
||||||
|
|
||||||
|
with open(abi_full_path, "r") as ifp:
|
||||||
|
build = json.load(ifp)
|
||||||
|
|
||||||
|
abi_json = build.get("abi")
|
||||||
|
if abi_json is None:
|
||||||
|
raise ValueError(f"Could not find ABI definition in: {abi_full_path}")
|
||||||
|
|
||||||
|
return abi_json
|
||||||
|
|
||||||
|
|
||||||
|
def contract_from_build(abi_name: str) -> ContractContainer:
|
||||||
|
# This is workaround because brownie currently doesn't support loading the same project multiple
|
||||||
|
# times. This causes problems when using multiple contracts from the same project in the same
|
||||||
|
# python project.
|
||||||
|
PROJECT = project.main.Project("moonworm", Path(PROJECT_DIRECTORY))
|
||||||
|
|
||||||
|
abi_full_path = os.path.join(BUILD_DIRECTORY, f"{abi_name}.json")
|
||||||
|
if not os.path.isfile(abi_full_path):
|
||||||
|
raise IOError(
|
||||||
|
f"File does not exist: {abi_full_path}. Maybe you have to compile the smart contracts?"
|
||||||
|
)
|
||||||
|
|
||||||
|
with open(abi_full_path, "r") as ifp:
|
||||||
|
build = json.load(ifp)
|
||||||
|
|
||||||
|
return ContractContainer(PROJECT, build)
|
||||||
|
|
||||||
|
|
||||||
|
class ERC20Initializer:
|
||||||
|
def __init__(self, contract_address: Optional[ChecksumAddress]):
|
||||||
|
self.contract_name = "ERC20Initializer"
|
||||||
|
self.address = contract_address
|
||||||
|
self.contract = None
|
||||||
|
self.abi = get_abi_json("ERC20Initializer")
|
||||||
|
if self.address is not None:
|
||||||
|
self.contract: Optional[Contract] = Contract.from_abi(
|
||||||
|
self.contract_name, self.address, self.abi
|
||||||
|
)
|
||||||
|
|
||||||
|
def deploy(self, transaction_config):
|
||||||
|
contract_class = contract_from_build(self.contract_name)
|
||||||
|
deployed_contract = contract_class.deploy(transaction_config)
|
||||||
|
self.address = deployed_contract.address
|
||||||
|
self.contract = deployed_contract
|
||||||
|
|
||||||
|
def assert_contract_is_instantiated(self) -> None:
|
||||||
|
if self.contract is None:
|
||||||
|
raise Exception("contract has not been instantiated")
|
||||||
|
|
||||||
|
def init(self, transaction_config) -> Any:
|
||||||
|
self.assert_contract_is_instantiated()
|
||||||
|
return self.contract.init(transaction_config)
|
||||||
|
|
||||||
|
|
||||||
|
def get_transaction_config(args: argparse.Namespace) -> Dict[str, Any]:
|
||||||
|
signer = network.accounts.load(args.sender, args.password)
|
||||||
|
transaction_config: Dict[str, Any] = {"from": signer}
|
||||||
|
if args.gas_price is not None:
|
||||||
|
transaction_config["gas_price"] = args.gas_price
|
||||||
|
if args.confirmations is not None:
|
||||||
|
transaction_config["required_confs"] = args.confirmations
|
||||||
|
return transaction_config
|
||||||
|
|
||||||
|
|
||||||
|
def add_default_arguments(parser: argparse.ArgumentParser, transact: bool) -> None:
|
||||||
|
parser.add_argument(
|
||||||
|
"--network", required=True, help="Name of brownie network to connect to"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--address", required=False, help="Address of deployed contract to connect to"
|
||||||
|
)
|
||||||
|
if not transact:
|
||||||
|
return
|
||||||
|
parser.add_argument(
|
||||||
|
"--sender", required=True, help="Path to keystore file for transaction sender"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--password",
|
||||||
|
required=False,
|
||||||
|
help="Password to keystore file (if you do not provide it, you will be prompted for it)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--gas-price", default=None, help="Gas price at which to submit transaction"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--confirmations",
|
||||||
|
type=int,
|
||||||
|
default=None,
|
||||||
|
help="Number of confirmations to await before considering a transaction completed",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_deploy(args: argparse.Namespace) -> None:
|
||||||
|
network.connect(args.network)
|
||||||
|
transaction_config = get_transaction_config(args)
|
||||||
|
contract = ERC20Initializer(None)
|
||||||
|
result = contract.deploy(transaction_config=transaction_config)
|
||||||
|
print(result)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_init(args: argparse.Namespace) -> None:
|
||||||
|
network.connect(args.network)
|
||||||
|
contract = ERC20Initializer(args.address)
|
||||||
|
transaction_config = get_transaction_config(args)
|
||||||
|
result = contract.init(transaction_config=transaction_config)
|
||||||
|
print(result)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_cli() -> argparse.ArgumentParser:
|
||||||
|
parser = argparse.ArgumentParser(description="CLI for ERC20Initializer")
|
||||||
|
parser.set_defaults(func=lambda _: parser.print_help())
|
||||||
|
subcommands = parser.add_subparsers()
|
||||||
|
|
||||||
|
deploy_parser = subcommands.add_parser("deploy")
|
||||||
|
add_default_arguments(deploy_parser, True)
|
||||||
|
deploy_parser.set_defaults(func=handle_deploy)
|
||||||
|
|
||||||
|
init_parser = subcommands.add_parser("init")
|
||||||
|
add_default_arguments(init_parser, True)
|
||||||
|
init_parser.set_defaults(func=handle_init)
|
||||||
|
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
parser = generate_cli()
|
||||||
|
args = parser.parse_args()
|
||||||
|
args.func(args)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
|
@ -1,6 +1,6 @@
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
from . import core, ERC20Facet
|
from . import core, ERC20Facet, ERC20Initializer
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
@ -16,6 +16,13 @@ def main():
|
||||||
moonstream_parser = ERC20Facet.generate_cli()
|
moonstream_parser = ERC20Facet.generate_cli()
|
||||||
dao_subparsers.add_parser("moonstream", parents=[moonstream_parser], add_help=False)
|
dao_subparsers.add_parser("moonstream", parents=[moonstream_parser], add_help=False)
|
||||||
|
|
||||||
|
moonstream_initializer_parser = ERC20Initializer.generate_cli()
|
||||||
|
dao_subparsers.add_parser(
|
||||||
|
"moonstream-initializer",
|
||||||
|
parents=[moonstream_initializer_parser],
|
||||||
|
add_help=False,
|
||||||
|
)
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
args.func(args)
|
args.func(args)
|
||||||
|
|
||||||
|
|
120
dao/core.py
120
dao/core.py
|
@ -3,7 +3,9 @@ Generic diamond functionality for Moonstream contracts.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
from typing import Any, Dict, List, Optional, Set
|
from typing import Any, Dict, List, Optional, Set
|
||||||
|
|
||||||
from brownie import network
|
from brownie import network
|
||||||
|
@ -14,6 +16,7 @@ from . import (
|
||||||
DiamondCutFacet,
|
DiamondCutFacet,
|
||||||
DiamondLoupeFacet,
|
DiamondLoupeFacet,
|
||||||
ERC20Facet,
|
ERC20Facet,
|
||||||
|
ERC20Initializer,
|
||||||
OwnershipFacet,
|
OwnershipFacet,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -42,6 +45,7 @@ def facet_cut(
|
||||||
facet_address: str,
|
facet_address: str,
|
||||||
action: str,
|
action: str,
|
||||||
transaction_config: Dict[str, Any],
|
transaction_config: Dict[str, Any],
|
||||||
|
initializer_address: str = ZERO_ADDRESS,
|
||||||
ignore_methods: Optional[List[str]] = None,
|
ignore_methods: Optional[List[str]] = None,
|
||||||
ignore_selectors: Optional[List[str]] = None,
|
ignore_selectors: Optional[List[str]] = None,
|
||||||
) -> Any:
|
) -> Any:
|
||||||
|
@ -98,13 +102,96 @@ def facet_cut(
|
||||||
facet_function_selectors,
|
facet_function_selectors,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
calldata = b""
|
||||||
|
if facet_name == "ERC20Facet":
|
||||||
|
if initializer_address != ZERO_ADDRESS and action != "remove":
|
||||||
|
erc20_initializer = ERC20Initializer.ERC20Initializer(initializer_address)
|
||||||
|
calldata = erc20_initializer.contract.init.encode_input()
|
||||||
|
|
||||||
diamond = DiamondCutFacet.DiamondCutFacet(diamond_address)
|
diamond = DiamondCutFacet.DiamondCutFacet(diamond_address)
|
||||||
transaction = diamond.diamond_cut(
|
transaction = diamond.diamond_cut(
|
||||||
[diamond_cut_action], ZERO_ADDRESS, b"", transaction_config
|
[diamond_cut_action], initializer_address, calldata, transaction_config
|
||||||
)
|
)
|
||||||
return transaction
|
return transaction
|
||||||
|
|
||||||
|
|
||||||
|
def gogogo(owner_address: str, transaction_config: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Deploy diamond along with all its basic facets and attach those facets to the diamond.
|
||||||
|
|
||||||
|
Returns addresses of all the deployed contracts with the contract names as keys.
|
||||||
|
"""
|
||||||
|
result: Dict[str, Any] = {}
|
||||||
|
|
||||||
|
try:
|
||||||
|
diamond_cut_facet = DiamondCutFacet.DiamondCutFacet(None)
|
||||||
|
diamond_cut_facet.deploy(transaction_config)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
result["error"] = "Failed to deploy DiamondCutFacet"
|
||||||
|
return result
|
||||||
|
result["DiamondCutFacet"] = diamond_cut_facet.address
|
||||||
|
|
||||||
|
try:
|
||||||
|
diamond = Diamond.Diamond(None)
|
||||||
|
diamond.deploy(owner_address, diamond_cut_facet.address, transaction_config)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
result["error"] = "Failed to deploy Diamond"
|
||||||
|
return result
|
||||||
|
result["Diamond"] = diamond.address
|
||||||
|
|
||||||
|
try:
|
||||||
|
diamond_loupe_facet = DiamondLoupeFacet.DiamondLoupeFacet(None)
|
||||||
|
diamond_loupe_facet.deploy(transaction_config)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
result["error"] = "Failed to deploy DiamondLoupeFacet"
|
||||||
|
return result
|
||||||
|
result["DiamondLoupeFacet"] = diamond_loupe_facet.address
|
||||||
|
|
||||||
|
try:
|
||||||
|
ownership_facet = OwnershipFacet.OwnershipFacet(None)
|
||||||
|
ownership_facet.deploy(transaction_config)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
result["error"] = "Failed to deploy OwnershipFacet"
|
||||||
|
return result
|
||||||
|
result["OwnershipFacet"] = ownership_facet.address
|
||||||
|
|
||||||
|
result["attached"] = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
facet_cut(
|
||||||
|
diamond.address,
|
||||||
|
"DiamondLoupeFacet",
|
||||||
|
diamond_loupe_facet.address,
|
||||||
|
"add",
|
||||||
|
transaction_config,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
result["error"] = "Failed to attach DiamondLoupeFacet"
|
||||||
|
return result
|
||||||
|
result["attached"].append("DiamondLoupeFacet")
|
||||||
|
|
||||||
|
try:
|
||||||
|
facet_cut(
|
||||||
|
diamond.address,
|
||||||
|
"OwnershipFacet",
|
||||||
|
ownership_facet.address,
|
||||||
|
"add",
|
||||||
|
transaction_config,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
result["error"] = "Failed to attach OwnershipFacet"
|
||||||
|
return result
|
||||||
|
result["attached"].append("OwnershipFacet")
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def handle_facet_cut(args: argparse.Namespace) -> None:
|
def handle_facet_cut(args: argparse.Namespace) -> None:
|
||||||
network.connect(args.network)
|
network.connect(args.network)
|
||||||
diamond_address = args.address
|
diamond_address = args.address
|
||||||
|
@ -118,11 +205,23 @@ def handle_facet_cut(args: argparse.Namespace) -> None:
|
||||||
facet_address,
|
facet_address,
|
||||||
action,
|
action,
|
||||||
transaction_config,
|
transaction_config,
|
||||||
|
initializer_address=args.initializer_address,
|
||||||
ignore_methods=args.ignore_methods,
|
ignore_methods=args.ignore_methods,
|
||||||
ignore_selectors=args.ignore_selectors,
|
ignore_selectors=args.ignore_selectors,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_gogogo(args: argparse.Namespace) -> None:
|
||||||
|
network.connect(args.network)
|
||||||
|
owner_address = args.owner
|
||||||
|
transaction_config = Diamond.get_transaction_config(args)
|
||||||
|
result = gogogo(owner_address, transaction_config)
|
||||||
|
if args.outfile is not None:
|
||||||
|
with args.outfile:
|
||||||
|
json.dump(result, args.outfile)
|
||||||
|
json.dump(result, sys.stdout, indent=4)
|
||||||
|
|
||||||
|
|
||||||
def generate_cli() -> argparse.ArgumentParser:
|
def generate_cli() -> argparse.ArgumentParser:
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="CLI to manage Moonstream DAO diamond contracts",
|
description="CLI to manage Moonstream DAO diamond contracts",
|
||||||
|
@ -153,6 +252,11 @@ def generate_cli() -> argparse.ArgumentParser:
|
||||||
choices=FACET_ACTIONS,
|
choices=FACET_ACTIONS,
|
||||||
help="Diamond cut action to take on entire facet",
|
help="Diamond cut action to take on entire facet",
|
||||||
)
|
)
|
||||||
|
facet_cut_parser.add_argument(
|
||||||
|
"--initializer-address",
|
||||||
|
default=ZERO_ADDRESS,
|
||||||
|
help=f"Address of contract to run as initializer after cut (default: {ZERO_ADDRESS})",
|
||||||
|
)
|
||||||
facet_cut_parser.add_argument(
|
facet_cut_parser.add_argument(
|
||||||
"--ignore-methods",
|
"--ignore-methods",
|
||||||
nargs="+",
|
nargs="+",
|
||||||
|
@ -165,6 +269,20 @@ def generate_cli() -> argparse.ArgumentParser:
|
||||||
)
|
)
|
||||||
facet_cut_parser.set_defaults(func=handle_facet_cut)
|
facet_cut_parser.set_defaults(func=handle_facet_cut)
|
||||||
|
|
||||||
|
gogogo_parser = subcommands.add_parser("gogogo")
|
||||||
|
Diamond.add_default_arguments(gogogo_parser, transact=True)
|
||||||
|
gogogo_parser.add_argument(
|
||||||
|
"--owner", required=True, help="Address of owner of diamond proxy"
|
||||||
|
)
|
||||||
|
gogogo_parser.add_argument(
|
||||||
|
"-o",
|
||||||
|
"--outfile",
|
||||||
|
type=argparse.FileType("w"),
|
||||||
|
default=None,
|
||||||
|
help="(Optional) file to write deployed addresses to",
|
||||||
|
)
|
||||||
|
gogogo_parser.set_defaults(func=handle_gogogo)
|
||||||
|
|
||||||
DiamondCutFacet_parser = DiamondCutFacet.generate_cli()
|
DiamondCutFacet_parser = DiamondCutFacet.generate_cli()
|
||||||
subcommands.add_parser(
|
subcommands.add_parser(
|
||||||
"diamond-cut", parents=[DiamondCutFacet_parser], add_help=False
|
"diamond-cut", parents=[DiamondCutFacet_parser], add_help=False
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from brownie import accounts, network
|
||||||
|
|
||||||
|
from dao.core import gogogo
|
||||||
|
|
||||||
|
|
||||||
|
class MoonstreamDAOTestCase(unittest.TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls) -> None:
|
||||||
|
try:
|
||||||
|
network.connect()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
cls.contracts = gogogo(accounts[0], {"from": accounts[0]})
|
||||||
|
|
||||||
|
|
||||||
|
class TestCoreDeployment(MoonstreamDAOTestCase):
|
||||||
|
def test_gogogo(self):
|
||||||
|
self.assertIn("DiamondCutFacet", self.contracts)
|
||||||
|
self.assertIn("Diamond", self.contracts)
|
||||||
|
self.assertIn("DiamondLoupeFacet", self.contracts)
|
||||||
|
self.assertIn("OwnershipFacet", self.contracts)
|
||||||
|
self.assertIn("attached", self.contracts)
|
||||||
|
|
||||||
|
self.assertListEqual(
|
||||||
|
self.contracts["attached"],
|
||||||
|
["DiamondLoupeFacet", "OwnershipFacet"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
|
@ -0,0 +1,69 @@
|
||||||
|
from brownie import accounts
|
||||||
|
|
||||||
|
from . import ERC20Facet, ERC20Initializer
|
||||||
|
from .core import facet_cut
|
||||||
|
from .test_core import MoonstreamDAOTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestDeployment(MoonstreamDAOTestCase):
|
||||||
|
def test_add_and_replace(self):
|
||||||
|
initializer = ERC20Initializer.ERC20Initializer(None)
|
||||||
|
initializer.deploy({"from": accounts[0]})
|
||||||
|
|
||||||
|
erc20_facet = ERC20Facet.ERC20Facet(None)
|
||||||
|
erc20_facet.deploy({"from": accounts[0]})
|
||||||
|
|
||||||
|
diamond_address = self.contracts["Diamond"]
|
||||||
|
facet_cut(
|
||||||
|
diamond_address,
|
||||||
|
"ERC20Facet",
|
||||||
|
erc20_facet.address,
|
||||||
|
"add",
|
||||||
|
{"from": accounts[0]},
|
||||||
|
initializer.address,
|
||||||
|
)
|
||||||
|
|
||||||
|
diamond_erc20 = ERC20Facet.ERC20Facet(diamond_address)
|
||||||
|
name = diamond_erc20.name()
|
||||||
|
expected_name = "Moonstream DAO"
|
||||||
|
self.assertEqual(name, expected_name)
|
||||||
|
|
||||||
|
symbol = diamond_erc20.symbol()
|
||||||
|
expected_symbol = "MNSTR"
|
||||||
|
self.assertEqual(symbol, expected_symbol)
|
||||||
|
|
||||||
|
decimals = diamond_erc20.decimals()
|
||||||
|
expected_decimals = 18
|
||||||
|
self.assertEqual(decimals, expected_decimals)
|
||||||
|
|
||||||
|
with self.assertRaises(Exception):
|
||||||
|
diamond_erc20.set_erc20_metadata("LOL", "ROFL", {"from": accounts[1]})
|
||||||
|
|
||||||
|
diamond_erc20.set_erc20_metadata("LOL", "ROFL", {"from": accounts[0]})
|
||||||
|
|
||||||
|
name = diamond_erc20.name()
|
||||||
|
expected_name = "LOL"
|
||||||
|
self.assertEqual(name, expected_name)
|
||||||
|
|
||||||
|
symbol = diamond_erc20.symbol()
|
||||||
|
expected_symbol = "ROFL"
|
||||||
|
self.assertEqual(symbol, expected_symbol)
|
||||||
|
|
||||||
|
new_erc20_facet = ERC20Facet.ERC20Facet(None)
|
||||||
|
new_erc20_facet.deploy({"from": accounts[0]})
|
||||||
|
facet_cut(
|
||||||
|
diamond_address,
|
||||||
|
"ERC20Facet",
|
||||||
|
new_erc20_facet.address,
|
||||||
|
"replace",
|
||||||
|
{"from": accounts[0]},
|
||||||
|
initializer.address,
|
||||||
|
)
|
||||||
|
|
||||||
|
name = diamond_erc20.name()
|
||||||
|
expected_name = "Moonstream DAO"
|
||||||
|
self.assertEqual(name, expected_name)
|
||||||
|
|
||||||
|
symbol = diamond_erc20.symbol()
|
||||||
|
expected_symbol = "MNSTR"
|
||||||
|
self.assertEqual(symbol, expected_symbol)
|
|
@ -0,0 +1,18 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Expects a Python environment to be active in which `dao` has been installed for development.
|
||||||
|
# You can set up the local copy of `dao` for development using:
|
||||||
|
# pip install -e .[dev]
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
echo "Usage: $0"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ "$1" = "-h" ] || [ "$1" = "--help" ]
|
||||||
|
then
|
||||||
|
usage
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
brownie compile
|
||||||
|
python -m unittest discover
|
Ładowanie…
Reference in New Issue