Migrate contract backend functionality with go-cosmwasm stub impl (#122)

* Start migration server side

* Return migration response and emit events

* Dispatch migrate contract messages

* Rebase to 0.9 and minor updates

* Review feedback

* Update changelog

* Add msg test
This commit is contained in:
Alexander Peters
2020-06-04 08:54:30 +02:00
committed by GitHub
parent bc76bb0b48
commit 81d8560d41
16 changed files with 359 additions and 24 deletions

View File

@@ -37,6 +37,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
## [Unreleased]
### Features
* (wasmd) [\#122](https://github.com/CosmWasm/wasmd/pull/122]) Migrate contract backend functionality with go-cosmwasm stub impl
* (wasmd)[\#2](https://github.com/cosmwasm/wasmd/pull/22) Improve wasm contract queries (all, raw, smart)
* (wasmd) [\#119](https://github.com/cosmwasm/wasmd/pull/119) Add support for the `--inter-block-cache` CLI
flag and configuration.

2
go.mod
View File

@@ -23,7 +23,9 @@ require (
github.com/tendermint/go-amino v0.15.1
github.com/tendermint/tendermint v0.33.3
github.com/tendermint/tm-db v0.5.1
go.etcd.io/bbolt v1.3.4 // indirect
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 // indirect
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 // indirect
)
replace github.com/keybase/go-keychain => github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4

7
go.sum
View File

@@ -9,8 +9,6 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/ChainSafe/go-schnorrkel v0.0.0-20200102211924-4bcbc698314f h1:4O1om+UVU+Hfcihr1timk8YNXHxzZWgCo7ofnrZRApw=
github.com/ChainSafe/go-schnorrkel v0.0.0-20200102211924-4bcbc698314f/go.mod h1:URdX5+vg25ts3aCh8H5IFZybJYKWhJHYMTnf+ULtoC4=
github.com/CosmWasm/go-cosmwasm v0.0.0-20200603080847-883f85520aac h1:Em+R1ZTKgnIT6bYhEqk8/e1IXuZDXvs7FyuUktNzWtg=
github.com/CosmWasm/go-cosmwasm v0.0.0-20200603080847-883f85520aac/go.mod h1:gAFCwllx97ejI+m9SqJQrmd2SBW7HA0fOjvWWJjM2uc=
github.com/CosmWasm/go-cosmwasm v0.8.1-0.20200603124627-0af410d57fa1 h1:yTh6KEZXpVtjkhRQQSKOpS4YbRNIz7VFcace5czQkDw=
github.com/CosmWasm/go-cosmwasm v0.8.1-0.20200603124627-0af410d57fa1/go.mod h1:gAFCwllx97ejI+m9SqJQrmd2SBW7HA0fOjvWWJjM2uc=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
@@ -460,6 +458,8 @@ github.com/zondax/hid v0.9.0/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWp
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg=
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
@@ -537,6 +537,9 @@ golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtD
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82 h1:ywK/j/KkyTHcdyYSZNXGjMwgmDSfjglYZ3vStQ/gSCU=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 h1:OjiUf46hAmXblsZdnoSXsEUSKU8r1UEzcL5RVZ4gO9Y=
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=

View File

@@ -93,6 +93,7 @@ type (
MsgStoreCode = types.MsgStoreCode
MsgInstantiateContract = types.MsgInstantiateContract
MsgExecuteContract = types.MsgExecuteContract
MsgMigrateContract = types.MsgMigrateContract
Model = types.Model
CodeInfo = types.CodeInfo
ContractInfo = types.ContractInfo

View File

@@ -34,6 +34,11 @@ func NewHandler(k Keeper) sdk.Handler {
case *MsgExecuteContract:
return handleExecute(ctx, k, msg)
case *MsgMigrateContract:
return handleMigration(ctx, k, msg)
case MsgMigrateContract:
return handleMigration(ctx, k, &msg)
default:
errMsg := fmt.Sprintf("unrecognized wasm message type: %T", msg)
return nil, sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, errMsg)
@@ -80,7 +85,7 @@ func handleStoreCode(ctx sdk.Context, k Keeper, msg *MsgStoreCode) (*sdk.Result,
}
func handleInstantiate(ctx sdk.Context, k Keeper, msg *MsgInstantiateContract) (*sdk.Result, error) {
contractAddr, err := k.Instantiate(ctx, msg.Code, msg.Sender, msg.InitMsg, msg.Label, msg.InitFunds)
contractAddr, err := k.Instantiate(ctx, msg.Code, msg.Sender, msg.Admin, msg.InitMsg, msg.Label, msg.InitFunds)
if err != nil {
return nil, err
}
@@ -117,3 +122,20 @@ func handleExecute(ctx sdk.Context, k Keeper, msg *MsgExecuteContract) (*sdk.Res
res.Events = append(events, ourEvent)
return &res, nil
}
func handleMigration(ctx sdk.Context, k Keeper, msg *MsgMigrateContract) (*sdk.Result, error) {
res, err := k.Migrate(ctx, msg.Contract, msg.Sender, msg.Code, msg.MigrateMsg)
if err != nil {
return nil, err
}
events := filterMessageEvents(ctx.EventManager())
ourEvent := sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, ModuleName),
sdk.NewAttribute(AttributeSigner, msg.Sender.String()),
sdk.NewAttribute(AttributeKeyContract, msg.Contract.String()),
)
res.Events = append(events, ourEvent)
return res, nil
}

View File

@@ -2,9 +2,10 @@ package keeper
import (
"encoding/binary"
"github.com/cosmos/cosmos-sdk/x/staking"
"path/filepath"
"github.com/cosmos/cosmos-sdk/x/staking"
wasm "github.com/CosmWasm/go-cosmwasm"
wasmTypes "github.com/CosmWasm/go-cosmwasm/types"
"github.com/cosmos/cosmos-sdk/codec"
@@ -92,7 +93,7 @@ func isSimulationMode(ctx sdk.Context) bool {
}
// Instantiate creates an instance of a WASM contract
func (k Keeper) Instantiate(ctx sdk.Context, codeID uint64, creator sdk.AccAddress, initMsg []byte, label string, deposit sdk.Coins) (sdk.AccAddress, error) {
func (k Keeper) Instantiate(ctx sdk.Context, codeID uint64, creator, admin sdk.AccAddress, initMsg []byte, label string, deposit sdk.Coins) (sdk.AccAddress, error) {
// create contract address
contractAddress := k.generateContractAddress(ctx, codeID)
existingAcct := k.accountKeeper.GetAccount(ctx, contractAddress)
@@ -155,7 +156,7 @@ func (k Keeper) Instantiate(ctx sdk.Context, codeID uint64, creator sdk.AccAddre
// persist instance
createdAt := types.NewCreatedAt(ctx)
instance := types.NewContractInfo(codeID, creator, initMsg, label, createdAt)
instance := types.NewContractInfo(codeID, creator, admin, initMsg, label, createdAt)
store.Set(types.GetContractAddressKey(contractAddress), k.cdc.MustMarshalBinaryBare(instance))
return contractAddress, nil
@@ -204,6 +205,56 @@ func (k Keeper) Execute(ctx sdk.Context, contractAddress sdk.AccAddress, caller
return value, nil
}
// Migrate allows to upgrade a contract to a new code with data migration.
func (k Keeper) Migrate(ctx sdk.Context, contractAddress sdk.AccAddress, caller sdk.AccAddress, newCodeID uint64, msg []byte) (*sdk.Result, error) {
contractInfo := k.GetContractInfo(ctx, contractAddress)
if contractInfo == nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "unknown contract")
}
if contractInfo.Admin == nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "migration not supported by this contract")
}
if !contractInfo.Admin.Equals(caller) {
return nil, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "no permission")
}
newCodeInfo := k.GetCodeInfo(ctx, newCodeID)
if newCodeInfo == nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "unknown code")
}
var noDeposit sdk.Coins
params := types.NewEnv(ctx, caller, noDeposit, contractAddress)
// prepare querier
querier := QueryHandler{
Ctx: ctx,
Plugins: k.queryPlugins,
}
prefixStoreKey := types.GetContractStorePrefixKey(contractAddress)
prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), prefixStoreKey)
gas := gasForContract(ctx)
res, gasUsed, err := k.wasmer.Migrate(newCodeInfo.CodeHash, params, msg, prefixStore, cosmwasmAPI, querier, ctx.GasMeter(), gas)
consumeGas(ctx, gasUsed)
if err != nil {
return nil, sdkerrors.Wrap(types.ErrMigrationFailed, err.Error())
}
// emit all events from this contract migration itself
value := types.CosmosResult(*res, contractAddress)
ctx.EventManager().EmitEvents(value.Events)
value.Events = nil
contractInfo.UpdateCodeID(ctx, newCodeID)
k.setContractInfo(ctx, contractAddress, *contractInfo)
if err := k.dispatchMessages(ctx, contractAddress, res.Messages); err != nil {
return nil, sdkerrors.Wrap(err, "dispatch")
}
return &value, nil
}
// QuerySmart queries the smart contract itself.
func (k Keeper) QuerySmart(ctx sdk.Context, contractAddr sdk.AccAddress, req []byte) ([]byte, error) {
ctx = ctx.WithGasMeter(sdk.NewGasMeter(k.queryGasLimit))

View File

@@ -1,6 +1,7 @@
package keeper
import (
"bytes"
"encoding/binary"
"encoding/json"
"io/ioutil"
@@ -11,6 +12,7 @@ import (
"github.com/CosmWasm/wasmd/x/wasm/internal/types"
stypes "github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -195,7 +197,7 @@ func TestInstantiate(t *testing.T) {
gasBefore := ctx.GasMeter().GasConsumed()
// create with no balance is also legal
addr, err := keeper.Instantiate(ctx, contractID, creator, initMsgBz, "demo contract 1", nil)
addr, err := keeper.Instantiate(ctx, contractID, creator, nil, initMsgBz, "demo contract 1", nil)
require.NoError(t, err)
require.Equal(t, "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5", addr.String())
@@ -228,7 +230,7 @@ func TestInstantiateWithNonExistingCodeID(t *testing.T) {
require.NoError(t, err)
const nonExistingCodeID = 9999
addr, err := keeper.Instantiate(ctx, nonExistingCodeID, creator, initMsgBz, "demo contract 2", nil)
addr, err := keeper.Instantiate(ctx, nonExistingCodeID, creator, nil, initMsgBz, "demo contract 2", nil)
require.True(t, types.ErrNotFound.Is(err), err)
require.Nil(t, addr)
}
@@ -259,7 +261,7 @@ func TestExecute(t *testing.T) {
initMsgBz, err := json.Marshal(initMsg)
require.NoError(t, err)
addr, err := keeper.Instantiate(ctx, contractID, creator, initMsgBz, "demo contract 3", deposit)
addr, err := keeper.Instantiate(ctx, contractID, creator, nil, initMsgBz, "demo contract 3", deposit)
require.NoError(t, err)
require.Equal(t, "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5", addr.String())
@@ -353,7 +355,7 @@ func TestExecuteWithPanic(t *testing.T) {
initMsgBz, err := json.Marshal(initMsg)
require.NoError(t, err)
addr, err := keeper.Instantiate(ctx, contractID, creator, initMsgBz, "demo contract 4", deposit)
addr, err := keeper.Instantiate(ctx, contractID, creator, nil, initMsgBz, "demo contract 4", deposit)
require.NoError(t, err)
// let's make sure we get a reasonable error, no panic/crash
@@ -387,7 +389,7 @@ func TestExecuteWithCpuLoop(t *testing.T) {
initMsgBz, err := json.Marshal(initMsg)
require.NoError(t, err)
addr, err := keeper.Instantiate(ctx, contractID, creator, initMsgBz, "demo contract 5", deposit)
addr, err := keeper.Instantiate(ctx, contractID, creator, nil, initMsgBz, "demo contract 5", deposit)
require.NoError(t, err)
// make sure we set a limit before calling
@@ -429,7 +431,7 @@ func TestExecuteWithStorageLoop(t *testing.T) {
initMsgBz, err := json.Marshal(initMsg)
require.NoError(t, err)
addr, err := keeper.Instantiate(ctx, contractID, creator, initMsgBz, "demo contract 6", deposit)
addr, err := keeper.Instantiate(ctx, contractID, creator, nil, initMsgBz, "demo contract 6", deposit)
require.NoError(t, err)
// make sure we set a limit before calling
@@ -451,6 +453,121 @@ func TestExecuteWithStorageLoop(t *testing.T) {
require.True(t, false, "We must panic before this line")
}
func TestMigrate(t *testing.T) {
tempDir, err := ioutil.TempDir("", "wasm")
require.NoError(t, err)
defer os.RemoveAll(tempDir)
ctx, keepers := CreateTestInput(t, false, tempDir, SupportedFeatures, nil, nil)
accKeeper, keeper := keepers.AccountKeeper, keepers.WasmKeeper
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 5000))
creator := createFakeFundedAccount(ctx, accKeeper, deposit.Add(deposit...))
fred := createFakeFundedAccount(ctx, accKeeper, topUp)
wasmCode, err := ioutil.ReadFile("./testdata/contract.wasm")
require.NoError(t, err)
originalContractID, err := keeper.Create(ctx, creator, wasmCode, "", "")
require.NoError(t, err)
newContractID, err := keeper.Create(ctx, creator, wasmCode, "", "")
require.NoError(t, err)
require.NotEqual(t, originalContractID, newContractID)
_, _, anyAddr := keyPubAddr()
initMsg := InitMsg{
Verifier: fred,
Beneficiary: anyAddr,
}
initMsgBz, err := json.Marshal(initMsg)
require.NoError(t, err)
specs := map[string]struct {
admin sdk.AccAddress
overrideContractAddr sdk.AccAddress
caller sdk.AccAddress
codeID uint64
migrateMsg []byte
expErr *sdkerrors.Error
}{
"all good with same code id": {
admin: creator,
caller: creator,
codeID: originalContractID,
},
"all good with new code id": {
admin: creator,
caller: creator,
codeID: newContractID,
},
"all good with admin set": {
admin: fred,
caller: fred,
codeID: newContractID,
},
"prevent migration when admin was not set on instantiate": {
caller: creator,
codeID: originalContractID,
expErr: sdkerrors.ErrUnauthorized,
},
"prevent migration when not admin": {
caller: creator,
admin: fred,
codeID: originalContractID,
expErr: sdkerrors.ErrUnauthorized,
},
"fail with non existing code id": {
admin: creator,
caller: creator,
codeID: 99999,
expErr: sdkerrors.ErrInvalidRequest,
},
"fail with non existing contract addr": {
admin: creator,
caller: creator,
overrideContractAddr: anyAddr,
codeID: originalContractID,
expErr: sdkerrors.ErrInvalidRequest,
},
"fail when migration caused error": {
admin: creator,
caller: creator,
codeID: originalContractID,
migrateMsg: bytes.Repeat([]byte{0x1}, 7), // condition hard coded in stub: >6 = error
expErr: types.ErrMigrationFailed,
},
}
var (
builtIntoGoCosmWasmStubGas = sdk.Gas(10000)
builtIntoGoCosmWasmStubData = []byte(("my-migration-response-data"))
)
for msg, spec := range specs {
t.Run(msg, func(t *testing.T) {
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
addr, err := keeper.Instantiate(ctx, originalContractID, creator, spec.admin, initMsgBz, "demo contract", nil)
require.NoError(t, err)
if spec.overrideContractAddr != nil {
addr = spec.overrideContractAddr
}
gasBefore := ctx.GasMeter().GasConsumed()
res, err := keeper.Migrate(ctx, addr, spec.caller, spec.codeID, spec.migrateMsg)
require.True(t, spec.expErr.Is(err), "expected %v but got %+v", spec.expErr, err)
if spec.expErr != nil {
return
}
gasAfter := ctx.GasMeter().GasConsumed()
assert.Greater(t, gasAfter-gasBefore, builtIntoGoCosmWasmStubGas/GasMultiplier)
assert.Equal(t, builtIntoGoCosmWasmStubData, res.Data)
cInfo := keeper.GetContractInfo(ctx, addr)
assert.Equal(t, spec.codeID, cInfo.CodeID)
assert.Equal(t, originalContractID, cInfo.PreviousCodeID)
assert.Equal(t, types.NewCreatedAt(ctx), cInfo.LastUpdated)
// TODO: check contract store was updated by migration code (impl also in contract)
// TODO: check any messages dispatched proper
// TODO: check events?
})
}
}
type InitMsg struct {
Verifier sdk.AccAddress `json:"verifier"`
Beneficiary sdk.AccAddress `json:"beneficiary"`

View File

@@ -41,7 +41,7 @@ func TestQueryContractState(t *testing.T) {
initMsgBz, err := json.Marshal(initMsg)
require.NoError(t, err)
addr, err := keeper.Instantiate(ctx, contractID, creator, initMsgBz, "demo contract to query", deposit)
addr, err := keeper.Instantiate(ctx, contractID, creator, nil, initMsgBz, "demo contract to query", deposit)
require.NoError(t, err)
contractModel := []types.Model{
@@ -187,7 +187,7 @@ func TestListContractByCodeOrdering(t *testing.T) {
ctx = setBlock(ctx, h)
h++
}
_, err = keeper.Instantiate(ctx, codeID, creator, initMsgBz, fmt.Sprintf("contract %d", i), topUp)
_, err = keeper.Instantiate(ctx, codeID, creator, nil, initMsgBz, fmt.Sprintf("contract %d", i), topUp)
require.NoError(t, err)
}

View File

@@ -78,7 +78,7 @@ func TestMaskReflectContractSend(t *testing.T) {
// creator instantiates a contract and gives it tokens
maskStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 40000))
maskAddr, err := keeper.Instantiate(ctx, maskID, creator, []byte("{}"), "mask contract 2", maskStart)
maskAddr, err := keeper.Instantiate(ctx, maskID, creator, nil, []byte("{}"), "mask contract 2", maskStart)
require.NoError(t, err)
require.NotEmpty(t, maskAddr)
@@ -90,7 +90,7 @@ func TestMaskReflectContractSend(t *testing.T) {
initMsgBz, err := json.Marshal(initMsg)
require.NoError(t, err)
escrowStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 25000))
escrowAddr, err := keeper.Instantiate(ctx, escrowID, creator, initMsgBz, "escrow contract 2", escrowStart)
escrowAddr, err := keeper.Instantiate(ctx, escrowID, creator, nil, initMsgBz, "escrow contract 2", escrowStart)
require.NoError(t, err)
require.NotEmpty(t, escrowAddr)
@@ -156,7 +156,7 @@ func TestMaskReflectCustomMsg(t *testing.T) {
// creator instantiates a contract and gives it tokens
contractStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 40000))
contractAddr, err := keeper.Instantiate(ctx, codeID, creator, []byte("{}"), "mask contract 1", contractStart)
contractAddr, err := keeper.Instantiate(ctx, codeID, creator, nil, []byte("{}"), "mask contract 1", contractStart)
require.NoError(t, err)
require.NotEmpty(t, contractAddr)
@@ -250,7 +250,7 @@ func TestMaskReflectCustomQuery(t *testing.T) {
// creator instantiates a contract and gives it tokens
contractStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 40000))
contractAddr, err := keeper.Instantiate(ctx, codeID, creator, []byte("{}"), "mask contract 1", contractStart)
contractAddr, err := keeper.Instantiate(ctx, codeID, creator, nil, []byte("{}"), "mask contract 1", contractStart)
require.NoError(t, err)
require.NotEmpty(t, contractAddr)

View File

@@ -119,7 +119,7 @@ func TestInitializeStaking(t *testing.T) {
initBz, err := json.Marshal(&initMsg)
require.NoError(t, err)
stakingAddr, err := keeper.Instantiate(ctx, stakingID, creator, initBz, "staking derivates - DRV", nil)
stakingAddr, err := keeper.Instantiate(ctx, stakingID, creator, nil, initBz, "staking derivates - DRV", nil)
require.NoError(t, err)
require.NotEmpty(t, stakingAddr)
@@ -139,7 +139,7 @@ func TestInitializeStaking(t *testing.T) {
badBz, err := json.Marshal(&badInitMsg)
require.NoError(t, err)
_, err = keeper.Instantiate(ctx, stakingID, creator, badBz, "missing validator", nil)
_, err = keeper.Instantiate(ctx, stakingID, creator, nil, badBz, "missing validator", nil)
require.Error(t, err)
// no changes to bonding shares
@@ -203,7 +203,7 @@ func initializeStaking(t *testing.T) initInfo {
initBz, err := json.Marshal(&initMsg)
require.NoError(t, err)
stakingAddr, err := keeper.Instantiate(ctx, stakingID, creator, initBz, "staking derivates - DRV", nil)
stakingAddr, err := keeper.Instantiate(ctx, stakingID, creator, nil, initBz, "staking derivates - DRV", nil)
require.NoError(t, err)
require.NotEmpty(t, stakingAddr)

View File

@@ -198,7 +198,7 @@ func TestHandler(k Keeper) sdk.Handler {
}
func handleInstantiate(ctx sdk.Context, k Keeper, msg *wasmTypes.MsgInstantiateContract) (*sdk.Result, error) {
contractAddr, err := k.Instantiate(ctx, msg.Code, msg.Sender, msg.InitMsg, msg.Label, msg.InitFunds)
contractAddr, err := k.Instantiate(ctx, msg.Code, msg.Sender, msg.Admin, msg.InitMsg, msg.Label, msg.InitFunds)
if err != nil {
return nil, err
}

View File

@@ -10,6 +10,7 @@ func RegisterCodec(cdc *codec.Codec) {
cdc.RegisterConcrete(&MsgStoreCode{}, "wasm/store-code", nil)
cdc.RegisterConcrete(&MsgInstantiateContract{}, "wasm/instantiate", nil)
cdc.RegisterConcrete(&MsgExecuteContract{}, "wasm/execute", nil)
cdc.RegisterConcrete(&MsgMigrateContract{}, "wasm/migrate", nil)
}
// ModuleCdc generic sealed codec to be used throughout module

View File

@@ -34,4 +34,7 @@ var (
// ErrInvalidMsg error when we cannot process the error returned from the contract
ErrInvalidMsg = sdkErrors.Register(DefaultCodespace, 9, "invalid CosmosMsg from the contract")
// ErrMigrationFailed error for rust execution contract failure
ErrMigrationFailed = sdkErrors.Register(DefaultCodespace, 10, "migrate wasm contract failed")
)

View File

@@ -102,7 +102,9 @@ func validateBuilder(buildTag string) error {
}
type MsgInstantiateContract struct {
Sender sdk.AccAddress `json:"sender" yaml:"sender"`
Sender sdk.AccAddress `json:"sender" yaml:"sender"`
// Admin is an optional address that can execute migrations
Admin sdk.AccAddress `json:"admin,omitempty" yaml:"admin"`
Code uint64 `json:"code_id" yaml:"code_id"`
Label string `json:"label" yaml:"label"`
InitMsg json.RawMessage `json:"init_msg" yaml:"init_msg"`
@@ -135,6 +137,13 @@ func (msg MsgInstantiateContract) ValidateBasic() error {
if msg.InitFunds.IsAnyNegative() {
return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, "negative InitFunds")
}
if len(msg.Admin) != 0 {
if err := sdk.VerifyAddressFormat(msg.Admin); err != nil {
return err
}
}
return nil
}
@@ -182,3 +191,39 @@ func (msg MsgExecuteContract) GetSignBytes() []byte {
func (msg MsgExecuteContract) GetSigners() []sdk.AccAddress {
return []sdk.AccAddress{msg.Sender}
}
type MsgMigrateContract struct {
Sender sdk.AccAddress `json:"sender" yaml:"sender"`
Contract sdk.AccAddress `json:"contract" yaml:"contract"`
Code uint64 `json:"code_id" yaml:"code_id"`
MigrateMsg json.RawMessage `json:"msg" yaml:"msg"`
}
func (msg MsgMigrateContract) Route() string {
return RouterKey
}
func (msg MsgMigrateContract) Type() string {
return "migrate"
}
func (msg MsgMigrateContract) ValidateBasic() error {
if msg.Code == 0 {
return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "code_id is required")
}
if err := sdk.VerifyAddressFormat(msg.Sender); err != nil {
return sdkerrors.Wrap(err, "sender")
}
if err := sdk.VerifyAddressFormat(msg.Contract); err != nil {
return sdkerrors.Wrap(err, "contract")
}
return nil
}
func (msg MsgMigrateContract) GetSignBytes() []byte {
return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(msg))
}
func (msg MsgMigrateContract) GetSigners() []sdk.AccAddress {
return []sdk.AccAddress{msg.Sender}
}

View File

@@ -1,6 +1,7 @@
package types
import (
"bytes"
"strings"
"testing"
@@ -210,3 +211,79 @@ func TestInstantiateContractValidation(t *testing.T) {
}
}
func TestMsgMigrateContract(t *testing.T) {
badAddress, err := sdk.AccAddressFromHex("012345")
require.NoError(t, err)
// proper address size
goodAddress := sdk.AccAddress(make([]byte, 20))
anotherGoodAddress := sdk.AccAddress(bytes.Repeat([]byte{0x2}, 20))
specs := map[string]struct {
src MsgMigrateContract
expErr bool
}{
"all good": {
src: MsgMigrateContract{
Sender: goodAddress,
Contract: anotherGoodAddress,
Code: 1,
MigrateMsg: []byte{1},
},
},
"MigrateMsg optional": {
src: MsgMigrateContract{
Sender: goodAddress,
Contract: anotherGoodAddress,
Code: 1,
},
},
"bad sender": {
src: MsgMigrateContract{
Sender: badAddress,
Contract: anotherGoodAddress,
Code: 1,
},
expErr: true,
},
"empty sender": {
src: MsgMigrateContract{
Contract: anotherGoodAddress,
Code: 1,
},
expErr: true,
},
"empty code": {
src: MsgMigrateContract{
Sender: goodAddress,
Contract: anotherGoodAddress,
},
expErr: true,
},
"bad contract addr": {
src: MsgMigrateContract{
Sender: goodAddress,
Contract: badAddress,
Code: 1,
},
expErr: true,
},
"empty contract addr": {
src: MsgMigrateContract{
Sender: goodAddress,
Code: 1,
},
expErr: true,
},
}
for msg, spec := range specs {
t.Run(msg, func(t *testing.T) {
err := spec.src.ValidateBasic()
if spec.expErr {
require.Error(t, err)
return
}
require.NoError(t, err)
})
}
}

View File

@@ -2,6 +2,7 @@ package types
import (
"encoding/json"
tmBytes "github.com/tendermint/tendermint/libs/bytes"
wasmTypes "github.com/CosmWasm/go-cosmwasm/types"
@@ -41,11 +42,21 @@ func NewCodeInfo(codeHash []byte, creator sdk.AccAddress, source string, builder
type ContractInfo struct {
CodeID uint64 `json:"code_id"`
Creator sdk.AccAddress `json:"creator"`
Admin sdk.AccAddress `json:"admin,omitempty"`
Label string `json:"label"`
InitMsg json.RawMessage `json:"init_msg,omitempty"`
// never show this in query results, just use for sorting
// (Note: when using json tag "-" amino refused to serialize it...)
Created *CreatedAt `json:"created,omitempty"`
// TODO: type CreatedAt is not an accurate name. how about renaming to BlockPosition?
LastUpdated *CreatedAt `json:"last_updated,omitempty"`
PreviousCodeID uint64 `json:"previous_code_id,omitempty"`
}
func (c *ContractInfo) UpdateCodeID(ctx sdk.Context, newCodeID uint64) {
c.PreviousCodeID = c.CodeID
c.CodeID = newCodeID
c.LastUpdated = NewCreatedAt(ctx)
}
// CreatedAt can be used to sort contracts
@@ -82,10 +93,11 @@ func NewCreatedAt(ctx sdk.Context) *CreatedAt {
}
// NewContractInfo creates a new instance of a given WASM contract info
func NewContractInfo(codeID uint64, creator sdk.AccAddress, initMsg []byte, label string, createdAt *CreatedAt) ContractInfo {
func NewContractInfo(codeID uint64, creator, admin sdk.AccAddress, initMsg []byte, label string, createdAt *CreatedAt) ContractInfo {
return ContractInfo{
CodeID: codeID,
Creator: creator,
Admin: admin,
InitMsg: initMsg,
Label: label,
Created: createdAt,