Summary
The parseUnit expression builtin divides the value by the raw decimal count instead of scaling by 10^decimals, and it cannot accept a fractional value at all. Both defects make it unusable for its documented purpose (building a fixed-point price threshold), and any task condition using it compares against a meaningless number.
Location
core/taskengine/macros/exp.go:118
func ParseUnit(val string, decimal uint) *big.Int {
b, ok := ethmath.ParseBig256(val)
if !ok {
panic(fmt.Errorf("Parse error: %s", val))
}
r := big.NewInt(0)
return r.Div(b, big.NewInt(int64(decimal))) // <-- divides by `decimal` (e.g. 8), not 10^decimal
}
Registered as the user-facing parseUnit builtin at exp.go:206.
Defects
1. Wrong scaling. The name mirrors ethers.js parseUnits(value, decimals), the universal EVM convention, which returns value * 10^decimals. The implementation instead computes value / decimals (integer division by the raw count).
| Call |
Current result |
Expected (value * 10^decimals) |
parseUnit("2621", 8) |
327 (2621 / 8) |
262100000000 |
parseUnit("100", 6) |
16 (100 / 6) |
100000000 |
2. No fractional input. ethmath.ParseBig256 only parses integers/hex, so parseUnit("2621.99", 8) panics (Parse error: 2621.99). Price thresholds are inherently fractional, so this is exactly the input the builtin needs to accept.
Impact
parseUnit is exposed to users in task-condition expressions (documented use: a price threshold compared against chainlinkPrice(...), which returns an 8-decimal integer). Because the produced threshold is wrong by orders of magnitude — or panics outright on a fractional value — conditions fire when they shouldn't or never fire. Silent, incorrect automation behavior with no error surfaced.
Proposed fix
Reimplement ParseUnit to match ethers parseUnits:
- scale by
10^decimals
- accept a fractional component, right-padded to
decimals
- reject over-precision (fraction longer than
decimals), negatives, and non-numeric input
func ParseUnit(val string, decimal uint) *big.Int {
parts := strings.SplitN(val, ".", 2)
whole, ok := new(big.Int).SetString(parts[0], 10)
// ... scale whole by 10^decimals, add right-padded fraction, reject over-precision/negatives
}
No existing tests or callers depend on the current (broken) output — parseUnit has zero coverage and is only referenced in a commented-out example — so the fix is safe for anyone reasonably expecting ethers semantics.
Summary
The
parseUnitexpression builtin divides the value by the raw decimal count instead of scaling by10^decimals, and it cannot accept a fractional value at all. Both defects make it unusable for its documented purpose (building a fixed-point price threshold), and any task condition using it compares against a meaningless number.Location
core/taskengine/macros/exp.go:118Registered as the user-facing
parseUnitbuiltin atexp.go:206.Defects
1. Wrong scaling. The name mirrors ethers.js
parseUnits(value, decimals), the universal EVM convention, which returnsvalue * 10^decimals. The implementation instead computesvalue / decimals(integer division by the raw count).value * 10^decimals)parseUnit("2621", 8)327(2621 / 8)262100000000parseUnit("100", 6)16(100 / 6)1000000002. No fractional input.
ethmath.ParseBig256only parses integers/hex, soparseUnit("2621.99", 8)panics (Parse error: 2621.99). Price thresholds are inherently fractional, so this is exactly the input the builtin needs to accept.Impact
parseUnitis exposed to users in task-condition expressions (documented use: a price threshold compared againstchainlinkPrice(...), which returns an 8-decimal integer). Because the produced threshold is wrong by orders of magnitude — or panics outright on a fractional value — conditions fire when they shouldn't or never fire. Silent, incorrect automation behavior with no error surfaced.Proposed fix
Reimplement
ParseUnitto match ethersparseUnits:10^decimalsdecimalsdecimals), negatives, and non-numeric inputNo existing tests or callers depend on the current (broken) output —
parseUnithas zero coverage and is only referenced in a commented-out example — so the fix is safe for anyone reasonably expecting ethers semantics.