Skip to content

Commit

Permalink
add support for contract abi
Browse files Browse the repository at this point in the history
  • Loading branch information
popenta committed Jul 9, 2024
1 parent d6ec097 commit 405ee5f
Show file tree
Hide file tree
Showing 6 changed files with 1,416 additions and 54 deletions.
44 changes: 31 additions & 13 deletions multiversx_sdk_cli/cli_contracts.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import logging
import os
from pathlib import Path
from typing import Any, List
from typing import Any, List, Tuple

from multiversx_sdk import (Address, AddressComputer, ProxyNetworkProvider,
Transaction, TransactionsFactoryConfig)
Expand Down Expand Up @@ -73,6 +73,7 @@ def setup_parser(args: List[str], subparsers: Any) -> Any:
output_description = CLIOutputBuilder.describe(with_contract=True, with_transaction_on_network=True, with_simulation=True)
sub = cli_shared.add_command_subparser(subparsers, "contract", "deploy", f"Deploy a Smart Contract.{output_description}")
_add_bytecode_arg(sub)
_add_contract_abi_arg(sub)
_add_metadata_arg(sub)
cli_shared.add_outfile_arg(sub)
cli_shared.add_wallet_args(args, sub)
Expand Down Expand Up @@ -111,6 +112,7 @@ def setup_parser(args: List[str], subparsers: Any) -> Any:
sub = cli_shared.add_command_subparser(subparsers, "contract", "upgrade",
f"Upgrade a previously-deployed Smart Contract.{output_description}")
_add_contract_arg(sub)
_add_contract_abi_arg(sub)
cli_shared.add_outfile_arg(sub)
_add_bytecode_arg(sub)
_add_metadata_arg(sub)
Expand Down Expand Up @@ -323,15 +325,16 @@ def deploy(args: Any):

sender = cli_shared.prepare_account(args)
config = TransactionsFactoryConfig(args.chain)
contract = SmartContract(config)
abi = Abi.load(Path(args.abi)) if args.abi else None
contract = SmartContract(config, abi)

address_computer = AddressComputer(NUMBER_OF_SHARDS)
contract_address = address_computer.compute_contract_address(deployer=sender.address, deployment_nonce=args.nonce)
arguments, args_from_file = _get_contract_arguments(args)

tx = contract.prepare_deploy_transaction(
owner=sender,
bytecode=Path(args.bytecode),
arguments=args.arguments,
arguments=arguments,
args_from_file=args_from_file,
upgradeable=args.metadata_upgradeable,
readable=args.metadata_readable,
payable=args.metadata_payable,
Expand All @@ -344,6 +347,9 @@ def deploy(args: Any):
guardian=args.guardian)
tx = _sign_guarded_tx(args, tx)

address_computer = AddressComputer(NUMBER_OF_SHARDS)
contract_address = address_computer.compute_contract_address(deployer=sender.address, deployment_nonce=args.nonce)

logger.info("Contract address: %s", contract_address.to_bech32())
utils.log_explorer_contract_address(args.chain, contract_address.to_bech32())

Expand Down Expand Up @@ -377,19 +383,15 @@ def call(args: Any):
abi = Abi.load(Path(args.abi)) if args.abi else None
contract = SmartContract(config, abi)

json_args = json.loads(Path(args.arguments_file).expanduser().read_text()) if args.arguments_file else None

if json_args and args.arguments:
raise Exception("Both '--arguments' and '--arguments-json' provided.")

arguments = json_args or args.arguments
arguments, args_from_file = _get_contract_arguments(args)
contract_address = Address.new_from_bech32(args.contract)

tx = contract.prepare_execute_transaction(
caller=sender,
contract=contract_address,
function=args.function,
arguments=arguments,
args_from_file=args_from_file,
gas_limit=int(args.gas_limit),
value=int(args.value),
transfers=args.token_transfers,
Expand All @@ -411,14 +413,18 @@ def upgrade(args: Any):

sender = cli_shared.prepare_account(args)
config = TransactionsFactoryConfig(args.chain)
contract = SmartContract(config)
abi = Abi.load(Path(args.abi)) if args.abi else None
contract = SmartContract(config, abi)

arguments, args_from_file = _get_contract_arguments(args)
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,
arguments=arguments,
args_from_file=args_from_file,
upgradeable=args.metadata_upgradeable,
readable=args.metadata_readable,
payable=args.metadata_payable,
Expand Down Expand Up @@ -451,6 +457,18 @@ def query(args: Any):
utils.dump_out_json(result)


def _get_contract_arguments(args: Any) -> Tuple[List[Any], bool]:
json_args = json.loads(Path(args.arguments_file).expanduser().read_text()) if args.arguments_file else None

if json_args and args.arguments:
raise Exception("Both '--arguments' and '--arguments-file' provided.")

if json_args:
return json_args, True
else:
return args.arguments, False


def _send_or_simulate(tx: Transaction, contract_address: IAddress, args: Any):
output_builder = cli_shared.send_or_simulate(tx, args, dump_output=False)
output_builder.set_contract_address(contract_address)
Expand Down
75 changes: 34 additions & 41 deletions multiversx_sdk_cli/contracts.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import base64
import logging
from pathlib import Path
from typing import Any, Dict, List, Optional, Protocol, Sequence, Union, cast
from typing import Any, List, Optional, Protocol, Sequence, Union

from multiversx_sdk import (Address, SmartContractTransactionsFactory, Token,
TokenComputer, TokenTransfer, Transaction,
Expand Down Expand Up @@ -81,7 +81,8 @@ def __init__(self, config: IConfig, abi: Optional[Abi] = None):
def prepare_deploy_transaction(self,
owner: Account,
bytecode: Path,
arguments: Union[List[str], None],
arguments: Union[List[Any], None],
args_from_file: bool,
upgradeable: bool,
readable: bool,
payable: bool,
Expand All @@ -92,7 +93,9 @@ def prepare_deploy_transaction(self,
version: int,
options: int,
guardian: str) -> Transaction:
args = self.prepare_args_for_factory("constructor", arguments) if arguments else []
args = arguments if arguments else []
if not args_from_file:
args = self.prepare_args_for_factory(args)

tx = self._factory.create_transaction_for_deploy(
sender=owner.address,
Expand All @@ -117,7 +120,8 @@ def prepare_execute_transaction(self,
caller: Account,
contract: Address,
function: str,
arguments: Any,
arguments: Union[List[Any], None],
args_from_file: bool,
gas_limit: int,
value: int,
transfers: Union[List[str], None],
Expand All @@ -126,7 +130,10 @@ def prepare_execute_transaction(self,
options: int,
guardian: str) -> Transaction:
token_transfers = self._prepare_token_transfers(transfers) if transfers else []
args = self.prepare_args_for_factory(function, arguments) if arguments else []

args = arguments if arguments else []
if not args_from_file:
args = self.prepare_args_for_factory(args)

tx = self._factory.create_transaction_for_execute(
sender=caller.address,
Expand All @@ -150,6 +157,7 @@ def prepare_upgrade_transaction(self,
contract: IAddress,
bytecode: Path,
arguments: Union[List[str], None],
args_from_file: bool,
upgradeable: bool,
readable: bool,
payable: bool,
Expand All @@ -160,7 +168,11 @@ def prepare_upgrade_transaction(self,
version: int,
options: int,
guardian: str) -> Transaction:
args = self.prepare_args_for_factory("upgrade_constructor", arguments) if arguments else []
args = self.prepare_args_for_factory(arguments) if arguments else []

args = arguments if arguments else []
if not args_from_file:
args = self.prepare_args_for_factory(args)

tx = self._factory.create_transaction_for_upgrade(
sender=owner.address,
Expand Down Expand Up @@ -197,46 +209,27 @@ def _prepare_token_transfers(self, transfers: List[str]) -> List[TokenTransfer]:

return token_transfers

def prepare_args_for_factory(self, endpoint: str, arguments: Any) -> List[Any]:
def prepare_args_for_factory(self, arguments: List[str]) -> List[Any]:
args: List[Any] = []

if isinstance(arguments, dict):
return self._prepare_args_using_abi(endpoint, arguments)

if isinstance(arguments, list):
for arg in arguments: # type: ignore
arg = cast(str, arg)
if arg.startswith(HEX_PREFIX):
args.append(hex_to_bytes(arg))
elif arg.isnumeric():
args.append(int(arg))
elif arg.startswith(DEFAULT_HRP):
args.append(Address.new_from_bech32(arg))
elif arg.lower() == FALSE_STR_LOWER:
args.append(False)
elif arg.lower() == TRUE_STR_LOWER:
args.append(True)
elif arg.startswith(STR_PREFIX):
args.append(arg[len(STR_PREFIX):])
else:
raise errors.BadUserInput(f"Unknown argument type for argument: `{arg}`. Use `mxpy contract <sub-command> --help` to check all supported arguments")
for arg in arguments:
if arg.startswith(HEX_PREFIX):
args.append(hex_to_bytes(arg))
elif arg.isnumeric():
args.append(int(arg))
elif arg.startswith(DEFAULT_HRP):
args.append(Address.new_from_bech32(arg))
elif arg.lower() == FALSE_STR_LOWER:
args.append(False)
elif arg.lower() == TRUE_STR_LOWER:
args.append(True)
elif arg.startswith(STR_PREFIX):
args.append(arg[len(STR_PREFIX):])
else:
raise errors.BadUserInput(f"Unknown argument type for argument: `{arg}`. Use `mxpy contract <sub-command> --help` to check all supported arguments")

return args

def _prepare_args_using_abi(self, function: str, arguments: Dict[str, Any]):
if not self._abi:
raise Exception("Abi file not provided")

endpoint_definition = [endpoint for endpoint in self._abi.definition.endpoints if endpoint.name == function]
if not endpoint_definition:
raise Exception(f"Endpoint [{function}] not found")
elif len(endpoint_definition) > 1:
raise Exception(f"More than one endpoint with name [{function}] found.")
else:
endpoint_definition = endpoint_definition[0]

input_parameters_names = [input.name for input in endpoint_definition.inputs]


def query_contract(
contract_address: IAddress,
Expand Down
37 changes: 37 additions & 0 deletions multiversx_sdk_cli/tests/test_cli_contracts.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,43 @@ def test_contract_commands_argument_parameter():
assert not return_code


def test_contract_deploy_with_abi(capsys: Any):
alice = f"{parent}/testdata/alice.pem"
multisig = f"{parent}/testdata/multisig.wasm"
multisig_abi = f"{parent}/testdata/multisig.abi.json"

return_code = main([
"contract", "deploy",
"--bytecode", multisig,
"--pem", alice,
"--chain", "T",
"--nonce", "7",
"--gas-limit", "5000000",
"--arguments", "2", "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5"
])
assert not return_code

deploy_without_abi_data = get_transaction_data(capsys)
# Clear the captured content
capsys.readouterr()

return_code = main([
"contract", "deploy",
"--bytecode", multisig,
"--abi", multisig_abi,
"--pem", alice,
"--chain", "T",
"--nonce", "7",
"--gas-limit", "5000000",
"--arguments-file", f"{parent}/testdata/deploy_multisig_args.json"
])
assert not return_code

deploy_with_abi_data = get_transaction_data(capsys)

assert deploy_without_abi_data == deploy_with_abi_data


def _read_stdout(capsys: Any) -> str:
return capsys.readouterr().out.strip()

Expand Down
12 changes: 12 additions & 0 deletions multiversx_sdk_cli/tests/testdata/deploy_multisig_args.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[
2,
[
{
"bech32": "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"
},
{
"bech32": "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5"
}

]
]
Loading

0 comments on commit 405ee5f

Please sign in to comment.