diff --git a/README.md b/README.md index 36d8126c..d9373cae 100644 --- a/README.md +++ b/README.md @@ -149,7 +149,7 @@ You can use `new-mnemonic --help` to see all arguments. Note that if there are m | `--mnemonic_language` | String. Options: `简体中文`, `繁體中文`, `český jazyk`, `English`, `Italiano`, `한국어`, `Português`, `Español`. Default to `English` | The language of the mnemonic word list | | `--folder` | String. Pointing to `./validator_keys` by default | The folder path for the keystore(s) and deposit(s) | | `--chain` | String. `mainnet` by default | The chain setting for the signing domain. | -| `--eth1_withdrawal_address` | String. Eth1 address in hexadecimal encoded form | If this field is set and valid, the given Eth1 address will be used to create the withdrawal credentials. Otherwise, it will generate withdrawal credentials with the mnemonic-derived withdrawal public key in [ERC-2334 format](https://eips.ethereum.org/EIPS/eip-2334#eth2-specific-parameters). | +| `--execution_address` (or `--eth1_withdrawal_address`) | String. Eth1 address in hexadecimal encoded form | If this field is set and valid, the given Eth1 address will be used to create the withdrawal credentials. Otherwise, it will generate withdrawal credentials with the mnemonic-derived withdrawal public key in [ERC-2334 format](https://eips.ethereum.org/EIPS/eip-2334#eth2-specific-parameters). | ###### `existing-mnemonic` Arguments @@ -161,7 +161,7 @@ You can use `existing-mnemonic --help` to see all arguments. Note that if there | `--num_validators` | Non-negative integer | The number of new signing keys you want to generate. Note that the child key(s) are generated via the same master key. | | `--folder` | String. Pointing to `./validator_keys` by default | The folder path for the keystore(s) and deposit(s) | | `--chain` | String. `mainnet` by default | The chain setting for the signing domain. | -| `--eth1_withdrawal_address` | String. Eth1 address in hexadecimal encoded form | If this field is set and valid, the given Eth1 address will be used to create the withdrawal credentials. Otherwise, it will generate withdrawal credentials with the mnemonic-derived withdrawal public key in [ERC-2334 format](https://eips.ethereum.org/EIPS/eip-2334#eth2-specific-parameters). | +| `--execution_address` (or `--eth1_withdrawal_address`) | String. Eth1 address in hexadecimal encoded form | If this field is set and valid, the given Eth1 address will be used to create the withdrawal credentials. Otherwise, it will generate withdrawal credentials with the mnemonic-derived withdrawal public key in [ERC-2334 format](https://eips.ethereum.org/EIPS/eip-2334#eth2-specific-parameters). | ###### Successful message @@ -191,7 +191,7 @@ You can use `bls-to-execution-change --help` to see all arguments. Note that if | `--validator_start_index` | Non-negative integer | The index position for the keys to start generating withdrawal credentials in [ERC-2334 format](https://eips.ethereum.org/EIPS/eip-2334#eth2-specific-parameters). | | `--validator_indices` | String of integer(s) | A list of the chosen validator index number(s) as identified on the beacon chain. Split multiple items with whitespaces or commas. | | `--bls_withdrawal_credentials_list` | String of hexstring(s). | A list of the old BLS withdrawal credentials of the given validator(s). It is for confirming you are using the correct keys. Split multiple items with whitespaces or commas. | -| `--execution_address` | String. 20-byte Execution (Eth1) address in hexadecimal encoded form | The execution (Eth1) address you want to change to for withdrawals. | +| `--execution_address` (or `--eth1_withdrawal_address`) | String. Eth1 address in hexadecimal encoded form | If this field is set and valid, the given Eth1 address will be used to create the withdrawal credentials. Otherwise, it will generate withdrawal credentials with the mnemonic-derived withdrawal public key in [ERC-2334 format](https://eips.ethereum.org/EIPS/eip-2334#eth2-specific-parameters). | | `--devnet_chain_setting` | String. JSON string `'{"network_name": "", "genesis_fork_version": "", "genesis_validator_root": ""}'` | The custom chain setting of a devnet or testnet. Note that it will override your `--chain` choice. | #### Option 2. Build `deposit-cli` with native Python diff --git a/staking_deposit/cli/generate_bls_to_execution_change.py b/staking_deposit/cli/generate_bls_to_execution_change.py index 1cba20a0..0b3b0f49 100644 --- a/staking_deposit/cli/generate_bls_to_execution_change.py +++ b/staking_deposit/cli/generate_bls_to_execution_change.py @@ -116,7 +116,7 @@ def get_password(text: str) -> str: lambda: load_text(['arg_execution_address', 'mismatch'], func=FUNC_NAME), ), help=lambda: load_text(['arg_execution_address', 'help'], func=FUNC_NAME), - param_decls='--execution_address', + param_decls=['--execution_address', '--eth1_withdrawal_address'], prompt=lambda: load_text(['arg_execution_address', 'prompt'], func=FUNC_NAME), ) @jit_option( diff --git a/staking_deposit/cli/generate_keys.py b/staking_deposit/cli/generate_keys.py index 69fa9b0b..9163d55c 100644 --- a/staking_deposit/cli/generate_keys.py +++ b/staking_deposit/cli/generate_keys.py @@ -102,7 +102,7 @@ def generate_keys_arguments_decorator(function: Callable[..., Any]) -> Callable[ ), default=None, help=lambda: load_text(['arg_execution_address', 'help'], func='generate_keys_arguments_decorator'), - param_decls='--eth1_withdrawal_address', + param_decls=['--execution_address', '--eth1_withdrawal_address'], ), ] for decorator in reversed(decorators): @@ -114,7 +114,7 @@ def generate_keys_arguments_decorator(function: Callable[..., Any]) -> Callable[ @click.pass_context def generate_keys(ctx: click.Context, validator_start_index: int, num_validators: int, folder: str, chain: str, keystore_password: str, - eth1_withdrawal_address: HexAddress, **kwargs: Any) -> None: + execution_address: HexAddress, **kwargs: Any) -> None: mnemonic = ctx.obj['mnemonic'] mnemonic_password = ctx.obj['mnemonic_password'] amounts = [MAX_DEPOSIT_AMOUNT] * num_validators @@ -132,7 +132,7 @@ def generate_keys(ctx: click.Context, validator_start_index: int, amounts=amounts, chain_setting=chain_setting, start_index=validator_start_index, - hex_eth1_withdrawal_address=eth1_withdrawal_address, + hex_eth1_withdrawal_address=execution_address, ) keystore_filefolders = credentials.export_keystores(password=keystore_password, folder=folder) deposits_file = credentials.export_deposit_data_json(folder=folder) diff --git a/staking_deposit/utils/click.py b/staking_deposit/utils/click.py index 8ade0e02..ceb36d64 100644 --- a/staking_deposit/utils/click.py +++ b/staking_deposit/utils/click.py @@ -25,7 +25,7 @@ class JITOption(click.Option): ''' def __init__( self, - param_decls: str, + param_decls: Union[str, Sequence[str]], default: Union[Callable[[], Any], None, Any] = None, help: Union[Callable[[], str], str, None] = None, prompt: Union[Callable[[], str], str, None] = None, @@ -36,8 +36,12 @@ def __init__( self.callable_help = help self.callable_prompt = prompt + # `click.Option.Argument.param_decls` takes a list of flags or argument names. + if isinstance(param_decls, str): + param_decls = [_value_of(param_decls)] + return super().__init__( - param_decls=[_value_of(param_decls)], + param_decls=param_decls, default=_value_of(default), help=_value_of(help), prompt=_value_of(prompt), diff --git a/tests/test_cli/test_new_mnemonic.py b/tests/test_cli/test_new_mnemonic.py index dc4e9626..0e091dd3 100644 --- a/tests/test_cli/test_new_mnemonic.py +++ b/tests/test_cli/test_new_mnemonic.py @@ -176,6 +176,93 @@ def mock_get_mnemonic(language, words_path, entropy=None) -> str: clean_key_folder(my_folder_path) +def test_new_mnemonic_eth1_address_withdrawal_alias(monkeypatch) -> None: + # monkeypatch get_mnemonic + def mock_get_mnemonic(language, words_path, entropy=None) -> str: + return "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" + + monkeypatch.setattr(new_mnemonic, "get_mnemonic", mock_get_mnemonic) + + # Prepare folder + my_folder_path = os.path.join(os.getcwd(), 'TESTING_TEMP_FOLDER') + clean_key_folder(my_folder_path) + if not os.path.exists(my_folder_path): + os.mkdir(my_folder_path) + + runner = CliRunner() + execution_address = '0x00000000219ab540356cBB839Cbe05303d7705Fa' + inputs = [execution_address, 'english', '1', 'mainnet', 'MyPassword', 'MyPassword', + 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'] + data = '\n'.join(inputs) + arguments = [ + '--language', 'english', + 'new-mnemonic', + '--folder', my_folder_path, + '--execution_address', execution_address, # execution_address and eth1_withdrawal_address are aliases + ] + result = runner.invoke(cli, arguments, input=data) + assert result.exit_code == 0 + + # Check files + validator_keys_folder_path = os.path.join(my_folder_path, DEFAULT_VALIDATOR_KEYS_FOLDER_NAME) + _, _, key_files = next(os.walk(validator_keys_folder_path)) + + deposit_file = [key_file for key_file in key_files if key_file.startswith('deposit_data')][0] + with open(validator_keys_folder_path + '/' + deposit_file, 'r') as f: + deposits_dict = json.load(f) + for deposit in deposits_dict: + withdrawal_credentials = bytes.fromhex(deposit['withdrawal_credentials']) + assert withdrawal_credentials == ( + ETH1_ADDRESS_WITHDRAWAL_PREFIX + b'\x00' * 11 + decode_hex(execution_address) + ) + + all_uuid = [ + get_uuid(validator_keys_folder_path + '/' + key_file) + for key_file in key_files + if key_file.startswith('keystore') + ] + assert len(set(all_uuid)) == 1 + + # Verify file permissions + if os.name == 'posix': + for file_name in key_files: + assert get_permissions(validator_keys_folder_path, file_name) == '0o440' + + # Clean up + clean_key_folder(my_folder_path) + + +def test_new_mnemonic_eth1_address_withdrawal_double_params(monkeypatch) -> None: + # monkeypatch get_mnemonic + def mock_get_mnemonic(language, words_path, entropy=None) -> str: + return "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" + + monkeypatch.setattr(new_mnemonic, "get_mnemonic", mock_get_mnemonic) + + # Prepare folder + my_folder_path = os.path.join(os.getcwd(), 'TESTING_TEMP_FOLDER') + clean_key_folder(my_folder_path) + if not os.path.exists(my_folder_path): + os.mkdir(my_folder_path) + + runner = CliRunner() + execution_address = '0x00000000219ab540356cBB839Cbe05303d7705Fa' + inputs = [execution_address, 'english', '1', 'mainnet', 'MyPassword', 'MyPassword', + 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'] + data = '\n'.join(inputs) + arguments = [ + '--language', 'english', + 'new-mnemonic', + '--folder', my_folder_path, + '--execution_address', execution_address, + '--eth1_withdrawal_address', execution_address, # double param + ] + result = runner.invoke(cli, arguments, input=data) + + # FIXME: Should not allow it + assert result.exit_code == 0 + + @pytest.mark.asyncio async def test_script_bls_withdrawal() -> None: # Prepare folder