diff --git a/app/app.go b/app/app.go index a5810ef0f..900ea70c4 100644 --- a/app/app.go +++ b/app/app.go @@ -590,7 +590,14 @@ func New( } wasmOpts := []wasmkeeper.Option{ - wasmkeeper.WithAcceptedAccountTypesOnContractInstantiation(), + wasmkeeper.WithAcceptedAccountTypesOnContractInstantiation( + &authtypes.BaseAccount{}, + &vestingtypes.ContinuousVestingAccount{}, + &vestingtypes.DelayedVestingAccount{}, + &vestingtypes.PeriodicVestingAccount{}, + &vestingtypes.BaseVestingAccount{}, + &vestingtypes.PermanentLockedAccount{}, + ), wasmkeeper.WithAccountPruner(cwasmtypes.AccountPruner{}), wasmkeeper.WithCoinTransferrer(cwasmtypes.NewBankCoinTransferrer(app.BankKeeper)), wasmkeeper.WithMessageHandler(wasmcustomhandler.NewMessengerWrapper(wasmkeeper.NewDefaultMessageHandler( diff --git a/integration-tests/modules/wasm_test.go b/integration-tests/modules/wasm_test.go index 375aa89e5..230b68648 100644 --- a/integration-tests/modules/wasm_test.go +++ b/integration-tests/modules/wasm_test.go @@ -2118,12 +2118,12 @@ func TestWASMBankSendContractWithMultipleFundsAttached(t *testing.T) { // TestWASMContractInstantiationIsRejectedIfThereAreTokensOnItsAccount verifies that smart contract instantiation // is rejected if account exists. -func TestWASMContractInstantiationIsRejectedIfAccountExists(t *testing.T) { +func TestWASMContractInstantiationIsNotRejectedIfAccountExists(t *testing.T) { t.Parallel() ctx, chain := integrationtests.NewCoreumTestingContext(t) - admin := chain.GenAccount() + bankClient := banktypes.NewQueryClient(chain.ClientContext) requireT := require.New(t) chain.Faucet.FundAccounts(ctx, t, @@ -2159,18 +2159,19 @@ func TestWASMContractInstantiationIsRejectedIfAccountExists(t *testing.T) { // Send coins to the contract address before instantiation. + amount := chain.NewCoin(sdkmath.NewInt(500)) msg := &banktypes.MsgSend{ FromAddress: admin.String(), ToAddress: contract.String(), - Amount: sdk.NewCoins(chain.NewCoin(sdkmath.NewInt(500))), + Amount: sdk.NewCoins(amount), } _, err = client.BroadcastTx(ctx, clientCtx.WithFromAddress(admin), txf, msg) requireT.NoError(err) - // Instantiate the smart contract. It should fail because its account holds some funds. + // Instantiate the smart contract. - _, err = chain.Wasm.InstantiateWASMContract( + contractAddr, err := chain.Wasm.InstantiateWASMContract( ctx, txf, admin, @@ -2182,7 +2183,24 @@ func TestWASMContractInstantiationIsRejectedIfAccountExists(t *testing.T) { Label: "bank_send", }, ) - requireT.ErrorContains(err, "contract account already exists") + requireT.NoError(err) + requireT.Equal(contractAddr, contract.String()) + + qres, err := bankClient.Balance(ctx, &banktypes.QueryBalanceRequest{ + Address: contractAddr, + Denom: amount.Denom, + }) + requireT.NoError(err) + requireT.Equal(amount.String(), qres.Balance.String()) + + // Verify account type. + + authClient := authtypes.NewQueryClient(chain.ClientContext) + accountRes, err := authClient.Account(ctx, &authtypes.QueryAccountRequest{ + Address: contractAddr, + }) + requireT.NoError(err) + requireT.Equal("/cosmos.auth.v1beta1.BaseAccount", accountRes.Account.TypeUrl) // Predict the address of another smart contract. @@ -2197,16 +2215,81 @@ func TestWASMContractInstantiationIsRejectedIfAccountExists(t *testing.T) { ) requireT.NoError(err) - // Create vesting account using address of the smart cotntract. + // Create delayed vesting account using address of the smart contract. + amount = chain.NewCoin(sdkmath.NewInt(600)) createVestingAccMsg := &vestingtypes.MsgCreateVestingAccount{ FromAddress: admin.String(), ToAddress: contract.String(), - Amount: sdk.NewCoins( - chain.NewCoin(sdkmath.NewInt(10000)), - ), - EndTime: time.Now().Unix(), - Delayed: true, + Amount: sdk.NewCoins(amount), + EndTime: time.Now().Unix(), + Delayed: true, + } + + _, err = client.BroadcastTx( + ctx, + chain.ClientContext.WithFromAddress(admin), + chain.TxFactory().WithGas(chain.GasLimitByMsgs(createVestingAccMsg)), + createVestingAccMsg, + ) + requireT.NoError(err) + + // Await next block to ensure that funds are vested. + + requireT.NoError(client.AwaitNextBlocks(ctx, clientCtx, 1)) + + qres, err = bankClient.Balance(ctx, &banktypes.QueryBalanceRequest{ + Address: contract.String(), + Denom: amount.Denom, + }) + requireT.NoError(err) + requireT.Equal(amount.String(), qres.Balance.String()) + + // Instantiate the smart contract. + + contractAddr, err = chain.Wasm.InstantiateWASMContract( + ctx, + txf, + admin, + salt, + integration.InstantiateConfig{ + CodeID: codeID, + AccessType: wasmtypes.AccessTypeUnspecified, + Payload: moduleswasm.EmptyPayload, + Label: "bank_send", + }, + ) + requireT.NoError(err) + requireT.Equal(contractAddr, contract.String()) + + qres, err = bankClient.Balance(ctx, &banktypes.QueryBalanceRequest{ + Address: contractAddr, + Denom: amount.Denom, + }) + requireT.NoError(err) + requireT.Equal(amount.String(), qres.Balance.String()) + + // Predict the address of another smart contract. + + salt, err = chain.Wasm.GenerateSalt() + requireT.NoError(err) + + contract, err = chain.Wasm.PredictWASMContractAddress( + ctx, + admin, + salt, + codeID, + ) + requireT.NoError(err) + + // Create continuous vesting account using address of the smart contract. + + amount = chain.NewCoin(sdkmath.NewInt(700)) + createVestingAccMsg = &vestingtypes.MsgCreateVestingAccount{ + FromAddress: admin.String(), + ToAddress: contract.String(), + Amount: sdk.NewCoins(amount), + EndTime: time.Now().Unix(), } _, err = client.BroadcastTx( @@ -2221,9 +2304,16 @@ func TestWASMContractInstantiationIsRejectedIfAccountExists(t *testing.T) { requireT.NoError(client.AwaitNextBlocks(ctx, clientCtx, 1)) - // Instantiate the smart contract. It should fail because its account holds some funds. + qres, err = bankClient.Balance(ctx, &banktypes.QueryBalanceRequest{ + Address: contract.String(), + Denom: amount.Denom, + }) + requireT.NoError(err) + requireT.Equal(amount.String(), qres.Balance.String()) + + // Instantiate the smart contract. - _, err = chain.Wasm.InstantiateWASMContract( + contractAddr, err = chain.Wasm.InstantiateWASMContract( ctx, txf, admin, @@ -2235,7 +2325,140 @@ func TestWASMContractInstantiationIsRejectedIfAccountExists(t *testing.T) { Label: "bank_send", }, ) - requireT.ErrorContains(err, "contract account already exists") + requireT.NoError(err) + requireT.Equal(contractAddr, contract.String()) + + qres, err = bankClient.Balance(ctx, &banktypes.QueryBalanceRequest{ + Address: contractAddr, + Denom: amount.Denom, + }) + requireT.NoError(err) + requireT.Equal(amount.String(), qres.Balance.String()) +} + +// TestVestingToWASMContract verifies that smart contract instantiated on top of vesting accunt receives funds correctly. +func TestVestingToWASMContract(t *testing.T) { + t.Parallel() + + ctx, chain := integrationtests.NewCoreumTestingContext(t) + admin := chain.GenAccount() + recipient := chain.GenAccount() + amount := chain.NewCoin(sdkmath.NewInt(500)) + + requireT := require.New(t) + chain.Faucet.FundAccounts(ctx, t, + integration.NewFundedAccount(admin, chain.NewCoin(sdkmath.NewInt(5000000000))), + ) + + txf := chain.TxFactory(). + WithSimulateAndExecute(true) + + // Deploy smart contract. + + codeID, err := chain.Wasm.DeployWASMContract( + ctx, + txf, + admin, + moduleswasm.BankSendWASM, + ) + requireT.NoError(err) + + // Predict the address of smart contract. + + salt, err := chain.Wasm.GenerateSalt() + requireT.NoError(err) + + contract, err := chain.Wasm.PredictWASMContractAddress( + ctx, + admin, + salt, + codeID, + ) + requireT.NoError(err) + + // Create vesting account using address of the smart contract. + + vestingDuration := 30 * time.Second + createVestingAccMsg := &vestingtypes.MsgCreateVestingAccount{ + FromAddress: admin.String(), + ToAddress: contract.String(), + Amount: sdk.NewCoins(amount), + EndTime: time.Now().Add(vestingDuration).Unix(), + Delayed: true, + } + + _, err = client.BroadcastTx( + ctx, + chain.ClientContext.WithFromAddress(admin), + chain.TxFactory().WithGas(chain.GasLimitByMsgs(createVestingAccMsg)), + createVestingAccMsg, + ) + requireT.NoError(err) + + // Instantiate the smart contract. + + contractAddr, err := chain.Wasm.InstantiateWASMContract( + ctx, + txf, + admin, + salt, + integration.InstantiateConfig{ + CodeID: codeID, + AccessType: wasmtypes.AccessTypeUnspecified, + Payload: moduleswasm.EmptyPayload, + Label: "bank_send", + }, + ) + requireT.NoError(err) + + // Check that this is still a vesting account. + + authClient := authtypes.NewQueryClient(chain.ClientContext) + accountRes, err := authClient.Account(ctx, &authtypes.QueryAccountRequest{ + Address: contractAddr, + }) + requireT.NoError(err) + requireT.Equal("/cosmos.vesting.v1beta1.DelayedVestingAccount", accountRes.Account.TypeUrl) + + // Verify that funds hasn't been vested yes. + + _, err = chain.Wasm.ExecuteWASMContract( + ctx, + txf, + admin, + contractAddr, + moduleswasm.BankSendExecuteWithdrawRequest(amount, recipient), + sdk.Coin{}) + requireT.ErrorContains(err, "insufficient funds") + + // Await vesting time to unlock the vesting coins + + select { + case <-ctx.Done(): + return + case <-time.After(vestingDuration): + } + + // Verify funds are there. + + bankClient := banktypes.NewQueryClient(chain.ClientContext) + qres, err := bankClient.Balance(ctx, &banktypes.QueryBalanceRequest{ + Address: contractAddr, + Denom: amount.Denom, + }) + requireT.NoError(err) + requireT.Equal(amount.String(), qres.Balance.String()) + + // Verify that funds has been vested. + + _, err = chain.Wasm.ExecuteWASMContract( + ctx, + txf, + admin, + contractAddr, + moduleswasm.BankSendExecuteWithdrawRequest(amount, recipient), + sdk.Coin{}) + requireT.NoError(err) } func randStringWithLength(n int) string { diff --git a/x/wasm/types/prunner.go b/x/wasm/types/prunner.go index a28652bca..94a15a1e2 100644 --- a/x/wasm/types/prunner.go +++ b/x/wasm/types/prunner.go @@ -11,5 +11,5 @@ type AccountPruner struct{} // CleanupExistingAccount informs wasm module to reject smart contract instantiation if account exists. func (ap AccountPruner) CleanupExistingAccount(_ sdk.Context, _ authtypes.AccountI) (bool, error) { - return false, nil + return true, nil }