Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 2 additions & 7 deletions .github/workflows/manual-sol-artifacts.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ on:
options:
- log-tables
- decimal-float

jobs:
deploy:
runs-on: ubuntu-latest
Expand All @@ -18,7 +17,6 @@ jobs:
with:
submodules: recursive
fetch-depth: 0

- uses: nixbuild/nix-quick-install-action@v30
with:
nix_conf: |
Expand All @@ -35,22 +33,19 @@ jobs:
# before trying to save a new cache
# 1G = 1073741824
gc-max-store-size-linux: 1G

- run: nix develop -c rainix-sol-prelude
- run: nix develop -c forge selectors up --all
- run: nix develop -c forge script script/Deploy.sol:Deploy -vvvvv --slow --broadcast --verify
env:
DEPLOYMENT_SUITE: ${{ inputs.suite }}
DEPLOYMENT_KEY: ${{ github.ref == 'refs/heads/main' && secrets.PRIVATE_KEY || secrets.PRIVATE_KEY_DEV }}

DEPLOYMENT_KEY: ${{ secrets.PRIVATE_KEY }}
CI_DEPLOY_ARBITRUM_RPC_URL: ${{ secrets.CI_DEPLOY_ARBITRUM_RPC_URL || vars.CI_DEPLOY_ARBITRUM_RPC_URL || '' }}
CI_DEPLOY_BASE_RPC_URL: ${{ secrets.CI_DEPLOY_BASE_RPC_URL || vars.CI_DEPLOY_BASE_RPC_URL || '' }}
CI_DEPLOY_BASE_SEPOLIA_RPC_URL: ${{ secrets.CI_DEPLOY_BASE_SEPOLIA_RPC_URL || vars.CI_DEPLOY_BASE_SEPOLIA_RPC_URL || '' }}
CI_DEPLOY_FLARE_RPC_URL: ${{ secrets.CI_DEPLOY_FLARE_RPC_URL || vars.CI_DEPLOY_FLARE_RPC_URL || '' }}
CI_DEPLOY_POLYGON_RPC_URL: ${{ secrets.CI_DEPLOY_POLYGON_RPC_URL || vars.CI_DEPLOY_POLYGON_RPC_URL || '' }}

CI_DEPLOY_ARBITRUM_ETHERSCAN_API_KEY: ${{ secrets.CI_DEPLOY_ARBITRUM_ETHERSCAN_API_KEY || vars.CI_DEPLOY_ARBITRUM_ETHERSCAN_API_KEY || '' }}
CI_DEPLOY_BASE_ETHERSCAN_API_KEY: ${{ secrets.CI_DEPLOY_BASE_ETHERSCAN_API_KEY || vars.CI_DEPLOY_BASE_ETHERSCAN_API_KEY || '' }}
CI_DEPLOY_BASE_SEPOLIA_ETHERSCAN_API_KEY: ${{ secrets.CI_DEPLOY_BASE_SEPOLIA_ETHERSCAN_API_KEY || vars.CI_DEPLOY_BASE_SEPOLIA_ETHERSCAN_API_KEY || '' }}
CI_DEPLOY_FLARE_ETHERSCAN_API_KEY: ${{ secrets.CI_DEPLOY_FLARE_ETHERSCAN_API_KEY || vars.CI_DEPLOY_FLARE_ETHERSCAN_API_KEY || '' }}
CI_DEPLOY_POLYGON_ETHERSCAN_API_KEY: ${{ secrets.CI_DEPLOY_POLYGON_ETHERSCAN_API_KEY || vars.CI_DEPLOY_POLYGON_ETHERSCAN_API_KEY || '' }}
CI_DEPLOY_POLYGON_ETHERSCAN_API_KEY: ${{ secrets.CI_DEPLOY_POLYGON_ETHERSCAN_API_KEY || vars.CI_DEPLOY_POLYGON_ETHERSCAN_API_KEY || '' }}
99 changes: 78 additions & 21 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
This file provides guidance to Claude Code (claude.ai/code) when working with
code in this repository.

## Project Overview

Decimal floating-point math library for Rainlang/DeFi. The `Float` type packs a 224-bit signed coefficient and 32-bit signed exponent into a single `bytes32`. Decimal (not binary) representation ensures exact decimal values (e.g., `0.1`). No NaN, Infinity, or negative zero — operations error on nonsense rather than producing special values.
Decimal floating-point math library for Rainlang/DeFi. The `Float` type packs a
224-bit signed coefficient and 32-bit signed exponent into a single `bytes32`.
Decimal (not binary) representation ensures exact decimal values (e.g., `0.1`).
No NaN, Infinity, or negative zero — operations error on nonsense rather than
producing special values.

Dual implementation: Solidity for on-chain, Rust/WASM for off-chain JS/TS consumption. The Rust crate uses revm to execute Solidity via an in-memory EVM, ensuring identical behavior.
Dual implementation: Solidity for on-chain, Rust/WASM for off-chain JS/TS
consumption. The Rust crate uses revm to execute Solidity via an in-memory EVM,
ensuring identical behavior.

## Build Commands

### Solidity (Foundry)

```bash
forge build # Compile contracts
forge test # Run all Solidity tests (5096 fuzz runs)
Expand All @@ -19,72 +27,121 @@ forge test -vvvv # Verbose trace output for debugging
```

### Rust

```bash
cargo build # Build native
cargo build --target wasm32-unknown-unknown --lib -r # Build WASM
cargo test # Run Rust tests
cargo test test_name # Run specific test
```

Rust tests depend on Foundry build artifacts (`out/`). Run `forge build` before `cargo test` if artifacts are missing.
Rust tests depend on Foundry build artifacts (`out/`). Run `forge build` before
`cargo test` if artifacts are missing.

### JavaScript/WASM

```bash
npm install
npm run build # Full pipeline: Rust WASM → wasm-bindgen → base64 embed → CJS/ESM dist
npm test # TypeScript type check + vitest (tests in test_js/)
```

### Nix

```bash
nix develop # Enter dev shell with all tooling
```

### Deployment
Contracts are deployed deterministically via the Zoltu proxy to the same address on all supported networks (Arbitrum, Base, Base Sepolia, Flare, Polygon). Two deployment suites (log-tables must be deployed first):

Contracts are deployed deterministically via the Zoltu proxy to the same address
on all supported networks (Arbitrum, Base, Base Sepolia, Flare, Polygon). The
deterministic address is a function of bytecode + salt only — not the branch or
deployer — so a successful deploy from any branch lands at the same address a
main-branch deploy would.

**Typical flow for a source-changing PR**: trigger the `Manual sol artifacts`
GitHub workflow on the PR's branch before merge.
`gh workflow run manual-sol-artifacts.yaml --ref <branch> -f suite=decimal-float`
(use `log-tables` only when table bytecode changes, which is rare). The workflow
runs `script/Deploy.sol` with `--broadcast --verify` across all networks, using
`PRIVATE_KEY` regardless of ref. Do NOT wait for merge before deploying — there
is nothing to gain from waiting, and the CI deploy-constant tests need updating
anyway based on the deployed address.
Comment on lines +67 to +70
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Nit: "Do NOT wait for merge before deploying" rationale.

The sentence "there is nothing to gain from waiting, and the CI deploy-constant tests need updating anyway based on the deployed address" is slightly misleading — the deterministic address is a pure function of bytecode+salt and is known without deploying (it's already asserted in LibRainDeploy.deployZoltu), so what actually needs updating after a source change is the codehash (and incidentally the address, if creation code changed). Consider rewording to "the deploy-constant tests need updating based on the new codehash" to avoid implying the address is only discoverable post-deploy.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CLAUDE.md` around lines 67 - 70, Reword the sentence that reads "Do NOT wait
for merge before deploying — there is nothing to gain from waiting, and the CI
deploy-constant tests need updating anyway based on the deployed address" to
avoid implying addresses are only known post-deploy; instead state that the
deterministic address is derivable from bytecode+salt (as asserted by
LibRainDeploy.deployZoltu) and that what changes after a source update is the
codehash (and possibly the address if creation code changes), so update the
CLAUDE.md text to say the CI deploy-constant tests need updating based on the
new codehash (and address only if creation code changed).


**Two deployment suites** (log-tables must be deployed first if redeploying
tables):

```bash
DEPLOYMENT_KEY=<key> DEPLOYMENT_SUITE=log-tables forge script script/Deploy.sol:Deploy --broadcast --verify
DEPLOYMENT_KEY=<key> DEPLOYMENT_SUITE=decimal-float forge script script/Deploy.sol:Deploy --broadcast --verify
```
Comment on lines +72 to 78
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Minor: suite-ordering guidance reads slightly ambiguously.

Line 72 says "log-tables must be deployed first if redeploying tables", and the example block then lists log-tables before decimal-float. For a typical source-only PR (the common case this section describes), only decimal-float should be run — the example invites copy-paste of both commands. Consider either annotating the log-tables line as conditional, or showing only the decimal-float command in the typical-flow block and keeping the two-suite example under a separate "when redeploying tables" heading.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CLAUDE.md` around lines 72 - 78, The guidance about deployment-suite ordering
is ambiguous: clarify that "log-tables" is only required when redeploying tables
and that the typical source-only flow runs only "decimal-float"; update the
CLAUDE.md example by either (a) marking the `DEPLOYMENT_SUITE=log-tables`
command as conditional/annotated (e.g., "only if redeploying tables") or (b)
splitting into two examples — a primary example showing only
`DEPLOYMENT_SUITE=decimal-float` for the normal PR flow and a separate "When
redeploying tables" block showing both `log-tables` then `decimal-float` —
referencing the `log-tables` and `decimal-float` suite names and the shown forge
commands so reviewers know which lines to change.

Expected addresses and code hashes are in `src/lib/deploy/LibDecimalFloatDeploy.sol`. Network RPC URLs are configured in `foundry.toml` via `CI_DEPLOY_*_RPC_URL` env vars.

Expected addresses and code hashes are in
`src/lib/deploy/LibDecimalFloatDeploy.sol`. Any source change to
`LibDecimalFloat` or `LibFormatDecimalFloat` invalidates these constants; CI's
`testDeployAddress` and `testExpectedCodeHashDecimalFloat` will fail until
they're regenerated and committed. Network RPC URLs are configured in
`foundry.toml` via `CI_DEPLOY_*_RPC_URL` env vars.

## Architecture

### Solidity Layer (`src/`)
- **`lib/LibDecimalFloat.sol`** — Public API: arithmetic, comparison, conversion, formatting, parsing. User-defined type `Float` wrapping `bytes32`.
- **`lib/implementation/`** — Internal arithmetic (512-bit intermediates for mul/div), normalization, packing.

- **`lib/LibDecimalFloat.sol`** — Public API: arithmetic, comparison,
conversion, formatting, parsing. User-defined type `Float` wrapping `bytes32`.
- **`lib/implementation/`** — Internal arithmetic (512-bit intermediates for
mul/div), normalization, packing.
- **`lib/parse/`** — String-to-Float parsing.
- **`lib/format/`** — Float-to-string formatting.
- **`lib/table/`** — Log lookup tables (deployed as a data contract at a deterministic address).
- **`concrete/DecimalFloat.sol`** — Exposes library functions as contract methods (required for Rust/revm interop via ABI).
- **`error/`** — Custom error definitions (CoefficientOverflow, ExponentOverflow, DivisionByZero, etc.).
- **`lib/table/`** — Log lookup tables (deployed as a data contract at a
deterministic address).
- **`concrete/DecimalFloat.sol`** — Exposes library functions as contract
methods (required for Rust/revm interop via ABI).
- **`error/`** — Custom error definitions (CoefficientOverflow,
ExponentOverflow, DivisionByZero, etc.).

### Scripts (`script/`)
- **`Deploy.sol`** — Production deployment script using Zoltu deterministic proxy. Deploys log tables and DecimalFloat contract to all supported networks.
- **`BuildPointers.sol`** — Generates `src/generated/LogTables.pointers.sol` (committed to repo; must be regenerated if log table data changes).

- **`Deploy.sol`** — Production deployment script using Zoltu deterministic
proxy. Deploys log tables and DecimalFloat contract to all supported networks.
- **`BuildPointers.sol`** — Generates `src/generated/LogTables.pointers.sol`
(committed to repo; must be regenerated if log table data changes).

### Rust Layer (`crates/float/`)
- **`lib.rs`** — `Float` struct wrapping `B256`, implements `Add`/`Sub`/`Mul`/`Div`/`Neg`. Uses `alloy::sol!` macro to generate bindings from Foundry JSON artifacts in `out/`.
- **`js_api.rs`** — `#[wasm_bindgen]` exports for JS consumption (parse, format, arithmetic, conversions).
- **`evm.rs`** — In-memory EVM setup via revm. All Rust float operations delegate to Solidity through this.

- **`lib.rs`** — `Float` struct wrapping `B256`, implements
`Add`/`Sub`/`Mul`/`Div`/`Neg`. Uses `alloy::sol!` macro to generate bindings
from Foundry JSON artifacts in `out/`.
- **`js_api.rs`** — `#[wasm_bindgen]` exports for JS consumption (parse, format,
arithmetic, conversions).
- **`evm.rs`** — In-memory EVM setup via revm. All Rust float operations
delegate to Solidity through this.
- **`error.rs`** — Maps Solidity error selectors to Rust error types.

### JavaScript Layer
- **`scripts/build.js`** — Build pipeline: compiles WASM, runs wasm-bindgen, base64-encodes WASM into JS modules for both CJS and ESM.

- **`scripts/build.js`** — Build pipeline: compiles WASM, runs wasm-bindgen,
base64-encodes WASM into JS modules for both CJS and ESM.
- **`test_js/`** — Vitest tests for the WASM bindings.
- **`dist/`** — Generated output (CJS + ESM with embedded WASM).

### Dependencies (`lib/`)
Git submodules: forge-std, rain.string, rain.datacontract, rain.math.fixedpoint, rain.deploy, rain.sol.codegen.

Git submodules: forge-std, rain.string, rain.datacontract, rain.math.fixedpoint,
rain.deploy, rain.sol.codegen.

## Key Design Details

- 512-bit intermediate values in multiply/divide to preserve precision.
- Exponent underflow silently rounds toward zero; exponent overflow reverts.
- Log/power use lookup table approximations with linear interpolation (table deployed as a data contract).
- Two packing modes: lossless (reverts on precision loss) and lossy (returns bool flag).
- Log/power use lookup table approximations with linear interpolation (table
deployed as a data contract).
- Two packing modes: lossless (reverts on precision loss) and lossy (returns
bool flag).
- Solidity compiler: 0.8.25, EVM target: Cancun, optimizer: 1,000,000 runs.

## License

LicenseRef-DCL-1.0 (Rain Decentralized Computer License). All source files require SPDX headers per REUSE.toml.
LicenseRef-DCL-1.0 (Rain Decentralized Computer License). All source files
require SPDX headers per REUSE.toml.
21 changes: 21 additions & 0 deletions src/lib/LibDecimalFloat.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,22 @@ import {
} from "../error/ErrDecimalFloat.sol";
import {LibDecimalFloatImplementation} from "./implementation/LibDecimalFloatImplementation.sol";

/// A decimal floating point number packed into 32 bytes. The high 32 bits are
/// a signed int32 exponent; the low 224 bits are a signed int224 coefficient.
/// The value represented is `coefficient × 10^exponent`.
///
/// Representations are non-canonical by design. Every non-zero value has an
/// infinite family of `(coefficient, exponent)` pairs that represent it — for
/// example `(5, 0)`, `(50, -1)`, and `(5000, -3)` all equal the number `5` and
/// pack to different `bytes32`. Equality between Floats is therefore numeric
/// (via `eq`, which rescales before comparing), not byte-level. `packLossy`
/// does not strip trailing decimal zeros from the coefficient; it only
/// shrinks the coefficient when it does not fit int224. This is deliberate:
/// canonicalization is not free on the arithmetic hot path, and most
/// operations do not care. Consumers that need a canonical form (raw-byte
/// equality, hashing as a map key, downstream range checks) must canonicalize
/// locally at the point of use. See `LibDecimalFloatImplementation.eq` for
/// the operative numeric-equality contract.
type Float is bytes32;

/// @title LibDecimalFloat
Expand All @@ -25,6 +41,11 @@ type Float is bytes32;
/// - There is no concept of rounding modes.
/// - There is no negative zero.
/// - This is a decimal floating point library, not binary.
/// - Representations are non-canonical. Multiple `(coefficient, exponent)`
/// pairs can encode the same numeric value; equality is numeric, not
/// byte-level. Canonicalization is deferred to consumers that need it
/// rather than enforced in packing, to keep the arithmetic hot path cheap.
/// See the docstring on the `Float` type for detail.
///
/// This means that operations such as divide by 0 will revert, rather than
/// produce nonsense like NaN or Infinity. This is a deliberate design choice
Expand Down
4 changes: 2 additions & 2 deletions src/lib/deploy/LibDecimalFloatDeploy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ library LibDecimalFloatDeploy {
/// @dev Address of the DecimalFloat contract deployed via Zoltu's
/// deterministic deployment proxy.
/// This address is the same across all EVM-compatible networks.
address constant ZOLTU_DEPLOYED_DECIMAL_FLOAT_ADDRESS = address(0x18e859f1a5d323b0baE73732211ABD979E2D3246);
address constant ZOLTU_DEPLOYED_DECIMAL_FLOAT_ADDRESS = address(0xf8206b5dF01D68a3625Ce48Ce593C6D89B7E8144);

/// @dev The expected codehash of the DecimalFloat contract deployed via
/// Zoltu's deterministic deployment proxy.
bytes32 constant DECIMAL_FLOAT_CONTRACT_HASH = 0x87ca4d55f93e5db8777b3e723b796e3a039c4b4ae2a486f69129c434edfb110e;
bytes32 constant DECIMAL_FLOAT_CONTRACT_HASH = 0x624040915402064e37887b92f6da7e8a46d5364afbfa4258402c1c24d99b33e2;

/// Combines all log and anti-log tables into a single bytes array for
/// deployment. These are using packed encoding to minimize size and remove
Expand Down
Loading
Loading