Merge pull request #1362 from nspcc-dev/feature/verification
Allow to use account with contracts on client
This commit is contained in:
commit
9e0c13b69d
9 changed files with 203 additions and 35 deletions
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/cli/options"
|
||||
"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/smartcontract/manifest"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/urfave/cli"
|
||||
|
@ -166,6 +167,20 @@ func NewCommands() []cli.Command {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "import-deployed",
|
||||
Usage: "import deployed contract",
|
||||
UsageText: "import-multisig --wallet <path> --wif <wif> --contract <hash>",
|
||||
Action: importDeployed,
|
||||
Flags: append([]cli.Flag{
|
||||
walletPathFlag,
|
||||
wifFlag,
|
||||
cli.StringFlag{
|
||||
Name: "contract, c",
|
||||
Usage: "Contract hash",
|
||||
},
|
||||
}, options.RPC...),
|
||||
},
|
||||
{
|
||||
Name: "remove",
|
||||
Usage: "remove an account from the wallet",
|
||||
|
@ -390,7 +405,7 @@ func importMultisig(ctx *cli.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func importWallet(ctx *cli.Context) error {
|
||||
func importDeployed(ctx *cli.Context) error {
|
||||
wall, err := openWallet(ctx.String("wallet"))
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
|
@ -398,6 +413,56 @@ func importWallet(ctx *cli.Context) error {
|
|||
|
||||
defer wall.Close()
|
||||
|
||||
rawHash := strings.TrimPrefix("0x", ctx.String("contract"))
|
||||
h, err := util.Uint160DecodeStringLE(rawHash)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("invalid contract hash: %w", err), 1)
|
||||
}
|
||||
|
||||
acc, err := newAccountFromWIF(ctx.String("wif"))
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
|
||||
gctx, cancel := options.GetTimeoutContext(ctx)
|
||||
defer cancel()
|
||||
|
||||
c, err := options.GetRPCClient(gctx, ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cs, err := c.GetContractState(h)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("can't fetch contract info: %w", err), 1)
|
||||
}
|
||||
md := cs.Manifest.ABI.GetMethod(manifest.MethodVerify)
|
||||
if md == nil {
|
||||
return cli.NewExitError("contract has no `verify` method", 1)
|
||||
}
|
||||
acc.Contract.Script = cs.Script
|
||||
for _, p := range md.Parameters {
|
||||
acc.Contract.Parameters = append(acc.Contract.Parameters, wallet.ContractParam{
|
||||
Name: p.Name,
|
||||
Type: p.Type,
|
||||
})
|
||||
}
|
||||
acc.Contract.Deployed = true
|
||||
|
||||
if err := addAccountAndSave(wall, acc); err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func importWallet(ctx *cli.Context) error {
|
||||
wall, err := openWallet(ctx.String("wallet"))
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
defer wall.Close()
|
||||
|
||||
acc, err := newAccountFromWIF(ctx.String("wif"))
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
|
|
|
@ -226,24 +226,7 @@ func TestCreateBasicChain(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
|
||||
// Push some contract into the chain.
|
||||
name := prefix + "test_contract.go"
|
||||
c, err := ioutil.ReadFile(name)
|
||||
require.NoError(t, err)
|
||||
avm, di, err := compiler.CompileWithDebugInfo(name, bytes.NewReader(c))
|
||||
require.NoError(t, err)
|
||||
t.Logf("contractHash: %s", hash.Hash160(avm).StringLE())
|
||||
|
||||
script := io.NewBufBinWriter()
|
||||
m, err := di.ConvertToManifest(smartcontract.HasStorage, nil)
|
||||
require.NoError(t, err)
|
||||
bs, err := m.MarshalJSON()
|
||||
require.NoError(t, err)
|
||||
emit.Bytes(script.BinWriter, bs)
|
||||
emit.Bytes(script.BinWriter, avm)
|
||||
emit.Syscall(script.BinWriter, interopnames.SystemContractCreate)
|
||||
txScript := script.Bytes()
|
||||
|
||||
txDeploy := transaction.New(testchain.Network(), txScript, 100*native.GASFactor)
|
||||
txDeploy, avm := newDeployTx(t, prefix+"test_contract.go")
|
||||
txDeploy.Nonce = getNextNonce()
|
||||
txDeploy.ValidUntilBlock = validUntilBlock
|
||||
txDeploy.Signers = []transaction.Signer{{Account: priv0ScriptHash}}
|
||||
|
@ -255,7 +238,7 @@ func TestCreateBasicChain(t *testing.T) {
|
|||
t.Logf("Block2 hash: %s", b.Hash().StringLE())
|
||||
|
||||
// Now invoke this contract.
|
||||
script = io.NewBufBinWriter()
|
||||
script := io.NewBufBinWriter()
|
||||
emit.AppCallWithOperationAndArgs(script.BinWriter, hash.Hash160(avm), "putValue", "testkey", "testvalue")
|
||||
|
||||
txInv := transaction.New(testchain.Network(), script.Bytes(), 1*native.GASFactor)
|
||||
|
@ -330,6 +313,16 @@ func TestCreateBasicChain(t *testing.T) {
|
|||
require.NoError(t, bc.AddBlock(b))
|
||||
t.Logf("sendRublesTx: %v", transferTx.Hash().StringLE())
|
||||
|
||||
// Push verification contract into the chain.
|
||||
txDeploy2, _ := newDeployTx(t, prefix+"verification_contract.go")
|
||||
txDeploy2.Nonce = getNextNonce()
|
||||
txDeploy2.ValidUntilBlock = validUntilBlock
|
||||
txDeploy2.Signers = []transaction.Signer{{Account: priv0ScriptHash}}
|
||||
require.NoError(t, addNetworkFee(bc, txDeploy2, acc0))
|
||||
require.NoError(t, acc0.SignTx(txDeploy2))
|
||||
b = bc.newBlock(txDeploy2)
|
||||
require.NoError(t, bc.AddBlock(b))
|
||||
|
||||
if saveChain {
|
||||
outStream, err := os.Create(prefix + "testblocks.acc")
|
||||
require.NoError(t, err)
|
||||
|
@ -378,6 +371,27 @@ func newNEP5Transfer(sc, from, to util.Uint160, amount int64) *transaction.Trans
|
|||
return transaction.New(testchain.Network(), script, 10000000)
|
||||
}
|
||||
|
||||
func newDeployTx(t *testing.T, name string) (*transaction.Transaction, []byte) {
|
||||
c, err := ioutil.ReadFile(name)
|
||||
require.NoError(t, err)
|
||||
avm, di, err := compiler.CompileWithDebugInfo(name, bytes.NewReader(c))
|
||||
require.NoError(t, err)
|
||||
t.Logf("contractHash (%s): %s", name, hash.Hash160(avm).StringLE())
|
||||
t.Logf("contractScript: %x", avm)
|
||||
|
||||
script := io.NewBufBinWriter()
|
||||
m, err := di.ConvertToManifest(smartcontract.HasStorage, nil)
|
||||
require.NoError(t, err)
|
||||
bs, err := m.MarshalJSON()
|
||||
require.NoError(t, err)
|
||||
emit.Bytes(script.BinWriter, bs)
|
||||
emit.Bytes(script.BinWriter, avm)
|
||||
emit.Syscall(script.BinWriter, interopnames.SystemContractCreate)
|
||||
txScript := script.Bytes()
|
||||
|
||||
return transaction.New(testchain.Network(), txScript, 100*native.GASFactor), avm
|
||||
}
|
||||
|
||||
func addSigners(txs ...*transaction.Transaction) {
|
||||
for _, tx := range txs {
|
||||
tx.Signers = []transaction.Signer{{
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||
)
|
||||
|
@ -20,6 +21,9 @@ func CheckHashedWitness(ic *interop.Context, hash util.Uint160) (bool, error) {
|
|||
return checkScope(ic.DAO, tx, ic.VM, hash)
|
||||
}
|
||||
|
||||
if !ic.VM.Context().GetCallFlags().Has(smartcontract.AllowStates) {
|
||||
return false, errors.New("missing AllowStates call flag")
|
||||
}
|
||||
return false, errors.New("script container is not a transaction")
|
||||
}
|
||||
|
||||
|
@ -49,6 +53,9 @@ func checkScope(d dao.DAO, tx *transaction.Transaction, v *vm.VM, hash util.Uint
|
|||
if callingScriptHash.Equals(util.Uint160{}) {
|
||||
return false, nil
|
||||
}
|
||||
if !v.Context().GetCallFlags().Has(smartcontract.AllowStates) {
|
||||
return false, errors.New("missing AllowStates call flag")
|
||||
}
|
||||
cs, err := d.GetContractState(callingScriptHash)
|
||||
if err != nil {
|
||||
return false, err
|
||||
|
|
|
@ -77,7 +77,7 @@ var systemInterops = []interop.Function{
|
|||
{Name: interopnames.SystemJSONDeserialize, Func: json.Deserialize, Price: 500000, ParamCount: 1},
|
||||
{Name: interopnames.SystemJSONSerialize, Func: json.Serialize, Price: 100000, ParamCount: 1},
|
||||
{Name: interopnames.SystemRuntimeCheckWitness, Func: runtime.CheckWitness, Price: 30000,
|
||||
RequiredFlags: smartcontract.AllowStates, ParamCount: 1},
|
||||
RequiredFlags: smartcontract.NoneFlag, ParamCount: 1},
|
||||
{Name: interopnames.SystemRuntimeGasLeft, Func: runtime.GasLeft, Price: 400},
|
||||
{Name: interopnames.SystemRuntimeGetCallingScriptHash, Func: engineGetCallingScriptHash, Price: 400},
|
||||
{Name: interopnames.SystemRuntimeGetEntryScriptHash, Func: engineGetEntryScriptHash, Price: 400},
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/rpc/request"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
)
|
||||
|
@ -499,16 +500,19 @@ func (c *Client) AddNetworkFee(tx *transaction.Transaction, extraFee int64, accs
|
|||
}
|
||||
size := io.GetVarSize(tx)
|
||||
for i, cosigner := range tx.Signers {
|
||||
if accs[i].Contract.Script == nil {
|
||||
contract, err := c.GetContractState(cosigner.Account)
|
||||
if err == nil {
|
||||
if contract == nil {
|
||||
continue
|
||||
if accs[i].Contract.Deployed {
|
||||
res, err := c.InvokeFunction(cosigner.Account, manifest.MethodVerify, []smartcontract.Parameter{}, tx.Signers)
|
||||
if err == nil && res.State == "HALT" && len(res.Stack) == 1 {
|
||||
r, err := topIntFromStack(res.Stack)
|
||||
if err != nil || r == 0 {
|
||||
return core.ErrVerificationFailed
|
||||
}
|
||||
netFee, sizeDelta := core.CalculateNetworkFee(contract.Script)
|
||||
tx.NetworkFee += netFee
|
||||
size += sizeDelta
|
||||
} else {
|
||||
return core.ErrVerificationFailed
|
||||
}
|
||||
tx.NetworkFee += res.GasConsumed
|
||||
size += io.GetVarSize([]byte{}) * 2 // both scripts are empty
|
||||
continue
|
||||
}
|
||||
netFee, sizeDelta := core.CalculateNetworkFee(accs[i].Contract.Script)
|
||||
tx.NetworkFee += netFee
|
||||
|
|
|
@ -2,6 +2,7 @@ package server
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
||||
|
@ -132,6 +133,65 @@ func TestAddNetworkFee(t *testing.T) {
|
|||
cFeeM, _ := core.CalculateNetworkFee(accs[1].Contract.Script)
|
||||
require.Equal(t, int64(io.GetVarSize(tx))*feePerByte+cFee+cFeeM+10, tx.NetworkFee)
|
||||
})
|
||||
t.Run("Contract", func(t *testing.T) {
|
||||
tx := transaction.New(testchain.Network(), []byte{byte(opcode.PUSH1)}, 0)
|
||||
priv := testchain.PrivateKeyByID(0)
|
||||
acc1, err := wallet.NewAccountFromWIF(priv.WIF())
|
||||
require.NoError(t, err)
|
||||
acc1.Contract.Deployed = true
|
||||
acc1.Contract.Script, _ = hex.DecodeString(verifyContractAVM)
|
||||
h, _ := util.Uint160DecodeStringLE(verifyContractHash)
|
||||
tx.ValidUntilBlock = chain.BlockHeight() + 10
|
||||
|
||||
t.Run("Valid", func(t *testing.T) {
|
||||
acc0, err := wallet.NewAccountFromWIF(priv.WIF())
|
||||
require.NoError(t, err)
|
||||
tx.Signers = []transaction.Signer{
|
||||
{
|
||||
Account: acc0.PrivateKey().GetScriptHash(),
|
||||
Scopes: transaction.CalledByEntry,
|
||||
},
|
||||
{
|
||||
Account: h,
|
||||
Scopes: transaction.Global,
|
||||
},
|
||||
}
|
||||
require.NoError(t, c.AddNetworkFee(tx, 10, acc0, acc1))
|
||||
require.NoError(t, acc0.SignTx(tx))
|
||||
tx.Scripts = append(tx.Scripts, transaction.Witness{})
|
||||
require.NoError(t, chain.VerifyTx(tx))
|
||||
})
|
||||
t.Run("Invalid", func(t *testing.T) {
|
||||
acc0, err := wallet.NewAccount()
|
||||
require.NoError(t, err)
|
||||
tx.Signers = []transaction.Signer{
|
||||
{
|
||||
Account: acc0.PrivateKey().GetScriptHash(),
|
||||
Scopes: transaction.CalledByEntry,
|
||||
},
|
||||
{
|
||||
Account: h,
|
||||
Scopes: transaction.Global,
|
||||
},
|
||||
}
|
||||
require.Error(t, c.AddNetworkFee(tx, 10, acc0, acc1))
|
||||
})
|
||||
t.Run("InvalidContract", func(t *testing.T) {
|
||||
acc0, err := wallet.NewAccountFromWIF(priv.WIF())
|
||||
require.NoError(t, err)
|
||||
tx.Signers = []transaction.Signer{
|
||||
{
|
||||
Account: acc0.PrivateKey().GetScriptHash(),
|
||||
Scopes: transaction.CalledByEntry,
|
||||
},
|
||||
{
|
||||
Account: util.Uint160{},
|
||||
Scopes: transaction.Global,
|
||||
},
|
||||
}
|
||||
require.Error(t, c.AddNetworkFee(tx, 10, acc0, acc1))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestSignAndPushInvocationTx(t *testing.T) {
|
||||
|
|
|
@ -51,8 +51,11 @@ type rpcTestCase struct {
|
|||
check func(t *testing.T, e *executor, result interface{})
|
||||
}
|
||||
|
||||
const testContractHash = "93c4983afe01a75f74c1e56011bd630e9d8cc755"
|
||||
const deploymentTxHash = "583cf0e49d69d8854869efc3e97ad741061da478292a7280580789351a39a1ac"
|
||||
const testContractHash = "4546ec6fcdaa1c3ccdb048526b78624b457b60a4"
|
||||
const deploymentTxHash = "17be1bbb0fdecae18cd4c6a2db19311f47bd540371e2ea479a46b349a66aa0b3"
|
||||
|
||||
const verifyContractHash = "47ef649f9a77cad161ddaa28b39c7e450e5429e7"
|
||||
const verifyContractAVM = "560340570300412d510830db4121700c14aa8acf859d4fe402b34e673f2156821796a488ebdb30716813cedb2869db289740"
|
||||
|
||||
var rpcTestCases = map[string][]rpcTestCase{
|
||||
"getapplicationlog": {
|
||||
|
@ -438,7 +441,7 @@ var rpcTestCases = map[string][]rpcTestCase{
|
|||
require.True(t, ok)
|
||||
expected := result.UnclaimedGas{
|
||||
Address: testchain.MultisigScriptHash(),
|
||||
Unclaimed: *big.NewInt(36000),
|
||||
Unclaimed: *big.NewInt(42000),
|
||||
}
|
||||
assert.Equal(t, expected, *actual)
|
||||
},
|
||||
|
@ -814,7 +817,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []
|
|||
require.NoErrorf(t, err, "could not parse response: %s", txOut)
|
||||
|
||||
assert.Equal(t, *block.Transactions[0], actual.Transaction)
|
||||
assert.Equal(t, 8, actual.Confirmations)
|
||||
assert.Equal(t, 9, actual.Confirmations)
|
||||
assert.Equal(t, TXHash, actual.Transaction.Hash())
|
||||
})
|
||||
|
||||
|
@ -999,8 +1002,8 @@ func checkNep5Balances(t *testing.T, e *executor, acc interface{}) {
|
|||
},
|
||||
{
|
||||
Asset: e.chain.UtilityTokenHash(),
|
||||
Amount: "915.61059740",
|
||||
LastUpdated: 6,
|
||||
Amount: "815.59478530",
|
||||
LastUpdated: 7,
|
||||
}},
|
||||
Address: testchain.PrivateKeyByID(0).GetScriptHash().StringLE(),
|
||||
}
|
||||
|
|
BIN
pkg/rpc/server/testdata/testblocks.acc
vendored
BIN
pkg/rpc/server/testdata/testblocks.acc
vendored
Binary file not shown.
15
pkg/rpc/server/testdata/verification_contract.go
vendored
Normal file
15
pkg/rpc/server/testdata/verification_contract.go
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
package testdata
|
||||
|
||||
import (
|
||||
"github.com/nspcc-dev/neo-go/pkg/interop/convert"
|
||||
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
||||
"github.com/nspcc-dev/neo-go/pkg/interop/util"
|
||||
)
|
||||
|
||||
// Verify is a verification contract method.
|
||||
// It returns true iff it is signed by NbTiM6h8r99kpRtb428XcsUk1TzKed2gTc (id-0 private key from testchain).
|
||||
func Verify() bool {
|
||||
tx := runtime.GetScriptContainer()
|
||||
addr := util.FromAddress("NbTiM6h8r99kpRtb428XcsUk1TzKed2gTc")
|
||||
return util.Equals(convert.ToByteArray(tx.Sender), convert.ToByteArray(addr))
|
||||
}
|
Loading…
Reference in a new issue