Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/dictionary/custom_wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ png
pre
PyPI
pytest
queryable
Readthedocs
yaml
svg
Expand Down
1 change: 1 addition & 0 deletions docs/source/dev_documentation/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

- `AccountBatchCloneStep` for optimized bulk account cloning: collects all data first, then pushes with single ESDT module reconciliation, single Elasticsearch bulk insert, and smart payload-sized batched `set_state` calls
- `ChainSimulatorSetStateStep` to set specific hex-encoded storage key-value pairs for an address on the chain simulator
- `ChainSimulatorSetTokenBalanceStep` to set arbitrary fungible ESDT balances on accounts in the chain simulator, auto-cloning token registrations from a source network when missing
- Explicit test for variadic counted values
- Dynamic batch size recovery: after a timeout reduces the batch size, subsequent successful requests with smaller payloads automatically double the batch size back toward the original (for both storage fetch and push operations)
- Tests verifying batch size resets between accounts
Expand Down
35 changes: 35 additions & 0 deletions docs/source/user_documentation/steps.md
Original file line number Diff line number Diff line change
Expand Up @@ -690,6 +690,41 @@ keys:
"6d795f6f746865725f6b6579": "01"
```

(chain_simulator_set_token_balance_target)=
### Chain Simulator Set Token Balance Step

Exclusive to the chain simulator.
This step allows you to give arbitrary amounts of fungible ESDT tokens to one or more accounts, without going through any on-chain transaction. It is the inverse of [Account Clone](#account_clone_target): instead of cloning real balances from a source network, you specify exactly which token, which receiver, and how many units they should hold.

If a referenced token is not yet registered on the chain simulator, the step automatically clones its registration from `source_network` (default: `mainnet`) — using the same plumbing as `AccountClone`. As a result, the tokens behave as if they had been natively issued on the chain simulator: they are visible in the explorer, queryable through the proxy, and usable in subsequent transactions.

```yaml
type: ChainSimulatorSetTokenBalance
source_network: mainnet # optional, default to mainnet
caching_period: "10 days" # optional, default to 10 days
balances:
- receiver: "%alice.address"
token_identifier: WEGLD-bd4d79
amount: 5000000000000000000 # 5 WEGLD
- receiver: "%alice.address"
token_identifier: USDC-c76f1f
amount: 1000000 # 1 USDC (6 decimals)
- receiver: bob
token_identifier: USDC-c76f1f
amount: 250000000
```

- `source_network` controls which network the step queries when a token is not yet registered on the simulator's ESDT module. Tokens already registered are not re-fetched.
- `caching_period` controls how long previously-fetched source-network data (the ESDT module entry and its companion Elasticsearch document) is reused before being re-fetched. It has no effect once a token is registered on the simulator — re-registration only happens if the local ESDT module entry is missing.

```{warning}
This step only supports **fungible** tokens (no nonce). NFT, SFT and Meta-ESDT minting from thin air is not yet supported — use [Account Clone](#account_clone_target) with a real holder as the source for those.
```

```{note}
"From thin air" balances are written directly to the receiver's storage via the chain-simulator address-level set-state endpoint, which only modifies the storage keys it is given. Other account fields (nonce, balance, code, other storage) are left untouched. The registered supply on the ESDT module entry is **not** incremented either, which is fine for the vast majority of test scenarios but may matter if your contracts read the on-module supply.
```

(account_clone_target)=
### Account Clone Step

Expand Down
65 changes: 65 additions & 0 deletions integration_tests/token_thin_air/mxops_scenes/01_mint.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
allowed_networks:
- chain-simulator

allowed_scenario:
- "integration_test_token_thin_air.*"

steps:
# Mint two real mainnet fungible tokens to two different receivers in a
# single step. Tokens that are not yet registered on the chain simulator
# are auto-cloned from mainnet by the step itself.
- type: ChainSimulatorSetTokenBalance
source_network: mainnet
balances:
- receiver: "%paul.address"
token_identifier: WEGLD-bd4d79
amount: 450000000000000000000 # 450 WEGLD
- receiver: "%paul.address"
token_identifier: USDC-c76f1f
amount: 1000000 # 1 USDC (6 decimals)
- receiver: "%marie.address"
token_identifier: USDC-c76f1f
amount: 250000000 # 250 USDC

# Verify the balances actually landed in the simulator's ESDT storage
# by going through the standard proxy lookup endpoint.
- type: Python
module_path: ./integration_tests/token_thin_air/scripts/checks.py
function: assert_account_token_balance
arguments:
- paul
- WEGLD-bd4d79
- 450000000000000000000

- type: Python
module_path: ./integration_tests/token_thin_air/scripts/checks.py
function: assert_account_token_balance
arguments:
- paul
- USDC-c76f1f
- 1000000

- type: Python
module_path: ./integration_tests/token_thin_air/scripts/checks.py
function: assert_account_token_balance
arguments:
- marie
- USDC-c76f1f
- 250000000

# Re-running the step on already-registered tokens must be a no-op for
# the registration phase and only update the balances. Bumps Paul's USDC
# to confirm overwriting an existing thin-air balance also works.
- type: ChainSimulatorSetTokenBalance
balances:
- receiver: "%paul.address"
token_identifier: USDC-c76f1f
amount: 9999999 # 9.999999 USDC

- type: Python
module_path: ./integration_tests/token_thin_air/scripts/checks.py
function: assert_account_token_balance
arguments:
- paul
- USDC-c76f1f
- 9999999
37 changes: 37 additions & 0 deletions integration_tests/token_thin_air/scripts/checks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""
Checks for the token_thin_air integration test.
Verifies that ESDT balances set "from thin air" via
ChainSimulatorSetTokenBalanceStep are observable through the standard
proxy ESDT lookup endpoints.
"""

from multiversx_sdk import Address, Token

from mxops.common.providers import MyProxyNetworkProvider
from mxops.data.execution_data import ScenarioData


def assert_account_token_balance(
account_id: str, token_identifier: str, expected_amount: int
):
"""
Query the chain simulator for the ESDT balance of an account and assert
it matches the expected amount.

:param account_id: MxOps account id (must resolve to a bech32 address)
:param token_identifier: ESDT token identifier (e.g. "WEGLD-bd4d79")
:param expected_amount: balance the account should hold (integer units)
"""
scenario_data = ScenarioData.get()
bech32 = scenario_data.get_account_value(account_id, "bech32")
address = Address.new_from_bech32(bech32)
expected_amount = int(expected_amount)

proxy = MyProxyNetworkProvider()
on_network = proxy.get_token_of_account(address, Token(token_identifier))
actual = int(on_network.amount)
if actual != expected_amount:
raise AssertionError(
f"Balance mismatch for {account_id} / {token_identifier}: "
f"expected {expected_amount}, got {actual}"
)
15 changes: 15 additions & 0 deletions integration_tests/token_thin_air/scripts/run_test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/bin/bash
set -e

if ! [[ " ${1} " =~ " chain-simulator " ]]; then
echo "Token thin-air tests not available on ${1}"
exit 0
fi

uv run mxops \
execute \
-n $1 \
-s integration_test_token_thin_air \
-c \
integration_tests/setup_scenes/01_accounts.yaml \
integration_tests/token_thin_air/mxops_scenes
2 changes: 2 additions & 0 deletions mxops/execution/steps/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
AccountCloneStep,
ChainSimulatorFaucetStep,
ChainSimulatorSetStateStep,
ChainSimulatorSetTokenBalanceStep,
GenerateWalletsStep,
R3D4FaucetStep,
)
Expand Down Expand Up @@ -49,6 +50,7 @@
"AssertStep",
"ChainSimulatorFaucetStep",
"ChainSimulatorSetStateStep",
"ChainSimulatorSetTokenBalanceStep",
"ContractCallStep",
"ContractDeployStep",
"ContractQueryStep",
Expand Down
Loading