> ## Documentation Index
> Fetch the complete documentation index at: https://docs.raydium.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Toán học CPMM

> Bất biến sản phẩm hằng số, SwapBaseInput vs SwapBaseOutput, xử lý phí chuyển Token-2022, và cách cập nhật tài khoản quan sát.

<Info>
  **Trang này được dịch tự động bằng AI. Phiên bản tiếng Anh là bản chính thức.**

  [Xem bản tiếng Anh →](/products/cpmm/math)
</Info>

## Bất biến

CPMM duy trì bất biến sản phẩm hằng số cổ điển trên hai kho lưu trữ của nó:

$$
x \cdot y = k
$$

trong đó `x` là số dư kho0 **sau** bất kỳ phí chuyển Token-2022 nào khi nhận, tương tự cho `y`. Mỗi swap phải để lại `k' ≥ k` sau khi tính đến phí giao dịch được ghi có cho LP (các khoá protocol, quỹ và người sáng tạo **không** được tính vào `k` — chúng nằm trong kho nhưng bị loại khỏi dạng xem đường cong, xem [Phí trên đường cong](#fees-on-the-curve) dưới đây). `k` do đó tăng đơn điệu theo thời gian khi LP tích lũy phí.

Cổ phiếu LP được định giá theo dự trữ của pool, không phải theo `k`:

$$
\text{Giá LP theo token0} = \frac{x}{\text{lpSupply}}, \qquad \text{Giá LP theo token1} = \frac{y}{\text{lpSupply}}
$$

Đốt `ΔLP` token LP sẽ trả lại chính xác `ΔLP × x / lpSupply` của token0 và `ΔLP × y / lpSupply` của token1. Cả đường cong và `k` không di chuyển khi gửi hoặc rút tiền — chỉ swap thay đổi giá.

## Mô hình phí trên đường dẫn swap

CPMM áp dụng **hai phí được định giá độc lập** trên mỗi swap:

* **Phí giao dịch** được lấy ở phía đầu vào, được tính với `AmmConfig.trade_fee_rate`. Sau đó nó được chia thành phần chia sẻ LP, protocol và quỹ (phần chia sẻ LP vẫn ở trong kho và tăng `k`; phần chia sẻ protocol và quỹ được trích xuất khỏi hạch toán kho).
* **Phí người sáng tạo** (chỉ hoạt động khi `enable_creator_fee == true`) được tính với `AmmConfig.creator_fee_rate`. Nó được lấy ở phía **đầu vào** hoặc **đầu ra** tùy thuộc vào `PoolState.creator_fee_on` và hướng swap (xem [`products/cpmm/fees`](/vi/products/cpmm/fees#which-side-of-the-trade-the-fees-are-taken-from)). Nó là một khoá riêng — không bao giờ là một phần của phí giao dịch.

Để:

* `FEE_RATE_DENOMINATOR = 1_000_000`
* `trade_fee_rate` — từ `AmmConfig`, ví dụ: `2500` = 0,25% của phía thể tích liên quan
* `creator_fee_rate` — từ `AmmConfig`, ví dụ: `1000` = 0,10% của phía thể tích liên quan
* `protocol_fee_rate`, `fund_fee_rate` — tính theo đơn vị `1/FEE_RATE_DENOMINATOR` **của phí giao dịch**, không phải của thể tích

Khi phí người sáng tạo ở phía đầu vào:

```
total_input_fee = ceil(amount_in * (trade_fee_rate + creator_fee_rate) / FEE_RATE_DENOMINATOR)
creator_fee     = floor(total_input_fee * creator_fee_rate / (trade_fee_rate + creator_fee_rate))
trade_fee       = total_input_fee - creator_fee
amount_in_after_fees = amount_in - total_input_fee
```

Khi phí người sáng tạo ở phía đầu ra:

```
trade_fee            = ceil(amount_in * trade_fee_rate / FEE_RATE_DENOMINATOR)
amount_in_after_fees = amount_in - trade_fee
amount_out_curve     = curve_output(amount_in_after_fees, ...)
creator_fee          = ceil(amount_out_curve * creator_fee_rate / FEE_RATE_DENOMINATOR)
amount_out           = amount_out_curve - creator_fee
```

Trong cả hai trường hợp, phí giao dịch được chia theo cách tương tự:

```
protocol_fee   = floor(trade_fee * protocol_fee_rate / FEE_RATE_DENOMINATOR)
fund_fee       = floor(trade_fee * fund_fee_rate     / FEE_RATE_DENOMINATOR)
lp_fee         = trade_fee - protocol_fee - fund_fee     // creator_fee được KHÔNG trừ ở đây
```

Số tiền `protocol_fee + fund_fee + creator_fee` được giữ trong các kho nhưng được theo dõi riêng trên trạng thái pool (`protocol_fees_token*`, `fund_fees_token*`, `creator_fees_token*`). Khi kiểm tra bất biến sản phẩm hằng số là `k' ≥ k`, nó sử dụng số dư kho **trừ** cả ba phí tích lũy nhưng chưa được thu thập — vì vậy LP chỉ nhận được `lp_fee`.

Xem [`products/cpmm/fees`](/vi/products/cpmm/fees) để biết các hướng dẫn thu thập và các ví dụ toán học đã hoàn thành.

## SwapBaseInput (input-exact)

"Người dùng cung cấp cho chúng tôi chính xác `amount_in` của mint đầu vào và nhận ít nhất `minimum_amount_out` của mint đầu ra."

Bỏ qua Token-2022 một lúc:

```
amount_in_after_trade_fee = amount_in - trade_fee
amount_out                = y − (x * y) / (x + amount_in_after_trade_fee)
```

Theo đại số:

$$
\text{amount\_out} = \frac{y \cdot \Delta x_{\text{net}}}{x + \Delta x_{\text{net}}}
$$

trong đó `Δx_net = amount_in_after_trade_fee`.

Chương trình sau đó cập nhật hạch toán kho sao cho phần `trade_fee` được nợ cho protocol/quỹ/người sáng tạo nằm trong các khoá "tích lũy" (không được bao gồm trong `x` tiếp theo của đường cong), trong khi phần chia sẻ LP sẽ tham gia `x` cho swap tiếp theo.

### Token-2022 ở phía đầu vào

Nếu mint đầu vào có tiện ích phí chuyển, **mint** sẽ trừ phí của nó khi chuyển từ người dùng → kho. Vì vậy, kho thực tế nhận được `amount_in − transfer_fee_in(amount_in)`. Do đó, chương trình CPMM tính toán:

```
amount_actually_received = amount_in − transfer_fee_in(amount_in)
trade_fee                = ceil(amount_actually_received * trade_fee_rate / FEE_RATE_DENOMINATOR)
amount_in_after_trade_fee = amount_actually_received − trade_fee
```

và chạy đường cong chống lại `amount_in_after_trade_fee`. Điều này quan trọng vì **giá đường cong được tính từ số tiền ròng đã hạ cánh trong kho**, không phải từ số tiền hàng loạt của người dùng.

### Token-2022 ở phía đầu ra

Nếu mint đầu ra có phí chuyển, pool sẽ gửi `amount_out` từ kho của nó đến người dùng. **Mint** sau đó sẽ lấy phí của nó trên đường đi, vì vậy người dùng nhận được `amount_out − transfer_fee_out(amount_out)`. Chương trình tính toán `amount_out` từ đường cong như thường lệ, nhưng trách nhiệm của tích hợp là chuyển đổi số "gửi từ kho" của pool thành số "nhận được của người dùng" khi hiển thị báo giá.

### Kiểm tra slippage

Sau khi tính toán `amount_out`:

```
require(amount_out >= minimum_amount_out, "AmountSpecifiedLessThanMinimum")
```

Nếu mint đầu ra tính phí chuyển, SDK áp dụng phí chuyển *trước* khi đặt `minimum_amount_out` vì vậy hằng số slippage được tính bằng những gì người dùng sẽ thực sự nhận được, không phải những gì kho gửi đi.

## SwapBaseOutput (output-exact)

"Người dùng sẽ nhận được chính xác `amount_out` của mint đầu ra và sẵn sàng trả tối đa `maximum_amount_in` của mint đầu vào."

Đảo ngược đường cong cho `Δx_net`:

$$
\Delta x_{\text{net}} = \left\lceil \frac{x \cdot \text{amount\_out}}{y - \text{amount\_out}} \right\rceil
$$

Giới hạn trên rất quan trọng — nó đảm bảo `k' ≥ k` sau khi cắt ngắn số nguyên. Sau đó:

```
// Làm việc ngược từ net in đến gross in.
// phí được áp dụng trên gross, vì vậy:
//   net = gross − ceil(gross * rate / D)
//       ≈ gross * (D − rate) / D
// đảo ngược với giới hạn trên ở những nơi thích hợp:
gross_needed = ceil(Δx_net * D / (D − trade_fee_rate))
```

Trên Token-2022 input, bao bọc với:

```
gross_needed_before_mint_fee
  = inflate_for_transfer_fee(gross_needed, input_mint)
```

vì vậy người dùng trả đủ để sau khi mint trừ phí chuyển, pool vẫn nhận được `gross_needed`.

### Kiểm tra slippage

```
require(gross_needed_before_mint_fee <= maximum_amount_in, "AmountSpecifiedExceedsMaximum")
```

## Ví dụ hoàn thành

Trạng thái pool, bỏ qua Token-2022:

* `x = 1_000_000_000_000` (1.000.000,000000 của token0, 6 chữ số thập phân)
* `y = 2_000_000_000_000` (2.000.000,000000 của token1, 6 chữ số thập phân)
* `AmmConfig`: `trade_fee_rate = 2500`, `protocol_fee_rate = 120_000`, `fund_fee_rate = 40_000`, `creator_fee_rate = 0`

Người dùng: `SwapBaseInput` với `amount_in = 1_000_000_000` (1.000,000000 của token0). Phí người sáng tạo bị vô hiệu hóa (`enable_creator_fee = false`).

```
trade_fee                = ceil(1_000_000_000 * 2500 / 1_000_000)       = 2_500_000
  protocol_fee           = floor(2_500_000 * 120_000 / 1_000_000)       = 300_000
  fund_fee               = floor(2_500_000 *  40_000 / 1_000_000)       = 100_000
  lp_fee                 = 2_500_000 − 300_000 − 100_000                 = 2_100_000
creator_fee              = 0                                              // disabled

amount_in_after_trade_fee = 1_000_000_000 − 2_500_000                    = 997_500_000

amount_out = y − (x * y) / (x + Δx_net)
           = 2_000_000_000_000
             − (1_000_000_000_000 * 2_000_000_000_000)
               / (1_000_000_000_000 + 997_500_000)
           ≈ 1_995_015_009

new_vault0_raw   = x + amount_in                                        = 1_001_000_000_000
new_vault1       = y − amount_out                                       ≈ 1_998_004_984_991

// Trong số 1_000_000_000 nhận được trong vault0, 400_000 là "phí tích lũy"
// (protocol + quỹ) mà đường cong nên loại trừ:
curve_x          = new_vault0_raw − (protocol_fees_token0 + fund_fees_token0)
                 = 1_001_000_000_000 − 400_000
                 = 1_000_999_600_000

k' = curve_x * new_vault1 ≈ 2.000_002_501_E24  ≥  k = 2.0E24   ✓
```

Nếu pool tương tự có `enable_creator_fee = true` với `creator_fee_rate = 1000` (0,10%) ở phía đầu vào, chương trình sẽ tính `total_input_fee = ceil(1_000_000_000 * 3500 / 1_000_000) = 3_500_000`, sau đó chia thành `creator_fee = 1_000_000` và `trade_fee = 2_500_000`. Số học protocol/quỹ/LP trên `trade_fee` không thay đổi từ ví dụ trên — phí người sáng tạo là khoá của riêng nó, được tích lũy thành `creator_fees_token0` và loại trừ khỏi `curve_x` cùng với các khoá protocol và quỹ.

Nếu mint đầu vào có phí chuyển Token-2022 là 1%, kho nhận được `990_000_000` token thay vì `1_000_000_000`, và mỗi tính toán tiếp theo sử dụng số tiền ròng đó.

## Quy tắc cập nhật quan sát

Trên mỗi swap, chương trình đánh giá xem có nên đẩy một quan sát mới vào vòng lặp đệm hay không:

```
let since_last = now − observations[head].block_timestamp;
if since_last >= MIN_OBSERVATION_INTERVAL {
    let price0 = (vault1 << 32) / vault0;            // Q32.32-ish
    let price1 = (vault0 << 32) / vault1;
    let head' = (head + 1) % OBSERVATION_NUM;
    observations[head'] = Observation {
        block_timestamp: now,
        cumulative_token0_price_x32:
            observations[head].cumulative_token0_price_x32 + price0 * since_last,
        cumulative_token1_price_x32:
            observations[head].cumulative_token1_price_x32 + price1 * since_last,
    };
    head = head';
}
```

Hai tính chất:

* **Giá tích lũy, không phải giá tại chỗ.** Một quan sát duy nhất không phải là giá. Để lấy TWAP từ thời gian `t0` đến `t1`, đọc các quan sát gần nhất với mỗi đầu và tính `(cumulative(t1) − cumulative(t0)) / (t1 − t0)`.
* **Các mẫu bị giới hạn tốc độ.** Các swap liên tiếp trong cùng một slot có thể chia sẻ một quan sát. Đọc một quan sát ngay sau một swap do đó có thể trông cũ bởi một slot — điều này là bình thường.

Thêm trong [`products/clmm/accounts`](/vi/products/clmm/accounts).

## Phí trên đường cong

Đây là phần tinh tế và đáng được nhấn mạnh. Số học đường cong hoạt động chống lại các số dư kho **ròng** — tức là, số dư SPL thô trừ đi phí tích lũy protocol, quỹ và người sáng tạo (cả ba là các khoá độc lập — xem [`products/cpmm/fees`](/vi/products/cpmm/fees)). Một bức tranh cụ thể:

```
raw_vault_balance   = những gì trả về RPC getTokenAccountBalance
accrued_fees        = protocol_fees_token{0,1} + fund_fees_token{0,1} + creator_fees_token{0,1}
curve_balance       = raw_vault_balance − accrued_fees
invariant           = curve_balance0 * curve_balance1
```

Hệ quả cho các tích hợp:

* **Không báo giá từ số dư thô.** Trừ đi các trường phí tích lũy trước, hoặc gọi `SwapBaseInput` như một mô phỏng và lấy kết quả trả về của nó.
* **`CollectProtocolFee` di chuyển token ra khỏi kho.** Sau khi thu thập, `raw_vault_balance` giảm nhưng `curve_balance` không thay đổi; giá của pool không di chuyển. Điều này là cố ý.

## Độ chính xác và tràn

* Tất cả số học đường cong sử dụng trung gian `u128` để ngăn chặn tràn trên `x * y`.
* Phép chia làm tròn về phía không, ngoại trừ `Δx_net` của `SwapBaseOutput`, làm tròn lên, và tính toán phí, làm tròn lên trên `trade_fee` và xuống trên các phân chia con. Các hướng làm tròn này được chọn sao cho bất biến không bao giờ giảm do cắt ngắn số nguyên.
* Các pool có tỷ lệ kho cực kỳ (hàng tỷ : 1) có thể hit precision floors trên các giao dịch nhỏ; chương trình trả về `ZeroTradingTokens` trong trường hợp đó. Xem [`reference/error-codes`](/vi/reference/error-codes).

## Tiếp theo là gì

* [`products/cpmm/fees`](/vi/products/cpmm/fees) — ngữ nghĩa tầng phí và thu thập đầy đủ.
* [`products/cpmm/instructions`](/vi/products/cpmm/instructions) — các hướng dẫn gọi toán học này.
* [`algorithms/constant-product`](/vi/algorithms/constant-product) — suy dẫn và các trường hợp cạnh của `x · y = k` được chia sẻ giữa AMM v4 và CPMM.

Nguồn:

* [`raydium-io/raydium-cp-swap` — swap math trong `states/curve.rs`](https://github.com/raydium-io/raydium-cp-swap)
* Báo cáo kiểm toán Raydium được liên kết trong [`security/audits`](/vi/security/audits)
