Skip to content

Commit

Permalink
implement contract deploy from source and contract upgrade from sourc…
Browse files Browse the repository at this point in the history
…e for the multisig
  • Loading branch information
popenta committed Jan 16, 2024
1 parent f3a9d40 commit 263f60b
Show file tree
Hide file tree
Showing 4 changed files with 203 additions and 40 deletions.
110 changes: 79 additions & 31 deletions multiversx_sdk_cli/cli_contracts.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand All @@ -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),
Expand Down Expand Up @@ -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)


Expand Down
4 changes: 0 additions & 4 deletions multiversx_sdk_cli/cli_shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down
70 changes: 65 additions & 5 deletions multiversx_sdk_cli/multisig.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand Down
59 changes: 59 additions & 0 deletions multiversx_sdk_cli/tests/test_cli_multisig.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit 263f60b

Please sign in to comment.