foundry-rs / foundry

Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.
https://getfoundry.sh
Apache License 2.0
7.94k stars 1.61k forks source link

Adding `vm.formatUnits`, like EthersJS formatEther and formatUnits function #5106

Open Sz0x opened 1 year ago

Sz0x commented 1 year ago

Component

Forge

Describe the feature you would like

In the EthersJS library, there is a function called formatEther that takes in a value in wei (usually in the form of either a string or Big Number) and outputs it in ether format.

Similarly, the formatUnits function does the same thing, but takes an optional second parameter that specifies the number of decimals.

This is helpful when logging the balances of contracts and wallets to make it easier to read

Additional context

The utility function could be as simple as the following:


    function formatEther(uint256 amount) internal virtual returns (uint256) {
        return amount / 1 ether;
    }

    function formatUnits(uint256 amount, uint256 decimals) internal virtual returns (uint256) {
        return amount / (10 ** decimals);
    }
tynes commented 1 year ago

This seems more like a forge-std thing. cast --to-unit has this ability on the CLI

Evalir commented 1 year ago

yep agreed—this seems useful but more for forge-std. There's already similar utils on cast. @mds1 wdyt about transferring this issue to forge-std?

mds1 commented 1 year ago

Since this is mainly for logging, my suggestion would be to avoid adding a cheat here or in forge-std and leverage the format specifiers that we already support: https://github.com/foundry-rs/foundry/blob/b988ae49b2a0dc3c1e43c736f4204080630d113f/macros/src/fmt/console_fmt.rs#L165-L190

contract Test9 is Test {
  function test_consoleFmt() external {
    uint256 x = 1e18;
    console2.log("x is %s", x);
    console2.log("x is %i", x);
    console2.log("x is %x", x);
    console2.log("x is %e", x);
  }
}

// Running 1 test for test/9_consoleFmt.t.sol:Test9
// [PASS] test_consoleFmt() (gas: 6623)
// Logs:
//   x is 1000000000000000000
//   x is 1000000000000000000
//   x is 0xde0b6b3a7640000
//   x is 1e18

We already have %e for exponential, so I'd suggest:

Tudmotu commented 10 months ago

I might be misunderstanding the request here, but ethers::utils::format_units outputs a string, not a uint. And simply dividing a uint won't preserve decimal precision, so 0.1 ether / 1 ether will just revert.

Using console2 is also not ideal IMO because:

  1. Sometimes you wanna compose messages before logging
  2. console2 only logs at the end of a script, while sometimes it's useful to write logs into a file for real-time logging
  3. Can't rule out the possibility it could be useful in non-logging scenarios

@Sz0x I have an implementation of a vm.formatUnits cheatcode on my own Foundry fork. If you have your own fork as well, you can copy the implementation from my branch.

And for Foundry team — it seems this is not something you'd want to merge but if you change your mind let me know and I'll happily open a PR :slightly_smiling_face: https://github.com/Tudmotu/foundry/compare/feature/vm.time...Tudmotu:foundry:feature/vm.formatUnits?expand=1

Tudmotu commented 9 months ago

@mattsse WDYT ?

zerosnacks commented 3 weeks ago

Related: https://github.com/alloy-rs/core/blob/main/crates/primitives/src/utils/units.rs