Skip to content

Flow-cli crashes when an array or dictionary are provided as contract constructor args through flow.json #1909

@rdlalmeida

Description

@rdlalmeida

Problem

If I create a Cadence contract that intends to receive an array (e.g. [UInt64], [String]), or a dictionary (e.g. {Int: String}), when I define the arguments in the deployments section in flow.json and try to deploy the contract with "flow project deploy", the flow-cli crashes with the following message:

runtime error: invalid memory address or nil pointer dereference
[] []
map[device:map[arch:amd64 num_cpu:8] os:map[name:linux] runtime:map[go_maxprocs:8 go_numcgocalls:10 go_numroutines:21 name:go version:go1.23.4] trace:map[span_id:fb8a7dcbb06c18f7 trace_id:09f46eebd528f6cf0cc4bd250733c68c]]
goroutine 1 [running]:
runtime/debug.Stack()
/root/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.23.4.linux-amd64/src/runtime/debug/stack.go:26 +0x5e
github.com/onflow/flow-cli/internal/command.initCrashReporting.func1(0xc0008fcc88, 0xc00004b4a0)
/go/src/github.com/onflow/flow-cli/internal/command/command.go:355 +0x21e
github.com/getsentry/sentry-go.(*Client).processEvent(0xc001802f00, 0xc0008fcc88?, 0xc00004b4a0, {0x3316c20, 0xc000a6c2c0})
/root/go/pkg/mod/github.com/getsentry/sentry-go@v0.31.1/client.go:628 +0x2d1
github.com/getsentry/sentry-go.(*Client).CaptureEvent(...)
/root/go/pkg/mod/github.com/getsentry/sentry-go@v0.31.1/client.go:444
github.com/getsentry/sentry-go.(*Client).RecoverWithContext(0xc001802f00, {0x0, 0x0}, {0x2784ac0?, 0x4a485b0?}, 0x60?, {0x3316c20, 0xc000a6c2c0})
/root/go/pkg/mod/github.com/getsentry/sentry-go@v0.31.1/client.go:495 +0x1d0
github.com/getsentry/sentry-go.(*Client).Recover(0x1?, {0x2784ac0?, 0x4a485b0?}, 0xc001d35008?, {0x3316c20?, 0xc000a6c2c0?})
/root/go/pkg/mod/github.com/getsentry/sentry-go@v0.31.1/client.go:459 +0x77
github.com/getsentry/sentry-go.(*Hub).Recover(0xc000c9e9f0, {0x2784ac0?, 0x4a485b0?})
/root/go/pkg/mod/github.com/getsentry/sentry-go@v0.31.1/hub.go:330 +0xc5
github.com/getsentry/sentry-go.Recover()
/root/go/pkg/mod/github.com/getsentry/sentry-go@v0.31.1/sentry.go:68 +0x32
panic({0x2784ac0?, 0x4a485b0?})
/root/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.23.4.linux-amd64/src/runtime/panic.go:785 +0x132
github.com/onflow/flowkit/v2/transactions.addAccountContractWithArgs(0xc0019ba210, {{0xc0008ce5e8?, 0x1882?}, {0xc000e61980?, 0xe?}}, {0xc001911000, 0x1, 0x28?})
/root/go/pkg/mod/github.com/onflow/flowkit/v2@v2.2.0/transactions/transaction.go:121 +0x34a
github.com/onflow/flowkit/v2/transactions.NewAddAccountContract(...)
/root/go/pkg/mod/github.com/onflow/flowkit/v2@v2.2.0/transactions/transaction.go:82
github.com/onflow/flowkit/v2.(*Flowkit).AddContract(0xc0019b43c0, {0x3337270, 0x4d343e0}, 0xc0019ba210, {{0xc000d2c000, 0x1840, 0x1a40}, {0xc001911000, 0x1, 0x1}, ...}, ...)
/root/go/pkg/mod/github.com/onflow/flowkit/v2@v2.2.0/flowkit.go:305 +0x325
github.com/onflow/flowkit/v2.(*Flowkit).DeployProject(0xc0019b43c0, {0x3337270, 0x4d343e0}, 0xc00196a780)
/root/go/pkg/mod/github.com/onflow/flowkit/v2@v2.2.0/flowkit.go:739 +0x5fc
github.com/onflow/flow-cli/internal/project.deploy({0x0?, 0x0?, 0x0?}, {{0x0, 0x0}, {0x2bc5ba3, 0x4}, {0x0, 0x0}, {0x0, ...}, ...}, ...)
/go/src/github.com/onflow/flow-cli/internal/project/deploy.go:80 +0x195
github.com/onflow/flow-cli/internal/command.Command.AddToParent.func1(0xc001838500?, {0x4d343e0, 0x0, 0x0})
/go/src/github.com/onflow/flow-cli/internal/command/command.go:147 +0x543
github.com/spf13/cobra.(*Command).execute(0x4aaf600, {0xc000052260, 0x0, 0x0})
/root/go/pkg/mod/github.com/spf13/cobra@v1.9.1/command.go:1019 +0xa7b
github.com/spf13/cobra.(*Command).ExecuteC(0xc000a4bb08)
/root/go/pkg/mod/github.com/spf13/cobra@v1.9.1/command.go:1148 +0x40c
github.com/spf13/cobra.(*Command).Execute(...)
/root/go/pkg/mod/github.com/spf13/cobra@v1.9.1/command.go:1071
main.main()
/go/src/github.com/onflow/flow-cli/cmd/flow/main.go:131 +0x1052

Steps to Reproduce

I've created a simple ExampleNFTContract.cdc to establish a base line:

access(all) contract ExampleContract {
      access(all) let param1: String
  
      init(par1: String) {
          self.param1 = par1
      }
  }

Simplest contract possible that receives a single String as argument for the contract constructor and sets it to a single internal variable

If I edit flow.json to provide that one String argument such as:

...
deployments": {
    "emulator": {
      "emulator-account": [
        {
          "name": "ExampleContract",
          "args": [
            {
              "type": "String",
              "value": "SomeExampleString"
            }
          ]
        }
      ]
    }
  }

And try to deploy it to the flow-cli emulator, it works 100% of the time. The same is valid for all sorts of Int and UInt, Bool, etc. Basic types have no problem in being used as contract constructors.

Image

But, if I change this simple parameter to an array of any type:

access(all) contract ExampleContract {
    
    access(all) let param1: [Int]

    init(par1: [Int]) {
        self.param1 = par1
    }
}

And adjust flow.json accordingly:

...
"deployments": {
        "emulator": {
            "emulator-account": [
                {
                    "name": "ExampleContract",
                    "args": [
                        {
                            "type": "Array",
                            "value": [
                                {
                                    "type": "Int",
                                    "value": "1"
                                },
                                {
                                    "type": "Int",
                                    "value": "5"
                                },
                                {
                                    "type": "Int",
                                    "value": "7"
                                }
                            ]
                        }
                    ]
                }
            ]
        }
    }

thus using the format defined for JSON-Cadence in https://cadence-lang.org/docs/json-cadence-spec.
But when I try to deploy this simple contract using the same command:

Image

The rest of the error output is indicated in the problem description.

NOTE 1: This behaviour occurs with all types of arrays and dictionaries. I haven't tested composite elements such as structs, resources, ect.

NOTE 2: I've removed the previous contract before attempted a new deployment (using flow accounts remove-contract ExampleContract

NOTE 3: The Test.deployContract module from the Test module does not have this problem, i.e., in a test file, if I do:

let param1: [Int] = [1, 5, 7]

let err: Test.Error? = Test.deployContract(
name: "ExampleContract",
path: "../contracts/ExampleContract.cdc",
arguments: [param1]
)

This one works fine, with arrays and dictionaries (haven't tried with anything more complex). It's just when I try to pass the constructor argument through the deployments section in flow.json.

Context

I'm working in a more complex contract of which I intend to use either a [UInt64], or a {UInt64: String} as a contract constructor argument to set a access(self) parameter. So far I've been using a workaround where I provide a String with a specific format as argument and then parse it to a [UInt64] internally in the constructor, but this solution is not ideal.
This ExampleContract serves to show how fundamental this problem really is.

I'm currently using flow-cli v2.2.8, but I've detected this behaviour for quite some time now.
I'm developing the Cadence project in a Linux Mint (Debian) machine, using VSCode and the Cadence plugin

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions