Skip to content

Commit

Permalink
Merge pull request #1125 from onflow/chasefleming/flix-integratin
Browse files Browse the repository at this point in the history
Execute FLIX scripts/transactions
  • Loading branch information
chasefleming authored Aug 9, 2023
2 parents 2dcfe06 + b5e363e commit 103fe31
Show file tree
Hide file tree
Showing 8 changed files with 209 additions and 33 deletions.
1 change: 1 addition & 0 deletions cmd/flow/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ func main() {
// super commands
super.SetupCommand.AddToParent(cmd)
super.DevCommand.AddToParent(cmd)
super.FlixCommand.AddToParent(cmd)

// structured commands
cmd.AddCommand(settings.Cmd)
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ require (
github.com/multiformats/go-varint v0.0.7 // indirect
github.com/onflow/atree v0.6.0 // indirect
github.com/onflow/cadence-tools/lint v0.11.1 // indirect
github.com/onflow/flixkit-go v0.1.0 // indirect
github.com/onflow/flow-archive v1.3.4-0.20230503192214-9e81e82d4dcc // indirect
github.com/onflow/flow-core-contracts/lib/go/contracts v1.2.4-0.20230703193002-53362441b57d // indirect
github.com/onflow/flow-ft/lib/go/contracts v0.7.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -781,6 +781,8 @@ github.com/onflow/cadence-tools/test v0.10.0 h1:tcNjOFXl7ZuuvgQ3uS36ENd8l3RkSBv4
github.com/onflow/cadence-tools/test v0.10.0/go.mod h1:eIvZOzFdi4JR2x6ekQfN8veQy04ZlZYbeeiQF/oZ0zM=
github.com/onflow/fcl-dev-wallet v0.7.2 h1:ZwhpzDakcZn9rHiIr52LwkdPh1MpUPbxA/PwCVaZ2SA=
github.com/onflow/fcl-dev-wallet v0.7.2/go.mod h1:kc42jkiuoPJmxMRFjfbRO9XvnR/3XLheaOerxVMDTiw=
github.com/onflow/flixkit-go v0.1.0 h1:3nH+1z+D+0YlmEJk9zYtvD8p4eUl2wJtiV6ZCgM7vYE=
github.com/onflow/flixkit-go v0.1.0/go.mod h1:gPffHQ6jDyuNtLG6W9C6FGvDZmcOb9CkvsJYKY0Opc4=
github.com/onflow/flow-archive v1.3.4-0.20230503192214-9e81e82d4dcc h1:C4ZniFeOv+pHlDLJdGc/4e3NklSjVuvaXKN47980gnY=
github.com/onflow/flow-archive v1.3.4-0.20230503192214-9e81e82d4dcc/go.mod h1:UPsvKk/37Atosif4wlBl3gsLbGJyGpdXYpXDsWtMVBE=
github.com/onflow/flow-core-contracts/lib/go/contracts v1.2.4-0.20230703193002-53362441b57d h1:B7PdhdUNkve5MVrekWDuQf84XsGBxNZ/D3x+QQ8XeVs=
Expand Down
21 changes: 13 additions & 8 deletions internal/scripts/execute.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,13 @@ import (
"github.com/onflow/flow-cli/internal/command"
)

type flagsScripts struct {
type Flags struct {
ArgsJSON string `default:"" flag:"args-json" info:"arguments in JSON-Cadence format"`
BlockID string `default:"" flag:"block-id" info:"block ID to execute the script at"`
BlockHeight uint64 `default:"" flag:"block-height" info:"block height to execute the script at"`
}

var scriptFlags = flagsScripts{}
var flags = Flags{}

var executeCommand = &command.Command{
Cmd: &cobra.Command{
Expand All @@ -47,7 +47,7 @@ var executeCommand = &command.Command{
Example: `flow scripts execute script.cdc "Meow" "Woof"`,
Args: cobra.MinimumNArgs(1),
},
Flags: &scriptFlags,
Flags: &flags,
Run: execute,
}

Expand All @@ -65,11 +65,16 @@ func execute(
return nil, fmt.Errorf("error loading script file: %w", err)
}

var scriptArgs []cadence.Value
return SendScript(code, args[1:], filename, flow, flags)
}

func SendScript(code []byte, argsArr []string, location string, flow flowkit.Services, scriptFlags Flags) (command.Result, error) {
var cadenceArgs []cadence.Value
var err error
if scriptFlags.ArgsJSON != "" {
scriptArgs, err = arguments.ParseJSON(scriptFlags.ArgsJSON)
cadenceArgs, err = arguments.ParseJSON(scriptFlags.ArgsJSON)
} else {
scriptArgs, err = arguments.ParseWithoutType(args[1:], code, filename)
cadenceArgs, err = arguments.ParseWithoutType(argsArr, code, location)
}

if err != nil {
Expand All @@ -89,8 +94,8 @@ func execute(
context.Background(),
flowkit.Script{
Code: code,
Args: scriptArgs,
Location: filename,
Args: cadenceArgs,
Location: location,
},
query,
)
Expand Down
2 changes: 1 addition & 1 deletion internal/scripts/scripts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func Test_Execute(t *testing.T) {

t.Run("Fail parsing invalid JSON args", func(t *testing.T) {
inArgs := []string{tests.TestScriptSimple.Filename}
scriptFlags.ArgsJSON = "invalid"
flags.ArgsJSON = "invalid"

result, err := execute(inArgs, command.GlobalFlags{}, util.NoLogger, rw, srv.Mock)
assert.Nil(t, result)
Expand Down
163 changes: 163 additions & 0 deletions internal/super/flix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/*
* Flow CLI
*
* Copyright 2019 Dapper Labs, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package super

import (
"context"
"encoding/hex"
"fmt"
"os"

"github.com/onflow/flixkit-go"

"github.com/onflow/flow-cli/flowkit/output"
"github.com/onflow/flow-cli/internal/command"
"github.com/onflow/flow-cli/internal/scripts"
"github.com/onflow/flow-cli/internal/transactions"

"github.com/onflow/flow-cli/flowkit"

"github.com/spf13/cobra"
)

type flixFlags struct {
ArgsJSON string `default:"" flag:"args-json" info:"arguments in JSON-Cadence format"`
BlockID string `default:"" flag:"block-id" info:"block ID to execute the script at"`
BlockHeight uint64 `default:"" flag:"block-height" info:"block height to execute the script at"`
Signer string `default:"" flag:"signer" info:"Account name from configuration used to sign the transaction as proposer, payer and suthorizer"`
Proposer string `default:"" flag:"proposer" info:"Account name from configuration used as proposer"`
Payer string `default:"" flag:"payer" info:"Account name from configuration used as payer"`
Authorizers []string `default:"" flag:"authorizer" info:"Name of a single or multiple comma-separated accounts used as authorizers from configuration"`
Include []string `default:"" flag:"include" info:"Fields to include in the output"`
Exclude []string `default:"" flag:"exclude" info:"Fields to exclude from the output (events)"`
GasLimit uint64 `default:"1000" flag:"gas-limit" info:"transaction gas limit"`
}

var flags = flixFlags{}

var FlixCommand = &command.Command{
Cmd: &cobra.Command{
Use: "flix <id | name | path>",
Short: "Execute FLIX template with a given id, name, or local filename",
Example: "flow flix multiply 2 3",
Args: cobra.ArbitraryArgs,
GroupID: "super",
},
Flags: &flags,
RunS: execute,
}

type flixQueryTypes string

const (
flixName flixQueryTypes = "name"
flixPath flixQueryTypes = "path"
flixId flixQueryTypes = "id"
)

func isHex(str string) bool {
if len(str) != 64 {
return false
}
_, err := hex.DecodeString(str)
return err == nil
}

func isPath(path string) bool {
_, err := os.Stat(path)
return err == nil
}

func getType(s string) flixQueryTypes {
switch {
case isPath(s):
return flixPath
case isHex(s):
return flixId
default:
return flixName
}
}

func execute(
args []string,
_ command.GlobalFlags,
logger output.Logger,
flow flowkit.Services,
state *flowkit.State,
) (result command.Result, err error) {
flixService := flixkit.NewFlixService(&flixkit.Config{})
ctx := context.Background()
var template *flixkit.FlowInteractionTemplate
flixQuery := args[0]

switch getType(flixQuery) {
case flixId:
template, err = flixService.GetFlixByID(ctx, flixQuery)
if err != nil {
return nil, fmt.Errorf("could not find flix with id %s: %w", flixQuery, err)
}

case flixName:
template, err = flixService.GetFlix(ctx, flixQuery)
if err != nil {
return nil, fmt.Errorf("could not find flix with name %s: %w", flixQuery, err)
}

case flixPath:
file, err := os.ReadFile(flixQuery)
if err != nil {
return nil, fmt.Errorf("could not read flix file %s: %w", flixQuery, err)
}
template, err = flixkit.ParseFlix(string(file))
if err != nil {
return nil, fmt.Errorf("could not parse flix from file %s: %w", flixQuery, err)
}

default:
return nil, fmt.Errorf("invalid flix query type: %s", flixQuery)
}

cadenceWithImportsReplaced, err := template.GetAndReplaceCadenceImports(flow.Network().Name)
if err != nil {
logger.Error("could not replace imports")
return nil, err
}

if template.IsScript() {
scriptsFlags := scripts.Flags{
ArgsJSON: flags.ArgsJSON,
BlockID: flags.BlockID,
BlockHeight: flags.BlockHeight,
}
return scripts.SendScript([]byte(cadenceWithImportsReplaced), args[1:], "", flow, scriptsFlags)
}

transactionFlags := transactions.Flags{
ArgsJSON: flags.ArgsJSON,
Signer: flags.Signer,
Proposer: flags.Proposer,
Payer: flags.Payer,
Authorizers: flags.Authorizers,
Include: flags.Include,
Exclude: flags.Exclude,
GasLimit: flags.GasLimit,
}
return transactions.SendTransaction([]byte(cadenceWithImportsReplaced), args[1:], "", flow, state, transactionFlags)
}
26 changes: 15 additions & 11 deletions internal/transactions/send.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import (
"github.com/onflow/flow-cli/internal/command"
)

type flagsSend struct {
type Flags struct {
ArgsJSON string `default:"" flag:"args-json" info:"arguments in JSON-Cadence format"`
Signer string `default:"" flag:"signer" info:"Account name from configuration used to sign the transaction as proposer, payer and suthorizer"`
Proposer string `default:"" flag:"proposer" info:"Account name from configuration used as proposer"`
Expand All @@ -44,7 +44,7 @@ type flagsSend struct {
GasLimit uint64 `default:"1000" flag:"gas-limit" info:"transaction gas limit"`
}

var sendFlags = flagsSend{}
var flags = Flags{}

var sendCommand = &command.Command{
Cmd: &cobra.Command{
Expand All @@ -53,7 +53,7 @@ var sendCommand = &command.Command{
Args: cobra.MinimumNArgs(1),
Example: `flow transactions send tx.cdc "Hello world"`,
},
Flags: &sendFlags,
Flags: &flags,
RunS: send,
}

Expand All @@ -64,8 +64,17 @@ func send(
flow flowkit.Services,
state *flowkit.State,
) (result command.Result, err error) {
codeFilename := args[0]
filename := args[0]

code, err := state.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("error loading transaction file: %w", err)
}

return SendTransaction(code, args, filename, flow, state, flags)
}

func SendTransaction(code []byte, args []string, location string, flow flowkit.Services, state *flowkit.State, sendFlags Flags) (result command.Result, err error) {
proposerName := sendFlags.Proposer
var proposer *accounts.Account
if proposerName != "" {
Expand Down Expand Up @@ -112,16 +121,11 @@ func send(
authorizers = append(authorizers, *signer)
}

code, err := state.ReadFile(codeFilename)
if err != nil {
return nil, fmt.Errorf("error loading transaction file: %w", err)
}

var transactionArgs []cadence.Value
if sendFlags.ArgsJSON != "" {
transactionArgs, err = arguments.ParseJSON(sendFlags.ArgsJSON)
} else {
transactionArgs, err = arguments.ParseWithoutType(args[1:], code, codeFilename)
transactionArgs, err = arguments.ParseWithoutType(args, code, location)
}
if err != nil {
return nil, fmt.Errorf("error parsing transaction arguments: %w", err)
Expand All @@ -134,7 +138,7 @@ func send(
Authorizers: authorizers,
Payer: *payer,
},
flowkit.Script{Code: code, Args: transactionArgs, Location: codeFilename},
flowkit.Script{Code: code, Args: transactionArgs, Location: location},
sendFlags.GasLimit,
)

Expand Down
26 changes: 13 additions & 13 deletions internal/transactions/transactions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,8 @@ func Test_Send(t *testing.T) {

t.Run("Success", func(t *testing.T) {
const gas = uint64(1000)
sendFlags.GasLimit = gas
inArgs := []string{tests.TransactionArgString.Filename, "foo"}
flags.GasLimit = gas
inArgs := []string{tests.TransactionArgString.Filename}

srv.SendTransaction.Run(func(args mock.Arguments) {
roles := args.Get(1).(transactions.AccountRoles)
Expand All @@ -160,33 +160,33 @@ func Test_Send(t *testing.T) {
})

t.Run("Fail non-existing account", func(t *testing.T) {
sendFlags.Proposer = "invalid"
flags.Proposer = "invalid"
_, err := send([]string{""}, command.GlobalFlags{}, util.NoLogger, srv.Mock, state)
assert.EqualError(t, err, "proposer account: [invalid] doesn't exists in configuration")
sendFlags.Proposer = "" // reset
flags.Proposer = "" // reset

sendFlags.Payer = "invalid"
flags.Payer = "invalid"
_, err = send([]string{""}, command.GlobalFlags{}, util.NoLogger, srv.Mock, state)
assert.EqualError(t, err, "payer account: [invalid] doesn't exists in configuration")
sendFlags.Payer = "" // reset
flags.Payer = "" // reset

sendFlags.Authorizers = []string{"invalid"}
flags.Authorizers = []string{"invalid"}
_, err = send([]string{""}, command.GlobalFlags{}, util.NoLogger, srv.Mock, state)
assert.EqualError(t, err, "authorizer account: [invalid] doesn't exists in configuration")
sendFlags.Authorizers = nil // reset
flags.Authorizers = nil // reset

sendFlags.Signer = "invalid"
flags.Signer = "invalid"
_, err = send([]string{""}, command.GlobalFlags{}, util.NoLogger, srv.Mock, state)
assert.EqualError(t, err, "signer account: [invalid] doesn't exists in configuration")
sendFlags.Signer = "" // reset
flags.Signer = "" // reset
})

t.Run("Fail signer and payer flag", func(t *testing.T) {
sendFlags.Proposer = config.DefaultEmulator.ServiceAccount
sendFlags.Signer = config.DefaultEmulator.ServiceAccount
flags.Proposer = config.DefaultEmulator.ServiceAccount
flags.Signer = config.DefaultEmulator.ServiceAccount
_, err := send([]string{""}, command.GlobalFlags{}, util.NoLogger, srv.Mock, state)
assert.EqualError(t, err, "signer flag cannot be combined with payer/proposer/authorizer flags")
sendFlags.Signer = "" // reset
flags.Signer = "" // reset
})

t.Run("Fail loading transaction file", func(t *testing.T) {
Expand Down

0 comments on commit 103fe31

Please sign in to comment.