Merge pull request #2299 from nspcc-dev/tests-migration
core: migrate native contract API tests to neotest framework
This commit is contained in:
commit
0b0531d723
39 changed files with 2462 additions and 2273 deletions
|
@ -1116,7 +1116,7 @@ func TestVerifyTx(t *testing.T) {
|
||||||
func TestVerifyHashAgainstScript(t *testing.T) {
|
func TestVerifyHashAgainstScript(t *testing.T) {
|
||||||
bc := newTestChain(t)
|
bc := newTestChain(t)
|
||||||
|
|
||||||
cs, csInvalid := getTestContractState(bc)
|
cs, csInvalid := getTestContractState(t, 4, 5, random.Uint160()) // sender and IDs are not important for the test
|
||||||
ic := bc.newInteropContext(trigger.Verification, bc.dao, nil, nil)
|
ic := bc.newInteropContext(trigger.Verification, bc.dao, nil, nil)
|
||||||
require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs))
|
require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs))
|
||||||
require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, csInvalid))
|
require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, csInvalid))
|
||||||
|
@ -1681,7 +1681,7 @@ func TestRemoveUntraceable(t *testing.T) {
|
||||||
func TestInvalidNotification(t *testing.T) {
|
func TestInvalidNotification(t *testing.T) {
|
||||||
bc := newTestChain(t)
|
bc := newTestChain(t)
|
||||||
|
|
||||||
cs, _ := getTestContractState(bc)
|
cs, _ := getTestContractState(t, 4, 5, random.Uint160()) // sender and IDs are not important for the test
|
||||||
require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs))
|
require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs))
|
||||||
|
|
||||||
aer, err := invokeContractMethod(bc, 1_00000000, cs.Hash, "invalidStack")
|
aer, err := invokeContractMethod(bc, 1_00000000, cs.Hash, "invalidStack")
|
||||||
|
@ -1695,7 +1695,7 @@ func TestInvalidNotification(t *testing.T) {
|
||||||
func TestMPTDeleteNoKey(t *testing.T) {
|
func TestMPTDeleteNoKey(t *testing.T) {
|
||||||
bc := newTestChain(t)
|
bc := newTestChain(t)
|
||||||
|
|
||||||
cs, _ := getTestContractState(bc)
|
cs, _ := getTestContractState(t, 4, 5, random.Uint160()) // sender and IDs are not important for the test
|
||||||
require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs))
|
require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs))
|
||||||
aer, err := invokeContractMethod(bc, 1_00000000, cs.Hash, "delValue", "non-existent-key")
|
aer, err := invokeContractMethod(bc, 1_00000000, cs.Hash, "delValue", "non-existent-key")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -1827,3 +1827,10 @@ func TestBlockchain_InitWithIncompleteStateJump(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setSigner(tx *transaction.Transaction, h util.Uint160) {
|
||||||
|
tx.Signers = []transaction.Signer{{
|
||||||
|
Account: h,
|
||||||
|
Scopes: transaction.Global,
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
|
@ -674,12 +674,6 @@ func signTxWithAccounts(chain *Blockchain, sysFee int64, tx *transaction.Transac
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareContractMethodInvoke(chain *Blockchain, sysfee int64,
|
|
||||||
hash util.Uint160, method string, args ...interface{}) (*transaction.Transaction, error) {
|
|
||||||
return prepareContractMethodInvokeGeneric(chain, sysfee, hash,
|
|
||||||
method, false, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func persistBlock(chain *Blockchain, txs ...*transaction.Transaction) ([]*state.AppExecResult, error) {
|
func persistBlock(chain *Blockchain, txs ...*transaction.Transaction) ([]*state.AppExecResult, error) {
|
||||||
b := chain.newBlock(txs...)
|
b := chain.newBlock(txs...)
|
||||||
err := chain.AddBlock(b)
|
err := chain.AddBlock(b)
|
||||||
|
@ -716,19 +710,6 @@ func invokeContractMethodGeneric(chain *Blockchain, sysfee int64, hash util.Uint
|
||||||
return aers[0], nil
|
return aers[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func invokeContractMethodBy(t *testing.T, chain *Blockchain, signer *wallet.Account, hash util.Uint160, method string, args ...interface{}) (*state.AppExecResult, error) {
|
|
||||||
var (
|
|
||||||
netfee int64 = 1000_0000
|
|
||||||
sysfee int64 = 1_0000_0000
|
|
||||||
)
|
|
||||||
transferTx := transferTokenFromMultisigAccount(t, chain, signer.PrivateKey().PublicKey().GetScriptHash(), chain.contracts.GAS.Hash, sysfee+netfee+1000_0000, nil)
|
|
||||||
res, err := chain.GetAppExecResults(transferTx.Hash(), trigger.Application)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, vm.HaltState, res[0].VMState)
|
|
||||||
require.Equal(t, 0, len(res[0].Stack))
|
|
||||||
return invokeContractMethodGeneric(chain, sysfee, hash, method, signer, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func transferTokenFromMultisigAccountCheckOK(t *testing.T, chain *Blockchain, to, tokenHash util.Uint160, amount int64, additionalArgs ...interface{}) {
|
func transferTokenFromMultisigAccountCheckOK(t *testing.T, chain *Blockchain, to, tokenHash util.Uint160, amount int64, additionalArgs ...interface{}) {
|
||||||
transferTx := transferTokenFromMultisigAccount(t, chain, to, tokenHash, amount, additionalArgs...)
|
transferTx := transferTokenFromMultisigAccount(t, chain, to, tokenHash, amount, additionalArgs...)
|
||||||
res, err := chain.GetAppExecResults(transferTx.Hash(), trigger.Application)
|
res, err := chain.GetAppExecResults(transferTx.Hash(), trigger.Application)
|
||||||
|
@ -758,6 +739,13 @@ func checkResult(t *testing.T, result *state.AppExecResult, expected stackitem.I
|
||||||
require.Equal(t, expected, result.Stack[0])
|
require.Equal(t, expected, result.Stack[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkTxHalt(t testing.TB, bc *Blockchain, h util.Uint256) {
|
||||||
|
aer, err := bc.GetAppExecResults(h, trigger.Application)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 1, len(aer))
|
||||||
|
require.Equal(t, vm.HaltState, aer[0].VMState, aer[0].FaultException)
|
||||||
|
}
|
||||||
|
|
||||||
func checkFAULTState(t *testing.T, result *state.AppExecResult) {
|
func checkFAULTState(t *testing.T, result *state.AppExecResult) {
|
||||||
require.Equal(t, vm.FaultState, result.VMState)
|
require.Equal(t, vm.FaultState, result.VMState)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/internal/random"
|
"github.com/nspcc-dev/neo-go/internal/random"
|
||||||
|
@ -36,6 +40,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
"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/vm/opcode"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -231,7 +236,7 @@ func TestRuntimeGetNotifications(t *testing.T) {
|
||||||
func TestRuntimeGetInvocationCounter(t *testing.T) {
|
func TestRuntimeGetInvocationCounter(t *testing.T) {
|
||||||
v, ic, bc := createVM(t)
|
v, ic, bc := createVM(t)
|
||||||
|
|
||||||
cs, _ := getTestContractState(bc)
|
cs, _ := getTestContractState(t, 4, 5, random.Uint160()) // sender and IDs are not important for the test
|
||||||
require.NoError(t, bc.contracts.Management.PutContractState(ic.DAO, cs))
|
require.NoError(t, bc.contracts.Management.PutContractState(ic.DAO, cs))
|
||||||
|
|
||||||
ic.Invocations[hash.Hash160([]byte{2})] = 42
|
ic.Invocations[hash.Hash160([]byte{2})] = 42
|
||||||
|
@ -658,22 +663,28 @@ func createVMAndContractState(t testing.TB) (*vm.VM, *state.Contract, *interop.C
|
||||||
return v, contractState, context, chain
|
return v, contractState, context, chain
|
||||||
}
|
}
|
||||||
|
|
||||||
func createVMAndTX(t *testing.T) (*vm.VM, *transaction.Transaction, *interop.Context, *Blockchain) {
|
var (
|
||||||
script := []byte{byte(opcode.PUSH1), byte(opcode.RET)}
|
helper1ContractNEFPath = filepath.Join("test_data", "management_helper", "management_helper1.nef")
|
||||||
tx := transaction.New(script, 0)
|
helper1ContractManifestPath = filepath.Join("test_data", "management_helper", "management_helper1.manifest.json")
|
||||||
tx.Signers = []transaction.Signer{{Account: util.Uint160{1, 2, 3, 4}}}
|
helper2ContractNEFPath = filepath.Join("test_data", "management_helper", "management_helper2.nef")
|
||||||
tx.Scripts = []transaction.Witness{{InvocationScript: []byte{}, VerificationScript: []byte{}}}
|
helper2ContractManifestPath = filepath.Join("test_data", "management_helper", "management_helper2.manifest.json")
|
||||||
chain := newTestChain(t)
|
)
|
||||||
d := dao.NewSimple(storage.NewMemoryStore(), chain.config.StateRootInHeader, chain.config.P2PSigExtensions)
|
|
||||||
context := chain.newInteropContext(trigger.Application, d, nil, tx)
|
|
||||||
v := context.SpawnVM()
|
|
||||||
return v, tx, context, chain
|
|
||||||
}
|
|
||||||
|
|
||||||
// getTestContractState returns 2 contracts second of which is allowed to call the first.
|
// TestGenerateManagementHelperContracts generates 2 helper contracts second of which is
|
||||||
func getTestContractState(bc *Blockchain) (*state.Contract, *state.Contract) {
|
// allowed to call the first. It uses test chain to define Management and StdLib
|
||||||
mgmtHash := bc.ManagementContractHash()
|
// native hashes and saves generated NEF and manifest to ../test_data/management_contract folder.
|
||||||
|
// Set `saveState` flag to true and run the test to rewrite NEF and manifest files.
|
||||||
|
func TestGenerateManagementHelperContracts(t *testing.T) {
|
||||||
|
const saveState = false
|
||||||
|
|
||||||
|
bc := newTestChain(t)
|
||||||
|
mgmtHash := bc.contracts.Management.Hash
|
||||||
stdHash := bc.contracts.Std.Hash
|
stdHash := bc.contracts.Std.Hash
|
||||||
|
neoHash := bc.contracts.NEO.Hash
|
||||||
|
singleChainValidator := testchain.PrivateKey(testchain.IDToOrder(0))
|
||||||
|
acc := wallet.NewAccountFromPrivateKey(singleChainValidator)
|
||||||
|
require.NoError(t, acc.ConvertMultisig(1, keys.PublicKeys{singleChainValidator.PublicKey()}))
|
||||||
|
singleChainValidatorHash := acc.Contract.ScriptHash()
|
||||||
|
|
||||||
w := io.NewBufBinWriter()
|
w := io.NewBufBinWriter()
|
||||||
emit.Opcodes(w.BinWriter, opcode.ABORT)
|
emit.Opcodes(w.BinWriter, opcode.ABORT)
|
||||||
|
@ -771,7 +782,6 @@ func getTestContractState(bc *Blockchain) (*state.Contract, *state.Contract) {
|
||||||
emit.Opcodes(w.BinWriter, opcode.RET)
|
emit.Opcodes(w.BinWriter, opcode.RET)
|
||||||
|
|
||||||
script := w.Bytes()
|
script := w.Bytes()
|
||||||
h := hash.Hash160(script)
|
|
||||||
m := manifest.NewManifest("TestMain")
|
m := manifest.NewManifest("TestMain")
|
||||||
m.ABI.Methods = []manifest.Method{
|
m.ABI.Methods = []manifest.Method{
|
||||||
{
|
{
|
||||||
|
@ -953,20 +963,14 @@ func getTestContractState(bc *Blockchain) (*state.Contract, *state.Contract) {
|
||||||
m.Permissions[1].Contract.Value = util.Uint160{}
|
m.Permissions[1].Contract.Value = util.Uint160{}
|
||||||
m.Permissions[1].Methods.Add("method")
|
m.Permissions[1].Methods.Add("method")
|
||||||
|
|
||||||
cs := &state.Contract{
|
// Generate NEF file.
|
||||||
ContractBase: state.ContractBase{
|
|
||||||
Hash: h,
|
|
||||||
Manifest: *m,
|
|
||||||
ID: 42,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
ne, err := nef.NewFile(script)
|
ne, err := nef.NewFile(script)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
ne.Tokens = []nef.MethodToken{
|
ne.Tokens = []nef.MethodToken{
|
||||||
{
|
{
|
||||||
Hash: bc.contracts.NEO.Hash,
|
Hash: neoHash,
|
||||||
Method: "balanceOf",
|
Method: "balanceOf",
|
||||||
ParamCount: 1,
|
ParamCount: 1,
|
||||||
HasReturn: true,
|
HasReturn: true,
|
||||||
|
@ -980,7 +984,25 @@ func getTestContractState(bc *Blockchain) (*state.Contract, *state.Contract) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
ne.Checksum = ne.CalculateChecksum()
|
ne.Checksum = ne.CalculateChecksum()
|
||||||
cs.NEF = *ne
|
|
||||||
|
// Write first NEF file.
|
||||||
|
bytes, err := ne.Bytes()
|
||||||
|
require.NoError(t, err)
|
||||||
|
if saveState {
|
||||||
|
err = ioutil.WriteFile(helper1ContractNEFPath, bytes, os.ModePerm)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write first manifest file.
|
||||||
|
mData, err := json.Marshal(m)
|
||||||
|
require.NoError(t, err)
|
||||||
|
if saveState {
|
||||||
|
err = ioutil.WriteFile(helper1ContractManifestPath, mData, os.ModePerm)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create hash of the first contract assuming that sender is single-chain validator.
|
||||||
|
h := state.CreateContractHash(singleChainValidatorHash, ne.Checksum, m.Name)
|
||||||
|
|
||||||
currScript := []byte{byte(opcode.RET)}
|
currScript := []byte{byte(opcode.RET)}
|
||||||
m = manifest.NewManifest("TestAux")
|
m = manifest.NewManifest("TestAux")
|
||||||
|
@ -997,14 +1019,74 @@ func getTestContractState(bc *Blockchain) (*state.Contract, *state.Contract) {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return cs, &state.Contract{
|
// Write second NEF file.
|
||||||
|
bytes, err = ne.Bytes()
|
||||||
|
require.NoError(t, err)
|
||||||
|
if saveState {
|
||||||
|
err = ioutil.WriteFile(helper2ContractNEFPath, bytes, os.ModePerm)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write second manifest file.
|
||||||
|
mData, err = json.Marshal(m)
|
||||||
|
require.NoError(t, err)
|
||||||
|
if saveState {
|
||||||
|
err = ioutil.WriteFile(helper2ContractManifestPath, mData, os.ModePerm)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
require.False(t, saveState)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTestContractState returns 2 contracts second of which is allowed to call the first.
|
||||||
|
func getTestContractState(t *testing.T, id1, id2 int32, sender2 util.Uint160) (*state.Contract, *state.Contract) {
|
||||||
|
errNotFound := errors.New("auto-generated oracle contract is not found, use TestGenerateOracleContract to regenerate")
|
||||||
|
|
||||||
|
neBytes, err := ioutil.ReadFile(helper1ContractNEFPath)
|
||||||
|
require.NoError(t, err, fmt.Errorf("nef1: %w", errNotFound))
|
||||||
|
ne, err := nef.FileFromBytes(neBytes)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
mBytes, err := ioutil.ReadFile(helper1ContractManifestPath)
|
||||||
|
require.NoError(t, err, fmt.Errorf("manifest1: %w", errNotFound))
|
||||||
|
m := &manifest.Manifest{}
|
||||||
|
err = json.Unmarshal(mBytes, m)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
cs1 := &state.Contract{
|
||||||
ContractBase: state.ContractBase{
|
ContractBase: state.ContractBase{
|
||||||
NEF: *ne,
|
NEF: ne,
|
||||||
Hash: hash.Hash160(currScript),
|
|
||||||
Manifest: *m,
|
Manifest: *m,
|
||||||
ID: 123,
|
ID: id1,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
neBytes, err = ioutil.ReadFile(helper2ContractNEFPath)
|
||||||
|
require.NoError(t, err, fmt.Errorf("nef2: %w", errNotFound))
|
||||||
|
ne, err = nef.FileFromBytes(neBytes)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
mBytes, err = ioutil.ReadFile(helper2ContractManifestPath)
|
||||||
|
require.NoError(t, err, fmt.Errorf("manifest2: %w", errNotFound))
|
||||||
|
m = &manifest.Manifest{}
|
||||||
|
err = json.Unmarshal(mBytes, m)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Retrieve hash of the first contract from the permissions of the second contract.
|
||||||
|
require.Equal(t, 1, len(m.Permissions))
|
||||||
|
require.Equal(t, manifest.PermissionHash, m.Permissions[0].Contract.Type)
|
||||||
|
cs1.Hash = m.Permissions[0].Contract.Hash()
|
||||||
|
|
||||||
|
cs2 := &state.Contract{
|
||||||
|
ContractBase: state.ContractBase{
|
||||||
|
NEF: ne,
|
||||||
|
Manifest: *m,
|
||||||
|
ID: id2,
|
||||||
|
Hash: state.CreateContractHash(sender2, ne.Checksum, m.Name),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return cs1, cs2
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadScript(ic *interop.Context, script []byte, args ...interface{}) {
|
func loadScript(ic *interop.Context, script []byte, args ...interface{}) {
|
||||||
|
@ -1028,12 +1110,12 @@ func loadScriptWithHashAndFlags(ic *interop.Context, script []byte, hash util.Ui
|
||||||
func TestContractCall(t *testing.T) {
|
func TestContractCall(t *testing.T) {
|
||||||
_, ic, bc := createVM(t)
|
_, ic, bc := createVM(t)
|
||||||
|
|
||||||
cs, currCs := getTestContractState(bc)
|
cs, currCs := getTestContractState(t, 4, 5, random.Uint160()) // sender and IDs are not important for the test
|
||||||
require.NoError(t, bc.contracts.Management.PutContractState(ic.DAO, cs))
|
require.NoError(t, bc.contracts.Management.PutContractState(ic.DAO, cs))
|
||||||
require.NoError(t, bc.contracts.Management.PutContractState(ic.DAO, currCs))
|
require.NoError(t, bc.contracts.Management.PutContractState(ic.DAO, currCs))
|
||||||
|
|
||||||
currScript := currCs.NEF.Script
|
currScript := currCs.NEF.Script
|
||||||
h := hash.Hash160(cs.NEF.Script)
|
h := cs.Hash
|
||||||
|
|
||||||
addArgs := stackitem.NewArray([]stackitem.Item{stackitem.Make(1), stackitem.Make(2)})
|
addArgs := stackitem.NewArray([]stackitem.Item{stackitem.Make(1), stackitem.Make(2)})
|
||||||
t.Run("Good", func(t *testing.T) {
|
t.Run("Good", func(t *testing.T) {
|
||||||
|
@ -1420,7 +1502,7 @@ func TestRuntimeCheckWitness(t *testing.T) {
|
||||||
func TestLoadToken(t *testing.T) {
|
func TestLoadToken(t *testing.T) {
|
||||||
bc := newTestChain(t)
|
bc := newTestChain(t)
|
||||||
|
|
||||||
cs, _ := getTestContractState(bc)
|
cs, _ := getTestContractState(t, 4, 5, random.Uint160()) // sender and IDs are not important for the test
|
||||||
require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs))
|
require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs))
|
||||||
|
|
||||||
t.Run("good", func(t *testing.T) {
|
t.Run("good", func(t *testing.T) {
|
||||||
|
@ -1463,7 +1545,7 @@ func TestRuntimeGetNetwork(t *testing.T) {
|
||||||
func TestRuntimeBurnGas(t *testing.T) {
|
func TestRuntimeBurnGas(t *testing.T) {
|
||||||
bc := newTestChain(t)
|
bc := newTestChain(t)
|
||||||
|
|
||||||
cs, _ := getTestContractState(bc)
|
cs, _ := getTestContractState(t, 4, 5, random.Uint160()) // sender and IDs are not important for the test
|
||||||
require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs))
|
require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs))
|
||||||
|
|
||||||
const sysFee = 2_000000
|
const sysFee = 2_000000
|
||||||
|
|
104
pkg/core/native/native_test/common_test.go
Normal file
104
pkg/core/native/native_test/common_test.go
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
package native_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newNativeClient(t *testing.T, name string) *neotest.ContractInvoker {
|
||||||
|
bc, acc := chain.NewSingle(t)
|
||||||
|
e := neotest.NewExecutor(t, bc, acc, acc)
|
||||||
|
|
||||||
|
return e.CommitteeInvoker(e.NativeHash(t, name))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testGetSet(t *testing.T, c *neotest.ContractInvoker, name string, defaultValue, minValue, maxValue int64) {
|
||||||
|
getName := "get" + name
|
||||||
|
setName := "set" + name
|
||||||
|
|
||||||
|
randomInvoker := c.WithSigners(c.NewAccount(t))
|
||||||
|
committeeInvoker := c.WithSigners(c.Committee)
|
||||||
|
|
||||||
|
t.Run("set, not signed by committee", func(t *testing.T) {
|
||||||
|
randomInvoker.InvokeFail(t, "invalid committee signature", setName, minValue+1)
|
||||||
|
})
|
||||||
|
t.Run("get, default value", func(t *testing.T) {
|
||||||
|
randomInvoker.Invoke(t, defaultValue, getName)
|
||||||
|
})
|
||||||
|
t.Run("set, too small value", func(t *testing.T) {
|
||||||
|
committeeInvoker.InvokeFail(t, "", setName, minValue-1)
|
||||||
|
})
|
||||||
|
|
||||||
|
if maxValue != 0 {
|
||||||
|
t.Run("set, too large value", func(t *testing.T) {
|
||||||
|
// use big.Int because max can be `math.MaxInt64`
|
||||||
|
max := big.NewInt(maxValue)
|
||||||
|
max.Add(max, big.NewInt(1))
|
||||||
|
committeeInvoker.InvokeFail(t, "", setName, max)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("set, success", func(t *testing.T) {
|
||||||
|
// Set and get in the same block.
|
||||||
|
txSet := committeeInvoker.PrepareInvoke(t, setName, defaultValue+1)
|
||||||
|
txGet := randomInvoker.PrepareInvoke(t, getName)
|
||||||
|
c.AddNewBlock(t, txSet, txGet)
|
||||||
|
c.CheckHalt(t, txSet.Hash(), stackitem.Null{})
|
||||||
|
|
||||||
|
if name != "GasPerBlock" { // GasPerBlock is set on the next block
|
||||||
|
c.CheckHalt(t, txGet.Hash(), stackitem.Make(defaultValue+1))
|
||||||
|
} else {
|
||||||
|
c.CheckHalt(t, txGet.Hash(), stackitem.Make(defaultValue))
|
||||||
|
c.AddNewBlock(t)
|
||||||
|
randomInvoker.Invoke(t, defaultValue+1, getName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get in the next block.
|
||||||
|
randomInvoker.Invoke(t, defaultValue+1, getName)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func setNodesByRole(t *testing.T, designateInvoker *neotest.ContractInvoker, ok bool, r noderoles.Role, nodes keys.PublicKeys) {
|
||||||
|
pubs := make([]interface{}, len(nodes))
|
||||||
|
for i := range nodes {
|
||||||
|
pubs[i] = nodes[i].Bytes()
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
h := designateInvoker.Invoke(t, stackitem.Null{}, "designateAsRole", int64(r), pubs)
|
||||||
|
designateInvoker.CheckTxNotificationEvent(t, h, 0, state.NotificationEvent{
|
||||||
|
ScriptHash: designateInvoker.Hash,
|
||||||
|
Name: native.DesignationEventName,
|
||||||
|
Item: stackitem.NewArray([]stackitem.Item{
|
||||||
|
stackitem.Make(int64(r)),
|
||||||
|
stackitem.Make(designateInvoker.Chain.BlockHeight()),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
designateInvoker.InvokeFail(t, "", "designateAsRole", int64(r), pubs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkNodeRoles(t *testing.T, designateInvoker *neotest.ContractInvoker, ok bool, r noderoles.Role, index uint32, res keys.PublicKeys) {
|
||||||
|
if ok {
|
||||||
|
designateInvoker.InvokeAndCheck(t, func(t *testing.T, stack []stackitem.Item) {
|
||||||
|
require.Equal(t, 1, len(stack))
|
||||||
|
arr := stack[0].Value().([]stackitem.Item)
|
||||||
|
require.Equal(t, len(res), len(arr))
|
||||||
|
for i := range arr {
|
||||||
|
require.Equal(t, res[i].Bytes(), arr[i].Value().([]byte), i)
|
||||||
|
}
|
||||||
|
}, "getDesignatedByRole", int64(r), int64(index))
|
||||||
|
} else {
|
||||||
|
designateInvoker.InvokeFail(t, "", "getDesignatedByRole", int64(r), int64(index))
|
||||||
|
}
|
||||||
|
}
|
47
pkg/core/native/native_test/designate_test.go
Normal file
47
pkg/core/native/native_test/designate_test.go
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
package native_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newDesignateClient(t *testing.T) *neotest.ContractInvoker {
|
||||||
|
return newNativeClient(t, nativenames.Designation)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDesignate_DesignateAsRole(t *testing.T) {
|
||||||
|
c := newDesignateClient(t)
|
||||||
|
e := c.Executor
|
||||||
|
designateInvoker := c.WithSigners(c.Committee)
|
||||||
|
|
||||||
|
priv, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
pubs := keys.PublicKeys{priv.PublicKey()}
|
||||||
|
|
||||||
|
setNodesByRole(t, designateInvoker, false, 0xFF, pubs)
|
||||||
|
setNodesByRole(t, designateInvoker, true, noderoles.Oracle, pubs)
|
||||||
|
index := e.Chain.BlockHeight() + 1
|
||||||
|
checkNodeRoles(t, designateInvoker, false, 0xFF, 0, nil)
|
||||||
|
checkNodeRoles(t, designateInvoker, false, noderoles.Oracle, 100500, nil)
|
||||||
|
checkNodeRoles(t, designateInvoker, true, noderoles.Oracle, 0, keys.PublicKeys{}) // returns an empty list
|
||||||
|
checkNodeRoles(t, designateInvoker, true, noderoles.Oracle, index, pubs) // returns pubs
|
||||||
|
|
||||||
|
priv1, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
pubs = keys.PublicKeys{priv1.PublicKey()}
|
||||||
|
setNodesByRole(t, designateInvoker, true, noderoles.StateValidator, pubs)
|
||||||
|
checkNodeRoles(t, designateInvoker, true, noderoles.StateValidator, e.Chain.BlockHeight()+1, pubs)
|
||||||
|
|
||||||
|
t.Run("neofs", func(t *testing.T) {
|
||||||
|
priv, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
pubs = keys.PublicKeys{priv.PublicKey()}
|
||||||
|
setNodesByRole(t, designateInvoker, true, noderoles.NeoFSAlphabet, pubs)
|
||||||
|
checkNodeRoles(t, designateInvoker, true, noderoles.NeoFSAlphabet, e.Chain.BlockHeight()+1, pubs)
|
||||||
|
})
|
||||||
|
}
|
139
pkg/core/native/native_test/gas_test.go
Normal file
139
pkg/core/native/native_test/gas_test.go
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
package native_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||||
|
"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/interop/native/roles"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newGasClient(t *testing.T) *neotest.ContractInvoker {
|
||||||
|
return newNativeClient(t, nativenames.Gas)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGAS_Roundtrip(t *testing.T) {
|
||||||
|
c := newGasClient(t)
|
||||||
|
e := c.Executor
|
||||||
|
gasInvoker := c.WithSigners(c.NewAccount(t))
|
||||||
|
owner := gasInvoker.Signers[0].ScriptHash()
|
||||||
|
|
||||||
|
getUtilityTokenBalance := func(acc util.Uint160) (*big.Int, uint32) {
|
||||||
|
lub, err := e.Chain.GetTokenLastUpdated(acc)
|
||||||
|
require.NoError(t, err)
|
||||||
|
return e.Chain.GetUtilityTokenBalance(acc), lub[e.NativeID(t, nativenames.Gas)]
|
||||||
|
}
|
||||||
|
|
||||||
|
initialBalance, _ := getUtilityTokenBalance(owner)
|
||||||
|
require.NotNil(t, initialBalance)
|
||||||
|
|
||||||
|
t.Run("bad: amount > initial balance", func(t *testing.T) {
|
||||||
|
h := gasInvoker.Invoke(t, false, "transfer", owner, owner, initialBalance.Int64()+1, nil)
|
||||||
|
tx, height := e.GetTransaction(t, h)
|
||||||
|
require.Equal(t, 0, len(e.GetTxExecResult(t, h).Events)) // no events (failed transfer)
|
||||||
|
// check balance and height were not changed
|
||||||
|
updatedBalance, updatedHeight := getUtilityTokenBalance(owner)
|
||||||
|
initialBalance.Sub(initialBalance, big.NewInt(tx.SystemFee+tx.NetworkFee))
|
||||||
|
require.Equal(t, initialBalance, updatedBalance)
|
||||||
|
require.Equal(t, height, updatedHeight)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("good: amount < initial balance", func(t *testing.T) {
|
||||||
|
h := gasInvoker.Invoke(t, true, "transfer", owner, owner, initialBalance.Int64()-1_0000_0000, nil)
|
||||||
|
tx, height := e.GetTransaction(t, h)
|
||||||
|
require.Equal(t, 1, len(e.GetTxExecResult(t, h).Events)) // roundtrip
|
||||||
|
// check balance wasn't changed and height was updated
|
||||||
|
updatedBalance, updatedHeight := getUtilityTokenBalance(owner)
|
||||||
|
initialBalance.Sub(initialBalance, big.NewInt(tx.SystemFee+tx.NetworkFee))
|
||||||
|
require.Equal(t, initialBalance, updatedBalance)
|
||||||
|
require.Equal(t, height, updatedHeight)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGAS_RewardWithP2PSigExtensionsEnabled(t *testing.T) {
|
||||||
|
const (
|
||||||
|
nNotaries = 2
|
||||||
|
nKeys = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
bc, validator, committee := chain.NewMultiWithCustomConfig(t, func(cfg *config.ProtocolConfiguration) {
|
||||||
|
cfg.P2PSigExtensions = true
|
||||||
|
})
|
||||||
|
e := neotest.NewExecutor(t, bc, validator, committee)
|
||||||
|
gasCommitteeInvoker := e.CommitteeInvoker(e.NativeHash(t, nativenames.Gas))
|
||||||
|
notaryHash := e.NativeHash(t, nativenames.Notary)
|
||||||
|
|
||||||
|
// transfer funds to committee
|
||||||
|
e.ValidatorInvoker(e.NativeHash(t, nativenames.Gas)).Invoke(t, true, "transfer", e.Validator.ScriptHash(), e.CommitteeHash, 1000_0000_0000, nil)
|
||||||
|
|
||||||
|
// set Notary nodes and check their balance
|
||||||
|
notaryNodes := make([]*keys.PrivateKey, nNotaries)
|
||||||
|
notaryNodesPublicKeys := make([]interface{}, nNotaries)
|
||||||
|
var err error
|
||||||
|
for i := range notaryNodes {
|
||||||
|
notaryNodes[i], err = keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
notaryNodesPublicKeys[i] = notaryNodes[i].PublicKey().Bytes()
|
||||||
|
}
|
||||||
|
e.CommitteeInvoker(e.NativeHash(t, nativenames.Designation)).Invoke(t, stackitem.Null{}, "designateAsRole", int(roles.P2PNotary), notaryNodesPublicKeys)
|
||||||
|
for _, notaryNode := range notaryNodes {
|
||||||
|
e.CheckGASBalance(t, notaryNode.GetScriptHash(), big.NewInt(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
// deposit GAS for `signer` with lock until the next block
|
||||||
|
depositAmount := 100_0000 + (2+int64(nKeys))*transaction.NotaryServiceFeePerKey // sysfee + netfee of the next transaction
|
||||||
|
gasCommitteeInvoker.Invoke(t, true, "transfer", e.CommitteeHash, notaryHash, depositAmount, []interface{}{e.CommitteeHash, e.Chain.BlockHeight() + 1})
|
||||||
|
|
||||||
|
// save initial GAS total supply
|
||||||
|
getGASTS := func(t *testing.T) int64 {
|
||||||
|
stack, err := gasCommitteeInvoker.TestInvoke(t, "totalSupply")
|
||||||
|
require.NoError(t, err)
|
||||||
|
return stack.Pop().Value().(*big.Int).Int64()
|
||||||
|
}
|
||||||
|
tsInitial := getGASTS(t)
|
||||||
|
|
||||||
|
// send transaction with Notary contract as a sender
|
||||||
|
tx := transaction.New([]byte{byte(opcode.PUSH1)}, 1_000_000)
|
||||||
|
tx.Nonce = neotest.Nonce()
|
||||||
|
tx.ValidUntilBlock = e.Chain.BlockHeight() + 1
|
||||||
|
tx.Attributes = append(tx.Attributes, transaction.Attribute{Type: transaction.NotaryAssistedT, Value: &transaction.NotaryAssisted{NKeys: uint8(nKeys)}})
|
||||||
|
tx.NetworkFee = (2 + int64(nKeys)) * transaction.NotaryServiceFeePerKey
|
||||||
|
tx.Signers = []transaction.Signer{
|
||||||
|
{
|
||||||
|
Account: notaryHash,
|
||||||
|
Scopes: transaction.None,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Account: e.CommitteeHash,
|
||||||
|
Scopes: transaction.None,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
tx.Scripts = []transaction.Witness{
|
||||||
|
{
|
||||||
|
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, notaryNodes[0].SignHashable(uint32(e.Chain.GetConfig().Magic), tx)...),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
InvocationScript: e.Committee.SignHashable(uint32(e.Chain.GetConfig().Magic), tx),
|
||||||
|
VerificationScript: e.Committee.Script(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
e.AddNewBlock(t, tx)
|
||||||
|
|
||||||
|
// check balance of notaries
|
||||||
|
e.CheckGASBalance(t, notaryHash, big.NewInt(int64(depositAmount-tx.SystemFee-tx.NetworkFee)))
|
||||||
|
for _, notaryNode := range notaryNodes {
|
||||||
|
e.CheckGASBalance(t, notaryNode.GetScriptHash(), big.NewInt(int64(transaction.NotaryServiceFeePerKey*(nKeys+1)/nNotaries)))
|
||||||
|
}
|
||||||
|
tsUpdated := getGASTS(t)
|
||||||
|
tsExpected := tsInitial + 5000_0000 - tx.SystemFee
|
||||||
|
require.Equal(t, tsExpected, tsUpdated)
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
package policyhelper
|
||||||
|
|
||||||
|
func Do() bool {
|
||||||
|
return true
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
name: "Policy helper contract"
|
||||||
|
sourceurl: https://github.com/nspcc-dev/neo-go
|
||||||
|
supportedstandards: []
|
||||||
|
safemethods: ["do"]
|
147
pkg/core/native/native_test/ledger_test.go
Normal file
147
pkg/core/native/native_test/ledger_test.go
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
package native_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newLedgerClient(t *testing.T) *neotest.ContractInvoker {
|
||||||
|
bc, acc := chain.NewSingleWithCustomConfig(t, func(cfg *config.ProtocolConfiguration) {
|
||||||
|
cfg.MaxTraceableBlocks = 10 // reduce number of traceable blocks for Ledger tests
|
||||||
|
})
|
||||||
|
e := neotest.NewExecutor(t, bc, acc, acc)
|
||||||
|
|
||||||
|
return e.CommitteeInvoker(e.NativeHash(t, nativenames.Ledger))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLedger_GetTransactionHeight(t *testing.T) {
|
||||||
|
c := newLedgerClient(t)
|
||||||
|
e := c.Executor
|
||||||
|
ledgerInvoker := c.WithSigners(c.Committee)
|
||||||
|
|
||||||
|
height := 13
|
||||||
|
e.GenerateNewBlocks(t, height-1)
|
||||||
|
hash := e.InvokeScript(t, []byte{byte(opcode.RET)}, []neotest.Signer{c.Committee})
|
||||||
|
|
||||||
|
t.Run("good", func(t *testing.T) {
|
||||||
|
ledgerInvoker.Invoke(t, height, "getTransactionHeight", hash)
|
||||||
|
})
|
||||||
|
t.Run("unknown transaction", func(t *testing.T) {
|
||||||
|
ledgerInvoker.Invoke(t, -1, "getTransactionHeight", util.Uint256{1, 2, 3})
|
||||||
|
})
|
||||||
|
t.Run("not a hash", func(t *testing.T) {
|
||||||
|
ledgerInvoker.InvokeFail(t, "expected []byte of size 32", "getTransactionHeight", []byte{1, 2, 3})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLedger_GetTransaction(t *testing.T) {
|
||||||
|
c := newLedgerClient(t)
|
||||||
|
e := c.Executor
|
||||||
|
ledgerInvoker := c.WithSigners(c.Committee)
|
||||||
|
|
||||||
|
hash := e.InvokeScript(t, []byte{byte(opcode.RET)}, []neotest.Signer{c.Committee})
|
||||||
|
tx, _ := e.GetTransaction(t, hash)
|
||||||
|
|
||||||
|
t.Run("success", func(t *testing.T) {
|
||||||
|
ledgerInvoker.Invoke(t, []stackitem.Item{
|
||||||
|
stackitem.NewByteArray(tx.Hash().BytesBE()),
|
||||||
|
stackitem.NewBigInteger(big.NewInt(int64(tx.Version))),
|
||||||
|
stackitem.NewBigInteger(big.NewInt(int64(tx.Nonce))),
|
||||||
|
stackitem.NewByteArray(tx.Sender().BytesBE()),
|
||||||
|
stackitem.NewBigInteger(big.NewInt(tx.SystemFee)),
|
||||||
|
stackitem.NewBigInteger(big.NewInt(tx.NetworkFee)),
|
||||||
|
stackitem.NewBigInteger(big.NewInt(int64(tx.ValidUntilBlock))),
|
||||||
|
stackitem.NewByteArray(tx.Script),
|
||||||
|
}, "getTransaction", tx.Hash())
|
||||||
|
})
|
||||||
|
t.Run("isn't traceable", func(t *testing.T) {
|
||||||
|
// Add more blocks so that tx becomes untraceable.
|
||||||
|
e.GenerateNewBlocks(t, int(e.Chain.GetConfig().MaxTraceableBlocks))
|
||||||
|
ledgerInvoker.Invoke(t, stackitem.Null{}, "getTransaction", tx.Hash())
|
||||||
|
})
|
||||||
|
t.Run("bad hash", func(t *testing.T) {
|
||||||
|
ledgerInvoker.Invoke(t, stackitem.Null{}, "getTransaction", util.Uint256{})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLedger_GetTransactionFromBlock(t *testing.T) {
|
||||||
|
c := newLedgerClient(t)
|
||||||
|
e := c.Executor
|
||||||
|
ledgerInvoker := c.WithSigners(c.Committee)
|
||||||
|
|
||||||
|
ledgerInvoker.Invoke(t, e.Chain.BlockHeight(), "currentIndex") // Adds a block.
|
||||||
|
b := e.GetBlockByIndex(t, int(e.Chain.BlockHeight()))
|
||||||
|
|
||||||
|
check := func(t *testing.T, stack []stackitem.Item) {
|
||||||
|
require.Equal(t, 1, len(stack))
|
||||||
|
actual, ok := stack[0].Value().([]stackitem.Item)
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Equal(t, b.Transactions[0].Hash().BytesBE(), actual[0].Value().([]byte))
|
||||||
|
}
|
||||||
|
t.Run("good, by hash", func(t *testing.T) {
|
||||||
|
ledgerInvoker.InvokeAndCheck(t, check, "getTransactionFromBlock", b.Hash(), int64(0))
|
||||||
|
})
|
||||||
|
t.Run("good, by index", func(t *testing.T) {
|
||||||
|
ledgerInvoker.InvokeAndCheck(t, check, "getTransactionFromBlock", int64(b.Index), int64(0))
|
||||||
|
})
|
||||||
|
t.Run("bad transaction index", func(t *testing.T) {
|
||||||
|
ledgerInvoker.InvokeFail(t, "", "getTransactionFromBlock", b.Hash(), int64(1))
|
||||||
|
})
|
||||||
|
t.Run("bad block hash (>int64)", func(t *testing.T) {
|
||||||
|
ledgerInvoker.InvokeFail(t, "", "getTransactionFromBlock", b.Hash().BytesBE()[:10], int64(0))
|
||||||
|
})
|
||||||
|
t.Run("invalid block hash (int64)", func(t *testing.T) {
|
||||||
|
ledgerInvoker.InvokeFail(t, "", "getTransactionFromBlock", b.Hash().BytesBE()[:6], int64(0))
|
||||||
|
})
|
||||||
|
t.Run("unknown block hash", func(t *testing.T) {
|
||||||
|
ledgerInvoker.Invoke(t, stackitem.Null{}, "getTransactionFromBlock", b.Hash().BytesLE(), int64(0))
|
||||||
|
})
|
||||||
|
t.Run("isn't traceable", func(t *testing.T) {
|
||||||
|
e.GenerateNewBlocks(t, int(e.Chain.GetConfig().MaxTraceableBlocks))
|
||||||
|
ledgerInvoker.Invoke(t, stackitem.Null{}, "getTransactionFromBlock", b.Hash(), int64(0))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLedger_GetBlock(t *testing.T) {
|
||||||
|
c := newLedgerClient(t)
|
||||||
|
e := c.Executor
|
||||||
|
ledgerInvoker := c.WithSigners(c.Committee)
|
||||||
|
|
||||||
|
ledgerInvoker.Invoke(t, e.Chain.GetHeaderHash(int(e.Chain.BlockHeight())).BytesBE(), "currentHash") // Adds a block.
|
||||||
|
b := e.GetBlockByIndex(t, int(e.Chain.BlockHeight()))
|
||||||
|
|
||||||
|
expected := []stackitem.Item{
|
||||||
|
stackitem.NewByteArray(b.Hash().BytesBE()),
|
||||||
|
stackitem.NewBigInteger(big.NewInt(int64(b.Version))),
|
||||||
|
stackitem.NewByteArray(b.PrevHash.BytesBE()),
|
||||||
|
stackitem.NewByteArray(b.MerkleRoot.BytesBE()),
|
||||||
|
stackitem.NewBigInteger(big.NewInt(int64(b.Timestamp))),
|
||||||
|
stackitem.NewBigInteger(big.NewInt(int64(b.Nonce))),
|
||||||
|
stackitem.NewBigInteger(big.NewInt(int64(b.Index))),
|
||||||
|
stackitem.NewByteArray(b.NextConsensus.BytesBE()),
|
||||||
|
stackitem.NewBigInteger(big.NewInt(int64(len(b.Transactions)))),
|
||||||
|
}
|
||||||
|
t.Run("good, by hash", func(t *testing.T) {
|
||||||
|
ledgerInvoker.Invoke(t, expected, "getBlock", b.Hash())
|
||||||
|
})
|
||||||
|
t.Run("good, by index", func(t *testing.T) {
|
||||||
|
ledgerInvoker.Invoke(t, expected, "getBlock", int64(b.Index))
|
||||||
|
})
|
||||||
|
t.Run("bad hash", func(t *testing.T) {
|
||||||
|
ledgerInvoker.Invoke(t, stackitem.Null{}, "getBlock", b.Hash().BytesLE())
|
||||||
|
})
|
||||||
|
t.Run("isn't traceable", func(t *testing.T) {
|
||||||
|
e.GenerateNewBlocks(t, int(e.Chain.GetConfig().MaxTraceableBlocks))
|
||||||
|
ledgerInvoker.Invoke(t, stackitem.Null{}, "getBlock", b.Hash())
|
||||||
|
})
|
||||||
|
}
|
590
pkg/core/native/native_test/management_test.go
Normal file
590
pkg/core/native/native_test/management_test.go
Normal file
|
@ -0,0 +1,590 @@
|
||||||
|
package native_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/chaindump"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||||
|
"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/io"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
|
||||||
|
"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"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
helper1ContractNEFPath = filepath.Join("..", "..", "test_data", "management_helper", "management_helper1.nef")
|
||||||
|
helper1ContractManifestPath = filepath.Join("..", "..", "test_data", "management_helper", "management_helper1.manifest.json")
|
||||||
|
helper2ContractNEFPath = filepath.Join("..", "..", "test_data", "management_helper", "management_helper2.nef")
|
||||||
|
helper2ContractManifestPath = filepath.Join("..", "..", "test_data", "management_helper", "management_helper2.manifest.json")
|
||||||
|
)
|
||||||
|
|
||||||
|
func newManagementClient(t *testing.T) *neotest.ContractInvoker {
|
||||||
|
return newNativeClient(t, nativenames.Management)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManagement_MinimumDeploymentFee(t *testing.T) {
|
||||||
|
testGetSet(t, newManagementClient(t), "MinimumDeploymentFee", 10_00000000, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTestContractState returns 2 contracts second of which is allowed to call the first.
|
||||||
|
func getTestContractState(t *testing.T, id1, id2 int32, sender2 util.Uint160) (*state.Contract, *state.Contract) {
|
||||||
|
errNotFound := errors.New("auto-generated oracle contract is not found, use TestGenerateOracleContract to regenerate")
|
||||||
|
|
||||||
|
neBytes, err := ioutil.ReadFile(helper1ContractNEFPath)
|
||||||
|
require.NoError(t, err, fmt.Errorf("nef1: %w", errNotFound))
|
||||||
|
ne, err := nef.FileFromBytes(neBytes)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
mBytes, err := ioutil.ReadFile(helper1ContractManifestPath)
|
||||||
|
require.NoError(t, err, fmt.Errorf("manifest1: %w", errNotFound))
|
||||||
|
m := &manifest.Manifest{}
|
||||||
|
err = json.Unmarshal(mBytes, m)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
cs1 := &state.Contract{
|
||||||
|
ContractBase: state.ContractBase{
|
||||||
|
NEF: ne,
|
||||||
|
Manifest: *m,
|
||||||
|
ID: id1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
neBytes, err = ioutil.ReadFile(helper2ContractNEFPath)
|
||||||
|
require.NoError(t, err, fmt.Errorf("nef2: %w", errNotFound))
|
||||||
|
ne, err = nef.FileFromBytes(neBytes)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
mBytes, err = ioutil.ReadFile(helper2ContractManifestPath)
|
||||||
|
require.NoError(t, err, fmt.Errorf("manifest2: %w", errNotFound))
|
||||||
|
m = &manifest.Manifest{}
|
||||||
|
err = json.Unmarshal(mBytes, m)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Retrieve hash of the first contract from the permissions of the second contract.
|
||||||
|
require.Equal(t, 1, len(m.Permissions))
|
||||||
|
require.Equal(t, manifest.PermissionHash, m.Permissions[0].Contract.Type)
|
||||||
|
cs1.Hash = m.Permissions[0].Contract.Hash()
|
||||||
|
|
||||||
|
cs2 := &state.Contract{
|
||||||
|
ContractBase: state.ContractBase{
|
||||||
|
NEF: ne,
|
||||||
|
Manifest: *m,
|
||||||
|
ID: id2,
|
||||||
|
Hash: state.CreateContractHash(sender2, ne.Checksum, m.Name),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return cs1, cs2
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManagement_ContractDeploy(t *testing.T) {
|
||||||
|
c := newManagementClient(t)
|
||||||
|
managementInvoker := c.WithSigners(c.Committee)
|
||||||
|
|
||||||
|
cs1, _ := getTestContractState(t, 1, 2, c.Committee.ScriptHash())
|
||||||
|
manifestBytes, err := json.Marshal(cs1.Manifest)
|
||||||
|
require.NoError(t, err)
|
||||||
|
nefBytes, err := cs1.NEF.Bytes()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
t.Run("no NEF", func(t *testing.T) {
|
||||||
|
managementInvoker.InvokeFail(t, "no valid NEF provided", "deploy", nil, manifestBytes)
|
||||||
|
})
|
||||||
|
t.Run("no manifest", func(t *testing.T) {
|
||||||
|
managementInvoker.InvokeFail(t, "no valid manifest provided", "deploy", nefBytes, nil)
|
||||||
|
})
|
||||||
|
t.Run("int for NEF", func(t *testing.T) {
|
||||||
|
managementInvoker.InvokeFail(t, "invalid NEF file", "deploy", int64(1), manifestBytes)
|
||||||
|
})
|
||||||
|
t.Run("zero-length NEF", func(t *testing.T) {
|
||||||
|
managementInvoker.InvokeFail(t, "invalid NEF file", "deploy", []byte{}, manifestBytes)
|
||||||
|
})
|
||||||
|
t.Run("array for NEF", func(t *testing.T) {
|
||||||
|
managementInvoker.InvokeFail(t, "invalid NEF file", "deploy", []interface{}{int64(1)}, manifestBytes)
|
||||||
|
})
|
||||||
|
t.Run("bad script in NEF", func(t *testing.T) {
|
||||||
|
nf, err := nef.FileFromBytes(nefBytes) // make a full copy
|
||||||
|
require.NoError(t, err)
|
||||||
|
nf.Script[0] = 0xff
|
||||||
|
nf.CalculateChecksum()
|
||||||
|
nefBad, err := nf.Bytes()
|
||||||
|
require.NoError(t, err)
|
||||||
|
managementInvoker.InvokeFail(t, "invalid NEF file", "deploy", nefBad, manifestBytes)
|
||||||
|
})
|
||||||
|
t.Run("int for manifest", func(t *testing.T) {
|
||||||
|
managementInvoker.InvokeFail(t, "invalid manifest", "deploy", nefBytes, int64(1))
|
||||||
|
})
|
||||||
|
t.Run("zero-length manifest", func(t *testing.T) {
|
||||||
|
managementInvoker.InvokeFail(t, "invalid manifest", "deploy", nefBytes, []byte{})
|
||||||
|
})
|
||||||
|
t.Run("array for manifest", func(t *testing.T) {
|
||||||
|
managementInvoker.InvokeFail(t, "invalid manifest", "deploy", nefBytes, []interface{}{int64(1)})
|
||||||
|
})
|
||||||
|
t.Run("non-utf8 manifest", func(t *testing.T) {
|
||||||
|
manifestBad := bytes.Replace(manifestBytes, []byte("TestMain"), []byte("\xff\xfe\xfd"), 1) // Replace name.
|
||||||
|
managementInvoker.InvokeFail(t, "manifest is not UTF-8 compliant", "deploy", nefBytes, manifestBad)
|
||||||
|
})
|
||||||
|
t.Run("invalid manifest", func(t *testing.T) {
|
||||||
|
pkey, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
badManifest := cs1.Manifest
|
||||||
|
badManifest.Groups = []manifest.Group{{PublicKey: pkey.PublicKey(), Signature: make([]byte, 64)}}
|
||||||
|
manifB, err := json.Marshal(&badManifest)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
managementInvoker.InvokeFail(t, "invalid manifest", "deploy", nefBytes, manifB)
|
||||||
|
})
|
||||||
|
t.Run("bad methods in manifest 1", func(t *testing.T) {
|
||||||
|
badManifest := cs1.Manifest
|
||||||
|
badManifest.ABI.Methods = make([]manifest.Method, len(cs1.Manifest.ABI.Methods))
|
||||||
|
copy(badManifest.ABI.Methods, cs1.Manifest.ABI.Methods)
|
||||||
|
badManifest.ABI.Methods[0].Offset = 100500 // out of bounds
|
||||||
|
manifB, err := json.Marshal(&badManifest)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
managementInvoker.InvokeFail(t, "out of bounds method offset", "deploy", nefBytes, manifB)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("bad methods in manifest 2", func(t *testing.T) {
|
||||||
|
var badManifest = cs1.Manifest
|
||||||
|
badManifest.ABI.Methods = make([]manifest.Method, len(cs1.Manifest.ABI.Methods))
|
||||||
|
copy(badManifest.ABI.Methods, cs1.Manifest.ABI.Methods)
|
||||||
|
badManifest.ABI.Methods[0].Offset = len(cs1.NEF.Script) - 2 // Ends with `CALLT(X,X);RET`.
|
||||||
|
|
||||||
|
manifB, err := json.Marshal(badManifest)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
managementInvoker.InvokeFail(t, "some methods point to wrong offsets (not to instruction boundary)", "deploy", nefBytes, manifB)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("not enough GAS", func(t *testing.T) {
|
||||||
|
tx := managementInvoker.NewUnsignedTx(t, managementInvoker.Hash, "deploy", nefBytes, manifestBytes)
|
||||||
|
managementInvoker.SignTx(t, tx, 1_0000_0000, managementInvoker.Signers...)
|
||||||
|
managementInvoker.AddNewBlock(t, tx)
|
||||||
|
managementInvoker.CheckFault(t, tx.Hash(), "gas limit exceeded")
|
||||||
|
})
|
||||||
|
|
||||||
|
si, err := cs1.ToStackItem()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
t.Run("positive", func(t *testing.T) {
|
||||||
|
tx1 := managementInvoker.PrepareInvoke(t, "deploy", nefBytes, manifestBytes)
|
||||||
|
tx2 := managementInvoker.PrepareInvoke(t, "getContract", cs1.Hash.BytesBE())
|
||||||
|
managementInvoker.AddNewBlock(t, tx1, tx2)
|
||||||
|
managementInvoker.CheckHalt(t, tx1.Hash(), si)
|
||||||
|
managementInvoker.CheckHalt(t, tx2.Hash(), si)
|
||||||
|
managementInvoker.CheckTxNotificationEvent(t, tx1.Hash(), 0, state.NotificationEvent{
|
||||||
|
ScriptHash: c.NativeHash(t, nativenames.Management),
|
||||||
|
Name: "Deploy",
|
||||||
|
Item: stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray(cs1.Hash.BytesBE())}),
|
||||||
|
})
|
||||||
|
t.Run("_deploy called", func(t *testing.T) {
|
||||||
|
helperInvoker := c.Executor.CommitteeInvoker(cs1.Hash)
|
||||||
|
expected := stackitem.NewArray([]stackitem.Item{stackitem.Make("create"), stackitem.Null{}})
|
||||||
|
expectedBytes, err := stackitem.Serialize(expected)
|
||||||
|
require.NoError(t, err)
|
||||||
|
helperInvoker.Invoke(t, stackitem.NewByteArray(expectedBytes), "getValue")
|
||||||
|
})
|
||||||
|
t.Run("get after deploy", func(t *testing.T) {
|
||||||
|
managementInvoker.Invoke(t, si, "getContract", cs1.Hash.BytesBE())
|
||||||
|
})
|
||||||
|
t.Run("get after restore", func(t *testing.T) {
|
||||||
|
w := io.NewBufBinWriter()
|
||||||
|
require.NoError(t, chaindump.Dump(c.Executor.Chain, w.BinWriter, 0, c.Executor.Chain.BlockHeight()+1))
|
||||||
|
require.NoError(t, w.Err)
|
||||||
|
|
||||||
|
r := io.NewBinReaderFromBuf(w.Bytes())
|
||||||
|
bc2, acc := chain.NewSingle(t)
|
||||||
|
e2 := neotest.NewExecutor(t, bc2, acc, acc)
|
||||||
|
managementInvoker2 := e2.CommitteeInvoker(e2.NativeHash(t, nativenames.Management))
|
||||||
|
|
||||||
|
require.NoError(t, chaindump.Restore(bc2, r, 0, c.Executor.Chain.BlockHeight()+1, nil))
|
||||||
|
require.NoError(t, r.Err)
|
||||||
|
managementInvoker2.Invoke(t, si, "getContract", cs1.Hash.BytesBE())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
t.Run("contract already exists", func(t *testing.T) {
|
||||||
|
managementInvoker.InvokeFail(t, "contract already exists", "deploy", nefBytes, manifestBytes)
|
||||||
|
})
|
||||||
|
t.Run("failed _deploy", func(t *testing.T) {
|
||||||
|
deployScript := []byte{byte(opcode.ABORT)}
|
||||||
|
m := manifest.NewManifest("TestDeployAbort")
|
||||||
|
m.ABI.Methods = []manifest.Method{
|
||||||
|
{
|
||||||
|
Name: manifest.MethodDeploy,
|
||||||
|
Offset: 0,
|
||||||
|
Parameters: []manifest.Parameter{
|
||||||
|
manifest.NewParameter("data", smartcontract.AnyType),
|
||||||
|
manifest.NewParameter("isUpdate", smartcontract.BoolType),
|
||||||
|
},
|
||||||
|
ReturnType: smartcontract.VoidType,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
nefD, err := nef.NewFile(deployScript)
|
||||||
|
require.NoError(t, err)
|
||||||
|
nefDb, err := nefD.Bytes()
|
||||||
|
require.NoError(t, err)
|
||||||
|
manifD, err := json.Marshal(m)
|
||||||
|
require.NoError(t, err)
|
||||||
|
managementInvoker.InvokeFail(t, "ABORT", "deploy", nefDb, manifD)
|
||||||
|
|
||||||
|
t.Run("get after failed deploy", func(t *testing.T) {
|
||||||
|
h := state.CreateContractHash(c.CommitteeHash, nefD.Checksum, m.Name)
|
||||||
|
managementInvoker.Invoke(t, stackitem.Null{}, "getContract", h.BytesBE())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
t.Run("bad _deploy", func(t *testing.T) { // invalid _deploy signature
|
||||||
|
deployScript := []byte{byte(opcode.RET)}
|
||||||
|
m := manifest.NewManifest("TestBadDeploy")
|
||||||
|
m.ABI.Methods = []manifest.Method{
|
||||||
|
{
|
||||||
|
Name: manifest.MethodDeploy,
|
||||||
|
Offset: 0,
|
||||||
|
Parameters: []manifest.Parameter{
|
||||||
|
manifest.NewParameter("data", smartcontract.AnyType),
|
||||||
|
manifest.NewParameter("isUpdate", smartcontract.BoolType),
|
||||||
|
},
|
||||||
|
ReturnType: smartcontract.ArrayType,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
nefD, err := nef.NewFile(deployScript)
|
||||||
|
require.NoError(t, err)
|
||||||
|
nefDb, err := nefD.Bytes()
|
||||||
|
require.NoError(t, err)
|
||||||
|
manifD, err := json.Marshal(m)
|
||||||
|
require.NoError(t, err)
|
||||||
|
managementInvoker.InvokeFail(t, "invalid return values count: expected 0, got 2", "deploy", nefDb, manifD)
|
||||||
|
|
||||||
|
t.Run("get after bad _deploy", func(t *testing.T) {
|
||||||
|
h := state.CreateContractHash(c.CommitteeHash, nefD.Checksum, m.Name)
|
||||||
|
managementInvoker.Invoke(t, stackitem.Null{}, "getContract", h.BytesBE())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManagement_StartFromHeight(t *testing.T) {
|
||||||
|
// Create database to be able to start another chain from the same height later.
|
||||||
|
ldbDir := t.TempDir()
|
||||||
|
dbConfig := storage.DBConfiguration{
|
||||||
|
Type: "leveldb",
|
||||||
|
LevelDBOptions: storage.LevelDBOptions{
|
||||||
|
DataDirectoryPath: ldbDir,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
newLevelStore, err := storage.NewLevelDBStore(dbConfig.LevelDBOptions)
|
||||||
|
require.Nil(t, err, "NewLevelDBStore error")
|
||||||
|
|
||||||
|
// Create blockchain and put contract state to it.
|
||||||
|
bc, acc := chain.NewSingleWithCustomConfigAndStore(t, nil, newLevelStore, false)
|
||||||
|
go bc.Run()
|
||||||
|
e := neotest.NewExecutor(t, bc, acc, acc)
|
||||||
|
c := e.CommitteeInvoker(e.NativeHash(t, nativenames.Management))
|
||||||
|
managementInvoker := c.WithSigners(c.Committee)
|
||||||
|
|
||||||
|
cs1, _ := getTestContractState(t, 1, 2, c.CommitteeHash)
|
||||||
|
manifestBytes, err := json.Marshal(cs1.Manifest)
|
||||||
|
require.NoError(t, err)
|
||||||
|
nefBytes, err := cs1.NEF.Bytes()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
si, err := cs1.ToStackItem()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
managementInvoker.Invoke(t, si, "deploy", nefBytes, manifestBytes)
|
||||||
|
managementInvoker.Invoke(t, si, "getContract", cs1.Hash.BytesBE())
|
||||||
|
|
||||||
|
// Close current blockchain and start the new one from the same height with the same db.
|
||||||
|
bc.Close()
|
||||||
|
newLevelStore, err = storage.NewLevelDBStore(dbConfig.LevelDBOptions)
|
||||||
|
require.NoError(t, err)
|
||||||
|
bc2, acc := chain.NewSingleWithCustomConfigAndStore(t, nil, newLevelStore, true)
|
||||||
|
e2 := neotest.NewExecutor(t, bc2, acc, acc)
|
||||||
|
managementInvoker2 := e2.CommitteeInvoker(e2.NativeHash(t, nativenames.Management))
|
||||||
|
|
||||||
|
// Check that initialisation of native Management was correctly performed.
|
||||||
|
managementInvoker2.Invoke(t, si, "getContract", cs1.Hash.BytesBE())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManagement_DeployManifestOverflow(t *testing.T) {
|
||||||
|
c := newManagementClient(t)
|
||||||
|
managementInvoker := c.WithSigners(c.Committee)
|
||||||
|
|
||||||
|
cs1, _ := getTestContractState(t, 1, 2, c.CommitteeHash)
|
||||||
|
manif1, err := json.Marshal(cs1.Manifest)
|
||||||
|
require.NoError(t, err)
|
||||||
|
nef1, err := nef.NewFile(cs1.NEF.Script)
|
||||||
|
require.NoError(t, err)
|
||||||
|
nef1b, err := nef1.Bytes()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
w := io.NewBufBinWriter()
|
||||||
|
emit.Bytes(w.BinWriter, manif1)
|
||||||
|
emit.Int(w.BinWriter, manifest.MaxManifestSize)
|
||||||
|
emit.Opcodes(w.BinWriter, opcode.NEWBUFFER, opcode.CAT)
|
||||||
|
emit.Bytes(w.BinWriter, nef1b)
|
||||||
|
emit.Int(w.BinWriter, 2)
|
||||||
|
emit.Opcodes(w.BinWriter, opcode.PACK)
|
||||||
|
emit.AppCallNoArgs(w.BinWriter, managementInvoker.Hash, "deploy", callflag.All)
|
||||||
|
require.NoError(t, w.Err)
|
||||||
|
script := w.Bytes()
|
||||||
|
|
||||||
|
tx := transaction.New(script, 0)
|
||||||
|
tx.ValidUntilBlock = managementInvoker.Chain.BlockHeight() + 1
|
||||||
|
managementInvoker.SignTx(t, tx, 100_0000_0000, managementInvoker.Signers...)
|
||||||
|
managementInvoker.AddNewBlock(t, tx)
|
||||||
|
managementInvoker.CheckFault(t, tx.Hash(), fmt.Sprintf("invalid manifest: len is %d (max %d)", manifest.MaxManifestSize+len(manif1), manifest.MaxManifestSize))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManagement_ContractDeployAndUpdateWithParameter(t *testing.T) {
|
||||||
|
c := newManagementClient(t)
|
||||||
|
managementInvoker := c.WithSigners(c.Committee)
|
||||||
|
|
||||||
|
cs1, _ := getTestContractState(t, 1, 2, c.CommitteeHash)
|
||||||
|
cs1.Manifest.Permissions = []manifest.Permission{*manifest.NewPermission(manifest.PermissionWildcard)}
|
||||||
|
cs1.ID = 1
|
||||||
|
cs1.Hash = state.CreateContractHash(c.CommitteeHash, cs1.NEF.Checksum, cs1.Manifest.Name)
|
||||||
|
manif1, err := json.Marshal(cs1.Manifest)
|
||||||
|
require.NoError(t, err)
|
||||||
|
nef1b, err := cs1.NEF.Bytes()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
si, err := cs1.ToStackItem()
|
||||||
|
require.NoError(t, err)
|
||||||
|
managementInvoker.Invoke(t, si, "deploy", nef1b, manif1)
|
||||||
|
helperInvoker := c.Executor.CommitteeInvoker(cs1.Hash)
|
||||||
|
|
||||||
|
t.Run("_deploy called", func(t *testing.T) {
|
||||||
|
expected := stackitem.NewArray([]stackitem.Item{stackitem.Make("create"), stackitem.Null{}})
|
||||||
|
expectedBytes, err := stackitem.Serialize(expected)
|
||||||
|
require.NoError(t, err)
|
||||||
|
helperInvoker.Invoke(t, stackitem.NewByteArray(expectedBytes), "getValue")
|
||||||
|
})
|
||||||
|
|
||||||
|
cs1.NEF.Script = append(cs1.NEF.Script, byte(opcode.RET))
|
||||||
|
cs1.NEF.Checksum = cs1.NEF.CalculateChecksum()
|
||||||
|
nef1b, err = cs1.NEF.Bytes()
|
||||||
|
require.NoError(t, err)
|
||||||
|
cs1.UpdateCounter++
|
||||||
|
|
||||||
|
helperInvoker.Invoke(t, stackitem.Null{}, "update", nef1b, nil, "new data")
|
||||||
|
|
||||||
|
t.Run("_deploy called", func(t *testing.T) {
|
||||||
|
expected := stackitem.NewArray([]stackitem.Item{stackitem.Make("update"), stackitem.Make("new data")})
|
||||||
|
expectedBytes, err := stackitem.Serialize(expected)
|
||||||
|
require.NoError(t, err)
|
||||||
|
helperInvoker.Invoke(t, stackitem.NewByteArray(expectedBytes), "getValue")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManagement_ContractUpdate(t *testing.T) {
|
||||||
|
c := newManagementClient(t)
|
||||||
|
managementInvoker := c.WithSigners(c.Committee)
|
||||||
|
|
||||||
|
cs1, _ := getTestContractState(t, 1, 2, c.CommitteeHash)
|
||||||
|
// Allow calling management contract.
|
||||||
|
cs1.Manifest.Permissions = []manifest.Permission{*manifest.NewPermission(manifest.PermissionWildcard)}
|
||||||
|
manifestBytes, err := json.Marshal(cs1.Manifest)
|
||||||
|
require.NoError(t, err)
|
||||||
|
nefBytes, err := cs1.NEF.Bytes()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
si, err := cs1.ToStackItem()
|
||||||
|
require.NoError(t, err)
|
||||||
|
managementInvoker.Invoke(t, si, "deploy", nefBytes, manifestBytes)
|
||||||
|
helperInvoker := c.Executor.CommitteeInvoker(cs1.Hash)
|
||||||
|
|
||||||
|
t.Run("unknown contract", func(t *testing.T) {
|
||||||
|
managementInvoker.InvokeFail(t, "contract doesn't exist", "update", nefBytes, manifestBytes)
|
||||||
|
})
|
||||||
|
t.Run("zero-length NEF", func(t *testing.T) {
|
||||||
|
helperInvoker.InvokeFail(t, "invalid NEF file: empty", "update", []byte{}, manifestBytes)
|
||||||
|
})
|
||||||
|
t.Run("zero-length manifest", func(t *testing.T) {
|
||||||
|
helperInvoker.InvokeFail(t, "invalid manifest: empty", "update", nefBytes, []byte{})
|
||||||
|
})
|
||||||
|
t.Run("no real params", func(t *testing.T) {
|
||||||
|
helperInvoker.InvokeFail(t, "both NEF and manifest are nil", "update", nil, nil)
|
||||||
|
})
|
||||||
|
t.Run("invalid manifest", func(t *testing.T) {
|
||||||
|
pkey, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var badManifest = cs1.Manifest
|
||||||
|
badManifest.Groups = []manifest.Group{{PublicKey: pkey.PublicKey(), Signature: make([]byte, 64)}}
|
||||||
|
manifB, err := json.Marshal(badManifest)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
helperInvoker.InvokeFail(t, "invalid manifest: incorrect group signature", "update", nefBytes, manifB)
|
||||||
|
})
|
||||||
|
t.Run("manifest and script mismatch", func(t *testing.T) {
|
||||||
|
nf, err := nef.FileFromBytes(nefBytes) // Make a full copy.
|
||||||
|
require.NoError(t, err)
|
||||||
|
nf.Script = append(nf.Script, byte(opcode.RET))
|
||||||
|
copy(nf.Script[1:], nf.Script) // Now all method offsets are wrong.
|
||||||
|
nf.Script[0] = byte(opcode.RET) // Even though the script is correct.
|
||||||
|
nf.CalculateChecksum()
|
||||||
|
nefnew, err := nf.Bytes()
|
||||||
|
require.NoError(t, err)
|
||||||
|
helperInvoker.InvokeFail(t, "invalid NEF file: checksum verification failure", "update", nefnew, manifestBytes)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("change name", func(t *testing.T) {
|
||||||
|
var badManifest = cs1.Manifest
|
||||||
|
badManifest.Name += "tail"
|
||||||
|
manifB, err := json.Marshal(badManifest)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
helperInvoker.InvokeFail(t, "contract name can't be changed", "update", nefBytes, manifB)
|
||||||
|
})
|
||||||
|
|
||||||
|
cs1.NEF.Script = append(cs1.NEF.Script, byte(opcode.RET))
|
||||||
|
cs1.NEF.Checksum = cs1.NEF.CalculateChecksum()
|
||||||
|
nefBytes, err = cs1.NEF.Bytes()
|
||||||
|
require.NoError(t, err)
|
||||||
|
cs1.UpdateCounter++
|
||||||
|
si, err = cs1.ToStackItem()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
t.Run("update script, positive", func(t *testing.T) {
|
||||||
|
tx1 := helperInvoker.PrepareInvoke(t, "update", nefBytes, nil)
|
||||||
|
tx2 := managementInvoker.PrepareInvoke(t, "getContract", cs1.Hash.BytesBE())
|
||||||
|
managementInvoker.AddNewBlock(t, tx1, tx2)
|
||||||
|
managementInvoker.CheckHalt(t, tx1.Hash(), stackitem.Null{})
|
||||||
|
managementInvoker.CheckHalt(t, tx2.Hash(), si)
|
||||||
|
managementInvoker.CheckTxNotificationEvent(t, tx1.Hash(), 0, state.NotificationEvent{
|
||||||
|
ScriptHash: c.NativeHash(t, nativenames.Management),
|
||||||
|
Name: "Update",
|
||||||
|
Item: stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray(cs1.Hash.BytesBE())}),
|
||||||
|
})
|
||||||
|
t.Run("_deploy called", func(t *testing.T) {
|
||||||
|
helperInvoker := c.Executor.CommitteeInvoker(cs1.Hash)
|
||||||
|
expected := stackitem.NewArray([]stackitem.Item{stackitem.Make("update"), stackitem.Null{}})
|
||||||
|
expectedBytes, err := stackitem.Serialize(expected)
|
||||||
|
require.NoError(t, err)
|
||||||
|
helperInvoker.Invoke(t, stackitem.NewByteArray(expectedBytes), "getValue")
|
||||||
|
})
|
||||||
|
t.Run("check contract", func(t *testing.T) {
|
||||||
|
managementInvoker.Invoke(t, si, "getContract", cs1.Hash.BytesBE())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
cs1.Manifest.Extra = []byte(`"update me"`)
|
||||||
|
manifestBytes, err = json.Marshal(cs1.Manifest)
|
||||||
|
require.NoError(t, err)
|
||||||
|
cs1.UpdateCounter++
|
||||||
|
si, err = cs1.ToStackItem()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
t.Run("update manifest, positive", func(t *testing.T) {
|
||||||
|
updHash := helperInvoker.Invoke(t, stackitem.Null{}, "update", nil, manifestBytes)
|
||||||
|
helperInvoker.CheckTxNotificationEvent(t, updHash, 0, state.NotificationEvent{
|
||||||
|
ScriptHash: helperInvoker.NativeHash(t, nativenames.Management),
|
||||||
|
Name: "Update",
|
||||||
|
Item: stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray(cs1.Hash.BytesBE())}),
|
||||||
|
})
|
||||||
|
t.Run("check contract", func(t *testing.T) {
|
||||||
|
managementInvoker.Invoke(t, si, "getContract", cs1.Hash.BytesBE())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
cs1.NEF.Script = append(cs1.NEF.Script, byte(opcode.ABORT))
|
||||||
|
cs1.NEF.Checksum = cs1.NEF.CalculateChecksum()
|
||||||
|
nefBytes, err = cs1.NEF.Bytes()
|
||||||
|
require.NoError(t, err)
|
||||||
|
cs1.Manifest.Extra = []byte(`"update me once more"`)
|
||||||
|
manifestBytes, err = json.Marshal(cs1.Manifest)
|
||||||
|
require.NoError(t, err)
|
||||||
|
cs1.UpdateCounter++
|
||||||
|
si, err = cs1.ToStackItem()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
t.Run("update both script and manifest", func(t *testing.T) {
|
||||||
|
updHash := helperInvoker.Invoke(t, stackitem.Null{}, "update", nefBytes, manifestBytes)
|
||||||
|
helperInvoker.CheckTxNotificationEvent(t, updHash, 0, state.NotificationEvent{
|
||||||
|
ScriptHash: helperInvoker.NativeHash(t, nativenames.Management),
|
||||||
|
Name: "Update",
|
||||||
|
Item: stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray(cs1.Hash.BytesBE())}),
|
||||||
|
})
|
||||||
|
t.Run("check contract", func(t *testing.T) {
|
||||||
|
managementInvoker.Invoke(t, si, "getContract", cs1.Hash.BytesBE())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManagement_GetContract(t *testing.T) {
|
||||||
|
c := newManagementClient(t)
|
||||||
|
managementInvoker := c.WithSigners(c.Committee)
|
||||||
|
|
||||||
|
cs1, _ := getTestContractState(t, 1, 2, c.CommitteeHash)
|
||||||
|
manifestBytes, err := json.Marshal(cs1.Manifest)
|
||||||
|
require.NoError(t, err)
|
||||||
|
nefBytes, err := cs1.NEF.Bytes()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
si, err := cs1.ToStackItem()
|
||||||
|
require.NoError(t, err)
|
||||||
|
managementInvoker.Invoke(t, si, "deploy", nefBytes, manifestBytes)
|
||||||
|
|
||||||
|
t.Run("bad parameter type", func(t *testing.T) {
|
||||||
|
managementInvoker.InvokeFail(t, "invalid conversion: Array/ByteString", "getContract", []interface{}{int64(1)})
|
||||||
|
})
|
||||||
|
t.Run("not a hash", func(t *testing.T) {
|
||||||
|
managementInvoker.InvokeFail(t, "expected byte size of 20 got 3", "getContract", []byte{1, 2, 3})
|
||||||
|
})
|
||||||
|
t.Run("positive", func(t *testing.T) {
|
||||||
|
managementInvoker.Invoke(t, si, "getContract", cs1.Hash.BytesBE())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManagement_ContractDestroy(t *testing.T) {
|
||||||
|
c := newManagementClient(t)
|
||||||
|
managementInvoker := c.WithSigners(c.Committee)
|
||||||
|
|
||||||
|
cs1, _ := getTestContractState(t, 1, 2, c.CommitteeHash)
|
||||||
|
// Allow calling management contract.
|
||||||
|
cs1.Manifest.Permissions = []manifest.Permission{*manifest.NewPermission(manifest.PermissionWildcard)}
|
||||||
|
manifestBytes, err := json.Marshal(cs1.Manifest)
|
||||||
|
require.NoError(t, err)
|
||||||
|
nefBytes, err := cs1.NEF.Bytes()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
si, err := cs1.ToStackItem()
|
||||||
|
require.NoError(t, err)
|
||||||
|
managementInvoker.Invoke(t, si, "deploy", nefBytes, manifestBytes)
|
||||||
|
helperInvoker := c.Executor.CommitteeInvoker(cs1.Hash)
|
||||||
|
|
||||||
|
t.Run("no contract", func(t *testing.T) {
|
||||||
|
managementInvoker.InvokeFail(t, "key not found", "destroy")
|
||||||
|
})
|
||||||
|
t.Run("positive", func(t *testing.T) {
|
||||||
|
dstrHash := helperInvoker.Invoke(t, stackitem.Null{}, "destroy")
|
||||||
|
helperInvoker.CheckTxNotificationEvent(t, dstrHash, 0, state.NotificationEvent{
|
||||||
|
ScriptHash: helperInvoker.NativeHash(t, nativenames.Management),
|
||||||
|
Name: "Destroy",
|
||||||
|
Item: stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray(cs1.Hash.BytesBE())}),
|
||||||
|
})
|
||||||
|
t.Run("check contract", func(t *testing.T) {
|
||||||
|
managementInvoker.Invoke(t, stackitem.Null{}, "getContract", cs1.Hash.BytesBE())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
468
pkg/core/native/native_test/neo_test.go
Normal file
468
pkg/core/native/native_test/neo_test.go
Normal file
|
@ -0,0 +1,468 @@
|
||||||
|
package native_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"math"
|
||||||
|
"math/big"
|
||||||
|
"sort"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/internal/random"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||||
|
"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/neotest"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newNeoCommitteeClient(t *testing.T, expectedGASBalance int) *neotest.ContractInvoker {
|
||||||
|
bc, validators, committee := chain.NewMulti(t)
|
||||||
|
e := neotest.NewExecutor(t, bc, validators, committee)
|
||||||
|
|
||||||
|
if expectedGASBalance > 0 {
|
||||||
|
e.ValidatorInvoker(e.NativeHash(t, nativenames.Gas)).Invoke(t, true, "transfer", e.Validator.ScriptHash(), e.CommitteeHash, 100_0000_0000, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.CommitteeInvoker(e.NativeHash(t, nativenames.Neo))
|
||||||
|
}
|
||||||
|
|
||||||
|
func newNeoValidatorsClient(t *testing.T) *neotest.ContractInvoker {
|
||||||
|
c := newNeoCommitteeClient(t, 100_0000_0000)
|
||||||
|
return c.ValidatorInvoker(c.NativeHash(t, nativenames.Neo))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNEO_GasPerBlock(t *testing.T) {
|
||||||
|
testGetSet(t, newNeoCommitteeClient(t, 100_0000_0000), "GasPerBlock", 5*native.GASFactor, 0, 10*native.GASFactor)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNEO_RegisterPrice(t *testing.T) {
|
||||||
|
testGetSet(t, newNeoCommitteeClient(t, 100_0000_0000), "RegisterPrice", native.DefaultRegisterPrice, 1, math.MaxInt64)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNEO_Vote(t *testing.T) {
|
||||||
|
neoCommitteeInvoker := newNeoCommitteeClient(t, 100_0000_0000)
|
||||||
|
neoValidatorsInvoker := neoCommitteeInvoker.WithSigners(neoCommitteeInvoker.Validator)
|
||||||
|
e := neoCommitteeInvoker.Executor
|
||||||
|
|
||||||
|
committeeSize := len(neoValidatorsInvoker.Chain.GetConfig().StandbyCommittee)
|
||||||
|
validatorsCount := neoCommitteeInvoker.Chain.GetConfig().ValidatorsCount
|
||||||
|
freq := validatorsCount + committeeSize
|
||||||
|
advanceChain := func(t *testing.T) {
|
||||||
|
for i := 0; i < freq; i++ {
|
||||||
|
neoCommitteeInvoker.AddNewBlock(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
standBySorted := e.Chain.GetStandByValidators()
|
||||||
|
sort.Sort(standBySorted)
|
||||||
|
pubs, err := e.Chain.GetValidators()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, standBySorted, keys.PublicKeys(pubs))
|
||||||
|
|
||||||
|
// voters vote for candidates. The aim of this test is to check that voting
|
||||||
|
// reward is proportional to the NEO balance.
|
||||||
|
voters := make([]neotest.Signer, committeeSize)
|
||||||
|
// referenceAccounts perform the same actions as voters except voting, i.e. we
|
||||||
|
// will transfer the same amount of NEO to referenceAccounts and see how much
|
||||||
|
// GAS they receive for NEO ownership. We need these values to be able to define
|
||||||
|
// how much GAS voters receive for NEO ownership.
|
||||||
|
referenceAccounts := make([]neotest.Signer, committeeSize)
|
||||||
|
candidates := make([]neotest.Signer, committeeSize)
|
||||||
|
for i := 0; i < committeeSize; i++ {
|
||||||
|
voters[i] = e.NewAccount(t, 10_0000_0000)
|
||||||
|
referenceAccounts[i] = e.NewAccount(t, 10_0000_0000)
|
||||||
|
candidates[i] = e.NewAccount(t, 2000_0000_0000) // enough for one registration
|
||||||
|
}
|
||||||
|
txes := make([]*transaction.Transaction, 0, committeeSize*4-2)
|
||||||
|
for i := 0; i < committeeSize; i++ {
|
||||||
|
transferTx := neoValidatorsInvoker.PrepareInvoke(t, "transfer", e.Validator.ScriptHash(), voters[i].(neotest.SingleSigner).Account().PrivateKey().GetScriptHash(), int64(committeeSize-i)*1000000, nil)
|
||||||
|
txes = append(txes, transferTx)
|
||||||
|
transferTx = neoValidatorsInvoker.PrepareInvoke(t, "transfer", e.Validator.ScriptHash(), referenceAccounts[i].(neotest.SingleSigner).Account().PrivateKey().GetScriptHash(), int64(committeeSize-i)*1000000, nil)
|
||||||
|
txes = append(txes, transferTx)
|
||||||
|
if i > 0 {
|
||||||
|
registerTx := neoValidatorsInvoker.WithSigners(candidates[i]).PrepareInvoke(t, "registerCandidate", candidates[i].(neotest.SingleSigner).Account().PrivateKey().PublicKey().Bytes())
|
||||||
|
txes = append(txes, registerTx)
|
||||||
|
voteTx := neoValidatorsInvoker.WithSigners(voters[i]).PrepareInvoke(t, "vote", voters[i].(neotest.SingleSigner).Account().PrivateKey().GetScriptHash(), candidates[i].(neotest.SingleSigner).Account().PrivateKey().PublicKey().Bytes())
|
||||||
|
txes = append(txes, voteTx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
neoValidatorsInvoker.AddNewBlock(t, txes...)
|
||||||
|
for _, tx := range txes {
|
||||||
|
e.CheckHalt(t, tx.Hash(), stackitem.Make(true)) // luckily, both `transfer`, `registerCandidate` and `vote` return boolean values
|
||||||
|
}
|
||||||
|
|
||||||
|
// We still haven't voted enough validators in.
|
||||||
|
pubs, err = e.Chain.GetValidators()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, standBySorted, keys.PublicKeys(pubs))
|
||||||
|
|
||||||
|
advanceChain(t)
|
||||||
|
pubs, err = e.Chain.GetNextBlockValidators()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.EqualValues(t, standBySorted, keys.PublicKeys(pubs))
|
||||||
|
|
||||||
|
// Register and give some value to the last validator.
|
||||||
|
txes = txes[:0]
|
||||||
|
registerTx := neoValidatorsInvoker.WithSigners(candidates[0]).PrepareInvoke(t, "registerCandidate", candidates[0].(neotest.SingleSigner).Account().PrivateKey().PublicKey().Bytes())
|
||||||
|
txes = append(txes, registerTx)
|
||||||
|
voteTx := neoValidatorsInvoker.WithSigners(voters[0]).PrepareInvoke(t, "vote", voters[0].(neotest.SingleSigner).Account().PrivateKey().GetScriptHash(), candidates[0].(neotest.SingleSigner).Account().PrivateKey().PublicKey().Bytes())
|
||||||
|
txes = append(txes, voteTx)
|
||||||
|
neoValidatorsInvoker.AddNewBlock(t, txes...)
|
||||||
|
for _, tx := range txes {
|
||||||
|
e.CheckHalt(t, tx.Hash(), stackitem.Make(true)) // luckily, both `transfer`, `registerCandidate` and `vote` return boolean values
|
||||||
|
}
|
||||||
|
|
||||||
|
advanceChain(t)
|
||||||
|
pubs, err = neoCommitteeInvoker.Chain.GetNextBlockValidators()
|
||||||
|
require.NoError(t, err)
|
||||||
|
sortedCandidates := make(keys.PublicKeys, validatorsCount)
|
||||||
|
for i := range candidates[:validatorsCount] {
|
||||||
|
sortedCandidates[i] = candidates[i].(neotest.SingleSigner).Account().PrivateKey().PublicKey()
|
||||||
|
}
|
||||||
|
sort.Sort(sortedCandidates)
|
||||||
|
require.EqualValues(t, sortedCandidates, keys.PublicKeys(pubs))
|
||||||
|
|
||||||
|
pubs, err = neoCommitteeInvoker.Chain.GetNextBlockValidators()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.EqualValues(t, sortedCandidates, pubs)
|
||||||
|
|
||||||
|
t.Run("check voter rewards", func(t *testing.T) {
|
||||||
|
gasBalance := make([]*big.Int, len(voters))
|
||||||
|
referenceGASBalance := make([]*big.Int, len(referenceAccounts))
|
||||||
|
neoBalance := make([]*big.Int, len(voters))
|
||||||
|
txes = make([]*transaction.Transaction, 0, len(voters))
|
||||||
|
var refTxFee int64
|
||||||
|
for i := range voters {
|
||||||
|
h := voters[i].ScriptHash()
|
||||||
|
refH := referenceAccounts[i].ScriptHash()
|
||||||
|
gasBalance[i] = e.Chain.GetUtilityTokenBalance(h)
|
||||||
|
neoBalance[i], _ = e.Chain.GetGoverningTokenBalance(h)
|
||||||
|
referenceGASBalance[i] = e.Chain.GetUtilityTokenBalance(refH)
|
||||||
|
|
||||||
|
tx := neoCommitteeInvoker.WithSigners(voters[i]).PrepareInvoke(t, "transfer", h.BytesBE(), h.BytesBE(), int64(1), nil)
|
||||||
|
txes = append(txes, tx)
|
||||||
|
tx = neoCommitteeInvoker.WithSigners(referenceAccounts[i]).PrepareInvoke(t, "transfer", refH.BytesBE(), refH.BytesBE(), int64(1), nil)
|
||||||
|
txes = append(txes, tx)
|
||||||
|
refTxFee = tx.SystemFee + tx.NetworkFee
|
||||||
|
}
|
||||||
|
neoCommitteeInvoker.AddNewBlock(t, txes...)
|
||||||
|
for _, tx := range txes {
|
||||||
|
e.CheckHalt(t, tx.Hash(), stackitem.Make(true))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define reference reward for NEO holding for each voter account.
|
||||||
|
for i := range referenceGASBalance {
|
||||||
|
newBalance := e.Chain.GetUtilityTokenBalance(referenceAccounts[i].ScriptHash())
|
||||||
|
referenceGASBalance[i].Sub(newBalance, referenceGASBalance[i])
|
||||||
|
referenceGASBalance[i].Add(referenceGASBalance[i], big.NewInt(refTxFee))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GAS increase consists of 2 parts: NEO holding + voting for committee nodes.
|
||||||
|
// Here we check that 2-nd part exists and is proportional to the amount of NEO given.
|
||||||
|
for i := range voters {
|
||||||
|
newGAS := e.Chain.GetUtilityTokenBalance(voters[i].ScriptHash())
|
||||||
|
newGAS.Sub(newGAS, gasBalance[i])
|
||||||
|
gasForHold := referenceGASBalance[i]
|
||||||
|
newGAS.Sub(newGAS, gasForHold)
|
||||||
|
require.True(t, newGAS.Sign() > 0)
|
||||||
|
gasBalance[i] = newGAS
|
||||||
|
}
|
||||||
|
// First account voted later than the others.
|
||||||
|
require.Equal(t, -1, gasBalance[0].Cmp(gasBalance[1]))
|
||||||
|
for i := 2; i < validatorsCount; i++ {
|
||||||
|
require.Equal(t, 0, gasBalance[i].Cmp(gasBalance[1]))
|
||||||
|
}
|
||||||
|
require.Equal(t, 1, gasBalance[1].Cmp(gasBalance[validatorsCount]))
|
||||||
|
for i := validatorsCount; i < committeeSize; i++ {
|
||||||
|
require.Equal(t, 0, gasBalance[i].Cmp(gasBalance[validatorsCount]))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
neoCommitteeInvoker.WithSigners(candidates[0]).Invoke(t, true, "unregisterCandidate", candidates[0].(neotest.SingleSigner).Account().PrivateKey().PublicKey().Bytes())
|
||||||
|
neoCommitteeInvoker.WithSigners(voters[0]).Invoke(t, false, "vote", voters[0].(neotest.SingleSigner).Account().PrivateKey().GetScriptHash(), candidates[0].(neotest.SingleSigner).Account().PrivateKey().PublicKey().Bytes())
|
||||||
|
|
||||||
|
advanceChain(t)
|
||||||
|
|
||||||
|
pubs, err = e.Chain.GetValidators()
|
||||||
|
require.NoError(t, err)
|
||||||
|
for i := range pubs {
|
||||||
|
require.NotEqual(t, candidates[0], pubs[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestNEO_RecursiveDistribution is a test for https://github.com/nspcc-dev/neo-go/pull/2181.
|
||||||
|
func TestNEO_RecursiveGASMint(t *testing.T) {
|
||||||
|
neoCommitteeInvoker := newNeoCommitteeClient(t, 100_0000_0000)
|
||||||
|
neoValidatorInvoker := neoCommitteeInvoker.WithSigners(neoCommitteeInvoker.Validator)
|
||||||
|
e := neoCommitteeInvoker.Executor
|
||||||
|
gasValidatorInvoker := e.ValidatorInvoker(e.NativeHash(t, nativenames.Gas))
|
||||||
|
|
||||||
|
c := neotest.CompileFile(t, e.Validator.ScriptHash(), "../../../rpc/server/testdata/test_contract.go", "../../../rpc/server/testdata/test_contract.yml")
|
||||||
|
e.DeployContract(t, c, nil)
|
||||||
|
|
||||||
|
gasValidatorInvoker.Invoke(t, true, "transfer", e.Validator.ScriptHash(), c.Hash, int64(2_0000_0000), nil)
|
||||||
|
|
||||||
|
// Transfer 10 NEO to test contract, the contract should earn some GAS by owning this NEO.
|
||||||
|
neoValidatorInvoker.Invoke(t, true, "transfer", e.Validator.ScriptHash(), c.Hash, int64(10), nil)
|
||||||
|
|
||||||
|
// Add blocks to be able to trigger NEO transfer from contract address to owner
|
||||||
|
// address inside onNEP17Payment (the contract starts NEO transfers from chain height = 100).
|
||||||
|
for i := e.Chain.BlockHeight(); i < 100; i++ {
|
||||||
|
e.AddNewBlock(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transfer 1 more NEO to the contract. Transfer will trigger onNEP17Payment. OnNEP17Payment will
|
||||||
|
// trigger transfer of 11 NEO to the contract owner (based on the contract code). 11 NEO Transfer will
|
||||||
|
// trigger GAS distribution. GAS transfer will trigger OnNEP17Payment one more time. The recursion
|
||||||
|
// shouldn't occur here, because contract's balance LastUpdated height has already been updated in
|
||||||
|
// this block.
|
||||||
|
neoValidatorInvoker.Invoke(t, true, "transfer", e.Validator.ScriptHash(), c.Hash, int64(1), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNEO_GetAccountState(t *testing.T) {
|
||||||
|
neoValidatorInvoker := newNeoValidatorsClient(t)
|
||||||
|
e := neoValidatorInvoker.Executor
|
||||||
|
|
||||||
|
t.Run("empty", func(t *testing.T) {
|
||||||
|
neoValidatorInvoker.Invoke(t, stackitem.Null{}, "getAccountState", util.Uint160{})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with funds", func(t *testing.T) {
|
||||||
|
amount := int64(1)
|
||||||
|
acc := e.NewAccount(t)
|
||||||
|
neoValidatorInvoker.Invoke(t, true, "transfer", e.Validator.ScriptHash(), acc.ScriptHash(), amount, nil)
|
||||||
|
lub := e.Chain.BlockHeight()
|
||||||
|
neoValidatorInvoker.Invoke(t, stackitem.NewStruct([]stackitem.Item{
|
||||||
|
stackitem.Make(amount),
|
||||||
|
stackitem.Make(lub),
|
||||||
|
stackitem.Null{},
|
||||||
|
}), "getAccountState", acc.ScriptHash())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNEO_CommitteeBountyOnPersist(t *testing.T) {
|
||||||
|
neoCommitteeInvoker := newNeoCommitteeClient(t, 0)
|
||||||
|
e := neoCommitteeInvoker.Executor
|
||||||
|
|
||||||
|
hs := e.Chain.GetStandByCommittee()
|
||||||
|
committeeSize := len(hs)
|
||||||
|
|
||||||
|
const singleBounty = 50000000
|
||||||
|
bs := map[int]int64{0: singleBounty}
|
||||||
|
checkBalances := func() {
|
||||||
|
for i := 0; i < committeeSize; i++ {
|
||||||
|
require.EqualValues(t, bs[i], e.Chain.GetUtilityTokenBalance(hs[i].GetScriptHash()).Int64(), i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := 0; i < committeeSize*2; i++ {
|
||||||
|
e.AddNewBlock(t)
|
||||||
|
bs[(i+1)%committeeSize] += singleBounty
|
||||||
|
checkBalances()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNEO_TransferOnPayment(t *testing.T) {
|
||||||
|
neoValidatorsInvoker := newNeoValidatorsClient(t)
|
||||||
|
e := neoValidatorsInvoker.Executor
|
||||||
|
managementValidatorsInvoker := e.ValidatorInvoker(e.NativeHash(t, nativenames.Management))
|
||||||
|
|
||||||
|
cs, _ := getTestContractState(t, 1, 2, e.CommitteeHash)
|
||||||
|
cs.Hash = state.CreateContractHash(e.Validator.ScriptHash(), cs.NEF.Checksum, cs.Manifest.Name) // set proper hash
|
||||||
|
manifB, err := json.Marshal(cs.Manifest)
|
||||||
|
require.NoError(t, err)
|
||||||
|
nefB, err := cs.NEF.Bytes()
|
||||||
|
require.NoError(t, err)
|
||||||
|
si, err := cs.ToStackItem()
|
||||||
|
require.NoError(t, err)
|
||||||
|
managementValidatorsInvoker.Invoke(t, si, "deploy", nefB, manifB)
|
||||||
|
|
||||||
|
const amount int64 = 2
|
||||||
|
|
||||||
|
h := neoValidatorsInvoker.Invoke(t, true, "transfer", e.Validator.ScriptHash(), cs.Hash, amount, nil)
|
||||||
|
aer := e.GetTxExecResult(t, h)
|
||||||
|
require.Equal(t, 3, len(aer.Events)) // transfer + GAS claim for sender + onPayment
|
||||||
|
e.CheckTxNotificationEvent(t, h, 2, state.NotificationEvent{
|
||||||
|
ScriptHash: cs.Hash,
|
||||||
|
Name: "LastPayment",
|
||||||
|
Item: stackitem.NewArray([]stackitem.Item{
|
||||||
|
stackitem.NewByteArray(neoValidatorsInvoker.Hash.BytesBE()),
|
||||||
|
stackitem.NewByteArray(e.Validator.ScriptHash().BytesBE()),
|
||||||
|
stackitem.NewBigInteger(big.NewInt(amount)),
|
||||||
|
stackitem.Null{},
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
h = neoValidatorsInvoker.Invoke(t, true, "transfer", e.Validator.ScriptHash(), cs.Hash, amount, nil)
|
||||||
|
aer = e.GetTxExecResult(t, h)
|
||||||
|
require.Equal(t, 5, len(aer.Events)) // Now we must also have GAS claim for contract and corresponding `onPayment`.
|
||||||
|
e.CheckTxNotificationEvent(t, h, 2, state.NotificationEvent{ // onPayment for GAS claim
|
||||||
|
ScriptHash: cs.Hash,
|
||||||
|
Name: "LastPayment",
|
||||||
|
Item: stackitem.NewArray([]stackitem.Item{
|
||||||
|
stackitem.NewByteArray(e.NativeHash(t, nativenames.Gas).BytesBE()),
|
||||||
|
stackitem.Null{},
|
||||||
|
stackitem.NewBigInteger(big.NewInt(1)),
|
||||||
|
stackitem.Null{},
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
e.CheckTxNotificationEvent(t, h, 4, state.NotificationEvent{ // onPayment for NEO transfer
|
||||||
|
ScriptHash: cs.Hash,
|
||||||
|
Name: "LastPayment",
|
||||||
|
Item: stackitem.NewArray([]stackitem.Item{
|
||||||
|
stackitem.NewByteArray(e.NativeHash(t, nativenames.Neo).BytesBE()),
|
||||||
|
stackitem.NewByteArray(e.Validator.ScriptHash().BytesBE()),
|
||||||
|
stackitem.NewBigInteger(big.NewInt(amount)),
|
||||||
|
stackitem.Null{},
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNEO_Roundtrip(t *testing.T) {
|
||||||
|
neoValidatorsInvoker := newNeoValidatorsClient(t)
|
||||||
|
e := neoValidatorsInvoker.Executor
|
||||||
|
validatorH := neoValidatorsInvoker.Validator.ScriptHash()
|
||||||
|
|
||||||
|
initialBalance, initialHeight := e.Chain.GetGoverningTokenBalance(validatorH)
|
||||||
|
require.NotNil(t, initialBalance)
|
||||||
|
|
||||||
|
t.Run("bad: amount > initial balance", func(t *testing.T) {
|
||||||
|
h := neoValidatorsInvoker.Invoke(t, false, "transfer", validatorH, validatorH, initialBalance.Int64()+1, nil)
|
||||||
|
aer, err := e.Chain.GetAppExecResults(h, trigger.Application)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 0, len(aer[0].Events)) // failed transfer => no events
|
||||||
|
// check balance and height were not changed
|
||||||
|
updatedBalance, updatedHeight := e.Chain.GetGoverningTokenBalance(validatorH)
|
||||||
|
require.Equal(t, initialBalance, updatedBalance)
|
||||||
|
require.Equal(t, initialHeight, updatedHeight)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("good: amount == initial balance", func(t *testing.T) {
|
||||||
|
h := neoValidatorsInvoker.Invoke(t, true, "transfer", validatorH, validatorH, initialBalance.Int64(), nil)
|
||||||
|
aer, err := e.Chain.GetAppExecResults(h, trigger.Application)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 2, len(aer[0].Events)) // roundtrip + GAS claim
|
||||||
|
// check balance wasn't changed and height was updated
|
||||||
|
updatedBalance, updatedHeight := e.Chain.GetGoverningTokenBalance(validatorH)
|
||||||
|
require.Equal(t, initialBalance, updatedBalance)
|
||||||
|
require.Equal(t, e.Chain.BlockHeight(), updatedHeight)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNEO_TransferZeroWithZeroBalance(t *testing.T) {
|
||||||
|
neoValidatorsInvoker := newNeoValidatorsClient(t)
|
||||||
|
e := neoValidatorsInvoker.Executor
|
||||||
|
|
||||||
|
check := func(t *testing.T, roundtrip bool) {
|
||||||
|
acc := neoValidatorsInvoker.WithSigners(e.NewAccount(t))
|
||||||
|
accH := acc.Signers[0].ScriptHash()
|
||||||
|
to := accH
|
||||||
|
if !roundtrip {
|
||||||
|
to = random.Uint160()
|
||||||
|
}
|
||||||
|
h := acc.Invoke(t, true, "transfer", accH, to, int64(0), nil)
|
||||||
|
aer, err := e.Chain.GetAppExecResults(h, trigger.Application)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 1, len(aer[0].Events)) // roundtrip/transfer only, no GAS claim
|
||||||
|
require.Equal(t, stackitem.NewBigInteger(big.NewInt(0)), aer[0].Events[0].Item.Value().([]stackitem.Item)[2]) // amount is 0
|
||||||
|
// check balance wasn't changed and height was updated
|
||||||
|
updatedBalance, updatedHeight := e.Chain.GetGoverningTokenBalance(accH)
|
||||||
|
require.Equal(t, int64(0), updatedBalance.Int64())
|
||||||
|
require.Equal(t, uint32(0), updatedHeight)
|
||||||
|
}
|
||||||
|
t.Run("roundtrip: amount == initial balance == 0", func(t *testing.T) {
|
||||||
|
check(t, true)
|
||||||
|
})
|
||||||
|
t.Run("non-roundtrip: amount == initial balance == 0", func(t *testing.T) {
|
||||||
|
check(t, false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNEO_TransferZeroWithNonZeroBalance(t *testing.T) {
|
||||||
|
neoValidatorsInvoker := newNeoValidatorsClient(t)
|
||||||
|
e := neoValidatorsInvoker.Executor
|
||||||
|
|
||||||
|
check := func(t *testing.T, roundtrip bool) {
|
||||||
|
acc := e.NewAccount(t)
|
||||||
|
neoValidatorsInvoker.Invoke(t, true, "transfer", neoValidatorsInvoker.Validator.ScriptHash(), acc.ScriptHash(), int64(100), nil)
|
||||||
|
neoAccInvoker := neoValidatorsInvoker.WithSigners(acc)
|
||||||
|
initialBalance, _ := e.Chain.GetGoverningTokenBalance(acc.ScriptHash())
|
||||||
|
require.True(t, initialBalance.Sign() > 0)
|
||||||
|
to := acc.ScriptHash()
|
||||||
|
if !roundtrip {
|
||||||
|
to = random.Uint160()
|
||||||
|
}
|
||||||
|
h := neoAccInvoker.Invoke(t, true, "transfer", acc.ScriptHash(), to, int64(0), nil)
|
||||||
|
|
||||||
|
aer, err := e.Chain.GetAppExecResults(h, trigger.Application)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 2, len(aer[0].Events)) // roundtrip + GAS claim
|
||||||
|
require.Equal(t, stackitem.NewBigInteger(big.NewInt(0)), aer[0].Events[1].Item.Value().([]stackitem.Item)[2]) // amount is 0
|
||||||
|
// check balance wasn't changed and height was updated
|
||||||
|
updatedBalance, updatedHeight := e.Chain.GetGoverningTokenBalance(acc.ScriptHash())
|
||||||
|
require.Equal(t, initialBalance, updatedBalance)
|
||||||
|
require.Equal(t, e.Chain.BlockHeight(), updatedHeight)
|
||||||
|
}
|
||||||
|
t.Run("roundtrip", func(t *testing.T) {
|
||||||
|
check(t, true)
|
||||||
|
})
|
||||||
|
t.Run("non-roundtrip", func(t *testing.T) {
|
||||||
|
check(t, false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNEO_CalculateBonus(t *testing.T) {
|
||||||
|
neoCommitteeInvoker := newNeoCommitteeClient(t, 10_0000_0000)
|
||||||
|
e := neoCommitteeInvoker.Executor
|
||||||
|
neoValidatorsInvoker := neoCommitteeInvoker.WithSigners(e.Validator)
|
||||||
|
|
||||||
|
acc := neoValidatorsInvoker.WithSigners(e.NewAccount(t))
|
||||||
|
accH := acc.Signers[0].ScriptHash()
|
||||||
|
rewardDistance := 10
|
||||||
|
|
||||||
|
t.Run("Zero", func(t *testing.T) {
|
||||||
|
initialGASBalance := e.Chain.GetUtilityTokenBalance(accH)
|
||||||
|
for i := 0; i < rewardDistance; i++ {
|
||||||
|
e.AddNewBlock(t)
|
||||||
|
}
|
||||||
|
// Claim GAS, but there's no NEO on the account, so no GAS should be earned.
|
||||||
|
h := acc.Invoke(t, true, "transfer", accH, accH, 0, nil)
|
||||||
|
claimTx, _ := e.GetTransaction(t, h)
|
||||||
|
|
||||||
|
e.CheckGASBalance(t, accH, big.NewInt(initialGASBalance.Int64()-claimTx.SystemFee-claimTx.NetworkFee))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Many blocks", func(t *testing.T) {
|
||||||
|
amount := 100
|
||||||
|
defaultGASParBlock := 5
|
||||||
|
newGASPerBlock := 1
|
||||||
|
|
||||||
|
initialGASBalance := e.Chain.GetUtilityTokenBalance(accH)
|
||||||
|
|
||||||
|
// Five blocks of NEO owning with default GasPerBlockValue.
|
||||||
|
neoValidatorsInvoker.Invoke(t, true, "transfer", e.Validator.ScriptHash(), accH, amount, nil)
|
||||||
|
for i := 0; i < rewardDistance/2-2; i++ {
|
||||||
|
e.AddNewBlock(t)
|
||||||
|
}
|
||||||
|
neoCommitteeInvoker.Invoke(t, stackitem.Null{}, "setGasPerBlock", newGASPerBlock*native.GASFactor)
|
||||||
|
|
||||||
|
// Five blocks more with modified GasPerBlock value.
|
||||||
|
for i := 0; i < rewardDistance/2; i++ {
|
||||||
|
e.AddNewBlock(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GAS claim for the last 10 blocks of NEO owning.
|
||||||
|
h := acc.Invoke(t, true, "transfer", accH, accH, amount, nil)
|
||||||
|
claimTx, _ := e.GetTransaction(t, h)
|
||||||
|
|
||||||
|
firstPart := int64(amount*rewardDistance/2*defaultGASParBlock) / int64(rewardDistance)
|
||||||
|
secondPart := int64(amount*rewardDistance/2*newGASPerBlock) / int64(rewardDistance)
|
||||||
|
e.CheckGASBalance(t, accH, big.NewInt(initialGASBalance.Int64()-
|
||||||
|
claimTx.SystemFee-claimTx.NetworkFee + +firstPart + secondPart))
|
||||||
|
})
|
||||||
|
}
|
233
pkg/core/native/native_test/notary_test.go
Normal file
233
pkg/core/native/native_test/notary_test.go
Normal file
|
@ -0,0 +1,233 @@
|
||||||
|
package native_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||||
|
"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/interop/native/roles"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newNotaryClient(t *testing.T) *neotest.ContractInvoker {
|
||||||
|
bc, acc := chain.NewSingleWithCustomConfig(t, func(cfg *config.ProtocolConfiguration) {
|
||||||
|
cfg.P2PSigExtensions = true
|
||||||
|
})
|
||||||
|
e := neotest.NewExecutor(t, bc, acc, acc)
|
||||||
|
|
||||||
|
return e.CommitteeInvoker(e.NativeHash(t, nativenames.Notary))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNotary_MaxNotValidBeforeDelta(t *testing.T) {
|
||||||
|
c := newNotaryClient(t)
|
||||||
|
testGetSet(t, c, "MaxNotValidBeforeDelta", 140, int64(c.Chain.GetConfig().ValidatorsCount), int64(c.Chain.GetConfig().MaxValidUntilBlockIncrement/2))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNotary_Pipeline(t *testing.T) {
|
||||||
|
notaryCommitteeInvoker := newNotaryClient(t)
|
||||||
|
e := notaryCommitteeInvoker.Executor
|
||||||
|
neoCommitteeInvoker := e.CommitteeInvoker(e.NativeHash(t, nativenames.Neo))
|
||||||
|
gasCommitteeInvoker := e.CommitteeInvoker(e.NativeHash(t, nativenames.Gas))
|
||||||
|
|
||||||
|
notaryHash := notaryCommitteeInvoker.NativeHash(t, nativenames.Notary)
|
||||||
|
multisigHash := notaryCommitteeInvoker.Validator.ScriptHash() // matches committee's one for single chain
|
||||||
|
depositLock := 100
|
||||||
|
|
||||||
|
checkBalanceOf := func(t *testing.T, acc util.Uint160, expected int) { // we don't have big numbers in this test, thus may use int
|
||||||
|
notaryCommitteeInvoker.CheckGASBalance(t, acc, big.NewInt(int64(expected)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// check Notary contract has no GAS on the account
|
||||||
|
checkBalanceOf(t, notaryHash, 0)
|
||||||
|
|
||||||
|
// `balanceOf`: check multisig account has no GAS on deposit
|
||||||
|
notaryCommitteeInvoker.Invoke(t, 0, "balanceOf", multisigHash)
|
||||||
|
|
||||||
|
// `expirationOf`: should fail to get deposit which does not exist
|
||||||
|
notaryCommitteeInvoker.Invoke(t, 0, "expirationOf", multisigHash)
|
||||||
|
|
||||||
|
// `lockDepositUntil`: should fail because there's no deposit
|
||||||
|
notaryCommitteeInvoker.Invoke(t, false, "lockDepositUntil", multisigHash, int64(depositLock+1))
|
||||||
|
|
||||||
|
// `onPayment`: bad token
|
||||||
|
neoCommitteeInvoker.InvokeFail(t, "only GAS can be accepted for deposit", "transfer", multisigHash, notaryHash, int64(1), []interface{}{nil, int64(depositLock)})
|
||||||
|
|
||||||
|
// `onPayment`: insufficient first deposit
|
||||||
|
gasCommitteeInvoker.InvokeFail(t, "first deposit can not be less then", "transfer", multisigHash, notaryHash, int64(2*transaction.NotaryServiceFeePerKey-1), []interface{}{nil, int64(depositLock)})
|
||||||
|
|
||||||
|
// `onPayment`: invalid `data` (missing `till` parameter)
|
||||||
|
gasCommitteeInvoker.InvokeFail(t, "`data` parameter should be an array of 2 elements", "transfer", multisigHash, notaryHash, 2*transaction.NotaryServiceFeePerKey, []interface{}{nil})
|
||||||
|
|
||||||
|
// `onPayment`: invalid `data` (outdated `till` parameter)
|
||||||
|
gasCommitteeInvoker.InvokeFail(t, "`till` shouldn't be less then the chain's height", "transfer", multisigHash, notaryHash, 2*transaction.NotaryServiceFeePerKey, []interface{}{nil, int64(0)})
|
||||||
|
|
||||||
|
// `onPayment`: good
|
||||||
|
gasCommitteeInvoker.Invoke(t, true, "transfer", multisigHash, notaryHash, 2*transaction.NotaryServiceFeePerKey, []interface{}{nil, int64(depositLock)})
|
||||||
|
checkBalanceOf(t, notaryHash, 2*transaction.NotaryServiceFeePerKey)
|
||||||
|
|
||||||
|
// `expirationOf`: check `till` was set
|
||||||
|
notaryCommitteeInvoker.Invoke(t, depositLock, "expirationOf", multisigHash)
|
||||||
|
|
||||||
|
// `balanceOf`: check deposited amount for the multisig account
|
||||||
|
notaryCommitteeInvoker.Invoke(t, 2*transaction.NotaryServiceFeePerKey, "balanceOf", multisigHash)
|
||||||
|
|
||||||
|
// `onPayment`: good second deposit and explicit `to` paramenter
|
||||||
|
gasCommitteeInvoker.Invoke(t, true, "transfer", multisigHash, notaryHash, transaction.NotaryServiceFeePerKey, []interface{}{multisigHash, int64(depositLock + 1)})
|
||||||
|
checkBalanceOf(t, notaryHash, 3*transaction.NotaryServiceFeePerKey)
|
||||||
|
|
||||||
|
// `balanceOf`: check deposited amount for the multisig account
|
||||||
|
notaryCommitteeInvoker.Invoke(t, 3*transaction.NotaryServiceFeePerKey, "balanceOf", multisigHash)
|
||||||
|
|
||||||
|
// `expirationOf`: check `till` is updated.
|
||||||
|
notaryCommitteeInvoker.Invoke(t, depositLock+1, "expirationOf", multisigHash)
|
||||||
|
|
||||||
|
// `onPayment`: empty payment, should fail because `till` less then the previous one
|
||||||
|
gasCommitteeInvoker.InvokeFail(t, "`till` shouldn't be less then the previous value", "transfer", multisigHash, notaryHash, int64(0), []interface{}{multisigHash, int64(depositLock)})
|
||||||
|
checkBalanceOf(t, notaryHash, 3*transaction.NotaryServiceFeePerKey)
|
||||||
|
notaryCommitteeInvoker.Invoke(t, depositLock+1, "expirationOf", multisigHash)
|
||||||
|
|
||||||
|
// `onPayment`: empty payment, should fail because `till` less then the chain height
|
||||||
|
gasCommitteeInvoker.InvokeFail(t, "`till` shouldn't be less then the chain's height", "transfer", multisigHash, notaryHash, int64(0), []interface{}{multisigHash, int64(1)})
|
||||||
|
checkBalanceOf(t, notaryHash, 3*transaction.NotaryServiceFeePerKey)
|
||||||
|
notaryCommitteeInvoker.Invoke(t, depositLock+1, "expirationOf", multisigHash)
|
||||||
|
|
||||||
|
// `onPayment`: empty payment, should successfully update `till`
|
||||||
|
gasCommitteeInvoker.Invoke(t, true, "transfer", multisigHash, notaryHash, int64(0), []interface{}{multisigHash, int64(depositLock + 2)})
|
||||||
|
checkBalanceOf(t, notaryHash, 3*transaction.NotaryServiceFeePerKey)
|
||||||
|
notaryCommitteeInvoker.Invoke(t, depositLock+2, "expirationOf", multisigHash)
|
||||||
|
|
||||||
|
// `lockDepositUntil`: bad witness
|
||||||
|
notaryCommitteeInvoker.Invoke(t, false, "lockDepositUntil", util.Uint160{1, 2, 3}, int64(depositLock+3))
|
||||||
|
notaryCommitteeInvoker.Invoke(t, depositLock+2, "expirationOf", multisigHash)
|
||||||
|
|
||||||
|
// `lockDepositUntil`: bad `till` (less then the previous one)
|
||||||
|
notaryCommitteeInvoker.Invoke(t, false, "lockDepositUntil", multisigHash, int64(depositLock+1))
|
||||||
|
notaryCommitteeInvoker.Invoke(t, depositLock+2, "expirationOf", multisigHash)
|
||||||
|
|
||||||
|
// `lockDepositUntil`: bad `till` (less then the chain's height)
|
||||||
|
notaryCommitteeInvoker.Invoke(t, false, "lockDepositUntil", multisigHash, int64(1))
|
||||||
|
notaryCommitteeInvoker.Invoke(t, depositLock+2, "expirationOf", multisigHash)
|
||||||
|
|
||||||
|
// `lockDepositUntil`: good `till`
|
||||||
|
notaryCommitteeInvoker.Invoke(t, true, "lockDepositUntil", multisigHash, int64(depositLock+3))
|
||||||
|
notaryCommitteeInvoker.Invoke(t, depositLock+3, "expirationOf", multisigHash)
|
||||||
|
|
||||||
|
// Create new account for the next test
|
||||||
|
notaryAccInvoker := notaryCommitteeInvoker.WithSigners(e.NewAccount(t))
|
||||||
|
accHash := notaryAccInvoker.Signers[0].ScriptHash()
|
||||||
|
|
||||||
|
// `withdraw`: bad witness
|
||||||
|
notaryAccInvoker.Invoke(t, false, "withdraw", multisigHash, accHash)
|
||||||
|
notaryCommitteeInvoker.Invoke(t, 3*transaction.NotaryServiceFeePerKey, "balanceOf", multisigHash)
|
||||||
|
|
||||||
|
// `withdraw`: locked deposit
|
||||||
|
notaryCommitteeInvoker.Invoke(t, false, "withdraw", multisigHash, multisigHash)
|
||||||
|
notaryCommitteeInvoker.Invoke(t, 3*transaction.NotaryServiceFeePerKey, "balanceOf", multisigHash)
|
||||||
|
|
||||||
|
// `withdraw`: unlock deposit and transfer GAS back to owner
|
||||||
|
e.GenerateNewBlocks(t, depositLock)
|
||||||
|
notaryCommitteeInvoker.Invoke(t, true, "withdraw", multisigHash, accHash)
|
||||||
|
notaryCommitteeInvoker.Invoke(t, 0, "balanceOf", multisigHash)
|
||||||
|
checkBalanceOf(t, notaryHash, 0)
|
||||||
|
|
||||||
|
// `withdraw`: the second time it should fail, because there's no deposit left
|
||||||
|
notaryCommitteeInvoker.Invoke(t, false, "withdraw", multisigHash, accHash)
|
||||||
|
|
||||||
|
// `onPayment`: good first deposit to other account, should set default `till` even if other `till` value is provided
|
||||||
|
gasCommitteeInvoker.Invoke(t, true, "transfer", multisigHash, notaryHash, 2*transaction.NotaryServiceFeePerKey, []interface{}{accHash, int64(math.MaxUint32 - 1)})
|
||||||
|
checkBalanceOf(t, notaryHash, 2*transaction.NotaryServiceFeePerKey)
|
||||||
|
notaryCommitteeInvoker.Invoke(t, 5760+e.Chain.BlockHeight()-1, "expirationOf", accHash)
|
||||||
|
|
||||||
|
// `onPayment`: good second deposit to other account, shouldn't update `till` even if other `till` value is provided
|
||||||
|
gasCommitteeInvoker.Invoke(t, true, "transfer", multisigHash, notaryHash, transaction.NotaryServiceFeePerKey, []interface{}{accHash, int64(math.MaxUint32 - 1)})
|
||||||
|
checkBalanceOf(t, notaryHash, 3*transaction.NotaryServiceFeePerKey)
|
||||||
|
notaryCommitteeInvoker.Invoke(t, 5760+e.Chain.BlockHeight()-3, "expirationOf", accHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNotary_NotaryNodesReward(t *testing.T) {
|
||||||
|
checkReward := func(nKeys int, nNotaryNodes int, spendFullDeposit bool) {
|
||||||
|
notaryCommitteeInvoker := newNotaryClient(t)
|
||||||
|
e := notaryCommitteeInvoker.Executor
|
||||||
|
gasCommitteeInvoker := e.CommitteeInvoker(e.NativeHash(t, nativenames.Gas))
|
||||||
|
designationCommitteeInvoker := e.CommitteeInvoker(e.NativeHash(t, nativenames.Designation))
|
||||||
|
|
||||||
|
notaryHash := notaryCommitteeInvoker.NativeHash(t, nativenames.Notary)
|
||||||
|
multisigHash := notaryCommitteeInvoker.Validator.ScriptHash() // matches committee's one for single chain
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// set Notary nodes and check their balance
|
||||||
|
notaryNodes := make([]*keys.PrivateKey, nNotaryNodes)
|
||||||
|
notaryNodesPublicKeys := make([]interface{}, nNotaryNodes)
|
||||||
|
for i := range notaryNodes {
|
||||||
|
notaryNodes[i], err = keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
notaryNodesPublicKeys[i] = notaryNodes[i].PublicKey().Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
designationCommitteeInvoker.Invoke(t, stackitem.Null{}, "designateAsRole", int(roles.P2PNotary), notaryNodesPublicKeys)
|
||||||
|
for _, notaryNode := range notaryNodes {
|
||||||
|
e.CheckGASBalance(t, notaryNode.GetScriptHash(), big.NewInt(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
// deposit GAS for `signer` with lock until the next block
|
||||||
|
depositAmount := 100_0000 + (2+int64(nKeys))*transaction.NotaryServiceFeePerKey // sysfee + netfee of the next transaction
|
||||||
|
if !spendFullDeposit {
|
||||||
|
depositAmount += 1_0000
|
||||||
|
}
|
||||||
|
gasCommitteeInvoker.Invoke(t, true, "transfer", multisigHash, notaryHash, depositAmount, []interface{}{multisigHash, e.Chain.BlockHeight() + 1})
|
||||||
|
|
||||||
|
// send transaction with Notary contract as a sender
|
||||||
|
tx := transaction.New([]byte{byte(opcode.PUSH1)}, 1_000_000)
|
||||||
|
tx.Nonce = neotest.Nonce()
|
||||||
|
tx.ValidUntilBlock = e.Chain.BlockHeight() + 1
|
||||||
|
tx.Attributes = append(tx.Attributes, transaction.Attribute{Type: transaction.NotaryAssistedT, Value: &transaction.NotaryAssisted{NKeys: uint8(nKeys)}})
|
||||||
|
tx.NetworkFee = (2 + int64(nKeys)) * transaction.NotaryServiceFeePerKey
|
||||||
|
tx.Signers = []transaction.Signer{
|
||||||
|
{
|
||||||
|
Account: notaryHash,
|
||||||
|
Scopes: transaction.None,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Account: multisigHash,
|
||||||
|
Scopes: transaction.None,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
tx.Scripts = []transaction.Witness{
|
||||||
|
{
|
||||||
|
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, notaryNodes[0].SignHashable(uint32(e.Chain.GetConfig().Magic), tx)...),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
InvocationScript: e.Committee.SignHashable(uint32(e.Chain.GetConfig().Magic), tx),
|
||||||
|
VerificationScript: e.Committee.Script(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
e.AddNewBlock(t, tx)
|
||||||
|
|
||||||
|
e.CheckGASBalance(t, notaryHash, big.NewInt(int64(depositAmount-tx.SystemFee-tx.NetworkFee)))
|
||||||
|
for _, notaryNode := range notaryNodes {
|
||||||
|
e.CheckGASBalance(t, notaryNode.GetScriptHash(), big.NewInt(int64(transaction.NotaryServiceFeePerKey*(nKeys+1)/nNotaryNodes)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, spendDeposit := range []bool{true, false} {
|
||||||
|
checkReward(0, 1, spendDeposit)
|
||||||
|
checkReward(0, 2, spendDeposit)
|
||||||
|
checkReward(1, 1, spendDeposit)
|
||||||
|
checkReward(1, 2, spendDeposit)
|
||||||
|
checkReward(1, 3, spendDeposit)
|
||||||
|
checkReward(5, 1, spendDeposit)
|
||||||
|
checkReward(5, 2, spendDeposit)
|
||||||
|
checkReward(5, 6, spendDeposit)
|
||||||
|
checkReward(5, 7, spendDeposit)
|
||||||
|
}
|
||||||
|
}
|
205
pkg/core/native/native_test/oracle_test.go
Normal file
205
pkg/core/native/native_test/oracle_test.go
Normal file
|
@ -0,0 +1,205 @@
|
||||||
|
package native_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"math"
|
||||||
|
"math/big"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||||
|
"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/io"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
|
||||||
|
"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/stackitem"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newOracleClient(t *testing.T) *neotest.ContractInvoker {
|
||||||
|
return newNativeClient(t, nativenames.Oracle)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetSetPrice(t *testing.T) {
|
||||||
|
testGetSet(t, newOracleClient(t), "Price", native.DefaultOracleRequestPrice, 1, math.MaxInt64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getOracleContractState reads pre-compiled oracle contract generated by
|
||||||
|
// TestGenerateOracleContract and returns its state.
|
||||||
|
func getOracleContractState(t *testing.T, sender util.Uint160, id int32) *state.Contract {
|
||||||
|
var (
|
||||||
|
oracleContractNEFPath = filepath.Join("..", "..", "test_data", "oracle_contract", "oracle.nef")
|
||||||
|
oracleContractManifestPath = filepath.Join("..", "..", "test_data", "oracle_contract", "oracle.manifest.json")
|
||||||
|
)
|
||||||
|
errNotFound := errors.New("auto-generated oracle contract is not found, use TestGenerateOracleContract to regenerate")
|
||||||
|
|
||||||
|
neBytes, err := ioutil.ReadFile(oracleContractNEFPath)
|
||||||
|
require.NoError(t, err, fmt.Errorf("nef: %w", errNotFound))
|
||||||
|
ne, err := nef.FileFromBytes(neBytes)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
mBytes, err := ioutil.ReadFile(oracleContractManifestPath)
|
||||||
|
require.NoError(t, err, fmt.Errorf("manifest: %w", errNotFound))
|
||||||
|
m := &manifest.Manifest{}
|
||||||
|
err = json.Unmarshal(mBytes, m)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return &state.Contract{
|
||||||
|
ContractBase: state.ContractBase{
|
||||||
|
NEF: ne,
|
||||||
|
Hash: state.CreateContractHash(sender, ne.Checksum, m.Name),
|
||||||
|
Manifest: *m,
|
||||||
|
ID: id,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func putOracleRequest(t *testing.T, oracleInvoker *neotest.ContractInvoker,
|
||||||
|
url string, filter *string, cb string, userData []byte, gas int64, errStr ...string) {
|
||||||
|
var filtItem interface{}
|
||||||
|
if filter != nil {
|
||||||
|
filtItem = *filter
|
||||||
|
}
|
||||||
|
if len(errStr) == 0 {
|
||||||
|
oracleInvoker.Invoke(t, stackitem.Null{}, "requestURL", url, filtItem, cb, userData, gas)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
oracleInvoker.InvokeFail(t, errStr[0], "requestURL", url, filtItem, cb, userData, gas)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOracle_Request(t *testing.T) {
|
||||||
|
oracleCommitteeInvoker := newOracleClient(t)
|
||||||
|
e := oracleCommitteeInvoker.Executor
|
||||||
|
managementCommitteeInvoker := e.CommitteeInvoker(e.NativeHash(t, nativenames.Management))
|
||||||
|
designationCommitteeInvoker := e.CommitteeInvoker(e.NativeHash(t, nativenames.Designation))
|
||||||
|
gasCommitteeInvoker := e.CommitteeInvoker(e.NativeHash(t, nativenames.Gas))
|
||||||
|
|
||||||
|
cs := getOracleContractState(t, e.Validator.ScriptHash(), 1)
|
||||||
|
nBytes, err := cs.NEF.Bytes()
|
||||||
|
require.NoError(t, err)
|
||||||
|
mBytes, err := json.Marshal(cs.Manifest)
|
||||||
|
require.NoError(t, err)
|
||||||
|
expected, err := cs.ToStackItem()
|
||||||
|
require.NoError(t, err)
|
||||||
|
managementCommitteeInvoker.Invoke(t, expected, "deploy", nBytes, mBytes)
|
||||||
|
helperValidatorInvoker := e.ValidatorInvoker(cs.Hash)
|
||||||
|
|
||||||
|
gasForResponse := int64(2000_1234)
|
||||||
|
var filter = "flt"
|
||||||
|
userData := []byte("custom info")
|
||||||
|
putOracleRequest(t, helperValidatorInvoker, "url", &filter, "handle", userData, gasForResponse)
|
||||||
|
|
||||||
|
// Designate single Oracle node.
|
||||||
|
oracleNode := e.NewAccount(t)
|
||||||
|
designationCommitteeInvoker.Invoke(t, stackitem.Null{}, "designateAsRole", int(noderoles.Oracle), []interface{}{oracleNode.(neotest.SingleSigner).Account().PrivateKey().PublicKey().Bytes()})
|
||||||
|
err = oracleNode.(neotest.SingleSigner).Account().ConvertMultisig(1, []*keys.PublicKey{oracleNode.(neotest.SingleSigner).Account().PrivateKey().PublicKey()})
|
||||||
|
require.NoError(t, err)
|
||||||
|
oracleNodeMulti := neotest.NewMultiSigner(oracleNode.(neotest.SingleSigner).Account())
|
||||||
|
gasCommitteeInvoker.Invoke(t, true, "transfer", gasCommitteeInvoker.CommitteeHash, oracleNodeMulti.ScriptHash(), 100_0000_0000, nil)
|
||||||
|
|
||||||
|
// Finish.
|
||||||
|
prepareResponseTx := func(t *testing.T, requestID uint64) *transaction.Transaction {
|
||||||
|
w := io.NewBufBinWriter()
|
||||||
|
emit.AppCall(w.BinWriter, oracleCommitteeInvoker.Hash, "finish", callflag.All)
|
||||||
|
require.NoError(t, w.Err)
|
||||||
|
|
||||||
|
script := w.Bytes()
|
||||||
|
tx := transaction.New(script, 1000_0000)
|
||||||
|
tx.Nonce = neotest.Nonce()
|
||||||
|
tx.ValidUntilBlock = e.Chain.BlockHeight() + 1
|
||||||
|
tx.Attributes = []transaction.Attribute{{
|
||||||
|
Type: transaction.OracleResponseT,
|
||||||
|
Value: &transaction.OracleResponse{
|
||||||
|
ID: requestID,
|
||||||
|
Code: transaction.Success,
|
||||||
|
Result: []byte{4, 8, 15, 16, 23, 42},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
tx.Signers = []transaction.Signer{
|
||||||
|
{
|
||||||
|
Account: oracleNodeMulti.ScriptHash(),
|
||||||
|
Scopes: transaction.None,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Account: oracleCommitteeInvoker.Hash,
|
||||||
|
Scopes: transaction.None,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
tx.NetworkFee = 1000_1234
|
||||||
|
tx.Scripts = []transaction.Witness{
|
||||||
|
{
|
||||||
|
InvocationScript: oracleNodeMulti.SignHashable(uint32(e.Chain.GetConfig().Magic), tx),
|
||||||
|
VerificationScript: oracleNodeMulti.Script(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
InvocationScript: []byte{},
|
||||||
|
VerificationScript: []byte{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return tx
|
||||||
|
}
|
||||||
|
tx := prepareResponseTx(t, 0)
|
||||||
|
e.AddNewBlock(t, tx)
|
||||||
|
e.CheckHalt(t, tx.Hash(), stackitem.Null{})
|
||||||
|
|
||||||
|
// Ensure that callback was called.
|
||||||
|
si := e.Chain.GetStorageItem(cs.ID, []byte("lastOracleResponse"))
|
||||||
|
require.NotNil(t, si)
|
||||||
|
actual, err := stackitem.Deserialize(si)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, stackitem.NewArray([]stackitem.Item{
|
||||||
|
stackitem.NewByteArray([]byte("url")),
|
||||||
|
stackitem.NewByteArray(userData),
|
||||||
|
stackitem.NewBigInteger(big.NewInt(int64(tx.Attributes[0].Value.(*transaction.OracleResponse).Code))),
|
||||||
|
stackitem.NewByteArray(tx.Attributes[0].Value.(*transaction.OracleResponse).Result),
|
||||||
|
}), actual)
|
||||||
|
|
||||||
|
// Check that processed request is removed. We can't access GetRequestInternal directly,
|
||||||
|
// but adding response to this request should fail due to invalid request error.
|
||||||
|
tx = prepareResponseTx(t, 0)
|
||||||
|
err = e.Chain.VerifyTx(tx)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.True(t, strings.Contains(err.Error(), "oracle tx points to invalid request"))
|
||||||
|
|
||||||
|
t.Run("ErrorOnFinish", func(t *testing.T) {
|
||||||
|
putOracleRequest(t, helperValidatorInvoker, "url", nil, "handle", []byte{1, 2}, gasForResponse)
|
||||||
|
tx := prepareResponseTx(t, 1)
|
||||||
|
e.AddNewBlock(t, tx)
|
||||||
|
e.CheckFault(t, tx.Hash(), "ABORT")
|
||||||
|
|
||||||
|
// Check that processed request is cleaned up even if callback failed. We can't
|
||||||
|
// access GetRequestInternal directly, but adding response to this request
|
||||||
|
// should fail due to invalid request error.
|
||||||
|
tx = prepareResponseTx(t, 1)
|
||||||
|
err = e.Chain.VerifyTx(tx)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.True(t, strings.Contains(err.Error(), "oracle tx points to invalid request"))
|
||||||
|
})
|
||||||
|
t.Run("BadRequest", func(t *testing.T) {
|
||||||
|
t.Run("non-UTF8 url", func(t *testing.T) {
|
||||||
|
putOracleRequest(t, helperValidatorInvoker, "\xff", nil, "", []byte{1, 2}, gasForResponse, "invalid value: not UTF-8")
|
||||||
|
})
|
||||||
|
t.Run("non-UTF8 filter", func(t *testing.T) {
|
||||||
|
var f = "\xff"
|
||||||
|
putOracleRequest(t, helperValidatorInvoker, "url", &f, "", []byte{1, 2}, gasForResponse, "invalid value: not UTF-8")
|
||||||
|
})
|
||||||
|
t.Run("not enough gas", func(t *testing.T) {
|
||||||
|
putOracleRequest(t, helperValidatorInvoker, "url", nil, "", nil, 1000, "not enough gas for response")
|
||||||
|
})
|
||||||
|
t.Run("disallowed callback", func(t *testing.T) {
|
||||||
|
putOracleRequest(t, helperValidatorInvoker, "url", nil, "_deploy", nil, 1000_0000, "disallowed callback method (starts with '_')")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
81
pkg/core/native/native_test/policy_test.go
Normal file
81
pkg/core/native/native_test/policy_test.go
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
package native_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newPolicyClient(t *testing.T) *neotest.ContractInvoker {
|
||||||
|
return newNativeClient(t, nativenames.Policy)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPolicy_FeePerByte(t *testing.T) {
|
||||||
|
testGetSet(t, newPolicyClient(t), "FeePerByte", 1000, 0, 100_000_000)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPolicy_ExecFeeFactor(t *testing.T) {
|
||||||
|
testGetSet(t, newPolicyClient(t), "ExecFeeFactor", interop.DefaultBaseExecFee, 1, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPolicy_StoragePrice(t *testing.T) {
|
||||||
|
testGetSet(t, newPolicyClient(t), "StoragePrice", native.DefaultStoragePrice, 1, 10000000)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPolicy_BlockedAccounts(t *testing.T) {
|
||||||
|
c := newPolicyClient(t)
|
||||||
|
e := c.Executor
|
||||||
|
randomInvoker := c.WithSigners(c.NewAccount(t))
|
||||||
|
committeeInvoker := c.WithSigners(c.Committee)
|
||||||
|
unlucky := util.Uint160{1, 2, 3}
|
||||||
|
|
||||||
|
t.Run("isBlocked", func(t *testing.T) {
|
||||||
|
randomInvoker.Invoke(t, false, "isBlocked", unlucky)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("block-unblock account", func(t *testing.T) {
|
||||||
|
committeeInvoker.Invoke(t, true, "blockAccount", unlucky)
|
||||||
|
randomInvoker.Invoke(t, true, "isBlocked", unlucky)
|
||||||
|
committeeInvoker.Invoke(t, true, "unblockAccount", unlucky)
|
||||||
|
randomInvoker.Invoke(t, false, "isBlocked", unlucky)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("double-block", func(t *testing.T) {
|
||||||
|
// block
|
||||||
|
committeeInvoker.Invoke(t, true, "blockAccount", unlucky)
|
||||||
|
|
||||||
|
// double-block should fail
|
||||||
|
committeeInvoker.Invoke(t, false, "blockAccount", unlucky)
|
||||||
|
|
||||||
|
// unblock
|
||||||
|
committeeInvoker.Invoke(t, true, "unblockAccount", unlucky)
|
||||||
|
|
||||||
|
// unblock the same account should fail as we don't have it blocked
|
||||||
|
committeeInvoker.Invoke(t, false, "unblockAccount", unlucky)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("not signed by committee", func(t *testing.T) {
|
||||||
|
randomInvoker.InvokeFail(t, "invalid committee signature", "blockAccount", unlucky)
|
||||||
|
randomInvoker.InvokeFail(t, "invalid committee signature", "unblockAccount", unlucky)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("block-unblock contract", func(t *testing.T) {
|
||||||
|
committeeInvoker.InvokeFail(t, "cannot block native contract", "blockAccount", c.NativeHash(t, nativenames.Neo))
|
||||||
|
|
||||||
|
helper := neotest.CompileFile(t, c.CommitteeHash, "./helpers/policyhelper", "./helpers/policyhelper/policyhelper.yml")
|
||||||
|
e.DeployContract(t, helper, nil)
|
||||||
|
helperInvoker := e.CommitteeInvoker(helper.Hash)
|
||||||
|
|
||||||
|
helperInvoker.Invoke(t, true, "do")
|
||||||
|
committeeInvoker.Invoke(t, true, "blockAccount", helper.Hash)
|
||||||
|
helperInvoker.InvokeFail(t, fmt.Sprintf("contract %s is blocked", helper.Hash.StringLE()), "do")
|
||||||
|
|
||||||
|
committeeInvoker.Invoke(t, true, "unblockAccount", helper.Hash)
|
||||||
|
helperInvoker.Invoke(t, true, "do")
|
||||||
|
})
|
||||||
|
}
|
|
@ -13,7 +13,6 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/contract"
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/contract"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||||
|
@ -29,7 +28,6 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util/slice"
|
"github.com/nspcc-dev/neo-go/pkg/util/slice"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
"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/vm/stackitem"
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -89,11 +87,7 @@ func newOracle() *Oracle {
|
||||||
defer o.UpdateHash()
|
defer o.UpdateHash()
|
||||||
|
|
||||||
w := io.NewBufBinWriter()
|
w := io.NewBufBinWriter()
|
||||||
emit.Opcodes(w.BinWriter, opcode.NEWARRAY0)
|
emit.AppCall(w.BinWriter, o.Hash, "finish", callflag.All)
|
||||||
emit.Int(w.BinWriter, int64(callflag.All))
|
|
||||||
emit.String(w.BinWriter, "finish")
|
|
||||||
emit.Bytes(w.BinWriter, o.Hash.BytesBE())
|
|
||||||
emit.Syscall(w.BinWriter, interopnames.SystemContractCall)
|
|
||||||
o.oracleScript = w.Bytes()
|
o.oracleScript = w.Bytes()
|
||||||
|
|
||||||
desc := newDescriptor("request", smartcontract.VoidType,
|
desc := newDescriptor("request", smartcontract.VoidType,
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"math/big"
|
"math/big"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/internal/random"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/fee"
|
"github.com/nspcc-dev/neo-go/pkg/core/fee"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||||
|
@ -301,7 +302,7 @@ func TestNativeContract_InvokeOtherContract(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cs, _ := getTestContractState(chain)
|
cs, _ := getTestContractState(t, 4, 5, random.Uint160()) // sender and IDs are not important for the test
|
||||||
require.NoError(t, chain.contracts.Management.PutContractState(chain.dao, cs))
|
require.NoError(t, chain.contracts.Management.PutContractState(chain.dao, cs))
|
||||||
|
|
||||||
baseFee := chain.GetBaseExecFee()
|
baseFee := chain.GetBaseExecFee()
|
||||||
|
|
|
@ -73,51 +73,6 @@ func (bc *Blockchain) setNodesByRole(t *testing.T, ok bool, r noderoles.Role, no
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bc *Blockchain) getNodesByRole(t *testing.T, ok bool, r noderoles.Role, index uint32, resLen int) {
|
|
||||||
res, err := invokeContractMethod(bc, 10_000_000, bc.contracts.Designate.Hash, "getDesignatedByRole", int64(r), int64(index))
|
|
||||||
require.NoError(t, err)
|
|
||||||
if ok {
|
|
||||||
require.Equal(t, vm.HaltState, res.VMState)
|
|
||||||
require.Equal(t, 1, len(res.Stack))
|
|
||||||
arrItem := res.Stack[0]
|
|
||||||
require.Equal(t, stackitem.ArrayT, arrItem.Type())
|
|
||||||
arr := arrItem.(*stackitem.Array)
|
|
||||||
require.Equal(t, resLen, arr.Len())
|
|
||||||
} else {
|
|
||||||
checkFAULTState(t, res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDesignate_DesignateAsRoleTx(t *testing.T) {
|
|
||||||
bc := newTestChain(t)
|
|
||||||
|
|
||||||
priv, err := keys.NewPrivateKey()
|
|
||||||
require.NoError(t, err)
|
|
||||||
pubs := keys.PublicKeys{priv.PublicKey()}
|
|
||||||
|
|
||||||
bc.setNodesByRole(t, false, 0xFF, pubs)
|
|
||||||
bc.setNodesByRole(t, true, noderoles.Oracle, pubs)
|
|
||||||
index := bc.BlockHeight() + 1
|
|
||||||
bc.getNodesByRole(t, false, 0xFF, 0, 0)
|
|
||||||
bc.getNodesByRole(t, false, noderoles.Oracle, 100500, 0)
|
|
||||||
bc.getNodesByRole(t, true, noderoles.Oracle, 0, 0) // returns an empty list
|
|
||||||
bc.getNodesByRole(t, true, noderoles.Oracle, index, 1) // returns pubs
|
|
||||||
|
|
||||||
priv1, err := keys.NewPrivateKey()
|
|
||||||
require.NoError(t, err)
|
|
||||||
pubs = keys.PublicKeys{priv1.PublicKey()}
|
|
||||||
bc.setNodesByRole(t, true, noderoles.StateValidator, pubs)
|
|
||||||
bc.getNodesByRole(t, true, noderoles.StateValidator, bc.BlockHeight()+1, 1)
|
|
||||||
|
|
||||||
t.Run("neofs", func(t *testing.T) {
|
|
||||||
priv, err := keys.NewPrivateKey()
|
|
||||||
require.NoError(t, err)
|
|
||||||
pubs = keys.PublicKeys{priv.PublicKey()}
|
|
||||||
bc.setNodesByRole(t, true, noderoles.NeoFSAlphabet, pubs)
|
|
||||||
bc.getNodesByRole(t, true, noderoles.NeoFSAlphabet, bc.BlockHeight()+1, 1)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDesignate_DesignateAsRole(t *testing.T) {
|
func TestDesignate_DesignateAsRole(t *testing.T) {
|
||||||
bc := newTestChain(t)
|
bc := newTestChain(t)
|
||||||
|
|
||||||
|
|
|
@ -1,149 +0,0 @@
|
||||||
package core
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math/big"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/internal/testchain"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
|
||||||
"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/trigger"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGAS_Roundtrip(t *testing.T) {
|
|
||||||
bc := newTestChain(t)
|
|
||||||
|
|
||||||
getUtilityTokenBalance := func(bc *Blockchain, acc util.Uint160) (*big.Int, uint32) {
|
|
||||||
lub, err := bc.GetTokenLastUpdated(acc)
|
|
||||||
require.NoError(t, err)
|
|
||||||
return bc.GetUtilityTokenBalance(acc), lub[bc.contracts.GAS.ID]
|
|
||||||
}
|
|
||||||
|
|
||||||
initialBalance, _ := getUtilityTokenBalance(bc, neoOwner)
|
|
||||||
require.NotNil(t, initialBalance)
|
|
||||||
|
|
||||||
t.Run("bad: amount > initial balance", func(t *testing.T) {
|
|
||||||
tx := transferTokenFromMultisigAccountWithAssert(t, bc, neoOwner, bc.contracts.GAS.Hash, initialBalance.Int64()+1, false)
|
|
||||||
aer, err := bc.GetAppExecResults(tx.Hash(), trigger.Application)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, vm.HaltState, aer[0].VMState, aer[0].FaultException) // transfer without assert => HALT state
|
|
||||||
checkResult(t, &aer[0], stackitem.NewBool(false))
|
|
||||||
require.Len(t, aer[0].Events, 0) // failed transfer => no events
|
|
||||||
// check balance and height were not changed
|
|
||||||
updatedBalance, updatedHeight := getUtilityTokenBalance(bc, neoOwner)
|
|
||||||
initialBalance.Sub(initialBalance, big.NewInt(tx.SystemFee+tx.NetworkFee))
|
|
||||||
require.Equal(t, initialBalance, updatedBalance)
|
|
||||||
require.Equal(t, bc.BlockHeight(), updatedHeight)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("good: amount < initial balance", func(t *testing.T) {
|
|
||||||
amount := initialBalance.Int64() - 10_00000000
|
|
||||||
tx := transferTokenFromMultisigAccountWithAssert(t, bc, neoOwner, bc.contracts.GAS.Hash, amount, false)
|
|
||||||
aer, err := bc.GetAppExecResults(tx.Hash(), trigger.Application)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, vm.HaltState, aer[0].VMState, aer[0].FaultException)
|
|
||||||
checkResult(t, &aer[0], stackitem.NewBool(true))
|
|
||||||
require.Len(t, aer[0].Events, 1) // roundtrip
|
|
||||||
// check balance wasn't changed and height was updated
|
|
||||||
updatedBalance, updatedHeight := getUtilityTokenBalance(bc, neoOwner)
|
|
||||||
initialBalance.Sub(initialBalance, big.NewInt(tx.SystemFee+tx.NetworkFee))
|
|
||||||
require.Equal(t, initialBalance, updatedBalance)
|
|
||||||
require.Equal(t, bc.BlockHeight(), updatedHeight)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGAS_RewardWithP2PSigExtensionsEnabled(t *testing.T) {
|
|
||||||
chain := newTestChain(t)
|
|
||||||
notaryHash := chain.contracts.Notary.Hash
|
|
||||||
gasHash := chain.contracts.GAS.Hash
|
|
||||||
signer := testchain.MultisigScriptHash()
|
|
||||||
var err error
|
|
||||||
|
|
||||||
const (
|
|
||||||
nNotaries = 2
|
|
||||||
nKeys = 4
|
|
||||||
)
|
|
||||||
|
|
||||||
// set Notary nodes and check their balance
|
|
||||||
notaryNodes := make([]*keys.PrivateKey, nNotaries)
|
|
||||||
notaryNodesPublicKeys := make(keys.PublicKeys, nNotaries)
|
|
||||||
for i := range notaryNodes {
|
|
||||||
notaryNodes[i], err = keys.NewPrivateKey()
|
|
||||||
require.NoError(t, err)
|
|
||||||
notaryNodesPublicKeys[i] = notaryNodes[i].PublicKey()
|
|
||||||
}
|
|
||||||
chain.setNodesByRole(t, true, noderoles.P2PNotary, notaryNodesPublicKeys)
|
|
||||||
for _, notaryNode := range notaryNodesPublicKeys {
|
|
||||||
checkBalanceOf(t, chain, notaryNode.GetScriptHash(), 0)
|
|
||||||
}
|
|
||||||
// deposit GAS for `signer` with lock until the next block
|
|
||||||
depositAmount := 100_0000 + (2+int64(nKeys))*transaction.NotaryServiceFeePerKey // sysfee + netfee of the next transaction
|
|
||||||
transferTx := transferTokenFromMultisigAccount(t, chain, notaryHash, gasHash, depositAmount, signer, int64(chain.BlockHeight()+1))
|
|
||||||
res, err := chain.GetAppExecResults(transferTx.Hash(), trigger.Application)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, vm.HaltState, res[0].VMState)
|
|
||||||
require.Equal(t, 0, len(res[0].Stack))
|
|
||||||
|
|
||||||
// save initial validators balance
|
|
||||||
balances := make(map[int]int64, testchain.ValidatorsCount)
|
|
||||||
for i := 0; i < testchain.ValidatorsCount; i++ {
|
|
||||||
balances[i] = chain.GetUtilityTokenBalance(testchain.PrivateKeyByID(i).GetScriptHash()).Int64()
|
|
||||||
}
|
|
||||||
ic := interop.NewContext(trigger.Application, chain, chain.dao, nil, nil, nil, nil, chain.log)
|
|
||||||
tsInitial := chain.contracts.GAS.TotalSupply(ic, nil).Value().(*big.Int).Int64()
|
|
||||||
|
|
||||||
// send transaction with Notary contract as a sender
|
|
||||||
tx := chain.newTestTx(util.Uint160{}, []byte{byte(opcode.PUSH1)})
|
|
||||||
tx.Attributes = append(tx.Attributes, transaction.Attribute{Type: transaction.NotaryAssistedT, Value: &transaction.NotaryAssisted{NKeys: uint8(nKeys)}})
|
|
||||||
tx.NetworkFee = (2 + int64(nKeys)) * transaction.NotaryServiceFeePerKey
|
|
||||||
tx.Signers = []transaction.Signer{
|
|
||||||
{
|
|
||||||
Account: notaryHash,
|
|
||||||
Scopes: transaction.None,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Account: signer,
|
|
||||||
Scopes: transaction.None,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
tx.Scripts = []transaction.Witness{
|
|
||||||
{
|
|
||||||
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, notaryNodes[0].SignHashable(uint32(testchain.Network()), tx)...),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
InvocationScript: testchain.Sign(tx),
|
|
||||||
VerificationScript: testchain.MultisigVerificationScript(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
b := chain.newBlock(tx)
|
|
||||||
require.NoError(t, chain.AddBlock(b))
|
|
||||||
checkBalanceOf(t, chain, notaryHash, int(depositAmount-tx.SystemFee-tx.NetworkFee))
|
|
||||||
singleReward := transaction.NotaryServiceFeePerKey * (nKeys + 1) / nNotaries
|
|
||||||
for _, notaryNode := range notaryNodesPublicKeys {
|
|
||||||
checkBalanceOf(t, chain, notaryNode.GetScriptHash(), singleReward)
|
|
||||||
}
|
|
||||||
for i := 0; i < testchain.ValidatorsCount; i++ {
|
|
||||||
newBalance := chain.GetUtilityTokenBalance(testchain.PrivateKeyByID(i).GetScriptHash()).Int64()
|
|
||||||
expectedBalance := balances[i]
|
|
||||||
if i == int(b.Index)%testchain.CommitteeSize() {
|
|
||||||
// committee reward
|
|
||||||
expectedBalance += 5000_0000
|
|
||||||
}
|
|
||||||
if testchain.IDToOrder(i) == int(b.PrimaryIndex) {
|
|
||||||
// primary reward
|
|
||||||
expectedBalance += tx.NetworkFee - int64(singleReward*nNotaries)
|
|
||||||
}
|
|
||||||
assert.Equal(t, expectedBalance, newBalance, i)
|
|
||||||
}
|
|
||||||
tsUpdated := chain.contracts.GAS.TotalSupply(ic, nil).Value().(*big.Int).Int64()
|
|
||||||
tsExpected := tsInitial + 5000_0000 - tx.SystemFee
|
|
||||||
require.Equal(t, tsExpected, tsUpdated)
|
|
||||||
}
|
|
|
@ -1,174 +0,0 @@
|
||||||
package core
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math/big"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestLedgerGetTransactionHeight(t *testing.T) {
|
|
||||||
_, tx, _, chain := createVMAndTX(t)
|
|
||||||
|
|
||||||
ledger := chain.contracts.ByName(nativenames.Ledger).Metadata().Hash
|
|
||||||
|
|
||||||
for i := 0; i < 13; i++ {
|
|
||||||
require.NoError(t, chain.AddBlock(chain.newBlock()))
|
|
||||||
}
|
|
||||||
require.NoError(t, chain.dao.StoreAsTransaction(tx, 13, nil, nil))
|
|
||||||
t.Run("good", func(t *testing.T) {
|
|
||||||
res, err := invokeContractMethod(chain, 100000000, ledger, "getTransactionHeight", tx.Hash().BytesBE())
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, res, stackitem.Make(13))
|
|
||||||
})
|
|
||||||
t.Run("bad", func(t *testing.T) {
|
|
||||||
res, err := invokeContractMethod(chain, 100000000, ledger, "getTransactionHeight", tx.Hash().BytesLE())
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, res, stackitem.Make(-1))
|
|
||||||
})
|
|
||||||
t.Run("not a hash", func(t *testing.T) {
|
|
||||||
res, err := invokeContractMethod(chain, 100000000, ledger, "getTransactionHeight", []byte{1})
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkFAULTState(t, res)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLedgerGetTransaction(t *testing.T) {
|
|
||||||
_, tx, _, chain := createVMAndTX(t)
|
|
||||||
ledger := chain.contracts.ByName(nativenames.Ledger).Metadata().Hash
|
|
||||||
|
|
||||||
t.Run("success", func(t *testing.T) {
|
|
||||||
require.NoError(t, chain.dao.StoreAsTransaction(tx, 0, nil, nil))
|
|
||||||
|
|
||||||
res, err := invokeContractMethod(chain, 100000000, ledger, "getTransaction", tx.Hash().BytesBE())
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, vm.HaltState, res.VMState, res.FaultException)
|
|
||||||
require.Equal(t, 1, len(res.Stack))
|
|
||||||
value := res.Stack[0].Value()
|
|
||||||
|
|
||||||
actual, ok := value.([]stackitem.Item)
|
|
||||||
require.True(t, ok)
|
|
||||||
require.Equal(t, 8, len(actual))
|
|
||||||
require.Equal(t, tx.Hash().BytesBE(), actual[0].Value().([]byte))
|
|
||||||
require.Equal(t, int64(tx.Version), actual[1].Value().(*big.Int).Int64())
|
|
||||||
require.Equal(t, int64(tx.Nonce), actual[2].Value().(*big.Int).Int64())
|
|
||||||
require.Equal(t, tx.Sender().BytesBE(), actual[3].Value().([]byte))
|
|
||||||
require.Equal(t, int64(tx.SystemFee), actual[4].Value().(*big.Int).Int64())
|
|
||||||
require.Equal(t, int64(tx.NetworkFee), actual[5].Value().(*big.Int).Int64())
|
|
||||||
require.Equal(t, int64(tx.ValidUntilBlock), actual[6].Value().(*big.Int).Int64())
|
|
||||||
require.Equal(t, tx.Script, actual[7].Value().([]byte))
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("isn't traceable", func(t *testing.T) {
|
|
||||||
require.NoError(t, chain.dao.StoreAsTransaction(tx, 2, nil, nil)) // block 1 is added above
|
|
||||||
res, err := invokeContractMethod(chain, 100000000, ledger, "getTransaction", tx.Hash().BytesBE())
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, res, stackitem.Null{})
|
|
||||||
})
|
|
||||||
t.Run("bad hash", func(t *testing.T) {
|
|
||||||
require.NoError(t, chain.dao.StoreAsTransaction(tx, 0, nil, nil))
|
|
||||||
res, err := invokeContractMethod(chain, 100000000, ledger, "getTransaction", tx.Hash().BytesLE())
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, res, stackitem.Null{})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLedgerGetTransactionFromBlock(t *testing.T) {
|
|
||||||
chain := newTestChain(t)
|
|
||||||
ledger := chain.contracts.ByName(nativenames.Ledger).Metadata().Hash
|
|
||||||
|
|
||||||
res, err := invokeContractMethod(chain, 100000000, ledger, "currentIndex") // adds a block
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, res, stackitem.Make(0))
|
|
||||||
bhash := chain.GetHeaderHash(1)
|
|
||||||
b, err := chain.GetBlock(bhash)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
t.Run("success", func(t *testing.T) {
|
|
||||||
res, err := invokeContractMethod(chain, 100000000, ledger, "getTransactionFromBlock", bhash.BytesBE(), int64(0))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, vm.HaltState, res.VMState, res.FaultException)
|
|
||||||
require.Equal(t, 1, len(res.Stack))
|
|
||||||
value := res.Stack[0].Value()
|
|
||||||
|
|
||||||
actual, ok := value.([]stackitem.Item)
|
|
||||||
require.True(t, ok)
|
|
||||||
require.Equal(t, b.Transactions[0].Hash().BytesBE(), actual[0].Value().([]byte))
|
|
||||||
})
|
|
||||||
t.Run("bad transaction index", func(t *testing.T) {
|
|
||||||
res, err := invokeContractMethod(chain, 100000000, ledger, "getTransactionFromBlock", bhash.BytesBE(), int64(1))
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkFAULTState(t, res)
|
|
||||||
})
|
|
||||||
t.Run("invalid block hash (>int64)", func(t *testing.T) {
|
|
||||||
res, err := invokeContractMethod(chain, 100000000, ledger, "getTransactionFromBlock", bhash.BytesBE()[:10], int64(0))
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkFAULTState(t, res)
|
|
||||||
})
|
|
||||||
t.Run("invalid block hash (int64)", func(t *testing.T) {
|
|
||||||
res, err := invokeContractMethod(chain, 100000000, ledger, "getTransactionFromBlock", bhash.BytesBE()[:6], int64(0))
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkFAULTState(t, res)
|
|
||||||
})
|
|
||||||
t.Run("bad block hash", func(t *testing.T) {
|
|
||||||
res, err := invokeContractMethod(chain, 100000000, ledger, "getTransactionFromBlock", bhash.BytesLE(), int64(0))
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, res, stackitem.Null{})
|
|
||||||
})
|
|
||||||
t.Run("isn't traceable", func(t *testing.T) {
|
|
||||||
b.Index = chain.BlockHeight() + 1
|
|
||||||
require.NoError(t, chain.dao.StoreAsBlock(b, nil, nil, nil))
|
|
||||||
res, err := invokeContractMethod(chain, 100000000, ledger, "getTransactionFromBlock", bhash.BytesBE(), int64(0))
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, res, stackitem.Null{})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLedgerGetBlock(t *testing.T) {
|
|
||||||
chain := newTestChain(t)
|
|
||||||
ledger := chain.contracts.ByName(nativenames.Ledger).Metadata().Hash
|
|
||||||
|
|
||||||
bhash := chain.GetHeaderHash(0)
|
|
||||||
res, err := invokeContractMethod(chain, 100000000, ledger, "currentHash") // adds a block
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, res, stackitem.Make(bhash.BytesBE()))
|
|
||||||
bhash = chain.GetHeaderHash(1)
|
|
||||||
b, err := chain.GetBlock(bhash)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
t.Run("success", func(t *testing.T) {
|
|
||||||
res, err := invokeContractMethod(chain, 100000000, ledger, "getBlock", bhash.BytesBE())
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, vm.HaltState, res.VMState, res.FaultException)
|
|
||||||
require.Equal(t, 1, len(res.Stack))
|
|
||||||
value := res.Stack[0].Value()
|
|
||||||
|
|
||||||
actual, ok := value.([]stackitem.Item)
|
|
||||||
require.True(t, ok)
|
|
||||||
require.Equal(t, 9, len(actual))
|
|
||||||
require.Equal(t, b.Hash().BytesBE(), actual[0].Value().([]byte))
|
|
||||||
require.Equal(t, int64(b.Version), actual[1].Value().(*big.Int).Int64())
|
|
||||||
require.Equal(t, b.PrevHash.BytesBE(), actual[2].Value().([]byte))
|
|
||||||
require.Equal(t, b.MerkleRoot.BytesBE(), actual[3].Value().([]byte))
|
|
||||||
require.Equal(t, int64(b.Timestamp), actual[4].Value().(*big.Int).Int64())
|
|
||||||
require.Equal(t, int64(b.Nonce), actual[5].Value().(*big.Int).Int64())
|
|
||||||
require.Equal(t, int64(b.Index), actual[6].Value().(*big.Int).Int64())
|
|
||||||
require.Equal(t, b.NextConsensus.BytesBE(), actual[7].Value().([]byte))
|
|
||||||
require.Equal(t, int64(len(b.Transactions)), actual[8].Value().(*big.Int).Int64())
|
|
||||||
})
|
|
||||||
t.Run("bad hash", func(t *testing.T) {
|
|
||||||
res, err := invokeContractMethod(chain, 100000000, ledger, "getBlock", bhash.BytesLE())
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, res, stackitem.Null{})
|
|
||||||
})
|
|
||||||
t.Run("isn't traceable", func(t *testing.T) {
|
|
||||||
b.Index = chain.BlockHeight() + 1
|
|
||||||
require.NoError(t, chain.dao.StoreAsBlock(b, nil, nil, nil))
|
|
||||||
res, err := invokeContractMethod(chain, 100000000, ledger, "getBlock", bhash.BytesBE())
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, res, stackitem.Null{})
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,617 +1,19 @@
|
||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"math/big"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/internal/testchain"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/chaindump"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/stateroot"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
||||||
"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/io"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"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/vm/stackitem"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDeployManifestOverflow(t *testing.T) {
|
|
||||||
bc := newTestChain(t)
|
|
||||||
|
|
||||||
// nef.NewFile() cares about version a lot.
|
|
||||||
config.Version = "0.90.0-test"
|
|
||||||
mgmtHash := bc.ManagementContractHash()
|
|
||||||
cs1, _ := getTestContractState(bc)
|
|
||||||
cs1.ID = 1
|
|
||||||
cs1.Hash = state.CreateContractHash(testchain.MultisigScriptHash(), cs1.NEF.Checksum, cs1.Manifest.Name)
|
|
||||||
manif1, err := json.Marshal(cs1.Manifest)
|
|
||||||
require.NoError(t, err)
|
|
||||||
nef1, err := nef.NewFile(cs1.NEF.Script)
|
|
||||||
require.NoError(t, err)
|
|
||||||
nef1b, err := nef1.Bytes()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
w := io.NewBufBinWriter()
|
|
||||||
emit.Bytes(w.BinWriter, manif1)
|
|
||||||
emit.Int(w.BinWriter, manifest.MaxManifestSize)
|
|
||||||
emit.Opcodes(w.BinWriter, opcode.NEWBUFFER, opcode.CAT)
|
|
||||||
emit.Bytes(w.BinWriter, nef1b)
|
|
||||||
emit.Int(w.BinWriter, 2)
|
|
||||||
emit.Opcodes(w.BinWriter, opcode.PACK)
|
|
||||||
emit.AppCallNoArgs(w.BinWriter, mgmtHash, "deploy", callflag.All)
|
|
||||||
require.NoError(t, w.Err)
|
|
||||||
script := w.Bytes()
|
|
||||||
|
|
||||||
tx := transaction.New(script, 0)
|
|
||||||
tx.ValidUntilBlock = bc.blockHeight + 1
|
|
||||||
addSigners(neoOwner, tx)
|
|
||||||
setTxSystemFee(bc, 100_00000000, tx)
|
|
||||||
require.NoError(t, testchain.SignTx(bc, tx))
|
|
||||||
|
|
||||||
aers, err := persistBlock(bc, tx)
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkFAULTState(t, aers[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
type memoryStore struct {
|
type memoryStore struct {
|
||||||
*storage.MemoryStore
|
*storage.MemoryStore
|
||||||
}
|
}
|
||||||
|
|
||||||
func (memoryStore) Close() error { return nil }
|
func (memoryStore) Close() error { return nil }
|
||||||
|
|
||||||
func TestStartFromHeight(t *testing.T) {
|
|
||||||
st := memoryStore{storage.NewMemoryStore()}
|
|
||||||
bc := newTestChainWithCustomCfgAndStore(t, st, nil)
|
|
||||||
cs1, _ := getTestContractState(bc)
|
|
||||||
func() {
|
|
||||||
require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs1))
|
|
||||||
checkContractState(t, bc, cs1.Hash, cs1)
|
|
||||||
_, err := bc.dao.Store.Persist()
|
|
||||||
require.NoError(t, err)
|
|
||||||
}()
|
|
||||||
|
|
||||||
bc2 := newTestChainWithCustomCfgAndStore(t, st, nil)
|
|
||||||
checkContractState(t, bc2, cs1.Hash, cs1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestContractDeployAndUpdateWithParameter(t *testing.T) {
|
|
||||||
bc := newTestChain(t)
|
|
||||||
|
|
||||||
// nef.NewFile() cares about version a lot.
|
|
||||||
config.Version = "0.90.0-test"
|
|
||||||
mgmtHash := bc.ManagementContractHash()
|
|
||||||
cs1, _ := getTestContractState(bc)
|
|
||||||
cs1.Manifest.Permissions = []manifest.Permission{*manifest.NewPermission(manifest.PermissionWildcard)}
|
|
||||||
cs1.ID = 1
|
|
||||||
cs1.Hash = state.CreateContractHash(testchain.MultisigScriptHash(), cs1.NEF.Checksum, cs1.Manifest.Name)
|
|
||||||
manif1, err := json.Marshal(cs1.Manifest)
|
|
||||||
require.NoError(t, err)
|
|
||||||
nef1b, err := cs1.NEF.Bytes()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
aer, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", nef1b, manif1, int64(42))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, vm.HaltState, aer.VMState)
|
|
||||||
|
|
||||||
t.Run("_deploy called", func(t *testing.T) {
|
|
||||||
res, err := invokeContractMethod(bc, 1_00000000, cs1.Hash, "getValue")
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, 1, len(res.Stack))
|
|
||||||
item, err := stackitem.Deserialize(res.Stack[0].Value().([]byte))
|
|
||||||
require.NoError(t, err)
|
|
||||||
expected := []stackitem.Item{stackitem.Make("create"), stackitem.Make(42)}
|
|
||||||
require.Equal(t, stackitem.NewArray(expected), item)
|
|
||||||
})
|
|
||||||
|
|
||||||
cs1.NEF.Script = append(cs1.NEF.Script, byte(opcode.RET))
|
|
||||||
cs1.NEF.Checksum = cs1.NEF.CalculateChecksum()
|
|
||||||
nef1b, err = cs1.NEF.Bytes()
|
|
||||||
require.NoError(t, err)
|
|
||||||
cs1.UpdateCounter++
|
|
||||||
|
|
||||||
aer, err = invokeContractMethod(bc, 10_00000000, cs1.Hash, "update", nef1b, nil, "new data")
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, vm.HaltState, aer.VMState)
|
|
||||||
|
|
||||||
t.Run("_deploy called", func(t *testing.T) {
|
|
||||||
res, err := invokeContractMethod(bc, 1_00000000, cs1.Hash, "getValue")
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, 1, len(res.Stack))
|
|
||||||
item, err := stackitem.Deserialize(res.Stack[0].Value().([]byte))
|
|
||||||
require.NoError(t, err)
|
|
||||||
expected := []stackitem.Item{stackitem.Make("update"), stackitem.Make("new data")}
|
|
||||||
require.Equal(t, stackitem.NewArray(expected), item)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestContractDeploy(t *testing.T) {
|
|
||||||
bc := newTestChain(t)
|
|
||||||
|
|
||||||
// nef.NewFile() cares about version a lot.
|
|
||||||
config.Version = "0.90.0-test"
|
|
||||||
mgmtHash := bc.ManagementContractHash()
|
|
||||||
cs1, _ := getTestContractState(bc)
|
|
||||||
cs1.ID = 1
|
|
||||||
cs1.Hash = state.CreateContractHash(testchain.MultisigScriptHash(), cs1.NEF.Checksum, cs1.Manifest.Name)
|
|
||||||
manif1, err := json.Marshal(cs1.Manifest)
|
|
||||||
require.NoError(t, err)
|
|
||||||
nef1b, err := cs1.NEF.Bytes()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
t.Run("no NEF", func(t *testing.T) {
|
|
||||||
res, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", nil, manif1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkFAULTState(t, res)
|
|
||||||
})
|
|
||||||
t.Run("no manifest", func(t *testing.T) {
|
|
||||||
res, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", nef1b, nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkFAULTState(t, res)
|
|
||||||
})
|
|
||||||
t.Run("int for NEF", func(t *testing.T) {
|
|
||||||
res, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", int64(1), manif1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkFAULTState(t, res)
|
|
||||||
})
|
|
||||||
t.Run("zero-length NEF", func(t *testing.T) {
|
|
||||||
res, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", []byte{}, manif1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkFAULTState(t, res)
|
|
||||||
})
|
|
||||||
t.Run("array for NEF", func(t *testing.T) {
|
|
||||||
res, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", []interface{}{int64(1)}, manif1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkFAULTState(t, res)
|
|
||||||
})
|
|
||||||
t.Run("bad script in NEF", func(t *testing.T) {
|
|
||||||
nf, err := nef.FileFromBytes(nef1b) // make a full copy
|
|
||||||
require.NoError(t, err)
|
|
||||||
nf.Script[0] = 0xff
|
|
||||||
nf.CalculateChecksum()
|
|
||||||
nefbad, err := nf.Bytes()
|
|
||||||
require.NoError(t, err)
|
|
||||||
res, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", nefbad, manif1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkFAULTState(t, res)
|
|
||||||
})
|
|
||||||
t.Run("int for manifest", func(t *testing.T) {
|
|
||||||
res, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", nef1b, int64(1))
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkFAULTState(t, res)
|
|
||||||
})
|
|
||||||
t.Run("zero-length manifest", func(t *testing.T) {
|
|
||||||
res, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", nef1b, []byte{})
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkFAULTState(t, res)
|
|
||||||
})
|
|
||||||
t.Run("array for manifest", func(t *testing.T) {
|
|
||||||
res, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", nef1b, []interface{}{int64(1)})
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkFAULTState(t, res)
|
|
||||||
})
|
|
||||||
t.Run("non-utf8 manifest", func(t *testing.T) {
|
|
||||||
manifB := bytes.Replace(manif1, []byte("TestMain"), []byte("\xff\xfe\xfd"), 1) // Replace name.
|
|
||||||
|
|
||||||
res, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", nef1b, manifB)
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkFAULTState(t, res)
|
|
||||||
})
|
|
||||||
t.Run("invalid manifest", func(t *testing.T) {
|
|
||||||
pkey, err := keys.NewPrivateKey()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
var badManifest = cs1.Manifest
|
|
||||||
badManifest.Groups = []manifest.Group{{PublicKey: pkey.PublicKey(), Signature: make([]byte, 64)}}
|
|
||||||
manifB, err := json.Marshal(badManifest)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
res, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", nef1b, manifB)
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkFAULTState(t, res)
|
|
||||||
})
|
|
||||||
t.Run("bad methods in manifest 1", func(t *testing.T) {
|
|
||||||
var badManifest = cs1.Manifest
|
|
||||||
badManifest.ABI.Methods = make([]manifest.Method, len(cs1.Manifest.ABI.Methods))
|
|
||||||
copy(badManifest.ABI.Methods, cs1.Manifest.ABI.Methods)
|
|
||||||
badManifest.ABI.Methods[0].Offset = 100500 // out of bounds
|
|
||||||
|
|
||||||
manifB, err := json.Marshal(badManifest)
|
|
||||||
require.NoError(t, err)
|
|
||||||
res, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", nef1b, manifB)
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkFAULTState(t, res)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("bad methods in manifest 2", func(t *testing.T) {
|
|
||||||
var badManifest = cs1.Manifest
|
|
||||||
badManifest.ABI.Methods = make([]manifest.Method, len(cs1.Manifest.ABI.Methods))
|
|
||||||
copy(badManifest.ABI.Methods, cs1.Manifest.ABI.Methods)
|
|
||||||
badManifest.ABI.Methods[0].Offset = len(cs1.NEF.Script) - 2 // Ends with `CALLT(X,X);RET`.
|
|
||||||
|
|
||||||
manifB, err := json.Marshal(badManifest)
|
|
||||||
require.NoError(t, err)
|
|
||||||
res, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", nef1b, manifB)
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkFAULTState(t, res)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("not enough GAS", func(t *testing.T) {
|
|
||||||
res, err := invokeContractMethod(bc, 1_00000000, mgmtHash, "deploy", nef1b, manif1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkFAULTState(t, res)
|
|
||||||
})
|
|
||||||
t.Run("positive", func(t *testing.T) {
|
|
||||||
tx1, err := prepareContractMethodInvoke(bc, 11_00000000, mgmtHash, "deploy", nef1b, manif1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
tx2, err := prepareContractMethodInvoke(bc, 1_00000000, mgmtHash, "getContract", cs1.Hash.BytesBE())
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
aers, err := persistBlock(bc, tx1, tx2)
|
|
||||||
require.NoError(t, err)
|
|
||||||
for _, res := range aers {
|
|
||||||
require.Equal(t, vm.HaltState, res.VMState)
|
|
||||||
require.Equal(t, 1, len(res.Stack))
|
|
||||||
compareContractStates(t, cs1, res.Stack[0])
|
|
||||||
}
|
|
||||||
require.Equal(t, aers[0].Events, []state.NotificationEvent{{
|
|
||||||
ScriptHash: mgmtHash,
|
|
||||||
Name: "Deploy",
|
|
||||||
Item: stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray(cs1.Hash.BytesBE())}),
|
|
||||||
}})
|
|
||||||
t.Run("_deploy called", func(t *testing.T) {
|
|
||||||
res, err := invokeContractMethod(bc, 1_00000000, cs1.Hash, "getValue")
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, 1, len(res.Stack))
|
|
||||||
item, err := stackitem.Deserialize(res.Stack[0].Value().([]byte))
|
|
||||||
require.NoError(t, err)
|
|
||||||
expected := []stackitem.Item{stackitem.Make("create"), stackitem.Null{}}
|
|
||||||
require.Equal(t, stackitem.NewArray(expected), item)
|
|
||||||
})
|
|
||||||
t.Run("get after deploy", func(t *testing.T) {
|
|
||||||
checkContractState(t, bc, cs1.Hash, cs1)
|
|
||||||
})
|
|
||||||
t.Run("get after restore", func(t *testing.T) {
|
|
||||||
w := io.NewBufBinWriter()
|
|
||||||
require.NoError(t, chaindump.Dump(bc, w.BinWriter, 0, bc.BlockHeight()+1))
|
|
||||||
require.NoError(t, w.Err)
|
|
||||||
|
|
||||||
r := io.NewBinReaderFromBuf(w.Bytes())
|
|
||||||
bc2 := newTestChain(t)
|
|
||||||
|
|
||||||
require.NoError(t, chaindump.Restore(bc2, r, 0, bc.BlockHeight()+1, nil))
|
|
||||||
require.NoError(t, r.Err)
|
|
||||||
checkContractState(t, bc2, cs1.Hash, cs1)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
t.Run("contract already exists", func(t *testing.T) {
|
|
||||||
res, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", nef1b, manif1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkFAULTState(t, res)
|
|
||||||
})
|
|
||||||
t.Run("failed _deploy", func(t *testing.T) {
|
|
||||||
deployScript := []byte{byte(opcode.ABORT)}
|
|
||||||
m := manifest.NewManifest("TestDeployAbort")
|
|
||||||
m.ABI.Methods = []manifest.Method{
|
|
||||||
{
|
|
||||||
Name: manifest.MethodDeploy,
|
|
||||||
Offset: 0,
|
|
||||||
Parameters: []manifest.Parameter{
|
|
||||||
manifest.NewParameter("data", smartcontract.AnyType),
|
|
||||||
manifest.NewParameter("isUpdate", smartcontract.BoolType),
|
|
||||||
},
|
|
||||||
ReturnType: smartcontract.VoidType,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
nefD, err := nef.NewFile(deployScript)
|
|
||||||
require.NoError(t, err)
|
|
||||||
nefDb, err := nefD.Bytes()
|
|
||||||
require.NoError(t, err)
|
|
||||||
manifD, err := json.Marshal(m)
|
|
||||||
require.NoError(t, err)
|
|
||||||
res, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", nefDb, manifD)
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkFAULTState(t, res)
|
|
||||||
|
|
||||||
t.Run("get after failed deploy", func(t *testing.T) {
|
|
||||||
h := state.CreateContractHash(neoOwner, nefD.Checksum, m.Name)
|
|
||||||
checkContractState(t, bc, h, nil)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
t.Run("bad _deploy", func(t *testing.T) { // invalid _deploy signature
|
|
||||||
deployScript := []byte{byte(opcode.RET)}
|
|
||||||
m := manifest.NewManifest("TestBadDeploy")
|
|
||||||
m.ABI.Methods = []manifest.Method{
|
|
||||||
{
|
|
||||||
Name: manifest.MethodDeploy,
|
|
||||||
Offset: 0,
|
|
||||||
Parameters: []manifest.Parameter{
|
|
||||||
manifest.NewParameter("data", smartcontract.AnyType),
|
|
||||||
manifest.NewParameter("isUpdate", smartcontract.BoolType),
|
|
||||||
},
|
|
||||||
ReturnType: smartcontract.ArrayType,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
nefD, err := nef.NewFile(deployScript)
|
|
||||||
require.NoError(t, err)
|
|
||||||
nefDb, err := nefD.Bytes()
|
|
||||||
require.NoError(t, err)
|
|
||||||
manifD, err := json.Marshal(m)
|
|
||||||
require.NoError(t, err)
|
|
||||||
res, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", nefDb, manifD)
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkFAULTState(t, res)
|
|
||||||
|
|
||||||
t.Run("get after bad _deploy", func(t *testing.T) {
|
|
||||||
h := state.CreateContractHash(neoOwner, nefD.Checksum, m.Name)
|
|
||||||
checkContractState(t, bc, h, nil)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkContractState(t *testing.T, bc *Blockchain, h util.Uint160, cs *state.Contract) {
|
|
||||||
mgmtHash := bc.contracts.Management.Hash
|
|
||||||
res, err := invokeContractMethod(bc, 1_00000000, mgmtHash, "getContract", h.BytesBE())
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, vm.HaltState, res.VMState)
|
|
||||||
require.Equal(t, 1, len(res.Stack))
|
|
||||||
if cs == nil {
|
|
||||||
require.Equal(t, stackitem.Null{}, res.Stack[0])
|
|
||||||
} else {
|
|
||||||
compareContractStates(t, cs, res.Stack[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestContractUpdate(t *testing.T) {
|
|
||||||
bc := newTestChain(t)
|
|
||||||
|
|
||||||
// nef.NewFile() cares about version a lot.
|
|
||||||
config.Version = "0.90.0-test"
|
|
||||||
mgmtHash := bc.ManagementContractHash()
|
|
||||||
cs1, _ := getTestContractState(bc)
|
|
||||||
// Allow calling management contract.
|
|
||||||
cs1.Manifest.Permissions = []manifest.Permission{*manifest.NewPermission(manifest.PermissionWildcard)}
|
|
||||||
err := bc.contracts.Management.PutContractState(bc.dao, cs1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
manif1, err := json.Marshal(cs1.Manifest)
|
|
||||||
require.NoError(t, err)
|
|
||||||
nef1, err := nef.NewFile(cs1.NEF.Script)
|
|
||||||
require.NoError(t, err)
|
|
||||||
nef1b, err := nef1.Bytes()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
t.Run("no contract", func(t *testing.T) {
|
|
||||||
res, err := invokeContractMethod(bc, 10_00000000, mgmtHash, "update", nef1b, manif1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkFAULTState(t, res)
|
|
||||||
})
|
|
||||||
t.Run("zero-length NEF", func(t *testing.T) {
|
|
||||||
res, err := invokeContractMethod(bc, 10_00000000, cs1.Hash, "update", []byte{}, manif1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkFAULTState(t, res)
|
|
||||||
})
|
|
||||||
t.Run("zero-length manifest", func(t *testing.T) {
|
|
||||||
res, err := invokeContractMethod(bc, 10_00000000, cs1.Hash, "update", nef1b, []byte{})
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkFAULTState(t, res)
|
|
||||||
})
|
|
||||||
t.Run("not enough GAS", func(t *testing.T) {
|
|
||||||
res, err := invokeContractMethod(bc, 1_00000000, cs1.Hash, "update", nef1b, manif1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkFAULTState(t, res)
|
|
||||||
})
|
|
||||||
t.Run("no real params", func(t *testing.T) {
|
|
||||||
res, err := invokeContractMethod(bc, 10_00000000, cs1.Hash, "update", nil, nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkFAULTState(t, res)
|
|
||||||
})
|
|
||||||
t.Run("invalid manifest", func(t *testing.T) {
|
|
||||||
pkey, err := keys.NewPrivateKey()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
var badManifest = cs1.Manifest
|
|
||||||
badManifest.Groups = []manifest.Group{{PublicKey: pkey.PublicKey(), Signature: make([]byte, 64)}}
|
|
||||||
manifB, err := json.Marshal(badManifest)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
res, err := invokeContractMethod(bc, 10_00000000, cs1.Hash, "update", nef1b, manifB)
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkFAULTState(t, res)
|
|
||||||
})
|
|
||||||
t.Run("manifest and script mismatch", func(t *testing.T) {
|
|
||||||
nf, err := nef.FileFromBytes(nef1b) // Make a full copy.
|
|
||||||
require.NoError(t, err)
|
|
||||||
nf.Script = append(nf.Script, byte(opcode.RET))
|
|
||||||
copy(nf.Script[1:], nf.Script) // Now all method offsets are wrong.
|
|
||||||
nf.Script[0] = byte(opcode.RET) // Even though the script is correct.
|
|
||||||
nf.CalculateChecksum()
|
|
||||||
nefnew, err := nf.Bytes()
|
|
||||||
require.NoError(t, err)
|
|
||||||
res, err := invokeContractMethod(bc, 10_00000000, cs1.Hash, "update", nefnew, manif1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkFAULTState(t, res)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("change name", func(t *testing.T) {
|
|
||||||
var badManifest = cs1.Manifest
|
|
||||||
badManifest.Name += "tail"
|
|
||||||
manifB, err := json.Marshal(badManifest)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
res, err := invokeContractMethod(bc, 10_00000000, cs1.Hash, "update", nef1b, manifB)
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkFAULTState(t, res)
|
|
||||||
})
|
|
||||||
|
|
||||||
cs1.NEF.Script = append(cs1.NEF.Script, byte(opcode.RET))
|
|
||||||
cs1.NEF.Checksum = cs1.NEF.CalculateChecksum()
|
|
||||||
nef1b, err = cs1.NEF.Bytes()
|
|
||||||
require.NoError(t, err)
|
|
||||||
cs1.UpdateCounter++
|
|
||||||
|
|
||||||
t.Run("update script, positive", func(t *testing.T) {
|
|
||||||
tx1, err := prepareContractMethodInvoke(bc, 10_00000000, cs1.Hash, "update", nef1b, nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
tx2, err := prepareContractMethodInvoke(bc, 1_00000000, mgmtHash, "getContract", cs1.Hash.BytesBE())
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
aers, err := persistBlock(bc, tx1, tx2)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, vm.HaltState, aers[0].VMState)
|
|
||||||
require.Equal(t, vm.HaltState, aers[1].VMState)
|
|
||||||
require.Equal(t, 1, len(aers[1].Stack))
|
|
||||||
compareContractStates(t, cs1, aers[1].Stack[0])
|
|
||||||
require.Equal(t, aers[0].Events, []state.NotificationEvent{{
|
|
||||||
ScriptHash: mgmtHash,
|
|
||||||
Name: "Update",
|
|
||||||
Item: stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray(cs1.Hash.BytesBE())}),
|
|
||||||
}})
|
|
||||||
t.Run("_deploy called", func(t *testing.T) {
|
|
||||||
res, err := invokeContractMethod(bc, 1_00000000, cs1.Hash, "getValue")
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, 1, len(res.Stack))
|
|
||||||
item, err := stackitem.Deserialize(res.Stack[0].Value().([]byte))
|
|
||||||
require.NoError(t, err)
|
|
||||||
expected := []stackitem.Item{stackitem.Make("update"), stackitem.Null{}}
|
|
||||||
require.Equal(t, stackitem.NewArray(expected), item)
|
|
||||||
})
|
|
||||||
t.Run("check contract", func(t *testing.T) {
|
|
||||||
checkContractState(t, bc, cs1.Hash, cs1)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
cs1.Manifest.Extra = []byte(`"update me"`)
|
|
||||||
manif1, err = json.Marshal(cs1.Manifest)
|
|
||||||
require.NoError(t, err)
|
|
||||||
cs1.UpdateCounter++
|
|
||||||
|
|
||||||
t.Run("update manifest, positive", func(t *testing.T) {
|
|
||||||
res, err := invokeContractMethod(bc, 10_00000000, cs1.Hash, "update", nil, manif1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, vm.HaltState, res.VMState)
|
|
||||||
require.Equal(t, res.Events, []state.NotificationEvent{{
|
|
||||||
ScriptHash: mgmtHash,
|
|
||||||
Name: "Update",
|
|
||||||
Item: stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray(cs1.Hash.BytesBE())}),
|
|
||||||
}})
|
|
||||||
t.Run("check contract", func(t *testing.T) {
|
|
||||||
checkContractState(t, bc, cs1.Hash, cs1)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
cs1.NEF.Script = append(cs1.NEF.Script, byte(opcode.ABORT))
|
|
||||||
cs1.NEF.Checksum = cs1.NEF.CalculateChecksum()
|
|
||||||
nef1b, err = cs1.NEF.Bytes()
|
|
||||||
require.NoError(t, err)
|
|
||||||
cs1.Manifest.Extra = []byte(`"update me once more"`)
|
|
||||||
manif1, err = json.Marshal(cs1.Manifest)
|
|
||||||
require.NoError(t, err)
|
|
||||||
cs1.UpdateCounter++
|
|
||||||
|
|
||||||
t.Run("update both script and manifest", func(t *testing.T) {
|
|
||||||
res, err := invokeContractMethod(bc, 10_00000000, cs1.Hash, "update", nef1b, manif1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, vm.HaltState, res.VMState)
|
|
||||||
require.Equal(t, res.Events, []state.NotificationEvent{{
|
|
||||||
ScriptHash: mgmtHash,
|
|
||||||
Name: "Update",
|
|
||||||
Item: stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray(cs1.Hash.BytesBE())}),
|
|
||||||
}})
|
|
||||||
t.Run("check contract", func(t *testing.T) {
|
|
||||||
checkContractState(t, bc, cs1.Hash, cs1)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetContract(t *testing.T) {
|
|
||||||
bc := newTestChain(t)
|
|
||||||
|
|
||||||
mgmtHash := bc.ManagementContractHash()
|
|
||||||
cs1, _ := getTestContractState(bc)
|
|
||||||
err := bc.contracts.Management.PutContractState(bc.dao, cs1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
t.Run("bad parameter type", func(t *testing.T) {
|
|
||||||
res, err := invokeContractMethod(bc, 1_00000000, mgmtHash, "getContract", []interface{}{int64(1)})
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkFAULTState(t, res)
|
|
||||||
})
|
|
||||||
t.Run("not a hash", func(t *testing.T) {
|
|
||||||
res, err := invokeContractMethod(bc, 1_00000000, mgmtHash, "getContract", []byte{1, 2, 3})
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkFAULTState(t, res)
|
|
||||||
})
|
|
||||||
t.Run("positive", func(t *testing.T) {
|
|
||||||
res, err := invokeContractMethod(bc, 1_00000000, mgmtHash, "getContract", cs1.Hash.BytesBE())
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, 1, len(res.Stack))
|
|
||||||
compareContractStates(t, cs1, res.Stack[0])
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestContractDestroy(t *testing.T) {
|
|
||||||
bc := newTestChain(t)
|
|
||||||
|
|
||||||
mgmtHash := bc.ManagementContractHash()
|
|
||||||
cs1, _ := getTestContractState(bc)
|
|
||||||
// Allow calling management contract.
|
|
||||||
cs1.Manifest.Permissions = []manifest.Permission{*manifest.NewPermission(manifest.PermissionWildcard)}
|
|
||||||
err := bc.contracts.Management.PutContractState(bc.dao, cs1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
err = bc.dao.PutStorageItem(cs1.ID, []byte{1, 2, 3}, state.StorageItem{3, 2, 1})
|
|
||||||
require.NoError(t, err)
|
|
||||||
b := bc.dao.GetMPTBatch()
|
|
||||||
_, _, err = bc.GetStateModule().(*stateroot.Module).AddMPTBatch(bc.BlockHeight(), b, bc.dao.Store)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
t.Run("no contract", func(t *testing.T) {
|
|
||||||
res, err := invokeContractMethod(bc, 1_00000000, mgmtHash, "destroy")
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkFAULTState(t, res)
|
|
||||||
})
|
|
||||||
t.Run("positive", func(t *testing.T) {
|
|
||||||
res, err := invokeContractMethod(bc, 1_00000000, cs1.Hash, "destroy")
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, vm.HaltState, res.VMState)
|
|
||||||
require.Equal(t, res.Events, []state.NotificationEvent{{
|
|
||||||
ScriptHash: mgmtHash,
|
|
||||||
Name: "Destroy",
|
|
||||||
Item: stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray(cs1.Hash.BytesBE())}),
|
|
||||||
}})
|
|
||||||
t.Run("check contract", func(t *testing.T) {
|
|
||||||
checkContractState(t, bc, cs1.Hash, nil)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func compareContractStates(t *testing.T, expected *state.Contract, actual stackitem.Item) {
|
|
||||||
act, ok := actual.Value().([]stackitem.Item)
|
|
||||||
require.True(t, ok)
|
|
||||||
|
|
||||||
expectedManifest, err := expected.Manifest.ToStackItem()
|
|
||||||
require.NoError(t, err)
|
|
||||||
expectedNef, err := expected.NEF.Bytes()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, 5, len(act))
|
|
||||||
require.Equal(t, expected.ID, int32(act[0].Value().(*big.Int).Int64()))
|
|
||||||
require.Equal(t, expected.UpdateCounter, uint16(act[1].Value().(*big.Int).Int64()))
|
|
||||||
require.Equal(t, expected.Hash.BytesBE(), act[2].Value().([]byte))
|
|
||||||
require.Equal(t, expectedNef, act[3].Value().([]byte))
|
|
||||||
require.Equal(t, expectedManifest, act[4])
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMinimumDeploymentFee(t *testing.T) {
|
func TestMinimumDeploymentFee(t *testing.T) {
|
||||||
chain := newTestChain(t)
|
chain := newTestChain(t)
|
||||||
|
|
||||||
|
@ -619,8 +21,6 @@ func TestMinimumDeploymentFee(t *testing.T) {
|
||||||
n := chain.contracts.Management.GetMinimumDeploymentFee(chain.dao)
|
n := chain.contracts.Management.GetMinimumDeploymentFee(chain.dao)
|
||||||
require.Equal(t, 10_00000000, int(n))
|
require.Equal(t, 10_00000000, int(n))
|
||||||
})
|
})
|
||||||
|
|
||||||
testGetSet(t, chain, chain.contracts.Management.Hash, "MinimumDeploymentFee", 10_00000000, 0, 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestManagement_GetNEP17Contracts(t *testing.T) {
|
func TestManagement_GetNEP17Contracts(t *testing.T) {
|
||||||
|
|
|
@ -2,483 +2,23 @@ package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
|
||||||
"math/big"
|
"math/big"
|
||||||
"sort"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/internal/random"
|
|
||||||
"github.com/nspcc-dev/neo-go/internal/testchain"
|
"github.com/nspcc-dev/neo-go/internal/testchain"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"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/crypto/keys"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
"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/emit"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func setSigner(tx *transaction.Transaction, h util.Uint160) {
|
|
||||||
tx.Signers = []transaction.Signer{{
|
|
||||||
Account: h,
|
|
||||||
Scopes: transaction.Global,
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkTxHalt(t testing.TB, bc *Blockchain, h util.Uint256) {
|
|
||||||
aer, err := bc.GetAppExecResults(h, trigger.Application)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, 1, len(aer))
|
|
||||||
require.Equal(t, vm.HaltState, aer[0].VMState, aer[0].FaultException)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNEO_Vote(t *testing.T) {
|
|
||||||
bc := newTestChain(t)
|
|
||||||
|
|
||||||
neo := bc.contracts.NEO
|
|
||||||
tx := transaction.New([]byte{byte(opcode.PUSH1)}, 0)
|
|
||||||
ic := bc.newInteropContext(trigger.Application, bc.dao, nil, tx)
|
|
||||||
ic.SpawnVM()
|
|
||||||
ic.Block = bc.newBlock(tx)
|
|
||||||
|
|
||||||
freq := testchain.ValidatorsCount + testchain.CommitteeSize()
|
|
||||||
advanceChain := func(t *testing.T) {
|
|
||||||
for i := 0; i < freq; i++ {
|
|
||||||
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
|
||||||
ic.Block.Index++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
standBySorted := bc.GetStandByValidators()
|
|
||||||
sort.Sort(standBySorted)
|
|
||||||
pubs, err := neo.ComputeNextBlockValidators(bc, ic.DAO)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, standBySorted, pubs)
|
|
||||||
|
|
||||||
sz := testchain.CommitteeSize()
|
|
||||||
accs := make([]*wallet.Account, sz)
|
|
||||||
candidates := make(keys.PublicKeys, sz)
|
|
||||||
txs := make([]*transaction.Transaction, 0, len(accs))
|
|
||||||
for i := 0; i < sz; i++ {
|
|
||||||
priv, err := keys.NewPrivateKey()
|
|
||||||
require.NoError(t, err)
|
|
||||||
candidates[i] = priv.PublicKey()
|
|
||||||
accs[i], err = wallet.NewAccount()
|
|
||||||
require.NoError(t, err)
|
|
||||||
if i > 0 {
|
|
||||||
require.NoError(t, neo.RegisterCandidateInternal(ic, candidates[i]))
|
|
||||||
}
|
|
||||||
|
|
||||||
to := accs[i].Contract.ScriptHash()
|
|
||||||
w := io.NewBufBinWriter()
|
|
||||||
emit.AppCall(w.BinWriter, bc.contracts.NEO.Hash, "transfer", callflag.All,
|
|
||||||
neoOwner.BytesBE(), to.BytesBE(),
|
|
||||||
big.NewInt(int64(sz-i)*1000000).Int64(), nil)
|
|
||||||
emit.Opcodes(w.BinWriter, opcode.ASSERT)
|
|
||||||
emit.AppCall(w.BinWriter, bc.contracts.GAS.Hash, "transfer", callflag.All,
|
|
||||||
neoOwner.BytesBE(), to.BytesBE(),
|
|
||||||
int64(1_000_000_000), nil)
|
|
||||||
emit.Opcodes(w.BinWriter, opcode.ASSERT)
|
|
||||||
require.NoError(t, w.Err)
|
|
||||||
tx := transaction.New(w.Bytes(), 1000_000_000)
|
|
||||||
tx.ValidUntilBlock = bc.BlockHeight() + 1
|
|
||||||
setSigner(tx, testchain.MultisigScriptHash())
|
|
||||||
require.NoError(t, testchain.SignTx(bc, tx))
|
|
||||||
txs = append(txs, tx)
|
|
||||||
}
|
|
||||||
require.NoError(t, bc.AddBlock(bc.newBlock(txs...)))
|
|
||||||
for _, tx := range txs {
|
|
||||||
checkTxHalt(t, bc, tx.Hash())
|
|
||||||
}
|
|
||||||
transferBlock := bc.BlockHeight()
|
|
||||||
|
|
||||||
for i := 1; i < sz; i++ {
|
|
||||||
priv := accs[i].PrivateKey()
|
|
||||||
h := priv.GetScriptHash()
|
|
||||||
setSigner(tx, h)
|
|
||||||
ic.VM.Load(priv.PublicKey().GetVerificationScript())
|
|
||||||
require.NoError(t, neo.VoteInternal(ic, h, candidates[i]))
|
|
||||||
}
|
|
||||||
|
|
||||||
// We still haven't voted enough validators in.
|
|
||||||
pubs, err = neo.ComputeNextBlockValidators(bc, ic.DAO)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, standBySorted, pubs)
|
|
||||||
|
|
||||||
advanceChain(t)
|
|
||||||
pubs = neo.GetNextBlockValidatorsInternal()
|
|
||||||
require.EqualValues(t, standBySorted, pubs)
|
|
||||||
|
|
||||||
// Register and give some value to the last validator.
|
|
||||||
require.NoError(t, neo.RegisterCandidateInternal(ic, candidates[0]))
|
|
||||||
priv := accs[0].PrivateKey()
|
|
||||||
h := priv.GetScriptHash()
|
|
||||||
setSigner(tx, h)
|
|
||||||
ic.VM.Load(priv.PublicKey().GetVerificationScript())
|
|
||||||
require.NoError(t, neo.VoteInternal(ic, h, candidates[0]))
|
|
||||||
|
|
||||||
_, err = ic.DAO.Persist()
|
|
||||||
require.NoError(t, err)
|
|
||||||
advanceChain(t)
|
|
||||||
pubs, err = neo.ComputeNextBlockValidators(bc, ic.DAO)
|
|
||||||
require.NoError(t, err)
|
|
||||||
sortedCandidates := candidates.Copy()[:testchain.Size()]
|
|
||||||
sort.Sort(sortedCandidates)
|
|
||||||
require.EqualValues(t, sortedCandidates, pubs)
|
|
||||||
|
|
||||||
pubs = neo.GetNextBlockValidatorsInternal()
|
|
||||||
require.EqualValues(t, sortedCandidates, pubs)
|
|
||||||
|
|
||||||
t.Run("check voter rewards", func(t *testing.T) {
|
|
||||||
gasBalance := make([]*big.Int, len(accs))
|
|
||||||
neoBalance := make([]*big.Int, len(accs))
|
|
||||||
txs := make([]*transaction.Transaction, 0, len(accs))
|
|
||||||
for i := range accs {
|
|
||||||
w := io.NewBufBinWriter()
|
|
||||||
h := accs[i].PrivateKey().GetScriptHash()
|
|
||||||
gasBalance[i] = bc.GetUtilityTokenBalance(h)
|
|
||||||
neoBalance[i], _ = bc.GetGoverningTokenBalance(h)
|
|
||||||
emit.AppCall(w.BinWriter, bc.contracts.NEO.Hash, "transfer", callflag.All,
|
|
||||||
h.BytesBE(), h.BytesBE(), int64(1), nil)
|
|
||||||
emit.Opcodes(w.BinWriter, opcode.ASSERT)
|
|
||||||
require.NoError(t, w.Err)
|
|
||||||
tx := transaction.New(w.Bytes(), 0)
|
|
||||||
tx.ValidUntilBlock = bc.BlockHeight() + 1
|
|
||||||
tx.NetworkFee = 2_000_000
|
|
||||||
tx.SystemFee = 11_000_000
|
|
||||||
setSigner(tx, h)
|
|
||||||
require.NoError(t, accs[i].SignTx(netmode.UnitTestNet, tx))
|
|
||||||
txs = append(txs, tx)
|
|
||||||
}
|
|
||||||
require.NoError(t, bc.AddBlock(bc.newBlock(txs...)))
|
|
||||||
for _, tx := range txs {
|
|
||||||
checkTxHalt(t, bc, tx.Hash())
|
|
||||||
}
|
|
||||||
|
|
||||||
// GAS increase consists of 2 parts: NEO holding + voting for committee nodes.
|
|
||||||
// Here we check that 2-nd part exists and is proportional to the amount of NEO given.
|
|
||||||
for i := range accs {
|
|
||||||
newGAS := bc.GetUtilityTokenBalance(accs[i].Contract.ScriptHash())
|
|
||||||
newGAS.Sub(newGAS, gasBalance[i])
|
|
||||||
|
|
||||||
gasForHold, err := bc.contracts.NEO.CalculateNEOHolderReward(bc.dao, neoBalance[i], transferBlock, bc.BlockHeight())
|
|
||||||
require.NoError(t, err)
|
|
||||||
newGAS.Sub(newGAS, gasForHold)
|
|
||||||
require.True(t, newGAS.Sign() > 0)
|
|
||||||
gasBalance[i] = newGAS
|
|
||||||
}
|
|
||||||
// First account voted later than the others.
|
|
||||||
require.Equal(t, -1, gasBalance[0].Cmp(gasBalance[1]))
|
|
||||||
for i := 2; i < testchain.ValidatorsCount; i++ {
|
|
||||||
require.Equal(t, 0, gasBalance[i].Cmp(gasBalance[1]))
|
|
||||||
}
|
|
||||||
require.Equal(t, 1, gasBalance[1].Cmp(gasBalance[testchain.ValidatorsCount]))
|
|
||||||
for i := testchain.ValidatorsCount; i < testchain.CommitteeSize(); i++ {
|
|
||||||
require.Equal(t, 0, gasBalance[i].Cmp(gasBalance[testchain.ValidatorsCount]))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
require.NoError(t, neo.UnregisterCandidateInternal(ic, candidates[0]))
|
|
||||||
require.Error(t, neo.VoteInternal(ic, h, candidates[0]))
|
|
||||||
advanceChain(t)
|
|
||||||
|
|
||||||
pubs, err = neo.ComputeNextBlockValidators(bc, ic.DAO)
|
|
||||||
require.NoError(t, err)
|
|
||||||
for i := range pubs {
|
|
||||||
require.NotEqual(t, candidates[0], pubs[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestNEO_RecursiveDistribution is a test for https://github.com/nspcc-dev/neo-go/pull/2181.
|
|
||||||
func TestNEO_RecursiveGASMint(t *testing.T) {
|
|
||||||
bc := newTestChain(t)
|
|
||||||
initBasicChain(t, bc)
|
|
||||||
|
|
||||||
contractHash, err := bc.GetContractScriptHash(1) // deployed rpc/server/testdata/test_contract.go contract
|
|
||||||
require.NoError(t, err)
|
|
||||||
tx := transferTokenFromMultisigAccount(t, bc, contractHash, bc.contracts.GAS.Hash, 2_0000_0000)
|
|
||||||
checkTxHalt(t, bc, tx.Hash())
|
|
||||||
|
|
||||||
// Transfer 10 NEO to test contract, the contract should earn some GAS by owning this NEO.
|
|
||||||
tx = transferTokenFromMultisigAccount(t, bc, contractHash, bc.contracts.NEO.Hash, 10)
|
|
||||||
res, err := bc.GetAppExecResults(tx.Hash(), trigger.Application)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, vm.HaltState, res[0].VMState)
|
|
||||||
|
|
||||||
// Add blocks to be able to trigger NEO transfer from contract address to owner
|
|
||||||
// address inside onNEP17Payment (the contract starts NEO transfers from chain height = 100).
|
|
||||||
for i := bc.BlockHeight(); i < 100; i++ {
|
|
||||||
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transfer 1 more NEO to the contract. Transfer will trigger onNEP17Payment. OnNEP17Payment will
|
|
||||||
// trigger transfer of 11 NEO to the contract owner (based on the contract code). 11 NEO Transfer will
|
|
||||||
// trigger GAS distribution. GAS transfer will trigger OnNEP17Payment one more time. The recursion
|
|
||||||
// shouldn't occur here, because contract's balance LastUpdated height has already been updated in
|
|
||||||
// this block.
|
|
||||||
tx = transferTokenFromMultisigAccount(t, bc, contractHash, bc.contracts.NEO.Hash, 1)
|
|
||||||
res, err = bc.GetAppExecResults(tx.Hash(), trigger.Application)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, vm.HaltState, res[0].VMState, res[0].FaultException)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNEO_SetGasPerBlock(t *testing.T) {
|
|
||||||
bc := newTestChain(t)
|
|
||||||
|
|
||||||
testGetSet(t, bc, bc.contracts.NEO.Hash, "GasPerBlock",
|
|
||||||
5*native.GASFactor, 0, 10*native.GASFactor)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNEO_CalculateBonus(t *testing.T) {
|
|
||||||
bc := newTestChain(t)
|
|
||||||
|
|
||||||
neo := bc.contracts.NEO
|
|
||||||
tx := transaction.New([]byte{byte(opcode.PUSH1)}, 0)
|
|
||||||
ic := bc.newInteropContext(trigger.Application, bc.dao, nil, tx)
|
|
||||||
ic.SpawnVM()
|
|
||||||
ic.VM.LoadScript([]byte{byte(opcode.RET)})
|
|
||||||
t.Run("Invalid", func(t *testing.T) {
|
|
||||||
_, err := neo.CalculateNEOHolderReward(ic.DAO, new(big.Int).SetInt64(-1), 0, 1)
|
|
||||||
require.Error(t, err)
|
|
||||||
})
|
|
||||||
t.Run("Zero", func(t *testing.T) {
|
|
||||||
res, err := neo.CalculateNEOHolderReward(ic.DAO, big.NewInt(0), 0, 100)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.EqualValues(t, 0, res.Int64())
|
|
||||||
})
|
|
||||||
t.Run("ManyBlocks", func(t *testing.T) {
|
|
||||||
setSigner(tx, neo.GetCommitteeAddress())
|
|
||||||
err := neo.SetGASPerBlock(ic, 10, big.NewInt(1*native.GASFactor))
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
res, err := neo.CalculateNEOHolderReward(ic.DAO, big.NewInt(100), 5, 15)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.EqualValues(t, (100*5*5/10)+(100*5*1/10), res.Int64())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNEO_GetAccountState(t *testing.T) {
|
|
||||||
bc := newTestChain(t)
|
|
||||||
|
|
||||||
acc, err := wallet.NewAccount()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
h := acc.Contract.ScriptHash()
|
|
||||||
t.Run("empty", func(t *testing.T) {
|
|
||||||
res, err := invokeContractMethod(bc, 1_0000000, bc.contracts.NEO.Hash, "getAccountState", h)
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, res, stackitem.Null{})
|
|
||||||
})
|
|
||||||
|
|
||||||
const amount = 123
|
|
||||||
transferTokenFromMultisigAccountCheckOK(t, bc, h, bc.GoverningTokenHash(), int64(amount))
|
|
||||||
|
|
||||||
t.Run("with funds", func(t *testing.T) {
|
|
||||||
bs := stackitem.NewStruct([]stackitem.Item{
|
|
||||||
stackitem.Make(123),
|
|
||||||
stackitem.Make(bc.BlockHeight()),
|
|
||||||
stackitem.Null{},
|
|
||||||
})
|
|
||||||
res, err := invokeContractMethod(bc, 1_0000000, bc.contracts.NEO.Hash, "getAccountState", h)
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, res, bs)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNEO_CommitteeBountyOnPersist(t *testing.T) {
|
|
||||||
bc := newTestChain(t)
|
|
||||||
|
|
||||||
hs := make([]util.Uint160, testchain.CommitteeSize())
|
|
||||||
for i := range hs {
|
|
||||||
hs[i] = testchain.PrivateKeyByID(i).GetScriptHash()
|
|
||||||
}
|
|
||||||
|
|
||||||
const singleBounty = 50000000
|
|
||||||
bs := map[int]int64{0: singleBounty}
|
|
||||||
checkBalances := func() {
|
|
||||||
for i := 0; i < testchain.CommitteeSize(); i++ {
|
|
||||||
require.EqualValues(t, bs[i], bc.GetUtilityTokenBalance(hs[i]).Int64(), i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i := 0; i < testchain.CommitteeSize()*2; i++ {
|
|
||||||
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
|
||||||
bs[(i+1)%testchain.CommitteeSize()] += singleBounty
|
|
||||||
checkBalances()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNEO_TransferOnPayment(t *testing.T) {
|
|
||||||
bc := newTestChain(t)
|
|
||||||
|
|
||||||
cs, _ := getTestContractState(bc)
|
|
||||||
require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs))
|
|
||||||
|
|
||||||
const amount = 2
|
|
||||||
tx := transferTokenFromMultisigAccount(t, bc, cs.Hash, bc.contracts.NEO.Hash, amount)
|
|
||||||
aer, err := bc.GetAppExecResults(tx.Hash(), trigger.Application)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, vm.HaltState, aer[0].VMState)
|
|
||||||
require.Len(t, aer[0].Events, 3) // transfer + GAS claim for sender + onPayment
|
|
||||||
|
|
||||||
e := aer[0].Events[2]
|
|
||||||
require.Equal(t, "LastPayment", e.Name)
|
|
||||||
arr := e.Item.Value().([]stackitem.Item)
|
|
||||||
require.Equal(t, bc.contracts.NEO.Hash.BytesBE(), arr[0].Value())
|
|
||||||
require.Equal(t, neoOwner.BytesBE(), arr[1].Value())
|
|
||||||
require.Equal(t, big.NewInt(amount), arr[2].Value())
|
|
||||||
|
|
||||||
tx = transferTokenFromMultisigAccount(t, bc, cs.Hash, bc.contracts.NEO.Hash, amount)
|
|
||||||
aer, err = bc.GetAppExecResults(tx.Hash(), trigger.Application)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, vm.HaltState, aer[0].VMState)
|
|
||||||
// Now we must also have GAS claim for contract and corresponding `onPayment`.
|
|
||||||
require.Len(t, aer[0].Events, 5)
|
|
||||||
|
|
||||||
e = aer[0].Events[2] // onPayment for GAS claim
|
|
||||||
require.Equal(t, "LastPayment", e.Name)
|
|
||||||
arr = e.Item.Value().([]stackitem.Item)
|
|
||||||
require.Equal(t, stackitem.Null{}, arr[1])
|
|
||||||
require.Equal(t, bc.contracts.GAS.Hash.BytesBE(), arr[0].Value())
|
|
||||||
|
|
||||||
e = aer[0].Events[4] // onPayment for NEO transfer
|
|
||||||
require.Equal(t, "LastPayment", e.Name)
|
|
||||||
arr = e.Item.Value().([]stackitem.Item)
|
|
||||||
require.Equal(t, bc.contracts.NEO.Hash.BytesBE(), arr[0].Value())
|
|
||||||
require.Equal(t, neoOwner.BytesBE(), arr[1].Value())
|
|
||||||
require.Equal(t, big.NewInt(amount), arr[2].Value())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRegisterPrice(t *testing.T) {
|
|
||||||
bc := newTestChain(t)
|
|
||||||
testGetSet(t, bc, bc.contracts.NEO.Hash, "RegisterPrice",
|
|
||||||
native.DefaultRegisterPrice, 1, math.MaxInt64)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNEO_Roundtrip(t *testing.T) {
|
|
||||||
bc := newTestChain(t)
|
|
||||||
initialBalance, initialHeight := bc.GetGoverningTokenBalance(neoOwner)
|
|
||||||
require.NotNil(t, initialBalance)
|
|
||||||
|
|
||||||
t.Run("bad: amount > initial balance", func(t *testing.T) {
|
|
||||||
tx := transferTokenFromMultisigAccountWithAssert(t, bc, neoOwner, bc.contracts.NEO.Hash, initialBalance.Int64()+1, false)
|
|
||||||
aer, err := bc.GetAppExecResults(tx.Hash(), trigger.Application)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, vm.HaltState, aer[0].VMState, aer[0].FaultException) // transfer without assert => HALT state
|
|
||||||
checkResult(t, &aer[0], stackitem.NewBool(false))
|
|
||||||
require.Len(t, aer[0].Events, 0) // failed transfer => no events
|
|
||||||
// check balance and height were not changed
|
|
||||||
updatedBalance, updatedHeight := bc.GetGoverningTokenBalance(neoOwner)
|
|
||||||
require.Equal(t, initialBalance, updatedBalance)
|
|
||||||
require.Equal(t, initialHeight, updatedHeight)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("good: amount == initial balance", func(t *testing.T) {
|
|
||||||
tx := transferTokenFromMultisigAccountWithAssert(t, bc, neoOwner, bc.contracts.NEO.Hash, initialBalance.Int64(), false)
|
|
||||||
aer, err := bc.GetAppExecResults(tx.Hash(), trigger.Application)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, vm.HaltState, aer[0].VMState, aer[0].FaultException)
|
|
||||||
checkResult(t, &aer[0], stackitem.NewBool(true))
|
|
||||||
require.Len(t, aer[0].Events, 2) // roundtrip + GAS claim
|
|
||||||
// check balance wasn't changed and height was updated
|
|
||||||
updatedBalance, updatedHeight := bc.GetGoverningTokenBalance(neoOwner)
|
|
||||||
require.Equal(t, initialBalance, updatedBalance)
|
|
||||||
require.Equal(t, bc.BlockHeight(), updatedHeight)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNEO_TransferZeroWithZeroBalance(t *testing.T) {
|
|
||||||
bc := newTestChain(t)
|
|
||||||
|
|
||||||
check := func(t *testing.T, roundtrip bool) {
|
|
||||||
acc := newAccountWithGAS(t, bc)
|
|
||||||
from := acc.PrivateKey().GetScriptHash()
|
|
||||||
to := from
|
|
||||||
if !roundtrip {
|
|
||||||
to = random.Uint160()
|
|
||||||
}
|
|
||||||
transferTx := newNEP17TransferWithAssert(bc.contracts.NEO.Hash, acc.PrivateKey().GetScriptHash(), to, 0, true)
|
|
||||||
transferTx.SystemFee = 100000000
|
|
||||||
transferTx.NetworkFee = 10000000
|
|
||||||
transferTx.ValidUntilBlock = bc.BlockHeight() + 1
|
|
||||||
addSigners(acc.PrivateKey().GetScriptHash(), transferTx)
|
|
||||||
require.NoError(t, acc.SignTx(bc.config.Magic, transferTx))
|
|
||||||
b := bc.newBlock(transferTx)
|
|
||||||
require.NoError(t, bc.AddBlock(b))
|
|
||||||
|
|
||||||
aer, err := bc.GetAppExecResults(transferTx.Hash(), trigger.Application)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, vm.HaltState, aer[0].VMState, aer[0].FaultException)
|
|
||||||
require.Len(t, aer[0].Events, 1) // roundtrip only, no GAS claim
|
|
||||||
require.Equal(t, stackitem.NewBigInteger(big.NewInt(0)), aer[0].Events[0].Item.Value().([]stackitem.Item)[2]) // amount is 0
|
|
||||||
// check balance wasn't changed and height wasn't updated
|
|
||||||
updatedBalance, updatedHeight := bc.GetGoverningTokenBalance(acc.PrivateKey().GetScriptHash())
|
|
||||||
require.Equal(t, big.NewInt(0), updatedBalance)
|
|
||||||
require.Equal(t, uint32(0), updatedHeight)
|
|
||||||
}
|
|
||||||
t.Run("roundtrip: amount == initial balance == 0", func(t *testing.T) {
|
|
||||||
check(t, true)
|
|
||||||
})
|
|
||||||
t.Run("non-roundtrip: amount == initial balance == 0", func(t *testing.T) {
|
|
||||||
check(t, false)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNEO_TransferZeroWithNonZeroBalance(t *testing.T) {
|
|
||||||
bc := newTestChain(t)
|
|
||||||
|
|
||||||
check := func(t *testing.T, roundtrip bool) {
|
|
||||||
acc := newAccountWithGAS(t, bc)
|
|
||||||
transferTokenFromMultisigAccount(t, bc, acc.PrivateKey().GetScriptHash(), bc.contracts.NEO.Hash, 100)
|
|
||||||
initialBalance, _ := bc.GetGoverningTokenBalance(acc.PrivateKey().GetScriptHash())
|
|
||||||
require.True(t, initialBalance.Sign() > 0)
|
|
||||||
|
|
||||||
from := acc.PrivateKey().GetScriptHash()
|
|
||||||
to := from
|
|
||||||
if !roundtrip {
|
|
||||||
to = random.Uint160()
|
|
||||||
}
|
|
||||||
transferTx := newNEP17TransferWithAssert(bc.contracts.NEO.Hash, acc.PrivateKey().GetScriptHash(), to, 0, true)
|
|
||||||
transferTx.SystemFee = 100000000
|
|
||||||
transferTx.NetworkFee = 10000000
|
|
||||||
transferTx.ValidUntilBlock = bc.BlockHeight() + 1
|
|
||||||
addSigners(acc.PrivateKey().GetScriptHash(), transferTx)
|
|
||||||
require.NoError(t, acc.SignTx(bc.config.Magic, transferTx))
|
|
||||||
b := bc.newBlock(transferTx)
|
|
||||||
require.NoError(t, bc.AddBlock(b))
|
|
||||||
|
|
||||||
aer, err := bc.GetAppExecResults(transferTx.Hash(), trigger.Application)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, vm.HaltState, aer[0].VMState, aer[0].FaultException)
|
|
||||||
require.Len(t, aer[0].Events, 2) // roundtrip + GAS claim
|
|
||||||
require.Equal(t, stackitem.NewBigInteger(big.NewInt(0)), aer[0].Events[1].Item.Value().([]stackitem.Item)[2]) // amount is 0
|
|
||||||
// check balance wasn't changed and height was updated
|
|
||||||
updatedBalance, updatedHeight := bc.GetGoverningTokenBalance(acc.PrivateKey().GetScriptHash())
|
|
||||||
require.Equal(t, initialBalance, updatedBalance)
|
|
||||||
require.Equal(t, bc.BlockHeight(), updatedHeight)
|
|
||||||
}
|
|
||||||
t.Run("roundtrip", func(t *testing.T) {
|
|
||||||
check(t, true)
|
|
||||||
})
|
|
||||||
t.Run("non-roundtrip", func(t *testing.T) {
|
|
||||||
check(t, false)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func newAccountWithGAS(t *testing.T, bc *Blockchain) *wallet.Account {
|
|
||||||
acc, err := wallet.NewAccount()
|
|
||||||
require.NoError(t, err)
|
|
||||||
transferTokenFromMultisigAccount(t, bc, acc.PrivateKey().GetScriptHash(), bc.contracts.GAS.Hash, 1000_00000000)
|
|
||||||
return acc
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkNEO_GetGASPerVote(t *testing.B) {
|
func BenchmarkNEO_GetGASPerVote(t *testing.B) {
|
||||||
var stores = map[string]func(testing.TB) storage.Store{
|
var stores = map[string]func(testing.TB) storage.Store{
|
||||||
"MemPS": func(t testing.TB) storage.Store {
|
"MemPS": func(t testing.TB) storage.Store {
|
||||||
|
|
|
@ -1,330 +0,0 @@
|
||||||
package core
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/internal/testchain"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
|
||||||
"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/io"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
|
||||||
"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/vm/stackitem"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNotaryContractPipeline(t *testing.T) {
|
|
||||||
chain := newTestChain(t)
|
|
||||||
|
|
||||||
notaryHash := chain.contracts.Notary.Hash
|
|
||||||
gasHash := chain.contracts.GAS.Hash
|
|
||||||
depositLock := 100
|
|
||||||
|
|
||||||
transferFundsToCommittee(t, chain)
|
|
||||||
|
|
||||||
// check Notary contract has no GAS on the account
|
|
||||||
checkBalanceOf(t, chain, notaryHash, 0)
|
|
||||||
|
|
||||||
// `balanceOf`: check multisig account has no GAS on deposit
|
|
||||||
balance, err := invokeContractMethod(chain, 100000000, notaryHash, "balanceOf", testchain.MultisigScriptHash())
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, balance, stackitem.Make(0))
|
|
||||||
|
|
||||||
// `expirationOf`: should fail to get deposit which does not exist
|
|
||||||
till, err := invokeContractMethod(chain, 100000000, notaryHash, "expirationOf", testchain.MultisigScriptHash())
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, till, stackitem.Make(0))
|
|
||||||
|
|
||||||
// `lockDepositUntil`: should fail because there's no deposit
|
|
||||||
lockDepositUntilRes, err := invokeContractMethod(chain, 100000000, notaryHash, "lockDepositUntil", testchain.MultisigScriptHash(), int64(depositLock+1))
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, lockDepositUntilRes, stackitem.NewBool(false))
|
|
||||||
|
|
||||||
// `onPayment`: bad token
|
|
||||||
transferTx := transferTokenFromMultisigAccount(t, chain, notaryHash, chain.contracts.NEO.Hash, 1, nil, int64(depositLock))
|
|
||||||
res, err := chain.GetAppExecResults(transferTx.Hash(), trigger.Application)
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkFAULTState(t, &res[0])
|
|
||||||
|
|
||||||
// `onPayment`: insufficient first deposit
|
|
||||||
transferTx = transferTokenFromMultisigAccount(t, chain, notaryHash, gasHash, 2*transaction.NotaryServiceFeePerKey-1, nil, int64(depositLock))
|
|
||||||
res, err = chain.GetAppExecResults(transferTx.Hash(), trigger.Application)
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkFAULTState(t, &res[0])
|
|
||||||
|
|
||||||
// `onPayment`: invalid `data` (missing `till` parameter)
|
|
||||||
transferTx = transferTokenFromMultisigAccount(t, chain, notaryHash, gasHash, 2*transaction.NotaryServiceFeePerKey-1, nil)
|
|
||||||
res, err = chain.GetAppExecResults(transferTx.Hash(), trigger.Application)
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkFAULTState(t, &res[0])
|
|
||||||
|
|
||||||
// `onPayment`: good
|
|
||||||
transferTx = transferTokenFromMultisigAccount(t, chain, notaryHash, gasHash, 2*transaction.NotaryServiceFeePerKey, nil, int64(depositLock))
|
|
||||||
res, err = chain.GetAppExecResults(transferTx.Hash(), trigger.Application)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, vm.HaltState, res[0].VMState)
|
|
||||||
require.Equal(t, 0, len(res[0].Stack))
|
|
||||||
checkBalanceOf(t, chain, notaryHash, 2*transaction.NotaryServiceFeePerKey)
|
|
||||||
|
|
||||||
// `expirationOf`: check `till` was set
|
|
||||||
till, err = invokeContractMethod(chain, 100000000, notaryHash, "expirationOf", testchain.MultisigScriptHash())
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, till, stackitem.Make(depositLock))
|
|
||||||
|
|
||||||
// `balanceOf`: check deposited amount for the multisig account
|
|
||||||
balance, err = invokeContractMethod(chain, 100000000, notaryHash, "balanceOf", testchain.MultisigScriptHash())
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, balance, stackitem.Make(2*transaction.NotaryServiceFeePerKey))
|
|
||||||
|
|
||||||
// `onPayment`: good second deposit and explicit `to` paramenter
|
|
||||||
transferTx = transferTokenFromMultisigAccount(t, chain, notaryHash, gasHash, transaction.NotaryServiceFeePerKey, testchain.MultisigScriptHash(), int64(depositLock+1))
|
|
||||||
res, err = chain.GetAppExecResults(transferTx.Hash(), trigger.Application)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, vm.HaltState, res[0].VMState)
|
|
||||||
require.Equal(t, 0, len(res[0].Stack))
|
|
||||||
checkBalanceOf(t, chain, notaryHash, 3*transaction.NotaryServiceFeePerKey)
|
|
||||||
|
|
||||||
// `balanceOf`: check deposited amount for the multisig account
|
|
||||||
balance, err = invokeContractMethod(chain, 100000000, notaryHash, "balanceOf", testchain.MultisigScriptHash())
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, balance, stackitem.Make(3*transaction.NotaryServiceFeePerKey))
|
|
||||||
|
|
||||||
// `expirationOf`: check `till` is updated.
|
|
||||||
till, err = invokeContractMethod(chain, 100000000, notaryHash, "expirationOf", testchain.MultisigScriptHash())
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, till, stackitem.Make(depositLock+1))
|
|
||||||
|
|
||||||
// `onPayment`: empty payment, should fail because `till` less then the previous one
|
|
||||||
transferTx = transferTokenFromMultisigAccount(t, chain, notaryHash, gasHash, 0, testchain.MultisigScriptHash(), int64(depositLock))
|
|
||||||
res, err = chain.GetAppExecResults(transferTx.Hash(), trigger.Application)
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkFAULTState(t, &res[0])
|
|
||||||
checkBalanceOf(t, chain, notaryHash, 3*transaction.NotaryServiceFeePerKey)
|
|
||||||
till, err = invokeContractMethod(chain, 100000000, notaryHash, "expirationOf", testchain.MultisigScriptHash())
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, till, stackitem.Make(depositLock+1))
|
|
||||||
|
|
||||||
// `onPayment`: empty payment, should fail because `till` less then the chain height
|
|
||||||
transferTx = transferTokenFromMultisigAccount(t, chain, notaryHash, gasHash, 0, testchain.MultisigScriptHash(), int64(1))
|
|
||||||
res, err = chain.GetAppExecResults(transferTx.Hash(), trigger.Application)
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkFAULTState(t, &res[0])
|
|
||||||
checkBalanceOf(t, chain, notaryHash, 3*transaction.NotaryServiceFeePerKey)
|
|
||||||
till, err = invokeContractMethod(chain, 100000000, notaryHash, "expirationOf", testchain.MultisigScriptHash())
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, till, stackitem.Make(depositLock+1))
|
|
||||||
|
|
||||||
// `onPayment`: empty payment, should successfully update `till`
|
|
||||||
transferTx = transferTokenFromMultisigAccount(t, chain, notaryHash, gasHash, 0, testchain.MultisigScriptHash(), int64(depositLock+2))
|
|
||||||
res, err = chain.GetAppExecResults(transferTx.Hash(), trigger.Application)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, vm.HaltState, res[0].VMState)
|
|
||||||
require.Equal(t, 0, len(res[0].Stack))
|
|
||||||
checkBalanceOf(t, chain, notaryHash, 3*transaction.NotaryServiceFeePerKey)
|
|
||||||
till, err = invokeContractMethod(chain, 100000000, notaryHash, "expirationOf", testchain.MultisigScriptHash())
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, till, stackitem.Make(depositLock+2))
|
|
||||||
|
|
||||||
// `lockDepositUntil`: bad witness
|
|
||||||
lockDepositUntilRes, err = invokeContractMethod(chain, 100000000, notaryHash, "lockDepositUntil", util.Uint160{1, 2, 3}, int64(depositLock+5))
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, lockDepositUntilRes, stackitem.NewBool(false))
|
|
||||||
till, err = invokeContractMethod(chain, 100000000, notaryHash, "expirationOf", testchain.MultisigScriptHash())
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, till, stackitem.Make(depositLock+2))
|
|
||||||
|
|
||||||
// `lockDepositUntil`: bad `till` (less then the previous one)
|
|
||||||
lockDepositUntilRes, err = invokeContractMethod(chain, 100000000, notaryHash, "lockDepositUntil", testchain.MultisigScriptHash(), int64(depositLock+1))
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, lockDepositUntilRes, stackitem.NewBool(false))
|
|
||||||
till, err = invokeContractMethod(chain, 100000000, notaryHash, "expirationOf", testchain.MultisigScriptHash())
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, till, stackitem.Make(depositLock+2))
|
|
||||||
|
|
||||||
// `lockDepositUntil`: bad `till` (less then the chain's height)
|
|
||||||
lockDepositUntilRes, err = invokeContractMethod(chain, 100000000, notaryHash, "lockDepositUntil", testchain.MultisigScriptHash(), int64(1))
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, lockDepositUntilRes, stackitem.NewBool(false))
|
|
||||||
till, err = invokeContractMethod(chain, 100000000, notaryHash, "expirationOf", testchain.MultisigScriptHash())
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, till, stackitem.Make(depositLock+2))
|
|
||||||
|
|
||||||
// `lockDepositUntil`: good `till`
|
|
||||||
lockDepositUntilRes, err = invokeContractMethod(chain, 100000000, notaryHash, "lockDepositUntil", testchain.MultisigScriptHash(), int64(depositLock+3))
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, lockDepositUntilRes, stackitem.NewBool(true))
|
|
||||||
till, err = invokeContractMethod(chain, 100000000, notaryHash, "expirationOf", testchain.MultisigScriptHash())
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, till, stackitem.Make(depositLock+3))
|
|
||||||
|
|
||||||
// transfer 1 GAS to the new account for the next test
|
|
||||||
acc, _ := wallet.NewAccount()
|
|
||||||
transferTx = transferTokenFromMultisigAccount(t, chain, acc.PrivateKey().PublicKey().GetScriptHash(), gasHash, 100000000)
|
|
||||||
res, err = chain.GetAppExecResults(transferTx.Hash(), trigger.Application)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, vm.HaltState, res[0].VMState)
|
|
||||||
require.Equal(t, 0, len(res[0].Stack))
|
|
||||||
|
|
||||||
// `withdraw`: bad witness
|
|
||||||
w := io.NewBufBinWriter()
|
|
||||||
emit.AppCall(w.BinWriter, notaryHash, "withdraw", callflag.All,
|
|
||||||
testchain.MultisigScriptHash(), acc.PrivateKey().PublicKey().GetScriptHash())
|
|
||||||
require.NoError(t, w.Err)
|
|
||||||
script := w.Bytes()
|
|
||||||
withdrawTx := transaction.New(script, 10000000)
|
|
||||||
withdrawTx.ValidUntilBlock = chain.blockHeight + 1
|
|
||||||
withdrawTx.NetworkFee = 10000000
|
|
||||||
withdrawTx.Signers = []transaction.Signer{
|
|
||||||
{
|
|
||||||
Account: acc.PrivateKey().PublicKey().GetScriptHash(),
|
|
||||||
Scopes: transaction.None,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
err = acc.SignTx(chain.GetConfig().Magic, withdrawTx)
|
|
||||||
require.NoError(t, err)
|
|
||||||
b := chain.newBlock(withdrawTx)
|
|
||||||
err = chain.AddBlock(b)
|
|
||||||
require.NoError(t, err)
|
|
||||||
appExecRes, err := chain.GetAppExecResults(withdrawTx.Hash(), trigger.Application)
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, &appExecRes[0], stackitem.NewBool(false))
|
|
||||||
balance, err = invokeContractMethod(chain, 100000000, notaryHash, "balanceOf", testchain.MultisigScriptHash())
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, balance, stackitem.Make(3*transaction.NotaryServiceFeePerKey))
|
|
||||||
|
|
||||||
// `withdraw`: locked deposit
|
|
||||||
withdrawRes, err := invokeContractMethod(chain, 100000000, notaryHash, "withdraw", testchain.MultisigScriptHash(), acc.PrivateKey().PublicKey().GetScriptHash())
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, withdrawRes, stackitem.NewBool(false))
|
|
||||||
balance, err = invokeContractMethod(chain, 100000000, notaryHash, "balanceOf", testchain.MultisigScriptHash())
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, balance, stackitem.Make(3*transaction.NotaryServiceFeePerKey))
|
|
||||||
|
|
||||||
// `withdraw`: unlock deposit and transfer GAS back to owner
|
|
||||||
_, err = chain.genBlocks(depositLock)
|
|
||||||
require.NoError(t, err)
|
|
||||||
withdrawRes, err = invokeContractMethod(chain, 100000000, notaryHash, "withdraw", testchain.MultisigScriptHash(), testchain.MultisigScriptHash())
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, withdrawRes, stackitem.NewBool(true))
|
|
||||||
balance, err = invokeContractMethod(chain, 100000000, notaryHash, "balanceOf", testchain.MultisigScriptHash())
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, balance, stackitem.Make(0))
|
|
||||||
checkBalanceOf(t, chain, notaryHash, 0)
|
|
||||||
|
|
||||||
// `withdraw`: the second time it should fail, because there's no deposit left
|
|
||||||
withdrawRes, err = invokeContractMethod(chain, 100000000, notaryHash, "withdraw", testchain.MultisigScriptHash(), testchain.MultisigScriptHash())
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, withdrawRes, stackitem.NewBool(false))
|
|
||||||
|
|
||||||
// `onPayment`: good first deposit to other account, should set default `till` even if other `till` value is provided
|
|
||||||
transferTx = transferTokenFromMultisigAccount(t, chain, notaryHash, gasHash, 2*transaction.NotaryServiceFeePerKey, acc.PrivateKey().PublicKey().GetScriptHash(), int64(math.MaxUint32-1))
|
|
||||||
res, err = chain.GetAppExecResults(transferTx.Hash(), trigger.Application)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, vm.HaltState, res[0].VMState)
|
|
||||||
require.Equal(t, 0, len(res[0].Stack))
|
|
||||||
checkBalanceOf(t, chain, notaryHash, 2*transaction.NotaryServiceFeePerKey)
|
|
||||||
till, err = invokeContractMethod(chain, 100000000, notaryHash, "expirationOf", acc.PrivateKey().PublicKey().GetScriptHash())
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, till, stackitem.Make(5760+chain.BlockHeight()-2))
|
|
||||||
|
|
||||||
// `onPayment`: good second deposit to other account, shouldn't update `till` even if other `till` value is provided
|
|
||||||
transferTx = transferTokenFromMultisigAccount(t, chain, notaryHash, gasHash, 2*transaction.NotaryServiceFeePerKey, acc.PrivateKey().PublicKey().GetScriptHash(), int64(math.MaxUint32-1))
|
|
||||||
res, err = chain.GetAppExecResults(transferTx.Hash(), trigger.Application)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, vm.HaltState, res[0].VMState)
|
|
||||||
require.Equal(t, 0, len(res[0].Stack))
|
|
||||||
checkBalanceOf(t, chain, notaryHash, 4*transaction.NotaryServiceFeePerKey)
|
|
||||||
till, err = invokeContractMethod(chain, 100000000, notaryHash, "expirationOf", acc.PrivateKey().PublicKey().GetScriptHash())
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, till, stackitem.Make(5760+chain.BlockHeight()-4))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNotaryNodesReward(t *testing.T) {
|
|
||||||
checkReward := func(nKeys int, nNotaryNodes int, spendFullDeposit bool) {
|
|
||||||
chain := newTestChain(t)
|
|
||||||
notaryHash := chain.contracts.Notary.Hash
|
|
||||||
gasHash := chain.contracts.GAS.Hash
|
|
||||||
signer := testchain.MultisigScriptHash()
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// set Notary nodes and check their balance
|
|
||||||
notaryNodes := make([]*keys.PrivateKey, nNotaryNodes)
|
|
||||||
notaryNodesPublicKeys := make(keys.PublicKeys, nNotaryNodes)
|
|
||||||
for i := range notaryNodes {
|
|
||||||
notaryNodes[i], err = keys.NewPrivateKey()
|
|
||||||
require.NoError(t, err)
|
|
||||||
notaryNodesPublicKeys[i] = notaryNodes[i].PublicKey()
|
|
||||||
}
|
|
||||||
chain.setNodesByRole(t, true, noderoles.P2PNotary, notaryNodesPublicKeys)
|
|
||||||
for _, notaryNode := range notaryNodesPublicKeys {
|
|
||||||
checkBalanceOf(t, chain, notaryNode.GetScriptHash(), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// deposit GAS for `signer` with lock until the next block
|
|
||||||
depositAmount := 100_0000 + (2+int64(nKeys))*transaction.NotaryServiceFeePerKey // sysfee + netfee of the next transaction
|
|
||||||
if !spendFullDeposit {
|
|
||||||
depositAmount += 1_0000
|
|
||||||
}
|
|
||||||
transferTx := transferTokenFromMultisigAccount(t, chain, notaryHash, gasHash, depositAmount, signer, int64(chain.BlockHeight()+1))
|
|
||||||
res, err := chain.GetAppExecResults(transferTx.Hash(), trigger.Application)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, vm.HaltState, res[0].VMState)
|
|
||||||
require.Equal(t, 0, len(res[0].Stack))
|
|
||||||
|
|
||||||
// send transaction with Notary contract as a sender
|
|
||||||
tx := chain.newTestTx(util.Uint160{}, []byte{byte(opcode.PUSH1)})
|
|
||||||
tx.Attributes = append(tx.Attributes, transaction.Attribute{Type: transaction.NotaryAssistedT, Value: &transaction.NotaryAssisted{NKeys: uint8(nKeys)}})
|
|
||||||
tx.NetworkFee = (2 + int64(nKeys)) * transaction.NotaryServiceFeePerKey
|
|
||||||
tx.Signers = []transaction.Signer{
|
|
||||||
{
|
|
||||||
Account: notaryHash,
|
|
||||||
Scopes: transaction.None,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Account: signer,
|
|
||||||
Scopes: transaction.None,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
tx.Scripts = []transaction.Witness{
|
|
||||||
{
|
|
||||||
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, notaryNodes[0].SignHashable(uint32(testchain.Network()), tx)...),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
InvocationScript: testchain.Sign(tx),
|
|
||||||
VerificationScript: testchain.MultisigVerificationScript(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
b := chain.newBlock(tx)
|
|
||||||
require.NoError(t, chain.AddBlock(b))
|
|
||||||
checkBalanceOf(t, chain, notaryHash, int(depositAmount-tx.SystemFee-tx.NetworkFee))
|
|
||||||
for _, notaryNode := range notaryNodesPublicKeys {
|
|
||||||
checkBalanceOf(t, chain, notaryNode.GetScriptHash(), transaction.NotaryServiceFeePerKey*(nKeys+1)/nNotaryNodes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, spendDeposit := range []bool{true, false} {
|
|
||||||
checkReward(0, 1, spendDeposit)
|
|
||||||
checkReward(0, 2, spendDeposit)
|
|
||||||
checkReward(1, 1, spendDeposit)
|
|
||||||
checkReward(1, 2, spendDeposit)
|
|
||||||
checkReward(1, 3, spendDeposit)
|
|
||||||
checkReward(5, 1, spendDeposit)
|
|
||||||
checkReward(5, 2, spendDeposit)
|
|
||||||
checkReward(5, 6, spendDeposit)
|
|
||||||
checkReward(5, 7, spendDeposit)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMaxNotValidBeforeDelta(t *testing.T) {
|
|
||||||
chain := newTestChain(t)
|
|
||||||
|
|
||||||
testGetSet(t, chain, chain.contracts.Notary.Hash, "MaxNotValidBeforeDelta",
|
|
||||||
140, int64(chain.GetConfig().ValidatorsCount), int64(chain.config.MaxValidUntilBlockIncrement/2))
|
|
||||||
}
|
|
|
@ -1,262 +0,0 @@
|
||||||
package core
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"math"
|
|
||||||
"math/big"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/internal/testchain"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
|
||||||
"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/smartcontract"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
|
||||||
"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/vm/stackitem"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
// getTestContractState returns test contract which uses oracles.
|
|
||||||
func getOracleContractState(h util.Uint160, stdHash util.Uint160) *state.Contract {
|
|
||||||
w := io.NewBufBinWriter()
|
|
||||||
emit.Int(w.BinWriter, 5)
|
|
||||||
emit.Opcodes(w.BinWriter, opcode.PACK)
|
|
||||||
emit.Int(w.BinWriter, int64(callflag.All))
|
|
||||||
emit.String(w.BinWriter, "request")
|
|
||||||
emit.Bytes(w.BinWriter, h.BytesBE())
|
|
||||||
emit.Syscall(w.BinWriter, interopnames.SystemContractCall)
|
|
||||||
emit.Opcodes(w.BinWriter, opcode.DROP)
|
|
||||||
emit.Opcodes(w.BinWriter, opcode.RET)
|
|
||||||
|
|
||||||
// `handle` method aborts if len(userData) == 2
|
|
||||||
offset := w.Len()
|
|
||||||
emit.Bytes(w.BinWriter, neoOwner.BytesBE())
|
|
||||||
emit.Syscall(w.BinWriter, interopnames.SystemRuntimeCheckWitness)
|
|
||||||
emit.Instruction(w.BinWriter, opcode.JMPIF, []byte{3})
|
|
||||||
emit.Opcodes(w.BinWriter, opcode.ABORT)
|
|
||||||
|
|
||||||
emit.Opcodes(w.BinWriter, opcode.OVER)
|
|
||||||
emit.Opcodes(w.BinWriter, opcode.SIZE)
|
|
||||||
emit.Int(w.BinWriter, 2)
|
|
||||||
emit.Instruction(w.BinWriter, opcode.JMPNE, []byte{3})
|
|
||||||
emit.Opcodes(w.BinWriter, opcode.ABORT)
|
|
||||||
emit.Int(w.BinWriter, 4) // url, userData, code, result
|
|
||||||
emit.Opcodes(w.BinWriter, opcode.PACK)
|
|
||||||
emit.Int(w.BinWriter, 1) // 1 byte (args count for `serialize`)
|
|
||||||
emit.Opcodes(w.BinWriter, opcode.PACK) // 1 byte (pack args into array for `serialize`)
|
|
||||||
emit.AppCallNoArgs(w.BinWriter, stdHash, "serialize", callflag.All) // 39 bytes
|
|
||||||
emit.String(w.BinWriter, "lastOracleResponse")
|
|
||||||
emit.Syscall(w.BinWriter, interopnames.SystemStorageGetContext)
|
|
||||||
emit.Syscall(w.BinWriter, interopnames.SystemStoragePut)
|
|
||||||
emit.Opcodes(w.BinWriter, opcode.RET)
|
|
||||||
|
|
||||||
m := manifest.NewManifest("TestOracle")
|
|
||||||
m.ABI.Methods = []manifest.Method{
|
|
||||||
{
|
|
||||||
Name: "requestURL",
|
|
||||||
Offset: 0,
|
|
||||||
Parameters: []manifest.Parameter{
|
|
||||||
manifest.NewParameter("url", smartcontract.StringType),
|
|
||||||
manifest.NewParameter("filter", smartcontract.StringType),
|
|
||||||
manifest.NewParameter("callback", smartcontract.StringType),
|
|
||||||
manifest.NewParameter("userData", smartcontract.AnyType),
|
|
||||||
manifest.NewParameter("gasForResponse", smartcontract.IntegerType),
|
|
||||||
},
|
|
||||||
ReturnType: smartcontract.VoidType,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "handle",
|
|
||||||
Offset: offset,
|
|
||||||
Parameters: []manifest.Parameter{
|
|
||||||
manifest.NewParameter("url", smartcontract.StringType),
|
|
||||||
manifest.NewParameter("userData", smartcontract.AnyType),
|
|
||||||
manifest.NewParameter("code", smartcontract.IntegerType),
|
|
||||||
manifest.NewParameter("result", smartcontract.ByteArrayType),
|
|
||||||
},
|
|
||||||
ReturnType: smartcontract.VoidType,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
perm := manifest.NewPermission(manifest.PermissionHash, h)
|
|
||||||
perm.Methods.Add("request")
|
|
||||||
m.Permissions = append(m.Permissions, *perm)
|
|
||||||
|
|
||||||
script := w.Bytes()
|
|
||||||
ne, err := nef.NewFile(script)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return &state.Contract{
|
|
||||||
ContractBase: state.ContractBase{
|
|
||||||
NEF: *ne,
|
|
||||||
Hash: hash.Hash160(script),
|
|
||||||
Manifest: *m,
|
|
||||||
ID: 42,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func putOracleRequest(t *testing.T, h util.Uint160, bc *Blockchain,
|
|
||||||
url string, filter *string, cb string, userData []byte, gas int64) util.Uint256 {
|
|
||||||
var filtItem interface{}
|
|
||||||
if filter != nil {
|
|
||||||
filtItem = *filter
|
|
||||||
}
|
|
||||||
res, err := invokeContractMethod(bc, gas+50_000_000+5_000_000, h, "requestURL",
|
|
||||||
url, filtItem, cb, userData, gas)
|
|
||||||
require.NoError(t, err)
|
|
||||||
return res.Container
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOracle_Request(t *testing.T) {
|
|
||||||
bc := newTestChain(t)
|
|
||||||
|
|
||||||
orc := bc.contracts.Oracle
|
|
||||||
cs := getOracleContractState(orc.Hash, bc.contracts.Std.Hash)
|
|
||||||
require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs))
|
|
||||||
|
|
||||||
gasForResponse := int64(2000_1234)
|
|
||||||
var filter = "flt"
|
|
||||||
userData := []byte("custom info")
|
|
||||||
txHash := putOracleRequest(t, cs.Hash, bc, "url", &filter, "handle", userData, gasForResponse)
|
|
||||||
|
|
||||||
req, err := orc.GetRequestInternal(bc.dao, 0)
|
|
||||||
require.NotNil(t, req)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, txHash, req.OriginalTxID)
|
|
||||||
require.Equal(t, "url", req.URL)
|
|
||||||
require.Equal(t, filter, *req.Filter)
|
|
||||||
require.Equal(t, cs.Hash, req.CallbackContract)
|
|
||||||
require.Equal(t, "handle", req.CallbackMethod)
|
|
||||||
require.Equal(t, uint64(gasForResponse), req.GasForResponse)
|
|
||||||
|
|
||||||
idList, err := orc.GetIDListInternal(bc.dao, "url")
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, &native.IDList{0}, idList)
|
|
||||||
|
|
||||||
// Finish.
|
|
||||||
priv, err := keys.NewPrivateKey()
|
|
||||||
require.NoError(t, err)
|
|
||||||
pub := priv.PublicKey()
|
|
||||||
|
|
||||||
tx := transaction.New([]byte{byte(opcode.PUSH1)}, 0)
|
|
||||||
bl := block.New(bc.config.StateRootInHeader)
|
|
||||||
bl.Index = bc.BlockHeight() + 1
|
|
||||||
setSigner(tx, testchain.CommitteeScriptHash())
|
|
||||||
ic := bc.newInteropContext(trigger.Application, bc.dao, bl, tx)
|
|
||||||
ic.SpawnVM()
|
|
||||||
ic.VM.LoadScript([]byte{byte(opcode.RET)})
|
|
||||||
err = bc.contracts.Designate.DesignateAsRole(ic, noderoles.Oracle, keys.PublicKeys{pub})
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
tx = transaction.New(orc.GetOracleResponseScript(), 0)
|
|
||||||
ic.Tx = tx
|
|
||||||
ic.Block = bc.newBlock(tx)
|
|
||||||
|
|
||||||
err = orc.FinishInternal(ic)
|
|
||||||
require.True(t, errors.Is(err, native.ErrResponseNotFound), "got: %v", err)
|
|
||||||
|
|
||||||
resp := &transaction.OracleResponse{
|
|
||||||
ID: 12,
|
|
||||||
Code: transaction.Success,
|
|
||||||
Result: []byte{4, 8, 15, 16, 23, 42},
|
|
||||||
}
|
|
||||||
tx.Attributes = []transaction.Attribute{{
|
|
||||||
Type: transaction.OracleResponseT,
|
|
||||||
Value: resp,
|
|
||||||
}}
|
|
||||||
err = orc.FinishInternal(ic)
|
|
||||||
require.True(t, errors.Is(err, native.ErrRequestNotFound), "got: %v", err)
|
|
||||||
|
|
||||||
// We need to ensure that callback is called thus, executing full script is necessary.
|
|
||||||
resp.ID = 0
|
|
||||||
ic.VM.LoadScriptWithFlags(tx.Script, callflag.All)
|
|
||||||
require.NoError(t, ic.VM.Run())
|
|
||||||
|
|
||||||
si := ic.DAO.GetStorageItem(cs.ID, []byte("lastOracleResponse"))
|
|
||||||
require.NotNil(t, si)
|
|
||||||
item, err := stackitem.Deserialize(si)
|
|
||||||
require.NoError(t, err)
|
|
||||||
arr, ok := item.Value().([]stackitem.Item)
|
|
||||||
require.True(t, ok)
|
|
||||||
require.Equal(t, []byte("url"), arr[0].Value())
|
|
||||||
require.Equal(t, userData, arr[1].Value())
|
|
||||||
require.Equal(t, big.NewInt(int64(resp.Code)), arr[2].Value())
|
|
||||||
require.Equal(t, resp.Result, arr[3].Value())
|
|
||||||
|
|
||||||
// Check that processed request is removed during `postPersist`.
|
|
||||||
_, err = orc.GetRequestInternal(ic.DAO, 0)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.NoError(t, orc.PostPersist(ic))
|
|
||||||
_, err = orc.GetRequestInternal(ic.DAO, 0)
|
|
||||||
require.Error(t, err)
|
|
||||||
|
|
||||||
t.Run("ErrorOnFinish", func(t *testing.T) {
|
|
||||||
const reqID = 1
|
|
||||||
|
|
||||||
putOracleRequest(t, cs.Hash, bc, "url", nil, "handle", []byte{1, 2}, gasForResponse)
|
|
||||||
_, err := orc.GetRequestInternal(bc.dao, reqID) // ensure ID is 1
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
tx = transaction.New(orc.GetOracleResponseScript(), 0)
|
|
||||||
tx.Attributes = []transaction.Attribute{{
|
|
||||||
Type: transaction.OracleResponseT,
|
|
||||||
Value: &transaction.OracleResponse{
|
|
||||||
ID: reqID,
|
|
||||||
Code: transaction.Success,
|
|
||||||
Result: []byte{4, 8, 15, 16, 23, 42},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
ic := bc.newInteropContext(trigger.Application, bc.dao, bc.newBlock(tx), tx)
|
|
||||||
ic.VM = ic.SpawnVM()
|
|
||||||
ic.VM.LoadScriptWithFlags(tx.Script, callflag.All)
|
|
||||||
require.Error(t, ic.VM.Run())
|
|
||||||
|
|
||||||
// Request is cleaned up even if callback failed.
|
|
||||||
require.NoError(t, orc.PostPersist(ic))
|
|
||||||
_, err = orc.GetRequestInternal(ic.DAO, reqID)
|
|
||||||
require.Error(t, err)
|
|
||||||
})
|
|
||||||
t.Run("BadRequest", func(t *testing.T) {
|
|
||||||
var doBadRequest = func(t *testing.T, h util.Uint160, url string, filter *string, cb string, userData []byte, gas int64) {
|
|
||||||
txHash := putOracleRequest(t, h, bc, url, filter, cb, userData, gas)
|
|
||||||
aer, err := bc.GetAppExecResults(txHash, trigger.Application)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, 1, len(aer))
|
|
||||||
require.Equal(t, vm.FaultState, aer[0].VMState)
|
|
||||||
}
|
|
||||||
t.Run("non-UTF8 url", func(t *testing.T) {
|
|
||||||
doBadRequest(t, cs.Hash, "\xff", nil, "", []byte{1, 2}, gasForResponse)
|
|
||||||
})
|
|
||||||
t.Run("non-UTF8 filter", func(t *testing.T) {
|
|
||||||
var f = "\xff"
|
|
||||||
doBadRequest(t, cs.Hash, "url", &f, "", []byte{1, 2}, gasForResponse)
|
|
||||||
})
|
|
||||||
t.Run("not enough gas", func(t *testing.T) {
|
|
||||||
doBadRequest(t, cs.Hash, "url", nil, "", nil, 1000)
|
|
||||||
})
|
|
||||||
t.Run("disallowed callback", func(t *testing.T) {
|
|
||||||
doBadRequest(t, cs.Hash, "url", nil, "_deploy", nil, 1000_0000)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetSetPrice(t *testing.T) {
|
|
||||||
bc := newTestChain(t)
|
|
||||||
testGetSet(t, bc, bc.contracts.Oracle.Hash, "Price",
|
|
||||||
native.DefaultOracleRequestPrice, 1, math.MaxInt64)
|
|
||||||
}
|
|
|
@ -1,16 +1,12 @@
|
||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/big"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/internal/random"
|
"github.com/nspcc-dev/neo-go/internal/random"
|
||||||
"github.com/nspcc-dev/neo-go/internal/testchain"
|
"github.com/nspcc-dev/neo-go/internal/testchain"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -19,68 +15,6 @@ func transferFundsToCommittee(t *testing.T, chain *Blockchain) {
|
||||||
chain.contracts.GAS.Hash, 1000_00000000)
|
chain.contracts.GAS.Hash, 1000_00000000)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testGetSet(t *testing.T, chain *Blockchain, hash util.Uint160, name string, defaultValue, minValue, maxValue int64) {
|
|
||||||
getName := "get" + name
|
|
||||||
setName := "set" + name
|
|
||||||
|
|
||||||
transferFundsToCommittee(t, chain)
|
|
||||||
t.Run("set, not signed by committee", func(t *testing.T) {
|
|
||||||
signer, err := wallet.NewAccount()
|
|
||||||
require.NoError(t, err)
|
|
||||||
invokeRes, err := invokeContractMethodBy(t, chain, signer, hash, setName, minValue+1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkFAULTState(t, invokeRes)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("get, defult value", func(t *testing.T) {
|
|
||||||
res, err := invokeContractMethod(chain, 100000000, hash, getName)
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, res, stackitem.Make(defaultValue))
|
|
||||||
_, err = chain.persist(false)
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("set, too small value", func(t *testing.T) {
|
|
||||||
res, err := invokeContractMethodGeneric(chain, 100000000, hash, setName, true, minValue-1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkFAULTState(t, res)
|
|
||||||
})
|
|
||||||
|
|
||||||
if maxValue != 0 {
|
|
||||||
t.Run("set, too large value", func(t *testing.T) {
|
|
||||||
// use big.Int because max can be `math.MaxInt64`
|
|
||||||
max := big.NewInt(maxValue)
|
|
||||||
max.Add(max, big.NewInt(1))
|
|
||||||
res, err := invokeContractMethodGeneric(chain, 100000000, hash, setName, true, max)
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkFAULTState(t, res)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("set, success", func(t *testing.T) {
|
|
||||||
// Set and get in the same block.
|
|
||||||
txSet, err := prepareContractMethodInvokeGeneric(chain, 100000000, hash, setName, true, defaultValue+1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
txGet1, err := prepareContractMethodInvoke(chain, 100000000, hash, getName)
|
|
||||||
require.NoError(t, err)
|
|
||||||
aers, err := persistBlock(chain, txSet, txGet1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, aers[0], stackitem.Null{})
|
|
||||||
if name != "GasPerBlock" { // GasPerBlock is set on the next block
|
|
||||||
checkResult(t, aers[1], stackitem.Make(defaultValue+1))
|
|
||||||
}
|
|
||||||
_, err = chain.persist(false)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Get in the next block.
|
|
||||||
res, err := invokeContractMethod(chain, 100000000, hash, getName)
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, res, stackitem.Make(defaultValue+1))
|
|
||||||
_, err = chain.persist(false)
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFeePerByte(t *testing.T) {
|
func TestFeePerByte(t *testing.T) {
|
||||||
chain := newTestChain(t)
|
chain := newTestChain(t)
|
||||||
|
|
||||||
|
@ -88,8 +22,6 @@ func TestFeePerByte(t *testing.T) {
|
||||||
n := chain.contracts.Policy.GetFeePerByteInternal(chain.dao)
|
n := chain.contracts.Policy.GetFeePerByteInternal(chain.dao)
|
||||||
require.Equal(t, 1000, int(n))
|
require.Equal(t, 1000, int(n))
|
||||||
})
|
})
|
||||||
|
|
||||||
testGetSet(t, chain, chain.contracts.Policy.Hash, "FeePerByte", 1000, 0, 100_000_000)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExecFeeFactor(t *testing.T) {
|
func TestExecFeeFactor(t *testing.T) {
|
||||||
|
@ -99,8 +31,6 @@ func TestExecFeeFactor(t *testing.T) {
|
||||||
n := chain.contracts.Policy.GetExecFeeFactorInternal(chain.dao)
|
n := chain.contracts.Policy.GetExecFeeFactorInternal(chain.dao)
|
||||||
require.EqualValues(t, interop.DefaultBaseExecFee, n)
|
require.EqualValues(t, interop.DefaultBaseExecFee, n)
|
||||||
})
|
})
|
||||||
|
|
||||||
testGetSet(t, chain, chain.contracts.Policy.Hash, "ExecFeeFactor", interop.DefaultBaseExecFee, 1, 1000)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStoragePrice(t *testing.T) {
|
func TestStoragePrice(t *testing.T) {
|
||||||
|
@ -110,15 +40,10 @@ func TestStoragePrice(t *testing.T) {
|
||||||
n := chain.contracts.Policy.GetStoragePriceInternal(chain.dao)
|
n := chain.contracts.Policy.GetStoragePriceInternal(chain.dao)
|
||||||
require.Equal(t, int64(native.DefaultStoragePrice), n)
|
require.Equal(t, int64(native.DefaultStoragePrice), n)
|
||||||
})
|
})
|
||||||
|
|
||||||
testGetSet(t, chain, chain.contracts.Policy.Hash, "StoragePrice", native.DefaultStoragePrice, 1, 10000000)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBlockedAccounts(t *testing.T) {
|
func TestBlockedAccounts(t *testing.T) {
|
||||||
chain := newTestChain(t)
|
chain := newTestChain(t)
|
||||||
account := util.Uint160{1, 2, 3}
|
|
||||||
policyHash := chain.contracts.Policy.Metadata().Hash
|
|
||||||
|
|
||||||
transferTokenFromMultisigAccount(t, chain, testchain.CommitteeScriptHash(),
|
transferTokenFromMultisigAccount(t, chain, testchain.CommitteeScriptHash(),
|
||||||
chain.contracts.GAS.Hash, 100_00000000)
|
chain.contracts.GAS.Hash, 100_00000000)
|
||||||
|
|
||||||
|
@ -126,100 +51,4 @@ func TestBlockedAccounts(t *testing.T) {
|
||||||
isBlocked := chain.contracts.Policy.IsBlockedInternal(chain.dao, random.Uint160())
|
isBlocked := chain.contracts.Policy.IsBlockedInternal(chain.dao, random.Uint160())
|
||||||
require.Equal(t, false, isBlocked)
|
require.Equal(t, false, isBlocked)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("isBlocked, contract method", func(t *testing.T) {
|
|
||||||
res, err := invokeContractMethod(chain, 100000000, policyHash, "isBlocked", random.Uint160())
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, res, stackitem.NewBool(false))
|
|
||||||
_, err = chain.persist(false)
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("block-unblock account", func(t *testing.T) {
|
|
||||||
res, err := invokeContractMethodGeneric(chain, 100000000, policyHash, "blockAccount", true, account.BytesBE())
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, res, stackitem.NewBool(true))
|
|
||||||
|
|
||||||
isBlocked := chain.contracts.Policy.IsBlockedInternal(chain.dao, account)
|
|
||||||
require.Equal(t, isBlocked, true)
|
|
||||||
_, err = chain.persist(false)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
res, err = invokeContractMethodGeneric(chain, 100000000, policyHash, "unblockAccount", true, account.BytesBE())
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, res, stackitem.NewBool(true))
|
|
||||||
|
|
||||||
isBlocked = chain.contracts.Policy.IsBlockedInternal(chain.dao, account)
|
|
||||||
require.Equal(t, false, isBlocked)
|
|
||||||
_, err = chain.persist(false)
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("double-block", func(t *testing.T) {
|
|
||||||
// block
|
|
||||||
res, err := invokeContractMethodGeneric(chain, 100000000, policyHash, "blockAccount", true, account.BytesBE())
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, res, stackitem.NewBool(true))
|
|
||||||
_, err = chain.persist(false)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// double-block should fail
|
|
||||||
res, err = invokeContractMethodGeneric(chain, 100000000, policyHash, "blockAccount", true, account.BytesBE())
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, res, stackitem.NewBool(false))
|
|
||||||
_, err = chain.persist(false)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// unblock
|
|
||||||
res, err = invokeContractMethodGeneric(chain, 100000000, policyHash, "unblockAccount", true, account.BytesBE())
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, res, stackitem.NewBool(true))
|
|
||||||
_, err = chain.persist(false)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// unblock the same account should fail as we don't have it blocked
|
|
||||||
res, err = invokeContractMethodGeneric(chain, 100000000, policyHash, "unblockAccount", true, account.BytesBE())
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, res, stackitem.NewBool(false))
|
|
||||||
_, err = chain.persist(false)
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("not signed by committee", func(t *testing.T) {
|
|
||||||
signer, err := wallet.NewAccount()
|
|
||||||
require.NoError(t, err)
|
|
||||||
invokeRes, err := invokeContractMethodBy(t, chain, signer, policyHash, "blockAccount", account.BytesBE())
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkFAULTState(t, invokeRes)
|
|
||||||
|
|
||||||
invokeRes, err = invokeContractMethodBy(t, chain, signer, policyHash, "unblockAccount", account.BytesBE())
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkFAULTState(t, invokeRes)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("block-unblock contract", func(t *testing.T) {
|
|
||||||
neoHash := chain.contracts.NEO.Metadata().Hash
|
|
||||||
res, err := invokeContractMethodGeneric(chain, 100000000, policyHash, "blockAccount", true, neoHash.BytesBE())
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkFAULTState(t, res)
|
|
||||||
|
|
||||||
cs, _ := getTestContractState(chain)
|
|
||||||
require.NoError(t, chain.contracts.Management.PutContractState(chain.dao, cs))
|
|
||||||
|
|
||||||
res, err = invokeContractMethodGeneric(chain, 100000000, policyHash, "blockAccount", true, cs.Hash.BytesBE())
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, res, stackitem.NewBool(true))
|
|
||||||
|
|
||||||
res, err = invokeContractMethod(chain, 100000000, cs.Hash, "justReturn")
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkFAULTState(t, res)
|
|
||||||
|
|
||||||
res, err = invokeContractMethodGeneric(chain, 100000000, policyHash, "unblockAccount", true, cs.Hash.BytesBE())
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, res, stackitem.NewBool(true))
|
|
||||||
|
|
||||||
res, err = invokeContractMethod(chain, 100000000, cs.Hash, "justReturn")
|
|
||||||
require.NoError(t, err)
|
|
||||||
checkResult(t, res, stackitem.Null{})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,14 @@ package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
gio "io"
|
gio "io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -16,20 +19,164 @@ import (
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"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/crypto/keys"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/services/oracle"
|
"github.com/nspcc-dev/neo-go/pkg/services/oracle"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"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"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"go.uber.org/zap/zaptest"
|
"go.uber.org/zap/zaptest"
|
||||||
)
|
)
|
||||||
|
|
||||||
var oracleModulePath = filepath.Join("..", "services", "oracle")
|
var (
|
||||||
|
oracleModulePath = filepath.Join("..", "services", "oracle")
|
||||||
|
oracleContractNEFPath = filepath.Join("test_data", "oracle_contract", "oracle.nef")
|
||||||
|
oracleContractManifestPath = filepath.Join("test_data", "oracle_contract", "oracle.manifest.json")
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestGenerateOracleContract generates helper contract that is able to call
|
||||||
|
// native Oracle contract and has callback method. It uses test chain to define
|
||||||
|
// Oracle and StdLib native hashes and saves generated NEF and manifest to ... folder.
|
||||||
|
// Set `saveState` flag to true and run the test to rewrite NEF and manifest files.
|
||||||
|
func TestGenerateOracleContract(t *testing.T) {
|
||||||
|
const saveState = false
|
||||||
|
|
||||||
|
bc := newTestChain(t)
|
||||||
|
oracleHash := bc.contracts.Oracle.Hash
|
||||||
|
stdHash := bc.contracts.Std.Hash
|
||||||
|
|
||||||
|
w := io.NewBufBinWriter()
|
||||||
|
emit.Int(w.BinWriter, 5)
|
||||||
|
emit.Opcodes(w.BinWriter, opcode.PACK)
|
||||||
|
emit.Int(w.BinWriter, int64(callflag.All))
|
||||||
|
emit.String(w.BinWriter, "request")
|
||||||
|
emit.Bytes(w.BinWriter, oracleHash.BytesBE())
|
||||||
|
emit.Syscall(w.BinWriter, interopnames.SystemContractCall)
|
||||||
|
emit.Opcodes(w.BinWriter, opcode.DROP)
|
||||||
|
emit.Opcodes(w.BinWriter, opcode.RET)
|
||||||
|
|
||||||
|
// `handle` method aborts if len(userData) == 2 and does NOT perform witness checks
|
||||||
|
// for the sake of contract code simplicity (the contract is used in multiple testchains).
|
||||||
|
offset := w.Len()
|
||||||
|
|
||||||
|
emit.Opcodes(w.BinWriter, opcode.OVER)
|
||||||
|
emit.Opcodes(w.BinWriter, opcode.SIZE)
|
||||||
|
emit.Int(w.BinWriter, 2)
|
||||||
|
emit.Instruction(w.BinWriter, opcode.JMPNE, []byte{3})
|
||||||
|
emit.Opcodes(w.BinWriter, opcode.ABORT)
|
||||||
|
emit.Int(w.BinWriter, 4) // url, userData, code, result
|
||||||
|
emit.Opcodes(w.BinWriter, opcode.PACK)
|
||||||
|
emit.Int(w.BinWriter, 1) // 1 byte (args count for `serialize`)
|
||||||
|
emit.Opcodes(w.BinWriter, opcode.PACK) // 1 byte (pack args into array for `serialize`)
|
||||||
|
emit.AppCallNoArgs(w.BinWriter, stdHash, "serialize", callflag.All) // 39 bytes
|
||||||
|
emit.String(w.BinWriter, "lastOracleResponse")
|
||||||
|
emit.Syscall(w.BinWriter, interopnames.SystemStorageGetContext)
|
||||||
|
emit.Syscall(w.BinWriter, interopnames.SystemStoragePut)
|
||||||
|
emit.Opcodes(w.BinWriter, opcode.RET)
|
||||||
|
|
||||||
|
m := manifest.NewManifest("TestOracle")
|
||||||
|
m.ABI.Methods = []manifest.Method{
|
||||||
|
{
|
||||||
|
Name: "requestURL",
|
||||||
|
Offset: 0,
|
||||||
|
Parameters: []manifest.Parameter{
|
||||||
|
manifest.NewParameter("url", smartcontract.StringType),
|
||||||
|
manifest.NewParameter("filter", smartcontract.StringType),
|
||||||
|
manifest.NewParameter("callback", smartcontract.StringType),
|
||||||
|
manifest.NewParameter("userData", smartcontract.AnyType),
|
||||||
|
manifest.NewParameter("gasForResponse", smartcontract.IntegerType),
|
||||||
|
},
|
||||||
|
ReturnType: smartcontract.VoidType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "handle",
|
||||||
|
Offset: offset,
|
||||||
|
Parameters: []manifest.Parameter{
|
||||||
|
manifest.NewParameter("url", smartcontract.StringType),
|
||||||
|
manifest.NewParameter("userData", smartcontract.AnyType),
|
||||||
|
manifest.NewParameter("code", smartcontract.IntegerType),
|
||||||
|
manifest.NewParameter("result", smartcontract.ByteArrayType),
|
||||||
|
},
|
||||||
|
ReturnType: smartcontract.VoidType,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
perm := manifest.NewPermission(manifest.PermissionHash, oracleHash)
|
||||||
|
perm.Methods.Add("request")
|
||||||
|
m.Permissions = append(m.Permissions, *perm)
|
||||||
|
|
||||||
|
// Generate NEF file.
|
||||||
|
script := w.Bytes()
|
||||||
|
ne, err := nef.NewFile(script)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Write NEF file.
|
||||||
|
bytes, err := ne.Bytes()
|
||||||
|
require.NoError(t, err)
|
||||||
|
if saveState {
|
||||||
|
err = ioutil.WriteFile(oracleContractNEFPath, bytes, os.ModePerm)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write manifest file.
|
||||||
|
mData, err := json.Marshal(m)
|
||||||
|
require.NoError(t, err)
|
||||||
|
if saveState {
|
||||||
|
err = ioutil.WriteFile(oracleContractManifestPath, mData, os.ModePerm)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
require.False(t, saveState)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getOracleContractState reads pre-compiled oracle contract generated by
|
||||||
|
// TestGenerateOracleContract and returns its state.
|
||||||
|
func getOracleContractState(t *testing.T, sender util.Uint160, id int32) *state.Contract {
|
||||||
|
errNotFound := errors.New("auto-generated oracle contract is not found, use TestGenerateOracleContract to regenerate")
|
||||||
|
|
||||||
|
neBytes, err := ioutil.ReadFile(oracleContractNEFPath)
|
||||||
|
require.NoError(t, err, fmt.Errorf("nef: %w", errNotFound))
|
||||||
|
ne, err := nef.FileFromBytes(neBytes)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
mBytes, err := ioutil.ReadFile(oracleContractManifestPath)
|
||||||
|
require.NoError(t, err, fmt.Errorf("manifest: %w", errNotFound))
|
||||||
|
m := &manifest.Manifest{}
|
||||||
|
err = json.Unmarshal(mBytes, m)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return &state.Contract{
|
||||||
|
ContractBase: state.ContractBase{
|
||||||
|
NEF: ne,
|
||||||
|
Hash: state.CreateContractHash(sender, ne.Checksum, m.Name),
|
||||||
|
Manifest: *m,
|
||||||
|
ID: id,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func putOracleRequest(t *testing.T, h util.Uint160, bc *Blockchain,
|
||||||
|
url string, filter *string, cb string, userData []byte, gas int64) util.Uint256 {
|
||||||
|
var filtItem interface{}
|
||||||
|
if filter != nil {
|
||||||
|
filtItem = *filter
|
||||||
|
}
|
||||||
|
res, err := invokeContractMethod(bc, gas+50_000_000+5_000_000, h, "requestURL",
|
||||||
|
url, filtItem, cb, userData, gas)
|
||||||
|
require.NoError(t, err)
|
||||||
|
return res.Container
|
||||||
|
}
|
||||||
|
|
||||||
func getOracleConfig(t *testing.T, bc *Blockchain, w, pass string) oracle.Config {
|
func getOracleConfig(t *testing.T, bc *Blockchain, w, pass string) oracle.Config {
|
||||||
return oracle.Config{
|
return oracle.Config{
|
||||||
|
@ -133,7 +280,7 @@ func TestOracle(t *testing.T) {
|
||||||
orc1.UpdateNativeContract(orcNative.NEF.Script, orcNative.GetOracleResponseScript(), orcNative.Hash, md.MD.Offset)
|
orc1.UpdateNativeContract(orcNative.NEF.Script, orcNative.GetOracleResponseScript(), orcNative.Hash, md.MD.Offset)
|
||||||
orc2.UpdateNativeContract(orcNative.NEF.Script, orcNative.GetOracleResponseScript(), orcNative.Hash, md.MD.Offset)
|
orc2.UpdateNativeContract(orcNative.NEF.Script, orcNative.GetOracleResponseScript(), orcNative.Hash, md.MD.Offset)
|
||||||
|
|
||||||
cs := getOracleContractState(bc.contracts.Oracle.Hash, bc.contracts.Std.Hash)
|
cs := getOracleContractState(t, util.Uint160{}, 42)
|
||||||
require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs))
|
require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs))
|
||||||
|
|
||||||
putOracleRequest(t, cs.Hash, bc, "https://get.1234", nil, "handle", []byte{}, 10_000_000)
|
putOracleRequest(t, cs.Hash, bc, "https://get.1234", nil, "handle", []byte{}, 10_000_000)
|
||||||
|
@ -301,7 +448,7 @@ func TestOracleFull(t *testing.T) {
|
||||||
orc.OnTransaction = func(tx *transaction.Transaction) { _ = mp.Add(tx, bc) }
|
orc.OnTransaction = func(tx *transaction.Transaction) { _ = mp.Add(tx, bc) }
|
||||||
bc.SetOracle(orc)
|
bc.SetOracle(orc)
|
||||||
|
|
||||||
cs := getOracleContractState(bc.contracts.Oracle.Hash, bc.contracts.Std.Hash)
|
cs := getOracleContractState(t, util.Uint160{}, 42)
|
||||||
require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs))
|
require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs))
|
||||||
|
|
||||||
go bc.Run()
|
go bc.Run()
|
||||||
|
@ -326,7 +473,7 @@ func TestNotYetRunningOracle(t *testing.T) {
|
||||||
orc.OnTransaction = func(tx *transaction.Transaction) { _ = mp.Add(tx, bc) }
|
orc.OnTransaction = func(tx *transaction.Transaction) { _ = mp.Add(tx, bc) }
|
||||||
bc.SetOracle(orc)
|
bc.SetOracle(orc)
|
||||||
|
|
||||||
cs := getOracleContractState(bc.contracts.Oracle.Hash, bc.contracts.Std.Hash)
|
cs := getOracleContractState(t, util.Uint160{}, 42)
|
||||||
require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs))
|
require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs))
|
||||||
|
|
||||||
go bc.Run()
|
go bc.Run()
|
||||||
|
|
9
pkg/core/test_data/management_helper/README.md
Normal file
9
pkg/core/test_data/management_helper/README.md
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
## Management helper contracts
|
||||||
|
|
||||||
|
Management helper contracts NEF and manifest files are generated automatically by
|
||||||
|
`TestGenerateManagementHelperContracts` and are used in tests. Do not modify these files manually.
|
||||||
|
To regenerate these files:
|
||||||
|
|
||||||
|
1. Open `TestGenerateManagementHelperContracts` and set `saveState` flag to `true`.
|
||||||
|
2. Run `TestGenerateManagementHelperContracts`.
|
||||||
|
3. Set `saveState` back to `false`.
|
1
pkg/core/test_data/management_helper/management_helper1.manifest.json
Executable file
1
pkg/core/test_data/management_helper/management_helper1.manifest.json
Executable file
|
@ -0,0 +1 @@
|
||||||
|
{"name":"TestMain","abi":{"methods":[{"name":"add","offset":1,"parameters":[{"name":"addend1","type":"Integer"},{"name":"addend2","type":"Integer"}],"returntype":"Integer","safe":false},{"name":"add","offset":3,"parameters":[{"name":"addend1","type":"Integer"},{"name":"addend2","type":"Integer"},{"name":"addend3","type":"Integer"}],"returntype":"Integer","safe":false},{"name":"ret7","offset":6,"parameters":[],"returntype":"Integer","safe":false},{"name":"drop","offset":8,"parameters":null,"returntype":"Void","safe":false},{"name":"_initialize","offset":10,"parameters":null,"returntype":"Void","safe":false},{"name":"add3","offset":15,"parameters":[{"name":"addend","type":"Integer"}],"returntype":"Integer","safe":false},{"name":"invalidReturn","offset":18,"parameters":null,"returntype":"Integer","safe":false},{"name":"justReturn","offset":21,"parameters":null,"returntype":"Void","safe":false},{"name":"verify","offset":22,"parameters":null,"returntype":"Boolean","safe":false},{"name":"_deploy","offset":27,"parameters":[{"name":"data","type":"Any"},{"name":"isUpdate","type":"Boolean"}],"returntype":"Void","safe":false},{"name":"getValue","offset":158,"parameters":null,"returntype":"String","safe":false},{"name":"putValue","offset":138,"parameters":[{"name":"value","type":"String"}],"returntype":"Void","safe":false},{"name":"delValue","offset":178,"parameters":[{"name":"key","type":"String"}],"returntype":"Void","safe":false},{"name":"onNEP11Payment","offset":215,"parameters":[{"name":"from","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"tokenid","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false},{"name":"onNEP17Payment","offset":189,"parameters":[{"name":"from","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false},{"name":"update","offset":244,"parameters":[{"name":"nef","type":"ByteArray"},{"name":"manifest","type":"ByteArray"}],"returntype":"Void","safe":false},{"name":"update","offset":241,"parameters":[{"name":"nef","type":"ByteArray"},{"name":"manifest","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false},{"name":"destroy","offset":284,"parameters":null,"returntype":"Void","safe":false},{"name":"invalidStack","offset":325,"parameters":null,"returntype":"Void","safe":false},{"name":"callT0","offset":335,"parameters":[{"name":"address","type":"Hash160"}],"returntype":"Integer","safe":false},{"name":"callT1","offset":341,"parameters":null,"returntype":"Integer","safe":false},{"name":"callT2","offset":345,"parameters":null,"returntype":"Integer","safe":false},{"name":"burnGas","offset":349,"parameters":[{"name":"amount","type":"Integer"}],"returntype":"Void","safe":false},{"name":"invocCounter","offset":355,"parameters":null,"returntype":"Integer","safe":false}],"events":[]},"features":{},"groups":[],"permissions":[{"contract":"0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5","methods":["balanceOf"]},{"contract":"0x0000000000000000000000000000000000000000","methods":["method"]}],"supportedstandards":[],"trusts":[],"extra":null}
|
BIN
pkg/core/test_data/management_helper/management_helper1.nef
Executable file
BIN
pkg/core/test_data/management_helper/management_helper1.nef
Executable file
Binary file not shown.
1
pkg/core/test_data/management_helper/management_helper2.manifest.json
Executable file
1
pkg/core/test_data/management_helper/management_helper2.manifest.json
Executable file
|
@ -0,0 +1 @@
|
||||||
|
{"name":"TestAux","abi":{"methods":[],"events":[]},"features":{},"groups":[],"permissions":[{"contract":"0x00ecaa2f079b65e3b31572e4c2c160a1abd02997","methods":["add","drop","add3","invalidReturn","justReturn","getValue"]}],"supportedstandards":[],"trusts":[],"extra":null}
|
BIN
pkg/core/test_data/management_helper/management_helper2.nef
Executable file
BIN
pkg/core/test_data/management_helper/management_helper2.nef
Executable file
Binary file not shown.
9
pkg/core/test_data/oracle_contract/README.md
Normal file
9
pkg/core/test_data/oracle_contract/README.md
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
## Oracle helper contract
|
||||||
|
|
||||||
|
Oracle helper contract NEF and manifest files are generated automatically by
|
||||||
|
`TestGenerateOracleContract` and are used in tests. Do not modify these files manually.
|
||||||
|
To regenerate these files:
|
||||||
|
|
||||||
|
1. Open `TestGenerateOracleContract` and set `saveState` flag to `true`.
|
||||||
|
2. Run `TestGenerateOracleContract`.
|
||||||
|
3. Set `saveState` back to `false`.
|
1
pkg/core/test_data/oracle_contract/oracle.manifest.json
Executable file
1
pkg/core/test_data/oracle_contract/oracle.manifest.json
Executable file
|
@ -0,0 +1 @@
|
||||||
|
{"name":"TestOracle","abi":{"methods":[{"name":"requestURL","offset":0,"parameters":[{"name":"url","type":"String"},{"name":"filter","type":"String"},{"name":"callback","type":"String"},{"name":"userData","type":"Any"},{"name":"gasForResponse","type":"Integer"}],"returntype":"Void","safe":false},{"name":"handle","offset":41,"parameters":[{"name":"url","type":"String"},{"name":"userData","type":"Any"},{"name":"code","type":"Integer"},{"name":"result","type":"ByteArray"}],"returntype":"Void","safe":false}],"events":[]},"features":{},"groups":[],"permissions":[{"contract":"0xfe924b7cfe89ddd271abaf7210a80a7e11178758","methods":["request"]}],"supportedstandards":[],"trusts":[],"extra":null}
|
BIN
pkg/core/test_data/oracle_contract/oracle.nef
Executable file
BIN
pkg/core/test_data/oracle_contract/oracle.nef
Executable file
Binary file not shown.
|
@ -3,6 +3,7 @@ package neotest
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/big"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -62,6 +63,14 @@ func (e *Executor) NativeHash(t *testing.T, name string) util.Uint160 {
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NativeID returns native contract ID by name.
|
||||||
|
func (e *Executor) NativeID(t *testing.T, name string) int32 {
|
||||||
|
h := e.NativeHash(t, name)
|
||||||
|
cs := e.Chain.GetContractState(h)
|
||||||
|
require.NotNil(t, cs)
|
||||||
|
return cs.ID
|
||||||
|
}
|
||||||
|
|
||||||
// NewUnsignedTx creates new unsigned transaction which invokes method of contract with hash.
|
// NewUnsignedTx creates new unsigned transaction which invokes method of contract with hash.
|
||||||
func (e *Executor) NewUnsignedTx(t *testing.T, hash util.Uint160, method string, args ...interface{}) *transaction.Transaction {
|
func (e *Executor) NewUnsignedTx(t *testing.T, hash util.Uint160, method string, args ...interface{}) *transaction.Transaction {
|
||||||
w := io.NewBufBinWriter()
|
w := io.NewBufBinWriter()
|
||||||
|
@ -100,15 +109,19 @@ func (e *Executor) SignTx(t *testing.T, tx *transaction.Transaction, sysFee int6
|
||||||
return tx
|
return tx
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAccount returns new signer holding 100.0 GAS. This method advances the chain
|
// NewAccount returns new signer holding 100.0 GAS (or given amount is specified).
|
||||||
// by one block with a transfer transaction.
|
// This method advances the chain by one block with a transfer transaction.
|
||||||
func (e *Executor) NewAccount(t *testing.T) Signer {
|
func (e *Executor) NewAccount(t *testing.T, expectedGASBalance ...int64) Signer {
|
||||||
acc, err := wallet.NewAccount()
|
acc, err := wallet.NewAccount()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
tx := e.NewTx(t, []Signer{e.Committee},
|
amount := int64(100_0000_0000)
|
||||||
|
if len(expectedGASBalance) != 0 {
|
||||||
|
amount = expectedGASBalance[0]
|
||||||
|
}
|
||||||
|
tx := e.NewTx(t, []Signer{e.Validator},
|
||||||
e.NativeHash(t, nativenames.Gas), "transfer",
|
e.NativeHash(t, nativenames.Gas), "transfer",
|
||||||
e.Committee.ScriptHash(), acc.Contract.ScriptHash(), int64(100_0000_0000), nil)
|
e.Validator.ScriptHash(), acc.Contract.ScriptHash(), amount, nil)
|
||||||
e.AddNewBlock(t, tx)
|
e.AddNewBlock(t, tx)
|
||||||
e.CheckHalt(t, tx.Hash())
|
e.CheckHalt(t, tx.Hash())
|
||||||
return NewSingleSigner(acc)
|
return NewSingleSigner(acc)
|
||||||
|
@ -202,6 +215,12 @@ func (e *Executor) CheckTxNotificationEvent(t *testing.T, h util.Uint256, index
|
||||||
require.Equal(t, expected, aer[0].Events[index])
|
require.Equal(t, expected, aer[0].Events[index])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CheckGASBalance ensures that provided account owns specified amount of GAS.
|
||||||
|
func (e *Executor) CheckGASBalance(t *testing.T, acc util.Uint160, expected *big.Int) {
|
||||||
|
actual := e.Chain.GetUtilityTokenBalance(acc)
|
||||||
|
require.Equal(t, expected, actual, fmt.Errorf("invalid GAS balance: expected %s, got %s", expected.String(), actual.String()))
|
||||||
|
}
|
||||||
|
|
||||||
// NewDeployTx returns new deployment tx for contract signed by committee.
|
// NewDeployTx returns new deployment tx for contract signed by committee.
|
||||||
func (e *Executor) NewDeployTx(t *testing.T, bc blockchainer.Blockchainer, c *Contract, data interface{}) *transaction.Transaction {
|
func (e *Executor) NewDeployTx(t *testing.T, bc blockchainer.Blockchainer, c *Contract, data interface{}) *transaction.Transaction {
|
||||||
rawManifest, err := json.Marshal(c.Manifest)
|
rawManifest, err := json.Marshal(c.Manifest)
|
||||||
|
@ -218,11 +237,11 @@ func (e *Executor) NewDeployTx(t *testing.T, bc blockchainer.Blockchainer, c *Co
|
||||||
tx.Nonce = Nonce()
|
tx.Nonce = Nonce()
|
||||||
tx.ValidUntilBlock = bc.BlockHeight() + 1
|
tx.ValidUntilBlock = bc.BlockHeight() + 1
|
||||||
tx.Signers = []transaction.Signer{{
|
tx.Signers = []transaction.Signer{{
|
||||||
Account: e.Committee.ScriptHash(),
|
Account: e.Validator.ScriptHash(),
|
||||||
Scopes: transaction.Global,
|
Scopes: transaction.Global,
|
||||||
}}
|
}}
|
||||||
addNetworkFee(bc, tx, e.Committee)
|
addNetworkFee(bc, tx, e.Validator)
|
||||||
require.NoError(t, e.Committee.SignTx(netmode.UnitTestNet, tx))
|
require.NoError(t, e.Validator.SignTx(netmode.UnitTestNet, tx))
|
||||||
return tx
|
return tx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -277,6 +296,13 @@ func (e *Executor) AddNewBlock(t *testing.T, txs ...*transaction.Transaction) *b
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GenerateNewBlocks adds specified number of empty blocks to the chain.
|
||||||
|
func (e *Executor) GenerateNewBlocks(t *testing.T, count int) {
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
e.AddNewBlock(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SignBlock add validators signature to b.
|
// SignBlock add validators signature to b.
|
||||||
func (e *Executor) SignBlock(b *block.Block) *block.Block {
|
func (e *Executor) SignBlock(b *block.Block) *block.Block {
|
||||||
invoc := e.Validator.SignHashable(uint32(e.Chain.GetConfig().Magic), b)
|
invoc := e.Validator.SignHashable(uint32(e.Chain.GetConfig().Magic), b)
|
||||||
|
@ -316,3 +342,27 @@ func TestInvoke(bc blockchainer.Blockchainer, tx *transaction.Transaction) (*vm.
|
||||||
err = v.Run()
|
err = v.Run()
|
||||||
return v, err
|
return v, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetTransaction returns transaction and its height by the specified hash.
|
||||||
|
func (e *Executor) GetTransaction(t *testing.T, h util.Uint256) (*transaction.Transaction, uint32) {
|
||||||
|
tx, height, err := e.Chain.GetTransaction(h)
|
||||||
|
require.NoError(t, err)
|
||||||
|
return tx, height
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlockByIndex returns block by the specified index.
|
||||||
|
func (e *Executor) GetBlockByIndex(t *testing.T, idx int) *block.Block {
|
||||||
|
h := e.Chain.GetHeaderHash(idx)
|
||||||
|
require.NotEmpty(t, h)
|
||||||
|
b, err := e.Chain.GetBlock(h)
|
||||||
|
require.NoError(t, err)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTxExecResult returns application execution results for the specified transaction.
|
||||||
|
func (e *Executor) GetTxExecResult(t *testing.T, h util.Uint256) *state.AppExecResult {
|
||||||
|
aer, err := e.Chain.GetAppExecResults(h, trigger.Application)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 1, len(aer))
|
||||||
|
return &aer[0]
|
||||||
|
}
|
||||||
|
|
|
@ -108,27 +108,49 @@ func init() {
|
||||||
// NewSingle creates new blockchain instance with a single validator and
|
// NewSingle creates new blockchain instance with a single validator and
|
||||||
// setups cleanup functions.
|
// setups cleanup functions.
|
||||||
func NewSingle(t *testing.T) (*core.Blockchain, neotest.Signer) {
|
func NewSingle(t *testing.T) (*core.Blockchain, neotest.Signer) {
|
||||||
|
return NewSingleWithCustomConfig(t, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSingleWithCustomConfig creates new blockchain instance with custom protocol
|
||||||
|
// configuration and a single validator. It also setups cleanup functions.
|
||||||
|
func NewSingleWithCustomConfig(t *testing.T, f func(*config.ProtocolConfiguration)) (*core.Blockchain, neotest.Signer) {
|
||||||
|
st := storage.NewMemoryStore()
|
||||||
|
return NewSingleWithCustomConfigAndStore(t, f, st, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSingleWithCustomConfigAndStore(t *testing.T, f func(cfg *config.ProtocolConfiguration), st storage.Store, run bool) (*core.Blockchain, neotest.Signer) {
|
||||||
protoCfg := config.ProtocolConfiguration{
|
protoCfg := config.ProtocolConfiguration{
|
||||||
Magic: netmode.UnitTestNet,
|
Magic: netmode.UnitTestNet,
|
||||||
|
MaxTraceableBlocks: 1000, // We don't need a lot of traceable blocks for tests.
|
||||||
SecondsPerBlock: 1,
|
SecondsPerBlock: 1,
|
||||||
StandbyCommittee: []string{hex.EncodeToString(committeeAcc.PrivateKey().PublicKey().Bytes())},
|
StandbyCommittee: []string{hex.EncodeToString(committeeAcc.PrivateKey().PublicKey().Bytes())},
|
||||||
ValidatorsCount: 1,
|
ValidatorsCount: 1,
|
||||||
VerifyBlocks: true,
|
VerifyBlocks: true,
|
||||||
VerifyTransactions: true,
|
VerifyTransactions: true,
|
||||||
}
|
}
|
||||||
|
if f != nil {
|
||||||
st := storage.NewMemoryStore()
|
f(&protoCfg)
|
||||||
|
}
|
||||||
log := zaptest.NewLogger(t)
|
log := zaptest.NewLogger(t)
|
||||||
bc, err := core.NewBlockchain(st, protoCfg, log)
|
bc, err := core.NewBlockchain(st, protoCfg, log)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
go bc.Run()
|
if run {
|
||||||
t.Cleanup(bc.Close)
|
go bc.Run()
|
||||||
|
t.Cleanup(bc.Close)
|
||||||
|
}
|
||||||
return bc, neotest.NewMultiSigner(committeeAcc)
|
return bc, neotest.NewMultiSigner(committeeAcc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMulti creates new blockchain instance with 4 validators and 6 committee members.
|
// NewMulti creates new blockchain instance with 4 validators and 6 committee members.
|
||||||
// Second return value is for validator signer, third -- for committee.
|
// Second return value is for validator signer, third -- for committee.
|
||||||
func NewMulti(t *testing.T) (*core.Blockchain, neotest.Signer, neotest.Signer) {
|
func NewMulti(t *testing.T) (*core.Blockchain, neotest.Signer, neotest.Signer) {
|
||||||
|
return NewMultiWithCustomConfig(t, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMultiWithCustomConfig creates new blockchain instance with custom protocol
|
||||||
|
// configuration, 4 validators and 6 committee members. Second return value is
|
||||||
|
// for validator signer, third -- for committee.
|
||||||
|
func NewMultiWithCustomConfig(t *testing.T, f func(*config.ProtocolConfiguration)) (*core.Blockchain, neotest.Signer, neotest.Signer) {
|
||||||
protoCfg := config.ProtocolConfiguration{
|
protoCfg := config.ProtocolConfiguration{
|
||||||
Magic: netmode.UnitTestNet,
|
Magic: netmode.UnitTestNet,
|
||||||
SecondsPerBlock: 1,
|
SecondsPerBlock: 1,
|
||||||
|
@ -137,6 +159,9 @@ func NewMulti(t *testing.T) (*core.Blockchain, neotest.Signer, neotest.Signer) {
|
||||||
VerifyBlocks: true,
|
VerifyBlocks: true,
|
||||||
VerifyTransactions: true,
|
VerifyTransactions: true,
|
||||||
}
|
}
|
||||||
|
if f != nil {
|
||||||
|
f(&protoCfg)
|
||||||
|
}
|
||||||
|
|
||||||
st := storage.NewMemoryStore()
|
st := storage.NewMemoryStore()
|
||||||
log := zaptest.NewLogger(t)
|
log := zaptest.NewLogger(t)
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ContractInvoker is a client for specific contract.
|
// ContractInvoker is a client for specific contract.
|
||||||
|
@ -18,7 +19,7 @@ type ContractInvoker struct {
|
||||||
Signers []Signer
|
Signers []Signer
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommitteeInvoker creates new ContractInvoker for contract with hash h.
|
// CommitteeInvoker creates new ContractInvoker for contract with hash h and committee multisignature signer.
|
||||||
func (e *Executor) CommitteeInvoker(h util.Uint160) *ContractInvoker {
|
func (e *Executor) CommitteeInvoker(h util.Uint160) *ContractInvoker {
|
||||||
return &ContractInvoker{
|
return &ContractInvoker{
|
||||||
Executor: e,
|
Executor: e,
|
||||||
|
@ -27,6 +28,15 @@ func (e *Executor) CommitteeInvoker(h util.Uint160) *ContractInvoker {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ValidatorInvoker creates new ContractInvoker for contract with hash h and validators multisignature signer.
|
||||||
|
func (e *Executor) ValidatorInvoker(h util.Uint160) *ContractInvoker {
|
||||||
|
return &ContractInvoker{
|
||||||
|
Executor: e,
|
||||||
|
Hash: h,
|
||||||
|
Signers: []Signer{e.Validator},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestInvoke creates test VM and invokes method with args.
|
// TestInvoke creates test VM and invokes method with args.
|
||||||
func (c *ContractInvoker) TestInvoke(t *testing.T, method string, args ...interface{}) (*vm.Stack, error) {
|
func (c *ContractInvoker) TestInvoke(t *testing.T, method string, args ...interface{}) (*vm.Stack, error) {
|
||||||
tx := c.PrepareInvokeNoSign(t, method, args...)
|
tx := c.PrepareInvokeNoSign(t, method, args...)
|
||||||
|
@ -65,6 +75,20 @@ func (c *ContractInvoker) Invoke(t *testing.T, result interface{}, method string
|
||||||
return tx.Hash()
|
return tx.Hash()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InvokeAndCheck invokes method with args, persists transaction and checks the result
|
||||||
|
// using provided function. Returns transaction hash.
|
||||||
|
func (c *ContractInvoker) InvokeAndCheck(t *testing.T, checkResult func(t *testing.T, stack []stackitem.Item), method string, args ...interface{}) util.Uint256 {
|
||||||
|
tx := c.PrepareInvoke(t, method, args...)
|
||||||
|
c.AddNewBlock(t, tx)
|
||||||
|
aer, err := c.Chain.GetAppExecResults(tx.Hash(), trigger.Application)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, vm.HaltState, aer[0].VMState, aer[0].FaultException)
|
||||||
|
if checkResult != nil {
|
||||||
|
checkResult(t, aer[0].Stack)
|
||||||
|
}
|
||||||
|
return tx.Hash()
|
||||||
|
}
|
||||||
|
|
||||||
// InvokeWithFeeFail is like InvokeFail but sets custom system fee for the transaction.
|
// InvokeWithFeeFail is like InvokeFail but sets custom system fee for the transaction.
|
||||||
func (c *ContractInvoker) InvokeWithFeeFail(t *testing.T, message string, sysFee int64, method string, args ...interface{}) util.Uint256 {
|
func (c *ContractInvoker) InvokeWithFeeFail(t *testing.T, message string, sysFee int64, method string, args ...interface{}) util.Uint256 {
|
||||||
tx := c.PrepareInvokeNoSign(t, method, args...)
|
tx := c.PrepareInvokeNoSign(t, method, args...)
|
||||||
|
|
2
pkg/rpc/server/testdata/test_contract.go
vendored
2
pkg/rpc/server/testdata/test_contract.go
vendored
|
@ -23,7 +23,7 @@ func Init() bool {
|
||||||
h := runtime.GetExecutingScriptHash()
|
h := runtime.GetExecutingScriptHash()
|
||||||
amount := totalSupply
|
amount := totalSupply
|
||||||
storage.Put(ctx, h, amount)
|
storage.Put(ctx, h, amount)
|
||||||
runtime.Notify("Transfer", []byte{}, h, amount)
|
runtime.Notify("Transfer", interop.Hash160([]byte{}), h, amount)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -76,12 +76,30 @@ func bigInt(w *io.BinWriter, n *big.Int) {
|
||||||
|
|
||||||
// Array emits array of elements to the given buffer.
|
// Array emits array of elements to the given buffer.
|
||||||
func Array(w *io.BinWriter, es ...interface{}) {
|
func Array(w *io.BinWriter, es ...interface{}) {
|
||||||
|
if len(es) == 0 {
|
||||||
|
Opcodes(w, opcode.NEWARRAY0)
|
||||||
|
return
|
||||||
|
}
|
||||||
for i := len(es) - 1; i >= 0; i-- {
|
for i := len(es) - 1; i >= 0; i-- {
|
||||||
switch e := es[i].(type) {
|
switch e := es[i].(type) {
|
||||||
case []interface{}:
|
case []interface{}:
|
||||||
Array(w, e...)
|
Array(w, e...)
|
||||||
case int64:
|
case int64:
|
||||||
Int(w, e)
|
Int(w, e)
|
||||||
|
case int32:
|
||||||
|
Int(w, int64(e))
|
||||||
|
case uint32:
|
||||||
|
Int(w, int64(e))
|
||||||
|
case int16:
|
||||||
|
Int(w, int64(e))
|
||||||
|
case uint16:
|
||||||
|
Int(w, int64(e))
|
||||||
|
case int8:
|
||||||
|
Int(w, int64(e))
|
||||||
|
case uint8:
|
||||||
|
Int(w, int64(e))
|
||||||
|
case int:
|
||||||
|
Int(w, int64(e))
|
||||||
case *big.Int:
|
case *big.Int:
|
||||||
bigInt(w, e)
|
bigInt(w, e)
|
||||||
case string:
|
case string:
|
||||||
|
|
|
@ -177,7 +177,7 @@ func TestEmitArray(t *testing.T) {
|
||||||
buf := io.NewBufBinWriter()
|
buf := io.NewBufBinWriter()
|
||||||
Array(buf.BinWriter)
|
Array(buf.BinWriter)
|
||||||
require.NoError(t, buf.Err)
|
require.NoError(t, buf.Err)
|
||||||
assert.EqualValues(t, []byte{byte(opcode.PUSH0), byte(opcode.PACK)}, buf.Bytes())
|
assert.EqualValues(t, []byte{byte(opcode.NEWARRAY0)}, buf.Bytes())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("invalid type", func(t *testing.T) {
|
t.Run("invalid type", func(t *testing.T) {
|
||||||
|
|
Loading…
Reference in a new issue