/
ibc_middleware.go
86 lines (76 loc) · 3.04 KB
/
ibc_middleware.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
package contractmanager
import (
"fmt"
wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types"
"github.com/cometbft/cometbft/libs/log"
sdk "github.com/cosmos/cosmos-sdk/types"
contractmanagerkeeper "github.com/neutron-org/neutron/v3/x/contractmanager/keeper"
contractmanagertypes "github.com/neutron-org/neutron/v3/x/contractmanager/types"
)
type SudoLimitWrapper struct {
contractManager contractmanagertypes.ContractManagerKeeper
contractmanagertypes.WasmKeeper
}
// NewSudoLimitWrapper suppresses an error from a Sudo contract handler and saves it to a store
func NewSudoLimitWrapper(contractManager contractmanagertypes.ContractManagerKeeper, sudoKeeper contractmanagertypes.WasmKeeper) contractmanagertypes.WasmKeeper {
return SudoLimitWrapper{
contractManager,
sudoKeeper,
}
}
// Sudo calls underlying Sudo handlers with a limited amount of gas
// in case of `out of gas` panic it converts the panic into an error and stops `out of gas` panic propagation
// if error happens during the Sudo call, we store the data that raised the error, and return the error
func (k SudoLimitWrapper) Sudo(ctx sdk.Context, contractAddress sdk.AccAddress, msg []byte) (resp []byte, err error) {
cacheCtx, writeFn := createCachedContext(ctx, k.contractManager.GetParams(ctx).SudoCallGasLimit)
func() {
defer outOfGasRecovery(cacheCtx.GasMeter(), &err)
// Actually we have only one kind of error returned from acknowledgement
// maybe later we'll retrieve actual errors from events
resp, err = k.WasmKeeper.Sudo(cacheCtx, contractAddress, msg)
}()
if err != nil { // the contract either returned an error or panicked with `out of gas`
failure := k.contractManager.AddContractFailure(
ctx,
contractAddress.String(),
msg,
contractmanagerkeeper.RedactError(err).Error(),
)
ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(
wasmtypes.EventTypeSudo,
sdk.NewAttribute(wasmtypes.AttributeKeyContractAddr, contractAddress.String()),
sdk.NewAttribute(contractmanagertypes.AttributeKeySudoFailureID, fmt.Sprintf("%d", failure.Id)),
sdk.NewAttribute(contractmanagertypes.AttributeKeySudoError, err.Error()),
),
})
} else {
writeFn()
}
ctx.GasMeter().ConsumeGas(cacheCtx.GasMeter().GasConsumedToLimit(), "consume gas from cached context")
return
}
func (k SudoLimitWrapper) Logger(ctx sdk.Context) log.Logger {
return ctx.Logger().With("module", fmt.Sprintf("x/%s", contractmanagertypes.ModuleName))
}
// outOfGasRecovery converts `out of gas` panic into an error
// leaving unprocessed any other kinds of panics
func outOfGasRecovery(
gasMeter sdk.GasMeter,
err *error,
) {
if r := recover(); r != nil {
_, ok := r.(sdk.ErrorOutOfGas)
if !ok || !gasMeter.IsOutOfGas() {
panic(r)
}
*err = contractmanagertypes.ErrSudoOutOfGas
}
}
// createCachedContext creates a cached context with a limited gas meter.
func createCachedContext(ctx sdk.Context, gasLimit uint64) (sdk.Context, func()) {
cacheCtx, writeFn := ctx.CacheContext()
gasMeter := sdk.NewGasMeter(gasLimit)
cacheCtx = cacheCtx.WithGasMeter(gasMeter)
return cacheCtx, writeFn
}