0g-chain/x/precisebank/README.md

651 lines
18 KiB
Markdown
Raw Normal View History

# `x/precisebank`
## Abstract
This document specifies the precisebank module of Kava.
The precisebank module is responsible for extending the precision of `x/bank`, intended to be used for the `x/evm`. It serves as a wrapper of `x/bank` to increase the precision of KAVA from 6 to 18 decimals, while preserving the behavior of existing `x/bank` balances.
This module is used only by `x/evm` where 18 decimal points are expected.
## Contents
- [Background](#background)
- [Adding](#adding)
- [Subtracting](#subtracting)
- [Transfer](#transfer)
- [Setup](#setup)
- [Remainder does not change](#remainder-does-not-change)
- [Reserve](#reserve)
- [Burn](#burn)
- [Mint](#mint)
- [State](#state)
- [Keepers](#keepers)
- [Messages](#messages)
- [Events](#events)
- [Keeper Events](#keeper-events)
- [SendCoins](#sendcoins)
- [MintCoins](#mintcoins)
- [BurnCoins](#burncoins)
- [Client](#client)
- [gRPC](#grpc)
- [TotalFractionalBalances](#totalfractionalbalances)
- [Remainder](#remainder)
- [FractionalBalance](#fractionalbalance)
## Background
The standard unit of currency on the Kava Chain is `KAVA`. This is denominated by the atomic unit `ukava`, which represents $10^{-6}$ `KAVA` and there are $10^6$ `ukava` per `KAVA`.
In order to support 18 decimals of precision while maintaining `ukava` as the cosmos-native atomic unit, we further split each `ukava` unit into $10^{12}$ `akava` units, the native currency of the Kava EVM.
This gives a full $10^{18}$ precision on the EVM. In order to avoid confusion with atomic `ukava` units, we will refer to `akava` as "sub-atomic units".
To review we have:
- `ukava`, the cosmos-native unit and atomic unit of the Kava chain
- `akava`, the evm-native unit and sub-atomic unit of the Kava chain
In order to maintain consistency between the `akava` supply and the `ukava` supply, we add the constraint that each sub-atomic `akava`, may only exist as part of an atomic `ukava`. Every `akava` is fully backed by a `ukava` in the `x/bank` module.
This is a requirement since `ukava` balances in `x/bank` are shared between the cosmos modules and the EVM. We are wrapping and extending the `x/bank` module with the `x/precisebank` module to add an extra $10^{12}$ units of precision. If $10^{12}$ `akava` is transferred in the EVM, the cosmos modules will see a 1 `ukava` transfer and vice versa. If `akava` was not fully backed by `ukava`, then balance changes would not be fully consistent across the cosmos and the EVM.
This brings us to how account balances are extended to represent `akava` balances larger than $10^{12}$. First, we define $a(n)$, $b(n)$, and $C$ where $a(n)$ is the `akava` balance of account `n`, $b(n)$ is the `ukava` balance of account `n` stored in the `x/bank` module, and $C$ is the conversion factor equal to $10^{12}$.
Any $a(n)$ divisible by $C$, can be represented by $C$ * $b(n)$. Any remainder not divisible by $C$, we define the "fractional balance" as $f(n)$ and store this in the `x/precisebank` store.
Thus,
$$a(n) = b(n) \cdot C + f(n)$$
where
$$0 \le f(n) < C$$
$$a(n), b(n) \ge 0$$
This is the quotient-remainder theorem and any $a(n)$ can be represented by unique integers $b(n)$, $f(n)$ where
$$b(n) = \lfloor a(n)/C \rfloor$$
$$f(n) = a(n)\bmod{C}$$
With this definition in mind we will refer to $b(n)$ units as integer units, and $f(n)$ as fractional units.
Now since $f(n)$ is stored in the `x/precisebank` and not tracked by the `x/bank` keeper, these are not counted in the `ukava` supply, so if we define
$$T_a \equiv \sum_{n \in \mathcal{A}}{a(n)}$$
$$T_b \equiv \sum_{n \in \mathcal{A}}{b(n)}$$
where $\mathcal{A}$ is the set of all accounts, $T_a$ is the total `akava` supply, and $T_b$ is the total `ukava` supply, then a reserve account $R$ is added such that
$$a(R) = 0$$
$$b(R) \cdot C = \sum_{n \in \mathcal{A}}{f(n)} + r$$
where $R$ is the module account of the `x/precisebank`, and $r$ is the remainder or fractional amount backed by $b(R)$, but not yet in circulation such that
$$T_a = T_b \cdot C - r$$
and
$$ 0 <= r < C$$
We see that $0 \le T_b \cdot C - T_a < C$. If we mint, burn, or transfer `akava` such that this inequality would be invalid after updates to account balances, we adjust the $T_b$ supply by minting or burning to the reserve account which holds `ukava` equal to that of all `akava` balances less than `C` plus the remainder.
If we didn't add these constraints, then the total supply of `ukava` reported by the bank keeper would not account for the `akava` units. We would incorrectly increase the supply of `akava` without increasing the reported total supply of KAVA.
### Adding
When adding we have
$$a'(n) = a(n) + a$$
$$b'(n) \cdot C + f'(n) = b(n) \cdot C + f(n) + a$$
where $a'(n)$ is the new `akava` balance after adding `akava` amount $a$. These
must hold true for all $a$. We can determine the new $b'(n)$ and $f'(n)$ with the following formula.
$$f'(n) = f(n) + a \mod{C}$$
$$b'(n) = \begin{cases} b(n) + \lfloor a/C \rfloor & f'(n) \geq f(n) \\
b(n) + \lfloor a/C \rfloor + 1 & f'(n) < f(n) \end{cases}$$
We can see that $b'(n)$ is incremented by an additional 1 integer unit if
$f'(n) < f(n)$ because the new balance requires an arithmetic carry from the
fractional to the integer unit.
### Subtracting
When subtracting we have
$$a'(n) = a(n) - a$$
$$b'(n) \cdot C + f'(n) = b(n) \cdot C + f(n) - a$$
and
$$f'(n) = f(n) - a \mod{C}$$
$$b'(n) = \begin{cases} b(n) - \lfloor a/C \rfloor & f'(n) \leq f(n) \\
b(n) - \lfloor a/C \rfloor - 1 & f'(n) > f(n) \end{cases}$$
Similar to the adding case, we subtract $b'(n)$ by an additional 1 if
$f'(n) > f(n)$ because $f(n)$ is insufficient on its own and requires an
arithmetic borrow from the integer units.
### Transfer
A transfer is a combination of adding and subtracting of a single amount between
two different accounts. The transfer is valid if both the subtraction for the
sender and the addition for the receiver are valid.
#### Setup
Let two accounts $1$ and $2$ have balances $a(1)$ and $a(2)$, and $a$ is the
amount to transfer. Assume that $a(1) \ge a$ to ensure that the transfer is
valid. We initiate a transfer by subtracting $a$ from account $1$ and adding $a$
to account $2$, yielding
$$a'(1) = a(1) - a$$
$$a'(2) = a(2) + a$$
The reserve account must also be updated to reflect the change in the total
supply of fractional units.
$$b(R) \cdot C = \sum_{n \in \mathcal{A}}{f(n)} + r$$
$$b'(R) \cdot C = \sum_{n \in \mathcal{A}}{f'(n)} + r'$$
With these two formulas, we can determine the new remainder and reserve by using
the delta of the sum of fractional units and the remainder.
$$(b'(R)-b(R)) \cdot C = \sum_{n \in \mathcal{A}}{f'(n)} - \sum_{n \in \mathcal{A}}{f(n)} + r' - r$$
Since only two accounts are involved in the transfer, we can use the two account
balances in place of the fractional sum delta.
$$(b'(R)-b(R)) \cdot C = f'(1) - f(1) + f'(2) - f(2) + r' - r$$
#### Remainder does not change
Take $\mod{C}$ of both sides of the equation.
$$(b'(R)-b(R)) \cdot C \mod{C} = [f'(1) - f(1) + f'(2) - f(2) + r' - r] \mod{C}$$
Since $C$ is a multiple of $C$, the left side of the equation is $0$.
$$0 = f'(1) - f(1) + f'(2) - f(2) + r' - r \mod{C}$$
Replace $f'(1)$ and $f'(2)$ with their definitions in terms of $f(1)$ and $f(2)$.
$$0 = (f(1) - a)\bmod{C} - f(1) + (f(2) + a)\bmod{C} - f(2) + r' - r \mod{C}$$
This can be simplified to:
$$0 = f(1) - a - f(1) + f(2) + a - f(2) + r' - r \mod{C}$$
Canceling out terms $a$, $f(1)$ and $f(2)$.
$$0 = r' - r \mod{C}$$
By the quotient remainder theorem, we can express $r' - r$ as:
$$q * C = r' - r$$
for some integer $q$.
With our known range of $r$ and $r'$:
$$0 \leq r' < C, 0 \leq r < C$$
We can see that $r' - r$ must be in the range
$$ -C < r' - r < C$$
This implies that $q$ must be $0$ as there is no other integer $q$ that satisfies the inequality.
$$ -C < q * C < C$$
$$q = 0$$
$$ r' - r = 0$$
Therefore, the remainder does not change during a transfer.
#### Reserve
The reserve account must be updated to reflect the change in the total supply of fractional units.
The change in reserve is determined by the change in the fractional units of the two accounts.
$$(b'(R)-b(R)) \cdot C = f'(1) - f(1) + f'(2) - f(2)$$
For $f'(1)$, we can represent the new fractional balance as:
$$f'(1) = f(1) - a \mod{C}$$
$$f'(1)\bmod{C}= f(1)\bmod{C} - a \bmod{C} \mod{C}$$
$$f'(1) = f(1) - a \bmod{C} \mod{C}$$
This results in two cases for $f'(1)$:
$$f'(1) = \begin{cases} f(1) - a\bmod{C} & 0 \leq f(1) - a\bmod{C} \\
f(1) - a\bmod{C} + C & 0 > f(1) - a\bmod{C} \end{cases}$$
Since we can identify the following:
$$f'(1) \leq f(1) \Longleftrightarrow f'(1) = f(1) - a\bmod{C} $$
$$f'(1) > f(1) \Longleftrightarrow f'(1) = f(1) - a\bmod{C} + C$$
We can simplify the two cases for $f'(1)$:
$$f'(1) = \begin{cases} f(1) - a\bmod{C} & f'(1) \leq f(1) \\
f(1) - a\bmod{C} + C & f'(1) > f(1) \end{cases}$$
The same for $f'(2)$:
$$f'(2) = f(2) + a \mod{C}$$
$$f'(2)\bmod{C}= f(2)\bmod{C} + a \bmod{C} \mod{C}$$
$$f'(2) = f(2) + a \bmod{C} \mod{C}$$
$$f'(2) = \begin{cases} f(2) + a\bmod{C} & f'(2) \geq f(2) \\
f(2) + a\bmod{C} - C & f'(2) < f(2) \end{cases}$$
Bringing the two cases for the two accounts together to determine the change in the reserve account:
$$b'(R) - b(R) \cdot C = \begin{cases} f(1) - a\bmod{C} + C - f(1) + f(2) + a\bmod{C} - C + f(2) & f'(1) > f(1) \land f'(2) < f(2) \\
f(1) - a\bmod{C} - f(1) + f(2) + a\bmod{C} - C + f(2) & f'(1) \leq f(1) \land f'(2) < f(2) \\
f(1) - a\bmod{C} + C - f(1) + f(2) + a\bmod{C} + f(2) & f'(1) > f(1) \land f'(2) \geq f(2) \\
f(1) - a\bmod{C} - f(1) + f(2) + a\bmod{C} + f(2) & f'(1) \leq f(1) \land f'(2) \geq f(2) \\
\end{cases}$$
This simplifies to:
$$b'(R) - b(R) \cdot C = \begin{cases} 0 & f'(1) > f(1) \land f'(2) < f(2) \\
-C & f'(1) \leq f(1) \land f'(2) < f(2) \\
C & f'(1) > f(1) \land f'(2) \geq f(2) \\
0 & f'(1) \leq f(1) \land f'(2) \geq f(2) \\
\end{cases}$$
Simplifying further by dividing by $C$:
$$b'(R) - b(R) = \begin{cases} 0 & f'(1) > f(1) \land f'(2) < f(2) \\
-1 & f'(1) \leq f(1) \land f'(2) < f(2) \\
1 & f'(1) > f(1) \land f'(2) \geq f(2) \\
0 & f'(1) \leq f(1) \land f'(2) \geq f(2) \\
\end{cases}$$
Thus the reserve account is updated based on the changes in the fractional units of the two accounts.
### Burn
When burning, we change only 1 account. Assume we are burning an amount $a$ from account $1$.
$$a'(1) = a(1) - a$$
The change in reserve is determined by the change in the fractional units of the account and the remainder.
$$(b'(R)-b(R)) \cdot C = f'(1) - f(1) + r' - r$$
The new fractional balance is:
$$f'(1) = f(1) - a \mod{C}$$
Apply modulo $C$ to both sides of the equation.
$$f'(1)\bmod{C}= f(1)\bmod{C} - a \bmod{C} \mod{C}$$
This simplifies to:
$$f'(1) = f(1) - a \bmod{C} \mod{C}$$
We can see two cases for $f'(1)$, depending on whether the new fractional balance is less than the old fractional balance.
$$f'(1) = \begin{cases} f(1) - a\bmod{C} & f'(1) \leq f(1) \\
f(1) - a\bmod{C} + C & f'(1) > f(1) \end{cases}$$
The second case occurs when we need to borrow from the integer units.
We update the remainder by adding $a$ to $r$ as burning increases the amount no longer in circulation but still backed by the reserve.
$$r' = r + a \mod{C}$$
$$r'\bmod{C}= r\bmod{C} + a \bmod{C} \mod{C}$$
$$r' = r + a \bmod{C} \mod{C}$$
We can see two cases for $r'$, depending on whether the new remainder is less than the old remainder.
$$r' = \begin{cases} r + a\bmod{C} & r' \geq r \\
r + a\bmod{C} - C & r' < r \end{cases}$$
The reserve account is updated based on the changes in the fractional units of the account and remainder.
$$b'(R) - b(R) = \begin{cases} 0 & f'(1) > f(1) \land r' < r \\
-1 & f'(1) \leq f(1) \land r' < r \\
1 & f'(1) > f(1) \land r' \geq r \\
0 & f'(1) \leq f(1) \land r' \geq r \\
\end{cases}$$
### Mint
Minting is similar to burning, but we add to the account instead of
removing it. Assume we are minting an amount $a$ to account $1$.
$$a'(1) = a(1) + a$$
The change in reserve is determined by the change in the fractional units of the account and the remainder.
$$(b'(R)-b(R)) \cdot C = f'(1) - f(1) + r' - r$$
The new fractional balance is:
$$f'(1) = f(1) + a \mod{C}$$
Apply modulo $C$ to both sides of the equation.
$$f'(1)\bmod{C}= f(1)\bmod{C} + a \bmod{C} \mod{C}$$
$$f'(1) = f(1) + a \bmod{C} \mod{C}$$
We can see two cases for $f'(1)$, depending on whether the new fractional balance is greater than the old fractional balance.
$$f'(1) = \begin{cases} f(1) + a\bmod{C} & f'(1) \geq f(1) \\
f(1) + a\bmod{C} - C & f'(1) < f(1) \end{cases}$$
The second case occurs when we need to carry to the integer unit.
We update the remainder by subtracting $a$ from $r$ as minting decreases the amount no longer in circulation but still backed by the reserve.
$$r' = r - a \mod{C}$$
$$r'\bmod{C}= r\bmod{C} - a \bmod{C} \mod{C}$$
$$r' = r - a \bmod{C} \mod{C}$$
$$r' = \begin{cases} r - a\bmod{C} & r' \leq r \\
r - a\bmod{C} + C & r' > r \end{cases}$$
The reserve account is updated based on the changes in the fractional units of the account and the remainder.
$$b'(R) - b(R) = \begin{cases} 0 & r' > r \land f'(1) < f(1) \\
-1 & r' \leq r \land f'(1) < f(1) \\
1 & r' > r \land f'(1) \geq f(1) \\
0 & r' \leq r \land f'(1) \geq f(1) \\
\end{cases}$$
## State
The `x/precisebank` module keeps state of the following:
1. Account fractional balances.
2. Remainder amount. This amount represents the fractional amount that is backed
by the reserve account but not yet in circulation. This can be non-zero if
a fractional amount less than `1ukava` is minted.
**Note:** Currently, mint and burns are only used to transfer fractional
amounts between accounts via `x/evm`. This means mint and burns on mainnet
state will always be equal and opposite, always resulting in a zero remainder
at the end of each transaction and block.
The `x/precisebank` module does not keep track of the reserve as it is stored in
the `x/bank` module.
## Keepers
The `x/precisebank module only exposes one keeper that wraps the bank module`
keeper and implements bank keeper compatible methods to support extended coin.
This complies with the `x/evm` module interface for `BankKeeper`.
```go
type BankKeeper interface {
authtypes.BankKeeper
SpendableCoin(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin
SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error
MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error
BurnCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error
}
```
## Messages
The `x/precisebank` module does not have any messages and is intended to be used
by other modules as a replacement of the bank module.
## Events
### Keeper Events
The `x/precisebank` module emits the following events, that are meant to be
match the events emitted by the `x/bank` module. Events emitted by
`x/precisebank` will only contain `akava` amounts, as the `x/bank` module will
emit events with all other denoms. This means if an account transfers multiple
coins including `akava`, the `x/precisebank` module will emit an event with the
full `akava` amount. If `ukava` is included in a transfer, mint, or burn, the
`x/precisebank` module will emit an event with the full equivalent `akava`
amount.
#### SendCoins
```json
{
"type": "transfer",
"attributes": [
{
"key": "recipient",
"value": "{{sdk.AccAddress of the recipient}}",
"index": true
},
{
"key": "sender",
"value": "{{sdk.AccAddress of the sender}}",
"index": true
},
{
"key": "amount",
"value": "{{sdk.Coins being transferred}}",
"index": true
}
]
}
```
```json
{
"type": "coin_spent",
"attributes": [
{
"key": "spender",
"value": "{{sdk.AccAddress of the address which is spending coins}}",
"index": true
},
{
"key": "amount",
"value": "{{sdk.Coins being spent}}",
"index": true
}
]
}
```
```json
{
"type": "coin_received",
"attributes": [
{
"key": "receiver",
"value": "{{sdk.AccAddress of the address beneficiary of the coins}}",
"index": true
},
{
"key": "amount",
"value": "{{sdk.Coins being received}}",
"index": true
}
]
}
```
#### MintCoins
```json
{
"type": "coinbase",
"attributes": [
{
"key": "minter",
"value": "{{sdk.AccAddress of the module minting coins}}",
"index": true
},
{
"key": "amount",
"value": "{{sdk.Coins being minted}}",
"index": true
}
]
}
```
```json
{
"type": "coin_received",
"attributes": [
{
"key": "receiver",
"value": "{{sdk.AccAddress of the module minting coins}}",
"index": true
},
{
"key": "amount",
"value": "{{sdk.Coins being received}}",
"index": true
}
]
}
```
#### BurnCoins
```json
{
"type": "burn",
"attributes": [
{
"key": "burner",
"value": "{{sdk.AccAddress of the module burning coins}}",
"index": true
},
{
"key": "amount",
"value": "{{sdk.Coins being burned}}",
"index": true
}
]
}
```
```json
{
"type": "coin_spent",
"attributes": [
{
"key": "spender",
"value": "{{sdk.AccAddress of the module burning coins}}",
"index": true
},
{
"key": "amount",
"value": "{{sdk.Coins being burned}}",
"index": true
}
]
}
```
## Client
### gRPC
A user can query the precisebank module using gRPC endpoints.
#### TotalFractionalBalances
The `TotalFractionalBalances` endpoint allows users to query the aggregate sum
of all fractional balances. This is primarily used for external verification of
the module state against the reserve balance.
```shell
kava.precisebank.v1.Query/TotalFractionalBalances
```
Example:
```shell
grpcurl -plaintext \
localhost:9090 \
kava.precisebank.v1.Query/TotalFractionalBalances
```
Example Output:
```json
{
"total": "2000000000000akava"
}
```
#### Remainder
The `Remainder` endpoint allows users to query the current remainder amount.
```shell
kava.precisebank.v1.Query/Remainder
```
Example:
```shell
grpcurl -plaintext \
localhost:9090 \
kava.precisebank.v1.Query/Remainder
```
Example Output:
```json
{
"remainder": "100akava"
}
```
#### FractionalBalance
The `FractionalBalance` endpoint allows users to query the fractional balance of
a specific account.
```shell
kava.precisebank.v1.Query/FractionalBalance
```
Example:
```shell
grpcurl -plaintext \
-d '{"address": "kava1..."}' \
localhost:9090 \
kava.precisebank.v1.Query/FractionalBalance
```
Example Output:
```json
{
"fractional_balance": "10000akava"
}
```