sway: The contract function returns wrong value

Issue: Incorrect Return Value from Contract Function

Summary

During the development of Spark Perps, we encountered a peculiar bug where the contract function vault::get_free_collateral_by_token() returns an incorrect value. Despite hardcoding a value within the function for testing, the returned value does not match the expected hardcoded value. This issue seems to be related to how forc compiles the contract, as the discrepancy is observed both in local tests and on the beta-5 test network.

Steps to Reproduce

  1. Clone the Spark Perps repository and check out the branch with the reported issue (if you don’t have a dotup, message me on t.me/defi_defiler):

    git clone git@github.com:compolabs/spark-perps.git
    cd spark-perps
    git checkout bug-failed-collateral-test
    forc build
    cargo test --package spark-perps --test integration_tests -- get_free_collateral::get_free_collateral_test --exact --nocapture
    
  2. Observe the output of the get_free_collateral::get_free_collateral_test which demonstrates the incorrect return value from the contract function.

Detailed Description

The function get_free_collateral_by_token is expected to return a hardcoded value of 2063986620 for testing purposes. However, the test logs indicate a returned value of 4000000000, suggesting a discrepancy in the function’s execution or return process.

  • Hardcoded function for reference:

    #[storage(read)]
    fn get_free_collateral_by_token(trader: Address, token: AssetId) -> u64 {
        let mut amount = 0;
        amount = get_free_collateral_by_token(trader, token);
        amount = 2063986620;
        return amount;
    }
    
  • Observation: Commenting out the line amount = get_free_collateral_by_token(trader, token);, rebuilding the contract, and rerunning the test yields the expected hardcoded value:

    #[storage(read)]
    fn get_free_collateral_by_token(trader: Address, token: AssetId) -> u64 {
        let mut amount = 0;
        // amount = get_free_collateral_by_token(trader, token);
        amount = 2063986620;
        return amount;
    }
    

Toolchain Configuration

  • fuel-toolchain.toml:
    [toolchain]
    channel = "latest-aarch64-apple-darwin"
    
    [components]
    forc = "0.49.3"
    fuel-core = "0.22.1"
    

Additional Information

This issue may point towards a potential bug in the forc compiler or the way it handles function returns. Any insights or fixes would be greatly appreciated.

Thank you for your attention to this matter.

Alex, Composability labs

About this issue

  • Original URL
  • State: closed
  • Created 3 months ago
  • Comments: 17 (8 by maintainers)

Most upvoted comments

@ironcev the function is not self-recursive, as that’s inside an impl block and calls a second function. This indeed is a miscompile. I reproduced it locally as well.

I was able to minimize the called function inside the codebase to this while still keeping the same miscompile:

#[storage(read)]
fn get_free_collateral_by_token(trader: Address, token: AssetId) -> u64 {
    let proxy_contract = abi(ProxyContract, PROXY_ADDRESS.into());
    let spark_contracts = proxy_contract.get_spark_contracts();
    let vault_contract = abi(VaultContract, spark_contracts.vault_address.into());
    let _ = vault_contract.get_collateral_balance(trader, SETTLEMENT_TOKEN);
    1
}

where


storage {
    collaterals: StorageMap<(Address, AssetId), u64> = StorageMap {},
}

impl ... {
    #[storage(read)]
    fn get_collateral_balance(trader: Address, token: AssetId) -> u64 {
        storage.collaterals.get((trader, token)).try_read().unwrap_or(0)
    }
}

Curiously, rewriting the original function to:

    #[storage(read)]
    fn get_free_collateral_by_token(trader: Address, token: AssetId) -> u64 {
        let mut amount = 0;
        asm(amount: amount) { log amount zero zero zero; };
        amount = get_free_collateral_by_token2(trader, token);
        asm(amount: amount) { log amount zero zero zero; };
        amount = 2063986620;
        asm(amount: amount) { log amount zero zero zero; };
        return amount;
    }

Logs the following values for amount: 0, 1, 2063986620, so the register/variable is updated correctly, the function just returns an incorrect value.

Looking into forc build --finalized-asm, I just cannot see the log statements at all. Same with forc parse-bytecode from the output binary. Not sure if this has anything to do with the actual issue above, but seemed to be worth a mention.

Sadly this is far as I got. Someone from @FuelLabs/sway-compiler should pick this one up.

Out of a quick glance at the function, get_free_collateral_by_token is an endlessly recursive function. Sway does not support recursions at moment and actually I would expect a compiler error here saying that the function is recursive. However, even if recursions were supported, the above definition would lead to endless recursion means running the code until running out of gas.