|
| 1 | +package wasm_hooks_test |
| 2 | + |
| 3 | +import ( |
| 4 | + "encoding/binary" |
| 5 | + "testing" |
| 6 | + "time" |
| 7 | + |
| 8 | + "github.com/stretchr/testify/require" |
| 9 | + |
| 10 | + "github.com/cometbft/cometbft/crypto" |
| 11 | + "github.com/cometbft/cometbft/crypto/ed25519" |
| 12 | + tmproto "github.com/cometbft/cometbft/proto/tendermint/types" |
| 13 | + |
| 14 | + "cosmossdk.io/log" |
| 15 | + "cosmossdk.io/math" |
| 16 | + "cosmossdk.io/store" |
| 17 | + "cosmossdk.io/store/metrics" |
| 18 | + storetypes "cosmossdk.io/store/types" |
| 19 | + "cosmossdk.io/x/tx/signing" |
| 20 | + dbm "github.com/cosmos/cosmos-db" |
| 21 | + "github.com/cosmos/cosmos-sdk/baseapp" |
| 22 | + "github.com/cosmos/cosmos-sdk/client" |
| 23 | + "github.com/cosmos/cosmos-sdk/codec" |
| 24 | + codecaddress "github.com/cosmos/cosmos-sdk/codec/address" |
| 25 | + codectypes "github.com/cosmos/cosmos-sdk/codec/types" |
| 26 | + "github.com/cosmos/cosmos-sdk/runtime" |
| 27 | + "github.com/cosmos/cosmos-sdk/std" |
| 28 | + sdk "github.com/cosmos/cosmos-sdk/types" |
| 29 | + "github.com/cosmos/cosmos-sdk/types/module" |
| 30 | + "github.com/cosmos/cosmos-sdk/x/auth" |
| 31 | + authcodec "github.com/cosmos/cosmos-sdk/x/auth/codec" |
| 32 | + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" |
| 33 | + "github.com/cosmos/cosmos-sdk/x/auth/tx" |
| 34 | + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" |
| 35 | + "github.com/cosmos/cosmos-sdk/x/bank" |
| 36 | + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" |
| 37 | + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" |
| 38 | + distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types" |
| 39 | + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" |
| 40 | + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" |
| 41 | + "github.com/cosmos/gogoproto/proto" |
| 42 | + |
| 43 | + "github.com/CosmWasm/wasmd/x/wasm" |
| 44 | + wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" |
| 45 | + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" |
| 46 | + |
| 47 | + "github.com/skip-mev/slinky/x/oracle" |
| 48 | + oraclekeeper "github.com/skip-mev/slinky/x/oracle/keeper" |
| 49 | + oracletypes "github.com/skip-mev/slinky/x/oracle/types" |
| 50 | +) |
| 51 | + |
| 52 | +var ModuleBasics = module.NewBasicManager( |
| 53 | + auth.AppModuleBasic{}, |
| 54 | + bank.AppModuleBasic{}, |
| 55 | + wasm.AppModuleBasic{}, |
| 56 | + oracle.AppModuleBasic{}, |
| 57 | +) |
| 58 | + |
| 59 | +var ( |
| 60 | + initiaSupply = math.NewInt(100_000_000_000) |
| 61 | + testDenoms = []string{ |
| 62 | + "test1", |
| 63 | + "test2", |
| 64 | + "test3", |
| 65 | + "test4", |
| 66 | + "test5", |
| 67 | + } |
| 68 | +) |
| 69 | + |
| 70 | +type EncodingConfig struct { |
| 71 | + InterfaceRegistry codectypes.InterfaceRegistry |
| 72 | + Codec codec.Codec |
| 73 | + TxConfig client.TxConfig |
| 74 | + Amino *codec.LegacyAmino |
| 75 | +} |
| 76 | + |
| 77 | +func MakeTestCodec(t testing.TB) codec.Codec { |
| 78 | + return MakeEncodingConfig(t).Codec |
| 79 | +} |
| 80 | + |
| 81 | +func MakeEncodingConfig(_ testing.TB) EncodingConfig { |
| 82 | + interfaceRegistry, _ := codectypes.NewInterfaceRegistryWithOptions(codectypes.InterfaceRegistryOptions{ |
| 83 | + ProtoFiles: proto.HybridResolver, |
| 84 | + SigningOptions: signing.Options{ |
| 85 | + AddressCodec: codecaddress.NewBech32Codec(sdk.GetConfig().GetBech32AccountAddrPrefix()), |
| 86 | + ValidatorAddressCodec: codecaddress.NewBech32Codec(sdk.GetConfig().GetBech32ValidatorAddrPrefix()), |
| 87 | + }, |
| 88 | + }) |
| 89 | + appCodec := codec.NewProtoCodec(interfaceRegistry) |
| 90 | + legacyAmino := codec.NewLegacyAmino() |
| 91 | + txConfig := tx.NewTxConfig(appCodec, tx.DefaultSignModes) |
| 92 | + |
| 93 | + std.RegisterInterfaces(interfaceRegistry) |
| 94 | + std.RegisterLegacyAminoCodec(legacyAmino) |
| 95 | + |
| 96 | + ModuleBasics.RegisterLegacyAminoCodec(legacyAmino) |
| 97 | + ModuleBasics.RegisterInterfaces(interfaceRegistry) |
| 98 | + |
| 99 | + return EncodingConfig{ |
| 100 | + InterfaceRegistry: interfaceRegistry, |
| 101 | + Codec: appCodec, |
| 102 | + TxConfig: txConfig, |
| 103 | + Amino: legacyAmino, |
| 104 | + } |
| 105 | +} |
| 106 | + |
| 107 | +var bondDenom = sdk.DefaultBondDenom |
| 108 | + |
| 109 | +func initialTotalSupply() sdk.Coins { |
| 110 | + faucetBalance := sdk.NewCoins(sdk.NewCoin(bondDenom, initiaSupply)) |
| 111 | + for _, testDenom := range testDenoms { |
| 112 | + faucetBalance = faucetBalance.Add(sdk.NewCoin(testDenom, initiaSupply)) |
| 113 | + } |
| 114 | + |
| 115 | + return faucetBalance |
| 116 | +} |
| 117 | + |
| 118 | +type TestFaucet struct { |
| 119 | + t testing.TB |
| 120 | + bankKeeper bankkeeper.Keeper |
| 121 | + sender sdk.AccAddress |
| 122 | + balance sdk.Coins |
| 123 | + minterModuleName string |
| 124 | +} |
| 125 | + |
| 126 | +func NewTestFaucet(t testing.TB, ctx sdk.Context, bankKeeper bankkeeper.Keeper, minterModuleName string, initiaSupply ...sdk.Coin) *TestFaucet { |
| 127 | + require.NotEmpty(t, initiaSupply) |
| 128 | + r := &TestFaucet{t: t, bankKeeper: bankKeeper, minterModuleName: minterModuleName} |
| 129 | + _, _, addr := keyPubAddr() |
| 130 | + r.sender = addr |
| 131 | + r.Mint(ctx, addr, initiaSupply...) |
| 132 | + r.balance = initiaSupply |
| 133 | + return r |
| 134 | +} |
| 135 | + |
| 136 | +func (f *TestFaucet) Mint(parentCtx sdk.Context, addr sdk.AccAddress, amounts ...sdk.Coin) { |
| 137 | + amounts = sdk.Coins(amounts).Sort() |
| 138 | + require.NotEmpty(f.t, amounts) |
| 139 | + ctx := parentCtx.WithEventManager(sdk.NewEventManager()) // discard all faucet related events |
| 140 | + err := f.bankKeeper.MintCoins(ctx, f.minterModuleName, amounts) |
| 141 | + require.NoError(f.t, err) |
| 142 | + err = f.bankKeeper.SendCoinsFromModuleToAccount(ctx, f.minterModuleName, addr, amounts) |
| 143 | + require.NoError(f.t, err) |
| 144 | + f.balance = f.balance.Add(amounts...) |
| 145 | +} |
| 146 | + |
| 147 | +func (f *TestFaucet) Fund(parentCtx sdk.Context, receiver sdk.AccAddress, amounts ...sdk.Coin) { |
| 148 | + require.NotEmpty(f.t, amounts) |
| 149 | + // ensure faucet is always filled |
| 150 | + if !f.balance.IsAllGTE(amounts) { |
| 151 | + f.Mint(parentCtx, f.sender, amounts...) |
| 152 | + } |
| 153 | + ctx := parentCtx.WithEventManager(sdk.NewEventManager()) // discard all faucet related events |
| 154 | + err := f.bankKeeper.SendCoins(ctx, f.sender, receiver, amounts) |
| 155 | + require.NoError(f.t, err) |
| 156 | + f.balance = f.balance.Sub(amounts...) |
| 157 | +} |
| 158 | + |
| 159 | +func (f *TestFaucet) NewFundedAccount(ctx sdk.Context, amounts ...sdk.Coin) sdk.AccAddress { |
| 160 | + _, _, addr := keyPubAddr() |
| 161 | + f.Fund(ctx, addr, amounts...) |
| 162 | + return addr |
| 163 | +} |
| 164 | + |
| 165 | +type TestKeepers struct { |
| 166 | + AccountKeeper authkeeper.AccountKeeper |
| 167 | + BankKeeper bankkeeper.Keeper |
| 168 | + WasmKeeper wasmkeeper.Keeper |
| 169 | + OracleKeeper oraclekeeper.Keeper |
| 170 | + |
| 171 | + EncodingConfig EncodingConfig |
| 172 | + Faucet *TestFaucet |
| 173 | + MultiStore storetypes.CommitMultiStore |
| 174 | +} |
| 175 | + |
| 176 | +// createDefaultTestInput common settings for createTestInput |
| 177 | +func createDefaultTestInput(t testing.TB) (sdk.Context, TestKeepers) { |
| 178 | + return createTestInput(t, false) |
| 179 | +} |
| 180 | + |
| 181 | +// createTestInput encoders can be nil to accept the defaults, or set it to override some of the message handlers (like default) |
| 182 | +func createTestInput(t testing.TB, isCheckTx bool) (sdk.Context, TestKeepers) { |
| 183 | + // Load default move config |
| 184 | + return _createTestInput(t, isCheckTx, dbm.NewMemDB()) |
| 185 | +} |
| 186 | + |
| 187 | +var keyCounter uint64 |
| 188 | + |
| 189 | +// we need to make this deterministic (same every test run), as encoded address size and thus gas cost, |
| 190 | +// depends on the actual bytes (due to ugly CanonicalAddress encoding) |
| 191 | +func keyPubAddr() (crypto.PrivKey, crypto.PubKey, sdk.AccAddress) { |
| 192 | + keyCounter++ |
| 193 | + seed := make([]byte, 8) |
| 194 | + binary.BigEndian.PutUint64(seed, keyCounter) |
| 195 | + |
| 196 | + key := ed25519.GenPrivKeyFromSecret(seed) |
| 197 | + pub := key.PubKey() |
| 198 | + addr := sdk.AccAddress(pub.Address()) |
| 199 | + return key, pub, addr |
| 200 | +} |
| 201 | + |
| 202 | +// encoders can be nil to accept the defaults, or set it to override some of the message handlers (like default) |
| 203 | +func _createTestInput( |
| 204 | + t testing.TB, |
| 205 | + isCheckTx bool, |
| 206 | + db dbm.DB, |
| 207 | +) (sdk.Context, TestKeepers) { |
| 208 | + keys := storetypes.NewKVStoreKeys( |
| 209 | + authtypes.StoreKey, banktypes.StoreKey, stakingtypes.StoreKey, |
| 210 | + distributiontypes.StoreKey, wasmtypes.StoreKey, |
| 211 | + oracletypes.StoreKey, |
| 212 | + ) |
| 213 | + ms := store.NewCommitMultiStore(db, log.NewNopLogger(), metrics.NewNoOpMetrics()) |
| 214 | + for _, v := range keys { |
| 215 | + ms.MountStoreWithDB(v, storetypes.StoreTypeIAVL, db) |
| 216 | + } |
| 217 | + memKeys := storetypes.NewMemoryStoreKeys() |
| 218 | + for _, v := range memKeys { |
| 219 | + ms.MountStoreWithDB(v, storetypes.StoreTypeMemory, db) |
| 220 | + } |
| 221 | + |
| 222 | + require.NoError(t, ms.LoadLatestVersion()) |
| 223 | + |
| 224 | + ctx := sdk.NewContext(ms, tmproto.Header{ |
| 225 | + Height: 1, |
| 226 | + Time: time.Date(2020, time.April, 22, 12, 0, 0, 0, time.UTC), |
| 227 | + }, isCheckTx, log.NewNopLogger()).WithHeaderHash(make([]byte, 32)) |
| 228 | + |
| 229 | + encodingConfig := MakeEncodingConfig(t) |
| 230 | + appCodec := encodingConfig.Codec |
| 231 | + |
| 232 | + maccPerms := map[string][]string{ // module account permissions |
| 233 | + authtypes.FeeCollectorName: nil, |
| 234 | + |
| 235 | + // for testing |
| 236 | + authtypes.Minter: {authtypes.Minter, authtypes.Burner}, |
| 237 | + } |
| 238 | + |
| 239 | + ac := authcodec.NewBech32Codec(sdk.GetConfig().GetBech32AccountAddrPrefix()) |
| 240 | + |
| 241 | + accountKeeper := authkeeper.NewAccountKeeper( |
| 242 | + appCodec, |
| 243 | + runtime.NewKVStoreService(keys[authtypes.StoreKey]), // target store |
| 244 | + authtypes.ProtoBaseAccount, // prototype |
| 245 | + maccPerms, |
| 246 | + ac, |
| 247 | + sdk.GetConfig().GetBech32AccountAddrPrefix(), |
| 248 | + authtypes.NewModuleAddress(govtypes.ModuleName).String(), |
| 249 | + ) |
| 250 | + blockedAddrs := make(map[string]bool) |
| 251 | + for acc := range maccPerms { |
| 252 | + blockedAddrs[authtypes.NewModuleAddress(acc).String()] = true |
| 253 | + } |
| 254 | + |
| 255 | + bankKeeper := bankkeeper.NewBaseKeeper( |
| 256 | + appCodec, |
| 257 | + runtime.NewKVStoreService(keys[banktypes.StoreKey]), |
| 258 | + accountKeeper, |
| 259 | + blockedAddrs, |
| 260 | + authtypes.NewModuleAddress(govtypes.ModuleName).String(), |
| 261 | + ctx.Logger().With("module", "x/"+banktypes.ModuleName), |
| 262 | + ) |
| 263 | + require.NoError(t, bankKeeper.SetParams(ctx, banktypes.DefaultParams())) |
| 264 | + |
| 265 | + oracleKeeper := oraclekeeper.NewKeeper( |
| 266 | + runtime.NewKVStoreService(keys[oracletypes.StoreKey]), |
| 267 | + appCodec, |
| 268 | + authtypes.NewModuleAddress(govtypes.ModuleName), |
| 269 | + ) |
| 270 | + |
| 271 | + msgRouter := baseapp.NewMsgServiceRouter() |
| 272 | + msgRouter.SetInterfaceRegistry(encodingConfig.InterfaceRegistry) |
| 273 | + queryRouter := baseapp.NewGRPCQueryRouter() |
| 274 | + queryRouter.SetInterfaceRegistry(encodingConfig.InterfaceRegistry) |
| 275 | + |
| 276 | + queryAllowlist := make(map[string]proto.Message) |
| 277 | + queryAllowlist["/slinky.oracle.v1.Query/GetAllCurrencyPairs"] = &oracletypes.GetAllCurrencyPairsResponse{} |
| 278 | + queryAllowlist["/slinky.oracle.v1.Query/GetPrice"] = &oracletypes.GetPriceResponse{} |
| 279 | + queryAllowlist["/slinky.oracle.v1.Query/GetPrices"] = &oracletypes.GetPricesResponse{} |
| 280 | + |
| 281 | + // use accept list stargate querier |
| 282 | + wasmOpts := []wasmkeeper.Option{} |
| 283 | + wasmOpts = append(wasmOpts, wasmkeeper.WithQueryPlugins(&wasmkeeper.QueryPlugins{ |
| 284 | + Stargate: wasmkeeper.AcceptListStargateQuerier(queryAllowlist, queryRouter, appCodec), |
| 285 | + })) |
| 286 | + |
| 287 | + wasmKeeper := wasmkeeper.NewKeeper( |
| 288 | + appCodec, |
| 289 | + runtime.NewKVStoreService(keys[wasmtypes.StoreKey]), |
| 290 | + accountKeeper, |
| 291 | + bankKeeper, |
| 292 | + nil, |
| 293 | + nil, |
| 294 | + nil, |
| 295 | + nil, |
| 296 | + nil, |
| 297 | + nil, |
| 298 | + nil, |
| 299 | + msgRouter, |
| 300 | + queryRouter, |
| 301 | + t.TempDir(), |
| 302 | + wasmtypes.DefaultWasmConfig(), |
| 303 | + "iterator,stargate,cosmwasm_1_1,cosmwasm_1_2,cosmwasm_1_3,cosmwasm_1_4", |
| 304 | + authtypes.NewModuleAddress(govtypes.ModuleName).String(), |
| 305 | + wasmOpts..., |
| 306 | + ) |
| 307 | + wasmParams := wasmtypes.DefaultParams() |
| 308 | + require.NoError(t, wasmKeeper.SetParams(ctx, wasmParams)) |
| 309 | + |
| 310 | + faucet := NewTestFaucet(t, ctx, bankKeeper, authtypes.Minter, initialTotalSupply()...) |
| 311 | + |
| 312 | + // register query service |
| 313 | + am := module.NewManager( // minimal module set that we use for message/ query tests |
| 314 | + oracle.NewAppModule(appCodec, oracleKeeper), |
| 315 | + ) |
| 316 | + am.RegisterServices(module.NewConfigurator(appCodec, msgRouter, queryRouter)) //nolint:errcheck |
| 317 | + |
| 318 | + keepers := TestKeepers{ |
| 319 | + AccountKeeper: accountKeeper, |
| 320 | + WasmKeeper: wasmKeeper, |
| 321 | + BankKeeper: bankKeeper, |
| 322 | + OracleKeeper: oracleKeeper, |
| 323 | + EncodingConfig: encodingConfig, |
| 324 | + Faucet: faucet, |
| 325 | + MultiStore: ms, |
| 326 | + } |
| 327 | + return ctx, keepers |
| 328 | +} |
0 commit comments