/
before_send.go
132 lines (108 loc) · 3.99 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
package keeper
import (
"encoding/json"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/osmosis-labs/osmosis/osmoutils"
"github.com/osmosis-labs/osmosis/v23/x/tokenfactory/types"
errorsmod "cosmossdk.io/errors"
)
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)
}
// 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 suppresses 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.ErrBeforeSendHookOutOfGas, "%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: osmoutils.CWCoinFromSDKCoin(coin),
},
}
msgBz, err = json.Marshal(msg)
} else {
msg := types.TrackBeforeSendSudoMsg{
TrackBeforeSend: types.TrackBeforeSendMsg{
From: from.String(),
To: to.String(),
Amount: osmoutils.CWCoinFromSDKCoin(coin),
},
}
msgBz, err = json.Marshal(msg)
}
if err != nil {
return err
}
em := sdk.NewEventManager()
childCtx := ctx.WithGasMeter(sdk.NewGasMeter(types.BeforeSendHookGasLimit))
_, 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
}