Merge pull request #781 from nspcc-dev/neo3/native

Implement Native contract mechanism
This commit is contained in:
Roman Khimov 2020-04-16 18:41:13 +03:00 committed by GitHub
commit c7c788d10b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 2210 additions and 235 deletions

View file

@ -34,7 +34,7 @@ func TestEntryPointWithArgs(t *testing.T) {
return 2 + args[1].(int) return 2 + args[1].(int)
} }
` `
args := []vm.StackItem{vm.NewBigIntegerItem(0), vm.NewBigIntegerItem(1)} args := []vm.StackItem{vm.NewBigIntegerItem(big.NewInt(0)), vm.NewBigIntegerItem(big.NewInt(1))}
evalWithArgs(t, src, nil, args, big.NewInt(3)) evalWithArgs(t, src, nil, args, big.NewInt(3))
} }
@ -49,7 +49,7 @@ func TestEntryPointWithMethodAndArgs(t *testing.T) {
return 0 return 0
} }
` `
args := []vm.StackItem{vm.NewBigIntegerItem(0), vm.NewBigIntegerItem(1)} args := []vm.StackItem{vm.NewBigIntegerItem(big.NewInt(0)), vm.NewBigIntegerItem(big.NewInt(1))}
evalWithArgs(t, src, []byte("foobar"), args, big.NewInt(3)) evalWithArgs(t, src, []byte("foobar"), args, big.NewInt(3))
} }
@ -154,9 +154,9 @@ func TestIntArray(t *testing.T) {
} }
` `
eval(t, src, []vm.StackItem{ eval(t, src, []vm.StackItem{
vm.NewBigIntegerItem(1), vm.NewBigIntegerItem(big.NewInt(1)),
vm.NewBigIntegerItem(2), vm.NewBigIntegerItem(big.NewInt(2)),
vm.NewBigIntegerItem(3), vm.NewBigIntegerItem(big.NewInt(3)),
}) })
} }

View file

@ -271,8 +271,8 @@ var structTestCases = []testCase{
} }
`, `,
[]vm.StackItem{ []vm.StackItem{
vm.NewBigIntegerItem(1), vm.NewBigIntegerItem(big.NewInt(1)),
vm.NewBigIntegerItem(2), vm.NewBigIntegerItem(big.NewInt(2)),
vm.NewByteArrayItem([]byte("hello")), vm.NewByteArrayItem([]byte("hello")),
vm.NewByteArrayItem([]byte{}), vm.NewByteArrayItem([]byte{}),
}, },

View file

@ -1,6 +1,7 @@
package compiler_test package compiler_test
import ( import (
"math/big"
"testing" "testing"
"github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm"
@ -40,7 +41,7 @@ func TestNotify(t *testing.T) {
require.NoError(t, v.Run()) require.NoError(t, v.Run())
require.Equal(t, 3, len(s.events)) require.Equal(t, 3, len(s.events))
exp0 := []vm.StackItem{vm.NewBigIntegerItem(11), vm.NewByteArrayItem([]byte("sum")), vm.NewBigIntegerItem(12)} exp0 := []vm.StackItem{vm.NewBigIntegerItem(big.NewInt(11)), vm.NewByteArrayItem([]byte("sum")), vm.NewBigIntegerItem(big.NewInt(12))}
assert.Equal(t, exp0, s.events[0].Value()) assert.Equal(t, exp0, s.events[0].Value())
assert.Equal(t, []vm.StackItem{}, s.events[1].Value()) assert.Equal(t, []vm.StackItem{}, s.events[1].Value())
assert.Equal(t, []vm.StackItem{vm.NewByteArrayItem([]byte("single"))}, s.events[2].Value()) assert.Equal(t, []vm.StackItem{vm.NewByteArrayItem([]byte("single"))}, s.events[2].Value())

View file

@ -14,6 +14,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/dao" "github.com/nspcc-dev/neo-go/pkg/core/dao"
"github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/mempool" "github.com/nspcc-dev/neo-go/pkg/core/mempool"
"github.com/nspcc-dev/neo-go/pkg/core/native"
"github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/storage" "github.com/nspcc-dev/neo-go/pkg/core/storage"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/core/transaction"
@ -123,6 +124,8 @@ type Blockchain struct {
log *zap.Logger log *zap.Logger
lastBatch *storage.MemBatch lastBatch *storage.MemBatch
contracts native.Contracts
} }
type headersOpFunc func(headerList *HeaderHashList) type headersOpFunc func(headerList *HeaderHashList)
@ -167,6 +170,8 @@ func NewBlockchain(s storage.Store, cfg config.ProtocolConfiguration, log *zap.L
generationAmount: genAmount, generationAmount: genAmount,
decrementInterval: decrementInterval, decrementInterval: decrementInterval,
contracts: *native.NewContracts(),
} }
if err := bc.init(); err != nil { if err := bc.init(); err != nil {
@ -193,6 +198,9 @@ func (bc *Blockchain) init() error {
if err != nil { if err != nil {
return err return err
} }
if err := bc.initNative(); err != nil {
return err
}
return bc.storeBlock(genesisBlock) return bc.storeBlock(genesisBlock)
} }
if ver != version { if ver != version {
@ -265,6 +273,27 @@ func (bc *Blockchain) init() error {
return nil return nil
} }
func (bc *Blockchain) initNative() error {
ic := bc.newInteropContext(trigger.Application, bc.dao, nil, nil)
gas := native.NewGAS()
neo := native.NewNEO()
neo.GAS = gas
gas.NEO = neo
if err := gas.Initialize(ic); err != nil {
return fmt.Errorf("can't initialize GAS native contract: %v", err)
}
if err := neo.Initialize(ic); err != nil {
return fmt.Errorf("can't initialize NEO native contract: %v", err)
}
bc.contracts.SetGAS(gas)
bc.contracts.SetNEO(neo)
return nil
}
// Run runs chain loop. // Run runs chain loop.
func (bc *Blockchain) Run() { func (bc *Blockchain) Run() {
persistTimer := time.NewTimer(persistInterval) persistTimer := time.NewTimer(persistInterval)
@ -634,7 +663,7 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
return err return err
} }
case *transaction.StateTX: case *transaction.StateTX:
if err := processStateTX(cache, t); err != nil { if err := bc.processStateTX(cache, tx, t); err != nil {
return err return err
} }
case *transaction.PublishTX: case *transaction.PublishTX:
@ -726,6 +755,13 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
bc.lastBatch = cache.DAO.GetBatch() bc.lastBatch = cache.DAO.GetBatch()
} }
for i := range bc.contracts.Contracts {
systemInterop := bc.newInteropContext(trigger.Application, cache, block, nil)
if err := bc.contracts.Contracts[i].OnPersist(systemInterop); err != nil {
return err
}
}
_, err := cache.Persist() _, err := cache.Persist()
if err != nil { if err != nil {
return err return err
@ -832,6 +868,11 @@ func (bc *Blockchain) LastBatch() *storage.MemBatch {
return bc.lastBatch return bc.lastBatch
} }
// RegisterNative registers native contract in the blockchain.
func (bc *Blockchain) RegisterNative(c native.Contract) {
bc.contracts.Add(c)
}
// processOutputs processes transaction outputs. // processOutputs processes transaction outputs.
func processOutputs(tx *transaction.Transaction, dao *dao.Cached) error { func processOutputs(tx *transaction.Transaction, dao *dao.Cached) error {
for index, output := range tx.Outputs { for index, output := range tx.Outputs {
@ -870,21 +911,8 @@ func processTXWithValidatorsSubtract(output *transaction.Output, account *state.
// modAccountVotes adds given value to given account voted validators. // modAccountVotes adds given value to given account voted validators.
func modAccountVotes(account *state.Account, dao *dao.Cached, value util.Fixed8) error { func modAccountVotes(account *state.Account, dao *dao.Cached, value util.Fixed8) error {
for _, vote := range account.Votes { if err := native.ModifyAccountVotes(account, dao, value); err != nil {
validator, err := dao.GetValidatorStateOrNew(vote) return err
if err != nil {
return err
}
validator.Votes += value
if validator.UnregisteredAndHasNoVotes() {
if err := dao.DeleteValidatorState(validator); err != nil {
return err
}
} else {
if err := dao.PutValidatorState(validator); err != nil {
return err
}
}
} }
if len(account.Votes) > 0 { if len(account.Votes) > 0 {
vc, err := dao.GetValidatorsCount() vc, err := dao.GetValidatorsCount()
@ -920,55 +948,19 @@ func processValidatorStateDescriptor(descriptor *transaction.StateDescriptor, da
return nil return nil
} }
func processAccountStateDescriptor(descriptor *transaction.StateDescriptor, dao *dao.Cached) error { func (bc *Blockchain) processAccountStateDescriptor(descriptor *transaction.StateDescriptor, t *transaction.Transaction, dao *dao.Cached) error {
hash, err := util.Uint160DecodeBytesBE(descriptor.Key) hash, err := util.Uint160DecodeBytesBE(descriptor.Key)
if err != nil { if err != nil {
return err return err
} }
account, err := dao.GetAccountStateOrNew(hash)
if err != nil {
return err
}
if descriptor.Field == "Votes" { if descriptor.Field == "Votes" {
balance := account.GetBalanceValues()[GoverningTokenID()]
if err = modAccountVotes(account, dao, -balance); err != nil {
return err
}
votes := keys.PublicKeys{} votes := keys.PublicKeys{}
err := votes.DecodeBytes(descriptor.Value) if err := votes.DecodeBytes(descriptor.Value); err != nil {
if err != nil {
return err return err
} }
if len(votes) > state.MaxValidatorsVoted { ic := bc.newInteropContext(trigger.Application, dao, nil, t)
return errors.New("voting candidate limit exceeded") return bc.contracts.NEO.VoteInternal(ic, hash, votes)
}
if len(votes) > 0 {
account.Votes = votes
for _, vote := range account.Votes {
validatorState, err := dao.GetValidatorStateOrNew(vote)
if err != nil {
return err
}
validatorState.Votes += balance
if err = dao.PutValidatorState(validatorState); err != nil {
return err
}
}
vc, err := dao.GetValidatorsCount()
if err != nil {
return err
}
vc[len(account.Votes)-1] += balance
err = dao.PutValidatorsCount(vc)
if err != nil {
return err
}
} else {
account.Votes = nil
}
return dao.PutAccountState(account)
} }
return nil return nil
} }
@ -1790,59 +1782,14 @@ func (bc *Blockchain) GetValidators(txes ...*transaction.Transaction) ([]*keys.P
return nil, err return nil, err
} }
case *transaction.StateTX: case *transaction.StateTX:
if err := processStateTX(cache, t); err != nil { if err := bc.processStateTX(cache, tx, t); err != nil {
return nil, err return nil, err
} }
} }
} }
} }
validators := cache.GetValidators() return bc.contracts.NEO.GetValidatorsInternal(bc, cache)
sort.Slice(validators, func(i, j int) bool {
// Unregistered validators go to the end of the list.
if validators[i].Registered != validators[j].Registered {
return validators[i].Registered
}
// The most-voted validators should end up in the front of the list.
if validators[i].Votes != validators[j].Votes {
return validators[i].Votes > validators[j].Votes
}
// Ties are broken with public keys.
return validators[i].PublicKey.Cmp(validators[j].PublicKey) == -1
})
validatorsCount, err := cache.GetValidatorsCount()
if err != nil {
return nil, err
}
count := validatorsCount.GetWeightedAverage()
standByValidators, err := bc.GetStandByValidators()
if err != nil {
return nil, err
}
if count < len(standByValidators) {
count = len(standByValidators)
}
uniqueSBValidators := standByValidators.Unique()
result := keys.PublicKeys{}
for _, validator := range validators {
if validator.RegisteredAndHasVotes() || uniqueSBValidators.Contains(validator.PublicKey) {
result = append(result, validator.PublicKey)
}
}
if result.Len() >= count {
result = result[:count]
} else {
for i := 0; i < uniqueSBValidators.Len() && result.Len() < count; i++ {
if !result.Contains(uniqueSBValidators[i]) {
result = append(result, uniqueSBValidators[i])
}
}
}
sort.Sort(result)
return result, nil
} }
// GetEnrollments returns all registered validators and non-registered SB validators // GetEnrollments returns all registered validators and non-registered SB validators
@ -1879,11 +1826,11 @@ func (bc *Blockchain) GetEnrollments() ([]*state.Validator, error) {
return result, nil return result, nil
} }
func processStateTX(dao *dao.Cached, tx *transaction.StateTX) error { func (bc *Blockchain) processStateTX(dao *dao.Cached, t *transaction.Transaction, tx *transaction.StateTX) error {
for _, desc := range tx.Descriptors { for _, desc := range tx.Descriptors {
switch desc.Type { switch desc.Type {
case transaction.Account: case transaction.Account:
if err := processAccountStateDescriptor(desc, dao); err != nil { if err := bc.processAccountStateDescriptor(desc, t, dao); err != nil {
return err return err
} }
case transaction.Validator: case transaction.Validator:

View file

@ -37,6 +37,7 @@ type Blockchainer interface {
GetNEP5TransferLog(util.Uint160) *state.NEP5TransferLog GetNEP5TransferLog(util.Uint160) *state.NEP5TransferLog
GetNEP5Balances(util.Uint160) *state.NEP5Balances GetNEP5Balances(util.Uint160) *state.NEP5Balances
GetValidators(txes ...*transaction.Transaction) ([]*keys.PublicKey, error) GetValidators(txes ...*transaction.Transaction) ([]*keys.PublicKey, error)
GetStandByValidators() (keys.PublicKeys, error)
GetScriptHashesForVerifying(*transaction.Transaction) ([]util.Uint160, error) GetScriptHashesForVerifying(*transaction.Transaction) ([]util.Uint160, error)
GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem
GetStorageItems(hash util.Uint160) (map[string]*state.StorageItem, error) GetStorageItems(hash util.Uint160) (map[string]*state.StorageItem, error)

View file

@ -32,8 +32,10 @@ type DAO interface {
GetCurrentBlockHeight() (uint32, error) GetCurrentBlockHeight() (uint32, error)
GetCurrentHeaderHeight() (i uint32, h util.Uint256, err error) GetCurrentHeaderHeight() (i uint32, h util.Uint256, err error)
GetHeaderHashes() ([]util.Uint256, error) GetHeaderHashes() ([]util.Uint256, error)
GetNativeContractState(h util.Uint160) ([]byte, error)
GetNEP5Balances(acc util.Uint160) (*state.NEP5Balances, error) GetNEP5Balances(acc util.Uint160) (*state.NEP5Balances, error)
GetNEP5TransferLog(acc util.Uint160, index uint32) (*state.NEP5TransferLog, error) GetNEP5TransferLog(acc util.Uint160, index uint32) (*state.NEP5TransferLog, error)
GetNextBlockValidators() (keys.PublicKeys, error)
GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem
GetStorageItems(hash util.Uint160) (map[string]*state.StorageItem, error) GetStorageItems(hash util.Uint160) (map[string]*state.StorageItem, error)
GetTransaction(hash util.Uint256) (*transaction.Transaction, uint32, error) GetTransaction(hash util.Uint256) (*transaction.Transaction, uint32, error)
@ -53,8 +55,10 @@ type DAO interface {
PutAssetState(as *state.Asset) error PutAssetState(as *state.Asset) error
PutContractState(cs *state.Contract) error PutContractState(cs *state.Contract) error
PutCurrentHeader(hashAndIndex []byte) error PutCurrentHeader(hashAndIndex []byte) error
PutNativeContractState(h util.Uint160, value []byte) error
PutNEP5Balances(acc util.Uint160, bs *state.NEP5Balances) error PutNEP5Balances(acc util.Uint160, bs *state.NEP5Balances) error
PutNEP5TransferLog(acc util.Uint160, index uint32, lg *state.NEP5TransferLog) error PutNEP5TransferLog(acc util.Uint160, index uint32, lg *state.NEP5TransferLog) error
PutNextBlockValidators(keys.PublicKeys) error
PutStorageItem(scripthash util.Uint160, key []byte, si *state.StorageItem) error PutStorageItem(scripthash util.Uint160, key []byte, si *state.StorageItem) error
PutUnspentCoinState(hash util.Uint256, ucs *state.UnspentCoin) error PutUnspentCoinState(hash util.Uint256, ucs *state.UnspentCoin) error
PutValidatorState(vs *state.Validator) error PutValidatorState(vs *state.Validator) error
@ -207,6 +211,18 @@ func (dao *Simple) DeleteContractState(hash util.Uint160) error {
return dao.Store.Delete(key) return dao.Store.Delete(key)
} }
// GetNativeContractState retrieves native contract state from the store.
func (dao *Simple) GetNativeContractState(h util.Uint160) ([]byte, error) {
key := storage.AppendPrefix(storage.STNativeContract, h.BytesBE())
return dao.Store.Get(key)
}
// PutNativeContractState puts native contract state into the store.
func (dao *Simple) PutNativeContractState(h util.Uint160, value []byte) error {
key := storage.AppendPrefix(storage.STNativeContract, h.BytesBE())
return dao.Store.Put(key, value)
}
// -- end contracts. // -- end contracts.
// -- start nep5 balances. // -- start nep5 balances.
@ -310,6 +326,38 @@ func (dao *Simple) putUnspentCoinState(hash util.Uint256, ucs *state.UnspentCoin
// -- start validator. // -- start validator.
// GetNextBlockValidators retrieves next block validators from store or nil if they are missing.
func (dao *Simple) GetNextBlockValidators() (keys.PublicKeys, error) {
key := []byte{byte(storage.STNextValidators)}
buf, err := dao.Store.Get(key)
if err != nil {
if err == storage.ErrKeyNotFound {
return nil, nil
}
return nil, err
}
var pubs keys.PublicKeys
r := io.NewBinReaderFromBuf(buf)
r.ReadArray(&pubs)
if r.Err != nil {
return nil, r.Err
}
return pubs, nil
}
// PutNextBlockValidators puts next block validators to store.
func (dao *Simple) PutNextBlockValidators(pubs keys.PublicKeys) error {
w := io.NewBufBinWriter()
w.WriteArray(pubs)
if w.Err != nil {
return w.Err
}
key := []byte{byte(storage.STNextValidators)}
return dao.Store.Put(key, w.Bytes())
}
// GetValidatorStateOrNew gets validator from store or created new one in case of error. // GetValidatorStateOrNew gets validator from store or created new one in case of error.
func (dao *Simple) GetValidatorStateOrNew(publicKey *keys.PublicKey) (*state.Validator, error) { func (dao *Simple) GetValidatorStateOrNew(publicKey *keys.PublicKey) (*state.Validator, error) {
validatorState, err := dao.GetValidatorState(publicKey) validatorState, err := dao.GetValidatorState(publicKey)

View file

@ -0,0 +1,54 @@
package runtime
import (
"github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/pkg/errors"
)
// CheckHashedWitness checks given hash against current list of script hashes
// for verifying in the interop context.
func CheckHashedWitness(ic *interop.Context, hash util.Uint160) (bool, error) {
hashes, err := ic.Chain.GetScriptHashesForVerifying(ic.Tx)
if err != nil {
return false, errors.Wrap(err, "failed to get script hashes")
}
for _, v := range hashes {
if hash.Equals(v) {
return true, nil
}
}
return false, nil
}
// CheckKeyedWitness checks hash of signature check contract with a given public
// key against current list of script hashes for verifying in the interop context.
func CheckKeyedWitness(ic *interop.Context, key *keys.PublicKey) (bool, error) {
return CheckHashedWitness(ic, key.GetScriptHash())
}
// CheckWitness checks witnesses.
func CheckWitness(ic *interop.Context, v *vm.VM) error {
var res bool
var err error
hashOrKey := v.Estack().Pop().Bytes()
hash, err := util.Uint160DecodeBytesBE(hashOrKey)
if err != nil {
var key *keys.PublicKey
key, err = keys.NewPublicKeyFromBytes(hashOrKey)
if err != nil {
return errors.New("parameter given is neither a key nor a hash")
}
res, err = CheckKeyedWitness(ic, key)
} else {
res, err = CheckHashedWitness(ic, hash)
}
if err != nil {
return errors.Wrap(err, "failed to check")
}
v.Estack().PushVal(res)
return nil
}

View file

@ -7,6 +7,7 @@ import (
"strings" "strings"
"github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/interop/runtime"
"github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
@ -574,7 +575,7 @@ func contractMigrate(ic *interop.Context, v *vm.VM) error {
return err return err
} }
if contract.HasStorage() { if contract.HasStorage() {
hash := getContextScriptHash(v, 0) hash := v.GetContextScriptHash(0)
siMap, err := ic.DAO.GetStorageItems(hash) siMap, err := ic.DAO.GetStorageItems(hash)
if err != nil { if err != nil {
return err return err
@ -635,7 +636,7 @@ func assetCreate(ic *interop.Context, v *vm.VM) error {
if owner.IsInfinity() { if owner.IsInfinity() {
return errors.New("can't have infinity as an owner key") return errors.New("can't have infinity as an owner key")
} }
witnessOk, err := checkKeyedWitness(ic, owner) witnessOk, err := runtime.CheckKeyedWitness(ic, owner)
if err != nil { if err != nil {
return err return err
} }

View file

@ -11,11 +11,9 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm"
gherr "github.com/pkg/errors"
"go.uber.org/zap" "go.uber.org/zap"
) )
@ -252,35 +250,19 @@ func engineGetScriptContainer(ic *interop.Context, v *vm.VM) error {
return nil return nil
} }
// pushContextScriptHash returns script hash of the invocation stack element
// number n.
func getContextScriptHash(v *vm.VM, n int) util.Uint160 {
ctxIface := v.Istack().Peek(n).Value()
ctx := ctxIface.(*vm.Context)
return ctx.ScriptHash()
}
// pushContextScriptHash pushes to evaluation stack the script hash of the
// invocation stack element number n.
func pushContextScriptHash(v *vm.VM, n int) error {
h := getContextScriptHash(v, n)
v.Estack().PushVal(h.BytesBE())
return nil
}
// engineGetExecutingScriptHash returns executing script hash. // engineGetExecutingScriptHash returns executing script hash.
func engineGetExecutingScriptHash(ic *interop.Context, v *vm.VM) error { func engineGetExecutingScriptHash(ic *interop.Context, v *vm.VM) error {
return pushContextScriptHash(v, 0) return v.PushContextScriptHash(0)
} }
// engineGetCallingScriptHash returns calling script hash. // engineGetCallingScriptHash returns calling script hash.
func engineGetCallingScriptHash(ic *interop.Context, v *vm.VM) error { func engineGetCallingScriptHash(ic *interop.Context, v *vm.VM) error {
return pushContextScriptHash(v, 1) return v.PushContextScriptHash(1)
} }
// engineGetEntryScriptHash returns entry script hash. // engineGetEntryScriptHash returns entry script hash.
func engineGetEntryScriptHash(ic *interop.Context, v *vm.VM) error { func engineGetEntryScriptHash(ic *interop.Context, v *vm.VM) error {
return pushContextScriptHash(v, v.Istack().Len()-1) return v.PushContextScriptHash(v.Istack().Len() - 1)
} }
// runtimePlatform returns the name of the platform. // runtimePlatform returns the name of the platform.
@ -295,50 +277,6 @@ func runtimeGetTrigger(ic *interop.Context, v *vm.VM) error {
return nil return nil
} }
// checkHashedWitness checks given hash against current list of script hashes
// for verifying in the interop context.
func checkHashedWitness(ic *interop.Context, hash util.Uint160) (bool, error) {
hashes, err := ic.Chain.GetScriptHashesForVerifying(ic.Tx)
if err != nil {
return false, gherr.Wrap(err, "failed to get script hashes")
}
for _, v := range hashes {
if hash.Equals(v) {
return true, nil
}
}
return false, nil
}
// checkKeyedWitness checks hash of signature check contract with a given public
// key against current list of script hashes for verifying in the interop context.
func checkKeyedWitness(ic *interop.Context, key *keys.PublicKey) (bool, error) {
return checkHashedWitness(ic, key.GetScriptHash())
}
// runtimeCheckWitness checks witnesses.
func runtimeCheckWitness(ic *interop.Context, v *vm.VM) error {
var res bool
var err error
hashOrKey := v.Estack().Pop().Bytes()
hash, err := util.Uint160DecodeBytesBE(hashOrKey)
if err != nil {
key, err := keys.NewPublicKeyFromBytes(hashOrKey)
if err != nil {
return errors.New("parameter given is neither a key nor a hash")
}
res, err = checkKeyedWitness(ic, key)
} else {
res, err = checkHashedWitness(ic, hash)
}
if err != nil {
return gherr.Wrap(err, "failed to check")
}
v.Estack().PushVal(res)
return nil
}
// runtimeNotify should pass stack item to the notify plugin to handle it, but // runtimeNotify should pass stack item to the notify plugin to handle it, but
// in neo-go the only meaningful thing to do here is to log. // in neo-go the only meaningful thing to do here is to log.
func runtimeNotify(ic *interop.Context, v *vm.VM) error { func runtimeNotify(ic *interop.Context, v *vm.VM) error {
@ -354,7 +292,7 @@ func runtimeNotify(ic *interop.Context, v *vm.VM) error {
if err != nil { if err != nil {
item = vm.NewByteArrayItem([]byte(fmt.Sprintf("bad notification: %v", err))) item = vm.NewByteArrayItem([]byte(fmt.Sprintf("bad notification: %v", err)))
} }
ne := state.NotificationEvent{ScriptHash: getContextScriptHash(v, 0), Item: item} ne := state.NotificationEvent{ScriptHash: v.GetContextScriptHash(0), Item: item}
ic.Notifications = append(ic.Notifications, ne) ic.Notifications = append(ic.Notifications, ne)
return nil return nil
} }
@ -363,7 +301,7 @@ func runtimeNotify(ic *interop.Context, v *vm.VM) error {
func runtimeLog(ic *interop.Context, v *vm.VM) error { func runtimeLog(ic *interop.Context, v *vm.VM) error {
msg := fmt.Sprintf("%q", v.Estack().Pop().Bytes()) msg := fmt.Sprintf("%q", v.Estack().Pop().Bytes())
ic.Log.Info("runtime log", ic.Log.Info("runtime log",
zap.Stringer("script", getContextScriptHash(v, 0)), zap.Stringer("script", v.GetContextScriptHash(0)),
zap.String("logs", msg)) zap.String("logs", msg))
return nil return nil
} }
@ -445,7 +383,7 @@ func storageGet(ic *interop.Context, v *vm.VM) error {
// storageGetContext returns storage context (scripthash). // storageGetContext returns storage context (scripthash).
func storageGetContext(ic *interop.Context, v *vm.VM) error { func storageGetContext(ic *interop.Context, v *vm.VM) error {
sc := &StorageContext{ sc := &StorageContext{
ScriptHash: getContextScriptHash(v, 0), ScriptHash: v.GetContextScriptHash(0),
ReadOnly: false, ReadOnly: false,
} }
v.Estack().PushVal(vm.NewInteropItem(sc)) v.Estack().PushVal(vm.NewInteropItem(sc))
@ -455,7 +393,7 @@ func storageGetContext(ic *interop.Context, v *vm.VM) error {
// storageGetReadOnlyContext returns read-only context (scripthash). // storageGetReadOnlyContext returns read-only context (scripthash).
func storageGetReadOnlyContext(ic *interop.Context, v *vm.VM) error { func storageGetReadOnlyContext(ic *interop.Context, v *vm.VM) error {
sc := &StorageContext{ sc := &StorageContext{
ScriptHash: getContextScriptHash(v, 0), ScriptHash: v.GetContextScriptHash(0),
ReadOnly: true, ReadOnly: true,
} }
v.Estack().PushVal(vm.NewInteropItem(sc)) v.Estack().PushVal(vm.NewInteropItem(sc))
@ -537,7 +475,7 @@ func contractDestroy(ic *interop.Context, v *vm.VM) error {
if ic.Trigger != trigger.Application { if ic.Trigger != trigger.Application {
return errors.New("can't destroy contract when not triggered by application") return errors.New("can't destroy contract when not triggered by application")
} }
hash := getContextScriptHash(v, 0) hash := v.GetContextScriptHash(0)
cs, err := ic.DAO.GetContractState(hash) cs, err := ic.DAO.GetContractState(hash)
if err != nil { if err != nil {
return nil return nil

View file

@ -14,6 +14,8 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/interop/crypto" "github.com/nspcc-dev/neo-go/pkg/core/interop/crypto"
"github.com/nspcc-dev/neo-go/pkg/core/interop/enumerator" "github.com/nspcc-dev/neo-go/pkg/core/interop/enumerator"
"github.com/nspcc-dev/neo-go/pkg/core/interop/iterator" "github.com/nspcc-dev/neo-go/pkg/core/interop/iterator"
"github.com/nspcc-dev/neo-go/pkg/core/interop/runtime"
"github.com/nspcc-dev/neo-go/pkg/core/native"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm"
@ -23,7 +25,12 @@ import (
// up for current blockchain. // up for current blockchain.
func SpawnVM(ic *interop.Context) *vm.VM { func SpawnVM(ic *interop.Context) *vm.VM {
vm := vm.New() vm := vm.New()
bc := ic.Chain.(*Blockchain)
vm.SetScriptGetter(func(hash util.Uint160) ([]byte, bool) { vm.SetScriptGetter(func(hash util.Uint160) ([]byte, bool) {
if c := bc.contracts.ByHash(hash); c != nil {
meta := c.Metadata()
return meta.Script, (meta.Manifest.Features&smartcontract.HasDynamicInvoke != 0)
}
cs, err := ic.DAO.GetContractState(hash) cs, err := ic.DAO.GetContractState(hash)
if err != nil { if err != nil {
return nil, false return nil, false
@ -33,6 +40,7 @@ func SpawnVM(ic *interop.Context) *vm.VM {
}) })
vm.RegisterInteropGetter(getSystemInterop(ic)) vm.RegisterInteropGetter(getSystemInterop(ic))
vm.RegisterInteropGetter(getNeoInterop(ic)) vm.RegisterInteropGetter(getNeoInterop(ic))
vm.RegisterInteropGetter(bc.contracts.GetNativeInterop(ic))
return vm return vm
} }
@ -85,7 +93,7 @@ var systemInterops = []interop.Function{
{Name: "System.Header.GetIndex", Func: headerGetIndex, Price: 1}, {Name: "System.Header.GetIndex", Func: headerGetIndex, Price: 1},
{Name: "System.Header.GetPrevHash", Func: headerGetPrevHash, Price: 1}, {Name: "System.Header.GetPrevHash", Func: headerGetPrevHash, Price: 1},
{Name: "System.Header.GetTimestamp", Func: headerGetTimestamp, Price: 1}, {Name: "System.Header.GetTimestamp", Func: headerGetTimestamp, Price: 1},
{Name: "System.Runtime.CheckWitness", Func: runtimeCheckWitness, Price: 200}, {Name: "System.Runtime.CheckWitness", Func: runtime.CheckWitness, Price: 200},
{Name: "System.Runtime.Deserialize", Func: runtimeDeserialize, Price: 1}, {Name: "System.Runtime.Deserialize", Func: runtimeDeserialize, Price: 1},
{Name: "System.Runtime.GetTime", Func: runtimeGetTime, Price: 1}, {Name: "System.Runtime.GetTime", Func: runtimeGetTime, Price: 1},
{Name: "System.Runtime.GetTrigger", Func: runtimeGetTrigger, Price: 1}, {Name: "System.Runtime.GetTrigger", Func: runtimeGetTrigger, Price: 1},
@ -160,10 +168,11 @@ var neoInterops = []interop.Function{
{Name: "Neo.Iterator.Key", Func: iterator.Key, Price: 1}, {Name: "Neo.Iterator.Key", Func: iterator.Key, Price: 1},
{Name: "Neo.Iterator.Keys", Func: iterator.Keys, Price: 1}, {Name: "Neo.Iterator.Keys", Func: iterator.Keys, Price: 1},
{Name: "Neo.Iterator.Values", Func: iterator.Values, Price: 1}, {Name: "Neo.Iterator.Values", Func: iterator.Values, Price: 1},
{Name: "Neo.Native.Deploy", Func: native.Deploy, Price: 1},
{Name: "Neo.Output.GetAssetId", Func: outputGetAssetID, Price: 1}, {Name: "Neo.Output.GetAssetId", Func: outputGetAssetID, Price: 1},
{Name: "Neo.Output.GetScriptHash", Func: outputGetScriptHash, Price: 1}, {Name: "Neo.Output.GetScriptHash", Func: outputGetScriptHash, Price: 1},
{Name: "Neo.Output.GetValue", Func: outputGetValue, Price: 1}, {Name: "Neo.Output.GetValue", Func: outputGetValue, Price: 1},
{Name: "Neo.Runtime.CheckWitness", Func: runtimeCheckWitness, Price: 200}, {Name: "Neo.Runtime.CheckWitness", Func: runtime.CheckWitness, Price: 200},
{Name: "Neo.Runtime.Deserialize", Func: runtimeDeserialize, Price: 1}, {Name: "Neo.Runtime.Deserialize", Func: runtimeDeserialize, Price: 1},
{Name: "Neo.Runtime.GetTime", Func: runtimeGetTime, Price: 1}, {Name: "Neo.Runtime.GetTime", Func: runtimeGetTime, Price: 1},
{Name: "Neo.Runtime.GetTrigger", Func: runtimeGetTrigger, Price: 1}, {Name: "Neo.Runtime.GetTrigger", Func: runtimeGetTrigger, Price: 1},
@ -235,7 +244,7 @@ var neoInterops = []interop.Function{
{Name: "AntShares.Output.GetAssetId", Func: outputGetAssetID, Price: 1}, {Name: "AntShares.Output.GetAssetId", Func: outputGetAssetID, Price: 1},
{Name: "AntShares.Output.GetScriptHash", Func: outputGetScriptHash, Price: 1}, {Name: "AntShares.Output.GetScriptHash", Func: outputGetScriptHash, Price: 1},
{Name: "AntShares.Output.GetValue", Func: outputGetValue, Price: 1}, {Name: "AntShares.Output.GetValue", Func: outputGetValue, Price: 1},
{Name: "AntShares.Runtime.CheckWitness", Func: runtimeCheckWitness, Price: 200}, {Name: "AntShares.Runtime.CheckWitness", Func: runtime.CheckWitness, Price: 200},
{Name: "AntShares.Runtime.Log", Func: runtimeLog, Price: 1}, {Name: "AntShares.Runtime.Log", Func: runtimeLog, Price: 1},
{Name: "AntShares.Runtime.Notify", Func: runtimeNotify, Price: 1}, {Name: "AntShares.Runtime.Notify", Func: runtimeNotify, Price: 1},
{Name: "AntShares.Storage.Delete", Func: storageDelete, Price: 100}, {Name: "AntShares.Storage.Delete", Func: storageDelete, Price: 100},

158
pkg/core/native/contract.go Normal file
View file

@ -0,0 +1,158 @@
package native
import (
"fmt"
"github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/pkg/errors"
)
// Method is a signature for a native method.
type Method = func(ic *interop.Context, args []vm.StackItem) vm.StackItem
// MethodAndPrice is a native-contract method descriptor.
type MethodAndPrice struct {
Func Method
Price int64
RequiredFlags smartcontract.CallFlag
}
// Contract is an interface for all native contracts.
type Contract interface {
Metadata() *ContractMD
OnPersist(*interop.Context) error
}
// ContractMD represents native contract instance.
type ContractMD struct {
Manifest manifest.Manifest
ServiceName string
ServiceID uint32
Script []byte
Hash util.Uint160
Methods map[string]MethodAndPrice
}
// Contracts is a set of registered native contracts.
type Contracts struct {
NEO *NEO
GAS *GAS
Contracts []Contract
}
// SetGAS sets GAS native contract.
func (cs *Contracts) SetGAS(g *GAS) {
cs.GAS = g
cs.Contracts = append(cs.Contracts, g)
}
// SetNEO sets NEO native contract.
func (cs *Contracts) SetNEO(n *NEO) {
cs.NEO = n
cs.Contracts = append(cs.Contracts, n)
}
// NewContractMD returns Contract with the specified list of methods.
func NewContractMD(name string) *ContractMD {
c := &ContractMD{
ServiceName: name,
ServiceID: vm.InteropNameToID([]byte(name)),
Methods: make(map[string]MethodAndPrice),
}
w := io.NewBufBinWriter()
emit.Syscall(w.BinWriter, c.ServiceName)
c.Script = w.Bytes()
c.Hash = hash.Hash160(c.Script)
c.Manifest = *manifest.DefaultManifest(c.Hash)
return c
}
// ByHash returns native contract with the specified hash.
func (cs *Contracts) ByHash(h util.Uint160) Contract {
for _, ctr := range cs.Contracts {
if ctr.Metadata().Hash.Equals(h) {
return ctr
}
}
return nil
}
// ByID returns native contract with the specified id.
func (cs *Contracts) ByID(id uint32) Contract {
for _, ctr := range cs.Contracts {
if ctr.Metadata().ServiceID == id {
return ctr
}
}
return nil
}
// AddMethod adds new method to a native contract.
func (c *ContractMD) AddMethod(md *MethodAndPrice, desc *manifest.Method, safe bool) {
c.Manifest.ABI.Methods = append(c.Manifest.ABI.Methods, *desc)
c.Methods[desc.Name] = *md
if safe {
c.Manifest.SafeMethods.Add(desc.Name)
}
}
// AddEvent adds new event to a native contract.
func (c *ContractMD) AddEvent(name string, ps ...manifest.Parameter) {
c.Manifest.ABI.Events = append(c.Manifest.ABI.Events, manifest.Event{
Name: name,
Parameters: ps,
})
}
// NewContracts returns new empty set of native contracts.
func NewContracts() *Contracts {
return &Contracts{
Contracts: []Contract{},
}
}
// Add adds new native contracts to the list.
func (cs *Contracts) Add(c Contract) {
cs.Contracts = append(cs.Contracts, c)
}
// GetNativeInterop returns an interop getter for a given set of contracts.
func (cs *Contracts) GetNativeInterop(ic *interop.Context) func(uint32) *vm.InteropFuncPrice {
return func(id uint32) *vm.InteropFuncPrice {
if c := cs.ByID(id); c != nil {
return &vm.InteropFuncPrice{
Func: getNativeInterop(ic, c),
Price: 0, // TODO price func
}
}
return nil
}
}
// getNativeInterop returns native contract interop.
func getNativeInterop(ic *interop.Context, c Contract) func(v *vm.VM) error {
return func(v *vm.VM) error {
h := v.GetContextScriptHash(0)
if !h.Equals(c.Metadata().Hash) {
return errors.New("invalid hash")
}
name := string(v.Estack().Pop().Bytes())
args := v.Estack().Pop().Array()
m, ok := c.Metadata().Methods[name]
if !ok {
return fmt.Errorf("method %s not found", name)
}
result := m.Func(ic, args)
v.Estack().PushVal(result)
return nil
}
}

View file

@ -0,0 +1,16 @@
package native
import (
"errors"
"github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/vm"
)
// Deploy deploys native contract.
func Deploy(ic *interop.Context, _ *vm.VM) error {
if ic.Block.Index != 0 {
return errors.New("native contracts can be deployed only at 0 block")
}
return nil
}

View file

@ -0,0 +1,134 @@
package native
import (
"errors"
"math/big"
"github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/storage"
"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/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
)
// GAS represents GAS native contract.
type GAS struct {
nep5TokenNative
NEO *NEO
}
const gasSyscallName = "Neo.Native.Tokens.GAS"
// NewGAS returns GAS native contract.
func NewGAS() *GAS {
nep5 := newNEP5Native(gasSyscallName)
nep5.name = "GAS"
nep5.symbol = "gas"
nep5.decimals = 8
nep5.factor = 100000000
g := &GAS{nep5TokenNative: *nep5}
desc := newDescriptor("getSysFeeAmount", smartcontract.IntegerType,
manifest.NewParameter("index", smartcontract.IntegerType))
md := newMethodAndPrice(g.getSysFeeAmount, 1, smartcontract.NoneFlag)
g.AddMethod(md, desc, true)
g.onPersist = chainOnPersist(g.onPersist, g.OnPersist)
g.incBalance = g.increaseBalance
return g
}
// initFromStore initializes variable contract parameters from the store.
func (g *GAS) initFromStore(data []byte) error {
g.totalSupply = *emit.BytesToInt(data)
return nil
}
func (g *GAS) serializeState() []byte {
return emit.IntToBytes(&g.totalSupply)
}
func (g *GAS) increaseBalance(_ *interop.Context, acc *state.Account, amount *big.Int) error {
if sign := amount.Sign(); sign == 0 {
return nil
} else if sign == -1 && acc.GAS.Balance.Cmp(new(big.Int).Neg(amount)) == -1 {
return errors.New("insufficient funds")
}
acc.GAS.Balance.Add(&acc.GAS.Balance, amount)
return nil
}
// Initialize initializes GAS contract.
func (g *GAS) Initialize(ic *interop.Context) error {
data, err := ic.DAO.GetNativeContractState(g.Hash)
if err == nil {
return g.initFromStore(data)
} else if err != storage.ErrKeyNotFound {
return err
}
if err := g.nep5TokenNative.Initialize(); err != nil {
return err
}
h, _, err := getStandbyValidatorsHash(ic)
if err != nil {
return err
}
g.mint(ic, h, big.NewInt(30000000*g.factor))
return ic.DAO.PutNativeContractState(g.Hash, g.serializeState())
}
// OnPersist implements Contract interface.
func (g *GAS) OnPersist(ic *interop.Context) error {
//for _ ,tx := range ic.block.Transactions {
// g.burn(ic, tx.Sender, tx.SystemFee + tx.NetworkFee)
//}
//validators := g.NEO.getNextBlockValidators(ic)
//var netFee util.Fixed8
//for _, tx := range ic.block.Transactions {
// netFee += tx.NetworkFee
//}
//g.mint(ic, <primary>, netFee)
return ic.DAO.PutNativeContractState(g.Hash, g.serializeState())
}
func (g *GAS) getSysFeeAmount(ic *interop.Context, args []vm.StackItem) vm.StackItem {
index := toBigInt(args[0])
h := ic.Chain.GetHeaderHash(int(index.Int64()))
_, sf, err := ic.DAO.GetBlock(h)
if err != nil {
panic(err)
}
return vm.NewBigIntegerItem(big.NewInt(int64(sf)))
}
func getStandbyValidatorsHash(ic *interop.Context) (util.Uint160, []*keys.PublicKey, error) {
vs, err := ic.Chain.GetStandByValidators()
if err != nil {
return util.Uint160{}, nil, err
}
s, err := smartcontract.CreateMultiSigRedeemScript(len(vs)/2+1, vs)
if err != nil {
return util.Uint160{}, nil, err
}
return hash.Hash160(s), vs, nil
}
func chainOnPersist(fs ...func(*interop.Context) error) func(*interop.Context) error {
return func(ic *interop.Context) error {
for i := range fs {
if fs[i] != nil {
if err := fs[i](ic); err != nil {
return err
}
}
}
return nil
}
}

View file

@ -0,0 +1,380 @@
package native
import (
"math/big"
"sort"
"github.com/nspcc-dev/neo-go/pkg/core/blockchainer"
"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/runtime"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/storage"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/pkg/errors"
)
// NEO represents NEO native contract.
type NEO struct {
nep5TokenNative
GAS *GAS
}
const neoSyscallName = "Neo.Native.Tokens.NEO"
// NewNEO returns NEO native contract.
func NewNEO() *NEO {
nep5 := newNEP5Native(neoSyscallName)
nep5.name = "NEO"
nep5.symbol = "neo"
nep5.decimals = 0
nep5.factor = 1
n := &NEO{nep5TokenNative: *nep5}
desc := newDescriptor("unclaimedGas", smartcontract.IntegerType,
manifest.NewParameter("account", smartcontract.Hash160Type),
manifest.NewParameter("end", smartcontract.IntegerType))
md := newMethodAndPrice(n.unclaimedGas, 1, smartcontract.NoneFlag)
n.AddMethod(md, desc, true)
desc = newDescriptor("registerValidator", smartcontract.BoolType,
manifest.NewParameter("pubkey", smartcontract.PublicKeyType))
md = newMethodAndPrice(n.registerValidator, 1, smartcontract.NoneFlag)
n.AddMethod(md, desc, false)
desc = newDescriptor("vote", smartcontract.BoolType,
manifest.NewParameter("account", smartcontract.Hash160Type),
manifest.NewParameter("pubkeys", smartcontract.ArrayType))
md = newMethodAndPrice(n.vote, 1, smartcontract.NoneFlag)
n.AddMethod(md, desc, false)
desc = newDescriptor("getRegisteredValidators", smartcontract.ArrayType)
md = newMethodAndPrice(n.getRegisteredValidators, 1, smartcontract.NoneFlag)
n.AddMethod(md, desc, true)
desc = newDescriptor("getValidators", smartcontract.ArrayType)
md = newMethodAndPrice(n.getValidators, 1, smartcontract.NoneFlag)
n.AddMethod(md, desc, true)
desc = newDescriptor("getNextBlockValidators", smartcontract.ArrayType)
md = newMethodAndPrice(n.getNextBlockValidators, 1, smartcontract.NoneFlag)
n.AddMethod(md, desc, true)
n.onPersist = chainOnPersist(n.onPersist, n.OnPersist)
n.incBalance = n.increaseBalance
return n
}
// Initialize initializes NEO contract.
func (n *NEO) Initialize(ic *interop.Context) error {
data, err := ic.DAO.GetNativeContractState(n.Hash)
if err == nil {
return n.initFromStore(data)
} else if err != storage.ErrKeyNotFound {
return err
}
if err := n.nep5TokenNative.Initialize(); err != nil {
return err
}
h, vs, err := getStandbyValidatorsHash(ic)
if err != nil {
return err
}
n.mint(ic, h, big.NewInt(100000000*n.factor))
for i := range vs {
if err := n.registerValidatorInternal(ic, vs[i]); err != nil {
return err
}
}
return ic.DAO.PutNativeContractState(n.Hash, n.serializeState())
}
// initFromStore initializes variable contract parameters from the store.
func (n *NEO) initFromStore(data []byte) error {
n.totalSupply = *emit.BytesToInt(data)
return nil
}
func (n *NEO) serializeState() []byte {
return emit.IntToBytes(&n.totalSupply)
}
// OnPersist implements Contract interface.
func (n *NEO) OnPersist(ic *interop.Context) error {
pubs, err := n.GetValidatorsInternal(ic.Chain, ic.DAO)
if err != nil {
return err
}
if err := ic.DAO.PutNextBlockValidators(pubs); err != nil {
return err
}
return ic.DAO.PutNativeContractState(n.Hash, n.serializeState())
}
func (n *NEO) increaseBalance(ic *interop.Context, acc *state.Account, amount *big.Int) error {
if sign := amount.Sign(); sign == 0 {
return nil
} else if sign == -1 && acc.NEO.Balance.Cmp(new(big.Int).Neg(amount)) == -1 {
return errors.New("insufficient funds")
}
if err := n.distributeGas(ic, acc); err != nil {
return err
}
acc.NEO.Balance.Add(&acc.NEO.Balance, amount)
return nil
}
func (n *NEO) distributeGas(ic *interop.Context, acc *state.Account) error {
if ic.Block == nil {
return nil
}
sys, net, err := ic.Chain.CalculateClaimable(util.Fixed8(acc.NEO.Balance.Int64()), acc.NEO.BalanceHeight, ic.Block.Index)
if err != nil {
return err
}
acc.NEO.BalanceHeight = ic.Block.Index
n.GAS.mint(ic, acc.ScriptHash, big.NewInt(int64(sys+net)))
return nil
}
func (n *NEO) unclaimedGas(ic *interop.Context, args []vm.StackItem) vm.StackItem {
u := toUint160(args[0])
end := uint32(toBigInt(args[1]).Int64())
bs, err := ic.DAO.GetNEP5Balances(u)
if err != nil {
panic(err)
}
tr := bs.Trackers[n.Hash]
sys, net, err := ic.Chain.CalculateClaimable(util.Fixed8(tr.Balance), tr.LastUpdatedBlock, end)
if err != nil {
panic(err)
}
return vm.NewBigIntegerItem(big.NewInt(int64(sys.Add(net))))
}
func (n *NEO) registerValidator(ic *interop.Context, args []vm.StackItem) vm.StackItem {
err := n.registerValidatorInternal(ic, toPublicKey(args[0]))
return vm.NewBoolItem(err == nil)
}
func (n *NEO) registerValidatorInternal(ic *interop.Context, pub *keys.PublicKey) error {
_, err := ic.DAO.GetValidatorState(pub)
if err == nil {
return err
}
return ic.DAO.PutValidatorState(&state.Validator{PublicKey: pub})
}
func (n *NEO) vote(ic *interop.Context, args []vm.StackItem) vm.StackItem {
acc := toUint160(args[0])
arr := args[1].Value().([]vm.StackItem)
var pubs keys.PublicKeys
for i := range arr {
pub := new(keys.PublicKey)
bs, err := arr[i].TryBytes()
if err != nil {
panic(err)
} else if err := pub.DecodeBytes(bs); err != nil {
panic(err)
}
pubs = append(pubs, pub)
}
err := n.VoteInternal(ic, acc, pubs)
return vm.NewBoolItem(err == nil)
}
// VoteInternal votes from account h for validarors specified in pubs.
func (n *NEO) VoteInternal(ic *interop.Context, h util.Uint160, pubs keys.PublicKeys) error {
ok, err := runtime.CheckHashedWitness(ic, h)
if err != nil {
return err
} else if !ok {
return errors.New("invalid signature")
}
acc, err := ic.DAO.GetAccountState(h)
if err != nil {
return err
}
balance := util.Fixed8(acc.NEO.Balance.Int64())
if err := ModifyAccountVotes(acc, ic.DAO, -balance); err != nil {
return err
}
pubs = pubs.Unique()
var newPubs keys.PublicKeys
for _, pub := range pubs {
_, err := ic.DAO.GetValidatorState(pub)
if err != nil {
if err == storage.ErrKeyNotFound {
continue
}
return err
}
newPubs = append(newPubs, pub)
}
if lp, lv := len(newPubs), len(acc.Votes); lp != lv {
vc, err := ic.DAO.GetValidatorsCount()
if err != nil {
return err
}
if lv > 0 {
vc[lv-1] -= balance
}
if len(newPubs) > 0 {
vc[lp-1] += balance
}
if err := ic.DAO.PutValidatorsCount(vc); err != nil {
return err
}
}
acc.Votes = newPubs
return ModifyAccountVotes(acc, ic.DAO, balance)
}
// ModifyAccountVotes modifies votes of the specified account by value (can be negative).
func ModifyAccountVotes(acc *state.Account, d dao.DAO, value util.Fixed8) error {
for _, vote := range acc.Votes {
validator, err := d.GetValidatorStateOrNew(vote)
if err != nil {
return err
}
validator.Votes += value
if validator.UnregisteredAndHasNoVotes() {
if err := d.DeleteValidatorState(validator); err != nil {
return err
}
} else {
if err := d.PutValidatorState(validator); err != nil {
return err
}
}
}
return nil
}
func (n *NEO) getRegisteredValidators(ic *interop.Context, _ []vm.StackItem) vm.StackItem {
vs := ic.DAO.GetValidators()
arr := make([]vm.StackItem, len(vs))
for i := range vs {
arr[i] = vm.NewStructItem([]vm.StackItem{
vm.NewByteArrayItem(vs[i].PublicKey.Bytes()),
vm.NewBigIntegerItem(big.NewInt(int64(vs[i].Votes))),
})
}
return vm.NewArrayItem(arr)
}
// GetValidatorsInternal returns a list of current validators.
func (n *NEO) GetValidatorsInternal(bc blockchainer.Blockchainer, d dao.DAO) ([]*keys.PublicKey, error) {
validatorsCount, err := d.GetValidatorsCount()
if err != nil {
return nil, err
} else if len(validatorsCount) == 0 {
sb, err := bc.GetStandByValidators()
if err != nil {
return nil, err
}
return sb, nil
}
validators := d.GetValidators()
sort.Slice(validators, func(i, j int) bool {
// Unregistered validators go to the end of the list.
if validators[i].Registered != validators[j].Registered {
return validators[i].Registered
}
// The most-voted validators should end up in the front of the list.
if validators[i].Votes != validators[j].Votes {
return validators[i].Votes > validators[j].Votes
}
// Ties are broken with public keys.
return validators[i].PublicKey.Cmp(validators[j].PublicKey) == -1
})
count := validatorsCount.GetWeightedAverage()
standByValidators, err := bc.GetStandByValidators()
if err != nil {
return nil, err
}
if count < len(standByValidators) {
count = len(standByValidators)
}
uniqueSBValidators := standByValidators.Unique()
result := keys.PublicKeys{}
for _, validator := range validators {
if validator.RegisteredAndHasVotes() || uniqueSBValidators.Contains(validator.PublicKey) {
result = append(result, validator.PublicKey)
}
}
if result.Len() >= count {
result = result[:count]
} else {
for i := 0; i < uniqueSBValidators.Len() && result.Len() < count; i++ {
if !result.Contains(uniqueSBValidators[i]) {
result = append(result, uniqueSBValidators[i])
}
}
}
sort.Sort(result)
return result, nil
}
func (n *NEO) getValidators(ic *interop.Context, _ []vm.StackItem) vm.StackItem {
result, err := n.GetValidatorsInternal(ic.Chain, ic.DAO)
if err != nil {
panic(err)
}
return pubsToArray(result)
}
func (n *NEO) getNextBlockValidators(ic *interop.Context, _ []vm.StackItem) vm.StackItem {
result, err := n.GetNextBlockValidatorsInternal(ic.Chain, ic.DAO)
if err != nil {
panic(err)
}
return pubsToArray(result)
}
// GetNextBlockValidatorsInternal returns next block validators.
func (n *NEO) GetNextBlockValidatorsInternal(bc blockchainer.Blockchainer, d dao.DAO) ([]*keys.PublicKey, error) {
result, err := d.GetNextBlockValidators()
if err != nil {
return nil, err
} else if result == nil {
return bc.GetStandByValidators()
}
return result, nil
}
func pubsToArray(pubs keys.PublicKeys) vm.StackItem {
arr := make([]vm.StackItem, len(pubs))
for i := range pubs {
arr[i] = vm.NewByteArrayItem(pubs[i].Bytes())
}
return vm.NewArrayItem(arr)
}
func toPublicKey(s vm.StackItem) *keys.PublicKey {
buf, err := s.TryBytes()
if err != nil {
panic(err)
}
pub := new(keys.PublicKey)
if err := pub.DecodeBytes(buf); err != nil {
panic(err)
}
return pub
}

View file

@ -0,0 +1,229 @@
package native
import (
"errors"
"math/big"
"github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
)
// nep5TokenNative represents NEP-5 token contract.
type nep5TokenNative struct {
ContractMD
name string
symbol string
decimals int64
factor int64
totalSupply big.Int
onPersist func(*interop.Context) error
incBalance func(*interop.Context, *state.Account, *big.Int) error
}
func (c *nep5TokenNative) Metadata() *ContractMD {
return &c.ContractMD
}
var _ Contract = (*nep5TokenNative)(nil)
func newNEP5Native(name string) *nep5TokenNative {
n := &nep5TokenNative{ContractMD: *NewContractMD(name)}
desc := newDescriptor("name", smartcontract.StringType)
md := newMethodAndPrice(n.Name, 1, smartcontract.NoneFlag)
n.AddMethod(md, desc, true)
desc = newDescriptor("symbol", smartcontract.StringType)
md = newMethodAndPrice(n.Symbol, 1, smartcontract.NoneFlag)
n.AddMethod(md, desc, true)
desc = newDescriptor("decimals", smartcontract.IntegerType)
md = newMethodAndPrice(n.Decimals, 1, smartcontract.NoneFlag)
n.AddMethod(md, desc, true)
desc = newDescriptor("balanceOf", smartcontract.IntegerType,
manifest.NewParameter("account", smartcontract.Hash160Type))
md = newMethodAndPrice(n.balanceOf, 1, smartcontract.NoneFlag)
n.AddMethod(md, desc, true)
desc = newDescriptor("transfer", smartcontract.BoolType,
manifest.NewParameter("from", smartcontract.Hash160Type),
manifest.NewParameter("to", smartcontract.Hash160Type),
manifest.NewParameter("amount", smartcontract.IntegerType),
)
md = newMethodAndPrice(n.Transfer, 1, smartcontract.NoneFlag)
n.AddMethod(md, desc, false)
n.AddEvent("Transfer", desc.Parameters...)
return n
}
func (c *nep5TokenNative) Initialize() error {
return nil
}
func (c *nep5TokenNative) Name(_ *interop.Context, _ []vm.StackItem) vm.StackItem {
return vm.NewByteArrayItem([]byte(c.name))
}
func (c *nep5TokenNative) Symbol(_ *interop.Context, _ []vm.StackItem) vm.StackItem {
return vm.NewByteArrayItem([]byte(c.symbol))
}
func (c *nep5TokenNative) Decimals(_ *interop.Context, _ []vm.StackItem) vm.StackItem {
return vm.NewBigIntegerItem(big.NewInt(c.decimals))
}
func (c *nep5TokenNative) Transfer(ic *interop.Context, args []vm.StackItem) vm.StackItem {
from := toUint160(args[0])
to := toUint160(args[1])
amount := toBigInt(args[2])
err := c.transfer(ic, from, to, amount)
return vm.NewBoolItem(err == nil)
}
func addrToStackItem(u *util.Uint160) vm.StackItem {
if u == nil {
return nil
}
return vm.NewByteArrayItem(u.BytesBE())
}
func (c *nep5TokenNative) emitTransfer(ic *interop.Context, from, to *util.Uint160, amount *big.Int) {
ne := state.NotificationEvent{
ScriptHash: c.Hash,
Item: vm.NewArrayItem([]vm.StackItem{
vm.NewByteArrayItem([]byte("Transfer")),
addrToStackItem(from),
addrToStackItem(to),
vm.NewBigIntegerItem(amount),
}),
}
ic.Notifications = append(ic.Notifications, ne)
}
func (c *nep5TokenNative) transfer(ic *interop.Context, from, to util.Uint160, amount *big.Int) error {
if amount.Sign() == -1 {
return errors.New("negative amount")
}
accFrom, err := ic.DAO.GetAccountStateOrNew(from)
if err != nil {
return err
}
isEmpty := from.Equals(to) || amount.Sign() == 0
inc := amount
if isEmpty {
inc = big.NewInt(0)
}
if err := c.incBalance(ic, accFrom, inc); err != nil {
return err
}
if err := ic.DAO.PutAccountState(accFrom); err != nil {
return err
}
if !isEmpty {
accTo, err := ic.DAO.GetAccountStateOrNew(to)
if err != nil {
return err
}
if err := c.incBalance(ic, accTo, amount); err != nil {
return err
}
if err := ic.DAO.PutAccountState(accTo); err != nil {
return err
}
}
c.emitTransfer(ic, &from, &to, amount)
return nil
}
func (c *nep5TokenNative) balanceOf(ic *interop.Context, args []vm.StackItem) vm.StackItem {
h := toUint160(args[0])
bs, err := ic.DAO.GetNEP5Balances(h)
if err != nil {
panic(err)
}
balance := bs.Trackers[c.Hash].Balance
return vm.NewBigIntegerItem(big.NewInt(balance))
}
func (c *nep5TokenNative) mint(ic *interop.Context, h util.Uint160, amount *big.Int) {
c.addTokens(ic, h, amount)
c.emitTransfer(ic, nil, &h, amount)
}
func (c *nep5TokenNative) burn(ic *interop.Context, h util.Uint160, amount *big.Int) {
amount = new(big.Int).Neg(amount)
c.addTokens(ic, h, amount)
c.emitTransfer(ic, &h, nil, amount)
}
func (c *nep5TokenNative) addTokens(ic *interop.Context, h util.Uint160, amount *big.Int) {
if sign := amount.Sign(); sign == -1 {
panic("negative amount")
} else if sign == 0 {
return
}
acc, err := ic.DAO.GetAccountStateOrNew(h)
if err != nil {
panic(err)
}
if err := c.incBalance(ic, acc, amount); err != nil {
panic(err)
}
if err := ic.DAO.PutAccountState(acc); err != nil {
panic(err)
}
c.totalSupply.Add(&c.totalSupply, amount)
}
func (c *nep5TokenNative) OnPersist(ic *interop.Context) error {
return c.onPersist(ic)
}
func newDescriptor(name string, ret smartcontract.ParamType, ps ...manifest.Parameter) *manifest.Method {
return &manifest.Method{
Name: name,
Parameters: ps,
ReturnType: ret,
}
}
func newMethodAndPrice(f Method, price int64, flags smartcontract.CallFlag) *MethodAndPrice {
return &MethodAndPrice{
Func: f,
Price: price,
RequiredFlags: flags,
}
}
func toBigInt(s vm.StackItem) *big.Int {
bi, err := s.TryInteger()
if err != nil {
panic(err)
}
return bi
}
func toUint160(s vm.StackItem) util.Uint160 {
buf, err := s.TryBytes()
if err != nil {
panic(err)
}
u, err := util.Uint160DecodeBytesBE(buf)
if err != nil {
panic(err)
}
return u
}

View file

@ -0,0 +1,106 @@
package core
import (
"errors"
"math/rand"
"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/transaction"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/stretchr/testify/require"
)
type testNative struct {
meta native.ContractMD
blocks chan uint32
}
func (tn *testNative) Metadata() *native.ContractMD {
return &tn.meta
}
func (tn *testNative) OnPersist(ic *interop.Context) error {
select {
case tn.blocks <- ic.Block.Index:
return nil
default:
return errors.New("error on persist")
}
}
var _ native.Contract = (*testNative)(nil)
func newTestNative() *testNative {
tn := &testNative{
meta: *native.NewContractMD("Test.Native.Sum"),
blocks: make(chan uint32, 1),
}
desc := &manifest.Method{
Name: "sum",
Parameters: []manifest.Parameter{
manifest.NewParameter("addend1", smartcontract.IntegerType),
manifest.NewParameter("addend2", smartcontract.IntegerType),
},
ReturnType: smartcontract.IntegerType,
}
md := &native.MethodAndPrice{
Func: tn.sum,
Price: 1,
RequiredFlags: smartcontract.NoneFlag,
}
tn.meta.AddMethod(md, desc, true)
return tn
}
func (tn *testNative) sum(_ *interop.Context, args []vm.StackItem) vm.StackItem {
s1, err := args[0].TryInteger()
if err != nil {
panic(err)
}
s2, err := args[1].TryInteger()
if err != nil {
panic(err)
}
return vm.NewBigIntegerItem(s1.Add(s1, s2))
}
func TestNativeContract_Invoke(t *testing.T) {
chain := newTestChain(t)
defer chain.Close()
tn := newTestNative()
chain.RegisterNative(tn)
w := io.NewBufBinWriter()
emit.AppCallWithOperationAndArgs(w.BinWriter, tn.Metadata().Hash, "sum", int64(14), int64(28))
script := w.Bytes()
tx := transaction.NewInvocationTX(script, 0)
mn := transaction.NewMinerTXWithNonce(rand.Uint32())
validUntil := chain.blockHeight + 1
tx.ValidUntilBlock = validUntil
mn.ValidUntilBlock = validUntil
b := chain.newBlock(mn, tx)
require.NoError(t, chain.AddBlock(b))
res, err := chain.GetAppExecResult(tx.Hash())
require.NoError(t, err)
require.Equal(t, "HALT", res.VMState)
require.Equal(t, 1, len(res.Stack))
require.Equal(t, smartcontract.IntegerType, res.Stack[0].Type)
require.EqualValues(t, 42, res.Stack[0].Value)
require.NoError(t, chain.persist())
select {
case index := <-tn.blocks:
require.Equal(t, chain.blockHeight, index)
default:
require.Fail(t, "onPersist wasn't called")
}
}

View file

@ -33,6 +33,8 @@ type Account struct {
ScriptHash util.Uint160 ScriptHash util.Uint160
IsFrozen bool IsFrozen bool
Votes []*keys.PublicKey Votes []*keys.PublicKey
GAS NEP5BalanceState
NEO NEOBalanceState
Balances map[util.Uint256][]UnspentBalance Balances map[util.Uint256][]UnspentBalance
Unclaimed UnclaimedBalances Unclaimed UnclaimedBalances
} }
@ -55,6 +57,8 @@ func (s *Account) DecodeBinary(br *io.BinReader) {
br.ReadBytes(s.ScriptHash[:]) br.ReadBytes(s.ScriptHash[:])
s.IsFrozen = br.ReadBool() s.IsFrozen = br.ReadBool()
br.ReadArray(&s.Votes) br.ReadArray(&s.Votes)
s.GAS.DecodeBinary(br)
s.NEO.DecodeBinary(br)
s.Balances = make(map[util.Uint256][]UnspentBalance) s.Balances = make(map[util.Uint256][]UnspentBalance)
lenBalances := br.ReadVarUint() lenBalances := br.ReadVarUint()
@ -80,6 +84,8 @@ func (s *Account) EncodeBinary(bw *io.BinWriter) {
bw.WriteBytes(s.ScriptHash[:]) bw.WriteBytes(s.ScriptHash[:])
bw.WriteBool(s.IsFrozen) bw.WriteBool(s.IsFrozen)
bw.WriteArray(s.Votes) bw.WriteArray(s.Votes)
s.GAS.EncodeBinary(bw)
s.NEO.EncodeBinary(bw)
bw.WriteVarUint(uint64(len(s.Balances))) bw.WriteVarUint(uint64(len(s.Balances)))
for k, v := range s.Balances { for k, v := range s.Balances {

View file

@ -0,0 +1,45 @@
package state
import (
"math/big"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
)
// NEP5BalanceState represents balance state of a NEP5-token.
type NEP5BalanceState struct {
Balance big.Int
}
// NEOBalanceState represents balance state of a NEO-token.
type NEOBalanceState struct {
NEP5BalanceState
BalanceHeight uint32
}
// EncodeBinary implements io.Serializable interface.
func (s *NEP5BalanceState) EncodeBinary(w *io.BinWriter) {
w.WriteVarBytes(emit.IntToBytes(&s.Balance))
}
// DecodeBinary implements io.Serializable interface.
func (s *NEP5BalanceState) DecodeBinary(r *io.BinReader) {
buf := r.ReadVarBytes()
if r.Err != nil {
return
}
s.Balance = *emit.BytesToInt(buf)
}
// EncodeBinary implements io.Serializable interface.
func (s *NEOBalanceState) EncodeBinary(w *io.BinWriter) {
s.NEP5BalanceState.EncodeBinary(w)
w.WriteU32LE(s.BalanceHeight)
}
// DecodeBinary implements io.Serializable interface.
func (s *NEOBalanceState) DecodeBinary(r *io.BinReader) {
s.NEP5BalanceState.DecodeBinary(r)
s.BalanceHeight = r.ReadU32LE()
}

View file

@ -12,10 +12,12 @@ const (
STAccount KeyPrefix = 0x40 STAccount KeyPrefix = 0x40
STCoin KeyPrefix = 0x44 STCoin KeyPrefix = 0x44
STSpentCoin KeyPrefix = 0x45 STSpentCoin KeyPrefix = 0x45
STNextValidators KeyPrefix = 0x47
STValidator KeyPrefix = 0x48 STValidator KeyPrefix = 0x48
STAsset KeyPrefix = 0x4c STAsset KeyPrefix = 0x4c
STNotification KeyPrefix = 0x4d STNotification KeyPrefix = 0x4d
STContract KeyPrefix = 0x50 STContract KeyPrefix = 0x50
STNativeContract KeyPrefix = 0x51
STStorage KeyPrefix = 0x70 STStorage KeyPrefix = 0x70
STNEP5Transfers KeyPrefix = 0x72 STNEP5Transfers KeyPrefix = 0x72
STNEP5Balances KeyPrefix = 0x73 STNEP5Balances KeyPrefix = 0x73

View file

@ -99,6 +99,9 @@ func (chain testChain) GetNEP5Balances(util.Uint160) *state.NEP5Balances {
func (chain testChain) GetValidators(...*transaction.Transaction) ([]*keys.PublicKey, error) { func (chain testChain) GetValidators(...*transaction.Transaction) ([]*keys.PublicKey, error) {
panic("TODO") panic("TODO")
} }
func (chain testChain) GetStandByValidators() (keys.PublicKeys, error) {
panic("TODO")
}
func (chain testChain) GetEnrollments() ([]*state.Validator, error) { func (chain testChain) GetEnrollments() ([]*state.Validator, error) {
panic("TODO") panic("TODO")
} }

View file

@ -0,0 +1,14 @@
package smartcontract
// CallFlag represents call flag.
type CallFlag byte
// Default flags.
const (
NoneFlag CallFlag = 0
AllowModifyStates CallFlag = 1 << iota
AllowCall
AllowNotify
ReadOnly = AllowCall | AllowNotify
All = AllowModifyStates | AllowCall | AllowNotify
)

View file

@ -0,0 +1,106 @@
package manifest
// This file contains types and helper methods for wildcard containers.
// Wildcard container can contain either a finite set of elements or
// every possible element, in which case it is named `wildcard`.
import (
"bytes"
"encoding/json"
"github.com/nspcc-dev/neo-go/pkg/util"
)
// WildStrings represents string set which can be wildcard.
type WildStrings struct {
Value []string
}
// WildUint160s represents Uint160 set which can be wildcard.
type WildUint160s struct {
Value []util.Uint160
}
// Contains checks if v is in the container.
func (c *WildStrings) Contains(v string) bool {
if c.IsWildcard() {
return true
}
for _, s := range c.Value {
if v == s {
return true
}
}
return false
}
// Contains checks if v is in the container.
func (c *WildUint160s) Contains(v util.Uint160) bool {
if c.IsWildcard() {
return true
}
for _, u := range c.Value {
if u.Equals(v) {
return true
}
}
return false
}
// IsWildcard returns true iff container is wildcard.
func (c *WildStrings) IsWildcard() bool { return c.Value == nil }
// IsWildcard returns true iff container is wildcard.
func (c *WildUint160s) IsWildcard() bool { return c.Value == nil }
// Restrict transforms container into an empty one.
func (c *WildStrings) Restrict() { c.Value = []string{} }
// Restrict transforms container into an empty one.
func (c *WildUint160s) Restrict() { c.Value = []util.Uint160{} }
// Add adds v to the container.
func (c *WildStrings) Add(v string) { c.Value = append(c.Value, v) }
// Add adds v to the container.
func (c *WildUint160s) Add(v util.Uint160) { c.Value = append(c.Value, v) }
// MarshalJSON implements json.Marshaler interface.
func (c *WildStrings) MarshalJSON() ([]byte, error) {
if c.IsWildcard() {
return []byte(`"*"`), nil
}
return json.Marshal(c.Value)
}
// MarshalJSON implements json.Marshaler interface.
func (c *WildUint160s) MarshalJSON() ([]byte, error) {
if c.IsWildcard() {
return []byte(`"*"`), nil
}
return json.Marshal(c.Value)
}
// UnmarshalJSON implements json.Unmarshaler interface.
func (c *WildStrings) UnmarshalJSON(data []byte) error {
if !bytes.Equal(data, []byte(`"*"`)) {
ss := []string{}
if err := json.Unmarshal(data, &ss); err != nil {
return err
}
c.Value = ss
}
return nil
}
// UnmarshalJSON implements json.Unmarshaler interface.
func (c *WildUint160s) UnmarshalJSON(data []byte) error {
if !bytes.Equal(data, []byte(`"*"`)) {
us := []util.Uint160{}
if err := json.Unmarshal(data, &us); err != nil {
return err
}
c.Value = us
}
return nil
}

View file

@ -0,0 +1,112 @@
package manifest
import (
"encoding/json"
"testing"
"github.com/nspcc-dev/neo-go/pkg/internal/random"
"github.com/nspcc-dev/neo-go/pkg/internal/testserdes"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/stretchr/testify/require"
)
func TestContainer_Restrict(t *testing.T) {
t.Run("string", func(t *testing.T) {
c := new(WildStrings)
require.True(t, c.IsWildcard())
require.True(t, c.Contains("abc"))
c.Restrict()
require.False(t, c.IsWildcard())
require.False(t, c.Contains("abc"))
require.Equal(t, 0, len(c.Value))
})
t.Run("uint160", func(t *testing.T) {
c := new(WildUint160s)
u := random.Uint160()
require.True(t, c.IsWildcard())
require.True(t, c.Contains(u))
c.Restrict()
require.False(t, c.IsWildcard())
require.False(t, c.Contains(u))
require.Equal(t, 0, len(c.Value))
})
}
func TestContainer_Add(t *testing.T) {
t.Run("string", func(t *testing.T) {
c := new(WildStrings)
require.Equal(t, []string(nil), c.Value)
c.Add("abc")
require.True(t, c.Contains("abc"))
require.False(t, c.Contains("aaa"))
})
t.Run("uint160", func(t *testing.T) {
c := new(WildUint160s)
require.Equal(t, []util.Uint160(nil), c.Value)
exp := []util.Uint160{random.Uint160(), random.Uint160()}
for i := range exp {
c.Add(exp[i])
}
for i := range exp {
require.True(t, c.Contains(exp[i]))
}
require.False(t, c.Contains(random.Uint160()))
})
}
func TestContainer_MarshalJSON(t *testing.T) {
t.Run("string", func(t *testing.T) {
t.Run("wildcard", func(t *testing.T) {
expected := new(WildStrings)
testserdes.MarshalUnmarshalJSON(t, expected, new(WildStrings))
})
t.Run("empty", func(t *testing.T) {
expected := new(WildStrings)
expected.Restrict()
testserdes.MarshalUnmarshalJSON(t, expected, new(WildStrings))
})
t.Run("non-empty", func(t *testing.T) {
expected := new(WildStrings)
expected.Add("string1")
expected.Add("string2")
testserdes.MarshalUnmarshalJSON(t, expected, new(WildStrings))
})
t.Run("invalid", func(t *testing.T) {
js := []byte(`[123]`)
c := new(WildStrings)
require.Error(t, json.Unmarshal(js, c))
})
})
t.Run("uint160", func(t *testing.T) {
t.Run("wildcard", func(t *testing.T) {
expected := new(WildUint160s)
testserdes.MarshalUnmarshalJSON(t, expected, new(WildUint160s))
})
t.Run("empty", func(t *testing.T) {
expected := new(WildUint160s)
expected.Restrict()
testserdes.MarshalUnmarshalJSON(t, expected, new(WildUint160s))
})
t.Run("non-empty", func(t *testing.T) {
expected := new(WildUint160s)
expected.Add(random.Uint160())
testserdes.MarshalUnmarshalJSON(t, expected, new(WildUint160s))
})
t.Run("invalid", func(t *testing.T) {
js := []byte(`["notahex"]`)
c := new(WildUint160s)
require.Error(t, json.Unmarshal(js, c))
})
})
}

View file

@ -0,0 +1,128 @@
package manifest
import (
"encoding/json"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util"
)
// MaxManifestSize is a max length for a valid contract manifest.
const MaxManifestSize = 2048
// ABI represents a contract application binary interface.
type ABI struct {
Hash util.Uint160 `json:"hash"`
EntryPoint Method `json:"entryPoint"`
Methods []Method `json:"methods"`
Events []Event `json:"events"`
}
// Manifest represens contract metadata.
type Manifest struct {
// ABI is a contract's ABI.
ABI ABI
// Groups is a set of groups to which a contract belongs.
Groups []Group
// Features is a set of contract's features.
Features smartcontract.PropertyState
Permissions []Permission
// Trusts is a set of hashes to a which contract trusts.
Trusts WildUint160s
// SafeMethods is a set of names of safe methods.
SafeMethods WildStrings
// Extra is an implementation-defined user data.
Extra interface{}
}
type manifestAux struct {
ABI *ABI `json:"abi"`
Groups []Group `json:"groups"`
Features map[string]bool `json:"features"`
Permissions []Permission `json:"permissions"`
Trusts *WildUint160s `json:"trusts"`
SafeMethods *WildStrings `json:"safeMethods"`
Extra interface{} `json:"extra"`
}
// NewManifest returns new manifest with necessary fields initialized.
func NewManifest(h util.Uint160) *Manifest {
m := &Manifest{
ABI: ABI{
Hash: h,
Methods: []Method{},
Events: []Event{},
},
Groups: []Group{},
Features: smartcontract.NoProperties,
}
m.Trusts.Restrict()
m.SafeMethods.Restrict()
return m
}
// DefaultManifest returns default contract manifest.
func DefaultManifest(h util.Uint160) *Manifest {
m := NewManifest(h)
m.ABI.EntryPoint = *DefaultEntryPoint()
m.Permissions = []Permission{*NewPermission(PermissionWildcard)}
return m
}
// CanCall returns true is current contract is allowed to call
// method of another contract.
func (m *Manifest) CanCall(toCall *Manifest, method string) bool {
// this if is not present in the original code but should probably be here
if toCall.SafeMethods.Contains(method) {
return true
}
for i := range m.Permissions {
if m.Permissions[i].IsAllowed(toCall, method) {
return true
}
}
return false
}
// MarshalJSON implements json.Marshaler interface.
func (m *Manifest) MarshalJSON() ([]byte, error) {
features := make(map[string]bool)
features["storage"] = m.Features&smartcontract.HasStorage != 0
features["payable"] = m.Features&smartcontract.IsPayable != 0
aux := &manifestAux{
ABI: &m.ABI,
Groups: m.Groups,
Features: features,
Permissions: m.Permissions,
Trusts: &m.Trusts,
SafeMethods: &m.SafeMethods,
Extra: m.Extra,
}
return json.Marshal(aux)
}
// UnmarshalJSON implements json.Unmarshaler interface.
func (m *Manifest) UnmarshalJSON(data []byte) error {
aux := &manifestAux{
ABI: &m.ABI,
Trusts: &m.Trusts,
SafeMethods: &m.SafeMethods,
}
if err := json.Unmarshal(data, aux); err != nil {
return err
}
if aux.Features["storage"] {
m.Features |= smartcontract.HasStorage
}
if aux.Features["payable"] {
m.Features |= smartcontract.IsPayable
}
m.Groups = aux.Groups
m.Permissions = aux.Permissions
m.Extra = aux.Extra
return nil
}

View file

@ -0,0 +1,121 @@
package manifest
import (
"encoding/json"
"testing"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/stretchr/testify/require"
)
// Test vectors are taken from the main NEO repo
// https://github.com/neo-project/neo/blob/master/tests/neo.UnitTests/SmartContract/Manifest/UT_ContractManifest.cs#L10
func TestManifest_MarshalJSON(t *testing.T) {
t.Run("default", func(t *testing.T) {
s := `{"groups":[],"features":{"storage":false,"payable":false},"abi":{"hash":"0x0000000000000000000000000000000000000000","entryPoint":{"name":"Main","parameters":[{"name":"operation","type":"String"},{"name":"args","type":"Array"}],"returnType":"Any"},"methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"safeMethods":[],"extra":null}`
m := testUnmarshalMarshalManifest(t, s)
require.Equal(t, DefaultManifest(util.Uint160{}), m)
})
// this vector is missing from original repo
t.Run("features", func(t *testing.T) {
s := `{"groups":[],"features":{"storage":true,"payable":true},"abi":{"hash":"0x0000000000000000000000000000000000000000","entryPoint":{"name":"Main","parameters":[{"name":"operation","type":"String"},{"name":"args","type":"Array"}],"returnType":"Any"},"methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"safeMethods":[],"extra":null}`
testUnmarshalMarshalManifest(t, s)
})
t.Run("permissions", func(t *testing.T) {
s := `{"groups":[],"features":{"storage":false,"payable":false},"abi":{"hash":"0x0000000000000000000000000000000000000000","entryPoint":{"name":"Main","parameters":[{"name":"operation","type":"String"},{"name":"args","type":"Array"}],"returnType":"Any"},"methods":[],"events":[]},"permissions":[{"contract":"0x0000000000000000000000000000000000000000","methods":["method1","method2"]}],"trusts":[],"safeMethods":[],"extra":null}`
testUnmarshalMarshalManifest(t, s)
})
t.Run("safe methods", func(t *testing.T) {
s := `{"groups":[],"features":{"storage":false,"payable":false},"abi":{"hash":"0x0000000000000000000000000000000000000000","entryPoint":{"name":"Main","parameters":[{"name":"operation","type":"String"},{"name":"args","type":"Array"}],"returnType":"Any"},"methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"safeMethods":["balanceOf"],"extra":null}`
testUnmarshalMarshalManifest(t, s)
})
t.Run("trust", func(t *testing.T) {
s := `{"groups":[],"features":{"storage":false,"payable":false},"abi":{"hash":"0x0000000000000000000000000000000000000000","entryPoint":{"name":"Main","parameters":[{"name":"operation","type":"String"},{"name":"args","type":"Array"}],"returnType":"Any"},"methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":["0x0000000000000000000000000000000000000001"],"safeMethods":[],"extra":null}`
testUnmarshalMarshalManifest(t, s)
})
t.Run("groups", func(t *testing.T) {
s := `{"groups":[{"pubKey":"03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c","signature":"QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQQ=="}],"features":{"storage":false,"payable":false},"abi":{"hash":"0x0000000000000000000000000000000000000000","entryPoint":{"name":"Main","parameters":[{"name":"operation","type":"String"},{"name":"args","type":"Array"}],"returnType":"Any"},"methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"safeMethods":[],"extra":null}`
testUnmarshalMarshalManifest(t, s)
})
t.Run("extra", func(t *testing.T) {
s := `{"groups":[],"features":{"storage":false,"payable":false},"abi":{"hash":"0x0000000000000000000000000000000000000000","entryPoint":{"name":"Main","parameters":[{"name":"operation","type":"String"},{"name":"args","type":"Array"}],"returnType":"Any"},"methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"safeMethods":[],"extra":{"key":"value"}}`
testUnmarshalMarshalManifest(t, s)
})
}
func testUnmarshalMarshalManifest(t *testing.T, s string) *Manifest {
js := []byte(s)
c := NewManifest(util.Uint160{})
require.NoError(t, json.Unmarshal(js, c))
data, err := json.Marshal(c)
require.NoError(t, err)
require.JSONEq(t, s, string(data))
return c
}
func TestManifest_CanCall(t *testing.T) {
t.Run("safe methods", func(t *testing.T) {
man1 := NewManifest(util.Uint160{})
man2 := DefaultManifest(util.Uint160{})
require.False(t, man1.CanCall(man2, "method1"))
man2.SafeMethods.Add("method1")
require.True(t, man1.CanCall(man2, "method1"))
})
t.Run("wildcard permission", func(t *testing.T) {
man1 := DefaultManifest(util.Uint160{})
man2 := DefaultManifest(util.Uint160{})
require.True(t, man1.CanCall(man2, "method1"))
})
}
func TestPermission_IsAllowed(t *testing.T) {
manifest := DefaultManifest(util.Uint160{})
t.Run("wildcard", func(t *testing.T) {
perm := NewPermission(PermissionWildcard)
require.True(t, perm.IsAllowed(manifest, "AAA"))
})
t.Run("hash", func(t *testing.T) {
perm := NewPermission(PermissionHash, util.Uint160{})
require.True(t, perm.IsAllowed(manifest, "AAA"))
t.Run("restrict methods", func(t *testing.T) {
perm.Methods.Restrict()
require.False(t, perm.IsAllowed(manifest, "AAA"))
perm.Methods.Add("AAA")
require.True(t, perm.IsAllowed(manifest, "AAA"))
})
})
t.Run("invalid hash", func(t *testing.T) {
perm := NewPermission(PermissionHash, util.Uint160{1})
require.False(t, perm.IsAllowed(manifest, "AAA"))
})
priv, err := keys.NewPrivateKey()
require.NoError(t, err)
manifest.Groups = []Group{{PublicKey: priv.PublicKey()}}
t.Run("group", func(t *testing.T) {
perm := NewPermission(PermissionGroup, priv.PublicKey())
require.True(t, perm.IsAllowed(manifest, "AAA"))
})
t.Run("invalid group", func(t *testing.T) {
priv2, err := keys.NewPrivateKey()
require.NoError(t, err)
perm := NewPermission(PermissionGroup, priv2.PublicKey())
require.False(t, perm.IsAllowed(manifest, "AAA"))
})
}

View file

@ -0,0 +1,89 @@
package manifest
import (
"encoding/hex"
"encoding/json"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
)
// Parameter represents smartcontract's parameter's definition.
type Parameter struct {
Name string `json:"name"`
Type smartcontract.ParamType `json:"type"`
}
// Event is a description of a single event.
type Event struct {
Name string `json:"name"`
Parameters []Parameter `json:"parameters"`
}
// Group represents a group of smartcontracts identified by a public key.
// Every SC in a group must provide signature of it's hash to prove
// it belongs to a group.
type Group struct {
PublicKey *keys.PublicKey `json:"pubKey"`
Signature []byte `json:"signature"`
}
type groupAux struct {
PublicKey string `json:"pubKey"`
Signature []byte `json:"signature"`
}
// Method represents method's metadata.
type Method struct {
Name string `json:"name"`
Parameters []Parameter `json:"parameters"`
ReturnType smartcontract.ParamType `json:"returnType"`
}
// NewParameter returns new paramter with the specified name and type.
func NewParameter(name string, typ smartcontract.ParamType) Parameter {
return Parameter{
Name: name,
Type: typ,
}
}
// DefaultEntryPoint represents default entrypoint to a contract.
func DefaultEntryPoint() *Method {
return &Method{
Name: "Main",
Parameters: []Parameter{
NewParameter("operation", smartcontract.StringType),
NewParameter("args", smartcontract.ArrayType),
},
ReturnType: smartcontract.AnyType,
}
}
// MarshalJSON implements json.Marshaler interface.
func (g *Group) MarshalJSON() ([]byte, error) {
aux := &groupAux{
PublicKey: hex.EncodeToString(g.PublicKey.Bytes()),
Signature: g.Signature,
}
return json.Marshal(aux)
}
// UnmarshalJSON implements json.Unmarshaler interface.
func (g *Group) UnmarshalJSON(data []byte) error {
aux := new(groupAux)
if err := json.Unmarshal(data, aux); err != nil {
return err
}
b, err := hex.DecodeString(aux.PublicKey)
if err != nil {
return err
}
pub := new(keys.PublicKey)
if err := pub.DecodeBytes(b); err != nil {
return err
}
g.PublicKey = pub
g.Signature = aux.Signature
return nil
}

View file

@ -0,0 +1,173 @@
package manifest
import (
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/util"
)
// PermissionType represents permission type.
type PermissionType uint8
const (
// PermissionWildcard allows everything.
PermissionWildcard PermissionType = 0
// PermissionHash restricts called contracts based on hash.
PermissionHash PermissionType = 1
// PermissionGroup restricts called contracts based on public key.
PermissionGroup PermissionType = 2
)
// PermissionDesc is a permission descriptor.
type PermissionDesc struct {
Type PermissionType
Value interface{}
}
// Permission describes which contracts may be invoked and which methods are called.
type Permission struct {
Contract PermissionDesc `json:"contract"`
Methods WildStrings `json:"methods"`
}
type permissionAux struct {
Contract PermissionDesc `json:"contract"`
Methods WildStrings `json:"methods"`
}
// NewPermission returns new permission of a given type.
func NewPermission(typ PermissionType, args ...interface{}) *Permission {
return &Permission{
Contract: *newPermissionDesc(typ, args...),
}
}
func newPermissionDesc(typ PermissionType, args ...interface{}) *PermissionDesc {
desc := &PermissionDesc{Type: typ}
switch typ {
case PermissionWildcard:
if len(args) != 0 {
panic("wildcard permission has no arguments")
}
case PermissionHash:
if len(args) == 0 {
panic("hash permission should have an argument")
} else if u, ok := args[0].(util.Uint160); !ok {
panic("hash permission should have util.Uint160 argument")
} else {
desc.Value = u
}
case PermissionGroup:
if len(args) == 0 {
panic("group permission should have an argument")
} else if pub, ok := args[0].(*keys.PublicKey); !ok {
panic("group permission should have a public key argument")
} else {
desc.Value = pub
}
}
return desc
}
// Hash returns hash for hash-permission.
func (d *PermissionDesc) Hash() util.Uint160 {
return d.Value.(util.Uint160)
}
// Group returns group's public key for group-permission.
func (d *PermissionDesc) Group() *keys.PublicKey {
return d.Value.(*keys.PublicKey)
}
// IsAllowed checks if method is allowed to be executed.
func (p *Permission) IsAllowed(m *Manifest, method string) bool {
switch p.Contract.Type {
case PermissionWildcard:
return true
case PermissionHash:
if !p.Contract.Hash().Equals(m.ABI.Hash) {
return false
}
case PermissionGroup:
g := p.Contract.Group()
for i := range m.Groups {
if !g.Equal(m.Groups[i].PublicKey) {
return false
}
}
default:
panic(fmt.Sprintf("unexpected permission: %d", p.Contract.Type))
}
if p.Methods.IsWildcard() {
return true
}
return p.Methods.Contains(method)
}
// UnmarshalJSON implements json.Unmarshaler interface.
func (p *Permission) UnmarshalJSON(data []byte) error {
aux := new(permissionAux)
if err := json.Unmarshal(data, aux); err != nil {
return err
}
p.Contract = aux.Contract
p.Methods = aux.Methods
return nil
}
// MarshalJSON implements json.Marshaler interface.
func (d *PermissionDesc) MarshalJSON() ([]byte, error) {
switch d.Type {
case PermissionHash:
return json.Marshal("0x" + d.Hash().StringLE())
case PermissionGroup:
return json.Marshal(hex.EncodeToString(d.Group().Bytes()))
default:
return []byte(`"*"`), nil
}
}
// UnmarshalJSON implements json.Unmarshaler interface.
func (d *PermissionDesc) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
const uint160HexSize = 2 * util.Uint160Size
switch len(s) {
case 2 + uint160HexSize:
// allow to unmarshal both hex and 0xhex forms
if s[0] != '0' || s[1] != 'x' {
return errors.New("invalid uint160")
}
s = s[2:]
fallthrough
case uint160HexSize:
u, err := util.Uint160DecodeStringLE(s)
if err != nil {
return err
}
d.Type = PermissionHash
d.Value = u
return nil
case 66:
pub, err := keys.NewPublicKeyFromString(s)
if err != nil {
return err
}
d.Type = PermissionGroup
d.Value = pub
return nil
case 1:
if s == "*" {
d.Type = PermissionWildcard
return nil
}
}
return errors.New("unknown permission")
}

View file

@ -0,0 +1,94 @@
package manifest
import (
"encoding/json"
"fmt"
"testing"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/internal/random"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/stretchr/testify/require"
)
func TestNewPermission(t *testing.T) {
require.Panics(t, func() { NewPermission(PermissionWildcard, util.Uint160{}) })
require.Panics(t, func() { NewPermission(PermissionHash) })
require.Panics(t, func() { NewPermission(PermissionHash, 1) })
require.Panics(t, func() { NewPermission(PermissionGroup) })
require.Panics(t, func() { NewPermission(PermissionGroup, util.Uint160{}) })
}
func TestPermission_MarshalJSON(t *testing.T) {
t.Run("wildcard", func(t *testing.T) {
expected := NewPermission(PermissionWildcard)
expected.Methods.Restrict()
testMarshalUnmarshal(t, expected, NewPermission(PermissionWildcard))
})
t.Run("group", func(t *testing.T) {
expected := NewPermission(PermissionWildcard)
expected.Contract.Type = PermissionGroup
priv, err := keys.NewPrivateKey()
require.NoError(t, err)
expected.Contract.Value = priv.PublicKey()
expected.Methods.Add("method1")
expected.Methods.Add("method2")
testMarshalUnmarshal(t, expected, NewPermission(PermissionWildcard))
})
t.Run("hash", func(t *testing.T) {
expected := NewPermission(PermissionWildcard)
expected.Contract.Type = PermissionHash
expected.Contract.Value = random.Uint160()
testMarshalUnmarshal(t, expected, NewPermission(PermissionWildcard))
})
}
func TestPermissionDesc_MarshalJSON(t *testing.T) {
t.Run("uint160 with 0x", func(t *testing.T) {
u := random.Uint160()
s := u.StringLE()
js := []byte(fmt.Sprintf(`"0x%s"`, s))
d := new(PermissionDesc)
require.NoError(t, json.Unmarshal(js, d))
require.Equal(t, u, d.Value.(util.Uint160))
})
t.Run("invalid uint160", func(t *testing.T) {
d := new(PermissionDesc)
s := random.String(util.Uint160Size * 2)
js := []byte(fmt.Sprintf(`"ok%s"`, s))
require.Error(t, json.Unmarshal(js, d))
js = []byte(fmt.Sprintf(`"%s"`, s))
require.Error(t, json.Unmarshal(js, d))
})
t.Run("invalid public key", func(t *testing.T) {
d := new(PermissionDesc)
s := random.String(65)
s = "k" + s // not a hex
js := []byte(fmt.Sprintf(`"%s"`, s))
require.Error(t, json.Unmarshal(js, d))
})
t.Run("not a string", func(t *testing.T) {
d := new(PermissionDesc)
js := []byte(`123`)
require.Error(t, json.Unmarshal(js, d))
})
t.Run("invalid string", func(t *testing.T) {
d := new(PermissionDesc)
js := []byte(`"invalid length"`)
require.Error(t, json.Unmarshal(js, d))
})
}
func testMarshalUnmarshal(t *testing.T, expected, actual interface{}) {
data, err := json.Marshal(expected)
require.NoError(t, err)
require.NoError(t, json.Unmarshal(data, actual))
require.Equal(t, expected, actual)
}

View file

@ -6,6 +6,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"math/big"
"os" "os"
"strconv" "strconv"
"strings" "strings"
@ -438,7 +439,7 @@ func parseArgs(args []string) ([]vm.StackItem, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
items[i] = vm.NewBigIntegerItem(val) items[i] = vm.NewBigIntegerItem(big.NewInt(val))
case stringType: case stringType:
items[i] = vm.NewByteArrayItem([]byte(value)) items[i] = vm.NewByteArrayItem([]byte(value))
} }

View file

@ -3,6 +3,7 @@ package vm
import ( import (
"encoding/binary" "encoding/binary"
"errors" "errors"
"math/big"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract"
@ -178,6 +179,11 @@ func (c *Context) TryBytes() ([]byte, error) {
return nil, errors.New("can't convert Context to ByteArray") return nil, errors.New("can't convert Context to ByteArray")
} }
// TryInteger implements StackItem interface.
func (c *Context) TryInteger() (*big.Int, error) {
return nil, errors.New("can't convert Context to Integer")
}
// Equals implements StackItem interface. // Equals implements StackItem interface.
func (c *Context) Equals(s StackItem) bool { func (c *Context) Equals(s StackItem) bool {
return c == s return c == s
@ -203,3 +209,19 @@ func (c *Context) atBreakPoint() bool {
func (c *Context) String() string { func (c *Context) String() string {
return "execution context" return "execution context"
} }
// GetContextScriptHash returns script hash of the invocation stack element
// number n.
func (v *VM) GetContextScriptHash(n int) util.Uint160 {
ctxIface := v.Istack().Peek(n).Value()
ctx := ctxIface.(*Context)
return ctx.ScriptHash()
}
// PushContextScriptHash pushes to evaluation stack the script hash of the
// invocation stack element number n.
func (v *VM) PushContextScriptHash(n int) error {
h := v.GetContextScriptHash(n)
v.Estack().PushVal(h.BytesBE())
return nil
}

View file

@ -7,7 +7,6 @@ import (
"math/big" "math/big"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
) )
// Stack implementation for the neo-go virtual machine. The stack implements // Stack implementation for the neo-go virtual machine. The stack implements
@ -74,18 +73,11 @@ func (e *Element) Value() interface{} {
// BigInt attempts to get the underlying value of the element as a big integer. // BigInt attempts to get the underlying value of the element as a big integer.
// Will panic if the assertion failed which will be caught by the VM. // Will panic if the assertion failed which will be caught by the VM.
func (e *Element) BigInt() *big.Int { func (e *Element) BigInt() *big.Int {
switch t := e.value.(type) { val, err := e.value.TryInteger()
case *BigIntegerItem: if err != nil {
return t.value panic(err)
case *BoolItem:
if t.value {
return big.NewInt(1)
}
return big.NewInt(0)
default:
b := t.Value().([]uint8)
return emit.BytesToInt(b)
} }
return val
} }
// TryBool attempts to get the underlying value of the element as a boolean. // TryBool attempts to get the underlying value of the element as a boolean.

View file

@ -22,6 +22,8 @@ type StackItem interface {
Dup() StackItem Dup() StackItem
// TryBytes converts StackItem to a byte slice. // TryBytes converts StackItem to a byte slice.
TryBytes() ([]byte, error) TryBytes() ([]byte, error)
// TryInteger converts StackItem to an integer.
TryInteger() (*big.Int, error)
// Equals checks if 2 StackItems are equal. // Equals checks if 2 StackItems are equal.
Equals(s StackItem) bool Equals(s StackItem) bool
// ToContractParameter converts StackItem to smartcontract.Parameter // ToContractParameter converts StackItem to smartcontract.Parameter
@ -134,6 +136,11 @@ func (i *StructItem) TryBytes() ([]byte, error) {
return nil, errors.New("can't convert Struct to ByteArray") return nil, errors.New("can't convert Struct to ByteArray")
} }
// TryInteger implements StackItem interface.
func (i *StructItem) TryInteger() (*big.Int, error) {
return nil, errors.New("can't convert Struct to Integer")
}
// Equals implements StackItem interface. // Equals implements StackItem interface.
func (i *StructItem) Equals(s StackItem) bool { func (i *StructItem) Equals(s StackItem) bool {
if i == s { if i == s {
@ -210,6 +217,11 @@ func (i NullItem) TryBytes() ([]byte, error) {
return nil, errors.New("can't convert Null to ByteArray") return nil, errors.New("can't convert Null to ByteArray")
} }
// TryInteger implements StackItem interface.
func (i NullItem) TryInteger() (*big.Int, error) {
return nil, errors.New("can't convert Null to Integer")
}
// Equals implements StackItem interface. // Equals implements StackItem interface.
func (i NullItem) Equals(s StackItem) bool { func (i NullItem) Equals(s StackItem) bool {
_, ok := s.(NullItem) _, ok := s.(NullItem)
@ -229,9 +241,9 @@ type BigIntegerItem struct {
} }
// NewBigIntegerItem returns an new BigIntegerItem object. // NewBigIntegerItem returns an new BigIntegerItem object.
func NewBigIntegerItem(value int64) *BigIntegerItem { func NewBigIntegerItem(value *big.Int) *BigIntegerItem {
return &BigIntegerItem{ return &BigIntegerItem{
value: big.NewInt(value), value: value,
} }
} }
@ -245,6 +257,11 @@ func (i *BigIntegerItem) TryBytes() ([]byte, error) {
return i.Bytes(), nil return i.Bytes(), nil
} }
// TryInteger implements StackItem interface.
func (i *BigIntegerItem) TryInteger() (*big.Int, error) {
return i.value, nil
}
// Equals implements StackItem interface. // Equals implements StackItem interface.
func (i *BigIntegerItem) Equals(s StackItem) bool { func (i *BigIntegerItem) Equals(s StackItem) bool {
if i == s { if i == s {
@ -334,6 +351,14 @@ func (i *BoolItem) TryBytes() ([]byte, error) {
return i.Bytes(), nil return i.Bytes(), nil
} }
// TryInteger implements StackItem interface.
func (i *BoolItem) TryInteger() (*big.Int, error) {
if i.value {
return big.NewInt(1), nil
}
return big.NewInt(0), nil
}
// Equals implements StackItem interface. // Equals implements StackItem interface.
func (i *BoolItem) Equals(s StackItem) bool { func (i *BoolItem) Equals(s StackItem) bool {
if i == s { if i == s {
@ -388,6 +413,11 @@ func (i *ByteArrayItem) TryBytes() ([]byte, error) {
return i.value, nil return i.value, nil
} }
// TryInteger implements StackItem interface.
func (i *ByteArrayItem) TryInteger() (*big.Int, error) {
return emit.BytesToInt(i.value), nil
}
// Equals implements StackItem interface. // Equals implements StackItem interface.
func (i *ByteArrayItem) Equals(s StackItem) bool { func (i *ByteArrayItem) Equals(s StackItem) bool {
if i == s { if i == s {
@ -445,6 +475,11 @@ func (i *ArrayItem) TryBytes() ([]byte, error) {
return nil, errors.New("can't convert Array to ByteArray") return nil, errors.New("can't convert Array to ByteArray")
} }
// TryInteger implements StackItem interface.
func (i *ArrayItem) TryInteger() (*big.Int, error) {
return nil, errors.New("can't convert Array to Integer")
}
// Equals implements StackItem interface. // Equals implements StackItem interface.
func (i *ArrayItem) Equals(s StackItem) bool { func (i *ArrayItem) Equals(s StackItem) bool {
return i == s return i == s
@ -505,6 +540,11 @@ func (i *MapItem) TryBytes() ([]byte, error) {
return nil, errors.New("can't convert Map to ByteArray") return nil, errors.New("can't convert Map to ByteArray")
} }
// TryInteger implements StackItem interface.
func (i *MapItem) TryInteger() (*big.Int, error) {
return nil, errors.New("can't convert Map to Integer")
}
// Equals implements StackItem interface. // Equals implements StackItem interface.
func (i *MapItem) Equals(s StackItem) bool { func (i *MapItem) Equals(s StackItem) bool {
return i == s return i == s
@ -616,6 +656,11 @@ func (i *InteropItem) TryBytes() ([]byte, error) {
return nil, errors.New("can't convert Interop to ByteArray") return nil, errors.New("can't convert Interop to ByteArray")
} }
// TryInteger implements StackItem interface.
func (i *InteropItem) TryInteger() (*big.Int, error) {
return nil, errors.New("can't convert Interop to Integer")
}
// Equals implements StackItem interface. // Equals implements StackItem interface.
func (i *InteropItem) Equals(s StackItem) bool { func (i *InteropItem) Equals(s StackItem) bool {
if i == s { if i == s {

View file

@ -104,7 +104,7 @@ var stringerTestCases = []struct {
result: "Struct", result: "Struct",
}, },
{ {
input: NewBigIntegerItem(3), input: NewBigIntegerItem(big.NewInt(3)),
result: "BigInteger", result: "BigInteger",
}, },
{ {
@ -148,48 +148,48 @@ var equalsTestCases = map[string][]struct {
}, },
{ {
item1: NewStructItem(nil), item1: NewStructItem(nil),
item2: NewBigIntegerItem(1), item2: NewBigIntegerItem(big.NewInt(1)),
result: false, result: false,
}, },
{ {
item1: NewStructItem(nil), item1: NewStructItem(nil),
item2: NewStructItem([]StackItem{NewBigIntegerItem(1)}), item2: NewStructItem([]StackItem{NewBigIntegerItem(big.NewInt(1))}),
result: false, result: false,
}, },
{ {
item1: NewStructItem([]StackItem{NewBigIntegerItem(1)}), item1: NewStructItem([]StackItem{NewBigIntegerItem(big.NewInt(1))}),
item2: NewStructItem([]StackItem{NewBigIntegerItem(2)}), item2: NewStructItem([]StackItem{NewBigIntegerItem(big.NewInt(2))}),
result: false, result: false,
}, },
{ {
item1: NewStructItem([]StackItem{NewBigIntegerItem(1)}), item1: NewStructItem([]StackItem{NewBigIntegerItem(big.NewInt(1))}),
item2: NewStructItem([]StackItem{NewBigIntegerItem(1)}), item2: NewStructItem([]StackItem{NewBigIntegerItem(big.NewInt(1))}),
result: true, result: true,
}, },
}, },
"bigint": { "bigint": {
{ {
item1: NewBigIntegerItem(2), item1: NewBigIntegerItem(big.NewInt(2)),
item2: nil, item2: nil,
result: false, result: false,
}, },
{ {
item1: NewBigIntegerItem(2), item1: NewBigIntegerItem(big.NewInt(2)),
item2: NewBigIntegerItem(2), item2: NewBigIntegerItem(big.NewInt(2)),
result: true, result: true,
}, },
{ {
item1: NewBigIntegerItem(2), item1: NewBigIntegerItem(big.NewInt(2)),
item2: NewBoolItem(false), item2: NewBoolItem(false),
result: false, result: false,
}, },
{ {
item1: NewBigIntegerItem(0), item1: NewBigIntegerItem(big.NewInt(0)),
item2: NewBoolItem(false), item2: NewBoolItem(false),
result: false, result: false,
}, },
{ {
item1: NewBigIntegerItem(2), item1: NewBigIntegerItem(big.NewInt(2)),
item2: makeStackItem(int32(2)), item2: makeStackItem(int32(2)),
result: true, result: true,
}, },
@ -207,7 +207,7 @@ var equalsTestCases = map[string][]struct {
}, },
{ {
item1: NewBoolItem(true), item1: NewBoolItem(true),
item2: NewBigIntegerItem(1), item2: NewBigIntegerItem(big.NewInt(1)),
result: true, result: true,
}, },
{ {
@ -234,7 +234,7 @@ var equalsTestCases = map[string][]struct {
}, },
{ {
item1: NewByteArrayItem([]byte{1}), item1: NewByteArrayItem([]byte{1}),
item2: NewBigIntegerItem(1), item2: NewBigIntegerItem(big.NewInt(1)),
result: true, result: true,
}, },
{ {
@ -261,7 +261,7 @@ var equalsTestCases = map[string][]struct {
}, },
{ {
item1: NewArrayItem([]StackItem{&BigIntegerItem{big.NewInt(1)}}), item1: NewArrayItem([]StackItem{&BigIntegerItem{big.NewInt(1)}}),
item2: NewBigIntegerItem(1), item2: NewBigIntegerItem(big.NewInt(1)),
result: false, result: false,
}, },
{ {
@ -282,13 +282,13 @@ var equalsTestCases = map[string][]struct {
result: false, result: false,
}, },
{ {
item1: &MapItem{value: []MapElement{{NewByteArrayItem([]byte("first")), NewBigIntegerItem(1)}, {NewBoolItem(true), NewByteArrayItem([]byte{2})}}}, item1: &MapItem{value: []MapElement{{NewByteArrayItem([]byte("first")), NewBigIntegerItem(big.NewInt(1))}, {NewBoolItem(true), NewByteArrayItem([]byte{2})}}},
item2: &MapItem{value: []MapElement{{NewByteArrayItem([]byte("first")), NewBigIntegerItem(1)}, {NewBoolItem(true), NewByteArrayItem([]byte{2})}}}, item2: &MapItem{value: []MapElement{{NewByteArrayItem([]byte("first")), NewBigIntegerItem(big.NewInt(1))}, {NewBoolItem(true), NewByteArrayItem([]byte{2})}}},
result: false, result: false,
}, },
{ {
item1: &MapItem{value: []MapElement{{NewByteArrayItem([]byte("first")), NewBigIntegerItem(1)}, {NewBoolItem(true), NewByteArrayItem([]byte{2})}}}, item1: &MapItem{value: []MapElement{{NewByteArrayItem([]byte("first")), NewBigIntegerItem(big.NewInt(1))}, {NewBoolItem(true), NewByteArrayItem([]byte{2})}}},
item2: &MapItem{value: []MapElement{{NewByteArrayItem([]byte("first")), NewBigIntegerItem(1)}, {NewBoolItem(true), NewByteArrayItem([]byte{3})}}}, item2: &MapItem{value: []MapElement{{NewByteArrayItem([]byte("first")), NewBigIntegerItem(big.NewInt(1))}, {NewBoolItem(true), NewByteArrayItem([]byte{3})}}},
result: false, result: false,
}, },
}, },
@ -333,7 +333,7 @@ var marshalJSONTestCases = []struct {
result []byte result []byte
}{ }{
{ {
input: NewBigIntegerItem(2), input: NewBigIntegerItem(big.NewInt(2)),
result: []byte(`2`), result: []byte(`2`),
}, },
{ {
@ -386,7 +386,7 @@ var toContractParameterTestCases = []struct {
}{ }{
{ {
input: NewStructItem([]StackItem{ input: NewStructItem([]StackItem{
NewBigIntegerItem(1), NewBigIntegerItem(big.NewInt(1)),
NewBoolItem(true), NewBoolItem(true),
}), }),
result: smartcontract.Parameter{Type: smartcontract.ArrayType, Value: []smartcontract.Parameter{ result: smartcontract.Parameter{Type: smartcontract.ArrayType, Value: []smartcontract.Parameter{
@ -403,7 +403,7 @@ var toContractParameterTestCases = []struct {
result: smartcontract.Parameter{Type: smartcontract.ByteArrayType, Value: []byte{0x01, 0x02, 0x03}}, result: smartcontract.Parameter{Type: smartcontract.ByteArrayType, Value: []byte{0x01, 0x02, 0x03}},
}, },
{ {
input: NewArrayItem([]StackItem{NewBigIntegerItem(2), NewBoolItem(true)}), input: NewArrayItem([]StackItem{NewBigIntegerItem(big.NewInt(2)), NewBoolItem(true)}),
result: smartcontract.Parameter{Type: smartcontract.ArrayType, Value: []smartcontract.Parameter{ result: smartcontract.Parameter{Type: smartcontract.ArrayType, Value: []smartcontract.Parameter{
{Type: smartcontract.IntegerType, Value: int64(2)}, {Type: smartcontract.IntegerType, Value: int64(2)},
{Type: smartcontract.BoolType, Value: true}, {Type: smartcontract.BoolType, Value: true},
@ -415,8 +415,8 @@ var toContractParameterTestCases = []struct {
}, },
{ {
input: &MapItem{value: []MapElement{ input: &MapItem{value: []MapElement{
{NewBigIntegerItem(1), NewBoolItem(true)}, {NewBigIntegerItem(big.NewInt(1)), NewBoolItem(true)},
{NewByteArrayItem([]byte("qwerty")), NewBigIntegerItem(3)}, {NewByteArrayItem([]byte("qwerty")), NewBigIntegerItem(big.NewInt(3))},
{NewBoolItem(true), NewBoolItem(false)}, {NewBoolItem(true), NewBoolItem(false)},
}}, }},
result: smartcontract.Parameter{ result: smartcontract.Parameter{

View file

@ -441,7 +441,7 @@ func testIterableCreate(t *testing.T, typ string) {
vm := load(prog) vm := load(prog)
arr := []StackItem{ arr := []StackItem{
NewBigIntegerItem(42), NewBigIntegerItem(big.NewInt(42)),
NewByteArrayItem([]byte{3, 2, 1}), NewByteArrayItem([]byte{3, 2, 1}),
} }
vm.estack.Push(&Element{value: NewArrayItem(arr)}) vm.estack.Push(&Element{value: NewArrayItem(arr)})
@ -479,7 +479,7 @@ func testIterableConcat(t *testing.T, typ string) {
arr := []StackItem{ arr := []StackItem{
NewBoolItem(false), NewBoolItem(false),
NewBigIntegerItem(123), NewBigIntegerItem(big.NewInt(123)),
NewMapItem(), NewMapItem(),
} }
vm.estack.Push(&Element{value: NewArrayItem(arr[:1])}) vm.estack.Push(&Element{value: NewArrayItem(arr[:1])})
@ -521,15 +521,15 @@ func TestIteratorKeys(t *testing.T) {
v := load(prog) v := load(prog)
arr := NewArrayItem([]StackItem{ arr := NewArrayItem([]StackItem{
NewBoolItem(false), NewBoolItem(false),
NewBigIntegerItem(42), NewBigIntegerItem(big.NewInt(42)),
}) })
v.estack.PushVal(arr) v.estack.PushVal(arr)
runVM(t, v) runVM(t, v)
checkEnumeratorStack(t, v, []StackItem{ checkEnumeratorStack(t, v, []StackItem{
NewBigIntegerItem(1), NewBoolItem(true), NewBigIntegerItem(big.NewInt(1)), NewBoolItem(true),
NewBigIntegerItem(0), NewBoolItem(true), NewBigIntegerItem(big.NewInt(0)), NewBoolItem(true),
}) })
} }
@ -541,7 +541,7 @@ func TestIteratorValues(t *testing.T) {
v := load(prog) v := load(prog)
m := NewMapItem() m := NewMapItem()
m.Add(NewBigIntegerItem(1), NewBoolItem(false)) m.Add(NewBigIntegerItem(big.NewInt(1)), NewBoolItem(false))
m.Add(NewByteArrayItem([]byte{32}), NewByteArrayItem([]byte{7})) m.Add(NewByteArrayItem([]byte{32}), NewByteArrayItem([]byte{7}))
v.estack.PushVal(m) v.estack.PushVal(m)
@ -680,7 +680,7 @@ func TestDeserializeUnknown(t *testing.T) {
prog := append(getSyscallProg("Neo.Runtime.Deserialize"), byte(opcode.RET)) prog := append(getSyscallProg("Neo.Runtime.Deserialize"), byte(opcode.RET))
vm := load(prog) vm := load(prog)
data, err := SerializeItem(NewBigIntegerItem(123)) data, err := SerializeItem(NewBigIntegerItem(big.NewInt(123)))
require.NoError(t, err) require.NoError(t, err)
data[0] = 0xFF data[0] = 0xFF