Skip to content

feat: adjustable fee configs#1160

Closed
williamrusdyputra wants to merge 7 commits into
mainfrom
feat/fee-configs
Closed

feat: adjustable fee configs#1160
williamrusdyputra wants to merge 7 commits into
mainfrom
feat/fee-configs

Conversation

@williamrusdyputra
Copy link
Copy Markdown
Contributor

@williamrusdyputra williamrusdyputra commented Sep 15, 2025

Related Problem

resolves: https://github.com/trufnetwork/truf-network/issues/1173

How Has This Been Tested?

Summary by CodeRabbit

  • New Features

    • Adds configurable bridge fee settings for deposits and withdrawals with a public lookup to fetch fee rate and treasury address.
    • Exposes bridge metadata and wallet balances via new public endpoints for both testnet and mainnet.
  • Refactor

    • Bridge flows now apply dynamic, config-driven fee calculation and route fees to configured treasury addresses while preserving existing interfaces.

@williamrusdyputra williamrusdyputra self-assigned this Sep 15, 2025
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Sep 15, 2025

Walkthrough

Adds a new fee_configs table and view for ERC20 bridge fees, and new/updated bridge actions that load fee configs to compute, lock/unlock, and route withdrawal fees to configured treasuries while exposing bridge info and wallet balances.

Changes

Cohort / File(s) Summary
Fee configuration migration
internal/migrations/023-fee-configs.sql
Creates fee_configs table (operation_type PK, fee_percentage NUMERIC(4,4), treasury_address, created_at, updated_at) with checks (operation_type in ('deposit','withdraw'), fee_percentage between 0–1, 42-char 0x address). Adds get_fee_config_by_type($operation_type TEXT) PUBLIC VIEW returning fee_percentage and treasury_address.
ERC20 bridge actions & views
internal/migrations/024-erc20-bridge.sql
Adds get_erc20_bridge_info() and wallet balance views (sepolia_wallet_balance, mainnet_wallet_balance). Adds/rewrites admin actions (sepolia_admin_bridge_tokens, mainnet_admin_bridge_tokens) to fetch withdraw config, compute fee = amount * fee_percentage, lock/unlock fee to configured treasury_address, then bridge amount - fee; raises if no config found.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Admin as Admin Caller
  participant Action as admin_bridge_tokens(amount)
  participant DB as DB: fee_configs
  participant Bridge as ERC20 Bridge

  Admin->>Action: invoke(amount)
  Action->>DB: get_fee_config_by_type('withdraw')
  DB-->>Action: {fee_percentage, treasury_address}
  alt fee_percentage > 0
    Action->>Bridge: lock(fee = amount * fee_percentage)
    Bridge-->>Action: locked
    Action->>Bridge: unlock(fee) -> treasury_address
    Bridge-->>Action: unlocked
  end
  Action->>Bridge: bridge(amount - fee)
  Bridge-->>Action: tx_result
  Action-->>Admin: return tx_result
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20–30 minutes

Possibly related PRs

Suggested reviewers

  • MicBun

Poem

"I hop through migrations, nibble schema seeds,
I tuck small fees where treasury needs.
Bridges hum, tokens find their way,
My whiskers guide the config play. 🐇🥕"

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Linked Issues Check ⚠️ Warning The changes implement a config-driven fee mechanism for withdrawals by adding fee_configs and using it in sepolia_admin_bridge_tokens and mainnet_admin_bridge_tokens, meeting the 'withdraw' objective of linked issue [#1173]; however, there is no deposit-side fee application in the provided migrations, so the PR does not fully implement a complete fee mechanism if deposit handling is expected and the PR's claim to "resolves #1173" is therefore inconsistent with the code. Either implement the deposit fee flow (consult fee_configs for operation_type = 'deposit' and apply fees on deposit code paths) or remove/update the "resolves #1173" linkage to indicate partial completion, and add automated tests covering fee calculation, treasury transfers, and missing-config error handling before merging.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "feat: adjustable fee configs" is concise and accurately reflects the primary change—adding configurable fee settings and wiring them into the ERC20 bridge via the new fee_configs table and related actions; it is specific and readable for a commit/PR history scan.
Out of Scope Changes Check ✅ Passed All changes are contained in internal/migrations/023-fee-configs.sql and 024-erc20-bridge.sql and are directly related to introducing adjustable fee configs and integrating them into bridge operations; the provided summary shows no unrelated file modifications or clearly out-of-scope edits.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/fee-configs

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown

@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: 5

🧹 Nitpick comments (3)
internal/migrations/023-fee-configs.sql (2)

16-16: Strengthen treasury address validation

Consider enforcing hex via regex (Postgres): treasury_address ~ '^0x[0-9a-fA-F]{40}$'. Current LIKE/LENGTH allows non-hex characters.

-    CHECK (treasury_address LIKE '0x%' AND LENGTH(treasury_address) = 42)
+    CHECK (treasury_address ~ '^0x[0-9a-fA-F]{40}$')

19-23: Seed/data notes: updated_at won’t auto-update; confirm @height

  • Add an UPDATE trigger to maintain updated_at on row changes.
  • Verify that @height is resolved by your migration engine in prod and local.

If using Postgres, I can provide a small trigger function; want me to add it?

internal/migrations/024-erc20-bridge.sql (1)

27-28: Comment typo

Says “withdraw” but queries deposit.

-  -- Get fee configuration for withdraw
+  -- Get fee configuration for deposit
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 48b6058 and 79fa8a8.

📒 Files selected for processing (2)
  • internal/migrations/023-fee-configs.sql (1 hunks)
  • internal/migrations/024-erc20-bridge.sql (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: acceptance-test

Comment thread internal/migrations/023-fee-configs.sql
Comment thread internal/migrations/023-fee-configs.sql
Comment thread internal/migrations/024-erc20-bridge.sql
Comment thread internal/migrations/024-erc20-bridge.sql
Comment thread internal/migrations/024-erc20-bridge.sql
@holdex
Copy link
Copy Markdown

holdex Bot commented Sep 15, 2025

Time Submission Status

Member Status Time Action Last Update
williamrusdyputra ✅ Submitted 1h Update time Sep 15, 2025, 11:27 AM
MicBun ✅ Submitted 20min Update time Sep 15, 2025, 12:29 PM

Copy link
Copy Markdown

@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

♻️ Duplicate comments (3)
internal/migrations/023-fee-configs.sql (2)

6-6: Fix precision to allow 1.0000 and keep types consistent.

NUMERIC(4,4) tops out at 0.9999 while the CHECK allows <= 1; also align the view return type with the column.

-    fee_percentage NUMERIC(4, 4) NOT NULL,
+    fee_percentage NUMERIC(5, 4) NOT NULL,
...
-PUBLIC VIEW RETURNS (
-    fee_percentage NUMERIC(4, 4),
+PUBLIC VIEW RETURNS (
+    fee_percentage NUMERIC(5, 4),

Also applies to: 28-29


11-11: Confirm single-config intent vs. multi-config design.

Primary key on operation_type enforces one row per op. If that’s the intended model, great; if you plan multiple fee receivers, switch to surrogate id PK + UNIQUE(operation_type, treasury_address) as previously suggested.

internal/migrations/024-erc20-bridge.sql (1)

27-34: Correct comment and truncate fractional fees to integer units; guard remainder.

Token amounts are integer-sized; avoid fractional $fee and ensure non-negative bridge amount.

-  -- Get fee configuration for deposit
-  $fee_percentage, $treasury_address := get_fee_config_by_type('withdraw');
-  $fee := $num_amount * $fee_percentage;
+  -- Get fee configuration for withdraw
+  $fee_percentage, $treasury_address := get_fee_config_by_type('withdraw');
+  -- floor/truncate to integer units
+  $fee := FLOOR($num_amount * $fee_percentage)::NUMERIC(78, 0);
   sepolia_bridge.lock($fee);
   sepolia_bridge.unlock($treasury_address, $fee);
 
   -- Bridge the rest to users
-  sepolia_bridge.bridge($num_amount - $fee);
+  $bridge_amount := $num_amount - $fee;
+  IF $bridge_amount < 0 { ERROR 'Bridge amount negative'; }
+  sepolia_bridge.bridge($bridge_amount);
🧹 Nitpick comments (3)
internal/migrations/023-fee-configs.sql (1)

13-16: Strengthen temporal integrity on timestamps.

Ensure updated_at is not earlier than created_at.

     -- Constraints
     CHECK (operation_type IN ('deposit', 'withdraw')),
     CHECK (fee_percentage >= 0 AND fee_percentage <= 1), -- 0% to 100%
-    CHECK (treasury_address LIKE '0x%' AND LENGTH(treasury_address) = 42)
+    CHECK (treasury_address LIKE '0x%' AND LENGTH(treasury_address) = 42),
+    CHECK (updated_at >= created_at)
internal/migrations/024-erc20-bridge.sql (2)

44-54: Unify var naming, fix comment, and truncate fee; mirror sepolia guard.

Keep naming consistent and prevent fractional/negative amounts.

-  $numAmount := $amount::NUMERIC(78, 0);
+  $num_amount := $amount::NUMERIC(78, 0);
 
-  -- Get fee configuration for deposit
-  $fee_percentage, $treasury_address := get_fee_config_by_type('withdraw');
-  $fee := $numAmount * $fee_percentage;
+  -- Get fee configuration for withdraw
+  $fee_percentage, $treasury_address := get_fee_config_by_type('withdraw');
+  $fee := FLOOR($num_amount * $fee_percentage)::NUMERIC(78, 0);
   mainnet_bridge.lock($fee);
-  mainnet_bridge.unlock($treasury_address, $fee);
+  mainnet_bridge.unlock($treasury_address, $fee);
 
   -- Bridge the rest to users
-  mainnet_bridge.bridge($numAmount - $fee);
+  $bridge_amount := $num_amount - $fee;
+  IF $bridge_amount < 0 { ERROR 'Bridge amount negative'; }
+  mainnet_bridge.bridge($bridge_amount);

29-31: Optionally skip zero-value lock/unlock for cleanliness.

No behavior change, but avoids no-op calls when $fee = 0.

-  sepolia_bridge.lock($fee);
-  sepolia_bridge.unlock($treasury_address, $fee);
+  IF $fee > 0 {
+    sepolia_bridge.lock($fee);
+    sepolia_bridge.unlock($treasury_address, $fee);
+  }

And similarly for mainnet:

-  mainnet_bridge.lock($fee);
-  mainnet_bridge.unlock($treasury_address, $fee);
+  IF $fee > 0 {
+    mainnet_bridge.lock($fee);
+    mainnet_bridge.unlock($treasury_address, $fee);
+  }

Also applies to: 49-51

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 79fa8a8 and f11636d.

📒 Files selected for processing (2)
  • internal/migrations/023-fee-configs.sql (1 hunks)
  • internal/migrations/024-erc20-bridge.sql (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: acceptance-test
🔇 Additional comments (1)
internal/migrations/024-erc20-bridge.sql (1)

27-29: Verify operation_type choice (‘withdraw’ vs ‘deposit’).

The actions are named “admin_bridge_tokens” (sounds like deposit path) but fetch fees for 'withdraw'. If intentional, ignore; otherwise switch to 'deposit'.

Also applies to: 46-48

Comment thread internal/migrations/023-fee-configs.sql
Copy link
Copy Markdown

@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 (3)
internal/migrations/024-erc20-bridge.sql (3)

27-38: Guardrail: cap Σ(fee_percentage) <= 100% before processing.

Add a pre-check to abort if misconfigured fees exceed 100%. Repeat for both networks.

+  -- Guardrail: total configured withdraw fee must not exceed 100%
+  $sum_pct := COALESCE((SELECT SUM(fee_percentage) FROM fee_configs WHERE operation_type = 'withdraw'), 0);
+  IF $sum_pct > 1 { ERROR 'Sum of withdraw fees exceeds 100%'; }

Place this before iterating configs.

Also applies to: 50-61


50-61: Mirror sepolia fixes in mainnet block (accumulate, floor, single bridge, 0-config guard).

Ensure identical behavior and safety on mainnet.

-  -- Get fee configuration for withdraw
-  FOR $config IN SELECT fee_percentage, treasury_address FROM fee_configs WHERE operation_type = 'withdraw' ORDER BY created_at LIMIT 1 {
-    $fee := $num_amount * $config.fee_percentage;
-
-    IF $fee > 0 {
-      mainnet_bridge.lock($fee);
-      mainnet_bridge.unlock($config.treasury_address, $fee);
-    }
-    
-    -- Bridge the rest to users
-    mainnet_bridge.bridge($num_amount - $fee);
-  }
+  -- Get fee configuration(s) for withdraw and accumulate fees
+  $total_fee := 0::NUMERIC(78,0);
+  $configs := 0::INT8;
+  FOR $config IN SELECT fee_percentage, treasury_address FROM get_fee_config_by_type('withdraw') {
+    $fee := FLOOR($num_amount * $config.fee_percentage)::NUMERIC(78,0);
+    IF $fee > 0 {
+      mainnet_bridge.lock($fee);
+      mainnet_bridge.unlock($config.treasury_address, $fee);
+      $total_fee := $total_fee + $fee;
+    }
+    $configs := $configs + 1;
+  }
+  IF $configs = 0 { ERROR 'No fee_configs rows for operation_type=withdraw'; }
+  IF $total_fee > $num_amount { ERROR 'Total fee exceeds amount'; }
+  $bridge_amount := $num_amount - $total_fee;
+  mainnet_bridge.bridge($bridge_amount);

27-38: Fix: handle 0-config case, integer rounding, single bridge, and guard against over‑fee.

Current logic silently does nothing if no config rows exist, may pass fractional amounts to lock/unlock, and bridges inside the loop. Bridge once after accumulating integer-rounded fees, error on misconfiguration, and prefer the provided view to encapsulate selection.

Apply:

-  -- Get fee configuration for withdraw
-  FOR $config IN SELECT fee_percentage, treasury_address FROM fee_configs WHERE operation_type = 'withdraw' ORDER BY created_at LIMIT 1 {
-    $fee := $num_amount * $config.fee_percentage;
-
-    IF $fee > 0 {
-      sepolia_bridge.lock($fee);
-      sepolia_bridge.unlock($config.treasury_address, $fee);
-    }
-    
-    -- Bridge the rest to users
-    sepolia_bridge.bridge($num_amount - $fee);
-  }
+  -- Get fee configuration(s) for withdraw and accumulate fees
+  $total_fee := 0::NUMERIC(78,0);
+  $configs := 0::INT8;
+  FOR $config IN SELECT fee_percentage, treasury_address FROM get_fee_config_by_type('withdraw') {
+    $fee := FLOOR($num_amount * $config.fee_percentage)::NUMERIC(78,0);
+    IF $fee > 0 {
+      sepolia_bridge.lock($fee);
+      sepolia_bridge.unlock($config.treasury_address, $fee);
+      $total_fee := $total_fee + $fee;
+    }
+    $configs := $configs + 1;
+  }
+  IF $configs = 0 { ERROR 'No fee_configs rows for operation_type=withdraw'; }
+  IF $total_fee > $num_amount { ERROR 'Total fee exceeds amount'; }
+  $bridge_amount := $num_amount - $total_fee;
+  sepolia_bridge.bridge($bridge_amount);
🧹 Nitpick comments (1)
internal/migrations/024-erc20-bridge.sql (1)

28-29: If you keep selecting from fee_configs directly: choose the latest row.

Using ORDER BY created_at without DESC picks the oldest, not the current config.

-FOR $config IN SELECT fee_percentage, treasury_address FROM fee_configs WHERE operation_type = 'withdraw' ORDER BY created_at LIMIT 1 {
+FOR $config IN SELECT fee_percentage, treasury_address FROM fee_configs WHERE operation_type = 'withdraw' ORDER BY created_at DESC LIMIT 1 {

Note: Prefer the get_fee_config_by_type view for encapsulation.

Also applies to: 51-52

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f11636d and 693fa04.

📒 Files selected for processing (1)
  • internal/migrations/024-erc20-bridge.sql (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: lint
  • GitHub Check: acceptance-test
🔇 Additional comments (1)
internal/migrations/024-erc20-bridge.sql (1)

27-38: get_fee_config_by_type present; FLOOR() not used — resolved

  • internal/migrations/023-fee-configs.sql defines CREATE OR REPLACE ACTION get_fee_config_by_type($operation_type TEXT) and exposes fee_percentage (NUMERIC(4,4)).
  • No runtime use of FLOOR() found in migrations (only a commented formula in internal/migrations/020-digest-actions.sql); no TRUNC() occurrences. No change required in internal/migrations/024-erc20-bridge.sql.

Copy link
Copy Markdown

@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: 3

♻️ Duplicate comments (1)
internal/migrations/024-erc20-bridge.sql (1)

48-61: Mainnet parity: truncation, guards, and 0‑row handling.

Align logic with sepolia to prevent fractional amounts and silent no‑ops.

-  $num_amount := $amount::NUMERIC(78, 0);
-  
-  -- Get fee configuration for withdraw
-  FOR $config IN SELECT fee_percentage, treasury_address FROM fee_configs WHERE operation_type = 'withdraw' ORDER BY created_at LIMIT 1 {
-    $fee := $num_amount * $config.fee_percentage;
-
-    IF $fee > 0::NUMERIC(78, 0) {
-      mainnet_bridge.lock($fee);
-      mainnet_bridge.unlock($config.treasury_address, $fee);
-    }
-    
-    -- Bridge the rest to users
-    mainnet_bridge.bridge($num_amount - $fee);
-  }
+  $num_amount := $amount::NUMERIC(78, 0);
+  $found := 0::INT8;
+  IF $num_amount < 0::NUMERIC(78,0) { ERROR 'Amount must be non-negative'; }
+
+  -- Get fee configuration for withdraw
+  FOR $config IN SELECT fee_percentage, treasury_address FROM fee_configs WHERE operation_type = 'withdraw' ORDER BY created_at DESC LIMIT 1 {
+    $fee := FLOOR($num_amount * $config.fee_percentage)::NUMERIC(78,0);
+    $bridge_amount := $num_amount - $fee;
+    IF $bridge_amount < 0::NUMERIC(78,0) { ERROR 'Bridge amount negative'; }
+
+    IF $fee > 0::NUMERIC(78, 0) {
+      mainnet_bridge.lock($fee);
+      mainnet_bridge.unlock($config.treasury_address, $fee);
+    }
+    -- Bridge the rest to users
+    mainnet_bridge.bridge($bridge_amount);
+    $found := 1;
+  }
+  IF $found = 0 { ERROR 'No fee_configs rows for operation_type=withdraw'; }
🧹 Nitpick comments (1)
internal/migrations/024-erc20-bridge.sql (1)

28-38: Optional: avoid FOR when LIMIT 1; select into locals and bridge once.

Cleaner and future‑proof; reduces chances of accidental per‑row bridging if LIMIT is changed.

Also applies to: 51-61

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 693fa04 and 8d5e609.

📒 Files selected for processing (1)
  • internal/migrations/024-erc20-bridge.sql (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: acceptance-test
🔇 Additional comments (1)
internal/migrations/024-erc20-bridge.sql (1)

28-38: Resolved — fee_configs enforces a single row per operation_type. internal/migrations/023-fee-configs.sql declares PRIMARY KEY (operation_type) and CHECK(fee_percentage >= 0 AND fee_percentage <= 1), so the LIMIT 1 is redundant and no accumulation logic is required; optionally use get_fee_config_by_type for consistency.

Comment thread internal/migrations/024-erc20-bridge.sql
Comment thread internal/migrations/024-erc20-bridge.sql Outdated
Comment thread internal/migrations/024-erc20-bridge.sql Outdated
Comment thread internal/migrations/023-fee-configs.sql Outdated
Comment on lines +20 to +23
INSERT INTO fee_configs (operation_type, fee_percentage, treasury_address, created_at, updated_at) VALUES
('deposit', 0.0100, '0xDe5B2aBce299eBdC3567895B1B4b02Ca2c33C94A', @height, @height),
('withdraw', 0.0100, '0xDe5B2aBce299eBdC3567895B1B4b02Ca2c33C94A', @height, @height)
ON CONFLICT (operation_type) DO NOTHING;
Copy link
Copy Markdown
Contributor

@MicBun MicBun Sep 15, 2025

Choose a reason for hiding this comment

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

I think it is better for insert statement to be in separate bootstrap migration file like internal/migrations/015-system-roles-bootstrap.sql.
But I think it will be better if it is not included in the regular migration file as it is specific to address 0xDe5B2aBce299eBdC3567895B1B4b02Ca2c33C94A like scripts/migrate.sh#41-51 does. This way, in the future (another PR I guess), we can test it with trufnetwork/kwil-db#1599. The script run using Taskfile.yml#317-328

cc @outerlook

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

got it, will separate or remove

@MicBun MicBun requested a review from outerlook September 15, 2025 07:58
Copy link
Copy Markdown

@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: 5

♻️ Duplicate comments (2)
internal/migrations/024-erc20-bridge.sql (2)

24-43: Truncate fee to integer units, guard negatives, and bridge once with validated remainder (sepolia)

Fee math can produce fractional NUMERIC; remainder may go negative if fee > amount. Add truncation and guards.

 CREATE OR REPLACE ACTION sepolia_admin_bridge_tokens($amount TEXT) PUBLIC {
   $num_amount := $amount::NUMERIC(78, 0);
-  
+  IF $num_amount < 0::NUMERIC(78,0) { ERROR('Amount must be non-negative'); }
   
   -- Get fee configuration for withdraw
-  $found := 0
+  $found := 0;
   FOR $config IN SELECT fee_percentage, treasury_address FROM fee_configs WHERE operation_type = 'withdraw' ORDER BY created_at DESC LIMIT 1 {
-    $fee := $num_amount * $config.fee_percentage;
+    $fee := FLOOR($num_amount * $config.fee_percentage)::NUMERIC(78,0);
+    $bridge_amount := $num_amount - $fee;
+    IF $bridge_amount < 0::NUMERIC(78,0) { ERROR('Bridge amount negative'); }
 
-    IF $fee > 0::NUMERIC(78, 0) {
+    IF $fee > 0::NUMERIC(78, 0) AND $config.treasury_address IS NOT NULL {
       sepolia_bridge.lock($fee);
       sepolia_bridge.unlock($config.treasury_address, $fee);
     }
     
     -- Bridge the rest to users
-    sepolia_bridge.bridge($num_amount - $fee);
+    sepolia_bridge.bridge($bridge_amount);
     $found := 1;
   }
 
   IF $found = 0 { ERROR('No fee_configs rows for operation_type=withdraw'); }
 };

51-70: Mirror the math/guards for mainnet action

-CREATE OR REPLACE ACTION mainnet_admin_bridge_tokens($amount TEXT) PUBLIC {
+CREATE OR REPLACE ACTION mainnet_admin_bridge_tokens($amount TEXT) PUBLIC {
   $num_amount := $amount::NUMERIC(78, 0);
-  
+  IF $num_amount < 0::NUMERIC(78,0) { ERROR('Amount must be non-negative'); }
   
   -- Get fee configuration for withdraw
-  $found := 0
+  $found := 0;
   FOR $config IN SELECT fee_percentage, treasury_address FROM fee_configs WHERE operation_type = 'withdraw' ORDER BY created_at DESC LIMIT 1 {
-    $fee := $num_amount * $config.fee_percentage;
+    $fee := FLOOR($num_amount * $config.fee_percentage)::NUMERIC(78,0);
+    $bridge_amount := $num_amount - $fee;
+    IF $bridge_amount < 0::NUMERIC(78,0) { ERROR('Bridge amount negative'); }
 
-    IF $fee > 0::NUMERIC(78, 0) {
+    IF $fee > 0::NUMERIC(78, 0) AND $config.treasury_address IS NOT NULL {
       mainnet_bridge.lock($fee);
       mainnet_bridge.unlock($config.treasury_address, $fee);
     }
     
     -- Bridge the rest to users
-    mainnet_bridge.bridge($num_amount - $fee);
+    mainnet_bridge.bridge($bridge_amount);
     $found := 1;
   }
 
   IF $found = 0 { ERROR('No fee_configs rows for operation_type=withdraw'); }
 };
🧹 Nitpick comments (4)
internal/migrations/024-erc20-bridge.sql (4)

33-35: Null/empty treasury safety

Validate treasury_address before unlock to avoid failing transfers or misrouting.

-    IF $fee > 0::NUMERIC(78, 0) AND $config.treasury_address IS NOT NULL {
+    IF $fee > 0::NUMERIC(78, 0) AND $config.treasury_address IS NOT NULL /* AND LENGTH($config.treasury_address) > 0 */ {

If LENGTH is unavailable, keep the NULL check and ensure non-empty at insert time via a CHECK constraint.

Also applies to: 60-62


1-16: Naming/scope: clarify which network get_erc20_bridge_info exposes

This view returns sepolia_bridge.info() but name suggests generic. Consider rename to sepolia_erc20_bridge_info or add a network parameter.


29-29: Indexing for config lookup

Add an index on fee_configs(operation_type, created_at DESC) to support ORDER BY ... LIMIT 1.

Example:

  • CREATE INDEX IF NOT EXISTS idx_fee_configs_op_created ON fee_configs(operation_type, created_at DESC);

Also applies to: 56-56


24-43: Atomicity and idempotency considerations

Ensure lock → unlock → bridge runs atomically (single transaction) and is safe to retry (idempotent) to avoid partial transfers on failures.

Also applies to: 51-70

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8d5e609 and 11438aa.

📒 Files selected for processing (1)
  • internal/migrations/024-erc20-bridge.sql (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: acceptance-test

Comment thread internal/migrations/024-erc20-bridge.sql
Comment thread internal/migrations/024-erc20-bridge.sql
Comment thread internal/migrations/024-erc20-bridge.sql
Comment thread internal/migrations/024-erc20-bridge.sql Outdated
Comment thread internal/migrations/024-erc20-bridge.sql Outdated
@williamrusdyputra
Copy link
Copy Markdown
Contributor Author

I can't test this rn, probably because of the pruning, the transaction keeps pending

@williamrusdyputra williamrusdyputra marked this pull request as draft September 15, 2025 11:22
Copy link
Copy Markdown

@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 (8)
internal/migrations/023-fee-configs.sql (4)

6-7: Allow 100% exactly; widen precision and keep CHECK.

NUMERIC(4,4) can’t store 1.0000 though CHECK allows <= 1. Bump to NUMERIC(5,4) in both table and action return type.

Apply:

-    fee_percentage NUMERIC(4, 4) NOT NULL,
+    fee_percentage NUMERIC(5, 4) NOT NULL,
@@
-PUBLIC VIEW RETURNS (
-    fee_percentage NUMERIC(4, 4),
+PUBLIC VIEW RETURNS (
+    fee_percentage NUMERIC(5, 4),

Also applies to: 22-22, 15-15


11-11: Support multiple configs; replace PK with surrogate id + UNIQUE.

Code in 024 iterates/filters by operation and created_at; PK on operation_type forbids multiple rows. Use id PK and UNIQUE(operation_type, treasury_address).

Apply:

-CREATE TABLE IF NOT EXISTS fee_configs (
-    operation_type TEXT NOT NULL,
+CREATE TABLE IF NOT EXISTS fee_configs (
+    id BIGSERIAL PRIMARY KEY,
+    operation_type TEXT NOT NULL,
@@
-    PRIMARY KEY (operation_type),
+    UNIQUE (operation_type, treasury_address),

19-28: Guard “not found” in getter action.

Return an explicit error when no row exists for the requested type.

Apply:

 CREATE OR REPLACE ACTION get_fee_config_by_type($operation_type TEXT) 
 PUBLIC VIEW RETURNS (
@@
 ) {
-    FOR $config IN SELECT fee_percentage, treasury_address FROM fee_configs WHERE operation_type = $operation_type {
+    IF NOT EXISTS (SELECT 1 FROM fee_configs WHERE operation_type = $operation_type) {
+        ERROR 'No fee_configs row for operation_type=' || $operation_type;
+    }
+    FOR $config IN SELECT fee_percentage, treasury_address FROM fee_configs WHERE operation_type = $operation_type {
         RETURN $config.fee_percentage, $config.treasury_address;
     }
 };

17-17: Index for latest-config lookup.

024 queries ORDER BY created_at DESC LIMIT 1; add a composite index.

Apply:

 );
 
+-- Optimize: latest-by-operation lookup
+CREATE INDEX IF NOT EXISTS idx_fee_configs_op_created_at
+ON fee_configs (operation_type, created_at DESC);
internal/migrations/024-erc20-bridge.sql (4)

29-31: Performance: rely on index in 023 for ORDER BY created_at DESC LIMIT 1.

Ensure idx_fee_configs_op_created_at exists; otherwise these lookups will scan.

Run to confirm index is present:

#!/bin/bash
rg -nP "CREATE\s+INDEX[^\n]+idx_fee_configs_op_created_at" -C1

Also applies to: 56-58


24-24: Admin actions should not be PUBLIC; add auth guard or restrict EXECUTE.

As PUBLIC, anyone can trigger admin bridging. Add an explicit role check now; adjust privileges in a follow-up migration.

Apply (inline guard):

-CREATE OR REPLACE ACTION sepolia_admin_bridge_tokens($amount TEXT) PUBLIC {
+CREATE OR REPLACE ACTION sepolia_admin_bridge_tokens($amount TEXT) PUBLIC {
+  IF NOT has_role('bridge_admin') { ERROR 'unauthorized'; }
-CREATE OR REPLACE ACTION mainnet_admin_bridge_tokens($amount TEXT) PUBLIC {
+CREATE OR REPLACE ACTION mainnet_admin_bridge_tokens($amount TEXT) PUBLIC {
+  IF NOT has_role('bridge_admin') { ERROR 'unauthorized'; }

Optionally also drop PUBLIC and manage GRANTs in a separate migration.

Also applies to: 51-51


24-43: Fees must be integer-truncated; add non-negative guard and bridge once per calc.

Current code multiplies NUMERIC(78,0) by a fraction, risking fractional amounts and negative remainders. Truncate fee, guard amount >= 0, compute remainder explicitly.

Apply:

 CREATE OR REPLACE ACTION sepolia_admin_bridge_tokens($amount TEXT) PUBLIC {
   $num_amount := $amount::NUMERIC(78, 0);
-  
+  IF $num_amount < 0::NUMERIC(78,0) { ERROR 'Amount must be non-negative'; }
@@
-  FOR $config IN SELECT fee_percentage, treasury_address FROM fee_configs WHERE operation_type = 'withdraw' ORDER BY created_at DESC LIMIT 1 {
-    $fee := $num_amount * $config.fee_percentage;
+  FOR $config IN SELECT fee_percentage, treasury_address FROM fee_configs WHERE operation_type = 'withdraw' ORDER BY created_at DESC LIMIT 1 {
+    $fee := FLOOR($num_amount * $config.fee_percentage)::NUMERIC(78,0);
+    $bridge_amount := $num_amount - $fee;
+    IF $bridge_amount < 0::NUMERIC(78,0) { ERROR 'Bridge amount negative'; }
 
-    IF $fee > 0::NUMERIC(78, 0) {
+    IF $fee > 0::NUMERIC(78, 0) {
       sepolia_bridge.lock($fee);
       sepolia_bridge.unlock($config.treasury_address, $fee);
     }
     
-    -- Bridge the rest to users
-    sepolia_bridge.bridge($num_amount - $fee);
+    -- Bridge the rest to users
+    sepolia_bridge.bridge($bridge_amount);
     $found := 1;
   }

51-70: Mirror fee truncation and guards for mainnet.

Same integer/negative issues as testnet block.

Apply:

 CREATE OR REPLACE ACTION mainnet_admin_bridge_tokens($amount TEXT) PUBLIC {
   $num_amount := $amount::NUMERIC(78, 0);
-  
+  IF $num_amount < 0::NUMERIC(78,0) { ERROR 'Amount must be non-negative'; }
@@
-  FOR $config IN SELECT fee_percentage, treasury_address FROM fee_configs WHERE operation_type = 'withdraw' ORDER BY created_at DESC LIMIT 1 {
-    $fee := $num_amount * $config.fee_percentage;
+  FOR $config IN SELECT fee_percentage, treasury_address FROM fee_configs WHERE operation_type = 'withdraw' ORDER BY created_at DESC LIMIT 1 {
+    $fee := FLOOR($num_amount * $config.fee_percentage)::NUMERIC(78,0);
+    $bridge_amount := $num_amount - $fee;
+    IF $bridge_amount < 0::NUMERIC(78,0) { ERROR 'Bridge amount negative'; }
 
     IF $fee > 0::NUMERIC(78, 0) {
       mainnet_bridge.lock($fee);
       mainnet_bridge.unlock($config.treasury_address, $fee);
     }
     
-    -- Bridge the rest to users
-    mainnet_bridge.bridge($num_amount - $fee);
+    -- Bridge the rest to users
+    mainnet_bridge.bridge($bridge_amount);
     $found := 1;
   }
🧹 Nitpick comments (2)
internal/migrations/023-fee-configs.sql (1)

16-16: Stricter address validation (optional).

If the dialect supports regex, ensure hex-only after 0x; otherwise keep as-is.

Apply (if regex available):

-    CHECK (treasury_address LIKE '0x%' AND LENGTH(treasury_address) = 42)
+    CHECK (treasury_address ~ '^0x[0-9a-fA-F]{40}$')
internal/migrations/024-erc20-bridge.sql (1)

1-16: Bridge info only returns Sepolia; clarify or parameterize.

Either rename to get_sepolia_bridge_info or accept a network arg and branch.

Apply (rename option):

-CREATE OR REPLACE ACTION get_erc20_bridge_info() 
+CREATE OR REPLACE ACTION get_sepolia_bridge_info() 
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 11438aa and 97d0caf.

📒 Files selected for processing (2)
  • internal/migrations/023-fee-configs.sql (1 hunks)
  • internal/migrations/024-erc20-bridge.sql (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: acceptance-test

@MicBun
Copy link
Copy Markdown
Contributor

MicBun commented Sep 15, 2025

I can't test this rn, probably because of the pruning, the transaction keeps pending

@outerlook this might be related? trufnetwork/kwil-db#1606

@outerlook
Copy link
Copy Markdown
Contributor

@MicBun

this might be related? trufnetwork/kwil-db#1606

Might be related. But after resetting, the network is normal again. If it's tried again, we can verify this

@outerlook
Copy link
Copy Markdown
Contributor

Also, the pruning should not prevent any transactions on our mainnet, especially if targetting the gateway/node1/node2 as it's very lightweight for them

@williamrusdyputra
Copy link
Copy Markdown
Contributor Author

@outerlook idk if this is related but previously when I directly multiplying (amount * 0.01) it works well,

then now I multiply by config with NUMERIC(4, 4), is it possible that it's the source of error?

amount is NUMERIC(78, 0).

@williamrusdyputra
Copy link
Copy Markdown
Contributor Author

this PR is cancelled, will be continued in this Goal:

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.

3 participants