The simulation tests are running on the TM Vault instance. They do not use any real data in the instance but everything (accounts, internal accounts, products) is provided before the test is run. The simulation result is provided in JSON format and contains all postings, balances, and other information grouped by the timestamp when they happened.

The inception library contains a helper function to run these tests easily using the Python unit test framework.

For simulation testing of a loan smart contract, you need to unpack the inception library and fill in the simulation config.

common/test_utils/contracts/simulation/test_config.json:

{
    "core_api_url": "https://core-api.tm3.sandbox.safibank.online",
    "auth_token": "A0006881253361691668490!fT/u+v5ueXB+Ld5qrYzh8Tkvjj41dwRLPaD+hSRUPnTbVQ9mxQysmHxa+t/jahwLsc1i7vZojdCip6htfpAsrMq1G9I="
}

Run the simulation:

PYTHONPATH=. python3 -m unittest library/loan/contracts/tests/simulation/loan_fixed_test.py -k test_monthly_due_for_fixed_rate_with_full_repayment

To store the result of the simulation simply add the following lines to the testing method after simulate_smart_contract method call.

        with open('simulation-result.json', 'w') as f:
            json.dump(res, f)

The result contains all the details and it is quite difficult to read it directly. For the purposes of debugging loan smart contract there is a script parsing a result and displaying data in simplified way.

#!/usr/bin/env python3

import json


def get_transfer_string(posting_instruction):
    if 'custom_instruction' in posting_instruction:
        ci = posting_instruction['custom_instruction']
        postings = ci['postings']
        if len(postings) != 2:
            return '[Unsupported CI]'
        amount = f'{postings[0]["amount"]}{postings[0]["denomination"]}'
        if postings[0]['credit']:
            raise Exception('Not expecting first posting to be credit!')
        from_account = f'{postings[0]["account_id"]}:{postings[0]["account_address"]}'
        to_account = f'{postings[1]["account_id"]}:{postings[1]["account_address"]}'
        return f'{from_account} -> {to_account} {amount}'
    elif 'inbound_hard_settlement' in posting_instruction:
        hs = posting_instruction['inbound_hard_settlement']
        amount = f'{hs["amount"]}{hs["denomination"]}'
        from_account = f'{hs["internal_account_id"]}'
        to_account = f'{hs["target_account_id"]}'
        return f'{from_account} -> {to_account} {amount}'
    return '[Unknown PI]'


def print_balances(sim, account):
    if account not in sim['balances']:
        return
    ab = sorted(sim['balances'][account]['balances'], key=lambda x: x['account_address'])
    balances_str = ''
    for balance in ab:
        balances_str += f'- {balance["account_address"]}: {balance["amount"]}{balance["denomination"]}\n'

    print(f'{account} balances:')
    print(balances_str, end='')


def process_simulation(sim):
    for entry in sim:
        result = entry['result']
        pib_str = ''
        for pib in result['posting_instruction_batches']:
            for posting_instruction in pib['posting_instructions']:
                description = posting_instruction['instruction_details'].get('description', None)
                pib_str += f'- {get_transfer_string(posting_instruction)}\n'
                if description is not None:
                    pib_str += f'  {description}\n'
        if pib_str != '':
            print(result['timestamp'])
            print('Postings:')
            print(pib_str, end='')
            print_balances(result, 'DEPOSIT_ACCOUNT')
            print_balances(result, 'LOAN_ACCOUNT')
            print()


def main():
    with open('simulation-result-pretty.json', 'r') as f:
        process_simulation(json.load(f))


if __name__ == '__main__':
    main()