/
before_send.go
155 lines (129 loc) · 4.61 KB
/
before_send.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
package keeper
import (
"encoding/json"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/osmosis-labs/osmosis/v19/x/tokenfactory/types"
errorsmod "cosmossdk.io/errors"
wasmvmtypes "github.com/CosmWasm/wasmvm/types"
)
func (k Keeper) setBeforeSendHook(ctx sdk.Context, denom string, cosmwasmAddress string) error {
// verify that denom is an x/tokenfactory denom
_, _, err := types.DeconstructDenom(denom)
if err != nil {
return err
}
store := k.GetDenomPrefixStore(ctx, denom)
// delete the store for denom prefix store when cosmwasm address is nil
if cosmwasmAddress == "" {
store.Delete([]byte(types.BeforeSendHookAddressPrefixKey))
return nil
}
_, err = sdk.AccAddressFromBech32(cosmwasmAddress)
if err != nil {
return err
}
store.Set([]byte(types.BeforeSendHookAddressPrefixKey), []byte(cosmwasmAddress))
return nil
}
func (k Keeper) GetBeforeSendHook(ctx sdk.Context, denom string) string {
store := k.GetDenomPrefixStore(ctx, denom)
bz := store.Get([]byte(types.BeforeSendHookAddressPrefixKey))
if bz == nil {
return ""
}
return string(bz)
}
func CWCoinsFromSDKCoins(in sdk.Coins) wasmvmtypes.Coins {
var cwCoins wasmvmtypes.Coins
for _, coin := range in {
cwCoins = append(cwCoins, CWCoinFromSDKCoin(coin))
}
return cwCoins
}
func CWCoinFromSDKCoin(in sdk.Coin) wasmvmtypes.Coin {
return wasmvmtypes.Coin{
Denom: in.GetDenom(),
Amount: in.Amount.String(),
}
}
// Hooks wrapper struct for bank keeper
type Hooks struct {
k Keeper
}
var _ types.BankHooks = Hooks{}
// Return the wrapper struct
func (k Keeper) Hooks() Hooks {
return Hooks{k}
}
// TrackBeforeSend calls the before send listener contract surpresses any errors
func (h Hooks) TrackBeforeSend(ctx sdk.Context, from, to sdk.AccAddress, amount sdk.Coins) {
_ = h.k.callBeforeSendListener(ctx, from, to, amount, false)
}
// TrackBeforeSend calls the before send listener contract returns any errors
func (h Hooks) BlockBeforeSend(ctx sdk.Context, from, to sdk.AccAddress, amount sdk.Coins) error {
return h.k.callBeforeSendListener(ctx, from, to, amount, true)
}
// callBeforeSendListener iterates over each coin and sends corresponding sudo msg to the contract address stored in state.
// If blockBeforeSend is true, sudoMsg wraps BlockBeforeSendMsg, otherwise sudoMsg wraps TrackBeforeSendMsg.
// Note that we gas meter trackBeforeSend to prevent infinite contract calls.
// CONTRACT: this should not be called in beginBlock or endBlock since out of gas will cause this method to panic.
func (k Keeper) callBeforeSendListener(ctx sdk.Context, from, to sdk.AccAddress, amount sdk.Coins, blockBeforeSend bool) (err error) {
defer func() {
if r := recover(); r != nil {
err = errorsmod.Wrapf(types.ErrTrackBeforeSendOutOfGas, "%v", r)
}
}()
for _, coin := range amount {
cosmwasmAddress := k.GetBeforeSendHook(ctx, coin.Denom)
if cosmwasmAddress != "" {
cwAddr, err := sdk.AccAddressFromBech32(cosmwasmAddress)
if err != nil {
return err
}
var msgBz []byte
// get msgBz, either BlockBeforeSend or TrackBeforeSend
// Note that for trackBeforeSend, we need to gas meter computations to prevent infinite loop
// specifically because module to module sends are not gas metered.
// We don't need to do this for blockBeforeSend since blockBeforeSend is not called during module to module sends.
if blockBeforeSend {
msg := types.BlockBeforeSendSudoMsg{
BlockBeforeSend: types.BlockBeforeSendMsg{
From: from.String(),
To: to.String(),
Amount: CWCoinFromSDKCoin(coin),
},
}
msgBz, err = json.Marshal(msg)
} else {
msg := types.TrackBeforeSendSudoMsg{
TrackBeforeSend: types.TrackBeforeSendMsg{
From: from.String(),
To: to.String(),
Amount: CWCoinFromSDKCoin(coin),
},
}
msgBz, err = json.Marshal(msg)
}
if err != nil {
return err
}
em := sdk.NewEventManager()
// if its track before send, apply gas meter to prevent infinite loop
if blockBeforeSend {
_, err = k.contractKeeper.Sudo(ctx.WithEventManager(em), cwAddr, msgBz)
if err != nil {
return errorsmod.Wrapf(err, "failed to call before send hook for denom %s", coin.Denom)
}
} else {
childCtx := ctx.WithGasMeter(sdk.NewGasMeter(types.TrackBeforeSendGasLimit))
_, err = k.contractKeeper.Sudo(childCtx.WithEventManager(em), cwAddr, msgBz)
if err != nil {
return errorsmod.Wrapf(err, "failed to call before send hook for denom %s", coin.Denom)
}
// consume gas used for calling contract to the parent ctx
ctx.GasMeter().ConsumeGas(childCtx.GasMeter().GasConsumed(), "track before send gas")
}
}
}
return nil
}