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 (backport #7527) #7840

Merged
merged 1 commit into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* [#7768](https://github.com/osmosis-labs/osmosis/pull/7768) Allow governance module account to transfer any CL position
* [#7746](https://github.com/osmosis-labs/osmosis/pull/7746) Make forfeited incentives redeposit into the pool instead of sending to community pool
* [#7785](https://github.com/osmosis-labs/osmosis/pull/7785) Remove reward claiming during position transfers
* [#7527](https://github.com/osmosis-labs/osmosis/pull/7527) Add 30M gas limit to CW pool contract calls

### SDK

Expand Down
20 changes: 19 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,26 @@ 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 {
var emptyResponse K
response = emptyResponse
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
108 changes: 108 additions & 0 deletions osmoutils/cosmwasm/helpers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
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)

if tc.expectedError != nil {
s.Require().ErrorContains(err, tc.expectedError.Error())
return
}

s.Require().NoError(err)
s.Require().Equal(CountMsgResponse{}, response)
})
}
}

// 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
}