Skip to content

Commit

Permalink
wallet: allow complex contract verification schemes, close nspcc-dev#…
Browse files Browse the repository at this point in the history
…3015

This was recently added in neotest, but working with the real RPC is
still not enjoyable. This commit extends `wallet.Account` with
invocation script builder to aid network fee calculations and signing.

Signed-off-by: Evgenii Stratonikov <[email protected]>
  • Loading branch information
fyfyrchik committed Dec 8, 2023
1 parent 441eb8a commit 24793ec
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 0 deletions.
8 changes: 8 additions & 0 deletions pkg/rpcclient/actor/maker.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,14 @@ func (a *Actor) MakeUnsignedUncheckedRun(script []byte, sysFee int64, attrs []tr
for i := range a.signers {
if !a.signers[i].Account.Contract.Deployed {
tx.Scripts[i].VerificationScript = a.signers[i].Account.Contract.Script
continue
}
if build := a.signers[i].Account.Contract.InvocationScript; build != nil {
invoc, err := build(tx)
if err != nil {
return nil, fmt.Errorf("building witness for contract signer: %w", err)
}
tx.Scripts[i].InvocationScript = invoc
}
}
// CalculateNetworkFee doesn't call Hash or Size, only serializes the
Expand Down
36 changes: 36 additions & 0 deletions pkg/wallet/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import (
"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/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"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"
)

Expand Down Expand Up @@ -55,6 +57,12 @@ type Contract struct {

// Indicates whether the contract has been deployed to the blockchain.
Deployed bool `json:"deployed"`

// InvocationScript returns invocation script for deployed contracts.
// In case contract is not deployed or has 0 arguments, this field is ignored.
// It might be executed on a partially formed tx, and is primarily needed to properly
// calculate network fee for complex contract signers.
InvocationScript func(tx *transaction.Transaction) ([]byte, error) `json:"-"`
}

// ContractParam is a descriptor of a contract parameter
Expand All @@ -78,6 +86,29 @@ func NewAccount() (*Account, error) {
return NewAccountFromPrivateKey(priv), nil
}

// NewContractAccount creates a contract account belonging to some deployed contract.
// SignTx can be called on this account with no error and will create invocation script,
// which puts provided arguments on stack for use in `verify`.
func NewContractAccount(hash util.Uint160, args ...any) *Account {
return &Account{
Address: address.Uint160ToString(hash),
Contract: &Contract{
Parameters: make([]ContractParam, len(args)),
Deployed: true,
InvocationScript: func(tx *transaction.Transaction) ([]byte, error) {
w := io.NewBufBinWriter()
for i := range args {
emit.Any(w.BinWriter, args[i])
}
if w.Err != nil {
return nil, w.Err
}
return w.Bytes(), nil
},
},
}
}

// SignTx signs transaction t and updates it's Witnesses.
func (a *Account) SignTx(net netmode.Magic, t *transaction.Transaction) error {
var (
Expand Down Expand Up @@ -108,6 +139,11 @@ func (a *Account) SignTx(net netmode.Magic, t *transaction.Transaction) error {
VerificationScript: a.Contract.Script, // Can be nil for deployed contract.
})
}
if a.Contract.Deployed && a.Contract.InvocationScript != nil {
invoc, err := a.Contract.InvocationScript(t)
t.Scripts[pos].InvocationScript = invoc
return err
}
if len(a.Contract.Parameters) == 0 {
return nil
}
Expand Down

0 comments on commit 24793ec

Please sign in to comment.