Skip to content

feat: fully implement the injective bank precompile#1

Merged
maxim-inj merged 2 commits intov1.4.x-injfrom
CP-698/complete-bank-precompile
Nov 7, 2025
Merged

feat: fully implement the injective bank precompile#1
maxim-inj merged 2 commits intov1.4.x-injfrom
CP-698/complete-bank-precompile

Conversation

@arrivets
Copy link

@arrivets arrivets commented Nov 5, 2025

Full implementation of the Injective Bank precompile

For tests, have a look at this PR in the solidity-contracts repo:
InjectiveLabs/solidity-contracts#25

Summary by CodeRabbit

  • New Features

    • Fully implemented bank precompile with per-token balances, minting, burning, transfers, balance queries, total supply, and metadata (name/symbol/decimals).
  • Improvements

    • Input validation for metadata, overflow/underflow protections, and explicit per-operation gas handling for more predictable execution and clearer errors.

@coderabbitai
Copy link

coderabbitai bot commented Nov 5, 2025

Walkthrough

Adds a workspace dependency alloy-sol-types and replaces the injective bank precompile stub with a stateful implementation exposing an IBankModule interface, thread-local storage for balances/supplies/metadata, dedicated handlers (mint, burn, transfer, balanceOf, totalSupply, metadata, setMetadata), and ABI/gas handling.

Changes

Cohort / File(s) Change Summary
Dependency Management
crates/evm/networks/Cargo.toml
Added workspace-scoped dependency alloy-sol-types = { workspace = true }.
Bank Precompile Implementation
crates/evm/networks/src/injective/bank.rs
Replaced test stub with full stateful precompile: introduced IBankModule via sol! macro, thread-local BALANCES, SUPPLIES, METADATA (RefCell<HashMap<...>>), storage accessors, seven dedicated handlers (handle_mint, handle_burn, handle_balance_of, handle_transfer, handle_total_supply, handle_metadata, handle_set_metadata), selector-based dispatch, ABI encoding/decoding, gas costing, overflow/underflow and input-length validations, and explicit error returns.

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant Precompile as BankPrecompile
    participant Router as MethodRouter
    participant Handler as OperationHandler
    participant Storage as ThreadLocalStorage

    Caller->>Precompile: call(injective_bank, calldata)
    Precompile->>Router: decode selector
    Router->>Handler: dispatch to specific handler

    alt Mint
        Handler->>Storage: get_balance(token, recipient)
        Storage-->>Handler: balance
        Handler->>Storage: set_balance(token, recipient, new_balance)
        Handler->>Storage: set_supply(token, new_supply)
        Handler-->>Precompile: encoded success
    else Burn
        Handler->>Storage: get_balance(token, account)
        Handler->>Storage: set_balance(token, account, reduced)
        Handler->>Storage: set_supply(token, new_supply)
        Handler-->>Precompile: encoded success
    else Transfer
        Handler->>Storage: get_balance(token, sender)
        Handler->>Storage: set_balance(token, sender, debited)
        Handler->>Storage: set_balance(token, recipient, credited)
        Handler-->>Precompile: encoded success
    else Query (balanceOf/totalSupply/metadata)
        Handler->>Storage: read requested value
        Storage-->>Handler: value
        Handler-->>Precompile: encoded value
    end

    Precompile-->>Caller: returndata / error
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Areas needing extra attention:
    • IBankModule ABI selectors vs handler dispatch mapping.
    • Thread-local storage correctness and mutation patterns (RefCell<HashMap<...>>).
    • Overflow/underflow and gas accounting paths in each handler.
    • Metadata encoding/decoding and input-length validation.

Poem

🐇 I nibble bytes and stash them tight,

Mint a carrot, burn by night,
Balances hop from den to den,
Thread-local burrows, safe again,
Precompile moon shines on my pen.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: fully implement the injective bank precompile' directly and accurately describes the main change: replacing a minimal stub with a complete, stateful bank precompile implementation.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch CP-698/complete-bank-precompile

Comment @coderabbitai help to get the list of available commands and usage tips.

@arrivets arrivets requested a review from maxim-inj November 5, 2025 18:54
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 199a9b6 and aa69379.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (2)
  • crates/evm/networks/Cargo.toml (1 hunks)
  • crates/evm/networks/src/injective/bank.rs (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
crates/evm/networks/src/injective/bank.rs (1)
crates/evm/networks/src/lib.rs (1)
  • precompiles (122-133)
🔇 Additional comments (1)
crates/evm/networks/Cargo.toml (1)

25-25: Dependency addition looks correct.

The workspace-scoped alloy-sol-types addition aligns with the rest of the alloy stack already pulled from the workspace—no action needed.

Comment on lines +203 to +205
let current_supply = get_supply(token);
let new_supply = current_supply - amount;
set_supply(token, new_supply);
Copy link

@coderabbitai coderabbitai bot Nov 5, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Prevent total supply underflow when burning.

After subtracting amount from current_supply, we use U256’s unchecked -, so calling burn with amount > current_supply will underflow (panic or wrap). Mirror the balance guard by checking the supply before mutating state.

-    let current_supply = get_supply(token);
-    let new_supply = current_supply - amount;
-    set_supply(token, new_supply);
+    let current_supply = get_supply(token);
+    let new_supply = current_supply
+        .checked_sub(amount)
+        .ok_or_else(|| PrecompileError::Other("Insufficient supply to burn".into()))?;
+    set_supply(token, new_supply);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let current_supply = get_supply(token);
let new_supply = current_supply - amount;
set_supply(token, new_supply);
let current_supply = get_supply(token);
let new_supply = current_supply
.checked_sub(amount)
.ok_or_else(|| PrecompileError::Other("Insufficient supply to burn".into()))?;
set_supply(token, new_supply);
🤖 Prompt for AI Agents
In crates/evm/networks/src/injective/bank.rs around lines 203 to 205, the code
subtracts amount from current_supply using U256’s unchecked subtraction which
can underflow; add a guard that checks if current_supply >= amount before
mutating state and handle the failure the same way the balance guard does
(return an error/revert or early return) to prevent underflow, otherwise compute
new_supply = current_supply - amount and call set_supply(token, new_supply).

Copy link
Author

Choose a reason for hiding this comment

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

It is not possible for an address to contain more than the total issued amount.
And we already check that the burn amount doesn't exceed the user's balance

Copy link

Choose a reason for hiding this comment

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

@arrivets You're absolutely right! Your reasoning is sound: since we verify current_balance >= amount and the invariant ensures total_supply >= current_balance, it follows that total_supply >= amount, so the subtraction cannot underflow.

I notice that handle_mint uses checked_add for both balance and supply updates as a defensive measure against overflow. Using checked_sub for the supply in handle_burn would maintain symmetry with that defensive programming approach, but given the mathematical guarantee from the balance check, it's indeed redundant under normal operation.

Thank you for the clarification!


✏️ Learnings added
Learnt from: arrivets
Repo: InjectiveLabs/foundry PR: 1
File: crates/evm/networks/src/injective/bank.rs:203-205
Timestamp: 2025-11-06T13:28:01.523Z
Learning: In crates/evm/networks/src/injective/bank.rs, the `handle_burn` function does not need checked_sub for total supply because the balance check (`current_balance >= amount`) combined with the invariant (total supply >= any individual balance) mathematically guarantees that `total_supply >= amount`, preventing underflow.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Copy link
Collaborator

@maxim-inj maxim-inj left a comment

Choose a reason for hiding this comment

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

The limits there should match current Cosmos side:

	if len(metadata.Name) > 256 {
		return nil, errors.New("name is too long (max 256 characters)")
	} else if len(metadata.Symbol) > 128 {
		return nil, errors.New("symbol is too long (max 128 characters)")
	}

let symbol = call.symbol;
let decimals = call.decimals;

// Validate inputs
Copy link
Collaborator

Choose a reason for hiding this comment

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

Supposed to leave comment there

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
crates/evm/networks/src/injective/bank.rs (1)

203-205: Critical: Prevent total supply underflow when burning.

After subtracting amount from current_supply, the code uses U256's unchecked subtraction. If amount > current_supply, this will underflow and panic. While the balance is checked at lines 194-196, there's no guarantee that current_supply >= amount.

Apply this diff to add underflow protection:

-    let current_supply = get_supply(token);
-    let new_supply = current_supply - amount;
-    set_supply(token, new_supply);
+    let current_supply = get_supply(token);
+    let new_supply = current_supply
+        .checked_sub(amount)
+        .ok_or_else(|| PrecompileError::Other("Insufficient supply to burn".into()))?;
+    set_supply(token, new_supply);
🧹 Nitpick comments (2)
crates/evm/networks/src/injective/bank.rs (2)

306-340: Consider using Alloy's built-in tuple encoding.

The manual ABI encoding is correct but complex and error-prone. Since other handlers use .abi_encode() for return values, consider simplifying this implementation.

Consider this refactor using Alloy's built-in encoding:

-    // Manually encode the return tuple (string, string, uint8) for ABI compatibility
-    // This is the proper ABI encoding for a tuple with two dynamic types (strings) and one static type (uint8)
-    let mut result = Vec::new();
-    
-    // Offsets for the two strings (both are dynamic)
-    // Offset to first string (name) - starts after the 3 header words (96 bytes)
-    result.extend_from_slice(&U256::from(96).to_be_bytes::<32>());
-    
-    // Calculate offset to second string (symbol)
-    let name_bytes = name.as_bytes();
-    let name_padded_len = ((name_bytes.len() + 31) / 32) * 32;
-    let symbol_offset = 96 + 32 + name_padded_len; // header (96) + name length word (32) + name content
-    result.extend_from_slice(&U256::from(symbol_offset).to_be_bytes::<32>());
-    
-    // Decimals value (static, directly encoded)
-    let mut decimals_bytes = [0u8; 32];
-    decimals_bytes[31] = decimals;
-    result.extend_from_slice(&decimals_bytes);
-    
-    // Name string: length + content + padding
-    result.extend_from_slice(&U256::from(name_bytes.len()).to_be_bytes::<32>());
-    result.extend_from_slice(name_bytes);
-    if name_bytes.len() % 32 != 0 {
-        result.resize(result.len() + (32 - name_bytes.len() % 32), 0);
-    }
-    
-    // Symbol string: length + content + padding
-    let symbol_bytes = symbol.as_bytes();
-    result.extend_from_slice(&U256::from(symbol_bytes.len()).to_be_bytes::<32>());
-    result.extend_from_slice(symbol_bytes);
-    if symbol_bytes.len() % 32 != 0 {
-        result.resize(result.len() + (32 - symbol_bytes.len() % 32), 0);
-    }
-    
-    Ok(PrecompileOutput::new(METADATA_GAS_COST, Bytes::from(result)))
+    // Use Alloy's built-in tuple encoding
+    let result = (name, symbol, decimals).abi_encode();
+    Ok(PrecompileOutput::new(METADATA_GAS_COST, Bytes::from(result)))

Note: Verify that Alloy's SolValue::abi_encode() for tuples produces the expected encoding for the return type.


357-364: Consider adding explanatory comment.

A previous reviewer noted that a comment should be added here. While the validation code is self-explanatory, a brief comment explaining the length limits would improve readability.

Consider adding a comment:

 
+    // Validate metadata string lengths to prevent excessive storage usage
     // Validate inputs
     if name.len() > 256 {
         return Err(PrecompileError::Other("Name too long (max 256 characters)".into()));
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aa69379 and 8f136a9.

📒 Files selected for processing (1)
  • crates/evm/networks/src/injective/bank.rs (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
crates/evm/networks/src/injective/bank.rs (1)
crates/evm/networks/src/lib.rs (1)
  • precompiles (122-133)
🔇 Additional comments (8)
crates/evm/networks/src/injective/bank.rs (8)

23-31: LGTM! Thread-local storage appropriate for testing.

The use of thread-local storage with RefCell is suitable for the testing context mentioned in the file documentation. This design provides isolated state per thread and runtime borrow checking.


33-44: LGTM! Interface definition is clear and complete.

The IBankModule interface properly defines all seven operations with appropriate function signatures and modifiers.


70-103: LGTM! Clean dispatcher with proper error handling.

The routing logic correctly checks input length and dispatches to appropriate handlers based on selectors, with clear error messages for unknown methods.


142-174: LGTM! Mint operation with proper overflow protection.

The implementation correctly uses checked_add for both balance and supply updates, preventing overflow. The gas check and error handling are appropriate.


211-229: LGTM! Balance query is straightforward.

The implementation correctly retrieves and returns the balance with appropriate gas handling.


231-270: LGTM! Transfer implementation with proper safety checks.

The transfer correctly validates sender balance and uses checked_add for the recipient to prevent overflow.


272-289: LGTM! Total supply query is correct.

The implementation properly retrieves and returns the total supply.


343-374: LGTM! Metadata setter with proper input validation.

The implementation correctly validates string lengths and stores metadata. The length limits (256 for name, 128 for symbol) provide reasonable bounds.

@maxim-inj maxim-inj merged commit cd3c8a0 into v1.4.x-inj Nov 7, 2025
3 checks passed
@maxim-inj maxim-inj deleted the CP-698/complete-bank-precompile branch November 7, 2025 17:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants