Skip to content

Commit 98caf44

Browse files
authored
Merge PR #2853: Write bank module specification, check spec/code consistency
* Update PENDING.md * New structure * Start transactions section * Remove MsgIssue * Update keepers.md * Add state.md * Update keepers.md, discovered #2887 * Move inputOutputCoins to BaseKeeper * Remove no-loner-applicable tests * More spec updates * Tiny cleanup * Clarify storage rationale * Warn the user * Remove extra newline
1 parent da83b25 commit 98caf44

File tree

9 files changed

+203
-58
lines changed

9 files changed

+203
-58
lines changed

PENDING.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ IMPROVEMENTS
3636
* Gaia
3737

3838
* SDK
39-
39+
- \#1277 Complete bank module specification
40+
4041
* Tendermint
4142

4243

docs/spec/bank/README.md

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Bank module specification
2+
3+
## Abstract
4+
5+
This document specifies the bank module of the Cosmos SDK.
6+
7+
The bank module is responsible for handling multi-asset coin transfers between
8+
accounts and tracking special-case pseudo-transfers which must work differently
9+
with particular kinds of accounts (notably delegating/undelegating for vesting
10+
accounts). It exposes several interfaces with varying capabilities for secure
11+
interaction with other modules which must alter user balances.
12+
13+
This module will be used in the Cosmos Hub.
14+
15+
## Contents
16+
17+
1. **[State](state.md)**
18+
1. **[Keepers](keepers.md)**
19+
1. [Common Types](keepers.md#common-types)
20+
1. [Input](keepers.md#input)
21+
1. [Output](keepers.md#output)
22+
1. [BaseKeeper](keepers.md#basekeeper)
23+
1. [SendKeeper](keepers.md#sendkeeper)
24+
1. [ViewKeeper](keepers.md#viewkeeper)
25+
1. **[Transactions](transactions.md)**
26+
1. [MsgSend](transactions.md#msgsend)

docs/spec/bank/WIP_keeper.md

-25
This file was deleted.

docs/spec/bank/keepers.md

+131
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
## Keepers
2+
3+
The bank module provides three different exported keeper interfaces which can be passed to other modules which need to read or update account balances. Modules should use the least-permissive interface which provides the functionality they require.
4+
5+
Note that you should always review the `bank` module code to ensure that permissions are limited in the way that you expect.
6+
7+
### Common Types
8+
9+
#### Input
10+
11+
An input of a multiparty transfer
12+
13+
```golang
14+
type Input struct {
15+
Address AccAddress
16+
Coins Coins
17+
}
18+
```
19+
20+
#### Output
21+
22+
An output of a multiparty transfer.
23+
24+
```golang
25+
type Output struct {
26+
Address AccAddress
27+
Coins Coins
28+
}
29+
```
30+
31+
### BaseKeeper
32+
33+
The base keeper provides full-permission access: the ability to arbitrary modify any account's balance and mint or burn coins.
34+
35+
```golang
36+
type BaseKeeper interface {
37+
SetCoins(addr AccAddress, amt Coins)
38+
SubtractCoins(addr AccAddress, amt Coins)
39+
AddCoins(addr AccAddress, amt Coins)
40+
InputOutputCoins(inputs []Input, outputs []Output)
41+
}
42+
```
43+
44+
`setCoins` fetches an account by address, sets the coins on the account, and saves the account.
45+
46+
```
47+
setCoins(addr AccAddress, amt Coins)
48+
account = accountKeeper.getAccount(addr)
49+
if account == nil
50+
fail with "no account found"
51+
account.Coins = amt
52+
accountKeeper.setAccount(account)
53+
```
54+
55+
`subtractCoins` fetches the coins of an account, subtracts the provided amount, and saves the account. This decreases the total supply.
56+
57+
```
58+
subtractCoins(addr AccAddress, amt Coins)
59+
oldCoins = getCoins(addr)
60+
newCoins = oldCoins - amt
61+
if newCoins < 0
62+
fail with "cannot end up with negative coins"
63+
setCoins(addr, newCoins)
64+
```
65+
66+
`addCoins` fetches the coins of an account, adds the provided amount, and saves the account. This increases the total supply.
67+
68+
```
69+
addCoins(addr AccAddress, amt Coins)
70+
oldCoins = getCoins(addr)
71+
newCoins = oldCoins + amt
72+
setCoins(addr, newCoins)
73+
```
74+
75+
`inputOutputCoins` transfers coins from any number of input accounts to any number of output accounts.
76+
77+
```
78+
inputOutputCoins(inputs []Input, outputs []Output)
79+
for input in inputs
80+
subtractCoins(input.Address, input.Coins)
81+
for output in outputs
82+
addCoins(output.Address, output.Coins)
83+
```
84+
85+
### SendKeeper
86+
87+
The send keeper provides access to account balances and the ability to transfer coins between accounts, but not to alter the total supply (mint or burn coins).
88+
89+
```golang
90+
type SendKeeper interface {
91+
SendCoins(from AccAddress, to AccAddress, amt Coins)
92+
}
93+
```
94+
95+
`sendCoins` transfers coins from one account to another.
96+
97+
```
98+
sendCoins(from AccAddress, to AccAddress, amt Coins)
99+
subtractCoins(from, amt)
100+
addCoins(to, amt)
101+
```
102+
103+
### ViewKeeper
104+
105+
The view keeper provides read-only access to account balances but no balance alteration functionality. All balance lookups are `O(1)`.
106+
107+
```golang
108+
type ViewKeeper interface {
109+
GetCoins(addr AccAddress) Coins
110+
HasCoins(addr AccAddress, amt Coins) bool
111+
}
112+
```
113+
114+
`getCoins` returns the coins associated with an account.
115+
116+
```
117+
getCoins(addr AccAddress)
118+
account = accountKeeper.getAccount(addr)
119+
if account == nil
120+
return Coins{}
121+
return account.Coins
122+
```
123+
124+
`hasCoins` returns whether or not an account has at least the provided amount of coins.
125+
126+
```
127+
hasCoins(addr AccAddress, amt Coins)
128+
account = accountKeeper.getAccount(addr)
129+
coins = getCoins(addr)
130+
return coins >= amt
131+
```

docs/spec/bank/state.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
## State
2+
3+
Presently, the bank module has no inherent state — it simply reads and writes accounts using the `AccountKeeper` from the `auth` module.
4+
5+
This implementation choice is intended to minimize necessary state reads/writes, since we expect most transactions to involve coin amounts (for fees), so storing coin data in the account saves reading it separately.

docs/spec/bank/transactions.md

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
## Transactions
2+
3+
### MsgSend
4+
5+
```golang
6+
type MsgSend struct {
7+
Inputs []Input
8+
Outputs []Output
9+
}
10+
```
11+
12+
`handleMsgSend` just runs `inputOutputCoins`.
13+
14+
```
15+
handleMsgSend(msg MsgSend)
16+
inputSum = 0
17+
for input in inputs
18+
inputSum += input.Amount
19+
outputSum = 0
20+
for output in outputs
21+
outputSum += output.Amount
22+
if inputSum != outputSum:
23+
fail with "input/output amount mismatch"
24+
25+
return inputOutputCoins(msg.Inputs, msg.Outputs)
26+
```

x/bank/keeper.go

+9-9
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ type Keeper interface {
2828
SetCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) sdk.Error
2929
SubtractCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error)
3030
AddCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error)
31+
InputOutputCoins(ctx sdk.Context, inputs []Input, outputs []Output) (sdk.Tags, sdk.Error)
3132
}
3233

3334
// BaseKeeper manages transfers between accounts. It implements the Keeper
@@ -67,6 +68,14 @@ func (keeper BaseKeeper) AddCoins(
6768
return addCoins(ctx, keeper.ak, addr, amt)
6869
}
6970

71+
// InputOutputCoins handles a list of inputs and outputs
72+
func (keeper BaseKeeper) InputOutputCoins(
73+
ctx sdk.Context, inputs []Input, outputs []Output,
74+
) (sdk.Tags, sdk.Error) {
75+
76+
return inputOutputCoins(ctx, keeper.ak, inputs, outputs)
77+
}
78+
7079
//-----------------------------------------------------------------------------
7180
// Send Keeper
7281

@@ -76,7 +85,6 @@ type SendKeeper interface {
7685
ViewKeeper
7786

7887
SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) (sdk.Tags, sdk.Error)
79-
InputOutputCoins(ctx sdk.Context, inputs []Input, outputs []Output) (sdk.Tags, sdk.Error)
8088
}
8189

8290
var _ SendKeeper = (*BaseSendKeeper)(nil)
@@ -105,14 +113,6 @@ func (keeper BaseSendKeeper) SendCoins(
105113
return sendCoins(ctx, keeper.ak, fromAddr, toAddr, amt)
106114
}
107115

108-
// InputOutputCoins handles a list of inputs and outputs
109-
func (keeper BaseSendKeeper) InputOutputCoins(
110-
ctx sdk.Context, inputs []Input, outputs []Output,
111-
) (sdk.Tags, sdk.Error) {
112-
113-
return inputOutputCoins(ctx, keeper.ak, inputs, outputs)
114-
}
115-
116116
//-----------------------------------------------------------------------------
117117
// View Keeper
118118

x/bank/keeper_test.go

-22
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,6 @@ func TestSendKeeper(t *testing.T) {
124124

125125
addr := sdk.AccAddress([]byte("addr1"))
126126
addr2 := sdk.AccAddress([]byte("addr2"))
127-
addr3 := sdk.AccAddress([]byte("addr3"))
128127
acc := accountKeeper.NewAccountWithAddress(ctx, addr)
129128

130129
// Test GetCoins/SetCoins
@@ -157,27 +156,6 @@ func TestSendKeeper(t *testing.T) {
157156
require.True(t, sendKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{sdk.NewInt64Coin("barcoin", 20), sdk.NewInt64Coin("foocoin", 5)}))
158157
require.True(t, sendKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{sdk.NewInt64Coin("barcoin", 10), sdk.NewInt64Coin("foocoin", 10)}))
159158

160-
// Test InputOutputCoins
161-
input1 := NewInput(addr2, sdk.Coins{sdk.NewInt64Coin("foocoin", 2)})
162-
output1 := NewOutput(addr, sdk.Coins{sdk.NewInt64Coin("foocoin", 2)})
163-
sendKeeper.InputOutputCoins(ctx, []Input{input1}, []Output{output1})
164-
require.True(t, sendKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{sdk.NewInt64Coin("barcoin", 20), sdk.NewInt64Coin("foocoin", 7)}))
165-
require.True(t, sendKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{sdk.NewInt64Coin("barcoin", 10), sdk.NewInt64Coin("foocoin", 8)}))
166-
167-
inputs := []Input{
168-
NewInput(addr, sdk.Coins{sdk.NewInt64Coin("foocoin", 3)}),
169-
NewInput(addr2, sdk.Coins{sdk.NewInt64Coin("barcoin", 3), sdk.NewInt64Coin("foocoin", 2)}),
170-
}
171-
172-
outputs := []Output{
173-
NewOutput(addr, sdk.Coins{sdk.NewInt64Coin("barcoin", 1)}),
174-
NewOutput(addr3, sdk.Coins{sdk.NewInt64Coin("barcoin", 2), sdk.NewInt64Coin("foocoin", 5)}),
175-
}
176-
sendKeeper.InputOutputCoins(ctx, inputs, outputs)
177-
require.True(t, sendKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{sdk.NewInt64Coin("barcoin", 21), sdk.NewInt64Coin("foocoin", 4)}))
178-
require.True(t, sendKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{sdk.NewInt64Coin("barcoin", 7), sdk.NewInt64Coin("foocoin", 6)}))
179-
require.True(t, sendKeeper.GetCoins(ctx, addr3).IsEqual(sdk.Coins{sdk.NewInt64Coin("barcoin", 2), sdk.NewInt64Coin("foocoin", 5)}))
180-
181159
}
182160

183161
func TestViewKeeper(t *testing.T) {

x/bank/msgs.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import (
66
sdk "github.com/cosmos/cosmos-sdk/types"
77
)
88

9+
// name to identify transaction routes
10+
const MsgRoute = "bank"
11+
912
// MsgSend - high level transaction of the coin module
1013
type MsgSend struct {
1114
Inputs []Input `json:"inputs"`
@@ -21,7 +24,7 @@ func NewMsgSend(in []Input, out []Output) MsgSend {
2124

2225
// Implements Msg.
2326
// nolint
24-
func (msg MsgSend) Route() string { return "bank" } // TODO: "bank/send"
27+
func (msg MsgSend) Route() string { return MsgRoute }
2528
func (msg MsgSend) Type() string { return "send" }
2629

2730
// Implements Msg.

0 commit comments

Comments
 (0)