2020-09-01 13:24:01 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/hex"
|
2021-09-14 12:02:54 +00:00
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
2020-09-01 13:24:01 +00:00
|
|
|
"math/big"
|
|
|
|
"os"
|
2021-11-17 11:14:22 +00:00
|
|
|
"path/filepath"
|
2020-09-01 13:24:01 +00:00
|
|
|
"testing"
|
|
|
|
|
|
|
|
"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"
|
2022-07-22 16:09:29 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
2020-09-01 13:24:01 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
2022-08-31 12:43:30 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/context"
|
2022-01-31 13:26:23 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
2022-07-08 14:28:29 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
|
2020-09-01 13:24:01 +00:00
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Test signing of multisig transactions.
|
|
|
|
// 1. Transfer funds to a created multisig address.
|
|
|
|
// 2. Transfer from multisig to another account.
|
|
|
|
func TestSignMultisigTx(t *testing.T) {
|
|
|
|
e := newExecutor(t, true)
|
|
|
|
|
|
|
|
privs, pubs := generateKeys(t, 3)
|
|
|
|
script, err := smartcontract.CreateMultiSigRedeemScript(2, pubs)
|
|
|
|
require.NoError(t, err)
|
|
|
|
multisigHash := hash.Hash160(script)
|
|
|
|
multisigAddr := address.Uint160ToString(multisigHash)
|
|
|
|
|
|
|
|
// Create 2 wallets participating in multisig.
|
2021-08-25 19:17:37 +00:00
|
|
|
tmpDir := t.TempDir()
|
2021-11-17 11:14:22 +00:00
|
|
|
wallet1Path := filepath.Join(tmpDir, "multiWallet1.json")
|
|
|
|
wallet2Path := filepath.Join(tmpDir, "multiWallet2.json")
|
2020-09-01 13:24:01 +00:00
|
|
|
|
|
|
|
addAccount := func(w string, wif string) {
|
|
|
|
e.Run(t, "neo-go", "wallet", "init", "--wallet", w)
|
2020-12-03 14:25:17 +00:00
|
|
|
e.In.WriteString("acc\rpass\rpass\r")
|
2020-09-01 13:24:01 +00:00
|
|
|
e.Run(t, "neo-go", "wallet", "import-multisig",
|
|
|
|
"--wallet", w,
|
|
|
|
"--wif", wif,
|
|
|
|
"--min", "2",
|
|
|
|
hex.EncodeToString(pubs[0].Bytes()),
|
|
|
|
hex.EncodeToString(pubs[1].Bytes()),
|
|
|
|
hex.EncodeToString(pubs[2].Bytes()))
|
|
|
|
}
|
|
|
|
addAccount(wallet1Path, privs[0].WIF())
|
|
|
|
addAccount(wallet2Path, privs[1].WIF())
|
|
|
|
|
|
|
|
// Transfer funds to the multisig.
|
|
|
|
e.In.WriteString("one\r")
|
2020-11-24 08:14:25 +00:00
|
|
|
e.Run(t, "neo-go", "wallet", "nep17", "multitransfer",
|
2020-10-14 15:13:20 +00:00
|
|
|
"--rpc-endpoint", "http://"+e.RPC.Addr,
|
2020-09-01 13:24:01 +00:00
|
|
|
"--wallet", validatorWallet,
|
|
|
|
"--from", validatorAddr,
|
2021-09-15 09:40:30 +00:00
|
|
|
"--force",
|
2020-12-13 18:05:49 +00:00
|
|
|
"NEO:"+multisigAddr+":4",
|
|
|
|
"GAS:"+multisigAddr+":1")
|
2020-09-01 13:24:01 +00:00
|
|
|
e.checkTxPersisted(t)
|
|
|
|
|
|
|
|
// Sign and transfer funds to another account.
|
|
|
|
priv, err := keys.NewPrivateKey()
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-01-31 13:26:23 +00:00
|
|
|
t.Run("bad cases", func(t *testing.T) {
|
|
|
|
txPath := filepath.Join(tmpDir, "multisigtx.json")
|
|
|
|
t.Cleanup(func() {
|
|
|
|
os.Remove(txPath)
|
|
|
|
})
|
|
|
|
e.In.WriteString("pass\r")
|
|
|
|
e.Run(t, "neo-go", "wallet", "nep17", "transfer",
|
|
|
|
"--rpc-endpoint", "http://"+e.RPC.Addr,
|
|
|
|
"--wallet", wallet1Path, "--from", multisigAddr,
|
|
|
|
"--to", priv.Address(), "--token", "NEO", "--amount", "1",
|
|
|
|
"--out", txPath)
|
|
|
|
|
|
|
|
// missing wallet
|
|
|
|
e.RunWithError(t, "neo-go", "wallet", "sign")
|
|
|
|
|
|
|
|
// missing in
|
|
|
|
e.RunWithError(t, "neo-go", "wallet", "sign",
|
|
|
|
"--wallet", wallet2Path)
|
|
|
|
|
|
|
|
// missing address
|
|
|
|
e.RunWithError(t, "neo-go", "wallet", "sign",
|
|
|
|
"--wallet", wallet2Path,
|
|
|
|
"--in", txPath)
|
|
|
|
|
|
|
|
// invalid address
|
|
|
|
e.RunWithError(t, "neo-go", "wallet", "sign",
|
|
|
|
"--wallet", wallet2Path, "--address", util.Uint160{}.StringLE(),
|
|
|
|
"--in", txPath)
|
|
|
|
|
|
|
|
// invalid out
|
|
|
|
e.In.WriteString("pass\r")
|
|
|
|
e.RunWithError(t, "neo-go", "wallet", "sign",
|
|
|
|
"--rpc-endpoint", "http://"+e.RPC.Addr,
|
|
|
|
"--wallet", wallet2Path, "--address", multisigAddr,
|
|
|
|
"--in", txPath, "--out", t.TempDir())
|
|
|
|
|
|
|
|
// invalid RPC endpoint
|
|
|
|
e.In.WriteString("pass\r")
|
|
|
|
e.RunWithError(t, "neo-go", "wallet", "sign",
|
|
|
|
"--rpc-endpoint", "http://not-an-address",
|
|
|
|
"--wallet", wallet2Path, "--address", multisigAddr,
|
|
|
|
"--in", txPath)
|
|
|
|
})
|
|
|
|
|
|
|
|
// Create transaction and save it for further multisigning.
|
2021-11-17 11:14:22 +00:00
|
|
|
txPath := filepath.Join(tmpDir, "multisigtx.json")
|
2021-03-01 11:14:15 +00:00
|
|
|
t.Cleanup(func() {
|
|
|
|
os.Remove(txPath)
|
|
|
|
})
|
2020-09-01 13:24:01 +00:00
|
|
|
e.In.WriteString("pass\r")
|
2020-11-24 08:14:25 +00:00
|
|
|
e.Run(t, "neo-go", "wallet", "nep17", "transfer",
|
2020-10-14 15:13:20 +00:00
|
|
|
"--rpc-endpoint", "http://"+e.RPC.Addr,
|
2020-09-01 13:24:01 +00:00
|
|
|
"--wallet", wallet1Path, "--from", multisigAddr,
|
2020-12-13 18:05:49 +00:00
|
|
|
"--to", priv.Address(), "--token", "NEO", "--amount", "1",
|
2020-09-01 13:24:01 +00:00
|
|
|
"--out", txPath)
|
|
|
|
|
2021-03-03 13:05:33 +00:00
|
|
|
simplePriv, err := keys.NewPrivateKey()
|
|
|
|
require.NoError(t, err)
|
|
|
|
{ // Simple signer, not in signers.
|
|
|
|
e.In.WriteString("acc\rpass\rpass\r")
|
|
|
|
e.Run(t, "neo-go", "wallet", "import",
|
|
|
|
"--wallet", wallet1Path,
|
|
|
|
"--wif", simplePriv.WIF())
|
|
|
|
t.Run("sign with missing signer", func(t *testing.T) {
|
|
|
|
e.In.WriteString("pass\r")
|
|
|
|
e.RunWithError(t, "neo-go", "wallet", "sign",
|
|
|
|
"--wallet", wallet1Path, "--address", simplePriv.Address(),
|
|
|
|
"--in", txPath, "--out", txPath)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-09-14 12:02:54 +00:00
|
|
|
t.Run("test invoke", func(t *testing.T) {
|
|
|
|
t.Run("missing file", func(t *testing.T) {
|
|
|
|
e.RunWithError(t, "neo-go", "util", "txdump")
|
|
|
|
fmt.Println(e.Out.String())
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("no invoke", func(t *testing.T) {
|
|
|
|
e.Run(t, "neo-go", "util", "txdump", txPath)
|
|
|
|
e.checkTxTestInvokeOutput(t, 11)
|
|
|
|
e.checkEOF(t)
|
|
|
|
})
|
|
|
|
|
2022-08-05 10:32:37 +00:00
|
|
|
t.Run("excessive parameters", func(t *testing.T) {
|
|
|
|
e.RunWithError(t, "neo-go", "util", "txdump",
|
|
|
|
"--rpc-endpoint", "http://"+e.RPC.Addr,
|
|
|
|
txPath, "garbage")
|
|
|
|
})
|
2021-09-14 12:02:54 +00:00
|
|
|
e.Run(t, "neo-go", "util", "txdump",
|
|
|
|
"--rpc-endpoint", "http://"+e.RPC.Addr,
|
|
|
|
txPath)
|
|
|
|
e.checkTxTestInvokeOutput(t, 11)
|
|
|
|
res := new(result.Invoke)
|
|
|
|
require.NoError(t, json.Unmarshal(e.Out.Bytes(), res))
|
2022-07-08 14:28:29 +00:00
|
|
|
require.Equal(t, vmstate.Halt.String(), res.State, res.FaultException)
|
2021-09-14 12:02:54 +00:00
|
|
|
})
|
|
|
|
|
2022-08-31 12:43:30 +00:00
|
|
|
t.Run("console output", func(t *testing.T) {
|
|
|
|
oldIn, err := os.ReadFile(txPath)
|
|
|
|
require.NoError(t, err)
|
|
|
|
e.In.WriteString("pass\r")
|
|
|
|
e.Run(t, "neo-go", "wallet", "sign",
|
|
|
|
"--wallet", wallet2Path, "--address", multisigAddr,
|
|
|
|
"--in", txPath)
|
|
|
|
newIn, err := os.ReadFile(txPath)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, oldIn, newIn)
|
|
|
|
|
|
|
|
pcOld := new(context.ParameterContext)
|
|
|
|
require.NoError(t, json.Unmarshal(oldIn, pcOld))
|
|
|
|
|
|
|
|
jOut := e.Out.Bytes()
|
|
|
|
pcNew := new(context.ParameterContext)
|
|
|
|
require.NoError(t, json.Unmarshal(jOut, pcNew))
|
|
|
|
|
|
|
|
require.Equal(t, pcOld.Type, pcNew.Type)
|
|
|
|
require.Equal(t, pcOld.Network, pcNew.Network)
|
|
|
|
require.Equal(t, pcOld.Verifiable, pcNew.Verifiable)
|
|
|
|
require.Equal(t, pcOld.Items[multisigHash].Script, pcNew.Items[multisigHash].Script)
|
|
|
|
// It's completely signed after this, so parameters have signatures now as well.
|
|
|
|
require.NotEqual(t, pcOld.Items[multisigHash].Parameters, pcNew.Items[multisigHash].Parameters)
|
|
|
|
require.NotEqual(t, pcOld.Items[multisigHash].Signatures, pcNew.Items[multisigHash].Signatures)
|
|
|
|
})
|
2020-09-01 13:24:01 +00:00
|
|
|
|
2022-08-31 12:43:30 +00:00
|
|
|
t.Run("sign, save and send", func(t *testing.T) {
|
|
|
|
e.In.WriteString("pass\r")
|
|
|
|
e.Run(t, "neo-go", "wallet", "sign",
|
|
|
|
"--rpc-endpoint", "http://"+e.RPC.Addr,
|
|
|
|
"--wallet", wallet2Path, "--address", multisigAddr,
|
|
|
|
"--in", txPath, "--out", txPath)
|
|
|
|
e.checkTxPersisted(t)
|
|
|
|
})
|
2022-01-31 13:26:23 +00:00
|
|
|
t.Run("double-sign", func(t *testing.T) {
|
|
|
|
e.In.WriteString("pass\r")
|
|
|
|
e.RunWithError(t, "neo-go", "wallet", "sign",
|
|
|
|
"--rpc-endpoint", "http://"+e.RPC.Addr,
|
|
|
|
"--wallet", wallet2Path, "--address", multisigAddr,
|
|
|
|
"--in", txPath, "--out", txPath)
|
|
|
|
})
|
|
|
|
|
2020-09-01 13:24:01 +00:00
|
|
|
b, _ := e.Chain.GetGoverningTokenBalance(priv.GetScriptHash())
|
|
|
|
require.Equal(t, big.NewInt(1), b)
|
|
|
|
b, _ = e.Chain.GetGoverningTokenBalance(multisigHash)
|
|
|
|
require.Equal(t, big.NewInt(3), b)
|
2020-10-02 13:13:17 +00:00
|
|
|
|
|
|
|
t.Run("via invokefunction", func(t *testing.T) {
|
2021-04-22 12:14:02 +00:00
|
|
|
h := deployVerifyContract(t, e)
|
2021-03-03 13:30:58 +00:00
|
|
|
|
|
|
|
e.In.WriteString("acc\rpass\rpass\r")
|
|
|
|
e.Run(t, "neo-go", "wallet", "import-deployed",
|
|
|
|
"--rpc-endpoint", "http://"+e.RPC.Addr,
|
|
|
|
"--wallet", wallet1Path, "--wif", simplePriv.WIF(),
|
|
|
|
"--contract", h.StringLE())
|
|
|
|
|
2020-10-02 13:13:17 +00:00
|
|
|
e.In.WriteString("pass\r")
|
|
|
|
e.Run(t, "neo-go", "contract", "invokefunction",
|
2020-10-14 15:13:20 +00:00
|
|
|
"--rpc-endpoint", "http://"+e.RPC.Addr,
|
2021-04-16 08:12:51 +00:00
|
|
|
"--wallet", wallet1Path, "--address", multisigHash.StringLE(), // test with scripthash instead of address
|
2020-10-02 13:13:17 +00:00
|
|
|
"--out", txPath,
|
2020-09-25 16:32:53 +00:00
|
|
|
e.Chain.GoverningTokenHash().StringLE(), "transfer",
|
2020-10-02 13:13:17 +00:00
|
|
|
"bytes:"+multisigHash.StringBE(),
|
|
|
|
"bytes:"+priv.GetScriptHash().StringBE(),
|
2020-11-19 15:01:42 +00:00
|
|
|
"int:1", "bytes:",
|
2021-03-03 13:30:58 +00:00
|
|
|
"--", multisigHash.StringLE()+":"+"Global",
|
|
|
|
h.StringLE(),
|
|
|
|
simplePriv.GetScriptHash().StringLE(),
|
|
|
|
)
|
2020-10-02 13:13:17 +00:00
|
|
|
|
|
|
|
e.In.WriteString("pass\r")
|
2021-03-03 11:47:36 +00:00
|
|
|
e.Run(t, "neo-go", "wallet", "sign",
|
2020-10-02 13:13:17 +00:00
|
|
|
"--wallet", wallet2Path, "--address", multisigAddr,
|
|
|
|
"--in", txPath, "--out", txPath)
|
2021-03-03 13:30:58 +00:00
|
|
|
|
|
|
|
// Simple signer, not in signers.
|
|
|
|
e.In.WriteString("pass\r")
|
|
|
|
e.Run(t, "neo-go", "wallet", "sign",
|
|
|
|
"--wallet", wallet1Path, "--address", simplePriv.Address(),
|
|
|
|
"--in", txPath, "--out", txPath)
|
|
|
|
|
|
|
|
// Contract.
|
|
|
|
e.In.WriteString("pass\r")
|
|
|
|
e.Run(t, "neo-go", "wallet", "sign",
|
|
|
|
"--rpc-endpoint", "http://"+e.RPC.Addr,
|
|
|
|
"--wallet", wallet1Path, "--address", address.Uint160ToString(h),
|
|
|
|
"--in", txPath, "--out", txPath)
|
|
|
|
tx, _ := e.checkTxPersisted(t)
|
|
|
|
require.Equal(t, 3, len(tx.Signers))
|
2020-10-02 13:13:17 +00:00
|
|
|
|
|
|
|
b, _ := e.Chain.GetGoverningTokenBalance(priv.GetScriptHash())
|
|
|
|
require.Equal(t, big.NewInt(2), b)
|
|
|
|
b, _ = e.Chain.GetGoverningTokenBalance(multisigHash)
|
|
|
|
require.Equal(t, big.NewInt(2), b)
|
|
|
|
})
|
2020-09-01 13:24:01 +00:00
|
|
|
}
|
2021-09-14 12:02:54 +00:00
|
|
|
|
|
|
|
func (e *executor) checkTxTestInvokeOutput(t *testing.T, scriptSize int) {
|
|
|
|
e.checkNextLine(t, `Hash:\s+`)
|
|
|
|
e.checkNextLine(t, `OnChain:\s+false`)
|
|
|
|
e.checkNextLine(t, `ValidUntil:\s+\d+`)
|
|
|
|
e.checkNextLine(t, `Signer:\s+\w+`)
|
|
|
|
e.checkNextLine(t, `SystemFee:\s+(\d|\.)+`)
|
|
|
|
e.checkNextLine(t, `NetworkFee:\s+(\d|\.)+`)
|
|
|
|
e.checkNextLine(t, `Script:\s+\w+`)
|
|
|
|
e.checkScriptDump(t, scriptSize)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *executor) checkScriptDump(t *testing.T, scriptSize int) {
|
|
|
|
e.checkNextLine(t, `INDEX\s+`)
|
|
|
|
for i := 0; i < scriptSize; i++ {
|
|
|
|
e.checkNextLine(t, `\d+\s+\w+`)
|
|
|
|
}
|
|
|
|
}
|