2022-06-08 12:17:40 +00:00
package basicchain
import (
"encoding/base64"
"encoding/hex"
"math/big"
"path"
"path/filepath"
"testing"
2024-06-05 14:30:10 +00:00
"github.com/nspcc-dev/neo-go/pkg/config"
2022-06-08 12:17:40 +00:00
"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/encoding/fixedn"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/neotest"
2022-07-21 19:39:53 +00:00
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nns"
2022-06-08 12:17:40 +00:00
"github.com/nspcc-dev/neo-go/pkg/util"
2022-11-16 09:35:26 +00:00
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
2022-06-08 12:17:40 +00:00
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/stretchr/testify/require"
)
const neoAmount = 99999000
2022-10-20 10:59:19 +00:00
// Various contract IDs that were deployed to basic chain.
const (
RublesContractID = int32 ( 1 )
VerifyContractID = int32 ( 2 )
VerifyWithArgsContractID = int32 ( 3 )
NNSContractID = int32 ( 4 )
NFSOContractID = int32 ( 5 )
StorageContractID = int32 ( 6 )
)
const (
// RublesOldTestvalue is a value initially stored by `testkey` key inside Rubles contract.
RublesOldTestvalue = "testvalue"
// RublesNewTestvalue is an updated value of Rubles' storage item with `testkey` key.
RublesNewTestvalue = "newtestvalue"
)
2022-10-04 10:05:51 +00:00
// InitSimple initializes chain with simple contracts from 'examples' folder.
2022-10-03 12:05:48 +00:00
// It's not as complicated as chain got after Init and may be used for tests where
// chain with a small amount of data is needed and for historical functionality testing.
// Needs a path to the root directory.
func InitSimple ( t * testing . T , rootpath string , e * neotest . Executor ) {
// examplesPrefix is a prefix of the example smart-contracts.
var examplesPrefix = filepath . Join ( rootpath , "examples" )
2022-10-04 10:05:51 +00:00
deployExample := func ( t * testing . T , name string ) util . Uint160 {
_ , h := newDeployTx ( t , e , e . Validator ,
filepath . Join ( examplesPrefix , name , name + ".go" ) ,
filepath . Join ( examplesPrefix , name , name + ".yml" ) ,
true )
return h
}
2022-10-03 12:05:48 +00:00
// Block #1: deploy storage contract (examples/storage/storage.go).
2022-10-04 10:05:51 +00:00
storageHash := deployExample ( t , "storage" )
storageValidatorInvoker := e . ValidatorInvoker ( storageHash )
2022-10-03 12:05:48 +00:00
// Block #2: put (1, 1) kv pair.
storageValidatorInvoker . Invoke ( t , 1 , "put" , 1 , 1 )
// Block #3: put (2, 2) kv pair.
storageValidatorInvoker . Invoke ( t , 2 , "put" , 2 , 2 )
// Block #4: update (1, 1) -> (1, 2).
storageValidatorInvoker . Invoke ( t , 1 , "put" , 1 , 2 )
2022-10-04 10:05:51 +00:00
// Block #5: deploy runtime contract (examples/runtime/runtime.go).
_ = deployExample ( t , "runtime" )
2022-10-03 12:05:48 +00:00
}
2022-06-08 12:17:40 +00:00
// Init pushes some predefined set of transactions into the given chain, it needs a path to
// the root project directory.
func Init ( t * testing . T , rootpath string , e * neotest . Executor ) {
if ! e . Chain . GetConfig ( ) . P2PSigExtensions {
t . Fatal ( "P2PSigExtensions should be enabled to init basic chain" )
}
2024-06-05 14:30:10 +00:00
const notaryDepositHeight uint32 = 8
domovoiH , ok := e . Chain . GetConfig ( ) . Hardforks [ config . HFDomovoi . String ( ) ]
require . Truef ( t , ok , "%s hardfork should be enabled since basic chain uses Notary contract" , config . HFDomovoi . String ( ) )
require . LessOrEqualf ( t , domovoiH , notaryDepositHeight - 1 , "%s hardfork should be enabled starting from height %d, got: %d" , config . HFDomovoi . String ( ) , notaryDepositHeight - 1 , domovoiH )
2022-06-08 12:17:40 +00:00
var (
// examplesPrefix is a prefix of the example smart-contracts.
examplesPrefix = filepath . Join ( rootpath , "examples" )
// testDataPrefix is used to retrieve smart contracts that should be deployed to
// Basic chain.
testDataPrefix = filepath . Join ( rootpath , "internal" , "basicchain" , "testdata" )
notaryModulePath = filepath . Join ( rootpath , "pkg" , "services" , "notary" )
)
gasHash := e . NativeHash ( t , nativenames . Gas )
neoHash := e . NativeHash ( t , nativenames . Neo )
policyHash := e . NativeHash ( t , nativenames . Policy )
notaryHash := e . NativeHash ( t , nativenames . Notary )
designationHash := e . NativeHash ( t , nativenames . Designation )
t . Logf ( "native GAS hash: %v" , gasHash )
t . Logf ( "native NEO hash: %v" , neoHash )
t . Logf ( "native Policy hash: %v" , policyHash )
t . Logf ( "native Notary hash: %v" , notaryHash )
t . Logf ( "Block0 hash: %s" , e . Chain . GetHeaderHash ( 0 ) . StringLE ( ) )
acc0 := e . Validator . ( neotest . MultiSigner ) . Single ( 2 ) // priv0 index->order and order->index conversion
priv0ScriptHash := acc0 . ScriptHash ( )
acc1 := e . Validator . ( neotest . MultiSigner ) . Single ( 0 ) // priv1 index->order and order->index conversion
priv1ScriptHash := acc1 . ScriptHash ( )
neoValidatorInvoker := e . ValidatorInvoker ( neoHash )
gasValidatorInvoker := e . ValidatorInvoker ( gasHash )
neoPriv0Invoker := e . NewInvoker ( neoHash , acc0 )
gasPriv0Invoker := e . NewInvoker ( gasHash , acc0 )
designateSuperInvoker := e . NewInvoker ( designationHash , e . Validator , e . Committee )
deployContractFromPriv0 := func ( t * testing . T , path , contractName string , configPath string , expectedID int32 ) ( util . Uint256 , util . Uint256 , util . Uint160 ) {
txDeployHash , cH := newDeployTx ( t , e , acc0 , path , configPath , true )
b := e . TopBlock ( t )
return b . Hash ( ) , txDeployHash , cH
}
e . CheckGASBalance ( t , priv0ScriptHash , big . NewInt ( 5000_0000 ) ) // gas bounty
// Block #1: move 1000 GAS and neoAmount NEO to priv0.
txMoveNeo := neoValidatorInvoker . PrepareInvoke ( t , "transfer" , e . Validator . ScriptHash ( ) , priv0ScriptHash , neoAmount , nil )
txMoveGas := gasValidatorInvoker . PrepareInvoke ( t , "transfer" , e . Validator . ScriptHash ( ) , priv0ScriptHash , int64 ( fixedn . Fixed8FromInt64 ( 1000 ) ) , nil )
b := e . AddNewBlock ( t , txMoveNeo , txMoveGas )
e . CheckHalt ( t , txMoveNeo . Hash ( ) , stackitem . Make ( true ) )
e . CheckHalt ( t , txMoveGas . Hash ( ) , stackitem . Make ( true ) )
t . Logf ( "Block1 hash: %s" , b . Hash ( ) . StringLE ( ) )
bw := io . NewBufBinWriter ( )
b . EncodeBinary ( bw . BinWriter )
require . NoError ( t , bw . Err )
jsonB , err := b . MarshalJSON ( )
require . NoError ( t , err )
t . Logf ( "Block1 base64: %s" , base64 . StdEncoding . EncodeToString ( bw . Bytes ( ) ) )
t . Logf ( "Block1 JSON: %s" , string ( jsonB ) )
bw . Reset ( )
b . Header . EncodeBinary ( bw . BinWriter )
require . NoError ( t , bw . Err )
jsonH , err := b . Header . MarshalJSON ( )
require . NoError ( t , err )
t . Logf ( "Header1 base64: %s" , base64 . StdEncoding . EncodeToString ( bw . Bytes ( ) ) )
t . Logf ( "Header1 JSON: %s" , string ( jsonH ) )
jsonTxMoveNeo , err := txMoveNeo . MarshalJSON ( )
require . NoError ( t , err )
t . Logf ( "txMoveNeo hash: %s" , txMoveNeo . Hash ( ) . StringLE ( ) )
t . Logf ( "txMoveNeo JSON: %s" , string ( jsonTxMoveNeo ) )
t . Logf ( "txMoveNeo base64: %s" , base64 . StdEncoding . EncodeToString ( txMoveNeo . Bytes ( ) ) )
t . Logf ( "txMoveGas hash: %s" , txMoveGas . Hash ( ) . StringLE ( ) )
e . EnsureGASBalance ( t , priv0ScriptHash , func ( balance * big . Int ) bool { return balance . Cmp ( big . NewInt ( 1000 * native . GASFactor ) ) >= 0 } )
// info for getblockheader rpc tests
t . Logf ( "header hash: %s" , b . Hash ( ) . StringLE ( ) )
buf := io . NewBufBinWriter ( )
b . Header . EncodeBinary ( buf . BinWriter )
t . Logf ( "header: %s" , hex . EncodeToString ( buf . Bytes ( ) ) )
// Block #2: deploy test_contract (Rubles contract).
cfgPath := filepath . Join ( testDataPrefix , "test_contract.yml" )
2022-10-20 10:59:19 +00:00
block2H , txDeployH , cHash := deployContractFromPriv0 ( t , filepath . Join ( testDataPrefix , "test_contract.go" ) , "Rubl" , cfgPath , RublesContractID )
2022-06-08 12:17:40 +00:00
t . Logf ( "txDeploy: %s" , txDeployH . StringLE ( ) )
t . Logf ( "Block2 hash: %s" , block2H . StringLE ( ) )
// Block #3: invoke `putValue` method on the test_contract.
rublPriv0Invoker := e . NewInvoker ( cHash , acc0 )
2022-10-20 10:59:19 +00:00
txInvH := rublPriv0Invoker . Invoke ( t , true , "putValue" , "testkey" , RublesOldTestvalue )
2022-06-08 12:17:40 +00:00
t . Logf ( "txInv: %s" , txInvH . StringLE ( ) )
// Block #4: transfer 1000 NEO from priv0 to priv1.
neoPriv0Invoker . Invoke ( t , true , "transfer" , priv0ScriptHash , priv1ScriptHash , 1000 , nil )
// Block #5: initialize rubles contract and transfer 1000 rubles from the contract to priv0.
initTx := rublPriv0Invoker . PrepareInvoke ( t , "init" )
transferTx := e . NewUnsignedTx ( t , rublPriv0Invoker . Hash , "transfer" , cHash , priv0ScriptHash , 1000 , nil )
e . SignTx ( t , transferTx , 1500_0000 , acc0 ) // Set system fee manually to avoid verification failure.
e . AddNewBlock ( t , initTx , transferTx )
e . CheckHalt ( t , initTx . Hash ( ) , stackitem . NewBool ( true ) )
e . CheckHalt ( t , transferTx . Hash ( ) , stackitem . Make ( true ) )
t . Logf ( "receiveRublesTx: %v" , transferTx . Hash ( ) . StringLE ( ) )
// Block #6: transfer 123 rubles from priv0 to priv1
transferTxH := rublPriv0Invoker . Invoke ( t , true , "transfer" , priv0ScriptHash , priv1ScriptHash , 123 , nil )
t . Logf ( "sendRublesTx: %v" , transferTxH . StringLE ( ) )
// Block #7: push verification contract into the chain.
verifyPath := filepath . Join ( testDataPrefix , "verify" , "verification_contract.go" )
verifyCfg := filepath . Join ( testDataPrefix , "verify" , "verification_contract.yml" )
2022-10-20 10:59:19 +00:00
_ , _ , _ = deployContractFromPriv0 ( t , verifyPath , "Verify" , verifyCfg , VerifyContractID )
2022-06-08 12:17:40 +00:00
// Block #8: deposit some GAS to notary contract for priv0.
2023-04-03 10:34:24 +00:00
transferTxH = gasPriv0Invoker . Invoke ( t , true , "transfer" , priv0ScriptHash , notaryHash , 10_0000_0000 , [ ] any { priv0ScriptHash , int64 ( e . Chain . BlockHeight ( ) + 1000 ) } )
2022-06-08 12:17:40 +00:00
t . Logf ( "notaryDepositTxPriv0: %v" , transferTxH . StringLE ( ) )
2024-06-05 14:30:10 +00:00
require . Equal ( t , uint32 ( notaryDepositHeight ) , e . Chain . BlockHeight ( ) , "notaryDepositHeight constant is out of date" )
2022-06-08 12:17:40 +00:00
// Block #9: designate new Notary node.
ntr , err := wallet . NewWalletFromFile ( path . Join ( notaryModulePath , "./testdata/notary1.json" ) )
require . NoError ( t , err )
require . NoError ( t , ntr . Accounts [ 0 ] . Decrypt ( "one" , ntr . Scrypt ) )
designateSuperInvoker . Invoke ( t , stackitem . Null { } , "designateAsRole" ,
2023-04-03 10:34:24 +00:00
int64 ( noderoles . P2PNotary ) , [ ] any { ntr . Accounts [ 0 ] . PublicKey ( ) . Bytes ( ) } )
2024-04-09 09:13:43 +00:00
t . Logf ( "Designated Notary node: %s" , ntr . Accounts [ 0 ] . PublicKey ( ) . StringCompressed ( ) )
2022-06-08 12:17:40 +00:00
// Block #10: push verification contract with arguments into the chain.
verifyPath = filepath . Join ( testDataPrefix , "verify_args" , "verification_with_args_contract.go" )
verifyCfg = filepath . Join ( testDataPrefix , "verify_args" , "verification_with_args_contract.yml" )
2022-10-20 10:59:19 +00:00
_ , _ , _ = deployContractFromPriv0 ( t , verifyPath , "VerifyWithArgs" , verifyCfg , VerifyWithArgsContractID ) // block #10
2022-06-08 12:17:40 +00:00
// Block #11: push NameService contract into the chain.
nsPath := filepath . Join ( examplesPrefix , "nft-nd-nns" )
nsConfigPath := filepath . Join ( nsPath , "nns.yml" )
2022-10-20 10:59:19 +00:00
_ , _ , nsHash := deployContractFromPriv0 ( t , nsPath , nsPath , nsConfigPath , NNSContractID ) // block #11
2022-06-08 12:17:40 +00:00
nsCommitteeInvoker := e . CommitteeInvoker ( nsHash )
nsPriv0Invoker := e . NewInvoker ( nsHash , acc0 )
// Block #12: transfer funds to committee for further NS record registration.
gasValidatorInvoker . Invoke ( t , true , "transfer" ,
e . Validator . ScriptHash ( ) , e . Committee . ScriptHash ( ) , 1000_00000000 , nil ) // block #12
// Block #13: add `.com` root to NNS.
nsCommitteeInvoker . Invoke ( t , stackitem . Null { } , "addRoot" , "com" ) // block #13
// Block #14: register `neo.com` via NNS.
registerTxH := nsPriv0Invoker . Invoke ( t , true , "register" ,
"neo.com" , priv0ScriptHash ) // block #14
res := e . GetTxExecResult ( t , registerTxH )
require . Equal ( t , 1 , len ( res . Events ) ) // transfer
tokenID , err := res . Events [ 0 ] . Item . Value ( ) . ( [ ] stackitem . Item ) [ 3 ] . TryBytes ( )
require . NoError ( t , err )
t . Logf ( "NNS token #1 ID (hex): %s" , hex . EncodeToString ( tokenID ) )
// Block #15: set A record type with priv0 owner via NNS.
nsPriv0Invoker . Invoke ( t , stackitem . Null { } , "setRecord" , "neo.com" , int64 ( nns . A ) , "1.2.3.4" ) // block #15
// Block #16: invoke `test_contract.go`: put new value with the same key to check `getstate` RPC call
2022-10-20 10:59:19 +00:00
txPutNewValue := rublPriv0Invoker . PrepareInvoke ( t , "putValue" , "testkey" , RublesNewTestvalue ) // tx1
2022-06-08 12:17:40 +00:00
// Invoke `test_contract.go`: put values to check `findstates` RPC call.
txPut1 := rublPriv0Invoker . PrepareInvoke ( t , "putValue" , "aa" , "v1" ) // tx2
txPut2 := rublPriv0Invoker . PrepareInvoke ( t , "putValue" , "aa10" , "v2" ) // tx3
txPut3 := rublPriv0Invoker . PrepareInvoke ( t , "putValue" , "aa50" , "v3" ) // tx4
e . AddNewBlock ( t , txPutNewValue , txPut1 , txPut2 , txPut3 ) // block #16
e . CheckHalt ( t , txPutNewValue . Hash ( ) , stackitem . NewBool ( true ) )
e . CheckHalt ( t , txPut1 . Hash ( ) , stackitem . NewBool ( true ) )
e . CheckHalt ( t , txPut2 . Hash ( ) , stackitem . NewBool ( true ) )
e . CheckHalt ( t , txPut3 . Hash ( ) , stackitem . NewBool ( true ) )
// Block #17: deploy NeoFS Object contract (NEP11-Divisible).
nfsPath := filepath . Join ( examplesPrefix , "nft-d" )
nfsConfigPath := filepath . Join ( nfsPath , "nft.yml" )
2022-10-20 10:59:19 +00:00
_ , _ , nfsHash := deployContractFromPriv0 ( t , nfsPath , nfsPath , nfsConfigPath , NFSOContractID ) // block #17
2022-06-08 12:17:40 +00:00
nfsPriv0Invoker := e . NewInvoker ( nfsHash , acc0 )
nfsPriv1Invoker := e . NewInvoker ( nfsHash , acc1 )
// Block #18: mint 1.00 NFSO token by transferring 10 GAS to NFSO contract.
containerID := util . Uint256 { 1 , 2 , 3 }
objectID := util . Uint256 { 4 , 5 , 6 }
txGas0toNFSH := gasPriv0Invoker . Invoke ( t , true , "transfer" ,
2023-04-03 10:34:24 +00:00
priv0ScriptHash , nfsHash , 10_0000_0000 , [ ] any { containerID . BytesBE ( ) , objectID . BytesBE ( ) } ) // block #18
2022-06-08 12:17:40 +00:00
res = e . GetTxExecResult ( t , txGas0toNFSH )
require . Equal ( t , 2 , len ( res . Events ) ) // GAS transfer + NFSO transfer
tokenID , err = res . Events [ 1 ] . Item . Value ( ) . ( [ ] stackitem . Item ) [ 3 ] . TryBytes ( )
require . NoError ( t , err )
t . Logf ( "NFSO token #1 ID (hex): %s" , hex . EncodeToString ( tokenID ) )
// Block #19: transfer 0.25 NFSO from priv0 to priv1.
nfsPriv0Invoker . Invoke ( t , true , "transfer" , priv0ScriptHash , priv1ScriptHash , 25 , tokenID , nil ) // block #19
// Block #20: transfer 1000 GAS to priv1.
gasValidatorInvoker . Invoke ( t , true , "transfer" , e . Validator . ScriptHash ( ) ,
priv1ScriptHash , int64 ( fixedn . Fixed8FromInt64 ( 1000 ) ) , nil ) // block #20
// Block #21: transfer 0.05 NFSO from priv1 back to priv0.
nfsPriv1Invoker . Invoke ( t , true , "transfer" , priv1ScriptHash , priv0ScriptHash , 5 , tokenID , nil ) // block #21
2022-06-15 18:23:29 +00:00
// Block #22: deploy storage_contract (Storage contract for `traverseiterator` and `terminatesession` RPC calls test).
storagePath := filepath . Join ( testDataPrefix , "storage" , "storage_contract.go" )
storageCfg := filepath . Join ( testDataPrefix , "storage" , "storage_contract.yml" )
2022-10-20 10:59:19 +00:00
_ , _ , _ = deployContractFromPriv0 ( t , storagePath , "Storage" , storageCfg , StorageContractID )
2022-06-15 18:23:29 +00:00
2022-11-16 09:35:26 +00:00
// Block #23: add FAULTed transaction to check WSClient waitloops.
faultedInvoker := e . NewInvoker ( cHash , acc0 )
faultedH := faultedInvoker . InvokeScriptCheckFAULT ( t , [ ] byte { byte ( opcode . ABORT ) } , [ ] neotest . Signer { acc0 } , "at instruction 0 (ABORT): ABORT" )
t . Logf ( "FAULTed transaction:\n\thash LE: %s\n\tblock index: %d" , faultedH . StringLE ( ) , e . Chain . BlockHeight ( ) )
2022-06-08 12:17:40 +00:00
// Compile contract to test `invokescript` RPC call
invokePath := filepath . Join ( testDataPrefix , "invoke" , "invokescript_contract.go" )
invokeCfg := filepath . Join ( testDataPrefix , "invoke" , "invoke.yml" )
_ , _ = newDeployTx ( t , e , acc0 , invokePath , invokeCfg , false )
// Prepare some transaction for future submission.
txSendRaw := neoPriv0Invoker . PrepareInvoke ( t , "transfer" , priv0ScriptHash , priv1ScriptHash , int64 ( fixedn . Fixed8FromInt64 ( 1000 ) ) , nil )
bw . Reset ( )
txSendRaw . EncodeBinary ( bw . BinWriter )
t . Logf ( "sendrawtransaction: \n\tbase64: %s\n\tHash LE: %s" , base64 . StdEncoding . EncodeToString ( bw . Bytes ( ) ) , txSendRaw . Hash ( ) . StringLE ( ) )
sr20 , err := e . Chain . GetStateModule ( ) . GetStateRoot ( 20 )
require . NoError ( t , err )
t . Logf ( "Block #20 stateroot LE: %s" , sr20 . Root . StringLE ( ) )
}
func newDeployTx ( t * testing . T , e * neotest . Executor , sender neotest . Signer , sourcePath , configPath string , deploy bool ) ( util . Uint256 , util . Uint160 ) {
c := neotest . CompileFile ( t , sender . ScriptHash ( ) , sourcePath , configPath )
t . Logf ( "contract (%s): \n\tHash: %s\n\tAVM: %s" , sourcePath , c . Hash . StringLE ( ) , base64 . StdEncoding . EncodeToString ( c . NEF . Script ) )
if deploy {
return e . DeployContractBy ( t , sender , c , nil ) , c . Hash
}
return util . Uint256 { } , c . Hash
}