Skip to content

How to accomodate Stellar Asset Contract calls in test setup #90

@aolieman

Description

@aolieman

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
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions