Skip to content

Commit 6620bc5

Browse files
author
Yongwoo Lee
committed
Merge branch 'v2/develop' into whylee/v2/feat/contract-access-control
2 parents b6a7f1a + c4bf377 commit 6620bc5

File tree

12 files changed

+1692
-123
lines changed

12 files changed

+1692
-123
lines changed

client/utils_test.go

+61
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package client_test
22

33
import (
4+
"strconv"
45
"testing"
56

7+
"github.com/spf13/pflag"
68
"github.com/stretchr/testify/require"
79

810
"github.com/line/lbm-sdk/v2/client"
11+
"github.com/line/lbm-sdk/v2/client/flags"
912
)
1013

1114
func TestPaginate(t *testing.T) {
@@ -75,3 +78,61 @@ func TestPaginate(t *testing.T) {
7578
})
7679
}
7780
}
81+
82+
func TestReadPageRequest(t *testing.T) {
83+
84+
testCases := []struct {
85+
name string
86+
pageKey string
87+
offset, limit, page int
88+
countTotal bool
89+
ok bool
90+
}{
91+
{
92+
"use page ok",
93+
"page key",
94+
0, 100, 10,
95+
true,
96+
true,
97+
},
98+
{
99+
"use offset ok",
100+
"page key",
101+
10, 100, 0,
102+
true,
103+
true,
104+
},
105+
{
106+
"page and offset cannot be used together",
107+
"page key",
108+
100, 100, 10,
109+
true,
110+
false,
111+
},
112+
}
113+
114+
for _, tc := range testCases {
115+
t.Run(tc.name, func(t *testing.T) {
116+
flagSet := pflag.NewFlagSet("test flag set", pflag.ContinueOnError)
117+
flagSet.String(flags.FlagPageKey, "default page key", "page key")
118+
flagSet.Uint64(flags.FlagOffset, 0, "offset")
119+
flagSet.Uint64(flags.FlagLimit, 0, "limit")
120+
flagSet.Uint64(flags.FlagPage, 0, "page")
121+
flagSet.Bool(flags.FlagCountTotal, false, "count total")
122+
123+
err := flagSet.Set(flags.FlagPageKey, tc.pageKey)
124+
err = flagSet.Set(flags.FlagOffset, strconv.Itoa(tc.offset))
125+
err = flagSet.Set(flags.FlagLimit, strconv.Itoa(tc.limit))
126+
err = flagSet.Set(flags.FlagPage, strconv.Itoa(tc.page))
127+
err = flagSet.Set(flags.FlagCountTotal, strconv.FormatBool(tc.countTotal))
128+
129+
pr, err := client.ReadPageRequest(flagSet)
130+
if tc.ok {
131+
require.NoError(t, err)
132+
require.NotNil(t, pr)
133+
} else {
134+
require.Error(t, err)
135+
}
136+
})
137+
}
138+
}

x/wasm/alias.go

+38-36
Original file line numberDiff line numberDiff line change
@@ -99,42 +99,44 @@ var (
9999
)
100100

101101
type (
102-
ProposalType = types.ProposalType
103-
GenesisState = types.GenesisState
104-
Code = types.Code
105-
Contract = types.Contract
106-
MsgStoreCode = types.MsgStoreCode
107-
MsgStoreCodeResponse = types.MsgStoreCodeResponse
108-
MsgInstantiateContract = types.MsgInstantiateContract
109-
MsgInstantiateContractResponse = types.MsgInstantiateContractResponse
110-
MsgExecuteContract = types.MsgExecuteContract
111-
MsgExecuteContractResponse = types.MsgExecuteContractResponse
112-
MsgMigrateContract = types.MsgMigrateContract
113-
MsgMigrateContractResponse = types.MsgMigrateContractResponse
114-
MsgUpdateAdmin = types.MsgUpdateAdmin
115-
MsgUpdateAdminResponse = types.MsgUpdateAdminResponse
116-
MsgClearAdmin = types.MsgClearAdmin
117-
MsgWasmIBCCall = types.MsgIBCSend
118-
MsgClearAdminResponse = types.MsgClearAdminResponse
119-
MsgServer = types.MsgServer
120-
Model = types.Model
121-
CodeInfo = types.CodeInfo
122-
ContractInfo = types.ContractInfo
123-
CreatedAt = types.AbsoluteTxPosition
124-
Config = types.WasmConfig
125-
ContractInfoWithAddress = types.ContractInfoWithAddress
126-
CodeInfoResponse = types.CodeInfoResponse
127-
MessageHandler = keeper.DefaultMessageHandler
128-
BankEncoder = keeper.BankEncoder
129-
CustomEncoder = keeper.CustomEncoder
130-
StakingEncoder = keeper.StakingEncoder
131-
WasmEncoder = keeper.WasmEncoder
132-
MessageEncoders = keeper.MessageEncoders
133-
Keeper = keeper.Keeper
134-
QueryHandler = keeper.QueryHandler
135-
CustomQuerier = keeper.CustomQuerier
136-
QueryPlugins = keeper.QueryPlugins
137-
Option = keeper.Option
102+
ProposalType = types.ProposalType
103+
GenesisState = types.GenesisState
104+
Code = types.Code
105+
Contract = types.Contract
106+
MsgStoreCode = types.MsgStoreCode
107+
MsgStoreCodeResponse = types.MsgStoreCodeResponse
108+
MsgInstantiateContract = types.MsgInstantiateContract
109+
MsgInstantiateContractResponse = types.MsgInstantiateContractResponse
110+
MsgStoreCodeAndInstantiateContract = types.MsgStoreCodeAndInstantiateContract
111+
MsgStoreCodeAndInstantiateContractResponse = types.MsgStoreCodeAndInstantiateContractResponse
112+
MsgExecuteContract = types.MsgExecuteContract
113+
MsgExecuteContractResponse = types.MsgExecuteContractResponse
114+
MsgMigrateContract = types.MsgMigrateContract
115+
MsgMigrateContractResponse = types.MsgMigrateContractResponse
116+
MsgUpdateAdmin = types.MsgUpdateAdmin
117+
MsgUpdateAdminResponse = types.MsgUpdateAdminResponse
118+
MsgClearAdmin = types.MsgClearAdmin
119+
MsgWasmIBCCall = types.MsgIBCSend
120+
MsgClearAdminResponse = types.MsgClearAdminResponse
121+
MsgServer = types.MsgServer
122+
Model = types.Model
123+
CodeInfo = types.CodeInfo
124+
ContractInfo = types.ContractInfo
125+
CreatedAt = types.AbsoluteTxPosition
126+
Config = types.WasmConfig
127+
ContractInfoWithAddress = types.ContractInfoWithAddress
128+
CodeInfoResponse = types.CodeInfoResponse
129+
MessageHandler = keeper.DefaultMessageHandler
130+
BankEncoder = keeper.BankEncoder
131+
CustomEncoder = keeper.CustomEncoder
132+
StakingEncoder = keeper.StakingEncoder
133+
WasmEncoder = keeper.WasmEncoder
134+
MessageEncoders = keeper.MessageEncoders
135+
Keeper = keeper.Keeper
136+
QueryHandler = keeper.QueryHandler
137+
CustomQuerier = keeper.CustomQuerier
138+
QueryPlugins = keeper.QueryPlugins
139+
Option = keeper.Option
138140

139141
EncodeHandler = types.EncodeHandler
140142
EncodeQuerier = types.EncodeQuerier

x/wasm/client/cli/tx.go

+123
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ func GetTxCmd() *cobra.Command {
4141
txCmd.AddCommand(
4242
StoreCodeCmd(),
4343
InstantiateContractCmd(),
44+
StoreCodeAndInstantiateContractCmd(),
4445
ExecuteContractCmd(),
4546
MigrateContractCmd(),
4647
UpdateContractAdminCmd(),
@@ -214,6 +215,128 @@ func parseInstantiateArgs(rawCodeID, initMsg string, sender sdk.AccAddress, flag
214215
return msg, nil
215216
}
216217

218+
// StoreCodeAndInstantiatecontractcmd will upload code and instantiate a contract using it
219+
func StoreCodeAndInstantiateContractCmd() *cobra.Command {
220+
cmd := &cobra.Command{
221+
Use: "store-instantiate [wasm file] [json_encoded_init_args] --source [source] --builder [builder] --label [text] --admin [address,optional] --amount [coins,optional]",
222+
Short: "Upload a wasm binary and instantiate a wasm contract from the code",
223+
Args: cobra.ExactArgs(2),
224+
RunE: func(cmd *cobra.Command, args []string) error {
225+
clientCtx, err := client.GetClientTxContext(cmd)
226+
if err != nil {
227+
return err
228+
}
229+
msg, err := parseStoreCodeAndInstantiateContractArgs(args[0], args[1], clientCtx.GetFromAddress(), cmd.Flags())
230+
if err != nil {
231+
return err
232+
}
233+
if err = msg.ValidateBasic(); err != nil {
234+
return err
235+
}
236+
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg)
237+
},
238+
}
239+
240+
cmd.Flags().String(flagSource, "", "A valid URI reference to the contract's source code, optional")
241+
cmd.Flags().String(flagBuilder, "", "A valid docker tag for the build system, optional")
242+
cmd.Flags().String(flagInstantiateByEverybody, "", "Everybody can instantiate a contract from the code, optional")
243+
cmd.Flags().String(flagInstantiateByAddress, "", "Only this address can instantiate a contract instance from the code, optional")
244+
cmd.Flags().String(flagAmount, "", "Coins to send to the contract during instantiation")
245+
cmd.Flags().String(flagLabel, "", "A human-readable name for this contract in lists")
246+
cmd.Flags().String(flagAdmin, "", "Address of an admin")
247+
flags.AddTxFlagsToCmd(cmd)
248+
return cmd
249+
}
250+
251+
func parseStoreCodeAndInstantiateContractArgs(file string, initMsg string, sender sdk.AccAddress, flags *flag.FlagSet) (types.MsgStoreCodeAndInstantiateContract, error) {
252+
wasm, err := ioutil.ReadFile(file)
253+
if err != nil {
254+
return types.MsgStoreCodeAndInstantiateContract{}, err
255+
}
256+
257+
// gzip the wasm file
258+
if wasmUtils.IsWasm(wasm) {
259+
wasm, err = wasmUtils.GzipIt(wasm)
260+
261+
if err != nil {
262+
return types.MsgStoreCodeAndInstantiateContract{}, err
263+
}
264+
} else if !wasmUtils.IsGzip(wasm) {
265+
return types.MsgStoreCodeAndInstantiateContract{}, fmt.Errorf("invalid input file. Use wasm binary or gzip")
266+
}
267+
268+
var perm *types.AccessConfig
269+
onlyAddrStr, err := flags.GetString(flagInstantiateByAddress)
270+
if err != nil {
271+
return types.MsgStoreCodeAndInstantiateContract{}, fmt.Errorf("instantiate by address: %s", err)
272+
}
273+
if onlyAddrStr != "" {
274+
allowedAddr, err := sdk.AccAddressFromBech32(onlyAddrStr)
275+
if err != nil {
276+
return types.MsgStoreCodeAndInstantiateContract{}, sdkerrors.Wrap(err, flagInstantiateByAddress)
277+
}
278+
x := types.AccessTypeOnlyAddress.With(allowedAddr)
279+
perm = &x
280+
} else {
281+
everybodyStr, err := flags.GetString(flagInstantiateByEverybody)
282+
if err != nil {
283+
return types.MsgStoreCodeAndInstantiateContract{}, fmt.Errorf("instantiate by everybody: %s", err)
284+
}
285+
if everybodyStr != "" {
286+
ok, err := strconv.ParseBool(everybodyStr)
287+
if err != nil {
288+
return types.MsgStoreCodeAndInstantiateContract{}, fmt.Errorf("boolean value expected for instantiate by everybody: %s", err)
289+
}
290+
if ok {
291+
perm = &types.AllowEverybody
292+
}
293+
}
294+
}
295+
296+
// build and sign the transaction, then broadcast to Tendermint
297+
source, err := flags.GetString(flagSource)
298+
if err != nil {
299+
return types.MsgStoreCodeAndInstantiateContract{}, fmt.Errorf("source: %s", err)
300+
}
301+
builder, err := flags.GetString(flagBuilder)
302+
if err != nil {
303+
return types.MsgStoreCodeAndInstantiateContract{}, fmt.Errorf("builder: %s", err)
304+
}
305+
306+
amountStr, err := flags.GetString(flagAmount)
307+
if err != nil {
308+
return types.MsgStoreCodeAndInstantiateContract{}, fmt.Errorf("amount: %s", err)
309+
}
310+
amount, err := sdk.ParseCoinsNormalized(amountStr)
311+
if err != nil {
312+
return types.MsgStoreCodeAndInstantiateContract{}, fmt.Errorf("amount: %s", err)
313+
}
314+
label, err := flags.GetString(flagLabel)
315+
if err != nil {
316+
return types.MsgStoreCodeAndInstantiateContract{}, fmt.Errorf("label: %s", err)
317+
}
318+
if label == "" {
319+
return types.MsgStoreCodeAndInstantiateContract{}, errors.New("label is required on all contracts")
320+
}
321+
adminStr, err := flags.GetString(flagAdmin)
322+
if err != nil {
323+
return types.MsgStoreCodeAndInstantiateContract{}, fmt.Errorf("admin: %s", err)
324+
}
325+
326+
msg := types.MsgStoreCodeAndInstantiateContract{
327+
Sender: sender.String(),
328+
WASMByteCode: wasm,
329+
Source: source,
330+
Builder: builder,
331+
InstantiatePermission: perm,
332+
Label: label,
333+
Funds: amount,
334+
InitMsg: []byte(initMsg),
335+
Admin: adminStr,
336+
}
337+
return msg, nil
338+
}
339+
217340
// ExecuteContractCmd will instantiate a contract from previously uploaded code.
218341
func ExecuteContractCmd() *cobra.Command {
219342
cmd := &cobra.Command{

x/wasm/client/rest/tx.go

+56
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
func registerTxRoutes(cliCtx client.Context, r *mux.Router) {
1717
r.HandleFunc("/wasm/code", storeCodeHandlerFn(cliCtx)).Methods("POST")
1818
r.HandleFunc("/wasm/code/{codeId}", instantiateContractHandlerFn(cliCtx)).Methods("POST")
19+
r.HandleFunc("/wasm/codeinit", storeCodeAndInstantiateContractHandlerFn(cliCtx)).Methods("POST")
1920
r.HandleFunc("/wasm/contract/{contractAddr}", executeContractHandlerFn(cliCtx)).Methods("POST")
2021
}
2122

@@ -32,6 +33,15 @@ type instantiateContractReq struct {
3233
InitMsg []byte `json:"init_msg" yaml:"init_msg"`
3334
}
3435

36+
type storeCodeAndInstantiateContractReq struct {
37+
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
38+
WasmBytes []byte `json:"wasm_bytes"`
39+
Label string `json:"label" yaml:"label"`
40+
Deposit sdk.Coins `json:"deposit" yaml:"deposit"`
41+
Admin string `json:"admin,omitempty" yaml:"admin"`
42+
InitMsg []byte `json:"init_msg" yaml:"init_msg"`
43+
}
44+
3545
type executeContractReq struct {
3646
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
3747
ExecMsg []byte `json:"exec_msg" yaml:"exec_msg"`
@@ -118,6 +128,52 @@ func instantiateContractHandlerFn(cliCtx client.Context) http.HandlerFunc {
118128
}
119129
}
120130

131+
func storeCodeAndInstantiateContractHandlerFn(cliCtx client.Context) http.HandlerFunc {
132+
return func(w http.ResponseWriter, r *http.Request) {
133+
var req storeCodeAndInstantiateContractReq
134+
if !rest.ReadRESTReq(w, r, cliCtx.LegacyAmino, &req) {
135+
return
136+
}
137+
138+
req.BaseReq = req.BaseReq.Sanitize()
139+
if !req.BaseReq.ValidateBasic(w) {
140+
return
141+
}
142+
143+
var err error
144+
wasm := req.WasmBytes
145+
146+
// gzip the wasm file
147+
if wasmUtils.IsWasm(wasm) {
148+
wasm, err = wasmUtils.GzipIt(wasm)
149+
if err != nil {
150+
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
151+
return
152+
}
153+
} else if !wasmUtils.IsGzip(wasm) {
154+
rest.WriteErrorResponse(w, http.StatusBadRequest, "Invalid input file, use wasm binary or zip")
155+
return
156+
}
157+
158+
// build and sign the transaction, then broadcast to Tendermint
159+
msg := types.MsgStoreCodeAndInstantiateContract{
160+
Sender: req.BaseReq.From,
161+
WASMByteCode: wasm,
162+
Label: req.Label,
163+
Funds: req.Deposit,
164+
InitMsg: req.InitMsg,
165+
Admin: req.Admin,
166+
}
167+
168+
if err := msg.ValidateBasic(); err != nil {
169+
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
170+
return
171+
}
172+
173+
tx.WriteGeneratedTxResponse(cliCtx, w, req.BaseReq, &msg)
174+
}
175+
}
176+
121177
func executeContractHandlerFn(cliCtx client.Context) http.HandlerFunc {
122178
return func(w http.ResponseWriter, r *http.Request) {
123179
var req executeContractReq

x/wasm/common_test.go

+14
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,17 @@ func parseInitResponse(t *testing.T, data []byte) string {
3131
require.NoError(t, err)
3232
return addr
3333
}
34+
35+
// ensures this returns a valid codeID and bech32 address and returns it
36+
func parseStoreAndInitResponse(t *testing.T, data []byte) (uint64, string) {
37+
var res MsgStoreCodeAndInstantiateContractResponse
38+
require.NoError(t, res.Unmarshal(data))
39+
require.NotEmpty(t, res.CodeID)
40+
require.NotEmpty(t, res.Address)
41+
addr := res.Address
42+
codeID := res.CodeID
43+
// ensure this is a valid sdk address
44+
_, err := sdk.AccAddressFromBech32(addr)
45+
require.NoError(t, err)
46+
return codeID, addr
47+
}

x/wasm/handler.go

+2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ func NewHandler(k *Keeper) sdk.Handler {
2828
res, err = msgServer.StoreCode(sdk.WrapSDKContext(ctx), msg)
2929
case *MsgInstantiateContract:
3030
res, err = msgServer.InstantiateContract(sdk.WrapSDKContext(ctx), msg)
31+
case *MsgStoreCodeAndInstantiateContract:
32+
res, err = msgServer.StoreCodeAndInstantiateContract(sdk.WrapSDKContext(ctx), msg)
3133
case *MsgExecuteContract:
3234
res, err = msgServer.ExecuteContract(sdk.WrapSDKContext(ctx), msg)
3335
case *MsgMigrateContract:

0 commit comments

Comments
 (0)