diff --git a/README.md b/README.md index 9e579b4..c3f7ac5 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,31 @@ # NeoFS smart-contract -This smart-contract controls list of NeoFS Inner Ring nodes and provides -methods to deposit and withdraw assets. These assets are used as a payment and -a reward for data storage. - +This smart-contract controls list of NeoFS Inner Ring nodes, user assets in +NeoFS balance contract and stores NeoFS runtime configuration. ## Getting Started This repository contains: -- NeoFS smart-contract written in Go -- Unit tests for the smart-contract +- NeoFS smart-contract in Go ### Prerequisites To compile smart-contract you need: -- [neo-go](https://github.com/nspcc-dev/neo-go) >= 0.74.0 - - -To run tests you need: - -- [go](https://golang.org/dl/) >= 1.12 +- [neo-go](https://github.com/nspcc-dev/neo-go) >= 0.90.0 ## Compiling To compile smart contract run `make build` command. Compiled contract -`neofs_contract.avm` will be placed in the same directory. +`neofs_contract.nef` and manifest `config.json` will be placed in the same +directory. ``` -$ make build -neo-go contract compile -i neofs_contract.go -02a600c56b6a007bc46a517bc468164e656f2e52756e74696d652e476574547269676765726165880d9e640700006c756668164e656f2e53746f726167652e476574436f6e74657874616a527bc46a00c376064465706c6f798764c7016a52c30d496e6e657252696e674c6973747c657c0d6a537bc46a5 -3c3c000a0642f0019636f6e747261637420616c7265616479206465706c6f796564680f4e656f2e52756e74696d652e4c6f67f06a51c3c06a547bc46a54c35297009e6448003270726f76696465207061697273206f6620696e6e65722072696e67206164647265737320616e64207075626c6963206b65 -... -c46a00c36a59c37c6592fd6476006a52c36a5ac36a59c3ad6469006a58c38b6a587bc4006a5b7bc46a5bc36a57c3c09f6444006a57c36a5bc3c36a59c387642b00156475706c6963617465207075626c6963206b657973680f4e656f2e52756e74696d652e4c6f67f06a5bc38b6a5b7bc462b7ff6a57c36 -a59c3787cc86a577bc46a53c30161936a537bc46234ff6a58c36a56c3a2640700516c75661e6e6f7420656e6f756768207665726966696564207369676e617475726573680f4e656f2e52756e74696d652e4c6f6761006c7566 -$ ls neofs_contract.avm -neofs_contract.avm +$ make build +neo-go contract compile -i neofs_contract.go -c neofs_config.yml -m config.json +$ ls neofs_contract.nef config.json +config.json neofs_contract.nef ``` You can specify path to the `neo-go` binary with `NEOGO` environment variable: @@ -46,51 +34,6 @@ You can specify path to the `neo-go` binary with `NEOGO` environment variable: $ NEOGO=/home/user/neo-go/bin/neo-go make build ``` -## Running the tests - -`neofs_contract_test.go` file contains tests for most of the provided methods. -It compiles smart-contract and uses instance of the NeoVM to run -code. - -To test smart contract run `make tests` command. - -``` -$ make tests -go mod vendor -go test -mod=vendor -v -race github.com/nspcc-dev/neofs-contract -=== RUN TestContract - TestContract: neofs_contract_test.go:360: provide pairs of inner ring address and public key - TestContract: neofs_contract_test.go:360: contract already deployed -=== RUN TestContract/InnerRingAddress - TestContract: neofs_contract_test.go:360: target element has been removed -=== RUN TestContract/Deposit - TestContract: neofs_contract_test.go:360: funds have been transfered -=== RUN TestContract/Withdraw -=== RUN TestContract/Withdraw/Double_Withdraw - TestContract: neofs_contract_test.go:360: verification check has already been used -=== RUN TestContract/InnerRingCandidateAdd -=== RUN TestContract/InnerRingCandidateAdd/Double_InnerRingCandidateAdd - TestContract: neofs_contract_test.go:360: is already in list -=== RUN TestContract/InnerRingCandidateRemove -=== RUN TestContract/InnerRingCandidateRemove/Remove_unknown_candidate - TestContract: neofs_contract_test.go:360: target element has not been removed - TestContract: neofs_contract_test.go:360: target element has not been removed -=== RUN TestContract/InnerRingUpdate - TestContract/InnerRingUpdate: neofs_contract_test.go:174: implement getIRExcludeCheque without neofs-node dependency ---- PASS: TestContract (0.43s) - --- PASS: TestContract/InnerRingAddress (0.00s) - --- PASS: TestContract/Deposit (0.00s) - --- PASS: TestContract/Withdraw (0.01s) - --- PASS: TestContract/Withdraw/Double_Withdraw (0.00s) - --- PASS: TestContract/InnerRingCandidateAdd (0.00s) - --- PASS: TestContract/InnerRingCandidateAdd/Double_InnerRingCandidateAdd (0.00s) - --- PASS: TestContract/InnerRingCandidateRemove (0.00s) - --- PASS: TestContract/InnerRingCandidateRemove/Remove_unknown_candidate (0.00s) - --- SKIP: TestContract/InnerRingUpdate (0.00s) -PASS -ok github.com/nspcc-dev/neofs-contract 0.453s -``` - ## License This project is licensed under the GPLv3 License - see the diff --git a/neofs_contract.go b/neofs_contract.go index b9a5fe8..d252121 100644 --- a/neofs_contract.go +++ b/neofs_contract.go @@ -1,5 +1,42 @@ package smart_contract +/* + NeoFS Smart Contract for NEO3.0. + + Utility operations, executed once in deploy stage: + - Init(pubKey, ... ) - setup initial inner ring nodes + - InitConfig(key, value, key, value...) - setup initial NeoFS configuration + + User operations: + - Deposit(script-hash, amount, script-hash(?)) - deposit gas assets to this script-hash address to NeoFS balance + - Withdraw(script-hash, amount) - initialize gas asset withdraw from NeoFS balance + - Bind(script-hash, pubKeys...) - bind public key with user's account to use it in NeoFS requests + - Unbind(script-hash, pubKeys...) - unbind public key from user's account + + Inner ring list operations: + - InnerRingList() - returns array of inner ring node keys + - InnerRingCandidates() - returns array of inner ring candidate node keys + - IsInnerRing(pubKey) - returns 'true' if key is inside of inner ring list + - InnerRingCandidateAdd(pubKey) - adds key to the list of inner ring candidates + - InnerRingCandidateRemove(pubKey) - removes key from the list of inner ring candidates + - InnerRingUpdate(id, pubKeys...) - updates list of inner ring nodes with provided list of public keys + + Config operations: + - Config(key) - returns value of NeoFS configuration with key 'key' + - ListConfig() - returns array of all key-value pairs of NeoFS configuration + - SetConfig(id, key, value) - set key-value pair as a NeoFS runtime configuration value + + Other utility operations: + - Version - returns contract version + - Cheque(id, script- hash, amount, script-hash) - sends gas assets back to the user if they were successfully + locked in NeoFS balance contract + + Parameters: + - (?) - parameter can be omitted + - pubKey - 33 bytes of public key + - id - unique byte sequence +*/ + import ( "github.com/nspcc-dev/neo-go/pkg/interop/binary" "github.com/nspcc-dev/neo-go/pkg/interop/blockchain" @@ -34,17 +71,22 @@ type ( ) const ( - tokenHash = "\x3b\x7d\x37\x11\xc6\xf0\xcc\xf9\xb1\xdc\xa9\x03\xd1\xbf\xa1\xd8\x96\xf1\x23\x8c" + // native gas token script hash + tokenHash = "\x3b\x7d\x37\x11\xc6\xf0\xcc\xf9\xb1\xdc\xa9\x03\xd1\xbf\xa1\xd8\x96\xf1\x23\x8c" + defaultCandidateFee = 100 * 1_0000_0000 // 100 Fixed8 Gas candidateFeeConfigKey = "InnerRingCandidateFee" - version = 2 - innerRingKey = "innerring" - voteKey = "ballots" - candidatesKey = "candidates" - cashedChequesKey = "cheques" - blockDiff = 20 // change base on performance evaluation - publicKeySize = 33 - minInnerRingSize = 3 + + version = 2 + + innerRingKey = "innerring" + voteKey = "ballots" + candidatesKey = "candidates" + cashedChequesKey = "cheques" + + blockDiff = 20 // change base on performance evaluation + publicKeySize = 33 + minInnerRingSize = 3 ) var ( @@ -58,29 +100,6 @@ func Main(op string, args []interface{}) interface{} { return false } - /* - Utility operations - they will be changed in production: - - Deploy(params: address, pubKey, ... ) - setup initial inner ring state - - User operations: - - InnerRingList() - get list of inner ring nodes addresses and public keys - - InnerRingCandidateRemove(params: pubKey) - remove node with given public key from the inner ring candidate queue - - InnerRingCandidateAdd(params: pubKey) - add node to the inner ring candidate queue - - Deposit(params: pubKey, amount) - deposit GAS to the NeoFS account - - Withdraw(params: withdrawCheque) - withdraw GAS from the NeoFS account - - InnerRingUpdate(params: irCheque) - change list of inner ring nodes - - IsInnerRing(params: pubKey) - returns true if pubKey presented in inner ring list - - Version() - get version of the NeoFS smart-contract - - Params: - - address - string of the valid multiaddress (github.com/multiformats/multiaddr) - - pubKey - 33 byte public key - - withdrawCheque - serialized structure, that confirms GAS transfer; - contains inner ring signatures - - irCheque - serialized structure, that confirms new inner ring node list; - contains inner ring signatures - */ - ctx := storage.GetContext() switch op { @@ -277,6 +296,7 @@ func Main(op string, args []interface{}) interface{} { } var keys [][]byte + for i := 1; i < len(args); i++ { pub := args[i].([]byte) if len(pub) != publicKeySize { @@ -367,7 +387,7 @@ func Main(op string, args []interface{}) interface{} { } key := args[0].([]byte) - if len(key) != 33 { + if len(key) != publicKeySize { panic("isInnerRing: incorrect public key") } @@ -473,119 +493,6 @@ func Main(op string, args []interface{}) interface{} { panic("unknown operation") } -// fixme: use strict type deserialization wrappers -func getSerialized(ctx storage.Context, key string) interface{} { - data := storage.Get(ctx, key).([]byte) - if len(data) != 0 { - return binary.Deserialize(data) - } - return nil -} - -func delSerialized(ctx storage.Context, key string, value []byte) bool { - data := storage.Get(ctx, key).([]byte) - deleted := false - - var newList [][]byte - if len(data) != 0 { - lst := binary.Deserialize(data).([][]byte) - for i := 0; i < len(lst); i++ { - if util.Equals(value, lst[i]) { - deleted = true - } else { - newList = append(newList, lst[i]) - } - } - if deleted { - if len(newList) != 0 { - data := binary.Serialize(newList) - storage.Put(ctx, key, data) - } else { - storage.Delete(ctx, key) - } - runtime.Log("target element has been removed") - return true - } - - } - - runtime.Log("target element has not been removed") - return false -} - -func putSerialized(ctx storage.Context, key string, value interface{}) bool { - data := storage.Get(ctx, key).([]byte) - - var lst []interface{} - if len(data) != 0 { - lst = binary.Deserialize(data).([]interface{}) - } - - lst = append(lst, value) - data = binary.Serialize(lst) - storage.Put(ctx, key, data) - - return true -} - -func pubToScriptHash(pkey []byte) []byte { - // pre := []byte{0x21} - // buf := append(pre, pkey...) - // buf = append(buf, 0xac) - // h := crypto.Hash160(buf) - // - // return h - - // fixme: someday ripemd syscall will appear - // or simply store script-hashes along with public key - return []byte{0x0F, 0xED} -} - -func containsCheck(lst []cheque, c cheque) bool { - for i := 0; i < len(lst); i++ { - if util.Equals(c, lst[i]) { - return true - } - } - return false -} -func containsPub(lst []node, elem []byte) bool { - for i := 0; i < len(lst); i++ { - e := lst[i] - if util.Equals(elem, e.pub) { - return true - } - } - return false -} - -func delSerializedIR(ctx storage.Context, key string, value []byte) bool { - data := storage.Get(ctx, key).([]byte) - deleted := false - - newList := []node{} - if len(data) != 0 { - lst := binary.Deserialize(data).([]node) - for i := 0; i < len(lst); i++ { - n := lst[i] - if util.Equals(value, n.pub) { - deleted = true - } else { - newList = append(newList, n) - } - } - if deleted { - data := binary.Serialize(newList) - storage.Put(ctx, key, data) - runtime.Log("target element has been removed") - return true - } - } - - runtime.Log("target element has not been removed") - return false -} - // innerRingInvoker returns public key of inner ring node that invoked contract. func innerRingInvoker(ir []node) []byte { for i := 0; i < len(ir); i++ { @@ -598,9 +505,11 @@ func innerRingInvoker(ir []node) []byte { return nil } +// vote adds ballot for the decision with specific 'id' and returns amount +// on unique voters for that decision. func vote(ctx storage.Context, id, from []byte) int { var ( - newCandidates []ballot + newCandidates = []ballot{} // it is explicit declaration of empty slice, not nil candidates = getBallots(ctx) found = -1 blockHeight = blockchain.GetHeight() @@ -643,9 +552,11 @@ func vote(ctx storage.Context, id, from []byte) int { return found } +// removeVotes clears ballots of the decision that has benn aceepted by +// inner ring nodes. func removeVotes(ctx storage.Context, id []byte) { var ( - newCandidates []ballot + newCandidates = []ballot{} // it is explicit declaration of empty slice, not nil candidates = getBallots(ctx) ) @@ -721,6 +632,7 @@ func addCheque(lst []cheque, c cheque) ([]cheque, bool) { } lst = append(lst, c) + return lst, true } @@ -734,6 +646,7 @@ func addNode(lst []node, n node) ([]node, bool) { } lst = append(lst, n) + return lst, true } diff --git a/neofs_contract_test.go b/neofs_contract_test.go deleted file mode 100644 index e9a2736..0000000 --- a/neofs_contract_test.go +++ /dev/null @@ -1,496 +0,0 @@ -package smart_contract - -import ( - "bytes" - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "encoding/hex" - "fmt" - "math/big" - "os" - "testing" - - "github.com/nspcc-dev/neo-go/pkg/compiler" - "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/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" - crypto "github.com/nspcc-dev/neofs-crypto" - "github.com/nspcc-dev/neofs-crypto/test" - "github.com/stretchr/testify/require" -) - -const contractTemplate = "./neofs_contract.go" - -var ( - contractHash = util.Uint160{0x1, 0x2, 0x3, 0x4} - // token hash is not random to run tests of .avm or .go files - contractStr = string(contractHash[:]) - txHash = mustHex("3ca2575bd90129e3730c46ba3f163fcfd5fff11eaedb2b6aa3d76bd03ab8a890") -) - -type contract struct { - script []byte - privs []*ecdsa.PrivateKey - cgasHash string -} - -func TestContract(t *testing.T) { - const nodeCount = 6 - plug := newStoragePlugin(t) - contract := initGoContract(t, contractTemplate, nodeCount) - - plug.cgas[contractStr] = util.Fixed8FromInt64(1000) - plug.invokeKey = crypto.MarshalPublicKey(&contract.privs[0].PublicKey) - - var args []interface{} - for i := range contract.privs { - args = append(args, crypto.MarshalPublicKey(&contract.privs[i].PublicKey)) - } - - v := initVM(contract, plug) - loadArg(t, v, "Deploy", args) - require.NoError(t, v.Run()) - - // double deploy - v = initVM(contract, plug) - loadArg(t, v, "Deploy", args) - require.Error(t, v.Run()) - - t.Run("Deposit", func(t *testing.T) { - const ( - amount = 1000 - balance = 4000 - ) - - before := plug.cgas[contractStr] - gas := util.Fixed8FromInt64(amount) - - key, err := keys.NewPublicKeyFromString("031a6c6fbbdf02ca351745fa86b9ba5a9452d785ac4f7fc2b7548ca2a46c4fcf4a") - require.NoError(t, err) - - plug.setCGASBalance(key.Bytes(), balance) - - v := initVM(contract, plug) - loadArg(t, v, "Deposit", []interface{}{key.Bytes(), int(gas.IntegralValue())}) - require.NoError(t, v.Run()) - - require.Equal(t, before+gas, plug.cgas[contractStr]) - require.Equal(t, util.Fixed8FromInt64(balance-amount), plug.cgas[string(key.GetScriptHash().BytesBE())]) - checkNotification(t, plug.notify, []byte("Deposit"), key.Bytes(), big.NewInt(int64(gas)), []byte{}, txHash) - }) - - t.Run("Withdraw", func(t *testing.T) { - const amount = 21 - - gas := util.Fixed8FromInt64(amount) - - key, err := keys.NewPublicKeyFromString("031a6c6fbbdf02ca351745fa86b9ba5a9452d785ac4f7fc2b7548ca2a46c4fcf4a") - require.NoError(t, err) - - v := initVM(contract, plug) - loadArg(t, v, "Withdraw", []interface{}{key.Bytes(), amount}) - require.NoError(t, v.Run()) - checkNotification(t, plug.notify, []byte("Withdraw"), key.Bytes(), big.NewInt(int64(gas)), txHash) - }) - - t.Run("Cheque", func(t *testing.T) { - const amount = 21 - - id := []byte("id") - gas := util.Fixed8FromInt64(amount) - user := randScriptHash() - lockAcc := randScriptHash() - contractGas := plug.cgas[contractStr] - - // call it threshold amount of times - for i := 0; i < 2*nodeCount/3+1; i++ { - plug.invokeKey = crypto.MarshalPublicKey(&contract.privs[i].PublicKey) - v := initVM(contract, plug) - - loadArg(t, v, "Cheque", []interface{}{id, user, int(gas), lockAcc}) - require.NoError(t, v.Run()) - } - - require.Equal(t, contractGas-gas, plug.cgas[contractStr]) - require.Equal(t, gas, plug.cgas[string(user)]) - checkNotification(t, plug.notify, []byte("Cheque"), id, user, big.NewInt(int64(gas)), lockAcc) - - t.Run("Double cheque", func(t *testing.T) { - v := initVM(contract, plug) - - loadArg(t, v, "Cheque", []interface{}{id, user, int(gas), lockAcc}) - require.Error(t, v.Run()) - }) - }) - - t.Run("InnerRingCandidateAdd", func(t *testing.T) { - v := initVM(contract, plug) - before := plug.cgas[contractStr] - - key := crypto.MarshalPublicKey(&test.DecodeKey(1).PublicKey) - plug.setCGASBalance(key, 4000) - - loadArg(t, v, "InnerRingCandidateAdd", []interface{}{key}) - require.NoError(t, v.Run()) - - fee := util.Fixed8FromInt64(1) - - require.Equal(t, before+fee, plug.cgas[contractStr]) - require.Equal(t, util.Fixed8FromInt64(4000)-fee, - plug.cgas[string(mustPKey(key).GetScriptHash().BytesBE())]) - require.True(t, bytes.Contains(plug.mem["InnerRingCandidates"], key)) - - t.Run("Double InnerRingCandidateAdd", func(t *testing.T) { - v := initVM(contract, plug) - loadArg(t, v, "InnerRingCandidateAdd", []interface{}{key}) - require.Error(t, v.Run()) - }) - }) - - t.Run("InnerRingCandidateRemove", func(t *testing.T) { - key := crypto.MarshalPublicKey(&test.DecodeKey(2).PublicKey) - plug.setCGASBalance(key, 4000) - - v := initVM(contract, plug) - loadArg(t, v, "InnerRingCandidateAdd", []interface{}{key}) - require.NoError(t, v.Run()) - require.True(t, bytes.Contains(plug.mem["InnerRingCandidates"], key)) - - t.Run("Remove unknown candidate", func(t *testing.T) { - v := initVM(contract, plug) - // unknown candidate - badKey := crypto.MarshalPublicKey(&test.DecodeKey(3).PublicKey) - loadArg(t, v, "InnerRingCandidateRemove", []interface{}{badKey}) - require.NoError(t, v.Run()) - require.True(t, bytes.Contains(plug.mem["InnerRingCandidates"], key)) - }) - - v = initVM(contract, plug) - key = mustHex("031a6c6fbbdf02ca351745fa86b9ba5a9452d785ac4f7fc2b7548ca2a46c4fcf4a") - loadArg(t, v, "InnerRingCandidateRemove", []interface{}{key}) - require.NoError(t, v.Run()) - require.False(t, bytes.Contains(plug.mem["InnerRingCandidates"], key)) - }) -} - -func initGoContract(t *testing.T, path string, n int) *contract { - f, err := os.Open(path) - require.NoError(t, err) - - defer f.Close() - - buf, err := compiler.Compile(f) - require.NoError(t, err) - - return &contract{script: buf, privs: getKeys(t, n)} -} - -func getKeys(t *testing.T, n int) []*ecdsa.PrivateKey { - privs := make([]*ecdsa.PrivateKey, n) - for i := range privs { - var err error - - privs[i], err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - require.NoError(t, err) - } - - return privs -} - -func randScriptHash() []byte { - var scriptHash = make([]byte, 20) - rand.Read(scriptHash) - return scriptHash -} - -func mustHex(s string) []byte { - result, err := hex.DecodeString(s) - if err != nil { - panic(fmt.Errorf("invalid hex: %v", err)) - } - - return result -} - -func initVM(c *contract, plug *storagePlugin) *vm.VM { - v := vm.New() - v.Load(c.script) - v.SetScriptGetter(plug.getScript) - v.RegisterInteropGetter(plug.getInterop) - - return v -} - -func loadArg(t *testing.T, v *vm.VM, operation string, params []interface{}) { - arr := make([]vm.StackItem, len(params)) - for i := range arr { - arr[i] = toStackItem(params[i]) - require.NotNil(t, arr[i], "invalid stack item") - } - v.Estack().PushVal(vm.NewArrayItem(arr)) - v.Estack().PushVal(operation) -} - -func toStackItem(v interface{}) vm.StackItem { - switch val := v.(type) { - case int: - return vm.NewBigIntegerItem(int64(val)) - case string: - return vm.NewByteArrayItem([]byte(val)) - case []byte: - return vm.NewByteArrayItem(val) - default: - return nil - } -} - -const cgasSyscall = "MockCGAS" - -type kv struct { - Operation string - Key []byte - Value []byte -} - -type storagePlugin struct { - mem map[string][]byte - cgas map[string]util.Fixed8 - interops map[uint32]vm.InteropFunc - storageOps []kv - notify []interface{} - invokeKey []byte -} - -func newStoragePlugin(t *testing.T) *storagePlugin { - s := &storagePlugin{ - mem: make(map[string][]byte), - cgas: make(map[string]util.Fixed8), - interops: make(map[uint32]vm.InteropFunc), - } - - s.interops[getID("Neo.Storage.Delete")] = s.Delete - s.interops[getID("Neo.Storage.Get")] = s.Get - s.interops[getID("Neo.Storage.GetContext")] = s.GetContext - s.interops[getID("Neo.Storage.Put")] = s.Put - s.interops[getID("Neo.Runtime.GetExecutingScriptHash")] = s.GetExecutingScriptHash - s.interops[getID("Neo.Runtime.GetTrigger")] = s.GetTrigger - s.interops[getID("Neo.Runtime.CheckWitness")] = s.CheckWitness - s.interops[getID("System.ExecutionEngine.GetExecutingScriptHash")] = s.GetExecutingScriptHash - s.interops[getID(cgasSyscall)] = s.CGASInvoke - s.interops[getID("Neo.Runtime.Log")] = func(v *vm.VM) error { - msg := string(v.Estack().Pop().Bytes()) - t.Log(msg) - return nil - } - s.interops[getID("Neo.Runtime.Notify")] = func(v *vm.VM) error { - val := v.Estack().Pop().Value() - s.notify = append(s.notify, toInterface(val)) - return nil - } - s.interops[getID("System.ExecutionEngine.GetScriptContainer")] = s.GetScriptContainer - s.interops[getID("Neo.Transaction.GetHash")] = s.GetHash - s.interops[getID("Neo.Blockchain.GetHeight")] = s.GetHeight - - return s -} - -func toInterface(val interface{}) interface{} { - switch v := val.(type) { - case []vm.StackItem: - arr := make([]interface{}, len(v)) - for i, item := range v { - arr[i] = toInterface(item) - } - return arr - case vm.StackItem: - return toInterface(v.Value()) - default: - return v - } -} - -func getID(name string) uint32 { - return vm.InteropNameToID([]byte(name)) -} - -func (s *storagePlugin) getInterop(id uint32) *vm.InteropFuncPrice { - f := s.interops[id] - if f != nil { - return &vm.InteropFuncPrice{Func: f, Price: 1} - } - - switch id { - case getID("Neo.Runtime.Serialize"): - case getID("Neo.Runtime.Deserialize"): - default: - panic("unexpected interop") - } - - return nil -} - -func mustPKey(pub []byte) *keys.PublicKey { - var pk keys.PublicKey - if err := pk.DecodeBytes(pub); err != nil { - panic(err) - } - return &pk -} - -func (s *storagePlugin) setCGASBalance(pub []byte, amount int64) { - pk := mustPKey(pub) - from := string(pk.GetScriptHash().BytesBE()) - s.cgas[from] = util.Fixed8FromInt64(amount) -} - -func (s *storagePlugin) CGASInvoke(v *vm.VM) error { - op := string(v.Estack().Pop().Bytes()) - args := v.Estack().Pop().Array() - - var result bool - - switch op { - case "transfer": - from := args[0].Value().([]byte) - to := args[1].Value().([]byte) - if len(from) != 20 || len(to) != 20 { - panic("invalid arguments") - } - - var amount util.Fixed8 - val := args[2].Value() - switch v := val.(type) { - case *big.Int: - amount = util.Fixed8(v.Int64()) - case []byte: - amount = util.Fixed8(emit.BytesToInt(v).Int64()) - default: - panic("invalid amount") - } - - if s.cgas[string(from)] >= amount { - s.cgas[string(from)] -= amount - s.cgas[string(to)] += amount - result = true - } - - default: - panic("invalid operation") - } - - v.Estack().PushVal(result) - - return nil -} - -func (s *storagePlugin) getScript(u util.Uint160) ([]byte, bool) { - var realHash util.Uint160 - copy(realHash[:], tokenHash[:]) - if u.Equals(realHash) { - buf := io.NewBufBinWriter() - emit.Syscall(buf.BinWriter, cgasSyscall) - return buf.Bytes(), false - } - panic("wrong script hash") -} - -func (s *storagePlugin) GetTrigger(v *vm.VM) error { - // todo: remove byte casting when neo-go issue will be resolved - // https: //github.com/nspcc-dev/neo-go/issues/776 - v.Estack().PushVal(byte(trigger.Application)) - return nil -} - -func (s *storagePlugin) CheckWitness(v *vm.VM) error { - key := v.Estack().Pop().Value().([]byte) - if bytes.Equal(key, s.invokeKey) { - v.Estack().PushVal(true) - } else { - v.Estack().PushVal(false) - } - - return nil -} - -func (s *storagePlugin) GetExecutingScriptHash(v *vm.VM) error { - var h util.Uint160 - copy(h[:], contractHash[:]) - v.Estack().PushVal(h.BytesBE()) - return nil -} - -func (s *storagePlugin) GetScriptContainer(v *vm.VM) error { - v.Estack().PushVal(true) - return nil -} - -func (s *storagePlugin) GetHash(v *vm.VM) error { - v.Estack().PushVal(txHash) - return nil -} - -func (s *storagePlugin) GetHeight(v *vm.VM) error { - v.Estack().PushVal(42) - return nil -} - -func (s *storagePlugin) logStorage(op string, key, value []byte) { - s.storageOps = append(s.storageOps, kv{ - Operation: op, - Key: key, - Value: value, - }) -} - -func (s *storagePlugin) Delete(v *vm.VM) error { - v.Estack().Pop() - key := v.Estack().Pop().Bytes() - s.logStorage("Delete", key, s.mem[string(key)]) - delete(s.mem, string(key)) - return nil -} - -func (s *storagePlugin) Put(v *vm.VM) error { - v.Estack().Pop() - key := v.Estack().Pop().Bytes() - value := v.Estack().Pop().Bytes() - s.logStorage("Put", key, value) - s.mem[string(key)] = value - return nil -} - -func (s *storagePlugin) Get(v *vm.VM) error { - v.Estack().Pop() - item := v.Estack().Pop().Bytes() - if val, ok := s.mem[string(item)]; ok { - v.Estack().PushVal(val) - s.logStorage("Get", item, val) - return nil - } - v.Estack().PushVal([]byte{}) - s.logStorage("Get", item, nil) - return nil -} - -func (s *storagePlugin) GetContext(v *vm.VM) error { - // Pushing anything on the stack here will work. This is just to satisfy - // the compiler, thinking it has pushed the context ^^. - v.Estack().PushVal(10) - return nil -} - -func checkNotification(t *testing.T, store []interface{}, args ...interface{}) { - ln := len(store) - require.True(t, ln > 0) - - notification := store[ln-1].([]interface{}) - require.Equal(t, len(args), len(notification)) - - for i := range args { - require.Equal(t, args[i], notification[i]) - } -}