neotest: Add contract signer support

Signed-off-by: Dmitrii Stepanov <dima-stepan@yandex.ru>
This commit is contained in:
Dmitrii Stepanov 2023-11-28 09:23:55 +03:00
parent 6c0c2a6a98
commit 7b53a0c239
3 changed files with 104 additions and 7 deletions

View file

@ -225,7 +225,7 @@ func TestLedger_GetTransactionSignersInteropAPI(t *testing.T) {
},
},
}}
neotest.AddNetworkFee(e.Chain, tx, c.Committee)
neotest.AddNetworkFee(t, e.Chain, tx, c.Committee)
neotest.AddSystemFee(e.Chain, tx, -1)
require.NoError(t, c.Committee.SignTx(e.Chain.GetConfig().Magic, tx))
c.AddNewBlock(t, tx)

View file

@ -106,7 +106,7 @@ func (e *Executor) SignTx(t testing.TB, tx *transaction.Transaction, sysFee int6
Scopes: transaction.Global,
})
}
AddNetworkFee(e.Chain, tx, signers...)
AddNetworkFee(t, e.Chain, tx, signers...)
AddSystemFee(e.Chain, tx, sysFee)
for _, acc := range signers {
@ -280,7 +280,7 @@ func NewDeployTxBy(t testing.TB, bc *core.Blockchain, signer Signer, c *Contract
Account: signer.ScriptHash(),
Scopes: transaction.Global,
}}
AddNetworkFee(bc, tx, signer)
AddNetworkFee(t, bc, tx, signer)
require.NoError(t, signer.SignTx(netmode.UnitTestNet, tx))
return tx
}
@ -297,13 +297,31 @@ func AddSystemFee(bc *core.Blockchain, tx *transaction.Transaction, sysFee int64
}
// AddNetworkFee adds network fee to the transaction.
func AddNetworkFee(bc *core.Blockchain, tx *transaction.Transaction, signers ...Signer) {
func AddNetworkFee(t testing.TB, bc *core.Blockchain, tx *transaction.Transaction, signers ...Signer) {
baseFee := bc.GetBaseExecFee()
size := io.GetVarSize(tx)
for _, sgr := range signers {
netFee, sizeDelta := fee.Calculate(baseFee, sgr.Script())
tx.NetworkFee += netFee
size += sizeDelta
if csgr, ok := sgr.(ContractSigner); ok {
sc, err := csgr.InvocationScript(tx)
require.NoError(t, err)
txCopy := *tx
ic, err := bc.GetTestVM(trigger.Verification, &txCopy, nil)
require.NoError(t, err)
ic.UseSigners(tx.Signers)
ic.VM.GasLimit = bc.GetMaxVerificationGAS()
require.NoError(t, bc.InitVerificationContext(ic, csgr.ScriptHash(), &transaction.Witness{InvocationScript: sc, VerificationScript: csgr.Script()}))
require.NoError(t, ic.VM.Run())
tx.NetworkFee += ic.VM.GasConsumed()
size += io.GetVarSize(sc) + io.GetVarSize(csgr.Script())
} else {
netFee, sizeDelta := fee.Calculate(baseFee, sgr.Script())
tx.NetworkFee += netFee
size += sizeDelta
}
}
tx.NetworkFee += int64(size)*bc.FeePerByte() + bc.CalculateAttributesFee(tx)
}

View file

@ -2,6 +2,7 @@ package neotest
import (
"bytes"
"errors"
"fmt"
"sort"
"testing"
@ -10,8 +11,10 @@ import (
"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/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
"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/wallet"
"github.com/stretchr/testify/require"
@ -44,6 +47,13 @@ type MultiSigner interface {
Single(n int) SingleSigner
}
// ContractSigner is an interface for contract signer.
type ContractSigner interface {
Signer
// InvocationScript returns an invocation script to be used as invocation script for contract-based witness.
InvocationScript(tx *transaction.Transaction) ([]byte, error)
}
// signer represents a simple-signature signer.
type signer wallet.Account
@ -179,3 +189,72 @@ func checkMultiSigner(t testing.TB, s Signer) {
require.Equal(t, h, accs[i].Contract.ScriptHash(), "inconsistent multi-signer accounts")
}
}
type contractSigner struct {
params func(tx *transaction.Transaction) []any
scriptHash util.Uint160
}
// NewContractSigner returns a contract signer for the provided contract hash.
// getInvParams must return params to be used as invocation script for contract-based witness.
func NewContractSigner(h util.Uint160, getInvParams func(tx *transaction.Transaction) []any) ContractSigner {
return &contractSigner{
scriptHash: h,
params: getInvParams,
}
}
// InvocationScript implements ContractSigner.
func (s *contractSigner) InvocationScript(tx *transaction.Transaction) ([]byte, error) {
params := s.params(tx)
script := io.NewBufBinWriter()
for i := range params {
emit.Any(script.BinWriter, params[i])
}
if script.Err != nil {
return nil, script.Err
}
return script.Bytes(), nil
}
// Script implements ContractSigner.
func (s *contractSigner) Script() []byte {
return []byte{}
}
// ScriptHash implements ContractSigner.
func (s *contractSigner) ScriptHash() util.Uint160 {
return s.scriptHash
}
// SignHashable implements ContractSigner.
func (s *contractSigner) SignHashable(uint32, hash.Hashable) []byte {
panic("not supported")
}
// SignTx implements ContractSigner.
func (s *contractSigner) SignTx(magic netmode.Magic, tx *transaction.Transaction) error {
pos := -1
for idx := range tx.Signers {
if tx.Signers[idx].Account.Equals(s.ScriptHash()) {
pos = idx
break
}
}
if pos < 0 {
return fmt.Errorf("signer %s not found", s.ScriptHash().String())
}
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
}
if len(tx.Scripts) == pos {
tx.Scripts = append(tx.Scripts, transaction.Witness{})
}
tx.Scripts[pos].InvocationScript = invoc
tx.Scripts[pos].VerificationScript = s.Script()
return nil
}