Some checks failed
Tests and linters / Run gofumpt (pull_request) Successful in 3m21s
Pre-commit hooks / Pre-commit (pull_request) Successful in 4m10s
Tests and linters / Tests (pull_request) Successful in 4m16s
DCO action / DCO (pull_request) Failing after 4m29s
Vulncheck / Vulncheck (pull_request) Successful in 5m44s
Tests and linters / Tests with -race (pull_request) Successful in 6m13s
Build / Build Components (pull_request) Successful in 6m23s
Tests and linters / Staticcheck (pull_request) Successful in 6m25s
Tests and linters / gopls check (pull_request) Successful in 6m27s
Tests and linters / Lint (pull_request) Successful in 7m27s
Currently, we allow using `--local-dump` in `morph init` command. We also have this flag in other commands, but no `--protocol` which is also needed. And in new command we do not have the ability to use local dump at all. This commit makes it possible to work either with an RPC or with local-dump in every command. Refs #1035. Refs TrueCloudLab/frostfs-dev-env#42. Writing gopatch this time was really satisfying, btw: ``` @@ var flags expression @@ -flags.StringP(commonflags.EndpointFlag, ...) +commonflags.InitRPC(flags) @@ var flags expression @@ -_ = viper.BindPFlag(commonflags.EndpointFlag, flags.Lookup(...)) +commonflags.BindRPC(flags) ``` Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
236 lines
7.3 KiB
Go
236 lines
7.3 KiB
Go
package contract
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
|
"github.com/nspcc-dev/neo-go/cli/cmdargs"
|
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
|
"github.com/nspcc-dev/neo-go/pkg/services/rpcsrv/params"
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
|
"github.com/spf13/cobra"
|
|
"github.com/spf13/viper"
|
|
)
|
|
|
|
const (
|
|
contractPathFlag = "contract"
|
|
updateFlag = "update"
|
|
)
|
|
|
|
var DeployCmd = &cobra.Command{
|
|
Use: "deploy",
|
|
Short: "Deploy additional smart-contracts",
|
|
Long: `Deploy additional smart-contract which are not related to core.
|
|
All contracts are deployed by the committee, so access to the alphabet wallets is required.
|
|
Optionally, arguments can be provided to be passed to a contract's _deploy function.
|
|
The syntax is the same as for 'neo-go contract testinvokefunction' command.
|
|
Compiled contract file name must contain '_contract.nef' suffix.
|
|
Contract's manifest file name must be 'config.json'.
|
|
NNS name is taken by stripping '_contract.nef' from the NEF file (similar to frostfs contracts).`,
|
|
PreRun: func(cmd *cobra.Command, _ []string) {
|
|
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
|
commonflags.BindRPC(cmd.Flags())
|
|
},
|
|
RunE: deployContractCmd,
|
|
}
|
|
|
|
func init() {
|
|
ff := DeployCmd.Flags()
|
|
|
|
ff.String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
|
_ = DeployCmd.MarkFlagFilename(commonflags.AlphabetWalletsFlag)
|
|
commonflags.InitRPC(ff)
|
|
ff.String(contractPathFlag, "", "Path to the contract directory")
|
|
_ = DeployCmd.MarkFlagFilename(contractPathFlag)
|
|
|
|
ff.Bool(updateFlag, false, "Update an existing contract")
|
|
ff.String(commonflags.CustomZoneFlag, "frostfs", "Custom zone for NNS")
|
|
}
|
|
|
|
func deployContractCmd(cmd *cobra.Command, args []string) error {
|
|
v := viper.GetViper()
|
|
c, err := helper.NewInitializeContext(cmd, v)
|
|
if err != nil {
|
|
return fmt.Errorf("initialization error: %w", err)
|
|
}
|
|
defer c.Close()
|
|
|
|
ctrPath, _ := cmd.Flags().GetString(contractPathFlag)
|
|
ctrName, err := probeContractName(ctrPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cs, err := helper.ReadContract(ctrPath, ctrName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
r := management.NewReader(c.ReadOnlyInvoker)
|
|
nnsCs, err := helper.GetContractByID(r, 1)
|
|
if err != nil {
|
|
return fmt.Errorf("can't fetch NNS contract state: %w", err)
|
|
}
|
|
|
|
callHash := management.Hash
|
|
method := constants.DeployMethodName
|
|
zone, _ := cmd.Flags().GetString(commonflags.CustomZoneFlag)
|
|
domain := ctrName + "." + zone
|
|
isUpdate, _ := cmd.Flags().GetBool(updateFlag)
|
|
if isUpdate {
|
|
cs.Hash, err = helper.NNSResolveHash(c.ReadOnlyInvoker, nnsCs.Hash, domain)
|
|
if err != nil {
|
|
return fmt.Errorf("can't fetch contract hash from NNS: %w", err)
|
|
}
|
|
callHash = cs.Hash
|
|
method = constants.UpdateMethodName
|
|
} else {
|
|
cs.Hash = state.CreateContractHash(
|
|
c.CommitteeAcc.Contract.ScriptHash(),
|
|
cs.NEF.Checksum,
|
|
cs.Manifest.Name)
|
|
}
|
|
|
|
writer := io.NewBufBinWriter()
|
|
if err := emitDeploymentArguments(writer.BinWriter, args); err != nil {
|
|
return err
|
|
}
|
|
emit.Bytes(writer.BinWriter, cs.RawManifest)
|
|
emit.Bytes(writer.BinWriter, cs.RawNEF)
|
|
emit.Int(writer.BinWriter, 3)
|
|
emit.Opcodes(writer.BinWriter, opcode.PACK)
|
|
emit.AppCallNoArgs(writer.BinWriter, callHash, method, callflag.All)
|
|
emit.Opcodes(writer.BinWriter, opcode.DROP) // contract state on stack
|
|
if !isUpdate {
|
|
err := registerNNS(nnsCs, c, zone, domain, cs, writer)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if writer.Err != nil {
|
|
panic(fmt.Errorf("BUG: can't create deployment script: %w", writer.Err))
|
|
}
|
|
|
|
if err := c.SendCommitteeTx(writer.Bytes(), false); err != nil {
|
|
return err
|
|
}
|
|
return c.AwaitTx()
|
|
}
|
|
|
|
func registerNNS(nnsCs *state.Contract, c *helper.InitializeContext, zone string, domain string, cs *helper.ContractState, writer *io.BufBinWriter) error {
|
|
bw := io.NewBufBinWriter()
|
|
emit.Instruction(bw.BinWriter, opcode.INITSSLOT, []byte{1})
|
|
emit.AppCall(bw.BinWriter, nnsCs.Hash, "getPrice", callflag.All)
|
|
emit.Opcodes(bw.BinWriter, opcode.STSFLD0)
|
|
emit.AppCall(bw.BinWriter, nnsCs.Hash, "setPrice", callflag.All, 1)
|
|
|
|
start := bw.Len()
|
|
needRecord := false
|
|
|
|
ok, err := c.NNSRootRegistered(nnsCs.Hash, zone)
|
|
if err != nil {
|
|
return err
|
|
} else if !ok {
|
|
needRecord = true
|
|
|
|
emit.AppCall(bw.BinWriter, nnsCs.Hash, "register", callflag.All,
|
|
zone, c.CommitteeAcc.Contract.ScriptHash(),
|
|
constants.FrostfsOpsEmail, constants.NNSRefreshDefVal, constants.NNSRetryDefVal,
|
|
int64(constants.DefaultExpirationTime), constants.NNSTtlDefVal)
|
|
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
|
|
|
|
emit.AppCall(bw.BinWriter, nnsCs.Hash, "register", callflag.All,
|
|
domain, c.CommitteeAcc.Contract.ScriptHash(),
|
|
constants.FrostfsOpsEmail, constants.NNSRefreshDefVal, constants.NNSRetryDefVal,
|
|
int64(constants.DefaultExpirationTime), constants.NNSTtlDefVal)
|
|
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
|
|
} else {
|
|
s, ok, err := c.NNSRegisterDomainScript(nnsCs.Hash, cs.Hash, domain)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
needRecord = !ok
|
|
if len(s) != 0 {
|
|
bw.WriteBytes(s)
|
|
}
|
|
}
|
|
if needRecord {
|
|
emit.AppCall(bw.BinWriter, nnsCs.Hash, "deleteRecords", callflag.All, domain, int64(nns.TXT))
|
|
emit.AppCall(bw.BinWriter, nnsCs.Hash, "addRecord", callflag.All,
|
|
domain, int64(nns.TXT), address.Uint160ToString(cs.Hash))
|
|
}
|
|
|
|
if bw.Err != nil {
|
|
panic(fmt.Errorf("BUG: can't create deployment script: %w", writer.Err))
|
|
} else if bw.Len() != start {
|
|
writer.WriteBytes(bw.Bytes())
|
|
emit.Opcodes(writer.BinWriter, opcode.LDSFLD0, opcode.PUSH1, opcode.PACK)
|
|
emit.AppCallNoArgs(writer.BinWriter, nnsCs.Hash, "setPrice", callflag.All)
|
|
|
|
if needRecord {
|
|
c.Command.Printf("NNS: Set %s -> %s\n", domain, cs.Hash.StringLE())
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func emitDeploymentArguments(w *io.BinWriter, args []string) error {
|
|
_, ps, err := cmdargs.ParseParams(args, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(ps) == 0 {
|
|
emit.Opcodes(w, opcode.NEWARRAY0)
|
|
return nil
|
|
}
|
|
|
|
if len(ps) != 1 {
|
|
return fmt.Errorf("at most one argument is expected for deploy, got %d", len(ps))
|
|
}
|
|
|
|
// We could emit this directly, but round-trip through JSON is more robust.
|
|
// This a CLI, so optimizing the conversion is not worth the effort.
|
|
data, err := json.Marshal(ps)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var pp params.Params
|
|
if err := json.Unmarshal(data, &pp); err != nil {
|
|
return err
|
|
}
|
|
return params.ExpandArrayIntoScript(w, pp)
|
|
}
|
|
|
|
func probeContractName(ctrPath string) (string, error) {
|
|
ds, err := os.ReadDir(ctrPath)
|
|
if err != nil {
|
|
return "", fmt.Errorf("can't read directory: %w", err)
|
|
}
|
|
|
|
var ctrName string
|
|
for i := range ds {
|
|
if strings.HasSuffix(ds[i].Name(), "_contract.nef") {
|
|
ctrName = strings.TrimSuffix(ds[i].Name(), "_contract.nef")
|
|
break
|
|
}
|
|
}
|
|
|
|
if ctrName == "" {
|
|
return "", fmt.Errorf("can't find any NEF files in %s", ctrPath)
|
|
}
|
|
return ctrName, nil
|
|
}
|