From 263f60b1da21afeeca24fd767fadbc5578acd567 Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Tue, 16 Jan 2024 13:56:20 +0200 Subject: [PATCH] implement contract deploy from source and contract upgrade from source for the multisig --- multiversx_sdk_cli/cli_contracts.py | 110 +++++++++++++----- multiversx_sdk_cli/cli_shared.py | 4 - multiversx_sdk_cli/multisig.py | 70 ++++++++++- multiversx_sdk_cli/tests/test_cli_multisig.py | 59 ++++++++++ 4 files changed, 203 insertions(+), 40 deletions(-) diff --git a/multiversx_sdk_cli/cli_contracts.py b/multiversx_sdk_cli/cli_contracts.py index 1467c248..03d760f8 100644 --- a/multiversx_sdk_cli/cli_contracts.py +++ b/multiversx_sdk_cli/cli_contracts.py @@ -20,8 +20,9 @@ from multiversx_sdk_cli.errors import (BadUsage, DockerMissingError, NoWalletProvided) from multiversx_sdk_cli.interfaces import IAddress -from multiversx_sdk_cli.multisig import \ - prepare_transaction_for_deploying_contract +from multiversx_sdk_cli.multisig import ( + prepare_transaction_for_deploying_contract, + prepare_transaction_upgrading_contract) from multiversx_sdk_cli.projects.core import get_project_paths_recursively from multiversx_sdk_cli.projects.templates import Contract from multiversx_sdk_cli.ux import show_message @@ -89,7 +90,7 @@ def setup_parser(args: List[str], subparsers: Any) -> Any: cli_shared.add_broadcast_args(sub) cli_shared.add_guardian_wallet_args(args, sub) cli_shared.add_multisig_address_arg(sub) - cli_shared.add_contract_address_for_multisig_deploy(sub) + add_contract_address_for_multisig_deploy(sub) sub.set_defaults(func=deploy) @@ -128,6 +129,8 @@ def setup_parser(args: List[str], subparsers: Any) -> Any: " - only valid if --wait-result is set") cli_shared.add_broadcast_args(sub) cli_shared.add_guardian_wallet_args(args, sub) + cli_shared.add_multisig_address_arg(sub) + add_contract_address_for_multisig_upgrade(sub) sub.set_defaults(func=upgrade) @@ -223,8 +226,8 @@ def _add_recursive_arg(sub: Any): def _add_bytecode_arg(sub: Any): - sub.add_argument("--bytecode", type=str, required=True, - help="the file containing the WASM bytecode") + sub.add_argument("--bytecode", type=str, + help="the file containing the WASM bytecode; not needed when deploying using a multisig contract") def _add_contract_arg(sub: Any): @@ -253,6 +256,14 @@ def _add_metadata_arg(sub: Any): sub.set_defaults(metadata_upgradeable=True, metadata_payable=False) +def add_contract_address_for_multisig_deploy(sub: Any): + sub.add_argument("--deployed-contract", help="the address of the already deployed contract to be re-deployed by the multisig") + + +def add_contract_address_for_multisig_upgrade(sub: Any): + sub.add_argument("--upgraded-contract", help="the address of the already upgraded contract, that will be used to upgrade the contract owned by the multisig") + + def list_templates(args: Any): tag = args.tag contract = Contract(tag) @@ -314,20 +325,25 @@ def deploy(args: Any): cli_shared.prepare_nonce_in_args(args) sender = cli_shared.prepare_account(args) - config = TransactionsFactoryConfig(args.chain) - contract = SmartContract(config) - address_computer = AddressComputer(NUMBER_OF_SHARDS) - contract_address = address_computer.compute_contract_address(deployer=sender.address, deployment_nonce=args.nonce) if args.multisig: if not args.deployed_contract: raise BadUsage("`--deployed-contract` needs to be provided when proposing a deploy action for the multisig contract") + multisig_address = Address.new_from_bech32(args.multisig) + + if not args.proxy: + raise BadUsage("`--proxy` is required in order to compute the contract address") + + proxy = ProxyNetworkProvider(args.proxy) + multisig_nonce = proxy.get_account(multisig_address).nonce + contract_address = address_computer.compute_contract_address(deployer=multisig_address, deployment_nonce=multisig_nonce) + tx = prepare_transaction_for_deploying_contract( sender=sender, - multisig=args.multisig, - deployed_contract=args.deployed_contract, + multisig=Address.new_from_bech32(args.multisig), + deployed_contract=Address.new_from_bech32(args.deployed_contract), arguments=args.arguments, upgradeable=args.metadata_upgradeable, readable=args.metadata_readable, @@ -339,9 +355,15 @@ def deploy(args: Any): nonce=int(args.nonce), version=int(args.version), options=int(args.options), - guardian=args.guardian - ) + guardian=args.guardian) else: + if not args.bytecode: + raise BadUsage("`--bytecode` is required when deploying a contract") + + contract_address = address_computer.compute_contract_address(deployer=sender.address, deployment_nonce=args.nonce) + config = TransactionsFactoryConfig(args.chain) + contract = SmartContract(config) + tx = contract.prepare_deploy_transaction( owner=sender, bytecode=Path(args.bytecode), @@ -416,27 +438,53 @@ def upgrade(args: Any): cli_shared.prepare_nonce_in_args(args) sender = cli_shared.prepare_account(args) - config = TransactionsFactoryConfig(args.chain) - contract = SmartContract(config) contract_address = Address.new_from_bech32(args.contract) - tx = contract.prepare_upgrade_transaction( - owner=sender, - contract=contract_address, - bytecode=Path(args.bytecode), - arguments=args.arguments, - upgradeable=args.metadata_upgradeable, - readable=args.metadata_readable, - payable=args.metadata_payable, - payable_by_sc=args.metadata_payable_by_sc, - gas_limit=int(args.gas_limit), - value=int(args.value), - nonce=int(args.nonce), - version=int(args.version), - options=int(args.options), - guardian=args.guardian) - tx = _sign_guarded_tx(args, tx) + if args.multisig: + if not args.upgraded_contract: + raise BadUsage("`--upgraded-contract` needs to be provided when proposing an upgrade action for a contract owned by a multisig contract") + tx = prepare_transaction_upgrading_contract( + sender=sender, + contract_address=Address.new_from_bech32(args.contract), + multisig=Address.new_from_bech32(args.multisig), + upgraded_contract=Address.new_from_bech32(args.upgraded_contract), + arguments=args.arguments, + upgradeable=args.metadata_upgradeable, + readable=args.metadata_readable, + payable=args.metadata_payable, + payable_by_sc=args.metadata_payable_by_sc, + chain_id=args.chain, + value=int(args.value), + gas_limit=int(args.gas_limit), + nonce=int(args.nonce), + version=int(args.version), + options=int(args.options), + guardian=args.guardian) + else: + if not args.bytecode: + raise BadUsage("`--bytecode` is required when upgrading a contract") + + config = TransactionsFactoryConfig(args.chain) + contract = SmartContract(config) + + tx = contract.prepare_upgrade_transaction( + owner=sender, + contract=contract_address, + bytecode=Path(args.bytecode), + arguments=args.arguments, + upgradeable=args.metadata_upgradeable, + readable=args.metadata_readable, + payable=args.metadata_payable, + payable_by_sc=args.metadata_payable_by_sc, + gas_limit=int(args.gas_limit), + value=int(args.value), + nonce=int(args.nonce), + version=int(args.version), + options=int(args.options), + guardian=args.guardian) + + tx = _sign_guarded_tx(args, tx) _send_or_simulate(tx, contract_address, args) diff --git a/multiversx_sdk_cli/cli_shared.py b/multiversx_sdk_cli/cli_shared.py index 68cf976a..af91217b 100644 --- a/multiversx_sdk_cli/cli_shared.py +++ b/multiversx_sdk_cli/cli_shared.py @@ -147,10 +147,6 @@ def add_multisig_address_arg(sub: Any): sub.add_argument("--multisig", help="the address of the multisig contract") -def add_contract_address_for_multisig_deploy(sub: Any): - sub.add_argument("--deployed-contract", help="the address of the already deployed contract to be deployed by the multisig") - - def add_multisig_view_address_arg(sub: Any): sub.add_argument("--multisig-view", help="the address of the multisig-view contract") diff --git a/multiversx_sdk_cli/multisig.py b/multiversx_sdk_cli/multisig.py index 2b2a17d9..02af842f 100644 --- a/multiversx_sdk_cli/multisig.py +++ b/multiversx_sdk_cli/multisig.py @@ -14,6 +14,7 @@ from multiversx_sdk_cli.interfaces import IAddress MULTISIG_DEPLOY_FUNCTION = "proposeSCDeployFromSource" +MULTISIG_UPGRADE_FUNCTION = "proposeSCUpgradeFromSource" def prepare_transaction_for_egld_transfer(sender: Account, @@ -115,8 +116,8 @@ def prepare_transaction_for_depositing_funds(sender: Account, def prepare_transaction_for_deploying_contract(sender: Account, - multisig: str, - deployed_contract: str, + multisig: IAddress, + deployed_contract: IAddress, arguments: Union[List[str], None], upgradeable: bool, readable: bool, @@ -132,15 +133,14 @@ def prepare_transaction_for_deploying_contract(sender: Account, # convert the args to proper type instead of strings prepared_arguments = prepare_args_for_factory(arguments) if arguments else [] metadata = CodeMetadata(upgradeable, readable, payable, payable_by_sc) - contract = Address.new_from_bech32(deployed_contract) data = _prepare_data_field_for_deploy_transaction(amount=value, - deployed_contract=contract, + deployed_contract=deployed_contract, metadata=metadata, arguments=prepared_arguments) tx = Transaction( sender=sender.address.to_bech32(), - receiver=multisig, + receiver=multisig.to_bech32(), gas_limit=gas_limit, chain_id=chain_id, nonce=nonce, @@ -155,6 +155,66 @@ def prepare_transaction_for_deploying_contract(sender: Account, return tx +def prepare_transaction_upgrading_contract(sender: Account, + contract_address: IAddress, + multisig: IAddress, + upgraded_contract: IAddress, + arguments: Union[List[str], None], + upgradeable: bool, + readable: bool, + payable: bool, + payable_by_sc: bool, + chain_id: str, + value: int, + gas_limit: int, + nonce: int, + version: int, + options: int, + guardian: str) -> Transaction: + # convert the args to proper type instead of strings + prepared_arguments = prepare_args_for_factory(arguments) if arguments else [] + metadata = CodeMetadata(upgradeable, readable, payable, payable_by_sc) + + data = _prepare_data_field_for_upgrade_transaction(contract_address=contract_address, + amount=value, + upgraded_contract=upgraded_contract, + metadata=metadata, + arguments=prepared_arguments) + tx = Transaction( + sender=sender.address.to_bech32(), + receiver=multisig.to_bech32(), + gas_limit=gas_limit, + chain_id=chain_id, + nonce=nonce, + amount=0, + data=data, + version=version, + options=options, + guardian=guardian + ) + tx.signature = bytes.fromhex(sender.sign_transaction(tx)) + + return tx + + +def _prepare_data_field_for_upgrade_transaction(contract_address: IAddress, + amount: int, + upgraded_contract: IAddress, + metadata: CodeMetadata, + arguments: List[Any]) -> bytes: + data_parts = [ + MULTISIG_UPGRADE_FUNCTION, + contract_address.to_hex(), + arg_to_string(amount), + upgraded_contract.to_hex(), + str(metadata) + ] + data_parts.extend(args_to_strings(arguments)) + payload = _build_data_payload(data_parts) + + return payload.encode() + + def _prepare_data_field_for_deploy_transaction(amount: int, deployed_contract: IAddress, metadata: CodeMetadata, diff --git a/multiversx_sdk_cli/tests/test_cli_multisig.py b/multiversx_sdk_cli/tests/test_cli_multisig.py index e1febc55..3431afd1 100644 --- a/multiversx_sdk_cli/tests/test_cli_multisig.py +++ b/multiversx_sdk_cli/tests/test_cli_multisig.py @@ -298,6 +298,65 @@ def test_propose_multi_esdt_nft_transfer(capsys: Any): assert signature == "563fc6eefe9469cf90191462cfe21ab25ae7291c1c411cd4b3778717c827045eabc58b689308e8ee45676a8e49cf75c2856a83e41e93dad5c4acb5ccb65c5b04" +def test_propose_contract_deploy_from_source(capsys: Any): + return_code = main([ + "contract", "deploy", + "--pem", str(alice), + "--nonce", "60", + "--chain", "T", + "--proxy", "https://testnet-api.multiversx.com", + "--gas-limit", "100000000", + "--multisig", "erd1qqqqqqqqqqqqqpgqpg4q7ye5p9uv9m4zdzj69h8ezuqjj78krawq9zqz30", + "--deployed-contract", "erd1qqqqqqqqqqqqqpgq8z2zzyu30f4607hth0tfj5m3vpjvwrvvrawqw09jem", + "--arguments", "0" + ]) + assert False if return_code else True + + transaction = get_transaction(capsys) + + data_field: str = transaction["data"] + data = base64.b64decode(data_field.encode()).decode() + assert data == "proposeSCDeployFromSource@@0000000000000000050038942113917a6ba7faebbbd69953716064c70d8c1f5c@0500@" + + receiver = transaction["receiver"] + assert receiver == "erd1qqqqqqqqqqqqqpgqpg4q7ye5p9uv9m4zdzj69h8ezuqjj78krawq9zqz30" + + chain_id = transaction["chainID"] + assert chain_id == "T" + + value = int(transaction["value"]) + assert value == 0 + + +def test_propose_contract_upgrade_from_source(capsys: Any): + return_code = main([ + "contract", "upgrade", "erd1qqqqqqqqqqqqqpgqz0kha878srg82eznjhdyvgarwycwjgs6rawq02lh6j", + "--pem", str(alice), + "--nonce", "6241", + "--chain", "T", + "--gas-limit", "100000000", + "--multisig", "erd1qqqqqqqqqqqqqpgqpg4q7ye5p9uv9m4zdzj69h8ezuqjj78krawq9zqz30", + "--upgraded-contract", "erd1qqqqqqqqqqqqqpgq8z2zzyu30f4607hth0tfj5m3vpjvwrvvrawqw09jem", + "--arguments", "0" + ]) + assert False if return_code else True + + transaction = get_transaction(capsys) + + data_field: str = transaction["data"] + data = base64.b64decode(data_field.encode()).decode() + assert data == "proposeSCUpgradeFromSource@0000000000000000050013ed7e9fc780d075645395da4623a37130e9221a1f5c@@0000000000000000050038942113917a6ba7faebbbd69953716064c70d8c1f5c@0500@" + + receiver = transaction["receiver"] + assert receiver == "erd1qqqqqqqqqqqqqpgqpg4q7ye5p9uv9m4zdzj69h8ezuqjj78krawq9zqz30" + + chain_id = transaction["chainID"] + assert chain_id == "T" + + value = int(transaction["value"]) + assert value == 0 + + def get_transaction(capsys: Any) -> Dict[str, Any]: out = _read_stdout(capsys) output = json.loads(out)