github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/x/wasm/module_test.go (about) 1 package wasm 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io/ioutil" 7 "testing" 8 9 "github.com/fibonacci-chain/fbc/x/wasm/keeper/testdata" 10 11 "github.com/dvsekhvalnov/jose2go/base64url" 12 sdk "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types" 13 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types/module" 14 authkeeper "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/auth/keeper" 15 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/bank" 16 bankkeeper "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/bank" 17 abci "github.com/fibonacci-chain/fbc/libs/tendermint/abci/types" 18 "github.com/fibonacci-chain/fbc/libs/tendermint/crypto" 19 "github.com/fibonacci-chain/fbc/libs/tendermint/crypto/ed25519" 20 "github.com/fibonacci-chain/fbc/libs/tendermint/libs/kv" 21 types2 "github.com/fibonacci-chain/fbc/libs/tendermint/types" 22 stakingkeeper "github.com/fibonacci-chain/fbc/x/staking/keeper" 23 "github.com/fibonacci-chain/fbc/x/wasm/keeper" 24 "github.com/fibonacci-chain/fbc/x/wasm/types" 25 "github.com/spf13/viper" 26 "github.com/stretchr/testify/assert" 27 "github.com/stretchr/testify/require" 28 ) 29 30 var zeroCoins sdk.Coins 31 32 type testData struct { 33 module module.AppModule 34 ctx sdk.Context 35 acctKeeper authkeeper.AccountKeeper 36 keeper Keeper 37 bankKeeper bankkeeper.Keeper 38 stakingKeeper stakingkeeper.Keeper 39 faucet *keeper.TestFaucet 40 } 41 42 func setupTest(t *testing.T) testData { 43 ctx, keepers := CreateTestInput(t, false, SupportedFeatures) 44 cdc := keeper.MakeTestCodec(t) 45 data := testData{ 46 module: NewAppModule(cdc, keepers.WasmKeeper), 47 ctx: ctx, 48 acctKeeper: keepers.AccountKeeper, 49 keeper: *keepers.WasmKeeper, 50 bankKeeper: keepers.BankKeeper, 51 stakingKeeper: keepers.StakingKeeper, 52 faucet: keepers.Faucet, 53 } 54 return data 55 } 56 57 func keyPubAddr() (crypto.PrivKey, crypto.PubKey, sdk.AccAddress) { 58 key := ed25519.GenPrivKey() 59 pub := key.PubKey() 60 addr := sdk.AccAddress(pub.Address()) 61 return key, pub, addr 62 } 63 64 func mustLoad(path string) []byte { 65 bz, err := ioutil.ReadFile(path) 66 if err != nil { 67 panic(err) 68 } 69 return bz 70 } 71 72 var ( 73 _, _, addrAcc1 = keyPubAddr() 74 addr1 = addrAcc1.String() 75 testContract = mustLoad("./keeper/testdata/hackatom.wasm") 76 maskContract = testdata.ReflectContractWasm() 77 oldContract = mustLoad("./testdata/escrow_0.7.wasm") 78 ) 79 80 func TestHandleCreate(t *testing.T) { 81 types2.UnittestOnlySetMilestoneEarthHeight(1) 82 cases := map[string]struct { 83 msg sdk.Msg 84 isValid bool 85 }{ 86 "empty": { 87 msg: &MsgStoreCode{}, 88 isValid: false, 89 }, 90 "invalid wasm": { 91 msg: &MsgStoreCode{ 92 Sender: addr1, 93 WASMByteCode: []byte("foobar"), 94 }, 95 isValid: false, 96 }, 97 "valid wasm": { 98 msg: &MsgStoreCode{ 99 Sender: addr1, 100 WASMByteCode: testContract, 101 }, 102 isValid: true, 103 }, 104 "other valid wasm": { 105 msg: &MsgStoreCode{ 106 Sender: addr1, 107 WASMByteCode: maskContract, 108 }, 109 isValid: true, 110 }, 111 "old wasm (0.7)": { 112 msg: &MsgStoreCode{ 113 Sender: addr1, 114 WASMByteCode: oldContract, 115 }, 116 isValid: false, 117 }, 118 } 119 120 for name, tc := range cases { 121 tc := tc 122 t.Run(name, func(t *testing.T) { 123 data := setupTest(t) 124 125 h := data.module.NewHandler() 126 q := data.module.NewQuerierHandler() 127 128 res, err := h(data.ctx, tc.msg) 129 if !tc.isValid { 130 require.Error(t, err, "%#v", res) 131 assertCodeList(t, q, data.ctx, 0) 132 assertCodeBytes(t, q, data.ctx, 1, nil) 133 return 134 } 135 require.NoError(t, err) 136 assertCodeList(t, q, data.ctx, 1) 137 }) 138 } 139 } 140 141 type initMsg struct { 142 Verifier sdk.AccAddress `json:"verifier"` 143 Beneficiary sdk.AccAddress `json:"beneficiary"` 144 } 145 146 type state struct { 147 Verifier string `json:"verifier"` 148 Beneficiary string `json:"beneficiary"` 149 Funder string `json:"funder"` 150 } 151 152 func TestHandleInstantiate(t *testing.T) { 153 types2.UnittestOnlySetMilestoneEarthHeight(1) 154 data := setupTest(t) 155 creator := data.faucet.NewFundedAccount(data.ctx, sdk.NewInt64Coin("denom", 100000)) 156 157 h := data.module.NewHandler() 158 q := data.module.NewQuerierHandler() 159 160 msg := &MsgStoreCode{ 161 Sender: creator.String(), 162 WASMByteCode: testContract, 163 } 164 res, err := h(data.ctx, msg) 165 require.NoError(t, err) 166 assertStoreCodeResponse(t, res.Data, 1) 167 168 _, _, bob := keyPubAddr() 169 _, _, fred := keyPubAddr() 170 171 initMsg := initMsg{ 172 Verifier: fred, 173 Beneficiary: bob, 174 } 175 initMsgBz, err := json.Marshal(initMsg) 176 require.NoError(t, err) 177 178 // create with no balance is also legal 179 initCmd := MsgInstantiateContract{ 180 Sender: creator.String(), 181 CodeID: firstCodeID, 182 Msg: initMsgBz, 183 Funds: nil, 184 } 185 res, err = h(data.ctx, &initCmd) 186 require.NoError(t, err) 187 contractBech32Addr := parseInitResponse(t, res.Data) 188 189 require.Equal(t, "cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr", contractBech32Addr) 190 // this should be standard x/wasm init event, nothing from contract 191 require.Equal(t, 3, len(res.Events), prettyEvents(res.Events)) 192 require.Equal(t, "message", res.Events[0].Type) 193 assertAttribute(t, "module", "wasm", res.Events[0].Attributes[0]) 194 require.Equal(t, "instantiate", res.Events[1].Type) 195 require.Equal(t, "wasm", res.Events[2].Type) 196 assertAttribute(t, "_contract_address", contractBech32Addr, res.Events[2].Attributes[0]) 197 198 assertCodeList(t, q, data.ctx, 1) 199 assertCodeBytes(t, q, data.ctx, 1, testContract) 200 201 assertContractList(t, q, data.ctx, 1, []string{contractBech32Addr}) 202 assertContractInfo(t, q, data.ctx, contractBech32Addr, 1, creator) 203 assertContractState(t, q, data.ctx, contractBech32Addr, state{ 204 Verifier: fred.String(), 205 Beneficiary: bob.String(), 206 Funder: creator.String(), 207 }) 208 } 209 210 func TestHandleExecute(t *testing.T) { 211 types2.UnittestOnlySetMilestoneEarthHeight(1) 212 data := setupTest(t) 213 types2.UnittestOnlySetMilestoneEarthHeight(1) 214 deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) 215 topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 5000)) 216 217 creator := data.faucet.NewFundedAccount(data.ctx, deposit.Add(deposit...)...) 218 fred := data.faucet.NewFundedAccount(data.ctx, topUp...) 219 220 h := data.module.NewHandler() 221 q := data.module.NewQuerierHandler() 222 223 msg := &MsgStoreCode{ 224 Sender: creator.String(), 225 WASMByteCode: testContract, 226 } 227 res, err := h(data.ctx, msg) 228 require.NoError(t, err) 229 assertStoreCodeResponse(t, res.Data, 1) 230 231 _, _, bob := keyPubAddr() 232 initMsg := initMsg{ 233 Verifier: fred, 234 Beneficiary: bob, 235 } 236 initMsgBz, err := json.Marshal(initMsg) 237 require.NoError(t, err) 238 239 initCmd := MsgInstantiateContract{ 240 Sender: creator.String(), 241 CodeID: firstCodeID, 242 Msg: initMsgBz, 243 Funds: sdk.CoinsToCoinAdapters(deposit), 244 } 245 res, err = h(data.ctx, &initCmd) 246 require.NoError(t, err) 247 contractBech32Addr := parseInitResponse(t, res.Data) 248 249 require.Equal(t, "cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr", contractBech32Addr) 250 // this should be standard x/wasm message event, init event, plus a bank send event (2), with no custom contract events 251 require.Equal(t, 3, len(res.Events), prettyEvents(res.Events)) 252 require.Equal(t, "message", res.Events[0].Type) 253 assertAttribute(t, "module", "wasm", res.Events[0].Attributes[0]) 254 require.Equal(t, "instantiate", res.Events[1].Type) 255 require.Equal(t, "wasm", res.Events[2].Type) 256 assertAttribute(t, "_contract_address", contractBech32Addr, res.Events[2].Attributes[0]) 257 258 // ensure bob doesn't exist 259 bobAcct := data.acctKeeper.GetAccount(data.ctx, bob) 260 require.Nil(t, bobAcct) 261 262 // ensure funder has reduced balance 263 creatorAcct := data.acctKeeper.GetAccount(data.ctx, creator) 264 require.NotNil(t, creatorAcct) 265 // we started at 2*deposit, should have spent one above 266 assert.Equal(t, deposit, bank.NewBankKeeperAdapter(data.bankKeeper).GetAllBalances(data.ctx, creatorAcct.GetAddress())) 267 268 // ensure contract has updated balance 269 contractAddr, _ := sdk.AccAddressFromBech32(contractBech32Addr) 270 contractAcct := data.acctKeeper.GetAccount(data.ctx, contractAddr) 271 require.NotNil(t, contractAcct) 272 assert.Equal(t, deposit, bank.NewBankKeeperAdapter(data.bankKeeper).GetAllBalances(data.ctx, contractAcct.GetAddress())) 273 274 execCmd := MsgExecuteContract{ 275 Sender: fred.String(), 276 Contract: contractBech32Addr, 277 Msg: []byte(`{"release":{}}`), 278 Funds: sdk.CoinsToCoinAdapters(topUp), 279 } 280 res, err = h(data.ctx, &execCmd) 281 require.NoError(t, err) 282 // from https://github.com/CosmWasm/cosmwasm/blob/master/contracts/hackatom/src/contract.rs#L167 283 assertExecuteResponse(t, res.Data, []byte{0xf0, 0x0b, 0xaa}) 284 285 // this should be standard message event, plus x/wasm init event, plus 2 bank send event, plus a special event from the contract 286 require.Equal(t, 5, len(res.Events), prettyEvents(res.Events)) 287 288 assert.Equal(t, "message", res.Events[0].Type) 289 assertAttribute(t, "module", "wasm", res.Events[0].Attributes[0]) 290 291 assert.Equal(t, "execute", res.Events[1].Type) 292 293 // custom contract event attribute 294 assert.Equal(t, "wasm", res.Events[2].Type) 295 assertAttribute(t, "_contract_address", contractBech32Addr, res.Events[2].Attributes[0]) 296 assertAttribute(t, "action", "release", res.Events[2].Attributes[1]) 297 // custom contract event 298 assert.Equal(t, "wasm-hackatom", res.Events[3].Type) 299 assertAttribute(t, "_contract_address", contractBech32Addr, res.Events[3].Attributes[0]) 300 assertAttribute(t, "action", "release", res.Events[3].Attributes[1]) 301 // second transfer (this without conflicting message) 302 assert.Equal(t, "transfer", res.Events[4].Type) 303 assertAttribute(t, "recipient", bob.String(), res.Events[4].Attributes[0]) 304 assertAttribute(t, "sender", contractBech32Addr, res.Events[4].Attributes[1]) 305 assertAttribute(t, "amount", "105000.000000000000000000denom", res.Events[4].Attributes[2]) 306 // finally, standard x/wasm tag 307 308 // ensure bob now exists and got both payments released 309 bobAcct = data.acctKeeper.GetAccount(data.ctx, bob) 310 require.NotNil(t, bobAcct) 311 balance := bank.NewBankKeeperAdapter(data.bankKeeper).GetAllBalances(data.ctx, bobAcct.GetAddress()) 312 assert.Equal(t, deposit.Add(topUp...), balance) 313 314 // ensure contract has updated balance 315 316 contractAcct = data.acctKeeper.GetAccount(data.ctx, contractAddr) 317 require.NotNil(t, contractAcct) 318 assert.Equal(t, zeroCoins, bank.NewBankKeeperAdapter(data.bankKeeper).GetAllBalances(data.ctx, contractAcct.GetAddress())) 319 320 // ensure all contract state is as after init 321 assertCodeList(t, q, data.ctx, 1) 322 assertCodeBytes(t, q, data.ctx, 1, testContract) 323 324 assertContractList(t, q, data.ctx, 1, []string{contractBech32Addr}) 325 assertContractInfo(t, q, data.ctx, contractBech32Addr, 1, creator) 326 assertContractState(t, q, data.ctx, contractBech32Addr, state{ 327 Verifier: fred.String(), 328 Beneficiary: bob.String(), 329 Funder: creator.String(), 330 }) 331 } 332 333 func TestHandleExecuteEscrow(t *testing.T) { 334 types2.UnittestOnlySetMilestoneEarthHeight(1) 335 data := setupTest(t) 336 types2.UnittestOnlySetMilestoneEarthHeight(1) 337 deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) 338 topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 5000)) 339 creator := data.faucet.NewFundedAccount(data.ctx, deposit.Add(deposit...)...) 340 fred := data.faucet.NewFundedAccount(data.ctx, topUp...) 341 342 h := data.module.NewHandler() 343 344 msg := &MsgStoreCode{ 345 Sender: creator.String(), 346 WASMByteCode: testContract, 347 } 348 res, err := h(data.ctx, msg) 349 require.NoError(t, err) 350 351 _, _, bob := keyPubAddr() 352 initMsg := map[string]interface{}{ 353 "verifier": fred.String(), 354 "beneficiary": bob.String(), 355 } 356 initMsgBz, err := json.Marshal(initMsg) 357 require.NoError(t, err) 358 359 initCmd := MsgInstantiateContract{ 360 Sender: creator.String(), 361 CodeID: firstCodeID, 362 Msg: initMsgBz, 363 Funds: sdk.CoinsToCoinAdapters(deposit), 364 } 365 res, err = h(data.ctx, &initCmd) 366 require.NoError(t, err) 367 contractBech32Addr := parseInitResponse(t, res.Data) 368 require.Equal(t, "cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr", contractBech32Addr) 369 370 handleMsg := map[string]interface{}{ 371 "release": map[string]interface{}{}, 372 } 373 handleMsgBz, err := json.Marshal(handleMsg) 374 require.NoError(t, err) 375 376 execCmd := MsgExecuteContract{ 377 Sender: fred.String(), 378 Contract: contractBech32Addr, 379 Msg: handleMsgBz, 380 Funds: sdk.CoinsToCoinAdapters(topUp), 381 } 382 res, err = h(data.ctx, &execCmd) 383 require.NoError(t, err) 384 // from https://github.com/CosmWasm/cosmwasm/blob/master/contracts/hackatom/src/contract.rs#L167 385 assertExecuteResponse(t, res.Data, []byte{0xf0, 0x0b, 0xaa}) 386 387 // ensure bob now exists and got both payments released 388 bobAcct := data.acctKeeper.GetAccount(data.ctx, bob) 389 require.NotNil(t, bobAcct) 390 balance := bank.NewBankKeeperAdapter(data.bankKeeper).GetAllBalances(data.ctx, bobAcct.GetAddress()) 391 assert.Equal(t, deposit.Add(topUp...), balance) 392 393 // ensure contract has updated balance 394 contractAddr, _ := sdk.AccAddressFromBech32(contractBech32Addr) 395 contractAcct := data.acctKeeper.GetAccount(data.ctx, contractAddr) 396 require.NotNil(t, contractAcct) 397 assert.Equal(t, zeroCoins, bank.NewBankKeeperAdapter(data.bankKeeper).GetAllBalances(data.ctx, contractAcct.GetAddress())) 398 } 399 400 func TestReadWasmConfig(t *testing.T) { 401 defaults := DefaultWasmConfig() 402 specs := map[string]struct { 403 src AppOptionsMock 404 exp types.WasmConfig 405 }{ 406 "set query gas limit via opts": { 407 src: AppOptionsMock{ 408 "wasm.query_gas_limit": 1, 409 }, 410 exp: types.WasmConfig{ 411 SimulationGasLimit: defaults.SimulationGasLimit, 412 SmartQueryGasLimit: 1, 413 MemoryCacheSize: defaults.MemoryCacheSize, 414 }, 415 }, 416 "set cache via opts": { 417 src: AppOptionsMock{ 418 "wasm.memory_cache_size": 2, 419 }, 420 exp: types.WasmConfig{ 421 SimulationGasLimit: defaults.SimulationGasLimit, 422 MemoryCacheSize: 2, 423 SmartQueryGasLimit: defaults.SmartQueryGasLimit, 424 }, 425 }, 426 "set debug via opts": { 427 src: AppOptionsMock{ 428 "trace": true, 429 }, 430 exp: types.WasmConfig{ 431 SimulationGasLimit: defaults.SimulationGasLimit, 432 SmartQueryGasLimit: defaults.SmartQueryGasLimit, 433 MemoryCacheSize: defaults.MemoryCacheSize, 434 ContractDebugMode: true, 435 }, 436 }, 437 "all defaults when no options set": { 438 exp: defaults, 439 }, 440 } 441 for msg, spec := range specs { 442 t.Run(msg, func(t *testing.T) { 443 viper.Reset() 444 for k, v := range spec.src { 445 viper.Set(k, v) 446 } 447 got, err := ReadWasmConfig() 448 require.NoError(t, err) 449 assert.Equal(t, spec.exp, got) 450 viper.Reset() 451 }) 452 } 453 } 454 455 type AppOptionsMock map[string]interface{} 456 457 func (a AppOptionsMock) Get(s string) interface{} { 458 return a[s] 459 } 460 461 type prettyEvent struct { 462 Type string 463 Attr []sdk.Attribute 464 } 465 466 func prettyEvents(evts []sdk.Event) string { 467 res := make([]prettyEvent, len(evts)) 468 for i, e := range evts { 469 res[i] = prettyEvent{ 470 Type: e.Type, 471 Attr: prettyAttrs(e.Attributes), 472 } 473 } 474 bz, err := json.MarshalIndent(res, "", " ") 475 if err != nil { 476 panic(err) 477 } 478 return string(bz) 479 } 480 481 func prettyAttrs(attrs []kv.Pair) []sdk.Attribute { 482 pretty := make([]sdk.Attribute, len(attrs)) 483 for i, a := range attrs { 484 pretty[i] = prettyAttr(a) 485 } 486 return pretty 487 } 488 489 func prettyAttr(attr kv.Pair) sdk.Attribute { 490 return sdk.NewAttribute(string(attr.Key), string(attr.Value)) 491 } 492 493 func assertAttribute(t *testing.T, key string, value string, attr kv.Pair) { 494 t.Helper() 495 assert.Equal(t, key, string(attr.Key), prettyAttr(attr)) 496 assert.Equal(t, value, string(attr.Value), prettyAttr(attr)) 497 } 498 499 func assertCodeList(t *testing.T, q sdk.Querier, ctx sdk.Context, expectedNum int) { 500 bz, sdkerr := q(ctx, []string{QueryListCode}, abci.RequestQuery{}) 501 require.NoError(t, sdkerr) 502 503 if len(bz) == 0 { 504 require.Equal(t, expectedNum, 0) 505 return 506 } 507 508 var res []CodeInfo 509 err := json.Unmarshal(bz, &res) 510 require.NoError(t, err) 511 512 assert.Equal(t, expectedNum, len(res)) 513 } 514 515 func assertCodeBytes(t *testing.T, q sdk.Querier, ctx sdk.Context, codeID uint64, expectedBytes []byte) { 516 path := []string{QueryGetCode, fmt.Sprintf("%d", codeID)} 517 bz, sdkerr := q(ctx, path, abci.RequestQuery{}) 518 require.NoError(t, sdkerr) 519 520 if len(expectedBytes) == 0 { 521 require.Equal(t, len(bz), 0, "%q", string(bz)) 522 return 523 } 524 var res map[string]interface{} 525 err := json.Unmarshal(bz, &res) 526 require.NoError(t, err) 527 528 require.Contains(t, res, "data") 529 b, err := base64url.Decode(res["data"].(string)) 530 require.NoError(t, err) 531 assert.Equal(t, expectedBytes, b) 532 assert.EqualValues(t, codeID, res["id"]) 533 } 534 535 func assertContractList(t *testing.T, q sdk.Querier, ctx sdk.Context, codeID uint64, expContractAddrs []string) { 536 bz, sdkerr := q(ctx, []string{QueryListContractByCode, fmt.Sprintf("%d", codeID)}, abci.RequestQuery{}) 537 require.NoError(t, sdkerr) 538 539 if len(bz) == 0 { 540 require.Equal(t, len(expContractAddrs), 0) 541 return 542 } 543 544 var res []string 545 err := json.Unmarshal(bz, &res) 546 require.NoError(t, err) 547 548 hasAddrs := make([]string, len(res)) 549 for i, r := range res { 550 hasAddrs[i] = r 551 } 552 553 assert.Equal(t, expContractAddrs, hasAddrs) 554 } 555 556 func assertContractState(t *testing.T, q sdk.Querier, ctx sdk.Context, contractBech32Addr string, expected state) { 557 t.Helper() 558 path := []string{QueryGetContractState, contractBech32Addr, keeper.QueryMethodContractStateAll} 559 bz, sdkerr := q(ctx, path, abci.RequestQuery{}) 560 require.NoError(t, sdkerr) 561 562 var res []Model 563 err := json.Unmarshal(bz, &res) 564 require.NoError(t, err) 565 require.Equal(t, 1, len(res), "#v", res) 566 require.Equal(t, []byte("config"), []byte(res[0].Key)) 567 568 expectedBz, err := json.Marshal(expected) 569 require.NoError(t, err) 570 assert.Equal(t, expectedBz, res[0].Value) 571 } 572 573 func assertContractInfo(t *testing.T, q sdk.Querier, ctx sdk.Context, contractBech32Addr string, codeID uint64, creator sdk.AccAddress) { 574 t.Helper() 575 path := []string{QueryGetContract, contractBech32Addr} 576 bz, sdkerr := q(ctx, path, abci.RequestQuery{}) 577 require.NoError(t, sdkerr) 578 579 var res ContractInfo 580 err := json.Unmarshal(bz, &res) 581 require.NoError(t, err) 582 583 assert.Equal(t, codeID, res.CodeID) 584 assert.Equal(t, creator.String(), res.Creator) 585 }