package tests import ( "errors" "path" "testing" "github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/neotest" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/stretchr/testify/require" ) const proxyPath = "../proxy" func deployProxyContract(t *testing.T, e *neotest.Executor, addrNetmap util.Uint160) util.Uint160 { args := make([]any, 1) args[0] = addrNetmap c := neotest.CompileFile(t, e.CommitteeHash, proxyPath, path.Join(proxyPath, "config.yml")) e.DeployContract(t, c, args) return c.Hash } func newProxyInvoker(t *testing.T) *neotest.ContractInvoker { e := newExecutor(t) ctrNetmap := neotest.CompileFile(t, e.CommitteeHash, netmapPath, path.Join(netmapPath, "config.yml")) ctrBalance := neotest.CompileFile(t, e.CommitteeHash, balancePath, path.Join(balancePath, "config.yml")) ctrContainer := neotest.CompileFile(t, e.CommitteeHash, containerPath, path.Join(containerPath, "config.yml")) ctrProxy := neotest.CompileFile(t, e.CommitteeHash, proxyPath, path.Join(proxyPath, "config.yml")) deployNetmapContract(t, e, ctrBalance.Hash, ctrContainer.Hash) deployProxyContract(t, e, ctrNetmap.Hash) return e.CommitteeInvoker(ctrProxy.Hash) } func TestVerify(t *testing.T) { e := newProxyInvoker(t) acc := e.NewAccount(t) gas := e.NewInvoker(e.NativeHash(t, nativenames.Gas), e.Validator) gas.Invoke(t, true, "transfer", e.Validator.ScriptHash(), e.Hash, 100_0000_0000, nil) t.Run("proxy + committee", func(t *testing.T) { s := &proxySigner{contract: e.Hash, account: e.CommitteeHash} tx := e.PrepareInvocation(t, []byte{byte(opcode.RET)}, []neotest.Signer{s, e.Committee}) require.NoError(t, e.Chain.VerifyTx(tx)) }) t.Run("proxy + custom account", func(t *testing.T) { s := &proxySigner{contract: e.Hash, account: acc.ScriptHash()} t.Run("bad, only proxy", func(t *testing.T) { tx := e.PrepareInvocation(t, []byte{byte(opcode.RET)}, []neotest.Signer{s, acc}) require.Error(t, e.Chain.VerifyTx(tx)) }) e.Invoke(t, stackitem.Null{}, "addAccount", s.account) tx := e.PrepareInvocation(t, []byte{byte(opcode.RET)}, []neotest.Signer{s, acc}) require.NoError(t, e.Chain.VerifyTx(tx)) }) } type proxySigner struct { contract util.Uint160 account util.Uint160 } var _ neotest.ContractSigner = (*proxySigner)(nil) func (s *proxySigner) Script() []byte { return nil } func (s *proxySigner) ScriptHash() util.Uint160 { return s.contract } func (s *proxySigner) SignHashable(uint32, hash.Hashable) []byte { panic("not implemented") } func (s *proxySigner) SignTx(_ netmode.Magic, tx *transaction.Transaction) error { pos := -1 for i := range tx.Signers { if tx.Signers[i].Account.Equals(s.contract) { pos = i break } } if pos < 0 { return errors.New("transaction is not signed by this account") } if len(tx.Scripts) < pos { return errors.New("transaction is not yet signed by the previous signer") } invoc, err := s.InvocationScript(tx) if err != nil { return err } w := transaction.Witness{InvocationScript: invoc} if len(tx.Scripts) == pos { tx.Scripts = append(tx.Scripts, w) } else { tx.Scripts[pos].InvocationScript = invoc } return nil } func (s *proxySigner) InvocationScript(tx *transaction.Transaction) ([]byte, error) { w := io.NewBufBinWriter() emit.Any(w.BinWriter, s.account) return w.Bytes(), nil }