Merge pull request #433 from CosmWasm/sudo-entry-point
Sudo entry point
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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!")
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user