Merge pull request #433 from CosmWasm/sudo-entry-point

Sudo entry point
This commit is contained in:
Ethan Frey
2021-03-04 11:09:38 +01:00
committed by GitHub
5 changed files with 137 additions and 0 deletions

View File

@@ -4,6 +4,8 @@
[Full Changelog](https://github.com/CosmWasm/wasmd/compare/v0.15.0...HEAD)
- Upgrade to CosmWasm v0.14.0 [\#432](https://github.com/CosmWasm/wasmd/pull/432)
- Expose Sudo contract entry point on Keeper [\#433](https://github.com/CosmWasm/wasmd/pull/433)
- Support custom MessageHandler [\#327](https://github.com/CosmWasm/wasmd/issues/327)
- 🎉 Implement IBC contract support [\#394](https://github.com/CosmWasm/wasmd/pull/394)

View File

@@ -440,6 +440,45 @@ func (k Keeper) migrate(ctx sdk.Context, contractAddress sdk.AccAddress, caller
}, nil
}
// Sudo allows priviledged access to a contract. This can never be called by governance or external tx, but only by
// another native Go module directly. Thus, the keeper doesn't place any access controls on it, that is the
// responsibility or the app developer (who passes the wasm.Keeper in app.go)
func (k Keeper) Sudo(ctx sdk.Context, contractAddress sdk.AccAddress, msg []byte) (*sdk.Result, error) {
ctx.GasMeter().ConsumeGas(InstanceCost, "Loading CosmWasm module: sudo")
contractInfo, codeInfo, prefixStore, err := k.contractInstance(ctx, contractAddress)
if err != nil {
return nil, err
}
env := types.NewEnv(ctx, contractAddress)
// prepare querier
querier := QueryHandler{
Ctx: ctx,
Plugins: k.queryPlugins,
}
gas := gasForContract(ctx)
res, gasUsed, execErr := k.wasmer.Sudo(codeInfo.CodeHash, env, msg, prefixStore, cosmwasmAPI, querier, gasMeter(ctx), gas)
consumeGas(ctx, gasUsed)
if execErr != nil {
return nil, sdkerrors.Wrap(types.ErrExecuteFailed, execErr.Error())
}
// emit all events from this contract itself
events := types.ParseEvents(res.Attributes, contractAddress)
ctx.EventManager().EmitEvents(events)
err = k.dispatchMessages(ctx, contractAddress, contractInfo.IBCPortID, res.Messages)
if err != nil {
return nil, sdkerrors.Wrap(err, "dispatch")
}
return &sdk.Result{
Data: res.Data,
}, nil
}
func (k Keeper) deleteContractSecondIndex(ctx sdk.Context, contractAddress sdk.AccAddress, contractInfo *types.ContractInfo) {
ctx.KVStore(k.storeKey).Delete(types.GetContractByCreatedSecondaryIndexKey(contractAddress, contractInfo))
}

View File

@@ -1025,6 +1025,77 @@ func TestMigrateWithDispatchedMessage(t *testing.T) {
assert.Equal(t, deposit, balance)
}
type sudoMsg struct {
// This is a tongue-in-check demo command. This is not the intended purpose of Sudo.
// Here we show that some priviledged Go module can make a call that should never be exposed
// to end users (via Tx/Execute).
//
// The contract developer can choose to expose anything to sudo. This functionality is not a true
// backdoor (it can never be called by end users), but allows the developers of the native blockchain
// code to make special calls. This can also be used as an authentication mechanism, if you want to expose
// some callback that only can be triggered by some system module and not faked by external users.
StealFunds stealFundsMsg `json:"steal_funds"`
}
type stealFundsMsg struct {
Recipient string `json:"recipient"`
Amount wasmvmtypes.Coins `json:"amount"`
}
func TestSudo(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, SupportedFeatures, nil, nil)
accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.WasmKeeper, keepers.BankKeeper
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
creator := createFakeFundedAccount(t, ctx, accKeeper, bankKeeper, deposit.Add(deposit...))
wasmCode, err := ioutil.ReadFile("./testdata/hackatom.wasm")
require.NoError(t, err)
contractID, err := keeper.Create(ctx, creator, wasmCode, "", "", nil)
require.NoError(t, err)
_, _, bob := keyPubAddr()
_, _, fred := keyPubAddr()
initMsg := HackatomExampleInitMsg{
Verifier: fred,
Beneficiary: bob,
}
initMsgBz, err := json.Marshal(initMsg)
require.NoError(t, err)
addr, _, err := keeper.Instantiate(ctx, contractID, creator, nil, initMsgBz, "demo contract 3", deposit)
require.NoError(t, err)
require.Equal(t, "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5", addr.String())
// the community is broke
_, _, community := keyPubAddr()
comAcct := accKeeper.GetAccount(ctx, community)
require.Nil(t, comAcct)
// now the community wants to get paid via sudo
msg := sudoMsg{
// This is a tongue-in-check demo command. This is not the intended purpose of Sudo.
// Here we show that some priviledged Go module can make a call that should never be exposed
// to end users (via Tx/Execute).
StealFunds: stealFundsMsg{
Recipient: community.String(),
Amount: wasmvmtypes.Coins{wasmvmtypes.NewCoin(76543, "denom")},
},
}
sudoMsg, err := json.Marshal(msg)
require.NoError(t, err)
res, err := keeper.Sudo(ctx, addr, sudoMsg)
require.NoError(t, err)
require.NotNil(t, res)
// ensure community now exists and got paid
comAcct = accKeeper.GetAccount(ctx, community)
require.NotNil(t, comAcct)
balance := bankKeeper.GetBalance(ctx, comAcct.GetAddress(), "denom")
assert.Equal(t, sdk.NewInt64Coin("denom", 76543), balance)
}
func prettyEvents(t *testing.T, events sdk.Events) string {
t.Helper()
type prettyEvent struct {

View File

@@ -21,6 +21,7 @@ type MockWasmer struct {
ExecuteFn func(codeID wasmvm.Checksum, env wasmvmtypes.Env, info wasmvmtypes.MessageInfo, executeMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64) (*wasmvmtypes.Response, uint64, error)
QueryFn func(codeID wasmvm.Checksum, env wasmvmtypes.Env, queryMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64) ([]byte, uint64, error)
MigrateFn func(codeID wasmvm.Checksum, env wasmvmtypes.Env, migrateMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64) (*wasmvmtypes.Response, uint64, error)
SudoFn func(codeID wasmvm.Checksum, env wasmvmtypes.Env, sudoMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64) (*wasmvmtypes.Response, uint64, error)
GetCodeFn func(codeID wasmvm.Checksum) (wasmvm.WasmCode, error)
CleanupFn func()
IBCChannelOpenFn func(codeID wasmvm.Checksum, env wasmvmtypes.Env, channel wasmvmtypes.IBCChannel, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64) (uint64, error)
@@ -115,6 +116,14 @@ func (m *MockWasmer) Migrate(codeID wasmvm.Checksum, env wasmvmtypes.Env, migrat
return m.MigrateFn(codeID, env, migrateMsg, store, goapi, querier, gasMeter, gasLimit)
}
func (m *MockWasmer) Sudo(codeID wasmvm.Checksum, env wasmvmtypes.Env, sudoMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64) (*wasmvmtypes.Response, uint64, error) {
if m.SudoFn == nil {
panic("not supposed to be called!")
}
return m.SudoFn(codeID, env, sudoMsg, store, goapi, querier, gasMeter, gasLimit)
}
func (m *MockWasmer) GetCode(codeID wasmvm.Checksum) (wasmvm.WasmCode, error) {
if m.GetCodeFn == nil {
panic("not supposed to be called!")

View File

@@ -91,6 +91,22 @@ type WasmerEngine interface {
gasLimit uint64,
) (*wasmvmtypes.Response, uint64, error)
// Sudo runs an existing contract in read/write mode (like Execute), but is never exposed to external callers
// (either transactions or government proposals), but can only be called by other native Go modules directly.
//
// This allows a contract to expose custom "super user" functions or priviledged operations that can be
// deeply integrated with native modules.
Sudo(
codeID wasmvm.Checksum,
env wasmvmtypes.Env,
sudoMsg []byte,
store wasmvm.KVStore,
goapi wasmvm.GoAPI,
querier wasmvm.Querier,
gasMeter wasmvm.GasMeter,
gasLimit uint64,
) (*wasmvmtypes.Response, uint64, error)
// GetCode will load the original wasm code for the given code id.
// This will only succeed if that code id was previously returned from
// a call to Create.