-
Notifications
You must be signed in to change notification settings - Fork 537
/
contract_keeper.go
253 lines (223 loc) · 10.6 KB
/
contract_keeper.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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
package simapp
import (
"fmt"
storetypes "cosmossdk.io/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
callbacktypes "github.com/cosmos/ibc-go/modules/apps/callbacks/types"
clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types"
channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types"
ibcexported "github.com/cosmos/ibc-go/v8/modules/core/exported"
ibcmock "github.com/cosmos/ibc-go/v8/testing/mock"
)
// MockKeeper implements callbacktypes.ContractKeeper
var _ callbacktypes.ContractKeeper = (*ContractKeeper)(nil)
var StatefulCounterKey = "stateful-callback-counter"
const (
// OogPanicContract is a contract address that will panic out of gas
OogPanicContract = "panics out of gas"
// OogErrorContract is a contract address that will error out of gas
OogErrorContract = "errors out of gas"
// PanicContract is a contract address that will panic
PanicContract = "panics"
// ErrorContract is a contract address that will return an error
ErrorContract = "errors"
// SuccessContract is a contract address that will return nil
SuccessContract = "success"
)
// This is a mock contract keeper used for testing. It is not wired up to any modules.
// It implements the interface functions expected by the ibccallbacks middleware
// so that it can be tested with simapp. The keeper is responsible for tracking
// two metrics:
// - number of callbacks called per callback type
// - stateful entry attempts
//
// The counter for callbacks allows us to ensure the correct callbacks were routed to
// and the stateful entries allows us to track state reversals or reverted state upon
// contract execution failure or out of gas errors.
type ContractKeeper struct {
key storetypes.StoreKey
Counters map[callbacktypes.CallbackType]int
IBCSendPacketCallbackFn func(
cachedCtx sdk.Context,
sourcePort string,
sourceChannel string,
timeoutHeight clienttypes.Height,
timeoutTimestamp uint64,
packetData []byte,
contractAddress,
packetSenderAddress string,
) error
IBCOnAcknowledgementPacketCallbackFn func(
cachedCtx sdk.Context,
packet channeltypes.Packet,
acknowledgement []byte,
relayer sdk.AccAddress,
contractAddress,
packetSenderAddress string,
) error
IBCOnTimeoutPacketCallbackFn func(
cachedCtx sdk.Context,
packet channeltypes.Packet,
relayer sdk.AccAddress,
contractAddress,
packetSenderAddress string,
) error
IBCReceivePacketCallbackFn func(
cachedCtx sdk.Context,
packet ibcexported.PacketI,
ack ibcexported.Acknowledgement,
contractAddress string,
) error
}
// SetStateEntryCounter sets state entry counter. The number of stateful
// entries is tracked as a uint8. This function is used to test state reversals.
func (k ContractKeeper) SetStateEntryCounter(ctx sdk.Context, count uint8) {
store := ctx.KVStore(k.key)
store.Set([]byte(StatefulCounterKey), []byte{count})
}
// GetStateEntryCounter returns the state entry counter stored in state.
func (k ContractKeeper) GetStateEntryCounter(ctx sdk.Context) uint8 {
store := ctx.KVStore(k.key)
bz := store.Get([]byte(StatefulCounterKey))
if bz == nil {
return 0
}
return bz[0]
}
// IncrementStatefulCounter increments the stateful callback counter in state.
func (k ContractKeeper) IncrementStateEntryCounter(ctx sdk.Context) {
count := k.GetStateEntryCounter(ctx)
k.SetStateEntryCounter(ctx, count+1)
}
// NewKeeper creates a new mock ContractKeeper.
func NewContractKeeper(key storetypes.StoreKey) *ContractKeeper {
k := &ContractKeeper{
key: key,
Counters: make(map[callbacktypes.CallbackType]int),
}
k.IBCSendPacketCallbackFn = func(ctx sdk.Context, _, _ string, _ clienttypes.Height, _ uint64, _ []byte, contractAddress, _ string) error {
return k.ProcessMockCallback(ctx, callbacktypes.CallbackTypeSendPacket, contractAddress)
}
k.IBCOnAcknowledgementPacketCallbackFn = func(ctx sdk.Context, _ channeltypes.Packet, _ []byte, _ sdk.AccAddress, contractAddress, _ string) error {
return k.ProcessMockCallback(ctx, callbacktypes.CallbackTypeAcknowledgementPacket, contractAddress)
}
k.IBCOnTimeoutPacketCallbackFn = func(ctx sdk.Context, _ channeltypes.Packet, _ sdk.AccAddress, contractAddress, _ string) error {
return k.ProcessMockCallback(ctx, callbacktypes.CallbackTypeTimeoutPacket, contractAddress)
}
k.IBCReceivePacketCallbackFn = func(ctx sdk.Context, _ ibcexported.PacketI, _ ibcexported.Acknowledgement, contractAddress string) error {
return k.ProcessMockCallback(ctx, callbacktypes.CallbackTypeReceivePacket, contractAddress)
}
return k
}
// IBCPacketSendCallback increments the stateful entry counter and the send_packet callback counter.
// This function:
// - returns MockApplicationCallbackError and consumes half the remaining gas if the contract address is ErrorContract
// - Oog panics and consumes all the remaining gas + 1 if the contract address is OogPanicContract
// - returns MockApplicationCallbackError and consumes all the remaining gas + 1 if the contract address is OogErrorContract
// - Panics and consumes half the remaining gas if the contract address is PanicContract
// - returns nil and consumes half the remaining gas if the contract address is SuccessContract or any other value
func (k ContractKeeper) IBCSendPacketCallback(
ctx sdk.Context,
sourcePort string,
sourceChannel string,
timeoutHeight clienttypes.Height,
timeoutTimestamp uint64,
packetData []byte,
contractAddress,
packetSenderAddress string,
) error {
return k.IBCSendPacketCallbackFn(ctx, sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, packetData, contractAddress, packetSenderAddress)
}
// IBCOnAcknowledgementPacketCallback increments the stateful entry counter and the acknowledgement_packet callback counter.
// This function:
// - returns MockApplicationCallbackError and consumes half the remaining gas if the contract address is ErrorContract
// - Oog panics and consumes all the remaining gas + 1 if the contract address is OogPanicContract
// - returns MockApplicationCallbackError and consumes all the remaining gas + 1 if the contract address is OogErrorContract
// - Panics and consumes half the remaining gas if the contract address is PanicContract
// - returns nil and consumes half the remaining gas if the contract address is SuccessContract or any other value
func (k ContractKeeper) IBCOnAcknowledgementPacketCallback(
ctx sdk.Context,
packet channeltypes.Packet,
acknowledgement []byte,
relayer sdk.AccAddress,
contractAddress,
packetSenderAddress string,
) error {
return k.IBCOnAcknowledgementPacketCallbackFn(ctx, packet, acknowledgement, relayer, contractAddress, packetSenderAddress)
}
// IBCOnTimeoutPacketCallback increments the stateful entry counter and the timeout_packet callback counter.
// This function:
// - returns MockApplicationCallbackError and consumes half the remaining gas if the contract address is ErrorContract
// - Oog panics and consumes all the remaining gas + 1 if the contract address is OogPanicContract
// - returns MockApplicationCallbackError and consumes all the remaining gas + 1 if the contract address is OogErrorContract
// - Panics and consumes half the remaining gas if the contract address is PanicContract
// - returns nil and consumes half the remaining gas if the contract address is SuccessContract or any other value
func (k ContractKeeper) IBCOnTimeoutPacketCallback(
ctx sdk.Context,
packet channeltypes.Packet,
relayer sdk.AccAddress,
contractAddress,
packetSenderAddress string,
) error {
return k.IBCOnTimeoutPacketCallbackFn(ctx, packet, relayer, contractAddress, packetSenderAddress)
}
// IBCReceivePacketCallback increments the stateful entry counter and the receive_packet callback counter.
// This function:
// - returns MockApplicationCallbackError and consumes half the remaining gas if the contract address is ErrorContract
// - Oog panics and consumes all the remaining gas + 1 if the contract address is OogPanicContract
// - returns MockApplicationCallbackError and consumes all the remaining gas + 1 if the contract address is OogErrorContract
// - Panics and consumes half the remaining gas if the contract address is PanicContract
// - returns nil and consumes half the remaining gas if the contract address is SuccessContract or any other value
func (k ContractKeeper) IBCReceivePacketCallback(
ctx sdk.Context,
packet ibcexported.PacketI,
ack ibcexported.Acknowledgement,
contractAddress string,
) error {
return k.IBCReceivePacketCallbackFn(ctx, packet, ack, contractAddress)
}
// ProcessMockCallback processes a mock callback.
// It increments the stateful entry counter and the callback counter.
// This function:
// - returns MockApplicationCallbackError and consumes half the remaining gas if the contract address is ErrorContract
// - Oog panics and consumes all the remaining gas + 1 if the contract address is OogPanicContract
// - returns MockApplicationCallbackError and consumes all the remaining gas + 1 if the contract address is OogErrorContract
// - Panics and consumes half the remaining gas if the contract address is PanicContract
// - returns nil and consumes half the remaining gas if the contract address is SuccessContract or any other value
func (k ContractKeeper) ProcessMockCallback(
ctx sdk.Context,
callbackType callbacktypes.CallbackType,
contractAddress string,
) (err error) {
gasRemaining := ctx.GasMeter().GasRemaining()
// increment stateful entries, if the callbacks module handler
// reverts state, we can check by querying for the counter
// currently stored.
k.IncrementStateEntryCounter(ctx)
// increment callback execution attempts
k.Counters[callbackType]++
switch contractAddress {
case ErrorContract:
// consume half of the remaining gas so that ConsumeGas cannot oog panic
ctx.GasMeter().ConsumeGas(gasRemaining/2, fmt.Sprintf("mock %s callback unauthorized", callbackType))
return ibcmock.MockApplicationCallbackError
case OogPanicContract:
ctx.GasMeter().ConsumeGas(gasRemaining+1, fmt.Sprintf("mock %s callback oog panic", callbackType))
return nil // unreachable
case OogErrorContract:
defer func() {
_ = recover()
err = ibcmock.MockApplicationCallbackError
}()
ctx.GasMeter().ConsumeGas(gasRemaining+1, fmt.Sprintf("mock %s callback oog error", callbackType))
return nil // unreachable
case PanicContract:
// consume half of the remaining gas so that ConsumeGas cannot oog panic
ctx.GasMeter().ConsumeGas(gasRemaining/2, fmt.Sprintf("mock %s callback panic", callbackType))
panic(ibcmock.MockApplicationCallbackError)
default:
// consume half of the remaining gas so that ConsumeGas cannot oog panic
ctx.GasMeter().ConsumeGas(gasRemaining/2, fmt.Sprintf("mock %s callback success", callbackType))
return nil // success
}
}