Skip to content

Commit

Permalink
add support fot contract calls for the multisig contract
Browse files Browse the repository at this point in the history
  • Loading branch information
popenta committed Jan 16, 2024
1 parent 263f60b commit 2a7d74d
Show file tree
Hide file tree
Showing 3 changed files with 265 additions and 20 deletions.
49 changes: 34 additions & 15 deletions multiversx_sdk_cli/cli_contracts.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
NoWalletProvided)
from multiversx_sdk_cli.interfaces import IAddress
from multiversx_sdk_cli.multisig import (
prepare_transaction_for_contract_call,
prepare_transaction_for_deploying_contract,
prepare_transaction_upgrading_contract)
from multiversx_sdk_cli.projects.core import get_project_paths_recursively
Expand Down Expand Up @@ -110,6 +111,7 @@ def setup_parser(args: List[str], subparsers: Any) -> Any:
" - only valid if --wait-result is set")
cli_shared.add_broadcast_args(sub, relay=True)
cli_shared.add_guardian_wallet_args(args, sub)
cli_shared.add_multisig_address_arg(sub)

sub.set_defaults(func=call)

Expand Down Expand Up @@ -409,24 +411,41 @@ def call(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_execute_transaction(
caller=sender,
contract=contract_address,
function=args.function,
arguments=args.arguments,
gas_limit=int(args.gas_limit),
value=int(args.value),
transfers=args.token_transfers,
nonce=int(args.nonce),
version=int(args.version),
options=int(args.options),
guardian=args.guardian)
tx = _sign_guarded_tx(args, tx)
if args.multisig:
tx = prepare_transaction_for_contract_call(
sender=sender,
contract_address=contract_address,
function=args.function,
arguments=args.arguments,
multisig=Address.new_from_bech32(args.multisig),
value=int(args.value),
transfers=args.token_transfers,
gas_limit=int(args.gas_limit),
chain_id=args.chain,
nonce=int(args.nonce),
version=int(args.version),
options=int(args.options),
guardian=args.guardian)
else:
config = TransactionsFactoryConfig(args.chain)
contract = SmartContract(config)

tx = contract.prepare_execute_transaction(
caller=sender,
contract=contract_address,
function=args.function,
arguments=args.arguments,
gas_limit=int(args.gas_limit),
value=int(args.value),
transfers=args.token_transfers,
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
87 changes: 82 additions & 5 deletions multiversx_sdk_cli/multisig.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,12 @@
from multiversx_sdk_cli.accounts import Account
from multiversx_sdk_cli.contracts import (SmartContract,
prepare_args_for_factory)
from multiversx_sdk_cli.errors import BadUsage
from multiversx_sdk_cli.interfaces import IAddress

MULTISIG_DEPOSIT_FUNCTION = "deposit"
MULTISIG_TRANSFER_AND_EXECUTE = "proposeTransferExecute"
MULTISIG_ASYNC_CALL = "proposeAsyncCall"
MULTISIG_DEPLOY_FUNCTION = "proposeSCDeployFromSource"
MULTISIG_UPGRADE_FUNCTION = "proposeSCUpgradeFromSource"

Expand All @@ -33,7 +37,7 @@ def prepare_transaction_for_egld_transfer(sender: Account,
return contract.prepare_execute_transaction(
caller=sender,
contract=Address.new_from_bech32(multisig),
function="proposeTransferExecute",
function=MULTISIG_TRANSFER_AND_EXECUTE,
arguments=[f"{receiver}", f"{value}"],
gas_limit=gas_limit,
value=0,
Expand Down Expand Up @@ -66,13 +70,13 @@ def prepare_transaction_for_custom_token_transfer(sender: Account,
if transfer_data_parts[0] != "ESDTTransfer":
arguments[0] = multisig_contract.to_hex()

transfer_data_parts[0] = transfer_data_parts[0].encode().hex()
transfer_data_parts[0] = arg_to_string(transfer_data_parts[0])
arguments.extend(transfer_data_parts)

tx = contract.prepare_execute_transaction(
caller=sender,
contract=multisig_contract,
function="proposeAsyncCall",
function=MULTISIG_ASYNC_CALL,
arguments=None,
gas_limit=gas_limit,
value=0,
Expand Down Expand Up @@ -104,7 +108,7 @@ def prepare_transaction_for_depositing_funds(sender: Account,
return contract.prepare_execute_transaction(
caller=sender,
contract=Address.new_from_bech32(multisig),
function="deposit",
function=MULTISIG_DEPOSIT_FUNCTION,
arguments=None,
gas_limit=gas_limit,
value=value,
Expand Down Expand Up @@ -197,6 +201,79 @@ def prepare_transaction_upgrading_contract(sender: Account,
return tx


def prepare_transaction_for_contract_call(sender: Account,
contract_address: IAddress,
function: str,
arguments: Union[List[str], None],
multisig: IAddress,
value: int,
transfers: Union[List[str], None],
gas_limit: int,
chain_id: str,
nonce: int,
version: int,
options: int,
guardian: str) -> Transaction:
if value and transfers:
raise BadUsage("Can't send both native and custom tokens")

config = TransactionsFactoryConfig(chain_id)
contract = SmartContract(config)

token_transfers = contract.prepare_token_transfers(transfers) if transfers else []
prepared_args = prepare_args_for_factory(arguments) if arguments else []

data_field = _prepare_data_field_for_contract_call(contract_address=contract_address,
multisig=multisig,
function=function,
arguments=prepared_args,
value=value,
token_transfers=token_transfers)
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_field,
version=version,
options=options,
guardian=guardian
)
tx.signature = bytes.fromhex(sender.sign_transaction(tx))

return tx


def _prepare_data_field_for_contract_call(contract_address: IAddress,
multisig: IAddress,
function: str,
arguments: List[Any],
value: int,
token_transfers: List[TokenTransfer]):
data_parts = [
MULTISIG_ASYNC_CALL,
contract_address.to_hex(),
arg_to_string(value)
]

transfer_data_parts = _prepare_data_parts_for_multisig_transfer(receiver=contract_address, token_transfers=token_transfers)

if transfer_data_parts:
if transfer_data_parts[0] != "ESDTTransfer":
data_parts[1] = multisig.to_hex()

transfer_data_parts[0] = arg_to_string(transfer_data_parts[0])
data_parts.extend(transfer_data_parts)

data_parts.append(arg_to_string(function))
data_parts.extend(args_to_strings(arguments))

data_field = _build_data_payload(data_parts)
return data_field.encode()


def _prepare_data_field_for_upgrade_transaction(contract_address: IAddress,
amount: int,
upgraded_contract: IAddress,
Expand Down Expand Up @@ -231,7 +308,7 @@ def _prepare_data_field_for_deploy_transaction(amount: int,
return payload.encode()


def _prepare_data_parts_for_multisig_transfer(receiver: Address, token_transfers: List[TokenTransfer]):
def _prepare_data_parts_for_multisig_transfer(receiver: IAddress, token_transfers: List[TokenTransfer]) -> List[str]:
token_computer = TokenComputer()
data_builder = TokenTransfersDataBuilder(token_computer)
data_parts: List[str] = []
Expand Down
149 changes: 149 additions & 0 deletions multiversx_sdk_cli/tests/test_cli_multisig.py
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,155 @@ def test_propose_contract_upgrade_from_source(capsys: Any):
assert value == 0


def test_propose_contract_call_no_transfer(capsys: Any):
return_code = main([
"contract", "call", "erd1qqqqqqqqqqqqqpgq8z2zzyu30f4607hth0tfj5m3vpjvwrvvrawqw09jem",
"--pem", str(alice),
"--nonce", "9550",
"--chain", "T",
"--gas-limit", "100000000",
"--function", "add",
"--arguments", "10",
"--multisig", "erd1qqqqqqqqqqqqqpgqpg4q7ye5p9uv9m4zdzj69h8ezuqjj78krawq9zqz30"
])
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 == "proposeAsyncCall@0000000000000000050038942113917a6ba7faebbbd69953716064c70d8c1f5c@@616464@0a"

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_call_with_egld_transfer(capsys: Any):
return_code = main([
"contract", "call", "erd1qqqqqqqqqqqqqpgq8z2zzyu30f4607hth0tfj5m3vpjvwrvvrawqw09jem",
"--pem", str(alice),
"--nonce", "9552",
"--chain", "T",
"--value", "1000000000000000",
"--gas-limit", "100000000",
"--function", "add",
"--arguments", "10",
"--multisig", "erd1qqqqqqqqqqqqqpgqpg4q7ye5p9uv9m4zdzj69h8ezuqjj78krawq9zqz30"
])
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 == "proposeAsyncCall@0000000000000000050038942113917a6ba7faebbbd69953716064c70d8c1f5c@038d7ea4c68000@616464@0a"

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_call_with_esdt_transfer(capsys: Any):
return_code = main([
"contract", "call", "erd1qqqqqqqqqqqqqpgq8z2zzyu30f4607hth0tfj5m3vpjvwrvvrawqw09jem",
"--pem", str(alice),
"--nonce", "9553",
"--chain", "T",
"--token-transfers", "ZZZ-9ee87d", "1000",
"--gas-limit", "100000000",
"--function", "add",
"--arguments", "10",
"--multisig", "erd1qqqqqqqqqqqqqpgqpg4q7ye5p9uv9m4zdzj69h8ezuqjj78krawq9zqz30"
])
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 == "proposeAsyncCall@0000000000000000050038942113917a6ba7faebbbd69953716064c70d8c1f5c@@455344545472616e73666572@5a5a5a2d396565383764@03e8@616464@0a"

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_call_with_multi_esdt_transfer(capsys: Any):
return_code = main([
"contract", "call", "erd1qqqqqqqqqqqqqpgq8z2zzyu30f4607hth0tfj5m3vpjvwrvvrawqw09jem",
"--pem", str(alice),
"--nonce", "9554",
"--chain", "T",
"--token-transfers", "ZZZ-9ee87d", "1300", "TST-267761", "600",
"--gas-limit", "100000000",
"--function", "add",
"--arguments", "10",
"--multisig", "erd1qqqqqqqqqqqqqpgqpg4q7ye5p9uv9m4zdzj69h8ezuqjj78krawq9zqz30"
])
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 == "proposeAsyncCall@000000000000000005000a2a0f13340978c2eea268a5a2dcf917012978f61f5c@@4d756c7469455344544e46545472616e73666572@0000000000000000050038942113917a6ba7faebbbd69953716064c70d8c1f5c@02@5a5a5a2d396565383764@@0514@5453542d323637373631@@0258@616464@0a"

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_call_with_multi_esdt_nft_transfer(capsys: Any):
return_code = main([
"contract", "call", "erd1qqqqqqqqqqqqqpgq8z2zzyu30f4607hth0tfj5m3vpjvwrvvrawqw09jem",
"--pem", str(alice),
"--nonce", "9555",
"--chain", "T",
"--token-transfers", "ZZZ-9ee87d", "700", "METATEST-e05d11-01", "1500",
"--gas-limit", "100000000",
"--function", "add",
"--arguments", "10",
"--multisig", "erd1qqqqqqqqqqqqqpgqpg4q7ye5p9uv9m4zdzj69h8ezuqjj78krawq9zqz30"
])
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 == "proposeAsyncCall@000000000000000005000a2a0f13340978c2eea268a5a2dcf917012978f61f5c@@4d756c7469455344544e46545472616e73666572@0000000000000000050038942113917a6ba7faebbbd69953716064c70d8c1f5c@02@5a5a5a2d396565383764@@02bc@4d455441544553542d653035643131@01@05dc@616464@0a"

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 2a7d74d

Please sign in to comment.