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

Stop rejecting contract instantiation if account holds funds #738

Closed
wants to merge 5 commits into from
Closed
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
9 changes: 8 additions & 1 deletion app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
253 changes: 238 additions & 15 deletions integration-tests/modules/wasm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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.

Expand All @@ -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(
Expand All @@ -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,
Expand All @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion x/wasm/types/prunner.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Loading