Bài viết
Chia sẻ kiến thức của bạn.
Sato$hii561
Aug 29, 2025
Hỏi đáp Chuyên Gia
How do i design deterministic gas accounting
How do I design deterministic gas accounting and cross-module fee sinks so fees are provably conserved and replay-safe?
- Move CLI
- Move
- Smart Contract
- Move Module
0
1
Chia sẻ
Bình luận
Câu trả lời
1Aug 29 2025, 22:03
Different modules perform work (calls, storage writes) and should pay fees that (a) can be split across modules, (b) are provably conserved (no hidden minting), and (c) are deterministic even across cross-module calls and reentrancy. On many chains gas is external, but for internal “service fees” you must manage accounting yourself.
Strategy
- Implement a single canonical
FeeSink
resource that lives at a known address; only this module can mint/burn the fee token. - Use a linear
FeeTicket
created at entrypoint that threads through subcalls (or is passed explicitly) so every module that does fee-earning must consume/return the ticket — prevents duplication. - Make all fee operations atomic and update
FeeSink
via a singlesettle
call at transaction end (or whenever you want to finalize), and store preconditions inspec
to prove conservation. - Use sequence numbers / nonce to make fee payments replay-safe.
Why this works
- Linearity of
FeeTicket
ensures fee cannot be duplicated. - Centralized sink with controlled mint/burn preserves conservation.
- Passing the ticket explicitly avoids hidden side-effects across modules.
Working-style Move package (conceptual)
module 0xA::fee {
use std::signer;
/// The canonical fee token (linear).
struct FeeToken has store, drop { amount: u128 }
/// FeeSink keeps accumulated fees under admin account
struct FeeSink has key { collected: u128, admin: address, nonce: u64 }
/// A FeeTicket must be obtained by a top-level caller and passed down to submodules
/// to register earned fees. FeeTicket holds the remaining budget for the call.
struct FeeTicket has key {
remaining: u128,
tx_nonce: u64
}
/// Initialize sink under admin
public fun init(admin: &signer, start_collected: u128) {
move_to(admin, FeeSink { collected: start_collected, admin: signer::address_of(admin), nonce: 0 });
}
/// Create a FeeTicket for a top-level transaction. This consumes no sink funds yet,
/// but records a nonce for replay protection.
public fun issue_ticket(caller: &signer, budget: u128) acquires FeeSink {
assert!(budget > 0, 1);
let sink_addr = 0xA; // admin stored here; adapt as needed
let s = borrow_global_mut<FeeSink>(sink_addr);
s.nonce = s.nonce + 1;
FeeTicket { remaining: budget, tx_nonce: s.nonce }
}
/// Submodules call this to claim part of the ticket. This consumes from the ticket and
/// returns a small FeeToken to the module to prove claim handling. The FeeToken is linear.
public fun claim(ticket: &mut FeeTicket, claim_amt: u128): FeeToken {
assert!(claim_amt > 0 && ticket.remaining >= claim_amt, 2);
ticket.remaining = ticket.remaining - claim_amt;
FeeToken { amount: claim_amt }
}
/// Module that earned fees returns FeeToken to the sink (settle).
public fun settle_to_sink(admin: &signer, t: FeeToken) acquires FeeSink {
let s = borrow_global_mut<FeeSink>(signer::address_of(admin));
s.collected = s.collected + t.amount;
// t consumed by function arguments (linear)
}
/// Finalize: any unused ticket budget can be optionally returned or burned.
public fun finalize_ticket(admin: &signer, ticket: FeeTicket) acquires FeeSink {
// Ticket being moved here consumes it; unused portion is burned or credited per policy.
let s = borrow_global_mut<FeeSink>(signer::address_of(admin));
s.collected = s.collected + ticket.remaining;
// ticket consumed
}
/////////////////////////////
// Example usage flow (conceptual)
/////////////////////////////
// 1. Top-level caller obtains ticket = issue_ticket(caller, 100)
// 2. call module A: feeA = claim(&mut ticket, 30); module A does work
// module A returns feeA: settle_to_sink(admin, feeA)
// 3. call module B: feeB = claim(&mut ticket, 20); module B returns feeB
// 4. finalize_ticket(admin, ticket) => leftover 50 credited to sink
}
Spec & proofs (advice)
- Add
spec
invariants thatcollected
equals sum of allsettled
amounts + burned leftovers and is non-negative. - Prove no ticket duplication by showing
FeeTicket
is linear andclaim
only reducesremaining
. - Prove replay-safety: each issued ticket carries a unique
tx_nonce
from the sink that you verify in any API requiring the ticket; if a client attempts to reuse a ticket nonce, the sink’s nonce will have moved forward.
0
Bình luận
Bạn có biết câu trả lời không?
Hãy đăng nhập và chia sẻ nó.
Move is an executable bytecode language used to implement custom transactions and smart contracts.
242Bài viết541Câu trả lời
Kiếm phần của bạn từ 1000 Sui
Tích lũy điểm danh tiếng và nhận phần thưởng khi giúp cộng đồng Sui phát triển.

Chiến dịch phần thưởngTháng Tám
- ... SUIMatthardy+2095
- ... SUIacher+1666
- ... SUIChubbycheeks +1091
- ... SUIjakodelarin+1060
- ... SUITucker+1047
- ... SUIKurosakisui+1034
- ... SUIOpiiii+861
Bài đăng tiền thưởng