diff --git a/README.md b/README.md index 7533242e..491e6832 100644 --- a/README.md +++ b/README.md @@ -54,18 +54,25 @@ All system contracts will be flattened and output into `${workspace}/contracts/f 1. Edit `init_holders.js` file to alloc the initial BNB holder. 2. Edit `validators.js` file to alloc the initial validator set. -3. Run `bash scripts/generate-*.sh` to change system contracts setting. +3. Edit system contracts setting as needed. 4. Run `node scripts/generate-genesis.js` will generate genesis.json ## How to generate mainnet/testnet/QA/local genesis file +You may need install some python dependencies firstly. +Save the following content to `requirements.txt` file, and run `pip install -r requirements.txt` to install them. +```txt +Jinja2==3.1.2 +typer==0.9.0 +``` + + +Then: ```shell -bash scripts/generate.sh mainnet -bash scripts/generate.sh testnet -bash scripts/generate.sh QA -bash scripts/generate.sh local +python scripts/generate.py ${network} ``` Check the `genesis.json` file, and you can get the exact compiled bytecode for different network. +(`python scripts/generate.py --help` for more details) ## How to update contract interface for test diff --git a/scripts/generate.py b/scripts/generate.py new file mode 100644 index 00000000..e8ceebb6 --- /dev/null +++ b/scripts/generate.py @@ -0,0 +1,398 @@ +import fileinput +import os +import re +import shutil +import subprocess + +import jinja2 +import typer +from typing_extensions import Annotated + +work_dir = os.getcwd() +if work_dir.endswith("scripts"): + work_dir = work_dir[:-8] + +network: str +chain_id: int +hex_chain_id: str + +main = typer.Typer() + + +def backup_file(source, destination): + try: + shutil.copyfile(source, destination) + except FileNotFoundError: + print(f"Source file '{source}' not found.") + except PermissionError: + print(f"Permission error: Unable to copy file '{source}' to '{destination}'.") + except Exception as e: + print(f"An error occurred: {e}") + + +def insert(contract, pattern, ins): + pattern = re.compile(pattern) + filepath = os.path.join(work_dir, "contracts", contract) + + found = False + with fileinput.FileInput(filepath, inplace=True) as file: + for line in file: + if not found and pattern.search(line): + print(ins) + found = True + print(line, end="") + + if not found: + raise Exception(f"{pattern} not found") + + +def replace(contract, pattern, repl): + pattern = re.compile(pattern) + filepath = os.path.join(work_dir, "contracts", contract) + + found = False + with fileinput.FileInput(filepath, inplace=True) as file: + for line in file: + if not found and pattern.search(line): + line = pattern.sub(repl, line) + found = True + print(line, end="") + + if not found: + raise Exception(f"{pattern} not found") + + +def replace_parameter(contract, parameter, value): + pattern = rf"{parameter} = .*;" + repl = f"{parameter} = {value};" + + replace(contract, pattern, repl) + + +def convert_chain_id(int_chain_id: int): + try: + hex_representation = hex(int_chain_id)[2:] + padded_hex = hex_representation.zfill(4) + return padded_hex + except Exception as e: + print(f"Error converting {int_chain_id} to hex: {e}") + return None + + +def generate_from_template(data, template_file, output_file): + template_loader = jinja2.FileSystemLoader(work_dir) + template_env = jinja2.Environment(loader=template_loader, autoescape=True) + + template = template_env.get_template(template_file) + result_string = template.render(data) + + output_path = os.path.join(work_dir, output_file) + with open(output_path, 'w') as output_file: + output_file.write(result_string) + + +def generate_cross_chain(init_batch_size="50"): + contract = "CrossChain.sol" + backup_file( + os.path.join(work_dir, "contracts", contract), os.path.join(work_dir, "contracts", contract[:-4] + ".bak") + ) + + replace_parameter(contract, "uint256 constant public CROSS_CHAIN_KEY_PREFIX", f"0x01{hex_chain_id}00") + replace_parameter(contract, "uint256 constant public INIT_BATCH_SIZE", f"{init_batch_size}") + + +def generate_relayer_hub(whitelist_1, whitelist_2): + contract = "RelayerHub.sol" + backup_file( + os.path.join(work_dir, "contracts", contract), os.path.join(work_dir, "contracts", contract[:-4] + ".bak") + ) + + replace_parameter(contract, "address public constant WHITELIST_1", f"{whitelist_1}") + replace_parameter(contract, "address public constant WHITELIST_2", f"{whitelist_2}") + + if network == "local": + replace(contract, r"function whitelistInit\(\) external", "function whitelistInit() public") + insert(contract, "alreadyInit = true;", "\t\twhitelistInit();") + + +def generate_slash_indicator(): + if network == "local": + contract = "SlashIndicator.sol" + backup_file( + os.path.join(work_dir, "contracts", contract), os.path.join(work_dir, "contracts", contract[:-4] + ".bak") + ) + + insert(contract, "alreadyInit = true;", "\t\tenableMaliciousVoteSlash = true;") + + +def generate_system(): + contract = "System.sol" + backup_file( + os.path.join(work_dir, "contracts", contract), os.path.join(work_dir, "contracts", contract[:-4] + ".bak") + ) + + replace_parameter(contract, "uint16 constant public bscChainID", f"0x{hex_chain_id}") + + +def generate_system_reward(): + if network == "local": + contract = "SystemReward.sol" + backup_file( + os.path.join(work_dir, "contracts", contract), os.path.join(work_dir, "contracts", contract[:-4] + ".bak") + ) + + insert(contract, "numOperator = 2;", "\t\toperators[VALIDATOR_CONTRACT_ADDR] = true;") + insert(contract, "numOperator = 2;", "\t\toperators[SLASH_CONTRACT_ADDR] = true;") + replace(contract, "numOperator = 2;", "numOperator = 4;") + + +def generate_tendermint_light_client(init_consensus_state_bytes, init_reward_for_validator_ser_change="1e16"): + contract = "TendermintLightClient.sol" + backup_file( + os.path.join(work_dir, "contracts", contract), os.path.join(work_dir, "contracts", contract[:-4] + ".bak") + ) + + replace_parameter( + contract, "bytes constant public INIT_CONSENSUS_STATE_BYTES", f"hex\"{init_consensus_state_bytes}\"" + ) + replace_parameter( + contract, "uint256 constant public INIT_REWARD_FOR_VALIDATOR_SER_CHANGE", + f"{init_reward_for_validator_ser_change}" + ) + + +def generate_token_hub(max_gas_for_transfer_bnb, max_gas_for_calling_bep20, reward_upper_limit, init_minimum_relay_fee): + contract = "TokenHub.sol" + backup_file( + os.path.join(work_dir, "contracts", contract), os.path.join(work_dir, "contracts", contract[:-4] + ".bak") + ) + + replace_parameter(contract, "uint256 constant public MAX_GAS_FOR_TRANSFER_BNB", f"{max_gas_for_transfer_bnb}") + replace_parameter(contract, "uint256 constant public MAX_GAS_FOR_CALLING_BEP20", f"{max_gas_for_calling_bep20}") + replace_parameter(contract, "uint256 constant public REWARD_UPPER_LIMIT", f"{reward_upper_limit}") + replace_parameter(contract, "uint256 constant public INIT_MINIMUM_RELAY_FEE", f"{init_minimum_relay_fee}") + + +def generate_token_recover_portal(source_chain_id, approval_address, merkle_root): + contract = "BC_fusion/TokenRecoverPortal.sol" + backup_file( + os.path.join(work_dir, "contracts", contract), os.path.join(work_dir, "contracts", contract[:-4] + ".bak") + ) + + replace_parameter(contract, "string public constant SOURCE_CHAIN_ID", f"\"{source_chain_id}\"") + replace_parameter(contract, "address public approvalAddress", f"{approval_address}") + replace_parameter(contract, "bytes32 public merkleRoot", f"{merkle_root}") + + +def generate_validator_set(init_burn_ratio, init_validatorset_bytes): + contract = "BSCValidatorSet.sol" + backup_file( + os.path.join(work_dir, "contracts", contract), os.path.join(work_dir, "contracts", contract[:-4] + ".bak") + ) + + replace_parameter(contract, "uint256 public constant INIT_BURN_RATIO", f"{init_burn_ratio}") + replace_parameter(contract, "bytes public constant INIT_VALIDATORSET_BYTES", f"hex\"{init_validatorset_bytes}\"") + + if network == "local": + insert( + contract, r"for \(uint i; i