Skip to content

feat: apply fee when withdrawing TRUF tokens#1158

Merged
MicBun merged 2 commits intomainfrom
feat/withdraw-fee
Sep 15, 2025
Merged

feat: apply fee when withdrawing TRUF tokens#1158
MicBun merged 2 commits intomainfrom
feat/withdraw-fee

Conversation

@williamrusdyputra
Copy link
Contributor

@williamrusdyputra williamrusdyputra commented Sep 15, 2025

Related Problem

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

  • wallet balance can be checked by anyone
  • bridge have 1% fee

How Has This Been Tested?


Initial Balance on Layer-1

Screenshot 2025-09-15 at 11 07 56

Bridge 2 tokens, (fee 1%)

Screenshot 2025-09-15 at 11 17 20

Claim tokens on Layer-1 (deducted by fee already)

Screenshot 2025-09-15 at 11 12 54 Screenshot 2025-09-15 at 11 14 53

Fee is still in our treasury

Screenshot 2025-09-15 at 11 19 51

Summary by CodeRabbit

  • New Features
    • Added a public bridge info endpoint showing network, escrow, epoch period, token details, balance, sync status, and enablement.
  • Refactor
    • Simplified per-network admin bridging with an automatic 1% treasury fee; the remaining 99% is bridged to users.
    • Wallet balances are now readable without special roles.
    • Deprecated separate admin lock/unlock/issue token actions across networks.

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

coderabbitai bot commented Sep 15, 2025

Walkthrough

Adds a public view action exposing ERC20 bridge metadata, removes role-based wallet-balance permission checks and individual admin lock/unlock/issue actions, and consolidates per-network admin bridge flows to calculate a 1% treasury fee, lock it, and bridge the remaining 99%.

Changes

Cohort / File(s) Summary
Bridge info view
internal/migrations/erc20-bridge/001-actions.sql
Added get_erc20_bridge_info() PUBLIC VIEW returning bridge metadata (chain, escrow, epoch_period, erc20, decimals, balance, synced, synced_at, enabled).
Admin action removals
internal/migrations/erc20-bridge/001-actions.sql
Removed sepolia_/mainnet_admin_lock_tokens, sepolia_/mainnet_admin_unlock_tokens, and sepolia_/mainnet_admin_issue_tokens actions.
Wallet balance permission adjustments
internal/migrations/erc20-bridge/001-actions.sql
Removed role-based membership checks on wallet balance read paths for sepolia and mainnet; balances now read without validating system:erc20_bridge_writer membership.
Admin bridge consolidation & fee logic
internal/migrations/erc20-bridge/001-actions.sql
Added sepolia_admin_bridge_tokens($amount TEXT) and mainnet_admin_bridge_tokens($amount TEXT) which compute a 1% treasury fee, lock that fee, and bridge the remaining 99% to recipient wallets.
Header/comment replacement
internal/migrations/erc20-bridge/001-actions.sql
Replaced previous explanatory header block with the new get_erc20_bridge_info() entrypoint description.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Admin as Admin
  participant DB as SQL Actions
  participant Treasury as Treasury (lock)
  participant Bridge as Bridge Engine
  participant User as User Wallet

  Admin->>DB: sepolia_admin_bridge_tokens(amount)
  rect rgba(200,230,255,0.25)
    note right of DB: Parse amount\nCompute fee = floor(amount * 0.01)
    DB->>Treasury: lock(fee)
    DB->>Bridge: bridge(amount - fee) -> target user
  end
  Bridge-->>User: deliver tokens
  alt Success
    DB-->>Admin: success
  else Failure
    DB-->>Admin: error
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • MicBun

Poem

I hopped through SQL, neat and spry,
A fee of one, the rest flies by.
Locks removed, new view in sight,
Ninety-nine seeds take flight tonight. 🐇✨

✨ Finishing touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/withdraw-fee

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.

Pre-merge checks

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Linked Issues Check ⚠️ Warning The PR implements a 1% fee on withdraw/bridge paths (sepolia_admin_bridge_tokens and mainnet_admin_bridge_tokens) and includes test evidence for withdrawals, but there is no indication in the provided summary or diffs that deposit flows were modified to apply fees; because linked issue #1173 requires fee coverage for both deposit and withdraw, the PR only partially satisfies the linked-issue objectives. Either implement and test the deposit-side fee flow to fully satisfy issue #1173 or update the linked issue and PR description to explicitly narrow scope to withdrawals and provide justification and tests for that scope.
Out of Scope Changes Check ⚠️ Warning The diff includes changes that appear outside the stated objective of adding a withdrawal fee, notably the new get_erc20_bridge_info() view, removal of role-based permission checks for wallet balance reads, and removal of per-network admin lock/unlock/issue actions in favor of consolidated admin_bridge_tokens; the permission and API-surface changes are security-relevant and should be treated as out-of-scope unless explicitly justified. Split unrelated additions and permission/API-surface changes into separate PRs or provide a concise justification, accompanying tests, and a security review for the permission removals and consolidated admin action changes before merging.
✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title "feat: apply fee when withdrawing TRUF tokens" is a concise, single-sentence summary that directly describes the PR's primary change (implementation of a withdrawal fee for TRUF via updated per-network admin_bridge_tokens).
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

@williamrusdyputra williamrusdyputra marked this pull request as ready for review September 15, 2025 04:15
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: 2

🧹 Nitpick comments (1)
internal/migrations/erc20-bridge/001-actions.sql (1)

8-15: Avoid duplication; parameterize fee rate.

Hard‑coding 1% twice invites drift. Extract a shared constant (e.g., fee_bps) or a config table per network, and compute fee from it.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 43b1906 and e23411d.

📒 Files selected for processing (1)
  • internal/migrations/erc20-bridge/001-actions.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
🔇 Additional comments (3)
internal/migrations/erc20-bridge/001-actions.sql (3)

8-15: Make sepolia_bridge.lock() + bridge() atomic and idempotent

Wrap sepolia_bridge.lock(...) and sepolia_bridge.bridge(...) in a single DB transaction with proper exception handling/rollback so either both succeed or neither does; add an idempotency key (e.g., request_id/migration_id) to the bridge operation to prevent double‑spend on retries. File: internal/migrations/erc20-bridge/001-actions.sql (lines 8–15).


24-31: Scope alignment with issue #1173 (fee on deposit and withdraw).

This migration only covers admin bridge/withdraw (sepolia_admin_bridge_tokens & mainnet_admin_bridge_tokens in internal/migrations/erc20-bridge/001-actions.sql). A search of internal/migrations found no deposit/mint/issue handlers — confirm deposit paths also apply the 1% fee or document that fees are deferred to admin/withdraw.


8-15: Restrict EXECUTE on admin migration — do not leave it PUBLIC

  • internal/migrations/erc20-bridge/001-actions.sql (lines 8–15): function currently exposes sepolia_bridge.lock and sepolia_bridge.bridge to PUBLIC. Revoke PUBLIC and grant EXECUTE only to the admin role (e.g., system:erc20_bridge_writer), or add a server-side role check/SECURITY DEFINER guard.
  • Repo search for auth patterns (erc20_bridge_writer / REQUIRES ROLE / has_role / auth) returned no matches — manual confirmation of the repo's role-naming convention and intended grant target required.

@holdex
Copy link

holdex bot commented Sep 15, 2025

Time Submission Status

Member Status Time Action Last Update
williamrusdyputra ✅ Submitted 4h Update time Sep 15, 2025, 4:52 AM
MicBun ✅ Submitted 20min Update time Sep 15, 2025, 7:47 AM

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 (2)
internal/migrations/erc20-bridge/001-actions.sql (2)

25-31: Fee math must be integer-safe; define rounding; validate inputs; cast outputs.

Current code multiplies NUMERIC(78,0) by 0.01, producing fractional values and risking type mismatches and over/under‑charging. Use integer math with floor semantics, validate amount > 0, ensure net >= 0, and cast back to NUMERIC(78,0) for the bridge calls.

Apply this diff:

-  -- Calculate 1% fee and lock it in our treasury
-  $numAmount := $amount::NUMERIC(78, 0);
-  $fee := $numAmount * 0.01;
-  sepolia_bridge.lock($fee);
-
-  -- Bridge the rest to users
-  sepolia_bridge.bridge($numAmount - $fee);
+  -- Calculate 1% fee (floor) and lock it in our treasury
+  $numAmount := $amount::NUMERIC(78,0);
+  IF $numAmount <= 0 THEN
+    ERROR 'amount must be positive';
+  END IF;
+  $fee := floor($numAmount / 100);     -- 1% fee, floored
+  $net := $numAmount - $fee;
+  IF $net < 0 THEN
+    ERROR 'fee exceeds amount';
+  END IF;
+  sepolia_bridge.lock($fee::NUMERIC(78,0));
+
+  -- Bridge the rest to users
+  sepolia_bridge.bridge($net::NUMERIC(78,0));

Follow‑ups:

  • Ensure lock() and bridge() are atomic (same TX) to avoid partial state if either fails.
  • Add an auth/role guard so only the bridge actor can call this action.

41-47: Replicate integer fee math + validation + casting for mainnet; ensure atomicity and auth.

Same issues as sepolia: fractional math, missing input validation, potential type mismatches, and no explicit auth guard/atomicity guarantees.

Apply this diff:

-  -- Calculate 1% fee and lock it in our treasury
-  $numAmount := $amount::NUMERIC(78, 0);
-  $fee := $numAmount * 0.01;
-  mainnet_bridge.lock($fee);
-
-  -- Bridge the rest to users
-  mainnet_bridge.bridge($numAmount - $fee);
+  -- Calculate 1% fee (floor) and lock it in our treasury
+  $numAmount := $amount::NUMERIC(78,0);
+  IF $numAmount <= 0 THEN
+    ERROR 'amount must be positive';
+  END IF;
+  $fee := floor($numAmount / 100);     -- 1% fee, floored
+  $net := $numAmount - $fee;
+  IF $net < 0 THEN
+    ERROR 'fee exceeds amount';
+  END IF;
+  mainnet_bridge.lock($fee::NUMERIC(78,0));
+
+  -- Bridge the rest to users
+  mainnet_bridge.bridge($net::NUMERIC(78,0));

Also ensure both calls are executed atomically and are restricted to the authorized bridge role.

🧹 Nitpick comments (5)
internal/migrations/erc20-bridge/001-actions.sql (5)

1-16: Bridge info action appears scoped to sepolia only; confirm multi-network intent and cardinality.

This returns rows only from sepolia_bridge.info(). If the intent is to expose info for both sepolia and mainnet, either parameterize the chain or union both sources. Also, the body loops and immediately RETURNs a row; if info() can yield multiple rows, this effectively returns just the first row. Adjust the return shape (set-returning) or add a WHERE/limit to make the single-row contract explicit.


18-22: PUBLIC balance view: consider access controls or rate limiting.

Making wallet balances publicly queryable can enable address enumeration or scraping. If intentional, ignore; otherwise gate via a role (e.g., system:erc20_bridge_reader) or add an auth guard.


34-38: PUBLIC balance view (mainnet): mirror the access-control stance.

Same concern as sepolia. If balances should be public, fine; otherwise add a role check or throttle.


24-32: Optional: centralize fee rate and unit semantics.

  • Replace the magic “1%” with a basis‑points variable (e.g., $fee_bps := 100) so the rate is configurable and consistent across networks.
  • Confirm amount is expressed in smallest token units (matches NUMERIC(78,0) integer semantics) to avoid implicit decimal scaling errors.

Also applies to: 40-48


24-32: Amount type: consider NUMERIC input instead of TEXT.

If the caller can supply a numeric, prefer ($amount NUMERIC(78,0)) and drop the cast to reduce invalid‑cast errors. Keep as TEXT only if required by transport/API.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e23411d and 4ee7d51.

📒 Files selected for processing (1)
  • internal/migrations/erc20-bridge/001-actions.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
🔇 Additional comments (1)
internal/migrations/erc20-bridge/001-actions.sql (1)

24-32: Verify deposit path applies same 1% fee and rounding

Admin action internal/migrations/erc20-bridge/001-actions.sql (lines 24–32) deducts 1% and locks it; confirm the deposit/receive path implements identical fee semantics and numeric rounding. rg search returned no matches — unable to locate a deposit action; verify and update the deposit implementation/migrations if missing.

Copy link
Member

@MicBun MicBun left a comment

Choose a reason for hiding this comment

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

Lgtm

@MicBun MicBun merged commit 19c6edd into main Sep 15, 2025
6 of 7 checks passed
@MicBun MicBun deleted the feat/withdraw-fee branch September 15, 2025 04:52
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