Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add 30M gas limit to sudo helper #7527

Merged
merged 12 commits into from
Mar 25, 2024
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* [#7689](https://github.com/osmosis-labs/osmosis/pull/7689) Make CL price estimations not cause state writes (speed and gas improvements)
* [#7745](https://github.com/osmosis-labs/osmosis/pull/7745) Add gauge id query to stargate whitelist
* [#7747](https://github.com/osmosis-labs/osmosis/pull/7747) Remove redundant call to incentive collection in CL position withdrawal logic
* [#7527](https://github.com/osmosis-labs/osmosis/pull/7527) Add 30M gas limit to CW pool contract calls

## v23.0.7-iavl-v1

Expand Down
19 changes: 18 additions & 1 deletion osmoutils/cosmwasm/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ package cosmwasm

import (
"encoding/json"
"fmt"

storetypes "github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
)

const DefaultContractCallGasLimit = 30_000_000

// ContracKeeper defines the interface needed to be fulfilled for
// the ContractKeeper.
type ContractKeeper interface {
Expand Down Expand Up @@ -118,11 +121,25 @@ func Sudo[T any, K any](ctx sdk.Context, contractKeeper ContractKeeper, contract
return response, err
}

responseBz, err := contractKeeper.Sudo(ctx, sdk.MustAccAddressFromBech32(contractAddress), bz)
// Defer to catch panics in case the sudo call runs out of gas.
defer func() {
if r := recover(); r != nil {
response = *new(K) // Create an empty version of response type K
AlpinYukseloglu marked this conversation as resolved.
Show resolved Hide resolved
err = fmt.Errorf("contract call ran out of gas")
}
}()

// Make contract call with a gas limit of 30M to ensure contracts cannot run unboundedly
gasLimit := min(ctx.GasMeter().Limit(), DefaultContractCallGasLimit)
childCtx := ctx.WithGasMeter(sdk.NewGasMeter(gasLimit))
responseBz, err := contractKeeper.Sudo(childCtx, sdk.MustAccAddressFromBech32(contractAddress), bz)
if err != nil {
return response, err
}

// Consume gas used for calling contract to the parent ctx
ctx.GasMeter().ConsumeGas(childCtx.GasMeter().GasConsumed(), "Track contract call gas")

// valid empty response
if len(responseBz) == 0 {
return response, nil
Expand Down
109 changes: 109 additions & 0 deletions osmoutils/cosmwasm/helpers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package cosmwasm_test

import (
"fmt"
"os"
"testing"

wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/suite"

"github.com/osmosis-labs/osmosis/osmoutils/cosmwasm"
"github.com/osmosis-labs/osmosis/v23/app/apptesting"
)

type KeeperTestSuite struct {
apptesting.KeeperTestHelper
}

func TestKeeperTestSuite(t *testing.T) {
suite.Run(t, new(KeeperTestSuite))
}

func (s *KeeperTestSuite) TestSudoGasLimit() {
// Skip test if there is system-side incompatibility
s.SkipIfWSL()

// We use contracts already defined in existing modules to avoid duplicate test contract code.
// This is a simple counter contract that counts `Amount` times and does a state write on each iteration.
// Source code can be found in x/concentrated-liquidity/testcontracts/contract-sources
counterContractPath := "../../x/concentrated-liquidity/testcontracts/compiled-wasm/counter.wasm"

// Message structs for the test CW contract
type CountMsg struct {
Amount int64 `json:"amount"`
}
type CountMsgResponse struct {
}
type CountSudoMsg struct {
Count CountMsg `json:"count"`
}

tests := map[string]struct {
wasmFile string
msg CountSudoMsg
noContractSet bool

expectedError error
}{
"contract consumes less than limit": {
wasmFile: counterContractPath,
msg: CountSudoMsg{
Count: CountMsg{
// Consumes roughly 100k gas, which should be comfortably under the limit.
Amount: 10,
},
},
},
"contract that consumes more than limit": {
wasmFile: counterContractPath,
msg: CountSudoMsg{
Count: CountMsg{
// Consumes roughly 1B gas, which is well above the 30M limit.
Amount: 100000,
},
},
expectedError: fmt.Errorf("contract call ran out of gas"),
},
}
for name, tc := range tests {
s.Run(name, func() {
s.Setup()

// We use a gov permissioned contract keeper to avoid having to manually set permissions
contractKeeper := wasmkeeper.NewGovPermissionKeeper(s.App.WasmKeeper)

// Upload and instantiate wasm code
_, cosmwasmAddressBech32 := s.uploadAndInstantiateContract(contractKeeper, tc.wasmFile)

// System under test
response, err := cosmwasm.Sudo[CountSudoMsg, CountMsgResponse](s.Ctx, contractKeeper, cosmwasmAddressBech32, tc.msg)

fmt.Println("response: ", response)

AlpinYukseloglu marked this conversation as resolved.
Show resolved Hide resolved
if tc.expectedError != nil {
s.Require().ErrorContains(err, tc.expectedError.Error())
return
}

s.Require().NoError(err)
AlpinYukseloglu marked this conversation as resolved.
Show resolved Hide resolved
})
}
}

// uploadAndInstantiateContract is a helper function to upload and instantiate a contract from a given file path.
// It calls an empty Instantiate message on the created contract and returns the bech32 address after instantiation.
func (s *KeeperTestSuite) uploadAndInstantiateContract(contractKeeper *wasmkeeper.PermissionedKeeper, filePath string) (rawCWAddr sdk.AccAddress, bech32CWAddr string) {
// Upload and instantiate wasm code
wasmCode, err := os.ReadFile(filePath)
s.Require().NoError(err)
codeID, _, err := contractKeeper.Create(s.Ctx, s.TestAccs[0], wasmCode, nil)
s.Require().NoError(err)
rawCWAddr, _, err = contractKeeper.Instantiate(s.Ctx, codeID, s.TestAccs[0], s.TestAccs[0], []byte("{}"), "", sdk.NewCoins())
s.Require().NoError(err)
bech32CWAddr, err = sdk.Bech32ifyAddressBytes("osmo", rawCWAddr)
s.Require().NoError(err)

return rawCWAddr, bech32CWAddr
}