forked from TrueCloudLab/neoneo-go
Merge pull request #781 from nspcc-dev/neo3/native
Implement Native contract mechanism
This commit is contained in:
commit
c7c788d10b
34 changed files with 2210 additions and 235 deletions
|
@ -34,7 +34,7 @@ func TestEntryPointWithArgs(t *testing.T) {
|
|||
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))
|
||||
}
|
||||
|
||||
|
@ -49,7 +49,7 @@ func TestEntryPointWithMethodAndArgs(t *testing.T) {
|
|||
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))
|
||||
}
|
||||
|
||||
|
@ -154,9 +154,9 @@ func TestIntArray(t *testing.T) {
|
|||
}
|
||||
`
|
||||
eval(t, src, []vm.StackItem{
|
||||
vm.NewBigIntegerItem(1),
|
||||
vm.NewBigIntegerItem(2),
|
||||
vm.NewBigIntegerItem(3),
|
||||
vm.NewBigIntegerItem(big.NewInt(1)),
|
||||
vm.NewBigIntegerItem(big.NewInt(2)),
|
||||
vm.NewBigIntegerItem(big.NewInt(3)),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -271,8 +271,8 @@ var structTestCases = []testCase{
|
|||
}
|
||||
`,
|
||||
[]vm.StackItem{
|
||||
vm.NewBigIntegerItem(1),
|
||||
vm.NewBigIntegerItem(2),
|
||||
vm.NewBigIntegerItem(big.NewInt(1)),
|
||||
vm.NewBigIntegerItem(big.NewInt(2)),
|
||||
vm.NewByteArrayItem([]byte("hello")),
|
||||
vm.NewByteArrayItem([]byte{}),
|
||||
},
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package compiler_test
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||
|
@ -40,7 +41,7 @@ func TestNotify(t *testing.T) {
|
|||
require.NoError(t, v.Run())
|
||||
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, []vm.StackItem{}, s.events[1].Value())
|
||||
assert.Equal(t, []vm.StackItem{vm.NewByteArrayItem([]byte("single"))}, s.events[2].Value())
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/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/storage"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
|
@ -123,6 +124,8 @@ type Blockchain struct {
|
|||
log *zap.Logger
|
||||
|
||||
lastBatch *storage.MemBatch
|
||||
|
||||
contracts native.Contracts
|
||||
}
|
||||
|
||||
type headersOpFunc func(headerList *HeaderHashList)
|
||||
|
@ -167,6 +170,8 @@ func NewBlockchain(s storage.Store, cfg config.ProtocolConfiguration, log *zap.L
|
|||
|
||||
generationAmount: genAmount,
|
||||
decrementInterval: decrementInterval,
|
||||
|
||||
contracts: *native.NewContracts(),
|
||||
}
|
||||
|
||||
if err := bc.init(); err != nil {
|
||||
|
@ -193,6 +198,9 @@ func (bc *Blockchain) init() error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := bc.initNative(); err != nil {
|
||||
return err
|
||||
}
|
||||
return bc.storeBlock(genesisBlock)
|
||||
}
|
||||
if ver != version {
|
||||
|
@ -265,6 +273,27 @@ func (bc *Blockchain) init() error {
|
|||
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.
|
||||
func (bc *Blockchain) Run() {
|
||||
persistTimer := time.NewTimer(persistInterval)
|
||||
|
@ -634,7 +663,7 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
|
|||
return err
|
||||
}
|
||||
case *transaction.StateTX:
|
||||
if err := processStateTX(cache, t); err != nil {
|
||||
if err := bc.processStateTX(cache, tx, t); err != nil {
|
||||
return err
|
||||
}
|
||||
case *transaction.PublishTX:
|
||||
|
@ -726,6 +755,13 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
|
|||
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()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -832,6 +868,11 @@ func (bc *Blockchain) LastBatch() *storage.MemBatch {
|
|||
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.
|
||||
func processOutputs(tx *transaction.Transaction, dao *dao.Cached) error {
|
||||
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.
|
||||
func modAccountVotes(account *state.Account, dao *dao.Cached, value util.Fixed8) error {
|
||||
for _, vote := range account.Votes {
|
||||
validator, err := dao.GetValidatorStateOrNew(vote)
|
||||
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 err := native.ModifyAccountVotes(account, dao, value); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(account.Votes) > 0 {
|
||||
vc, err := dao.GetValidatorsCount()
|
||||
|
@ -920,55 +948,19 @@ func processValidatorStateDescriptor(descriptor *transaction.StateDescriptor, da
|
|||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
account, err := dao.GetAccountStateOrNew(hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if descriptor.Field == "Votes" {
|
||||
balance := account.GetBalanceValues()[GoverningTokenID()]
|
||||
if err = modAccountVotes(account, dao, -balance); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
votes := keys.PublicKeys{}
|
||||
err := votes.DecodeBytes(descriptor.Value)
|
||||
if err != nil {
|
||||
if err := votes.DecodeBytes(descriptor.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(votes) > state.MaxValidatorsVoted {
|
||||
return errors.New("voting candidate limit exceeded")
|
||||
}
|
||||
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)
|
||||
ic := bc.newInteropContext(trigger.Application, dao, nil, t)
|
||||
return bc.contracts.NEO.VoteInternal(ic, hash, votes)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -1790,59 +1782,14 @@ func (bc *Blockchain) GetValidators(txes ...*transaction.Transaction) ([]*keys.P
|
|||
return nil, err
|
||||
}
|
||||
case *transaction.StateTX:
|
||||
if err := processStateTX(cache, t); err != nil {
|
||||
if err := bc.processStateTX(cache, tx, t); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
validators := cache.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
|
||||
})
|
||||
|
||||
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
|
||||
return bc.contracts.NEO.GetValidatorsInternal(bc, cache)
|
||||
}
|
||||
|
||||
// GetEnrollments returns all registered validators and non-registered SB validators
|
||||
|
@ -1879,11 +1826,11 @@ func (bc *Blockchain) GetEnrollments() ([]*state.Validator, error) {
|
|||
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 {
|
||||
switch desc.Type {
|
||||
case transaction.Account:
|
||||
if err := processAccountStateDescriptor(desc, dao); err != nil {
|
||||
if err := bc.processAccountStateDescriptor(desc, t, dao); err != nil {
|
||||
return err
|
||||
}
|
||||
case transaction.Validator:
|
||||
|
|
|
@ -37,6 +37,7 @@ type Blockchainer interface {
|
|||
GetNEP5TransferLog(util.Uint160) *state.NEP5TransferLog
|
||||
GetNEP5Balances(util.Uint160) *state.NEP5Balances
|
||||
GetValidators(txes ...*transaction.Transaction) ([]*keys.PublicKey, error)
|
||||
GetStandByValidators() (keys.PublicKeys, error)
|
||||
GetScriptHashesForVerifying(*transaction.Transaction) ([]util.Uint160, error)
|
||||
GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem
|
||||
GetStorageItems(hash util.Uint160) (map[string]*state.StorageItem, error)
|
||||
|
|
|
@ -32,8 +32,10 @@ type DAO interface {
|
|||
GetCurrentBlockHeight() (uint32, error)
|
||||
GetCurrentHeaderHeight() (i uint32, h util.Uint256, err error)
|
||||
GetHeaderHashes() ([]util.Uint256, error)
|
||||
GetNativeContractState(h util.Uint160) ([]byte, error)
|
||||
GetNEP5Balances(acc util.Uint160) (*state.NEP5Balances, error)
|
||||
GetNEP5TransferLog(acc util.Uint160, index uint32) (*state.NEP5TransferLog, error)
|
||||
GetNextBlockValidators() (keys.PublicKeys, error)
|
||||
GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem
|
||||
GetStorageItems(hash util.Uint160) (map[string]*state.StorageItem, error)
|
||||
GetTransaction(hash util.Uint256) (*transaction.Transaction, uint32, error)
|
||||
|
@ -53,8 +55,10 @@ type DAO interface {
|
|||
PutAssetState(as *state.Asset) error
|
||||
PutContractState(cs *state.Contract) error
|
||||
PutCurrentHeader(hashAndIndex []byte) error
|
||||
PutNativeContractState(h util.Uint160, value []byte) error
|
||||
PutNEP5Balances(acc util.Uint160, bs *state.NEP5Balances) 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
|
||||
PutUnspentCoinState(hash util.Uint256, ucs *state.UnspentCoin) error
|
||||
PutValidatorState(vs *state.Validator) error
|
||||
|
@ -207,6 +211,18 @@ func (dao *Simple) DeleteContractState(hash util.Uint160) error {
|
|||
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.
|
||||
|
||||
// -- start nep5 balances.
|
||||
|
@ -310,6 +326,38 @@ func (dao *Simple) putUnspentCoinState(hash util.Uint256, ucs *state.UnspentCoin
|
|||
|
||||
// -- 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.
|
||||
func (dao *Simple) GetValidatorStateOrNew(publicKey *keys.PublicKey) (*state.Validator, error) {
|
||||
validatorState, err := dao.GetValidatorState(publicKey)
|
||||
|
|
54
pkg/core/interop/runtime/witness.go
Normal file
54
pkg/core/interop/runtime/witness.go
Normal 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
|
||||
}
|
|
@ -7,6 +7,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"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/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
|
@ -574,7 +575,7 @@ func contractMigrate(ic *interop.Context, v *vm.VM) error {
|
|||
return err
|
||||
}
|
||||
if contract.HasStorage() {
|
||||
hash := getContextScriptHash(v, 0)
|
||||
hash := v.GetContextScriptHash(0)
|
||||
siMap, err := ic.DAO.GetStorageItems(hash)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -635,7 +636,7 @@ func assetCreate(ic *interop.Context, v *vm.VM) error {
|
|||
if owner.IsInfinity() {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -11,11 +11,9 @@ import (
|
|||
"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/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||
gherr "github.com/pkg/errors"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
|
@ -252,35 +250,19 @@ func engineGetScriptContainer(ic *interop.Context, v *vm.VM) error {
|
|||
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.
|
||||
func engineGetExecutingScriptHash(ic *interop.Context, v *vm.VM) error {
|
||||
return pushContextScriptHash(v, 0)
|
||||
return v.PushContextScriptHash(0)
|
||||
}
|
||||
|
||||
// engineGetCallingScriptHash returns calling script hash.
|
||||
func engineGetCallingScriptHash(ic *interop.Context, v *vm.VM) error {
|
||||
return pushContextScriptHash(v, 1)
|
||||
return v.PushContextScriptHash(1)
|
||||
}
|
||||
|
||||
// engineGetEntryScriptHash returns entry script hash.
|
||||
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.
|
||||
|
@ -295,50 +277,6 @@ func runtimeGetTrigger(ic *interop.Context, v *vm.VM) error {
|
|||
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
|
||||
// in neo-go the only meaningful thing to do here is to log.
|
||||
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 {
|
||||
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)
|
||||
return nil
|
||||
}
|
||||
|
@ -363,7 +301,7 @@ func runtimeNotify(ic *interop.Context, v *vm.VM) error {
|
|||
func runtimeLog(ic *interop.Context, v *vm.VM) error {
|
||||
msg := fmt.Sprintf("%q", v.Estack().Pop().Bytes())
|
||||
ic.Log.Info("runtime log",
|
||||
zap.Stringer("script", getContextScriptHash(v, 0)),
|
||||
zap.Stringer("script", v.GetContextScriptHash(0)),
|
||||
zap.String("logs", msg))
|
||||
return nil
|
||||
}
|
||||
|
@ -445,7 +383,7 @@ func storageGet(ic *interop.Context, v *vm.VM) error {
|
|||
// storageGetContext returns storage context (scripthash).
|
||||
func storageGetContext(ic *interop.Context, v *vm.VM) error {
|
||||
sc := &StorageContext{
|
||||
ScriptHash: getContextScriptHash(v, 0),
|
||||
ScriptHash: v.GetContextScriptHash(0),
|
||||
ReadOnly: false,
|
||||
}
|
||||
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).
|
||||
func storageGetReadOnlyContext(ic *interop.Context, v *vm.VM) error {
|
||||
sc := &StorageContext{
|
||||
ScriptHash: getContextScriptHash(v, 0),
|
||||
ScriptHash: v.GetContextScriptHash(0),
|
||||
ReadOnly: true,
|
||||
}
|
||||
v.Estack().PushVal(vm.NewInteropItem(sc))
|
||||
|
@ -537,7 +475,7 @@ func contractDestroy(ic *interop.Context, v *vm.VM) error {
|
|||
if ic.Trigger != trigger.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)
|
||||
if err != nil {
|
||||
return nil
|
||||
|
|
|
@ -14,6 +14,8 @@ import (
|
|||
"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/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/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||
|
@ -23,7 +25,12 @@ import (
|
|||
// up for current blockchain.
|
||||
func SpawnVM(ic *interop.Context) *vm.VM {
|
||||
vm := vm.New()
|
||||
bc := ic.Chain.(*Blockchain)
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
|
@ -33,6 +40,7 @@ func SpawnVM(ic *interop.Context) *vm.VM {
|
|||
})
|
||||
vm.RegisterInteropGetter(getSystemInterop(ic))
|
||||
vm.RegisterInteropGetter(getNeoInterop(ic))
|
||||
vm.RegisterInteropGetter(bc.contracts.GetNativeInterop(ic))
|
||||
return vm
|
||||
}
|
||||
|
||||
|
@ -85,7 +93,7 @@ var systemInterops = []interop.Function{
|
|||
{Name: "System.Header.GetIndex", Func: headerGetIndex, Price: 1},
|
||||
{Name: "System.Header.GetPrevHash", Func: headerGetPrevHash, 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.GetTime", Func: runtimeGetTime, 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.Keys", Func: iterator.Keys, 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.GetScriptHash", Func: outputGetScriptHash, 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.GetTime", Func: runtimeGetTime, 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.GetScriptHash", Func: outputGetScriptHash, 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.Notify", Func: runtimeNotify, Price: 1},
|
||||
{Name: "AntShares.Storage.Delete", Func: storageDelete, Price: 100},
|
||||
|
|
158
pkg/core/native/contract.go
Normal file
158
pkg/core/native/contract.go
Normal 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
|
||||
}
|
||||
}
|
16
pkg/core/native/interop.go
Normal file
16
pkg/core/native/interop.go
Normal 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
|
||||
}
|
134
pkg/core/native/native_gas.go
Normal file
134
pkg/core/native/native_gas.go
Normal 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
|
||||
}
|
||||
}
|
380
pkg/core/native/native_neo.go
Normal file
380
pkg/core/native/native_neo.go
Normal 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
|
||||
}
|
229
pkg/core/native/native_nep5.go
Normal file
229
pkg/core/native/native_nep5.go
Normal 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
|
||||
}
|
106
pkg/core/native_contract_test.go
Normal file
106
pkg/core/native_contract_test.go
Normal 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")
|
||||
}
|
||||
}
|
|
@ -33,6 +33,8 @@ type Account struct {
|
|||
ScriptHash util.Uint160
|
||||
IsFrozen bool
|
||||
Votes []*keys.PublicKey
|
||||
GAS NEP5BalanceState
|
||||
NEO NEOBalanceState
|
||||
Balances map[util.Uint256][]UnspentBalance
|
||||
Unclaimed UnclaimedBalances
|
||||
}
|
||||
|
@ -55,6 +57,8 @@ func (s *Account) DecodeBinary(br *io.BinReader) {
|
|||
br.ReadBytes(s.ScriptHash[:])
|
||||
s.IsFrozen = br.ReadBool()
|
||||
br.ReadArray(&s.Votes)
|
||||
s.GAS.DecodeBinary(br)
|
||||
s.NEO.DecodeBinary(br)
|
||||
|
||||
s.Balances = make(map[util.Uint256][]UnspentBalance)
|
||||
lenBalances := br.ReadVarUint()
|
||||
|
@ -80,6 +84,8 @@ func (s *Account) EncodeBinary(bw *io.BinWriter) {
|
|||
bw.WriteBytes(s.ScriptHash[:])
|
||||
bw.WriteBool(s.IsFrozen)
|
||||
bw.WriteArray(s.Votes)
|
||||
s.GAS.EncodeBinary(bw)
|
||||
s.NEO.EncodeBinary(bw)
|
||||
|
||||
bw.WriteVarUint(uint64(len(s.Balances)))
|
||||
for k, v := range s.Balances {
|
||||
|
|
45
pkg/core/state/native_state.go
Normal file
45
pkg/core/state/native_state.go
Normal 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()
|
||||
}
|
|
@ -12,10 +12,12 @@ const (
|
|||
STAccount KeyPrefix = 0x40
|
||||
STCoin KeyPrefix = 0x44
|
||||
STSpentCoin KeyPrefix = 0x45
|
||||
STNextValidators KeyPrefix = 0x47
|
||||
STValidator KeyPrefix = 0x48
|
||||
STAsset KeyPrefix = 0x4c
|
||||
STNotification KeyPrefix = 0x4d
|
||||
STContract KeyPrefix = 0x50
|
||||
STNativeContract KeyPrefix = 0x51
|
||||
STStorage KeyPrefix = 0x70
|
||||
STNEP5Transfers KeyPrefix = 0x72
|
||||
STNEP5Balances KeyPrefix = 0x73
|
||||
|
|
|
@ -99,6 +99,9 @@ func (chain testChain) GetNEP5Balances(util.Uint160) *state.NEP5Balances {
|
|||
func (chain testChain) GetValidators(...*transaction.Transaction) ([]*keys.PublicKey, error) {
|
||||
panic("TODO")
|
||||
}
|
||||
func (chain testChain) GetStandByValidators() (keys.PublicKeys, error) {
|
||||
panic("TODO")
|
||||
}
|
||||
func (chain testChain) GetEnrollments() ([]*state.Validator, error) {
|
||||
panic("TODO")
|
||||
}
|
||||
|
|
14
pkg/smartcontract/call_flags.go
Normal file
14
pkg/smartcontract/call_flags.go
Normal 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
|
||||
)
|
106
pkg/smartcontract/manifest/container.go
Normal file
106
pkg/smartcontract/manifest/container.go
Normal 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
|
||||
}
|
112
pkg/smartcontract/manifest/container_test.go
Normal file
112
pkg/smartcontract/manifest/container_test.go
Normal 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))
|
||||
})
|
||||
})
|
||||
}
|
128
pkg/smartcontract/manifest/manifest.go
Normal file
128
pkg/smartcontract/manifest/manifest.go
Normal 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
|
||||
}
|
121
pkg/smartcontract/manifest/manifest_test.go
Normal file
121
pkg/smartcontract/manifest/manifest_test.go
Normal 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"))
|
||||
})
|
||||
}
|
89
pkg/smartcontract/manifest/method.go
Normal file
89
pkg/smartcontract/manifest/method.go
Normal 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
|
||||
}
|
173
pkg/smartcontract/manifest/permission.go
Normal file
173
pkg/smartcontract/manifest/permission.go
Normal 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")
|
||||
}
|
94
pkg/smartcontract/manifest/permission_test.go
Normal file
94
pkg/smartcontract/manifest/permission_test.go
Normal 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)
|
||||
}
|
|
@ -6,6 +6,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -438,7 +439,7 @@ func parseArgs(args []string) ([]vm.StackItem, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items[i] = vm.NewBigIntegerItem(val)
|
||||
items[i] = vm.NewBigIntegerItem(big.NewInt(val))
|
||||
case stringType:
|
||||
items[i] = vm.NewByteArrayItem([]byte(value))
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package vm
|
|||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"math/big"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
||||
"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")
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (c *Context) Equals(s StackItem) bool {
|
||||
return c == s
|
||||
|
@ -203,3 +209,19 @@ func (c *Context) atBreakPoint() bool {
|
|||
func (c *Context) String() string {
|
||||
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
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"math/big"
|
||||
|
||||
"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
|
||||
|
@ -74,18 +73,11 @@ func (e *Element) Value() interface{} {
|
|||
// 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.
|
||||
func (e *Element) BigInt() *big.Int {
|
||||
switch t := e.value.(type) {
|
||||
case *BigIntegerItem:
|
||||
return t.value
|
||||
case *BoolItem:
|
||||
if t.value {
|
||||
return big.NewInt(1)
|
||||
}
|
||||
return big.NewInt(0)
|
||||
default:
|
||||
b := t.Value().([]uint8)
|
||||
return emit.BytesToInt(b)
|
||||
val, err := e.value.TryInteger()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// TryBool attempts to get the underlying value of the element as a boolean.
|
||||
|
|
|
@ -22,6 +22,8 @@ type StackItem interface {
|
|||
Dup() StackItem
|
||||
// TryBytes converts StackItem to a byte slice.
|
||||
TryBytes() ([]byte, error)
|
||||
// TryInteger converts StackItem to an integer.
|
||||
TryInteger() (*big.Int, error)
|
||||
// Equals checks if 2 StackItems are equal.
|
||||
Equals(s StackItem) bool
|
||||
// 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")
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (i *StructItem) Equals(s StackItem) bool {
|
||||
if i == s {
|
||||
|
@ -210,6 +217,11 @@ func (i NullItem) TryBytes() ([]byte, error) {
|
|||
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.
|
||||
func (i NullItem) Equals(s StackItem) bool {
|
||||
_, ok := s.(NullItem)
|
||||
|
@ -229,9 +241,9 @@ type BigIntegerItem struct {
|
|||
}
|
||||
|
||||
// NewBigIntegerItem returns an new BigIntegerItem object.
|
||||
func NewBigIntegerItem(value int64) *BigIntegerItem {
|
||||
func NewBigIntegerItem(value *big.Int) *BigIntegerItem {
|
||||
return &BigIntegerItem{
|
||||
value: big.NewInt(value),
|
||||
value: value,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -245,6 +257,11 @@ func (i *BigIntegerItem) TryBytes() ([]byte, error) {
|
|||
return i.Bytes(), nil
|
||||
}
|
||||
|
||||
// TryInteger implements StackItem interface.
|
||||
func (i *BigIntegerItem) TryInteger() (*big.Int, error) {
|
||||
return i.value, nil
|
||||
}
|
||||
|
||||
// Equals implements StackItem interface.
|
||||
func (i *BigIntegerItem) Equals(s StackItem) bool {
|
||||
if i == s {
|
||||
|
@ -334,6 +351,14 @@ func (i *BoolItem) TryBytes() ([]byte, error) {
|
|||
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.
|
||||
func (i *BoolItem) Equals(s StackItem) bool {
|
||||
if i == s {
|
||||
|
@ -388,6 +413,11 @@ func (i *ByteArrayItem) TryBytes() ([]byte, error) {
|
|||
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.
|
||||
func (i *ByteArrayItem) Equals(s StackItem) bool {
|
||||
if i == s {
|
||||
|
@ -445,6 +475,11 @@ func (i *ArrayItem) TryBytes() ([]byte, error) {
|
|||
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.
|
||||
func (i *ArrayItem) Equals(s StackItem) bool {
|
||||
return i == s
|
||||
|
@ -505,6 +540,11 @@ func (i *MapItem) TryBytes() ([]byte, error) {
|
|||
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.
|
||||
func (i *MapItem) Equals(s StackItem) bool {
|
||||
return i == s
|
||||
|
@ -616,6 +656,11 @@ func (i *InteropItem) TryBytes() ([]byte, error) {
|
|||
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.
|
||||
func (i *InteropItem) Equals(s StackItem) bool {
|
||||
if i == s {
|
||||
|
|
|
@ -104,7 +104,7 @@ var stringerTestCases = []struct {
|
|||
result: "Struct",
|
||||
},
|
||||
{
|
||||
input: NewBigIntegerItem(3),
|
||||
input: NewBigIntegerItem(big.NewInt(3)),
|
||||
result: "BigInteger",
|
||||
},
|
||||
{
|
||||
|
@ -148,48 +148,48 @@ var equalsTestCases = map[string][]struct {
|
|||
},
|
||||
{
|
||||
item1: NewStructItem(nil),
|
||||
item2: NewBigIntegerItem(1),
|
||||
item2: NewBigIntegerItem(big.NewInt(1)),
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
item1: NewStructItem(nil),
|
||||
item2: NewStructItem([]StackItem{NewBigIntegerItem(1)}),
|
||||
item2: NewStructItem([]StackItem{NewBigIntegerItem(big.NewInt(1))}),
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
item1: NewStructItem([]StackItem{NewBigIntegerItem(1)}),
|
||||
item2: NewStructItem([]StackItem{NewBigIntegerItem(2)}),
|
||||
item1: NewStructItem([]StackItem{NewBigIntegerItem(big.NewInt(1))}),
|
||||
item2: NewStructItem([]StackItem{NewBigIntegerItem(big.NewInt(2))}),
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
item1: NewStructItem([]StackItem{NewBigIntegerItem(1)}),
|
||||
item2: NewStructItem([]StackItem{NewBigIntegerItem(1)}),
|
||||
item1: NewStructItem([]StackItem{NewBigIntegerItem(big.NewInt(1))}),
|
||||
item2: NewStructItem([]StackItem{NewBigIntegerItem(big.NewInt(1))}),
|
||||
result: true,
|
||||
},
|
||||
},
|
||||
"bigint": {
|
||||
{
|
||||
item1: NewBigIntegerItem(2),
|
||||
item1: NewBigIntegerItem(big.NewInt(2)),
|
||||
item2: nil,
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
item1: NewBigIntegerItem(2),
|
||||
item2: NewBigIntegerItem(2),
|
||||
item1: NewBigIntegerItem(big.NewInt(2)),
|
||||
item2: NewBigIntegerItem(big.NewInt(2)),
|
||||
result: true,
|
||||
},
|
||||
{
|
||||
item1: NewBigIntegerItem(2),
|
||||
item1: NewBigIntegerItem(big.NewInt(2)),
|
||||
item2: NewBoolItem(false),
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
item1: NewBigIntegerItem(0),
|
||||
item1: NewBigIntegerItem(big.NewInt(0)),
|
||||
item2: NewBoolItem(false),
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
item1: NewBigIntegerItem(2),
|
||||
item1: NewBigIntegerItem(big.NewInt(2)),
|
||||
item2: makeStackItem(int32(2)),
|
||||
result: true,
|
||||
},
|
||||
|
@ -207,7 +207,7 @@ var equalsTestCases = map[string][]struct {
|
|||
},
|
||||
{
|
||||
item1: NewBoolItem(true),
|
||||
item2: NewBigIntegerItem(1),
|
||||
item2: NewBigIntegerItem(big.NewInt(1)),
|
||||
result: true,
|
||||
},
|
||||
{
|
||||
|
@ -234,7 +234,7 @@ var equalsTestCases = map[string][]struct {
|
|||
},
|
||||
{
|
||||
item1: NewByteArrayItem([]byte{1}),
|
||||
item2: NewBigIntegerItem(1),
|
||||
item2: NewBigIntegerItem(big.NewInt(1)),
|
||||
result: true,
|
||||
},
|
||||
{
|
||||
|
@ -261,7 +261,7 @@ var equalsTestCases = map[string][]struct {
|
|||
},
|
||||
{
|
||||
item1: NewArrayItem([]StackItem{&BigIntegerItem{big.NewInt(1)}}),
|
||||
item2: NewBigIntegerItem(1),
|
||||
item2: NewBigIntegerItem(big.NewInt(1)),
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
|
@ -282,13 +282,13 @@ var equalsTestCases = map[string][]struct {
|
|||
result: false,
|
||||
},
|
||||
{
|
||||
item1: &MapItem{value: []MapElement{{NewByteArrayItem([]byte("first")), NewBigIntegerItem(1)}, {NewBoolItem(true), NewByteArrayItem([]byte{2})}}},
|
||||
item2: &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(big.NewInt(1))}, {NewBoolItem(true), NewByteArrayItem([]byte{2})}}},
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
item1: &MapItem{value: []MapElement{{NewByteArrayItem([]byte("first")), NewBigIntegerItem(1)}, {NewBoolItem(true), NewByteArrayItem([]byte{2})}}},
|
||||
item2: &MapItem{value: []MapElement{{NewByteArrayItem([]byte("first")), NewBigIntegerItem(1)}, {NewBoolItem(true), NewByteArrayItem([]byte{3})}}},
|
||||
item1: &MapItem{value: []MapElement{{NewByteArrayItem([]byte("first")), NewBigIntegerItem(big.NewInt(1))}, {NewBoolItem(true), NewByteArrayItem([]byte{2})}}},
|
||||
item2: &MapItem{value: []MapElement{{NewByteArrayItem([]byte("first")), NewBigIntegerItem(big.NewInt(1))}, {NewBoolItem(true), NewByteArrayItem([]byte{3})}}},
|
||||
result: false,
|
||||
},
|
||||
},
|
||||
|
@ -333,7 +333,7 @@ var marshalJSONTestCases = []struct {
|
|||
result []byte
|
||||
}{
|
||||
{
|
||||
input: NewBigIntegerItem(2),
|
||||
input: NewBigIntegerItem(big.NewInt(2)),
|
||||
result: []byte(`2`),
|
||||
},
|
||||
{
|
||||
|
@ -386,7 +386,7 @@ var toContractParameterTestCases = []struct {
|
|||
}{
|
||||
{
|
||||
input: NewStructItem([]StackItem{
|
||||
NewBigIntegerItem(1),
|
||||
NewBigIntegerItem(big.NewInt(1)),
|
||||
NewBoolItem(true),
|
||||
}),
|
||||
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}},
|
||||
},
|
||||
{
|
||||
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{
|
||||
{Type: smartcontract.IntegerType, Value: int64(2)},
|
||||
{Type: smartcontract.BoolType, Value: true},
|
||||
|
@ -415,8 +415,8 @@ var toContractParameterTestCases = []struct {
|
|||
},
|
||||
{
|
||||
input: &MapItem{value: []MapElement{
|
||||
{NewBigIntegerItem(1), NewBoolItem(true)},
|
||||
{NewByteArrayItem([]byte("qwerty")), NewBigIntegerItem(3)},
|
||||
{NewBigIntegerItem(big.NewInt(1)), NewBoolItem(true)},
|
||||
{NewByteArrayItem([]byte("qwerty")), NewBigIntegerItem(big.NewInt(3))},
|
||||
{NewBoolItem(true), NewBoolItem(false)},
|
||||
}},
|
||||
result: smartcontract.Parameter{
|
||||
|
|
|
@ -441,7 +441,7 @@ func testIterableCreate(t *testing.T, typ string) {
|
|||
|
||||
vm := load(prog)
|
||||
arr := []StackItem{
|
||||
NewBigIntegerItem(42),
|
||||
NewBigIntegerItem(big.NewInt(42)),
|
||||
NewByteArrayItem([]byte{3, 2, 1}),
|
||||
}
|
||||
vm.estack.Push(&Element{value: NewArrayItem(arr)})
|
||||
|
@ -479,7 +479,7 @@ func testIterableConcat(t *testing.T, typ string) {
|
|||
|
||||
arr := []StackItem{
|
||||
NewBoolItem(false),
|
||||
NewBigIntegerItem(123),
|
||||
NewBigIntegerItem(big.NewInt(123)),
|
||||
NewMapItem(),
|
||||
}
|
||||
vm.estack.Push(&Element{value: NewArrayItem(arr[:1])})
|
||||
|
@ -521,15 +521,15 @@ func TestIteratorKeys(t *testing.T) {
|
|||
v := load(prog)
|
||||
arr := NewArrayItem([]StackItem{
|
||||
NewBoolItem(false),
|
||||
NewBigIntegerItem(42),
|
||||
NewBigIntegerItem(big.NewInt(42)),
|
||||
})
|
||||
v.estack.PushVal(arr)
|
||||
|
||||
runVM(t, v)
|
||||
|
||||
checkEnumeratorStack(t, v, []StackItem{
|
||||
NewBigIntegerItem(1), NewBoolItem(true),
|
||||
NewBigIntegerItem(0), NewBoolItem(true),
|
||||
NewBigIntegerItem(big.NewInt(1)), NewBoolItem(true),
|
||||
NewBigIntegerItem(big.NewInt(0)), NewBoolItem(true),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -541,7 +541,7 @@ func TestIteratorValues(t *testing.T) {
|
|||
|
||||
v := load(prog)
|
||||
m := NewMapItem()
|
||||
m.Add(NewBigIntegerItem(1), NewBoolItem(false))
|
||||
m.Add(NewBigIntegerItem(big.NewInt(1)), NewBoolItem(false))
|
||||
m.Add(NewByteArrayItem([]byte{32}), NewByteArrayItem([]byte{7}))
|
||||
v.estack.PushVal(m)
|
||||
|
||||
|
@ -680,7 +680,7 @@ func TestDeserializeUnknown(t *testing.T) {
|
|||
prog := append(getSyscallProg("Neo.Runtime.Deserialize"), byte(opcode.RET))
|
||||
vm := load(prog)
|
||||
|
||||
data, err := SerializeItem(NewBigIntegerItem(123))
|
||||
data, err := SerializeItem(NewBigIntegerItem(big.NewInt(123)))
|
||||
require.NoError(t, err)
|
||||
|
||||
data[0] = 0xFF
|
||||
|
|
Loading…
Reference in a new issue