Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

neotest: Add contract signer support #3233

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pkg/core/native/native_test/ledger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
30 changes: 24 additions & 6 deletions pkg/neotest/basic.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
Expand All @@ -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()

AnnaShaleva marked this conversation as resolved.
Show resolved Hide resolved
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)
}
Expand Down
79 changes: 79 additions & 0 deletions pkg/neotest/signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package neotest

import (
"bytes"
"errors"
"fmt"
"sort"
"testing"
Expand All @@ -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"
Expand Down Expand Up @@ -44,6 +47,13 @@ type MultiSigner interface {
Single(n int) SingleSigner
}

// ContractSigner is an interface for contract signer.
type ContractSigner interface {
AnnaShaleva marked this conversation as resolved.
Show resolved Hide resolved
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

Expand Down Expand Up @@ -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 {
roman-khimov marked this conversation as resolved.
Show resolved Hide resolved
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
}