Skip to content

Commit

Permalink
feat: experimental wasm bindings for token factory & scheduler (#1321)
Browse files Browse the repository at this point in the history
# Related Github tickets

- #2459

# Background

This change adds experimental bindings for the token factory, as well as
a limited scope of the scheduler module, allowing jobs to be queried and
created from smart contracts.

Bindings are untested and should be treated as experimental.

# Testing completed

- [ ] test coverage exists or has been added/updated
- [x] tested in a private testnet

# Breaking changes

- [x] I have checked my code for breaking changes
- [x] If there are breaking changes, there is a supporting migration.
  • Loading branch information
byte-bandit authored Dec 30, 2024
1 parent 288cbcc commit ec53cca
Show file tree
Hide file tree
Showing 16 changed files with 910 additions and 15 deletions.
24 changes: 18 additions & 6 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,13 +139,15 @@ import (
palomamodulekeeper "github.com/palomachain/paloma/v2/x/paloma/keeper"
palomamoduletypes "github.com/palomachain/paloma/v2/x/paloma/types"
schedulermodule "github.com/palomachain/paloma/v2/x/scheduler"
schedulerbindings "github.com/palomachain/paloma/v2/x/scheduler/bindings"
schedulermodulekeeper "github.com/palomachain/paloma/v2/x/scheduler/keeper"
schedulermoduletypes "github.com/palomachain/paloma/v2/x/scheduler/types"
skywaymodule "github.com/palomachain/paloma/v2/x/skyway"
skywayclient "github.com/palomachain/paloma/v2/x/skyway/client"
skywaymodulekeeper "github.com/palomachain/paloma/v2/x/skyway/keeper"
skywaymoduletypes "github.com/palomachain/paloma/v2/x/skyway/types"
"github.com/palomachain/paloma/v2/x/tokenfactory"
tokenfactorybindings "github.com/palomachain/paloma/v2/x/tokenfactory/bindings"
tokenfactorymodulekeeper "github.com/palomachain/paloma/v2/x/tokenfactory/keeper"
tokenfactorymoduletypes "github.com/palomachain/paloma/v2/x/tokenfactory/types"
treasurymodule "github.com/palomachain/paloma/v2/x/treasury"
Expand Down Expand Up @@ -739,6 +741,21 @@ func New(
"cosmwasm_1_4",
"cosmwasm_2_0",
}

opts := []wasmkeeper.Option{
wasmkeeper.WithMessageHandlerDecorator(func(old wasmkeeper.Messenger) wasmkeeper.Messenger {
return wasmkeeper.NewMessageHandlerChain(
old,
app.SchedulerKeeper.ExecuteWasmJobEventListener(),
)
}),
}
bbk, ok := app.BankKeeper.(bankkeeper.BaseKeeper)
if !ok {
panic("bankkeeper is not a BaseKeeper")
}
opts = append(opts, tokenfactorybindings.RegisterCustomPlugins(&bbk, &app.TokenFactoryKeeper)...)
opts = append(opts, schedulerbindings.RegisterCustomPlugins(&app.SchedulerKeeper)...)
app.wasmKeeper = wasmkeeper.NewKeeper(
appCodec,
runtime.NewKVStoreService(keys[wasmtypes.StoreKey]),
Expand All @@ -757,12 +774,7 @@ func New(
wasmConfig,
wasmAvailableCapabilities,
authorityAddress,
wasmkeeper.WithMessageHandlerDecorator(func(old wasmkeeper.Messenger) wasmkeeper.Messenger {
return wasmkeeper.NewMessageHandlerChain(
old,
app.SchedulerKeeper.ExecuteWasmJobEventListener(),
)
}),
opts...,
)

app.AuthzKeeper = authzkeeper.NewKeeper(runtime.NewKVStoreService(keys[authzkeeper.StoreKey]), appCodec, app.MsgServiceRouter(), app.AccountKeeper)
Expand Down
90 changes: 90 additions & 0 deletions x/scheduler/bindings/msg_plugin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package bindings

import (
"encoding/json"

sdkerrors "cosmossdk.io/errors"
wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper"
wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
errtypes "github.com/cosmos/cosmos-sdk/types/errors"
bindingstypes "github.com/palomachain/paloma/v2/x/scheduler/bindings/types"
schedulerkeeper "github.com/palomachain/paloma/v2/x/scheduler/keeper"
schedulertypes "github.com/palomachain/paloma/v2/x/scheduler/types"
)

func CustomMessageDecorator(scheduler *schedulerkeeper.Keeper) func(wasmkeeper.Messenger) wasmkeeper.Messenger {
return func(old wasmkeeper.Messenger) wasmkeeper.Messenger {
return &CustomMessenger{
wrapped: old,
scheduler: scheduler,
}
}
}

type CustomMessenger struct {
wrapped wasmkeeper.Messenger
scheduler *schedulerkeeper.Keeper
}

var _ wasmkeeper.Messenger = (*CustomMessenger)(nil)

func (m *CustomMessenger) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) ([]sdk.Event, [][]byte, [][]*codectypes.Any, error) {
if msg.Custom != nil {
var contractMsg bindingstypes.SchedulerMsg
if err := json.Unmarshal(msg.Custom, &contractMsg); err != nil {
return nil, nil, nil, sdkerrors.Wrap(err, "scheduler msg")
}
if contractMsg.Message == nil {
return nil, nil, nil, sdkerrors.Wrap(errtypes.ErrUnknownRequest, "nil message field")
}
msgType := contractMsg.Message
if msgType.CreateJob != nil {
return m.createJob(ctx, contractAddr, msgType.CreateJob)
}
}
return m.wrapped.DispatchMsg(ctx, contractAddr, contractIBCPortID, msg)
}

func (m *CustomMessenger) createJob(ctx sdk.Context, contractAddr sdk.AccAddress, createJob *bindingstypes.CreateJob) ([]sdk.Event, [][]byte, [][]*codectypes.Any, error) {
if createJob == nil {
return nil, nil, nil, wasmvmtypes.InvalidRequest{Err: "null create job"}
}
if createJob.Job == nil {
return nil, nil, nil, wasmvmtypes.InvalidRequest{Err: "null job"}
}

j := &schedulertypes.Job{
ID: createJob.Job.JobId,
Routing: schedulertypes.Routing{
ChainType: createJob.Job.ChainType,
ChainReferenceID: createJob.Job.ChainReferenceId,
},
Definition: []byte(createJob.Job.Definition),
Payload: []byte(createJob.Job.Payload),
IsPayloadModifiable: createJob.Job.PayloadModifiable,
EnforceMEVRelay: createJob.Job.IsMEV,
}

if err := j.ValidateBasic(); err != nil {
return nil, nil, nil, sdkerrors.Wrap(err, "failed to validate job")
}

msgServer := schedulerkeeper.NewMsgServerImpl(m.scheduler)
msgCreateJob := schedulertypes.NewMsgCreateJob(contractAddr.String(), j)
if err := msgCreateJob.ValidateBasic(); err != nil {
return nil, nil, nil, sdkerrors.Wrap(err, "failed validating MsgCreateJob")
}

resp, err := msgServer.CreateJob(ctx, msgCreateJob)
if err != nil {
return nil, nil, nil, sdkerrors.Wrap(err, "failed to create job")
}

bz, err := resp.Marshal()
if err != nil {
return nil, nil, nil, sdkerrors.Wrap(err, "failed to marshal response")
}
return nil, [][]byte{bz}, nil, nil
}
67 changes: 67 additions & 0 deletions x/scheduler/bindings/query_plugin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package bindings

import (
"encoding/json"

sdkerrors "cosmossdk.io/errors"
wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types"
sdk "github.com/cosmos/cosmos-sdk/types"
errtypes "github.com/cosmos/cosmos-sdk/types/errors"
bindingstypes "github.com/palomachain/paloma/v2/x/scheduler/bindings/types"
schedulerkeeper "github.com/palomachain/paloma/v2/x/scheduler/keeper"
)

type QueryPlugin struct {
scheduler *schedulerkeeper.Keeper
}

func NewQueryPlugin(s *schedulerkeeper.Keeper) *QueryPlugin {
return &QueryPlugin{
scheduler: s,
}
}

func CustomQuerier(qp *QueryPlugin) func(ctx sdk.Context, request json.RawMessage) ([]byte, error) {
return func(ctx sdk.Context, request json.RawMessage) ([]byte, error) {
var contractQuery bindingstypes.SchedulerQuery
if err := json.Unmarshal(request, &contractQuery); err != nil {
return nil, sdkerrors.Wrap(err, "query")
}
if contractQuery.Query == nil {
return nil, sdkerrors.Wrap(errtypes.ErrUnknownRequest, "nil query field")
}
queryType := contractQuery.Query

switch {
case queryType.JobById != nil:
j, err := qp.scheduler.GetJob(ctx, queryType.JobById.JobId)
if err != nil {
return nil, sdkerrors.Wrap(err, "failed to query for job")
}
if j == nil {
return nil, sdkerrors.Wrap(errtypes.ErrNotFound, "job id")
}

res := bindingstypes.JobByIdResponse{
Job: &bindingstypes.Job{
JobId: j.ID,
ChainType: j.Routing.ChainType,
ChainReferenceId: j.Routing.ChainReferenceID,
Definition: string(j.Definition),
Payload: string(j.Payload),
PayloadModifiable: j.IsPayloadModifiable,
IsMEV: j.EnforceMEVRelay,
},
}

bz, err := json.Marshal(res)
if err != nil {
return nil, sdkerrors.Wrap(err, "failed to marshal response")
}

return bz, nil
default:
return nil, wasmvmtypes.UnsupportedRequest{Kind: "unknown scheduler query variant"}
}
}
}
23 changes: 23 additions & 0 deletions x/scheduler/bindings/types/msg.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package types

type SchedulerMsg struct {
Message *SchedulerMsgType `json:"scheduler_msg_type,omitempty"`
}

type SchedulerMsgType struct {
// Contracts can create new jobs. Any number of jobs
// may be created, so lang as job IDs stay unique.
CreateJob *CreateJob `json:"create_job,omitempty"`
}

// CreateJob is a message to create a new job.
// JobId is a unique identifier for the job.
// ChainType is the type of chain the job is for (e.g. "evm").
// ChainReferenceId is the reference for the chain (e.g. "eth-main").
// Definition containts the ABI of the target contract.
// Payload is the data to be sent to the contract.
// PayloadModifiable indicates whether the payload can be modified.
// IsMEV indicates whether the job should be routed via an MEV pool.
type CreateJob struct {
Job *Job `json:"job,omitempty"`
}
17 changes: 17 additions & 0 deletions x/scheduler/bindings/types/query.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package types

type SchedulerQuery struct {
Query *SchedulerQueryType `json:"query,omitempty"`
}

type SchedulerQueryType struct {
JobById *JobByIdRequest `json:"full_denom,omitempty"`
}

type JobByIdRequest struct {
JobId string `json:"job_id"`
}

type JobByIdResponse struct {
Job *Job `json:"job"`
}
18 changes: 18 additions & 0 deletions x/scheduler/bindings/types/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package types

// JobId is a unique identifier for the job.
// ChainType is the type of chain the job is for (e.g. "evm").
// ChainReferenceId is the reference for the chain (e.g. "eth-main").
// Definition containts the ABI of the target contract.
// Payload is the data to be sent to the contract.
// PayloadModifiable indicates whether the payload can be modified.
// IsMEV indicates whether the job should be routed via an MEV pool.
type Job struct {
JobId string `json:"job_id"`
ChainType string `json:"chain_type"`
ChainReferenceId string `json:"chain_reference_id"`
Definition string `json:"definition"`
Payload string `json:"payload"`
PayloadModifiable bool `json:"payload_modifiable"`
IsMEV bool `json:"is_mev"`
}
23 changes: 23 additions & 0 deletions x/scheduler/bindings/wasm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package bindings

import (
wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper"
schedulerkeeper "github.com/palomachain/paloma/v2/x/scheduler/keeper"
)

func RegisterCustomPlugins(
scheduler *schedulerkeeper.Keeper,
) []wasmkeeper.Option {
wasmQueryPlugin := NewQueryPlugin(scheduler)
queryPluginOpt := wasmkeeper.WithQueryPlugins(&wasmkeeper.QueryPlugins{
Custom: CustomQuerier(wasmQueryPlugin),
})
messengerDecoratorOpt := wasmkeeper.WithMessageHandlerDecorator(
CustomMessageDecorator(scheduler),
)

return []wasmkeeper.Option{
queryPluginOpt,
messengerDecoratorOpt,
}
}
9 changes: 1 addition & 8 deletions x/scheduler/client/cli/tx_create_job.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/client/tx"
"github.com/palomachain/paloma/v2/x/scheduler/types"
vtypes "github.com/palomachain/paloma/v2/x/valset/types"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -58,13 +57,7 @@ func CmdCreateJob() *cobra.Command {
}

creator := clientCtx.GetFromAddress().String()
msg := &types.MsgCreateJob{
Job: job,
Metadata: vtypes.MsgMetadata{
Creator: creator,
Signers: []string{creator},
},
}
msg := types.NewMsgCreateJob(creator, job)
if err := msg.ValidateBasic(); err != nil {
return err
}
Expand Down
4 changes: 3 additions & 1 deletion x/scheduler/types/message_create_job.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ const TypeMsgCreateJob = "create_job"

var _ sdk.Msg = &MsgCreateJob{}

func NewMsgCreateJob(creator string) *MsgCreateJob {
func NewMsgCreateJob(creator string, j *Job) *MsgCreateJob {
return &MsgCreateJob{
Metadata: types.MsgMetadata{
Creator: creator,
Signers: []string{creator},
},
Job: j,
}
}

Expand Down
Loading

0 comments on commit ec53cca

Please sign in to comment.