Merge pull request #2299 from nspcc-dev/tests-migration

core: migrate native contract API tests to neotest framework
This commit is contained in:
Roman Khimov 2022-01-14 20:10:17 +03:00 committed by GitHub
commit 0b0531d723
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 2462 additions and 2273 deletions

View file

@ -1116,7 +1116,7 @@ func TestVerifyTx(t *testing.T) {
func TestVerifyHashAgainstScript(t *testing.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)
require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs))
require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, csInvalid))
@ -1681,7 +1681,7 @@ func TestRemoveUntraceable(t *testing.T) {
func TestInvalidNotification(t *testing.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))
aer, err := invokeContractMethod(bc, 1_00000000, cs.Hash, "invalidStack")
@ -1695,7 +1695,7 @@ func TestInvalidNotification(t *testing.T) {
func TestMPTDeleteNoKey(t *testing.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))
aer, err := invokeContractMethod(bc, 1_00000000, cs.Hash, "delValue", "non-existent-key")
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,
}}
}

View file

@ -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) {
b := chain.newBlock(txs...)
err := chain.AddBlock(b)
@ -716,19 +710,6 @@ func invokeContractMethodGeneric(chain *Blockchain, sysfee int64, hash util.Uint
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{}) {
transferTx := transferTokenFromMultisigAccount(t, chain, to, tokenHash, amount, additionalArgs...)
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])
}
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) {
require.Equal(t, vm.FaultState, result.VMState)
}

View file

@ -1,10 +1,14 @@
package core
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"math"
"math/big"
"os"
"path/filepath"
"testing"
"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/opcode"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/stretchr/testify/require"
)
@ -231,7 +236,7 @@ func TestRuntimeGetNotifications(t *testing.T) {
func TestRuntimeGetInvocationCounter(t *testing.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))
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
}
func createVMAndTX(t *testing.T) (*vm.VM, *transaction.Transaction, *interop.Context, *Blockchain) {
script := []byte{byte(opcode.PUSH1), byte(opcode.RET)}
tx := transaction.New(script, 0)
tx.Signers = []transaction.Signer{{Account: util.Uint160{1, 2, 3, 4}}}
tx.Scripts = []transaction.Witness{{InvocationScript: []byte{}, VerificationScript: []byte{}}}
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
}
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")
)
// getTestContractState returns 2 contracts second of which is allowed to call the first.
func getTestContractState(bc *Blockchain) (*state.Contract, *state.Contract) {
mgmtHash := bc.ManagementContractHash()
// TestGenerateManagementHelperContracts generates 2 helper contracts second of which is
// allowed to call the first. It uses test chain to define Management and StdLib
// 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
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()
emit.Opcodes(w.BinWriter, opcode.ABORT)
@ -771,7 +782,6 @@ func getTestContractState(bc *Blockchain) (*state.Contract, *state.Contract) {
emit.Opcodes(w.BinWriter, opcode.RET)
script := w.Bytes()
h := hash.Hash160(script)
m := manifest.NewManifest("TestMain")
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].Methods.Add("method")
cs := &state.Contract{
ContractBase: state.ContractBase{
Hash: h,
Manifest: *m,
ID: 42,
},
}
// Generate NEF file.
ne, err := nef.NewFile(script)
if err != nil {
panic(err)
}
ne.Tokens = []nef.MethodToken{
{
Hash: bc.contracts.NEO.Hash,
Hash: neoHash,
Method: "balanceOf",
ParamCount: 1,
HasReturn: true,
@ -980,7 +984,25 @@ func getTestContractState(bc *Blockchain) (*state.Contract, *state.Contract) {
},
}
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)}
m = manifest.NewManifest("TestAux")
@ -997,14 +1019,74 @@ func getTestContractState(bc *Blockchain) (*state.Contract, *state.Contract) {
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{
NEF: *ne,
Hash: hash.Hash160(currScript),
NEF: ne,
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{}) {
@ -1028,12 +1110,12 @@ func loadScriptWithHashAndFlags(ic *interop.Context, script []byte, hash util.Ui
func TestContractCall(t *testing.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, currCs))
currScript := currCs.NEF.Script
h := hash.Hash160(cs.NEF.Script)
h := cs.Hash
addArgs := stackitem.NewArray([]stackitem.Item{stackitem.Make(1), stackitem.Make(2)})
t.Run("Good", func(t *testing.T) {
@ -1420,7 +1502,7 @@ func TestRuntimeCheckWitness(t *testing.T) {
func TestLoadToken(t *testing.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))
t.Run("good", func(t *testing.T) {
@ -1463,7 +1545,7 @@ func TestRuntimeGetNetwork(t *testing.T) {
func TestRuntimeBurnGas(t *testing.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))
const sysFee = 2_000000

View 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))
}
}

View 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)
})
}

View 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)
}

View file

@ -0,0 +1,5 @@
package policyhelper
func Do() bool {
return true
}

View file

@ -0,0 +1,4 @@
name: "Policy helper contract"
sourceurl: https://github.com/nspcc-dev/neo-go
supportedstandards: []
safemethods: ["do"]

View 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())
})
}

View 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())
})
})
}

View 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))
})
}

View 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)
}
}

View 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 '_')")
})
})
}

View 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")
})
}

View file

@ -13,7 +13,6 @@ import (
"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/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/noderoles"
"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/slice"
"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"
)
@ -89,11 +87,7 @@ func newOracle() *Oracle {
defer o.UpdateHash()
w := io.NewBufBinWriter()
emit.Opcodes(w.BinWriter, opcode.NEWARRAY0)
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)
emit.AppCall(w.BinWriter, o.Hash, "finish", callflag.All)
o.oracleScript = w.Bytes()
desc := newDescriptor("request", smartcontract.VoidType,

View file

@ -5,6 +5,7 @@ import (
"math/big"
"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/fee"
"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))
baseFee := chain.GetBaseExecFee()

View file

@ -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) {
bc := newTestChain(t)

View file

@ -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)
}

View file

@ -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{})
})
}

View file

@ -1,617 +1,19 @@
package core
import (
"bytes"
"encoding/json"
"math/big"
"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/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/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"
)
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 {
*storage.MemoryStore
}
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) {
chain := newTestChain(t)
@ -619,8 +21,6 @@ func TestMinimumDeploymentFee(t *testing.T) {
n := chain.contracts.Management.GetMinimumDeploymentFee(chain.dao)
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) {

View file

@ -2,483 +2,23 @@ package core
import (
"fmt"
"math"
"math/big"
"sort"
"testing"
"github.com/nspcc-dev/neo-go/internal/random"
"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/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 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) {
var stores = map[string]func(testing.TB) storage.Store{
"MemPS": func(t testing.TB) storage.Store {

View file

@ -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))
}

View file

@ -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)
}

View file

@ -1,16 +1,12 @@
package core
import (
"math/big"
"testing"
"github.com/nspcc-dev/neo-go/internal/random"
"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"
"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"
)
@ -19,68 +15,6 @@ func transferFundsToCommittee(t *testing.T, chain *Blockchain) {
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) {
chain := newTestChain(t)
@ -88,8 +22,6 @@ func TestFeePerByte(t *testing.T) {
n := chain.contracts.Policy.GetFeePerByteInternal(chain.dao)
require.Equal(t, 1000, int(n))
})
testGetSet(t, chain, chain.contracts.Policy.Hash, "FeePerByte", 1000, 0, 100_000_000)
}
func TestExecFeeFactor(t *testing.T) {
@ -99,8 +31,6 @@ func TestExecFeeFactor(t *testing.T) {
n := chain.contracts.Policy.GetExecFeeFactorInternal(chain.dao)
require.EqualValues(t, interop.DefaultBaseExecFee, n)
})
testGetSet(t, chain, chain.contracts.Policy.Hash, "ExecFeeFactor", interop.DefaultBaseExecFee, 1, 1000)
}
func TestStoragePrice(t *testing.T) {
@ -110,15 +40,10 @@ func TestStoragePrice(t *testing.T) {
n := chain.contracts.Policy.GetStoragePriceInternal(chain.dao)
require.Equal(t, int64(native.DefaultStoragePrice), n)
})
testGetSet(t, chain, chain.contracts.Policy.Hash, "StoragePrice", native.DefaultStoragePrice, 1, 10000000)
}
func TestBlockedAccounts(t *testing.T) {
chain := newTestChain(t)
account := util.Uint160{1, 2, 3}
policyHash := chain.contracts.Policy.Metadata().Hash
transferTokenFromMultisigAccount(t, chain, testchain.CommitteeScriptHash(),
chain.contracts.GAS.Hash, 100_00000000)
@ -126,100 +51,4 @@ func TestBlockedAccounts(t *testing.T) {
isBlocked := chain.contracts.Policy.IsBlockedInternal(chain.dao, random.Uint160())
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{})
})
}

View file

@ -2,11 +2,14 @@ package core
import (
"bytes"
"encoding/json"
"errors"
"fmt"
gio "io"
"io/ioutil"
"net/http"
"net/url"
"os"
"path"
"path/filepath"
"strings"
@ -16,20 +19,164 @@ import (
"github.com/nspcc-dev/neo-go/pkg/config"
"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/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/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/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/wallet"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"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 {
return oracle.Config{
@ -133,7 +280,7 @@ func TestOracle(t *testing.T) {
orc1.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))
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) }
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))
go bc.Run()
@ -326,7 +473,7 @@ func TestNotYetRunningOracle(t *testing.T) {
orc.OnTransaction = func(tx *transaction.Transaction) { _ = mp.Add(tx, bc) }
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))
go bc.Run()

View 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`.

View 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}

Binary file not shown.

View 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}

Binary file not shown.

View 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`.

View 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}

Binary file not shown.

View file

@ -3,6 +3,7 @@ package neotest
import (
"encoding/json"
"fmt"
"math/big"
"strings"
"testing"
@ -62,6 +63,14 @@ func (e *Executor) NativeHash(t *testing.T, name string) util.Uint160 {
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.
func (e *Executor) NewUnsignedTx(t *testing.T, hash util.Uint160, method string, args ...interface{}) *transaction.Transaction {
w := io.NewBufBinWriter()
@ -100,15 +109,19 @@ func (e *Executor) SignTx(t *testing.T, tx *transaction.Transaction, sysFee int6
return tx
}
// NewAccount returns new signer holding 100.0 GAS. This method advances the chain
// by one block with a transfer transaction.
func (e *Executor) NewAccount(t *testing.T) Signer {
// NewAccount returns new signer holding 100.0 GAS (or given amount is specified).
// This method advances the chain by one block with a transfer transaction.
func (e *Executor) NewAccount(t *testing.T, expectedGASBalance ...int64) Signer {
acc, err := wallet.NewAccount()
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.Committee.ScriptHash(), acc.Contract.ScriptHash(), int64(100_0000_0000), nil)
e.Validator.ScriptHash(), acc.Contract.ScriptHash(), amount, nil)
e.AddNewBlock(t, tx)
e.CheckHalt(t, tx.Hash())
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])
}
// 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.
func (e *Executor) NewDeployTx(t *testing.T, bc blockchainer.Blockchainer, c *Contract, data interface{}) *transaction.Transaction {
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.ValidUntilBlock = bc.BlockHeight() + 1
tx.Signers = []transaction.Signer{{
Account: e.Committee.ScriptHash(),
Account: e.Validator.ScriptHash(),
Scopes: transaction.Global,
}}
addNetworkFee(bc, tx, e.Committee)
require.NoError(t, e.Committee.SignTx(netmode.UnitTestNet, tx))
addNetworkFee(bc, tx, e.Validator)
require.NoError(t, e.Validator.SignTx(netmode.UnitTestNet, tx))
return tx
}
@ -277,6 +296,13 @@ func (e *Executor) AddNewBlock(t *testing.T, txs ...*transaction.Transaction) *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.
func (e *Executor) SignBlock(b *block.Block) *block.Block {
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()
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]
}

View file

@ -108,27 +108,49 @@ func init() {
// NewSingle creates new blockchain instance with a single validator and
// setups cleanup functions.
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{
Magic: netmode.UnitTestNet,
MaxTraceableBlocks: 1000, // We don't need a lot of traceable blocks for tests.
SecondsPerBlock: 1,
StandbyCommittee: []string{hex.EncodeToString(committeeAcc.PrivateKey().PublicKey().Bytes())},
ValidatorsCount: 1,
VerifyBlocks: true,
VerifyTransactions: true,
}
st := storage.NewMemoryStore()
if f != nil {
f(&protoCfg)
}
log := zaptest.NewLogger(t)
bc, err := core.NewBlockchain(st, protoCfg, log)
require.NoError(t, err)
go bc.Run()
t.Cleanup(bc.Close)
if run {
go bc.Run()
t.Cleanup(bc.Close)
}
return bc, neotest.NewMultiSigner(committeeAcc)
}
// NewMulti creates new blockchain instance with 4 validators and 6 committee members.
// Second return value is for validator signer, third -- for committee.
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{
Magic: netmode.UnitTestNet,
SecondsPerBlock: 1,
@ -137,6 +159,9 @@ func NewMulti(t *testing.T) (*core.Blockchain, neotest.Signer, neotest.Signer) {
VerifyBlocks: true,
VerifyTransactions: true,
}
if f != nil {
f(&protoCfg)
}
st := storage.NewMemoryStore()
log := zaptest.NewLogger(t)

View file

@ -9,6 +9,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/stretchr/testify/require"
)
// ContractInvoker is a client for specific contract.
@ -18,7 +19,7 @@ type ContractInvoker struct {
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 {
return &ContractInvoker{
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.
func (c *ContractInvoker) TestInvoke(t *testing.T, method string, args ...interface{}) (*vm.Stack, error) {
tx := c.PrepareInvokeNoSign(t, method, args...)
@ -65,6 +75,20 @@ func (c *ContractInvoker) Invoke(t *testing.T, result interface{}, method string
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.
func (c *ContractInvoker) InvokeWithFeeFail(t *testing.T, message string, sysFee int64, method string, args ...interface{}) util.Uint256 {
tx := c.PrepareInvokeNoSign(t, method, args...)

View file

@ -23,7 +23,7 @@ func Init() bool {
h := runtime.GetExecutingScriptHash()
amount := totalSupply
storage.Put(ctx, h, amount)
runtime.Notify("Transfer", []byte{}, h, amount)
runtime.Notify("Transfer", interop.Hash160([]byte{}), h, amount)
return true
}

View file

@ -76,12 +76,30 @@ func bigInt(w *io.BinWriter, n *big.Int) {
// Array emits array of elements to the given buffer.
func Array(w *io.BinWriter, es ...interface{}) {
if len(es) == 0 {
Opcodes(w, opcode.NEWARRAY0)
return
}
for i := len(es) - 1; i >= 0; i-- {
switch e := es[i].(type) {
case []interface{}:
Array(w, e...)
case int64:
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:
bigInt(w, e)
case string:

View file

@ -177,7 +177,7 @@ func TestEmitArray(t *testing.T) {
buf := io.NewBufBinWriter()
Array(buf.BinWriter)
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) {