Skip to content

Commit 1e7132d

Browse files
Add 30M gas limit to sudo helper (#7527)
* add 30M gas limit to sudo helper * add changelog * ensure existing lower limit is not overridden * using min, which is allowed now that we support go 1.21 * start implementing tests * catch panics and add tests * clean up test cases * change error return to generic default and clean up tests --------- Co-authored-by: Nicolas Lara <nicolaslara@gmail.com>
1 parent 900c0a0 commit 1e7132d

File tree

3 files changed

+128
-1
lines changed

3 files changed

+128
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7575
* [#7768](https://github.com/osmosis-labs/osmosis/pull/7768) Allow governance module account to transfer any CL position
7676
* [#7746](https://github.com/osmosis-labs/osmosis/pull/7746) Make forfeited incentives redeposit into the pool instead of sending to community pool
7777
* [#7785](https://github.com/osmosis-labs/osmosis/pull/7785) Remove reward claiming during position transfers
78+
* [#7527](https://github.com/osmosis-labs/osmosis/pull/7527) Add 30M gas limit to CW pool contract calls
7879

7980
### SDK
8081

osmoutils/cosmwasm/helpers.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@ package cosmwasm
22

33
import (
44
"encoding/json"
5+
"fmt"
56

67
storetypes "github.com/cosmos/cosmos-sdk/store/types"
78
sdk "github.com/cosmos/cosmos-sdk/types"
89
)
910

11+
const DefaultContractCallGasLimit = 30_000_000
12+
1013
// ContracKeeper defines the interface needed to be fulfilled for
1114
// the ContractKeeper.
1215
type ContractKeeper interface {
@@ -118,11 +121,26 @@ func Sudo[T any, K any](ctx sdk.Context, contractKeeper ContractKeeper, contract
118121
return response, err
119122
}
120123

121-
responseBz, err := contractKeeper.Sudo(ctx, sdk.MustAccAddressFromBech32(contractAddress), bz)
124+
// Defer to catch panics in case the sudo call runs out of gas.
125+
defer func() {
126+
if r := recover(); r != nil {
127+
var emptyResponse K
128+
response = emptyResponse
129+
err = fmt.Errorf("contract call ran out of gas")
130+
}
131+
}()
132+
133+
// Make contract call with a gas limit of 30M to ensure contracts cannot run unboundedly
134+
gasLimit := min(ctx.GasMeter().Limit(), DefaultContractCallGasLimit)
135+
childCtx := ctx.WithGasMeter(sdk.NewGasMeter(gasLimit))
136+
responseBz, err := contractKeeper.Sudo(childCtx, sdk.MustAccAddressFromBech32(contractAddress), bz)
122137
if err != nil {
123138
return response, err
124139
}
125140

141+
// Consume gas used for calling contract to the parent ctx
142+
ctx.GasMeter().ConsumeGas(childCtx.GasMeter().GasConsumed(), "Track contract call gas")
143+
126144
// valid empty response
127145
if len(responseBz) == 0 {
128146
return response, nil

osmoutils/cosmwasm/helpers_test.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package cosmwasm_test
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"testing"
7+
8+
wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper"
9+
sdk "github.com/cosmos/cosmos-sdk/types"
10+
"github.com/stretchr/testify/suite"
11+
12+
"github.com/osmosis-labs/osmosis/osmoutils/cosmwasm"
13+
"github.com/osmosis-labs/osmosis/v23/app/apptesting"
14+
)
15+
16+
type KeeperTestSuite struct {
17+
apptesting.KeeperTestHelper
18+
}
19+
20+
func TestKeeperTestSuite(t *testing.T) {
21+
suite.Run(t, new(KeeperTestSuite))
22+
}
23+
24+
func (s *KeeperTestSuite) TestSudoGasLimit() {
25+
// Skip test if there is system-side incompatibility
26+
s.SkipIfWSL()
27+
28+
// We use contracts already defined in existing modules to avoid duplicate test contract code.
29+
// This is a simple counter contract that counts `Amount` times and does a state write on each iteration.
30+
// Source code can be found in x/concentrated-liquidity/testcontracts/contract-sources
31+
counterContractPath := "../../x/concentrated-liquidity/testcontracts/compiled-wasm/counter.wasm"
32+
33+
// Message structs for the test CW contract
34+
type CountMsg struct {
35+
Amount int64 `json:"amount"`
36+
}
37+
type CountMsgResponse struct {
38+
}
39+
type CountSudoMsg struct {
40+
Count CountMsg `json:"count"`
41+
}
42+
43+
tests := map[string]struct {
44+
wasmFile string
45+
msg CountSudoMsg
46+
noContractSet bool
47+
48+
expectedError error
49+
}{
50+
"contract consumes less than limit": {
51+
wasmFile: counterContractPath,
52+
msg: CountSudoMsg{
53+
Count: CountMsg{
54+
// Consumes roughly 100k gas, which should be comfortably under the limit.
55+
Amount: 10,
56+
},
57+
},
58+
},
59+
"contract that consumes more than limit": {
60+
wasmFile: counterContractPath,
61+
msg: CountSudoMsg{
62+
Count: CountMsg{
63+
// Consumes roughly 1B gas, which is well above the 30M limit.
64+
Amount: 100000,
65+
},
66+
},
67+
expectedError: fmt.Errorf("contract call ran out of gas"),
68+
},
69+
}
70+
for name, tc := range tests {
71+
s.Run(name, func() {
72+
s.Setup()
73+
74+
// We use a gov permissioned contract keeper to avoid having to manually set permissions
75+
contractKeeper := wasmkeeper.NewGovPermissionKeeper(s.App.WasmKeeper)
76+
77+
// Upload and instantiate wasm code
78+
_, cosmwasmAddressBech32 := s.uploadAndInstantiateContract(contractKeeper, tc.wasmFile)
79+
80+
// System under test
81+
response, err := cosmwasm.Sudo[CountSudoMsg, CountMsgResponse](s.Ctx, contractKeeper, cosmwasmAddressBech32, tc.msg)
82+
83+
if tc.expectedError != nil {
84+
s.Require().ErrorContains(err, tc.expectedError.Error())
85+
return
86+
}
87+
88+
s.Require().NoError(err)
89+
s.Require().Equal(CountMsgResponse{}, response)
90+
})
91+
}
92+
}
93+
94+
// uploadAndInstantiateContract is a helper function to upload and instantiate a contract from a given file path.
95+
// It calls an empty Instantiate message on the created contract and returns the bech32 address after instantiation.
96+
func (s *KeeperTestSuite) uploadAndInstantiateContract(contractKeeper *wasmkeeper.PermissionedKeeper, filePath string) (rawCWAddr sdk.AccAddress, bech32CWAddr string) {
97+
// Upload and instantiate wasm code
98+
wasmCode, err := os.ReadFile(filePath)
99+
s.Require().NoError(err)
100+
codeID, _, err := contractKeeper.Create(s.Ctx, s.TestAccs[0], wasmCode, nil)
101+
s.Require().NoError(err)
102+
rawCWAddr, _, err = contractKeeper.Instantiate(s.Ctx, codeID, s.TestAccs[0], s.TestAccs[0], []byte("{}"), "", sdk.NewCoins())
103+
s.Require().NoError(err)
104+
bech32CWAddr, err = sdk.Bech32ifyAddressBytes("osmo", rawCWAddr)
105+
s.Require().NoError(err)
106+
107+
return rawCWAddr, bech32CWAddr
108+
}

0 commit comments

Comments
 (0)