package helper import ( "fmt" "github.com/google/uuid" "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/neorpc/result" "github.com/nspcc-dev/neo-go/pkg/rpcclient/actor" "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker" "github.com/nspcc-dev/neo-go/pkg/smartcontract/context" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/spf13/viper" ) // LocalActor is a kludge, do not use it outside of the morph commands. type LocalActor struct { neoActor *actor.Actor accounts []*wallet.Account Invoker *invoker.Invoker rpcInvoker invoker.RPCInvoke } type AlphabetWallets struct { Label string Path string } func (a *AlphabetWallets) GetAccount(v *viper.Viper) ([]*wallet.Account, error) { w, err := GetAlphabetWallets(v, a.Path) if err != nil { return nil, err } var accounts []*wallet.Account for _, wall := range w { acc, err := GetWalletAccount(wall, a.Label) if err != nil { return nil, err } accounts = append(accounts, acc) } return accounts, nil } type RegularWallets struct{ Path string } func (r *RegularWallets) GetAccount() ([]*wallet.Account, error) { w, err := getRegularWallet(r.Path) if err != nil { return nil, err } return []*wallet.Account{w.GetAccount(w.GetChangeAddress())}, nil } // NewLocalActor create LocalActor with accounts form provided wallets. // In case of empty wallets provided created actor with dummy account only for read operation. // // If wallets are provided, the contract client will use accounts with accName name from these wallets. // To determine which account name should be used in a contract client, refer to how the contract // verifies the transaction signature. func NewLocalActor(c actor.RPCActor, alphabet *AlphabetWallets, regularWallets ...*RegularWallets) (*LocalActor, error) { var act *actor.Actor var accounts []*wallet.Account var signers []actor.SignerAccount if alphabet != nil { account, err := alphabet.GetAccount(viper.GetViper()) if err != nil { return nil, err } accounts = append(accounts, account...) signers = append(signers, actor.SignerAccount{ Signer: transaction.Signer{ Account: account[0].Contract.ScriptHash(), Scopes: transaction.Global, }, Account: account[0], }) } for _, w := range regularWallets { if w == nil { continue } account, err := w.GetAccount() if err != nil { return nil, err } accounts = append(accounts, account...) signers = append(signers, actor.SignerAccount{ Signer: transaction.Signer{ Account: account[0].Contract.ScriptHash(), Scopes: transaction.Global, }, Account: account[0], }) } act, err := actor.New(c, signers) if err != nil { return nil, err } return &LocalActor{ neoActor: act, accounts: accounts, Invoker: &act.Invoker, rpcInvoker: c, }, nil } func (a *LocalActor) SendCall(contract util.Uint160, method string, params ...any) (util.Uint256, uint32, error) { tx, err := a.neoActor.MakeCall(contract, method, params...) if err != nil { return util.Uint256{}, 0, err } err = a.resign(tx) if err != nil { return util.Uint256{}, 0, err } return a.neoActor.Send(tx) } func (a *LocalActor) SendRun(script []byte) (util.Uint256, uint32, error) { tx, err := a.neoActor.MakeRun(script) if err != nil { return util.Uint256{}, 0, err } err = a.resign(tx) if err != nil { return util.Uint256{}, 0, err } return a.neoActor.Send(tx) } // resign is used to sign tx with committee accounts. // Inside the methods `MakeCall` and `SendRun` of the NeoGO's actor transaction is signing by committee account, // because actor uses committee wallet. // But it is not enough, need to sign with another committee accounts. func (a *LocalActor) resign(tx *transaction.Transaction) error { if len(a.accounts[0].Contract.Parameters) > 1 { // Use parameter context to avoid dealing with signature order. network := a.neoActor.GetNetwork() pc := context.NewParameterContext("", network, tx) h := a.accounts[0].Contract.ScriptHash() for _, acc := range a.accounts { priv := acc.PrivateKey() sign := priv.SignHashable(uint32(network), tx) if err := pc.AddSignature(h, acc.Contract, priv.PublicKey(), sign); err != nil { return fmt.Errorf("can't add signature: %w", err) } if len(pc.Items[h].Signatures) == len(acc.Contract.Parameters) { break } } w, err := pc.GetWitness(h) if err != nil { return fmt.Errorf("incomplete signature: %w", err) } tx.Scripts[0] = *w } return nil } func (a *LocalActor) Wait(h util.Uint256, vub uint32, err error) (*state.AppExecResult, error) { return a.neoActor.Wait(h, vub, err) } func (a *LocalActor) Sender() util.Uint160 { return a.neoActor.Sender() } func (a *LocalActor) Call(contract util.Uint160, operation string, params ...any) (*result.Invoke, error) { return a.neoActor.Call(contract, operation, params...) } func (a *LocalActor) CallAndExpandIterator(_ util.Uint160, _ string, _ int, _ ...any) (*result.Invoke, error) { panic("unimplemented") } func (a *LocalActor) TerminateSession(_ uuid.UUID) error { panic("unimplemented") } func (a *LocalActor) TraverseIterator(sessionID uuid.UUID, iterator *result.Iterator, num int) ([]stackitem.Item, error) { return a.neoActor.TraverseIterator(sessionID, iterator, num) } func (a *LocalActor) MakeRun(_ []byte) (*transaction.Transaction, error) { panic("unimplemented") } func (a *LocalActor) MakeUnsignedCall(_ util.Uint160, _ string, _ []transaction.Attribute, _ ...any) (*transaction.Transaction, error) { panic("unimplemented") } func (a *LocalActor) MakeUnsignedRun(_ []byte, _ []transaction.Attribute) (*transaction.Transaction, error) { panic("unimplemented") } func (a *LocalActor) MakeCall(_ util.Uint160, _ string, _ ...any) (*transaction.Transaction, error) { panic("unimplemented") } func (a *LocalActor) GetRPCInvoker() invoker.RPCInvoke { return a.rpcInvoker }