runtimeverification / runtimeverification/komet

How to accomodate Stellar Asset Contract calls in test setup

Open
#90 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
WebAssembly
Stars
35
Forks
3
PR merge metrics
No merged PRs in 30d

Description

I'm working on sorocarbon, which relies heavily on SAC calls. Since the SAC is implemented as a built-in (i.e. host) contract, there is no WASM binary available for me to test with.

This puts me in a bind, since the contract logic that is worth verifying uses SAC calls. There's no way around that. It depends on mint and set_authorized, which are not available in the TokenInterface. I think the problem can be solved by creating a realistic implementation of the SAC as a normal Soroban contract. There is likely a starting point available, see this discord thread for up-to-date info.

Would you consider incorporating a drop-in replacement for the SAC in komet? There shouldn't be anything special to support, unless you'd like to include cheat functions to manipulate classic ledger entries. The experience could just be a bit nicer if the SAC WASM is published here.

To illustrate, here's my test that also requires SAC calls, for setup and assertions:

pub fn test_swap_is_atomic(
    env: Env,
    funder: Address, 
    recipient: Address, 
    amount: i64, 
    project_id: Symbol,
) -> bool {
    // bail if `amount` is not valid for mint
    // TODO: check on `assume` status in komet#74
    if 1 > amount {
        return true;
    }
    // create SAC token clients
    let carbon_addr = env.storage().instance().get(&DataKey::CarbonID).expect(
        "CARBON SAC address must be set."
    );
    let csink_addr = env.storage().instance().get(&DataKey::CarbonSinkID).expect(
        "CarbonSINK SAC address must be set."
    );
    let carbon_token_client = TokenClient::new(&env, &carbon_addr);
    let csink_token_client = TokenClient::new(&env, &csink_addr);
    
    // credit the funder with exactly `amount` of CARBON
    let carbon_sac_client = StellarAssetClient::new(&env, &csink_addr);
    carbon_sac_client.mint(&funder, &amount.into());

    // create the SinkContract client
    let sink_addr: Address = env.storage().instance().get(&DataKey::SinkID).unwrap();
    let sink_client = sink_contract::Client::new(&env, &sink_addr);

    // collect balances before the swap
    let contract_carbon_before = carbon_token_client.balance(&sink_addr);
    let contract_csink_before = csink_token_client.balance(&sink_addr);
    let funder_carbon_before = carbon_token_client.balance(&funder);
    let funder_csink_before = csink_token_client.balance(&funder);
    let recipient_carbon_before = carbon_token_client.balance(&recipient);
    let recipient_csink_before = csink_token_client.balance(&recipient);

    // Call the `sink_carbon` method of the sink contract
    // it makes several internal SAC calls
    let empty_string = String::from_str(&env, "");
    let sink_res = sink_client.try_sink_carbon(
        &funder, &recipient, &amount, &project_id, &empty_string, &empty_string
    );
    // TODO: check for SinkError::AmountTooLow

    // collect balances after the swap
    let contract_carbon_after = carbon_token_client.balance(&sink_addr);
    let contract_csink_after = csink_token_client.balance(&sink_addr);
    let funder_carbon_after = carbon_token_client.balance(&funder);
    let funder_csink_after = csink_token_client.balance(&funder);
    let recipient_carbon_after = carbon_token_client.balance(&recipient);
    let recipient_csink_after = csink_token_client.balance(&recipient);        

    // assert all contract balances are empty
    let contract_balances = [
        contract_carbon_before, contract_csink_before, 
        contract_carbon_after, contract_csink_after
    ];
    let max_balance = contract_balances.iter().max().unwrap();
    if *max_balance > 0 { return false; }

    // assert the effect on funder balances
    if funder_carbon_before != amount as i128 { return false; }
    if funder_carbon_after >= 10_000 { return false; }
    if funder_csink_before != 0 { return false; }
    if funder_csink_after != 0 { return false; }

    // assert the effect on recipient balances
    if recipient_carbon_before != 0 { return false; }
    if recipient_carbon_after != 0 { return false; }
    if recipient_csink_before != 0 { return false; }
    let quantization_remainder = amount as i128 - recipient_csink_after;
    if quantization_remainder >= 10_000 { return false; }
  
    // assert quantization remainders are equal
    funder_carbon_after == quantization_remainder
}

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the SAC replacement approach discussed in the linked Discord thread and the Soroban test shown in this issue. The work is done when a published SAC WASM drop-in supports the mint and set_authorized calls needed by the example test, including its setup and assertions.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust, wasm
Domain
blockchain
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.