From 090bee86248e22fcf34d0400c7c1968946df3cf2 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Sun, 13 Dec 2020 21:36:06 +0300 Subject: [PATCH 01/14] native: change OnPersist/PostPersist handling Every contract now has these and they're always invoked. See neo-project/neo#1913 and neo-project/neo#2119. --- pkg/core/interop/context.go | 2 + pkg/core/interop/interopnames/names.go | 4 ++ pkg/core/interops.go | 2 + pkg/core/native/contract.go | 52 +++----------------------- pkg/core/native/designate.go | 18 +++++---- pkg/core/native/interop.go | 29 ++++++++++++++ pkg/core/native/native_gas.go | 23 +++--------- pkg/core/native/native_neo.go | 10 ----- pkg/core/native/native_nep17.go | 31 ++------------- pkg/core/native/notary.go | 13 +++---- pkg/core/native/oracle.go | 14 +++---- pkg/core/native/policy.go | 12 +++--- pkg/core/native_contract_test.go | 28 +++++++++----- 13 files changed, 95 insertions(+), 143 deletions(-) diff --git a/pkg/core/interop/context.go b/pkg/core/interop/context.go index 0f06e96d9..f1d858901 100644 --- a/pkg/core/interop/context.go +++ b/pkg/core/interop/context.go @@ -89,6 +89,8 @@ type MethodAndPrice struct { type Contract interface { Initialize(*Context) error Metadata() *ContractMD + OnPersist(*Context) error + PostPersist(*Context) error } // ContractMD represents native contract instance. diff --git a/pkg/core/interop/interopnames/names.go b/pkg/core/interop/interopnames/names.go index c3021014d..44536d706 100644 --- a/pkg/core/interop/interopnames/names.go +++ b/pkg/core/interop/interopnames/names.go @@ -27,6 +27,8 @@ const ( SystemContractDestroy = "System.Contract.Destroy" SystemContractIsStandard = "System.Contract.IsStandard" SystemContractGetCallFlags = "System.Contract.GetCallFlags" + SystemContractNativeOnPersist = "System.Contract.NativeOnPersist" + SystemContractNativePostPersist = "System.Contract.NativePostPersist" SystemContractUpdate = "System.Contract.Update" SystemEnumeratorConcat = "System.Enumerator.Concat" SystemEnumeratorCreate = "System.Enumerator.Create" @@ -96,6 +98,8 @@ var names = []string{ SystemContractDestroy, SystemContractIsStandard, SystemContractGetCallFlags, + SystemContractNativeOnPersist, + SystemContractNativePostPersist, SystemContractUpdate, SystemEnumeratorConcat, SystemEnumeratorCreate, diff --git a/pkg/core/interops.go b/pkg/core/interops.go index 83ac95824..12f4439bf 100644 --- a/pkg/core/interops.go +++ b/pkg/core/interops.go @@ -67,6 +67,8 @@ var systemInterops = []interop.Function{ {Name: interopnames.SystemContractDestroy, Func: contractDestroy, Price: 1000000, RequiredFlags: smartcontract.WriteStates, DisallowCallback: true}, {Name: interopnames.SystemContractIsStandard, Func: contractIsStandard, Price: 30000, RequiredFlags: smartcontract.ReadStates, ParamCount: 1}, {Name: interopnames.SystemContractGetCallFlags, Func: contractGetCallFlags, Price: 30000, DisallowCallback: true}, + {Name: interopnames.SystemContractNativeOnPersist, Func: native.OnPersist, Price: 0, DisallowCallback: true}, + {Name: interopnames.SystemContractNativePostPersist, Func: native.PostPersist, Price: 0, DisallowCallback: true}, {Name: interopnames.SystemContractUpdate, Func: contractUpdate, Price: 0, RequiredFlags: smartcontract.WriteStates, ParamCount: 2, DisallowCallback: true}, {Name: interopnames.SystemEnumeratorConcat, Func: enumerator.Concat, Price: 400, ParamCount: 2, DisallowCallback: true}, diff --git a/pkg/core/native/contract.go b/pkg/core/native/contract.go index 29f7e58b7..0e925f450 100644 --- a/pkg/core/native/contract.go +++ b/pkg/core/native/contract.go @@ -1,15 +1,13 @@ package native import ( - "errors" "strings" "github.com/nspcc-dev/neo-go/pkg/core/interop" + "github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames" "github.com/nspcc-dev/neo-go/pkg/io" - "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/emit" - "github.com/nspcc-dev/neo-go/pkg/vm/opcode" ) // reservedContractID represents the upper bound of the reserved IDs for native contracts. @@ -62,9 +60,9 @@ func NewContracts(p2pSigExtensionsEnabled bool) *Contracts { gas.NEO = neo cs.GAS = gas - cs.Contracts = append(cs.Contracts, gas) cs.NEO = neo cs.Contracts = append(cs.Contracts, neo) + cs.Contracts = append(cs.Contracts, gas) policy := newPolicy() cs.Policy = policy @@ -93,62 +91,24 @@ func NewContracts(p2pSigExtensionsEnabled bool) *Contracts { return cs } -// GetPersistScript returns VM script calling "onPersist" method of every native contract. +// GetPersistScript returns VM script calling "onPersist" syscall for native contracts. func (cs *Contracts) GetPersistScript() []byte { if cs.persistScript != nil { return cs.persistScript } w := io.NewBufBinWriter() - for i := range cs.Contracts { - md := cs.Contracts[i].Metadata() - // Not every contract is persisted: - // https://github.com/neo-project/neo/blob/master/src/neo/Ledger/Blockchain.cs#L90 - if md.ContractID == policyContractID || md.ContractID == oracleContractID || md.ContractID == designateContractID { - continue - } - emit.Int(w.BinWriter, 0) - emit.Opcodes(w.BinWriter, opcode.NEWARRAY) - emit.String(w.BinWriter, "onPersist") - emit.AppCall(w.BinWriter, md.Hash) - emit.Opcodes(w.BinWriter, opcode.DROP) - } + emit.Syscall(w.BinWriter, interopnames.SystemContractNativeOnPersist) cs.persistScript = w.Bytes() return cs.persistScript } -// GetPostPersistScript returns VM script calling "postPersist" method of some native contracts. +// GetPostPersistScript returns VM script calling "postPersist" syscall for native contracts. func (cs *Contracts) GetPostPersistScript() []byte { if cs.postPersistScript != nil { return cs.postPersistScript } w := io.NewBufBinWriter() - for i := range cs.Contracts { - md := cs.Contracts[i].Metadata() - // Not every contract is persisted: - // https://github.com/neo-project/neo/blob/master/src/neo/Ledger/Blockchain.cs#L103 - if md.ContractID == policyContractID || md.ContractID == gasContractID || md.ContractID == designateContractID || md.ContractID == notaryContractID { - continue - } - emit.Int(w.BinWriter, 0) - emit.Opcodes(w.BinWriter, opcode.NEWARRAY) - emit.String(w.BinWriter, "postPersist") - emit.AppCall(w.BinWriter, md.Hash) - emit.Opcodes(w.BinWriter, opcode.DROP) - } + emit.Syscall(w.BinWriter, interopnames.SystemContractNativePostPersist) cs.postPersistScript = w.Bytes() return cs.postPersistScript } - -func postPersistBase(ic *interop.Context) error { - if ic.Trigger != trigger.PostPersist { - return errors.New("postPersist must be trigered by system") - } - return nil -} - -func onPersistBase(ic *interop.Context) error { - if ic.Trigger != trigger.OnPersist { - return errors.New("onPersist must be trigered by system") - } - return nil -} diff --git a/pkg/core/native/designate.go b/pkg/core/native/designate.go index bc4f4f4f0..d57864266 100644 --- a/pkg/core/native/designate.go +++ b/pkg/core/native/designate.go @@ -87,14 +87,6 @@ func newDesignate(p2pSigExtensionsEnabled bool) *Designate { md = newMethodAndPrice(s.designateAsRole, 0, smartcontract.WriteStates) s.AddMethod(md, desc) - desc = newDescriptor("onPersist", smartcontract.VoidType) - md = newMethodAndPrice(getOnPersistWrapper(onPersistBase), 0, smartcontract.WriteStates) - s.AddMethod(md, desc) - - desc = newDescriptor("postPersist", smartcontract.VoidType) - md = newMethodAndPrice(getOnPersistWrapper(postPersistBase), 0, smartcontract.WriteStates) - s.AddMethod(md, desc) - return s } @@ -103,6 +95,16 @@ func (s *Designate) Initialize(ic *interop.Context) error { return nil } +// OnPersist implements Contract interface. +func (s *Designate) OnPersist(ic *interop.Context) error { + return nil +} + +// PostPersist implements Contract interface. +func (s *Designate) PostPersist(ic *interop.Context) error { + return nil +} + // OnPersistEnd updates cached values if they've been changed. func (s *Designate) OnPersistEnd(d dao.DAO) error { if !s.rolesChanged() { diff --git a/pkg/core/native/interop.go b/pkg/core/native/interop.go index 4fbcf2ffa..ecd529fc4 100644 --- a/pkg/core/native/interop.go +++ b/pkg/core/native/interop.go @@ -7,6 +7,7 @@ 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/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" ) // Deploy deploys native contract. @@ -70,3 +71,31 @@ func Call(ic *interop.Context) error { } return nil } + +// OnPersist calls OnPersist methods for all native contracts. +func OnPersist(ic *interop.Context) error { + if ic.Trigger != trigger.OnPersist { + return errors.New("onPersist must be trigered by system") + } + for _, c := range ic.Natives { + err := c.OnPersist(ic) + if err != nil { + return err + } + } + return nil +} + +// PostPersist calls PostPersist methods for all native contracts. +func PostPersist(ic *interop.Context) error { + if ic.Trigger != trigger.PostPersist { + return errors.New("postPersist must be trigered by system") + } + for _, c := range ic.Natives { + err := c.PostPersist(ic) + if err != nil { + return err + } + } + return nil +} diff --git a/pkg/core/native/native_gas.go b/pkg/core/native/native_gas.go index 8965f0236..2d6122712 100644 --- a/pkg/core/native/native_gas.go +++ b/pkg/core/native/native_gas.go @@ -31,16 +31,11 @@ func newGAS() *GAS { nep17.symbol = "gas" nep17.decimals = 8 nep17.factor = GASFactor - nep17.onPersist = chainOnPersist(onPersistBase, g.OnPersist) nep17.incBalance = g.increaseBalance nep17.ContractID = gasContractID g.nep17TokenNative = *nep17 - onp := g.Methods["onPersist"] - onp.Func = getOnPersistWrapper(g.onPersist) - g.Methods["onPersist"] = onp - return g } @@ -98,6 +93,11 @@ func (g *GAS) OnPersist(ic *interop.Context) error { return nil } +// PostPersist implements Contract interface. +func (g *GAS) PostPersist(ic *interop.Context) error { + return nil +} + func getStandbyValidatorsHash(ic *interop.Context) (util.Uint160, error) { s, err := smartcontract.CreateDefaultMultiSigRedeemScript(ic.Chain.GetStandByValidators()) if err != nil { @@ -105,16 +105,3 @@ func getStandbyValidatorsHash(ic *interop.Context) (util.Uint160, error) { } return hash.Hash160(s), 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 - } -} diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index 40800dc1a..fd255a47b 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -97,8 +97,6 @@ func newNEO() *NEO { nep17.symbol = "neo" nep17.decimals = 0 nep17.factor = 1 - nep17.onPersist = chainOnPersist(onPersistBase, n.OnPersist) - nep17.postPersist = chainOnPersist(nep17.postPersist, n.PostPersist) nep17.incBalance = n.increaseBalance nep17.ContractID = neoContractID @@ -109,14 +107,6 @@ func newNEO() *NEO { n.committee.Store(keysWithVotes(nil)) n.committeeHash.Store(util.Uint160{}) - onp := n.Methods["onPersist"] - onp.Func = getOnPersistWrapper(n.onPersist) - n.Methods["onPersist"] = onp - - pp := n.Methods["postPersist"] - pp.Func = getOnPersistWrapper(n.postPersist) - n.Methods["postPersist"] = pp - desc := newDescriptor("unclaimedGas", smartcontract.IntegerType, manifest.NewParameter("account", smartcontract.Hash160Type), manifest.NewParameter("end", smartcontract.IntegerType)) diff --git a/pkg/core/native/native_nep17.go b/pkg/core/native/native_nep17.go index 8f42dc8da..9049bcbb7 100644 --- a/pkg/core/native/native_nep17.go +++ b/pkg/core/native/native_nep17.go @@ -2,7 +2,6 @@ package native import ( "errors" - "fmt" "math" "math/big" @@ -33,12 +32,10 @@ func makeAccountKey(h util.Uint160) []byte { // nep17TokenNative represents NEP-17 token contract. type nep17TokenNative struct { interop.ContractMD - symbol string - decimals int64 - factor int64 - onPersist func(*interop.Context) error - postPersist func(*interop.Context) error - incBalance func(*interop.Context, util.Uint160, *state.StorageItem, *big.Int) error + symbol string + decimals int64 + factor int64 + incBalance func(*interop.Context, util.Uint160, *state.StorageItem, *big.Int) error } // totalSupplyKey is the key used to store totalSupply value. @@ -48,8 +45,6 @@ func (c *nep17TokenNative) Metadata() *interop.ContractMD { return &c.ContractMD } -var _ interop.Contract = (*nep17TokenNative)(nil) - func newNEP17Native(name string) *nep17TokenNative { n := &nep17TokenNative{ContractMD: *interop.NewContractMD(name)} n.Manifest.SupportedStandards = []string{manifest.NEP17StandardName} @@ -82,14 +77,6 @@ func newNEP17Native(name string) *nep17TokenNative { md = newMethodAndPrice(n.Transfer, 8000000, smartcontract.WriteStates|smartcontract.AllowCall|smartcontract.AllowNotify) n.AddMethod(md, desc) - desc = newDescriptor("onPersist", smartcontract.VoidType) - md = newMethodAndPrice(getOnPersistWrapper(onPersistBase), 0, smartcontract.WriteStates) - n.AddMethod(md, desc) - - desc = newDescriptor("postPersist", smartcontract.VoidType) - md = newMethodAndPrice(getOnPersistWrapper(postPersistBase), 0, smartcontract.WriteStates) - n.AddMethod(md, desc) - n.AddEvent("Transfer", transferParams...) return n @@ -333,13 +320,3 @@ func toUint32(s stackitem.Item) uint32 { } return uint32(int64Value) } - -func getOnPersistWrapper(f func(ic *interop.Context) error) interop.Method { - return func(ic *interop.Context, _ []stackitem.Item) stackitem.Item { - err := f(ic) - if err != nil { - panic(fmt.Errorf("OnPersist for native contract: %w", err)) - } - return stackitem.Null{} - } -} diff --git a/pkg/core/native/notary.go b/pkg/core/native/notary.go index 3f29bca37..9a087b721 100644 --- a/pkg/core/native/notary.go +++ b/pkg/core/native/notary.go @@ -98,14 +98,6 @@ func newNotary() *Notary { md = newMethodAndPrice(n.setMaxNotValidBeforeDelta, 300_0000, smartcontract.WriteStates) n.AddMethod(md, desc) - desc = newDescriptor("onPersist", smartcontract.VoidType) - md = newMethodAndPrice(getOnPersistWrapper(n.OnPersist), 0, smartcontract.WriteStates) - n.AddMethod(md, desc) - - desc = newDescriptor("postPersist", smartcontract.VoidType) - md = newMethodAndPrice(getOnPersistWrapper(postPersistBase), 0, smartcontract.WriteStates) - n.AddMethod(md, desc) - return n } @@ -179,6 +171,11 @@ func (n *Notary) OnPersistEnd(dao dao.DAO) error { return nil } +// PostPersist implements Contract interface. +func (n *Notary) PostPersist(ic *interop.Context) error { + return nil +} + // onPayment records deposited amount as belonging to "from" address with a lock // till the specified chain's height. func (n *Notary) onPayment(ic *interop.Context, args []stackitem.Item) stackitem.Item { diff --git a/pkg/core/native/oracle.go b/pkg/core/native/oracle.go index 92ff3f57c..a2f1e8ac7 100644 --- a/pkg/core/native/oracle.go +++ b/pkg/core/native/oracle.go @@ -122,15 +122,6 @@ func newOracle() *Oracle { md = newMethodAndPrice(o.verify, 100_0000, smartcontract.NoneFlag) o.AddMethod(md, desc) - pp := chainOnPersist(postPersistBase, o.PostPersist) - desc = newDescriptor("postPersist", smartcontract.VoidType) - md = newMethodAndPrice(getOnPersistWrapper(pp), 0, smartcontract.WriteStates) - o.AddMethod(md, desc) - - desc = newDescriptor("onPersist", smartcontract.VoidType) - md = newMethodAndPrice(getOnPersistWrapper(onPersistBase), 0, smartcontract.WriteStates) - o.AddMethod(md, desc) - o.AddEvent("OracleRequest", manifest.NewParameter("Id", smartcontract.IntegerType), manifest.NewParameter("RequestContract", smartcontract.Hash160Type), manifest.NewParameter("Url", smartcontract.StringType), @@ -141,6 +132,11 @@ func newOracle() *Oracle { return o } +// OnPersist implements Contract interface. +func (o *Oracle) OnPersist(ic *interop.Context) error { + return nil +} + // PostPersist represents `postPersist` method. func (o *Oracle) PostPersist(ic *interop.Context) error { var nodes keys.PublicKeys diff --git a/pkg/core/native/policy.go b/pkg/core/native/policy.go index 79a7897d9..2dc80b5fb 100644 --- a/pkg/core/native/policy.go +++ b/pkg/core/native/policy.go @@ -124,13 +124,6 @@ func newPolicy() *Policy { md = newMethodAndPrice(p.unblockAccount, 3000000, smartcontract.WriteStates) p.AddMethod(md, desc) - desc = newDescriptor("onPersist", smartcontract.VoidType) - md = newMethodAndPrice(getOnPersistWrapper(p.OnPersist), 0, smartcontract.WriteStates) - p.AddMethod(md, desc) - - desc = newDescriptor("postPersist", smartcontract.VoidType) - md = newMethodAndPrice(getOnPersistWrapper(postPersistBase), 0, smartcontract.WriteStates) - p.AddMethod(md, desc) return p } @@ -157,6 +150,11 @@ func (p *Policy) OnPersist(ic *interop.Context) error { return nil } +// PostPersist implements Contract interface. +func (p *Policy) PostPersist(ic *interop.Context) error { + return nil +} + // OnPersistEnd updates cached Policy values if they've been changed func (p *Policy) OnPersistEnd(dao dao.DAO) error { if p.isValid { diff --git a/pkg/core/native_contract_test.go b/pkg/core/native_contract_test.go index 97b70cad1..0e1f9069a 100644 --- a/pkg/core/native_contract_test.go +++ b/pkg/core/native_contract_test.go @@ -1,6 +1,7 @@ package core import ( + "errors" "math/big" "testing" @@ -33,18 +34,19 @@ func (tn *testNative) Metadata() *interop.ContractMD { return &tn.meta } -func (tn *testNative) OnPersist(ic *interop.Context, _ []stackitem.Item) stackitem.Item { - if ic.Trigger != trigger.OnPersist { - panic("invalid trigger") - } +func (tn *testNative) OnPersist(ic *interop.Context) error { select { case tn.blocks <- ic.Block.Index: - return stackitem.NewBool(true) + return nil default: - return stackitem.NewBool(false) + return errors.New("can't send index") } } +func (tn *testNative) PostPersist(ic *interop.Context) error { + return nil +} + var _ interop.Contract = (*testNative)(nil) // registerNative registers native contract in the blockchain. @@ -106,10 +108,6 @@ func newTestNative() *testNative { RequiredFlags: smartcontract.NoneFlag} tn.meta.AddMethod(md, desc) - desc = &manifest.Method{Name: "onPersist", ReturnType: smartcontract.BoolType} - md = &interop.MethodAndPrice{Func: tn.OnPersist, RequiredFlags: smartcontract.WriteStates} - tn.meta.AddMethod(md, desc) - return tn } @@ -252,18 +250,28 @@ func TestNativeContract_InvokeOtherContract(t *testing.T) { }) require.NoError(t, err) + var drainTN = func(t *testing.T) { + select { + case <-tn.blocks: + default: + require.Fail(t, "testNative didn't send us block") + } + } + cs, _ := getTestContractState() require.NoError(t, chain.dao.PutContractState(cs)) t.Run("non-native, no return", func(t *testing.T) { res, err := invokeContractMethod(chain, testSumPrice*4+10000, tn.Metadata().Hash, "callOtherContractNoReturn", cs.Hash, "justReturn", []interface{}{}) require.NoError(t, err) + drainTN(t) checkResult(t, res, stackitem.Null{}) // simple call is done with EnsureNotEmpty }) t.Run("non-native, with return", func(t *testing.T) { res, err := invokeContractMethod(chain, testSumPrice*4+10000, tn.Metadata().Hash, "callOtherContractWithReturn", cs.Hash, "ret7", []interface{}{}) require.NoError(t, err) + drainTN(t) checkResult(t, res, stackitem.Make(8)) }) } From ad3547783d45b841022e2448fd785bdef4fe548f Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Tue, 8 Dec 2020 18:28:00 +0300 Subject: [PATCH 02/14] native: drop Neo.Native.Deploy, move contract init to management contract The contract is almost a stub at the moment, though it does deploy other contracts. --- cli/testdata/chain50x2.acc | Bin 45771 -> 45734 bytes pkg/core/blockchain.go | 24 ++++----- pkg/core/blockchain_test.go | 17 +++++-- pkg/core/dao/dao.go | 2 +- pkg/core/dao/dao_test.go | 6 +-- pkg/core/interop/interopnames/names.go | 2 - pkg/core/interop_system_test.go | 2 +- pkg/core/interops.go | 1 - pkg/core/native/contract.go | 19 ++++--- pkg/core/native/interop.go | 26 ---------- pkg/core/native/management.go | 67 +++++++++++++++++++++++++ pkg/core/util.go | 31 +----------- pkg/core/util_test.go | 2 +- pkg/rpc/server/server_test.go | 23 +++++---- pkg/rpc/server/testdata/testblocks.acc | Bin 7522 -> 7522 bytes 15 files changed, 123 insertions(+), 99 deletions(-) create mode 100644 pkg/core/native/management.go diff --git a/cli/testdata/chain50x2.acc b/cli/testdata/chain50x2.acc index 6c10dabc4bbb20a3ea17a175d596cabfe737091e..43ee863186792eb93b161deaa8e1b707d56bddfc 100644 GIT binary patch literal 45734 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

D^`)$j$OHs_P+Yy?`?0a6hN6kq&Q+Z7ZEYCO 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 A`~Uy| literal 45771 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 ZFpDPCmXR}4%n`C382Dc8Zrq_0=6`Y)NgMzG diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index a762830f1..544294129 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -600,18 +600,16 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error } writeBuf.Reset() - if block.Index > 0 { - aer, err := bc.runPersist(bc.contracts.GetPersistScript(), block, cache, trigger.OnPersist) - if err != nil { - return fmt.Errorf("onPersist failed: %w", err) - } - appExecResults = append(appExecResults, aer) - err = cache.PutAppExecResult(aer, writeBuf) - if err != nil { - return fmt.Errorf("failed to store onPersist exec result: %w", err) - } - writeBuf.Reset() + aer, err := bc.runPersist(bc.contracts.GetPersistScript(), block, cache, trigger.OnPersist) + if err != nil { + return fmt.Errorf("onPersist failed: %w", err) } + appExecResults = append(appExecResults, aer) + err = cache.PutAppExecResult(aer, writeBuf) + if err != nil { + return fmt.Errorf("failed to store onPersist exec result: %w", err) + } + writeBuf.Reset() for _, tx := range block.Transactions { if err := cache.StoreAsTransaction(tx, block.Index, writeBuf); err != nil { @@ -673,7 +671,7 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error } } - aer, err := bc.runPersist(bc.contracts.GetPostPersistScript(), block, cache, trigger.PostPersist) + aer, err = bc.runPersist(bc.contracts.GetPostPersistScript(), block, cache, trigger.PostPersist) if err != nil { return fmt.Errorf("postPersist failed: %w", err) } @@ -1677,7 +1675,7 @@ func (bc *Blockchain) initVerificationVM(ic *interop.Context, hash util.Uint160, v.LoadScriptWithHash(cs.Script, hash, smartcontract.ReadStates|smartcontract.AllowCall) v.Jump(v.Context(), md.Offset) - if cs.ID < 0 { + if cs.ID <= 0 { w := io.NewBufBinWriter() emit.Opcodes(w.BinWriter, opcode.DEPTH, opcode.PACK) emit.String(w.BinWriter, manifest.MethodVerify) diff --git a/pkg/core/blockchain_test.go b/pkg/core/blockchain_test.go index d63e0a803..785c18e79 100644 --- a/pkg/core/blockchain_test.go +++ b/pkg/core/blockchain_test.go @@ -713,11 +713,18 @@ func TestVerifyTx(t *testing.T) { require.True(t, errors.Is(bc.VerifyTx(tx), ErrHasConflicts)) }) t.Run("attribute on-chain conflict", func(t *testing.T) { - b, err := bc.GetBlock(bc.GetHeaderHash(0)) - require.NoError(t, err) - conflictsHash := b.Transactions[0].Hash() - tx := getConflictsTx(conflictsHash) - require.Error(t, bc.VerifyTx(tx)) + tx := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0) + tx.ValidUntilBlock = 4242 + tx.Signers = []transaction.Signer{{ + Account: testchain.MultisigScriptHash(), + Scopes: transaction.None, + }} + require.NoError(t, testchain.SignTx(bc, tx)) + b := bc.newBlock(tx) + + require.NoError(t, bc.AddBlock(b)) + txConflict := getConflictsTx(tx.Hash()) + require.Error(t, bc.VerifyTx(txConflict)) }) t.Run("positive", func(t *testing.T) { tx := getConflictsTx(random.Uint256()) diff --git a/pkg/core/dao/dao.go b/pkg/core/dao/dao.go index b1fb35a7d..d7af83996 100644 --- a/pkg/core/dao/dao.go +++ b/pkg/core/dao/dao.go @@ -163,7 +163,7 @@ func (dao *Simple) DeleteContractState(hash util.Uint160) error { // GetAndUpdateNextContractID returns id for the next contract and increases stored ID. func (dao *Simple) GetAndUpdateNextContractID() (int32, error) { - var id int32 + var id = int32(1) key := storage.SYSContractID.Bytes() data, err := dao.Store.Get(key) if err == nil { diff --git a/pkg/core/dao/dao_test.go b/pkg/core/dao/dao_test.go index a1a022eea..b3971c74c 100644 --- a/pkg/core/dao/dao_test.go +++ b/pkg/core/dao/dao_test.go @@ -73,13 +73,13 @@ func TestSimple_GetAndUpdateNextContractID(t *testing.T) { dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false) id, err := dao.GetAndUpdateNextContractID() require.NoError(t, err) - require.EqualValues(t, 0, id) - id, err = dao.GetAndUpdateNextContractID() - require.NoError(t, err) require.EqualValues(t, 1, id) id, err = dao.GetAndUpdateNextContractID() require.NoError(t, err) require.EqualValues(t, 2, id) + id, err = dao.GetAndUpdateNextContractID() + require.NoError(t, err) + require.EqualValues(t, 3, id) } func TestPutGetAppExecResult(t *testing.T) { diff --git a/pkg/core/interop/interopnames/names.go b/pkg/core/interop/interopnames/names.go index 44536d706..ac60bca12 100644 --- a/pkg/core/interop/interopnames/names.go +++ b/pkg/core/interop/interopnames/names.go @@ -69,7 +69,6 @@ const ( NeoCryptoSHA256 = "Neo.Crypto.SHA256" NeoCryptoRIPEMD160 = "Neo.Crypto.RIPEMD160" NeoNativeCall = "Neo.Native.Call" - NeoNativeDeploy = "Neo.Native.Deploy" ) var names = []string{ @@ -140,5 +139,4 @@ var names = []string{ NeoCryptoSHA256, NeoCryptoRIPEMD160, NeoNativeCall, - NeoNativeDeploy, } diff --git a/pkg/core/interop_system_test.go b/pkg/core/interop_system_test.go index faef05bb6..b8b2ab42c 100644 --- a/pkg/core/interop_system_test.go +++ b/pkg/core/interop_system_test.go @@ -718,7 +718,7 @@ func TestContractCreate(t *testing.T) { ic.Tx = transaction.New(netmode.UnitTestNet, []byte{1}, 0) ic.Tx.Signers = append(ic.Tx.Signers, transaction.Signer{Account: sender}) - cs.ID = 0 + cs.ID = 1 cs.Hash = state.CreateContractHash(sender, cs.Script) t.Run("missing NEF", func(t *testing.T) { diff --git a/pkg/core/interops.go b/pkg/core/interops.go index 12f4439bf..bb7eda1ed 100644 --- a/pkg/core/interops.go +++ b/pkg/core/interops.go @@ -126,7 +126,6 @@ var neoInterops = []interop.Function{ {Name: interopnames.NeoCryptoSHA256, Func: crypto.Sha256, Price: 1000000, ParamCount: 1}, {Name: interopnames.NeoCryptoRIPEMD160, Func: crypto.RipeMD160, Price: 1000000, ParamCount: 1}, {Name: interopnames.NeoNativeCall, Func: native.Call, Price: 0, RequiredFlags: smartcontract.AllowCall, ParamCount: 1, DisallowCallback: true}, - {Name: interopnames.NeoNativeDeploy, Func: native.Deploy, Price: 0, RequiredFlags: smartcontract.WriteStates, DisallowCallback: true}, } // initIDinInteropsSlice initializes IDs from names in one given diff --git a/pkg/core/native/contract.go b/pkg/core/native/contract.go index 0e925f450..7dad9d2e8 100644 --- a/pkg/core/native/contract.go +++ b/pkg/core/native/contract.go @@ -15,13 +15,14 @@ const reservedContractID = -100 // Contracts is a set of registered native contracts. type Contracts struct { - NEO *NEO - GAS *GAS - Policy *Policy - Oracle *Oracle - Designate *Designate - Notary *Notary - Contracts []interop.Contract + Management *Management + NEO *NEO + GAS *GAS + Policy *Policy + Oracle *Oracle + Designate *Designate + Notary *Notary + Contracts []interop.Contract // persistScript is vm script which executes "onPersist" method of every native contract. persistScript []byte // postPersistScript is vm script which executes "postPersist" method of every native contract. @@ -54,6 +55,10 @@ func (cs *Contracts) ByName(name string) interop.Contract { func NewContracts(p2pSigExtensionsEnabled bool) *Contracts { cs := new(Contracts) + mgmt := newManagement() + cs.Management = mgmt + cs.Contracts = append(cs.Contracts, mgmt) + gas := newGAS() neo := newNEO() neo.GAS = gas diff --git a/pkg/core/native/interop.go b/pkg/core/native/interop.go index ecd529fc4..78737da08 100644 --- a/pkg/core/native/interop.go +++ b/pkg/core/native/interop.go @@ -5,36 +5,10 @@ import ( "fmt" "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/trigger" ) -// Deploy deploys native contract. -func Deploy(ic *interop.Context) error { - if ic.Block == nil || ic.Block.Index != 0 { - return errors.New("native contracts can be deployed only at 0 block") - } - - for _, native := range ic.Natives { - md := native.Metadata() - - cs := &state.Contract{ - ID: md.ContractID, - Hash: md.Hash, - Script: md.Script, - Manifest: md.Manifest, - } - if err := ic.DAO.PutContractState(cs); err != nil { - return err - } - if err := native.Initialize(ic); err != nil { - return fmt.Errorf("initializing %s native contract: %w", md.Name, err) - } - } - return nil -} - // Call calls specified native contract method. func Call(ic *interop.Context) error { name := ic.VM.Estack().Pop().String() diff --git a/pkg/core/native/management.go b/pkg/core/native/management.go new file mode 100644 index 000000000..9df29fb97 --- /dev/null +++ b/pkg/core/native/management.go @@ -0,0 +1,67 @@ +package native + +import ( + "fmt" + + "github.com/nspcc-dev/neo-go/pkg/core/interop" + "github.com/nspcc-dev/neo-go/pkg/core/state" +) + +// Management is contract-managing native contract. +type Management struct { + interop.ContractMD +} + +const ( + managementName = "Neo Contract Management" + prefixContract = 8 + prefixNextAvailableId = 15 +) + +// newManagement creates new Management native contract. +func newManagement() *Management { + var m = &Management{ContractMD: *interop.NewContractMD(managementName)} + + return m +} + +// Metadata implements Contract interface. +func (m *Management) Metadata() *interop.ContractMD { + return &m.ContractMD +} + +// OnPersist implements Contract interface. +func (m *Management) OnPersist(ic *interop.Context) error { + if ic.Block.Index != 0 { // We're only deploying at 0 at the moment. + return nil + } + + for _, native := range ic.Natives { + md := native.Metadata() + + cs := &state.Contract{ + ID: md.ContractID, + Hash: md.Hash, + Script: md.Script, + Manifest: md.Manifest, + } + if err := ic.DAO.PutContractState(cs); err != nil { + return err + } + if err := native.Initialize(ic); err != nil { + return fmt.Errorf("initializing %s native contract: %w", md.Name, err) + } + } + + return nil +} + +// PostPersist implements Contract interface. +func (m *Management) PostPersist(_ *interop.Context) error { + return nil +} + +// Initialize implements Contract interface. +func (m *Management) Initialize(_ *interop.Context) error { + return nil +} diff --git a/pkg/core/util.go b/pkg/core/util.go index eb85640b6..283c9d050 100644 --- a/pkg/core/util.go +++ b/pkg/core/util.go @@ -5,16 +5,12 @@ import ( "time" "github.com/nspcc-dev/neo-go/pkg/config" - "github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/core/block" - "github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" - "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/util" - "github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" ) @@ -56,10 +52,8 @@ func createGenesisBlock(cfg config.ProtocolConfiguration) (*block.Block, error) } b := &block.Block{ - Base: base, - Transactions: []*transaction.Transaction{ - deployNativeContracts(cfg.Magic), - }, + Base: base, + Transactions: []*transaction.Transaction{}, ConsensusData: block.ConsensusData{ PrimaryIndex: 0, Nonce: 2083236893, @@ -70,27 +64,6 @@ func createGenesisBlock(cfg config.ProtocolConfiguration) (*block.Block, error) return b, nil } -func deployNativeContracts(magic netmode.Magic) *transaction.Transaction { - buf := io.NewBufBinWriter() - emit.Syscall(buf.BinWriter, interopnames.NeoNativeDeploy) - script := buf.Bytes() - tx := transaction.New(magic, script, 0) - tx.Nonce = 0 - tx.Signers = []transaction.Signer{ - { - Account: hash.Hash160([]byte{byte(opcode.PUSH1)}), - Scopes: transaction.None, - }, - } - tx.Scripts = []transaction.Witness{ - { - InvocationScript: []byte{}, - VerificationScript: []byte{byte(opcode.PUSH1)}, - }, - } - return tx -} - func validatorsFromConfig(cfg config.ProtocolConfiguration) ([]*keys.PublicKey, error) { vs, err := committeeFromConfig(cfg) if err != nil { diff --git a/pkg/core/util_test.go b/pkg/core/util_test.go index dae606d64..ceec4237a 100644 --- a/pkg/core/util_test.go +++ b/pkg/core/util_test.go @@ -17,7 +17,7 @@ func TestGenesisBlockMainNet(t *testing.T) { block, err := createGenesisBlock(cfg.ProtocolConfiguration) require.NoError(t, err) - expect := "ecaee33262f1bc7c7c28f2b25b54a5d61d50670871f45c0c6fe755a40cbde4a8" + expect := "00c6803707b564153d444bfcdf3a13325fc96dda55cc8a740bbd543a1d752fda" assert.Equal(t, expect, block.Hash().StringLE()) } diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index 8f9e2cc08..d8cf65ed5 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -57,8 +57,8 @@ type rpcTestCase struct { } const testContractHash = "743ed26f78e29ecd595535b74a943b1f9ccbc444" -const deploymentTxHash = "9ecf1273fe0d8868cc024c8270b569a12edd7ea9d675c88554b937134efb03f8" -const genesisBlockHash = "a496577895eb8c227bb866dc44f99f21c0cf06417ca8f2a877cc5d761a50dac0" +const deploymentTxHash = "a72dfaebf9543964d74e803723dae6a86196e0915ae9d76b3cc57c3b2e3e8c49" +const genesisBlockHash = "0542f4350c6e236d0509bcd98188b0034bfbecc1a0c7fcdb8e4295310d468b70" const verifyContractHash = "a2eb22340979804cb10cc1add0b8822c201f4d8a" const verifyContractAVM = "570300412d51083021700c14aa8acf859d4fe402b34e673f2156821796a488ebdb30716813cedb2869db289740" @@ -89,8 +89,9 @@ var rpcTestCases = map[string][]rpcTestCase{ res, ok := acc.(*result.ApplicationLog) require.True(t, ok) assert.Equal(t, genesisBlockHash, res.Container.StringLE()) - assert.Equal(t, 1, len(res.Executions)) - assert.Equal(t, trigger.PostPersist, res.Executions[0].Trigger) // no onPersist for genesis block + assert.Equal(t, 2, len(res.Executions)) + assert.Equal(t, trigger.OnPersist, res.Executions[0].Trigger) + assert.Equal(t, trigger.PostPersist, res.Executions[1].Trigger) assert.Equal(t, vm.HaltState, res.Executions[0].VMState) }, }, @@ -103,7 +104,7 @@ var rpcTestCases = map[string][]rpcTestCase{ require.True(t, ok) assert.Equal(t, genesisBlockHash, res.Container.StringLE()) assert.Equal(t, 1, len(res.Executions)) - assert.Equal(t, trigger.PostPersist, res.Executions[0].Trigger) // no onPersist for genesis block + assert.Equal(t, trigger.PostPersist, res.Executions[0].Trigger) assert.Equal(t, vm.HaltState, res.Executions[0].VMState) }, }, @@ -115,7 +116,9 @@ var rpcTestCases = map[string][]rpcTestCase{ res, ok := acc.(*result.ApplicationLog) require.True(t, ok) assert.Equal(t, genesisBlockHash, res.Container.StringLE()) - assert.Equal(t, 0, len(res.Executions)) // no onPersist for genesis block + assert.Equal(t, 1, len(res.Executions)) + assert.Equal(t, trigger.OnPersist, res.Executions[0].Trigger) + assert.Equal(t, vm.HaltState, res.Executions[0].VMState) }, }, { @@ -1075,7 +1078,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] }) t.Run("getrawtransaction", func(t *testing.T) { - block, _ := chain.GetBlock(chain.GetHeaderHash(0)) + block, _ := chain.GetBlock(chain.GetHeaderHash(1)) tx := block.Transactions[0] rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "getrawtransaction", "params": ["%s"]}"`, tx.Hash().StringLE()) body := doRPCCall(rpc, httpSrv.URL, t) @@ -1090,7 +1093,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] }) t.Run("getrawtransaction 2 arguments", func(t *testing.T) { - block, _ := chain.GetBlock(chain.GetHeaderHash(0)) + block, _ := chain.GetBlock(chain.GetHeaderHash(1)) tx := block.Transactions[0] rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "getrawtransaction", "params": ["%s", 0]}"`, tx.Hash().StringLE()) body := doRPCCall(rpc, httpSrv.URL, t) @@ -1105,7 +1108,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] }) t.Run("getrawtransaction 2 arguments, verbose", func(t *testing.T) { - block, _ := chain.GetBlock(chain.GetHeaderHash(0)) + block, _ := chain.GetBlock(chain.GetHeaderHash(1)) TXHash := block.Transactions[0].Hash() _ = block.Transactions[0].Size() rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "getrawtransaction", "params": ["%s", 1]}"`, TXHash.StringLE()) @@ -1116,7 +1119,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] require.NoErrorf(t, err, "could not parse response: %s", txOut) assert.Equal(t, *block.Transactions[0], actual.Transaction) - assert.Equal(t, 9, actual.Confirmations) + assert.Equal(t, 8, actual.Confirmations) assert.Equal(t, TXHash, actual.Transaction.Hash()) }) diff --git a/pkg/rpc/server/testdata/testblocks.acc b/pkg/rpc/server/testdata/testblocks.acc index 444446512145627acecfbd90cb2d7782fe79d6bf..1d0bc04ede489d2e417c2f34643f8265cb7f1a7f 100644 GIT binary patch delta 1975 zcmV;o2T1thI^sGk2LJ#7p9KH_00000aEnF_F_l7&+x*9%!R-4>1F(pJ*}Mq_Z6j_B zHS|ISkt`qtk;ddgkx(FiH)ZmhhNg*`f@sf)Yi1W4{;o@W=|3h7l3`6BiiVhx?b*Bx zUlhg>b|H~HCVxeaa`H9Nm*_nD@~3V;XI>Vs3_w=Bmd$9@okYp9omIYk8$JB-Jw8rF z<@)DT!R^(Z2{XWe=m9xnK<^xHRJ{GgexVIg*NVabc$&(@cmf@L#olG9a;jf@& zsI^c;fLCEecY#B#^yE2X_PhPmIFMNCe%daQ#^geF0RR93le`cce=|zz@c)x``fG+Z zMXImIs8S{smPjZL(Nl^V#pmS~ZRZLsiuJeEEFvGnyBXU`=h%YEZOWcV4b@+-bOh$`>-pOX%CPR(;@@60~S8Ee-U=9*hhQc~lxE9|W|i|}b= z$+EVHFj$5C#&jJ{MS3vg3_wRop|+0le7eKCbI{dd#w_kV2?(H<&)d3=w>OCZS`N0P zg0{+o-e#bEtA|ACE3pX;U}V%f!WRcY0Y_Azi@KAs8laPR2db0k3BZ#s2?~>=2}QFZ z2zUXL>l=)-Gz%mGK*gLVROx6b6Hm9OWQ$np=5S|c6!aP(aTwcZp!t^XcXgGue4%Yh zhQHL~LV4|hl3$=4x(QjgjYfN}9(W`om6JgbDk!A_00000004K*F6m{~F-2|=L1aba zPVnAsV_=oQyQ~uQQ_AAt|<=2(1uF?N#6^iY#vCUrPmuCs=EqI!|^nNLZh* zF;C=;X6eA##}9wUM2nGsl%~QQ{jq+qBn0Qh^{4RS(~EedMPUMt{q>Z_R4ocC~h&oC<-E#p-tgvX&vxUl_J6j27=;eenAv0^Os0T*` z8cJ)tRPeLOq9?H9URp{Z;ga-R^E5@W(~Jy2LW&Vsse5&jlEfs<-H7JMU)^qS|J}f~ z%@U#S-}wiLPuIcq8bQ-6;E=~k`RSh{KpOTX1KagRld&40lRFasAm5c; zAf}7Zt`%zle9VkZ`}YCx4Ylsim?ha-q`Q(I(vw;fAOw}hl2zp+Yr&5`v*h6fD$+>@k)Y3|25%UsW0OB$MW*cK$U*j70AF%c3 zG!nK9K)>$eZxXHwP$@@)s5^$GL%9|0Pi`P1T&F7QaiZ-6r)S(ph*Y@LUL1|xSAZ*h z<3_t_imkd!i|t0ZEqMz5(TP zJX~+fmxVE`h=j3LubNF8)?Eh_dOWl5Q9_P?W%s`*0;dsy8dx%!f7O0@jk)5a-fUw$ zAF5=U20)g^i#+YW7hZZqf(RerHLQ)>OdF4PUmIOwQxJA8G6BV7mB* zZYgDKe5ey+ypypSpp%Oj{vhRl^0mZkO%xld_oAchbfms>QYRt!>I`HM?az?f+=iPC3_t`~(4|()z4bA19>>0t8LsDJbJA87cacv> zZzy3et_iy6KTe-lK3tQe-ir(pv&-VS<88vwal;Z?Ju3f2Kf;X+K!|vA8s`nWSv8!) z{7Mj4r9}$Li;eDGAdOj6Z`J`ZV(%8NedV$}dA11!K73*s9a@OO&jAv(?)H+e%JXTo JXS1;yu>x~mvfKaw delta 1975 zcmV;o2T1thI^sGk2LJ#7p9KH_00000z}iq6c3sSOsPd?MK?cvjA)om~+-A6YB8=;m zcvqIBkt`qtp^!Qlkx(FiUaSfiB^J3s?<>y5#Yt7Ot%0J||N6>E5oo51`K>7n^BEBS z?>cLap7sI?42X3w+t-Jjpvd0JBeoyh6i7)+3_xm3j{-&?lKEGwgzC^SXqyHh zO)}UVkM6QQcP-D$^E3kPQ}re&{i7g2M6pUHWdc<&yYv>zMxdKTb_J^pK!l-%W&_qT zSmnYk)GeUot%>*+$pTd~uv=l>-5xJ&%n^4@1)V_`27MMP^x0ggmtfj?Fp zY%CdD8I!RZm9xnK<^xId9Y2Ff+YaA|!GU?twPxQkT>|dtz=7EfRdyd?9;lK8kjMc! z0)*4;F{|S#vV}D{9zF0KVa$iYzl$4@o_OYplsXu80RR93le`cce+K}%fn*~o%yxD> zV7D()XB!fq(&s_vT_;&5qY#xpT)Uk*b1{2Zi;X+T-L_l{>QtWb;~GD5r)@2@UoN>n zI1E5Mo&k{36~E(Wj*`Y zAqc>e>l=)-Gz%mGKzEpNJDIsnsn#BKFzkT*jvS9m0H;4hV%se4Yk#m92|^o05I4Xu zq#a!hXA!eHMOKbXE23?v1|9iuTF8RxR+B*xDk!A_00000002tkS-KexUS*$!*>D=_ z$YPLae~gh8I$oHYrTnNZW@VG=4Il)IlsXub4h|rHNc2;ex;C@o?Z7ym+ww6=pIj(t z*BNRWUJN{gpdmu}kbN(7M;v^Q?>#s^Lr?z5gN79teQk-f^AqcS3Z5C23_z%9B77uT zHv3XUZIS0~mZjr~7nVOs$^eJN8RDJb9(eeowiFBY%Fe~b-?1i!BLIvA6;5FmfN zXCxYWT4lA|kyU2>&|^{Ib-8!ACHxdG`+&H0^E`<_dA4z-xrPJf3YA7Kb2uY|n!uIZr9SS5L>UyfPO70%+v^BA7s@*6fO<>#32+Ac2 zmCXt8J12+{{p|c71B{kdl}WrMl5!Cn(LX9WQhcK{zaDf>vyE;4ld&40lRFasAd&Gp zR<j!t75F{xm82@u~S+;OMi{5vep=XQp5*u z(dknRK=T!7%%-?Q0YzCId^G?ZfL{Yx^<_nQcM1$ZZQNwU+CZ#idR}r&rgF4n&Z27-5QYsp;m~};^5nY6 zc4Ci2oO3r0y;8!1_42t0ZIC}<_=^i zDcq>t4h4P)C(T2=ouW~sm~p>26zh*i(QuA`W%s`*0;dsy8dx%!f7O0@jk)5a-fUw$ zAF5=U20)IKIv92V000J)ybv3IPbQS21TLSVJL|IPs;+ztYI2weVXGufC$WwcWqtRH z4OD@`)1aVg%RloGEiMd-^AqBSHhiCtF3q7Dl zc&;e72-!<_eQz_WNC~+F>PhvEiScl5K{Y!Ez%?!rLxzmG$Q$2-C;@6kUdR3nK-_(N z#pg1^kv3~nm|!@Jpb+_Ao@m4g0Ji1^D8M0?vsjXn?RYp7vn$hZ=TQ7)j3C9$LAnt9 zG||awYMp}(6O*wTpp%Oj{vdf5*gb@xl7d6o*@@hKv7X&gMi~4p3&gV0VpWAtzeSU& z86X6YlsXub%^4tnmo+S^!A^r-s#^;w?Fe4xJQ_XgbkK|1?M?XPhwONPuQ%)+f?W|m z3wLF!OCCO;w0_%n5r_B?@nLk#IW=@C3_y!%|MKtrpSUTn;+~;%@i!)rM7P(w{WLnwo;f4s&>EL26HE2WUvr!z`&~ql2S$wvwK}#KE JqO-9Yu>zp>ojU*k From ab12eee346ff472b750d1872523b22eb5ec19402 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Sun, 13 Dec 2020 18:26:35 +0300 Subject: [PATCH 03/14] native: move contract deployment to management contract See neo-project/neo#2119. --- cli/smartcontract/smart_contract.go | 13 +- cli/testdata/deploy/main.go | 16 +- examples/timer/timer.go | 10 +- internal/testchain/transaction.go | 17 +- pkg/compiler/debug_test.go | 4 +- pkg/compiler/interop_test.go | 31 +- pkg/compiler/syscall.go | 4 - pkg/core/blockchain.go | 13 +- pkg/core/blockchain_test.go | 8 +- pkg/core/blockchainer/blockchainer.go | 1 + pkg/core/dao/cacheddao.go | 33 +- pkg/core/dao/cacheddao_test.go | 41 --- pkg/core/dao/dao.go | 82 ++--- pkg/core/dao/dao_test.go | 40 --- pkg/core/gas_price.go | 3 - pkg/core/helper_test.go | 8 +- pkg/core/interop/callback/method.go | 7 +- pkg/core/interop/context.go | 13 +- pkg/core/interop/contract/call.go | 6 +- pkg/core/interop/interopnames/names.go | 8 - pkg/core/interop/runtime/witness.go | 9 +- pkg/core/interop_neo.go | 151 -------- pkg/core/interop_neo_test.go | 2 +- pkg/core/interop_system.go | 64 +--- pkg/core/interop_system_test.go | 463 ++++++------------------- pkg/core/interops.go | 9 +- pkg/core/native/management.go | 318 ++++++++++++++++- pkg/core/native/management_test.go | 63 ++++ pkg/core/native/native_nep17.go | 7 +- pkg/core/native/notary.go | 2 +- pkg/core/native/oracle.go | 4 +- pkg/core/native/util.go | 9 + pkg/core/native_contract_test.go | 18 +- pkg/core/native_management_test.go | 362 +++++++++++++++++++ pkg/core/native_neo_test.go | 4 +- pkg/core/native_oracle_test.go | 2 +- pkg/core/state/contract.go | 90 ++++- pkg/core/state/contract_test.go | 41 +++ pkg/core/storage/store.go | 2 - pkg/core/storage/store_test.go | 2 - pkg/interop/blockchain/blockchain.go | 9 - pkg/interop/contract/contract.go | 33 -- pkg/network/helper_test.go | 3 + pkg/rpc/request/txBuilder.go | 22 -- pkg/rpc/server/server_test.go | 4 +- pkg/rpc/server/testdata/testblocks.acc | Bin 7522 -> 7586 bytes scripts/gendump/main.go | 2 +- 47 files changed, 1135 insertions(+), 918 deletions(-) create mode 100644 pkg/core/native/management_test.go create mode 100644 pkg/core/native_management_test.go diff --git a/cli/smartcontract/smart_contract.go b/cli/smartcontract/smart_contract.go index dc9205953..b9ccc3032 100644 --- a/cli/smartcontract/smart_contract.go +++ b/cli/smartcontract/smart_contract.go @@ -20,13 +20,14 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" - "github.com/nspcc-dev/neo-go/pkg/rpc/request" + "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/rpc/response/result" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm" + "github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/urfave/cli" "gopkg.in/yaml.v2" @@ -770,10 +771,16 @@ func contractDeploy(ctx *cli.Context) error { return err } - txScript, err := request.CreateDeploymentScript(&nefFile, m) + mgmtHash, err := c.GetNativeContractHash("Neo Contract Management") if err != nil { - return cli.NewExitError(fmt.Errorf("failed to create deployment script: %w", err), 1) + return cli.NewExitError(fmt.Errorf("failed to get management contract's hash: %w", err), 1) } + buf := io.NewBufBinWriter() + emit.AppCallWithOperationAndArgs(buf.BinWriter, mgmtHash, "deploy", f, manifestBytes) + if buf.Err != nil { + return cli.NewExitError(fmt.Errorf("failed to create deployment script: %w", buf.Err), 1) + } + txScript := buf.Bytes() // It doesn't require any signers. invRes, err := c.InvokeScript(txScript, nil) if err == nil && invRes.FaultException != "" { diff --git a/cli/testdata/deploy/main.go b/cli/testdata/deploy/main.go index 6182751c9..525d932d9 100644 --- a/cli/testdata/deploy/main.go +++ b/cli/testdata/deploy/main.go @@ -2,18 +2,28 @@ package deploy import ( "github.com/nspcc-dev/neo-go/cli/testdata/deploy/sub" + "github.com/nspcc-dev/neo-go/pkg/interop" "github.com/nspcc-dev/neo-go/pkg/interop/contract" + "github.com/nspcc-dev/neo-go/pkg/interop/runtime" "github.com/nspcc-dev/neo-go/pkg/interop/storage" ) var key = "key" +const mgmtKey = "mgmt" + func _deploy(isUpdate bool) { + var value string + ctx := storage.GetContext() - value := "on create" if isUpdate { value = "on update" + } else { + value = "on create" + sh := runtime.GetCallingScriptHash() + storage.Put(ctx, mgmtKey, sh) } + storage.Put(ctx, key, value) } @@ -24,7 +34,9 @@ func Fail() { // Update updates contract with the new one. func Update(script, manifest []byte) { - contract.Update(script, manifest) + ctx := storage.GetReadOnlyContext() + mgmt := storage.Get(ctx, mgmtKey).(interop.Hash160) + contract.Call(mgmt, "update", script, manifest) } // GetValue returns stored value. diff --git a/examples/timer/timer.go b/examples/timer/timer.go index 0b0839df2..1d58a20fe 100644 --- a/examples/timer/timer.go +++ b/examples/timer/timer.go @@ -1,6 +1,7 @@ package timer import ( + "github.com/nspcc-dev/neo-go/pkg/interop" "github.com/nspcc-dev/neo-go/pkg/interop/binary" "github.com/nspcc-dev/neo-go/pkg/interop/contract" "github.com/nspcc-dev/neo-go/pkg/interop/runtime" @@ -9,6 +10,7 @@ import ( ) const defaultTicks = 3 +const mgmtKey = "mgmt" var ( // ctx holds storage context for contract methods @@ -30,6 +32,8 @@ func _deploy(isUpdate bool) { runtime.Log("One more tick is added.") return } + sh := runtime.GetCallingScriptHash() + storage.Put(ctx, mgmtKey, sh) storage.Put(ctx, ticksKey, defaultTicks) i := binary.Itoa(defaultTicks, 10) runtime.Log("Timer set to " + i + " ticks.") @@ -41,7 +45,8 @@ func Migrate(script []byte, manifest []byte) bool { runtime.Log("Only owner is allowed to update the contract.") return false } - contract.Update(script, manifest) + mgmt := storage.Get(ctx, mgmtKey).(interop.Hash160) + contract.Call(mgmt, "update", script, manifest) runtime.Log("Contract updated.") return true } @@ -67,7 +72,8 @@ func SelfDestroy() bool { runtime.Log("Only owner or the contract itself are allowed to destroy the contract.") return false } - contract.Destroy() + mgmt := storage.Get(ctx, mgmtKey).(interop.Hash160) + contract.Call(mgmt, "destroy") runtime.Log("Destroyed.") return true } diff --git a/internal/testchain/transaction.go b/internal/testchain/transaction.go index 10e71529e..044f82d82 100644 --- a/internal/testchain/transaction.go +++ b/internal/testchain/transaction.go @@ -1,6 +1,7 @@ package testchain import ( + "encoding/json" gio "io" "github.com/nspcc-dev/neo-go/pkg/compiler" @@ -12,7 +13,6 @@ import ( "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/io" - "github.com/nspcc-dev/neo-go/pkg/rpc/request" "github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/emit" @@ -48,7 +48,7 @@ func NewTransferFromOwner(bc blockchainer.Blockchainer, contractHash, to util.Ui } // NewDeployTx returns new deployment tx for contract with name with Go code read from r. -func NewDeployTx(name string, sender util.Uint160, r gio.Reader) (*transaction.Transaction, util.Uint160, error) { +func NewDeployTx(bc blockchainer.Blockchainer, name string, sender util.Uint160, r gio.Reader) (*transaction.Transaction, util.Uint160, error) { // nef.NewFile() cares about version a lot. config.Version = "0.90.0-test" @@ -67,12 +67,21 @@ func NewDeployTx(name string, sender util.Uint160, r gio.Reader) (*transaction.T return nil, util.Uint160{}, err } - txScript, err := request.CreateDeploymentScript(ne, m) + rawManifest, err := json.Marshal(m) if err != nil { return nil, util.Uint160{}, err } + neb, err := ne.Bytes() + if err != nil { + return nil, util.Uint160{}, err + } + buf := io.NewBufBinWriter() + emit.AppCallWithOperationAndArgs(buf.BinWriter, bc.ManagementContractHash(), "deploy", neb, rawManifest) + if buf.Err != nil { + return nil, util.Uint160{}, buf.Err + } - tx := transaction.New(Network(), txScript, 100*native.GASFactor) + tx := transaction.New(Network(), buf.Bytes(), 100*native.GASFactor) tx.Signers = []transaction.Signer{{Account: sender}} h := state.CreateContractHash(tx.Sender(), avm) diff --git a/pkg/compiler/debug_test.go b/pkg/compiler/debug_test.go index fcced5851..506142f6d 100644 --- a/pkg/compiler/debug_test.go +++ b/pkg/compiler/debug_test.go @@ -17,7 +17,6 @@ func TestCodeGen_DebugInfo(t *testing.T) { import "github.com/nspcc-dev/neo-go/pkg/interop" import "github.com/nspcc-dev/neo-go/pkg/interop/storage" import "github.com/nspcc-dev/neo-go/pkg/interop/blockchain" - import "github.com/nspcc-dev/neo-go/pkg/interop/contract" func Main(op string) bool { var s string _ = s @@ -47,7 +46,7 @@ func MethodStruct() struct{} { return struct{}{} } func unexportedMethod() int { return 1 } func MethodParams(addr interop.Hash160, h interop.Hash256, sig interop.Signature, pub interop.PublicKey, - inter interop.Interface, ctr contract.Contract, + inter interop.Interface, ctx storage.Context, tx blockchain.Transaction) bool { return true } @@ -238,7 +237,6 @@ func _deploy(isUpdate bool) {} manifest.NewParameter("sig", smartcontract.SignatureType), manifest.NewParameter("pub", smartcontract.PublicKeyType), manifest.NewParameter("inter", smartcontract.InteropInterfaceType), - manifest.NewParameter("ctr", smartcontract.ArrayType), manifest.NewParameter("ctx", smartcontract.InteropInterfaceType), manifest.NewParameter("tx", smartcontract.ArrayType), }, diff --git a/pkg/compiler/interop_test.go b/pkg/compiler/interop_test.go index eb81b717b..ee618b999 100644 --- a/pkg/compiler/interop_test.go +++ b/pkg/compiler/interop_test.go @@ -1,6 +1,7 @@ package compiler_test import ( + "errors" "fmt" "math/big" "strings" @@ -18,6 +19,7 @@ import ( cinterop "github.com/nspcc-dev/neo-go/pkg/interop" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" + "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/stretchr/testify/require" @@ -132,12 +134,6 @@ func TestAppCall(t *testing.T) { require.NoError(t, err) barH := hash.Hash160(barCtr) - ic := interop.NewContext(trigger.Application, nil, dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false), nil, nil, nil, zaptest.NewLogger(t)) - require.NoError(t, ic.DAO.PutContractState(&state.Contract{ - Hash: barH, - Script: barCtr, - Manifest: *mBar, - })) srcInner := `package foo import "github.com/nspcc-dev/neo-go/pkg/interop/contract" @@ -164,11 +160,24 @@ func TestAppCall(t *testing.T) { require.NoError(t, err) ih := hash.Hash160(inner) - require.NoError(t, ic.DAO.PutContractState(&state.Contract{ - Hash: ih, - Script: inner, - Manifest: *m, - })) + var contractGetter = func(_ dao.DAO, h util.Uint160) (*state.Contract, error) { + if h.Equals(ih) { + return &state.Contract{ + Hash: ih, + Script: inner, + Manifest: *m, + }, nil + } else if h.Equals(barH) { + return &state.Contract{ + Hash: barH, + Script: barCtr, + Manifest: *mBar, + }, nil + } + return nil, errors.New("not found") + } + + ic := interop.NewContext(trigger.Application, nil, dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false), contractGetter, nil, nil, nil, zaptest.NewLogger(t)) t.Run("valid script", func(t *testing.T) { src := getAppCallScript(fmt.Sprintf("%#v", ih.BytesBE())) diff --git a/pkg/compiler/syscall.go b/pkg/compiler/syscall.go index 964564582..abc5bebe0 100644 --- a/pkg/compiler/syscall.go +++ b/pkg/compiler/syscall.go @@ -16,7 +16,6 @@ var syscalls = map[string]map[string]string{ }, "blockchain": { "GetBlock": interopnames.SystemBlockchainGetBlock, - "GetContract": interopnames.SystemBlockchainGetContract, "GetHeight": interopnames.SystemBlockchainGetHeight, "GetTransaction": interopnames.SystemBlockchainGetTransaction, "GetTransactionFromBlock": interopnames.SystemBlockchainGetTransactionFromBlock, @@ -25,12 +24,9 @@ var syscalls = map[string]map[string]string{ "contract": { "Call": interopnames.SystemContractCall, "CallEx": interopnames.SystemContractCallEx, - "Create": interopnames.SystemContractCreate, "CreateStandardAccount": interopnames.SystemContractCreateStandardAccount, - "Destroy": interopnames.SystemContractDestroy, "IsStandard": interopnames.SystemContractIsStandard, "GetCallFlags": interopnames.SystemContractGetCallFlags, - "Update": interopnames.SystemContractUpdate, }, "crypto": { "ECDsaSecp256k1Verify": interopnames.NeoCryptoVerifyWithECDsaSecp256k1, diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 544294129..0604f1847 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -841,7 +841,7 @@ func (bc *Blockchain) processNEP17Transfer(cache *dao.Cached, h util.Uint256, b if nativeContract != nil { id = nativeContract.Metadata().ContractID } else { - assetContract, err := cache.GetContractState(sc) + assetContract, err := bc.contracts.Management.GetContract(cache, sc) if err != nil { return } @@ -1141,7 +1141,7 @@ func (bc *Blockchain) HeaderHeight() uint32 { // GetContractState returns contract by its script hash. func (bc *Blockchain) GetContractState(hash util.Uint160) *state.Contract { - contract, err := bc.dao.GetContractState(hash) + contract, err := bc.contracts.Management.GetContract(bc.dao, hash) if contract == nil && err != storage.ErrKeyNotFound { bc.log.Warn("failed to get contract state", zap.Error(err)) } @@ -1663,7 +1663,7 @@ func (bc *Blockchain) initVerificationVM(ic *interop.Context, hash util.Uint160, } v.LoadScriptWithFlags(witness.VerificationScript, smartcontract.NoneFlag) } else { - cs, err := ic.DAO.GetContractState(hash) + cs, err := ic.GetContract(hash) if err != nil { return ErrUnknownVerificationContract } @@ -1786,6 +1786,11 @@ func (bc *Blockchain) UtilityTokenHash() util.Uint160 { return bc.contracts.GAS.Hash } +// ManagementContractHash returns management contract's hash. +func (bc *Blockchain) ManagementContractHash() util.Uint160 { + return bc.contracts.Management.Hash +} + func hashAndIndexToBytes(h util.Uint256, index uint32) []byte { buf := io.NewBufBinWriter() buf.WriteBytes(h.BytesLE()) @@ -1794,7 +1799,7 @@ func hashAndIndexToBytes(h util.Uint256, index uint32) []byte { } func (bc *Blockchain) newInteropContext(trigger trigger.Type, d dao.DAO, block *block.Block, tx *transaction.Transaction) *interop.Context { - ic := interop.NewContext(trigger, bc, d, bc.contracts.Contracts, block, tx, bc.log) + ic := interop.NewContext(trigger, bc, d, bc.contracts.Management.GetContract, bc.contracts.Contracts, block, tx, bc.log) ic.Functions = [][]interop.Function{systemInterops, neoInterops} switch { case tx != nil: diff --git a/pkg/core/blockchain_test.go b/pkg/core/blockchain_test.go index 785c18e79..ea3fbb437 100644 --- a/pkg/core/blockchain_test.go +++ b/pkg/core/blockchain_test.go @@ -1030,10 +1030,10 @@ func TestVerifyHashAgainstScript(t *testing.T) { bc := newTestChain(t) defer bc.Close() - cs, csInvalid := getTestContractState() + cs, csInvalid := getTestContractState(bc) ic := bc.newInteropContext(trigger.Verification, bc.dao, nil, nil) - require.NoError(t, ic.DAO.PutContractState(cs)) - require.NoError(t, ic.DAO.PutContractState(csInvalid)) + require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs)) + require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, csInvalid)) gas := bc.contracts.Policy.GetMaxVerificationGas(ic.DAO) t.Run("Contract", func(t *testing.T) { @@ -1169,7 +1169,7 @@ func TestIsTxStillRelevant(t *testing.T) { currentHeight := blockchain.GetHeight() return currentHeight < %d }`, bc.BlockHeight()+2) // deploy + next block - txDeploy, h, err := testchain.NewDeployTx("TestVerify", neoOwner, strings.NewReader(src)) + txDeploy, h, err := testchain.NewDeployTx(bc, "TestVerify", neoOwner, strings.NewReader(src)) require.NoError(t, err) txDeploy.ValidUntilBlock = bc.BlockHeight() + 1 addSigners(txDeploy) diff --git a/pkg/core/blockchainer/blockchainer.go b/pkg/core/blockchainer/blockchainer.go index bcbc2c780..0bbd90fe8 100644 --- a/pkg/core/blockchainer/blockchainer.go +++ b/pkg/core/blockchainer/blockchainer.go @@ -58,6 +58,7 @@ type Blockchainer interface { GetTestVM(tx *transaction.Transaction, b *block.Block) *vm.VM GetTransaction(util.Uint256) (*transaction.Transaction, uint32, error) mempool.Feer // fee interface + ManagementContractHash() util.Uint160 PoolTx(t *transaction.Transaction, pools ...*mempool.Pool) error PoolTxWithData(t *transaction.Transaction, data interface{}, mp *mempool.Pool, feer mempool.Feer, verificationFunction func(bc Blockchainer, t *transaction.Transaction, data interface{}) error) error RegisterPostBlock(f func(Blockchainer, *mempool.Pool, *block.Block)) diff --git a/pkg/core/dao/cacheddao.go b/pkg/core/dao/cacheddao.go index 037dc64a5..32e5019b5 100644 --- a/pkg/core/dao/cacheddao.go +++ b/pkg/core/dao/cacheddao.go @@ -9,11 +9,10 @@ import ( ) // Cached is a data access object that mimics DAO, but has a write cache -// for accounts and read cache for contracts. These are the most frequently used +// for accounts and NEP17 transfer data. These are the most frequently used // objects in the storeBlock(). type Cached struct { DAO - contracts map[util.Uint160]*state.Contract balances map[util.Uint160]*state.NEP17Balances transfers map[util.Uint160]map[uint32]*state.NEP17TransferLog @@ -22,34 +21,9 @@ type Cached struct { // NewCached returns new Cached wrapping around given backing store. func NewCached(d DAO) *Cached { - ctrs := make(map[util.Uint160]*state.Contract) balances := make(map[util.Uint160]*state.NEP17Balances) transfers := make(map[util.Uint160]map[uint32]*state.NEP17TransferLog) - return &Cached{d.GetWrapped(), ctrs, balances, transfers, false} -} - -// GetContractState returns contract state from cache or underlying store. -func (cd *Cached) GetContractState(hash util.Uint160) (*state.Contract, error) { - if cd.contracts[hash] != nil { - return cd.contracts[hash], nil - } - cs, err := cd.DAO.GetContractState(hash) - if err == nil { - cd.contracts[hash] = cs - } - return cs, err -} - -// PutContractState puts given contract state into the given store. -func (cd *Cached) PutContractState(cs *state.Contract) error { - cd.contracts[cs.Hash] = cs - return cd.DAO.PutContractState(cs) -} - -// DeleteContractState deletes given contract state in cache and backing store. -func (cd *Cached) DeleteContractState(hash util.Uint160) error { - cd.contracts[hash] = nil - return cd.DAO.DeleteContractState(hash) + return &Cached{d.GetWrapped(), balances, transfers, false} } // GetNEP17Balances retrieves NEP17Balances for the acc. @@ -105,7 +79,7 @@ func (cd *Cached) Persist() (int, error) { // If the lower DAO is Cached, we only need to flush the MemCached DB. // This actually breaks DAO interface incapsulation, but for our current // usage scenario it should be good enough if cd doesn't modify object - // caches (accounts/contracts/etc) in any way. + // caches (accounts/transfer data) in any way. if ok { if cd.dropNEP17Cache { lowerCache.balances = make(map[util.Uint160]*state.NEP17Balances) @@ -145,7 +119,6 @@ func (cd *Cached) Persist() (int, error) { // GetWrapped implements DAO interface. func (cd *Cached) GetWrapped() DAO { return &Cached{cd.DAO.GetWrapped(), - cd.contracts, cd.balances, cd.transfers, false, diff --git a/pkg/core/dao/cacheddao_test.go b/pkg/core/dao/cacheddao_test.go index f38987e85..ee8211ade 100644 --- a/pkg/core/dao/cacheddao_test.go +++ b/pkg/core/dao/cacheddao_test.go @@ -7,51 +7,10 @@ import ( "github.com/nspcc-dev/neo-go/pkg/config/netmode" "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/smartcontract/manifest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestCachedDaoContracts(t *testing.T) { - store := storage.NewMemoryStore() - pdao := NewSimple(store, netmode.UnitTestNet, false) - dao := NewCached(pdao) - - script := []byte{0xde, 0xad, 0xbe, 0xef} - sh := hash.Hash160(script) - _, err := dao.GetContractState(sh) - require.NotNil(t, err) - - m := manifest.NewManifest("Test") - - cs := &state.Contract{ - ID: 123, - Hash: sh, - Script: script, - Manifest: *m, - } - - require.NoError(t, dao.PutContractState(cs)) - cs2, err := dao.GetContractState(sh) - require.Nil(t, err) - require.Equal(t, cs, cs2) - - _, err = dao.Persist() - require.Nil(t, err) - dao2 := NewCached(pdao) - cs2, err = dao2.GetContractState(sh) - require.Nil(t, err) - require.Equal(t, cs, cs2) - - require.NoError(t, dao.DeleteContractState(sh)) - cs2, err = dao2.GetContractState(sh) - require.Nil(t, err) - require.Equal(t, cs, cs2) - _, err = dao.GetContractState(sh) - require.NotNil(t, err) -} - func TestCachedCachedDao(t *testing.T) { store := storage.NewMemoryStore() // Persistent DAO to check for backing storage. diff --git a/pkg/core/dao/dao.go b/pkg/core/dao/dao.go index d7af83996..40911e227 100644 --- a/pkg/core/dao/dao.go +++ b/pkg/core/dao/dao.go @@ -33,13 +33,12 @@ type DAO interface { AppendAppExecResult(aer *state.AppExecResult, buf *io.BufBinWriter) error AppendNEP17Transfer(acc util.Uint160, index uint32, tr *state.NEP17Transfer) (bool, error) DeleteBlock(h util.Uint256, buf *io.BufBinWriter) error - DeleteContractState(hash util.Uint160) error + DeleteContractID(id int32) error DeleteStorageItem(id int32, key []byte) error GetAndDecode(entity io.Serializable, key []byte) error GetAppExecResults(hash util.Uint256, trig trigger.Type) ([]state.AppExecResult, error) GetBatch() *storage.MemBatch GetBlock(hash util.Uint256) (*block.Block, error) - GetContractState(hash util.Uint160) (*state.Contract, error) GetContractScriptHash(id int32) (util.Uint160, error) GetCurrentBlockHeight() (uint32, error) GetCurrentHeaderHeight() (i uint32, h util.Uint256, err error) @@ -47,7 +46,6 @@ type DAO interface { GetHeaderHashes() ([]util.Uint256, error) GetNEP17Balances(acc util.Uint160) (*state.NEP17Balances, error) GetNEP17TransferLog(acc util.Uint160, index uint32) (*state.NEP17TransferLog, error) - GetAndUpdateNextContractID() (int32, error) GetStateRoot(height uint32) (*state.MPTRootState, error) PutStateRoot(root *state.MPTRootState) error GetStorageItem(id int32, key []byte) *state.StorageItem @@ -59,7 +57,7 @@ type DAO interface { HasTransaction(hash util.Uint256) error Persist() (int, error) PutAppExecResult(aer *state.AppExecResult, buf *io.BufBinWriter) error - PutContractState(cs *state.Contract) error + PutContractID(id int32, hash util.Uint160) error PutCurrentHeader(hashAndIndex []byte) error PutNEP17Balances(acc util.Uint160, bs *state.NEP17Balances) error PutNEP17TransferLog(acc util.Uint160, index uint32, lg *state.NEP17TransferLog) error @@ -125,72 +123,32 @@ func (dao *Simple) putWithBuffer(entity io.Serializable, key []byte, buf *io.Buf return dao.Store.Put(key, buf.Bytes()) } -// -- start contracts. - -// GetContractState returns contract state as recorded in the given -// store by the given script hash. -func (dao *Simple) GetContractState(hash util.Uint160) (*state.Contract, error) { - contract := &state.Contract{} - key := storage.AppendPrefix(storage.STContract, hash.BytesBE()) - err := dao.GetAndDecode(contract, key) - if err != nil { - return nil, err - } - - return contract, nil -} - -// PutContractState puts given contract state into the given store. -func (dao *Simple) PutContractState(cs *state.Contract) error { - key := storage.AppendPrefix(storage.STContract, cs.Hash.BytesBE()) - if err := dao.Put(cs, key); err != nil { - return err - } - if cs.UpdateCounter != 0 { // Update. - return nil - } - key = key[:5] - key[0] = byte(storage.STContractID) - binary.LittleEndian.PutUint32(key[1:], uint32(cs.ID)) - return dao.Store.Put(key, cs.Hash.BytesBE()) -} - -// DeleteContractState deletes given contract state in the given store. -func (dao *Simple) DeleteContractState(hash util.Uint160) error { - key := storage.AppendPrefix(storage.STContract, hash.BytesBE()) - return dao.Store.Delete(key) -} - -// GetAndUpdateNextContractID returns id for the next contract and increases stored ID. -func (dao *Simple) GetAndUpdateNextContractID() (int32, error) { - var id = int32(1) - key := storage.SYSContractID.Bytes() - data, err := dao.Store.Get(key) - if err == nil { - id = int32(binary.LittleEndian.Uint32(data)) - } else if err != storage.ErrKeyNotFound { - return 0, err - } - data = make([]byte, 4) - binary.LittleEndian.PutUint32(data, uint32(id+1)) - return id, dao.Store.Put(key, data) -} - -// GetContractScriptHash returns script hash of the contract with the specified ID. -// Contract with the script hash may be destroyed. -func (dao *Simple) GetContractScriptHash(id int32) (util.Uint160, error) { +func makeContractIDKey(id int32) []byte { key := make([]byte, 5) key[0] = byte(storage.STContractID) binary.LittleEndian.PutUint32(key[1:], uint32(id)) - data := &util.Uint160{} - if err := dao.GetAndDecode(data, key); err != nil { + return key +} + +// DeleteContractID deletes contract's id to hash mapping. +func (dao *Simple) DeleteContractID(id int32) error { + return dao.Store.Delete(makeContractIDKey(id)) +} + +// PutContractID adds a mapping from contract's ID to its hash. +func (dao *Simple) PutContractID(id int32, hash util.Uint160) error { + return dao.Store.Put(makeContractIDKey(id), hash.BytesBE()) +} + +// GetContractScriptHash retrieves contract's hash given its ID. +func (dao *Simple) GetContractScriptHash(id int32) (util.Uint160, error) { + var data = new(util.Uint160) + if err := dao.GetAndDecode(data, makeContractIDKey(id)); err != nil { return *data, err } return *data, nil } -// -- end contracts. - // -- start nep17 balances. // GetNEP17Balances retrieves nep17 balances from the cache. diff --git a/pkg/core/dao/dao_test.go b/pkg/core/dao/dao_test.go index b3971c74c..97b1ddb54 100644 --- a/pkg/core/dao/dao_test.go +++ b/pkg/core/dao/dao_test.go @@ -10,7 +10,6 @@ import ( "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" - "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/trigger" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" @@ -43,45 +42,6 @@ func (t *TestSerializable) DecodeBinary(reader *io.BinReader) { t.field = reader.ReadString() } -func TestPutAndGetContractState(t *testing.T) { - dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false) - script := []byte{} - h := hash.Hash160(script) - contractState := &state.Contract{Hash: h, Script: script} - err := dao.PutContractState(contractState) - require.NoError(t, err) - gotContractState, err := dao.GetContractState(contractState.Hash) - require.NoError(t, err) - require.Equal(t, contractState, gotContractState) -} - -func TestDeleteContractState(t *testing.T) { - dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false) - script := []byte{} - h := hash.Hash160(script) - contractState := &state.Contract{Hash: h, Script: script} - err := dao.PutContractState(contractState) - require.NoError(t, err) - err = dao.DeleteContractState(h) - require.NoError(t, err) - gotContractState, err := dao.GetContractState(h) - require.Error(t, err) - require.Nil(t, gotContractState) -} - -func TestSimple_GetAndUpdateNextContractID(t *testing.T) { - dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false) - id, err := dao.GetAndUpdateNextContractID() - require.NoError(t, err) - require.EqualValues(t, 1, id) - id, err = dao.GetAndUpdateNextContractID() - require.NoError(t, err) - require.EqualValues(t, 2, id) - id, err = dao.GetAndUpdateNextContractID() - require.NoError(t, err) - require.EqualValues(t, 3, id) -} - func TestPutGetAppExecResult(t *testing.T) { dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false) hash := random.Uint256() diff --git a/pkg/core/gas_price.go b/pkg/core/gas_price.go index e8641bc1a..b5ead6426 100644 --- a/pkg/core/gas_price.go +++ b/pkg/core/gas_price.go @@ -6,9 +6,6 @@ import ( "github.com/nspcc-dev/neo-go/pkg/vm/opcode" ) -// StoragePrice is a price for storing 1 byte of storage. -const StoragePrice = 100000 - // getPrice returns a price for executing op with the provided parameter. func getPrice(v *vm.VM, op opcode.Opcode, parameter []byte) int64 { return fee.Opcode(op) diff --git a/pkg/core/helper_test.go b/pkg/core/helper_test.go index 653c9d2a4..76d91f05f 100644 --- a/pkg/core/helper_test.go +++ b/pkg/core/helper_test.go @@ -271,7 +271,7 @@ func initBasicChain(t *testing.T, bc *Blockchain) { acc0 := wallet.NewAccountFromPrivateKey(priv0) // Push some contract into the chain. - txDeploy, cHash := newDeployTx(t, priv0ScriptHash, prefix+"test_contract.go", "Rubl") + txDeploy, cHash := newDeployTx(t, bc, priv0ScriptHash, prefix+"test_contract.go", "Rubl") txDeploy.Nonce = getNextNonce() txDeploy.ValidUntilBlock = validUntilBlock require.NoError(t, addNetworkFee(bc, txDeploy, acc0)) @@ -357,7 +357,7 @@ func initBasicChain(t *testing.T, bc *Blockchain) { t.Logf("sendRublesTx: %v", transferTx.Hash().StringLE()) // Push verification contract into the chain. - txDeploy2, _ := newDeployTx(t, priv0ScriptHash, prefix+"verification_contract.go", "Verify") + txDeploy2, _ := newDeployTx(t, bc, priv0ScriptHash, prefix+"verification_contract.go", "Verify") txDeploy2.Nonce = getNextNonce() txDeploy2.ValidUntilBlock = validUntilBlock require.NoError(t, addNetworkFee(bc, txDeploy2, acc0)) @@ -375,10 +375,10 @@ func newNEP17Transfer(sc, from, to util.Uint160, amount int64, additionalArgs .. return transaction.New(testchain.Network(), script, 10000000) } -func newDeployTx(t *testing.T, sender util.Uint160, name, ctrName string) (*transaction.Transaction, util.Uint160) { +func newDeployTx(t *testing.T, bc *Blockchain, sender util.Uint160, name, ctrName string) (*transaction.Transaction, util.Uint160) { c, err := ioutil.ReadFile(name) require.NoError(t, err) - tx, h, err := testchain.NewDeployTx(ctrName, sender, bytes.NewReader(c)) + tx, h, err := testchain.NewDeployTx(bc, ctrName, sender, bytes.NewReader(c)) require.NoError(t, err) t.Logf("contractHash (%s): %s", name, h.StringLE()) return tx, h diff --git a/pkg/core/interop/callback/method.go b/pkg/core/interop/callback/method.go index ada8e2d41..4c6764a89 100644 --- a/pkg/core/interop/callback/method.go +++ b/pkg/core/interop/callback/method.go @@ -2,6 +2,7 @@ package callback import ( "errors" + "fmt" "strings" "github.com/nspcc-dev/neo-go/pkg/core/interop" @@ -39,15 +40,15 @@ func CreateFromMethod(ic *interop.Context) error { if err != nil { return err } - cs, err := ic.DAO.GetContractState(h) + cs, err := ic.GetContract(h) if err != nil { - return errors.New("contract not found") + return fmt.Errorf("contract not found: %w", err) } method := string(ic.VM.Estack().Pop().Bytes()) if strings.HasPrefix(method, "_") { return errors.New("invalid method name") } - currCs, err := ic.DAO.GetContractState(ic.VM.GetCurrentScriptHash()) + currCs, err := ic.GetContract(ic.VM.GetCurrentScriptHash()) if err == nil && !currCs.Manifest.CanCall(h, &cs.Manifest, method) { return errors.New("method call is not allowed") } diff --git a/pkg/core/interop/context.go b/pkg/core/interop/context.go index f1d858901..2e26d349f 100644 --- a/pkg/core/interop/context.go +++ b/pkg/core/interop/context.go @@ -37,10 +37,13 @@ type Context struct { Log *zap.Logger VM *vm.VM Functions [][]Function + getContract func(dao.DAO, util.Uint160) (*state.Contract, error) } // NewContext returns new interop context. -func NewContext(trigger trigger.Type, bc blockchainer.Blockchainer, d dao.DAO, natives []Contract, block *block.Block, tx *transaction.Transaction, log *zap.Logger) *Context { +func NewContext(trigger trigger.Type, bc blockchainer.Blockchainer, d dao.DAO, + getContract func(dao.DAO, util.Uint160) (*state.Contract, error), natives []Contract, + block *block.Block, tx *transaction.Transaction, log *zap.Logger) *Context { dao := dao.NewCached(d) nes := make([]state.NotificationEvent, 0) return &Context{ @@ -53,7 +56,8 @@ func NewContext(trigger trigger.Type, bc blockchainer.Blockchainer, d dao.DAO, n Notifications: nes, Log: log, // Functions is a slice of slices of interops sorted by ID. - Functions: [][]Function{}, + Functions: [][]Function{}, + getContract: getContract, } } @@ -142,6 +146,11 @@ func Sort(fs []Function) { sort.Slice(fs, func(i, j int) bool { return fs[i].ID < fs[j].ID }) } +// GetContract returns contract by its hash in current interop context. +func (ic *Context) GetContract(hash util.Uint160) (*state.Contract, error) { + return ic.getContract(ic.DAO, hash) +} + // GetFunction returns metadata for interop with the specified id. func (ic *Context) GetFunction(id uint32) *Function { for _, slice := range ic.Functions { diff --git a/pkg/core/interop/contract/call.go b/pkg/core/interop/contract/call.go index 6c6943c67..c828b6f54 100644 --- a/pkg/core/interop/contract/call.go +++ b/pkg/core/interop/contract/call.go @@ -39,9 +39,9 @@ func callExInternal(ic *interop.Context, h []byte, name string, args []stackitem if err != nil { return errors.New("invalid contract hash") } - cs, err := ic.DAO.GetContractState(u) + cs, err := ic.GetContract(u) if err != nil { - return errors.New("contract not found") + return fmt.Errorf("contract not found: %w", err) } if strings.HasPrefix(name, "_") { return errors.New("invalid method name (starts with '_')") @@ -53,7 +53,7 @@ func callExInternal(ic *interop.Context, h []byte, name string, args []stackitem if md.Safe { f &^= smartcontract.WriteStates } else if ctx := ic.VM.Context(); ctx != nil && ctx.IsDeployed() { - curr, err := ic.DAO.GetContractState(ic.VM.GetCurrentScriptHash()) + curr, err := ic.GetContract(ic.VM.GetCurrentScriptHash()) if err == nil { if !curr.Manifest.CanCall(u, &cs.Manifest, name) { return errors.New("disallowed method call") diff --git a/pkg/core/interop/interopnames/names.go b/pkg/core/interop/interopnames/names.go index ac60bca12..ae1167e2f 100644 --- a/pkg/core/interop/interopnames/names.go +++ b/pkg/core/interop/interopnames/names.go @@ -11,7 +11,6 @@ const ( SystemBinaryItoa = "System.Binary.Itoa" SystemBinarySerialize = "System.Binary.Serialize" SystemBlockchainGetBlock = "System.Blockchain.GetBlock" - SystemBlockchainGetContract = "System.Blockchain.GetContract" SystemBlockchainGetHeight = "System.Blockchain.GetHeight" SystemBlockchainGetTransaction = "System.Blockchain.GetTransaction" SystemBlockchainGetTransactionFromBlock = "System.Blockchain.GetTransactionFromBlock" @@ -22,14 +21,11 @@ const ( SystemCallbackInvoke = "System.Callback.Invoke" SystemContractCall = "System.Contract.Call" SystemContractCallEx = "System.Contract.CallEx" - SystemContractCreate = "System.Contract.Create" SystemContractCreateStandardAccount = "System.Contract.CreateStandardAccount" - SystemContractDestroy = "System.Contract.Destroy" SystemContractIsStandard = "System.Contract.IsStandard" SystemContractGetCallFlags = "System.Contract.GetCallFlags" SystemContractNativeOnPersist = "System.Contract.NativeOnPersist" SystemContractNativePostPersist = "System.Contract.NativePostPersist" - SystemContractUpdate = "System.Contract.Update" SystemEnumeratorConcat = "System.Enumerator.Concat" SystemEnumeratorCreate = "System.Enumerator.Create" SystemEnumeratorNext = "System.Enumerator.Next" @@ -81,7 +77,6 @@ var names = []string{ SystemBinaryItoa, SystemBinarySerialize, SystemBlockchainGetBlock, - SystemBlockchainGetContract, SystemBlockchainGetHeight, SystemBlockchainGetTransaction, SystemBlockchainGetTransactionFromBlock, @@ -92,14 +87,11 @@ var names = []string{ SystemCallbackInvoke, SystemContractCall, SystemContractCallEx, - SystemContractCreate, SystemContractCreateStandardAccount, - SystemContractDestroy, SystemContractIsStandard, SystemContractGetCallFlags, SystemContractNativeOnPersist, SystemContractNativePostPersist, - SystemContractUpdate, SystemEnumeratorConcat, SystemEnumeratorCreate, SystemEnumeratorNext, diff --git a/pkg/core/interop/runtime/witness.go b/pkg/core/interop/runtime/witness.go index bdee68beb..5123128a0 100644 --- a/pkg/core/interop/runtime/witness.go +++ b/pkg/core/interop/runtime/witness.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" - "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/transaction" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" @@ -22,13 +21,13 @@ func CheckHashedWitness(ic *interop.Context, hash util.Uint160) (bool, error) { return true, nil } if tx, ok := ic.Container.(*transaction.Transaction); ok { - return checkScope(ic.DAO, tx, ic.VM, hash) + return checkScope(ic, tx, ic.VM, hash) } return false, errors.New("script container is not a transaction") } -func checkScope(d dao.DAO, tx *transaction.Transaction, v *vm.VM, hash util.Uint160) (bool, error) { +func checkScope(ic *interop.Context, tx *transaction.Transaction, v *vm.VM, hash util.Uint160) (bool, error) { for _, c := range tx.Signers { if c.Account == hash { if c.Scopes == transaction.Global { @@ -57,9 +56,9 @@ func checkScope(d dao.DAO, tx *transaction.Transaction, v *vm.VM, hash util.Uint if !v.Context().GetCallFlags().Has(smartcontract.ReadStates) { return false, errors.New("missing ReadStates call flag") } - cs, err := d.GetContractState(callingScriptHash) + cs, err := ic.GetContract(callingScriptHash) if err != nil { - return false, err + return false, fmt.Errorf("unable to find calling script: %w", err) } // check if the current group is the required one for _, allowedGroup := range c.AllowedGroups { diff --git a/pkg/core/interop_neo.go b/pkg/core/interop_neo.go index 2971d57b3..adf7d3f6a 100644 --- a/pkg/core/interop_neo.go +++ b/pkg/core/interop_neo.go @@ -2,33 +2,15 @@ package core import ( "bytes" - "encoding/json" "errors" "fmt" - "math" "sort" "github.com/nspcc-dev/neo-go/pkg/core/interop" - "github.com/nspcc-dev/neo-go/pkg/core/interop/contract" - "github.com/nspcc-dev/neo-go/pkg/core/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/smartcontract/nef" "github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) -const ( - // MaxContractDescriptionLen is the maximum length for contract description. - MaxContractDescriptionLen = 65536 - // MaxContractScriptSize is the maximum script size for a contract. - MaxContractScriptSize = 1024 * 1024 - // MaxContractParametersNum is the maximum number of parameters for a contract. - MaxContractParametersNum = 252 - // MaxContractStringLen is the maximum length for contract metadata strings. - MaxContractStringLen = 252 -) - var errGasLimitExceeded = errors.New("gas limit exceeded") // storageFind finds stored key-value pair. @@ -58,136 +40,3 @@ func storageFind(ic *interop.Context) error { return nil } - -// getNefAndManifestFromVM pops NEF and manifest from the VM's evaluation stack, -// does a lot of checks and returns deserialized structures if succeeds. -func getNefAndManifestFromVM(v *vm.VM) (*nef.File, *manifest.Manifest, error) { - // Always pop both elements. - nefBytes := v.Estack().Pop().BytesOrNil() - manifestBytes := v.Estack().Pop().BytesOrNil() - - if err := checkNonEmpty(nefBytes, math.MaxInt32); err != nil { // Upper limits are checked during NEF deserialization. - return nil, nil, fmt.Errorf("invalid NEF file: %w", err) - } - if err := checkNonEmpty(manifestBytes, manifest.MaxManifestSize); err != nil { - return nil, nil, fmt.Errorf("invalid manifest: %w", err) - } - - if !v.AddGas(int64(StoragePrice * (len(nefBytes) + len(manifestBytes)))) { - return nil, nil, errGasLimitExceeded - } - var resManifest *manifest.Manifest - var resNef *nef.File - if nefBytes != nil { - nf, err := nef.FileFromBytes(nefBytes) - if err != nil { - return nil, nil, fmt.Errorf("invalid NEF file: %w", err) - } - resNef = &nf - } - if manifestBytes != nil { - resManifest = new(manifest.Manifest) - err := json.Unmarshal(manifestBytes, resManifest) - if err != nil { - return nil, nil, fmt.Errorf("invalid manifest: %w", err) - } - } - return resNef, resManifest, nil -} - -// contractCreate creates a contract. -func contractCreate(ic *interop.Context) error { - neff, manif, err := getNefAndManifestFromVM(ic.VM) - if err != nil { - return err - } - if neff == nil { - return errors.New("no valid NEF provided") - } - if manif == nil { - return errors.New("no valid manifest provided") - } - if ic.Tx == nil { - return errors.New("no transaction provided") - } - h := state.CreateContractHash(ic.Tx.Sender(), neff.Script) - contract, err := ic.DAO.GetContractState(h) - if contract != nil && err == nil { - return errors.New("contract already exists") - } - if !manif.IsValid(h) { - return errors.New("failed to check contract script hash against manifest") - } - id, err := ic.DAO.GetAndUpdateNextContractID() - if err != nil { - return err - } - newcontract := &state.Contract{ - ID: id, - Hash: h, - Script: neff.Script, - Manifest: *manif, - } - if err := ic.DAO.PutContractState(newcontract); err != nil { - return err - } - cs, err := contractToStackItem(newcontract) - if err != nil { - return fmt.Errorf("cannot convert contract to stack item: %w", err) - } - ic.VM.Estack().PushVal(cs) - return callDeploy(ic, newcontract, false) -} - -func checkNonEmpty(b []byte, max int) error { - if b != nil { - if l := len(b); l == 0 { - return errors.New("empty") - } else if l > max { - return fmt.Errorf("len is %d (max %d)", l, max) - } - } - return nil -} - -// contractUpdate migrates a contract. This method assumes that Manifest and Script -// of the contract can be updated independently. -func contractUpdate(ic *interop.Context) error { - neff, manif, err := getNefAndManifestFromVM(ic.VM) - if err != nil { - return err - } - if neff == nil && manif == nil { - return errors.New("both NEF and manifest are nil") - } - contract, _ := ic.DAO.GetContractState(ic.VM.GetCurrentScriptHash()) - if contract == nil { - return errors.New("contract doesn't exist") - } - // if NEF was provided, update the contract script - if neff != nil { - contract.Script = neff.Script - } - // if manifest was provided, update the contract manifest - if manif != nil { - contract.Manifest = *manif - if !contract.Manifest.IsValid(contract.Hash) { - return errors.New("failed to check contract script hash against new manifest") - } - } - contract.UpdateCounter++ - - if err := ic.DAO.PutContractState(contract); err != nil { - return fmt.Errorf("failed to update contract: %w", err) - } - return callDeploy(ic, contract, true) -} - -func callDeploy(ic *interop.Context, cs *state.Contract, isUpdate bool) error { - md := cs.Manifest.ABI.GetMethod(manifest.MethodDeploy) - if md != nil { - return contract.CallExInternal(ic, cs, manifest.MethodDeploy, - []stackitem.Item{stackitem.NewBool(isUpdate)}, smartcontract.All, vm.EnsureIsEmpty) - } - return nil -} diff --git a/pkg/core/interop_neo_test.go b/pkg/core/interop_neo_test.go index 5366e4d91..eeb29a22c 100644 --- a/pkg/core/interop_neo_test.go +++ b/pkg/core/interop_neo_test.go @@ -49,7 +49,7 @@ func TestStorageFind(t *testing.T) { }, } - require.NoError(t, context.DAO.PutContractState(contractState)) + require.NoError(t, chain.contracts.Management.PutContractState(chain.dao, contractState)) id := contractState.ID diff --git a/pkg/core/interop_system.go b/pkg/core/interop_system.go index 50e2f377b..e455f482c 100644 --- a/pkg/core/interop_system.go +++ b/pkg/core/interop_system.go @@ -2,7 +2,6 @@ package core import ( "crypto/elliptic" - "encoding/json" "errors" "fmt" "math" @@ -12,6 +11,7 @@ import ( "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/native" "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" @@ -93,41 +93,6 @@ func bcGetBlock(ic *interop.Context) error { return nil } -// contractToStackItem converts state.Contract to stackitem.Item -func contractToStackItem(cs *state.Contract) (stackitem.Item, error) { - manifest, err := json.Marshal(cs.Manifest) - if err != nil { - return nil, err - } - return stackitem.NewArray([]stackitem.Item{ - stackitem.Make(cs.ID), - stackitem.Make(cs.UpdateCounter), - stackitem.NewByteArray(cs.Hash.BytesBE()), - stackitem.NewByteArray(cs.Script), - stackitem.NewByteArray(manifest), - }), nil -} - -// bcGetContract returns contract. -func bcGetContract(ic *interop.Context) error { - hashbytes := ic.VM.Estack().Pop().Bytes() - hash, err := util.Uint160DecodeBytesBE(hashbytes) - if err != nil { - return err - } - cs, err := ic.DAO.GetContractState(hash) - if err != nil { - ic.VM.Estack().PushVal(stackitem.Null{}) - } else { - item, err := contractToStackItem(cs) - if err != nil { - return err - } - ic.VM.Estack().PushVal(item) - } - return nil -} - // bcGetHeight returns blockchain height. func bcGetHeight(ic *interop.Context) error { ic.VM.Estack().PushVal(ic.Chain.BlockHeight()) @@ -274,7 +239,7 @@ func storageGetReadOnlyContext(ic *interop.Context) error { // storageGetContextInternal is internal version of storageGetContext and // storageGetReadOnlyContext which allows to specify ReadOnly context flag. func storageGetContextInternal(ic *interop.Context, isReadOnly bool) error { - contract, err := ic.DAO.GetContractState(ic.VM.GetCurrentScriptHash()) + contract, err := ic.GetContract(ic.VM.GetCurrentScriptHash()) if err != nil { return err } @@ -311,7 +276,7 @@ func putWithContextAndFlags(ic *interop.Context, stc *StorageContext, key []byte sizeInc = (len(si.Value)-1)/4 + 1 + len(value) - len(si.Value) } } - if !ic.VM.AddGas(int64(sizeInc) * StoragePrice) { + if !ic.VM.AddGas(int64(sizeInc) * native.StoragePrice) { return errGasLimitExceeded } si.Value = value @@ -363,27 +328,6 @@ func storageContextAsReadOnly(ic *interop.Context) error { return nil } -// contractDestroy destroys a contract. -func contractDestroy(ic *interop.Context) error { - hash := ic.VM.GetCurrentScriptHash() - cs, err := ic.DAO.GetContractState(hash) - if err != nil { - return nil - } - err = ic.DAO.DeleteContractState(hash) - if err != nil { - return err - } - siMap, err := ic.DAO.GetStorageItems(cs.ID) - if err != nil { - return err - } - for k := range siMap { - _ = ic.DAO.DeleteStorageItem(cs.ID, []byte(k)) - } - return nil -} - // contractIsStandard checks if contract is standard (sig or multisig) contract. func contractIsStandard(ic *interop.Context) error { h := ic.VM.Estack().Pop().Bytes() @@ -392,7 +336,7 @@ func contractIsStandard(ic *interop.Context) error { return err } var result bool - cs, _ := ic.DAO.GetContractState(u) + cs, _ := ic.GetContract(u) if cs != nil { result = vm.IsStandardContract(cs.Script) } else { diff --git a/pkg/core/interop_system_test.go b/pkg/core/interop_system_test.go index b8b2ab42c..19781c46e 100644 --- a/pkg/core/interop_system_test.go +++ b/pkg/core/interop_system_test.go @@ -1,20 +1,19 @@ package core import ( - "encoding/json" "errors" "math/big" "testing" "github.com/nspcc-dev/dbft/crypto" "github.com/nspcc-dev/neo-go/internal/random" - "github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop/callback" "github.com/nspcc-dev/neo-go/pkg/core/interop/contract" "github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames" "github.com/nspcc-dev/neo-go/pkg/core/interop/runtime" + "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/transaction" "github.com/nspcc-dev/neo-go/pkg/crypto/hash" @@ -22,7 +21,6 @@ import ( "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/smartcontract/nef" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm/emit" @@ -228,7 +226,7 @@ func TestContractIsStandard(t *testing.T) { require.NoError(t, err) pub := priv.PublicKey() - err = ic.DAO.PutContractState(&state.Contract{ID: 42, Hash: pub.GetScriptHash(), Script: pub.GetVerificationScript()}) + err = chain.contracts.Management.PutContractState(ic.DAO, &state.Contract{ID: 42, Hash: pub.GetScriptHash(), Script: pub.GetVerificationScript()}) require.NoError(t, err) v.Estack().PushVal(pub.GetScriptHash().BytesBE()) @@ -237,7 +235,7 @@ func TestContractIsStandard(t *testing.T) { }) t.Run("contract stored, false", func(t *testing.T) { script := []byte{byte(opcode.PUSHT)} - require.NoError(t, ic.DAO.PutContractState(&state.Contract{ID: 24, Hash: hash.Hash160(script), Script: script})) + require.NoError(t, chain.contracts.Management.PutContractState(ic.DAO, &state.Contract{ID: 24, Hash: hash.Hash160(script), Script: script})) v.Estack().PushVal(crypto.Hash160(script).BytesBE()) require.NoError(t, contractIsStandard(ic)) @@ -266,25 +264,74 @@ func TestContractCreateAccount(t *testing.T) { }) } -func TestBlockchainGetContractState(t *testing.T) { - v, cs, ic, bc := createVMAndContractState(t) - defer bc.Close() - require.NoError(t, ic.DAO.PutContractState(cs)) +func TestRuntimeGasLeft(t *testing.T) { + v, ic, chain := createVM(t) + defer chain.Close() - t.Run("positive", func(t *testing.T) { - v.Estack().PushVal(cs.Hash.BytesBE()) - require.NoError(t, bcGetContract(ic)) + v.GasLimit = 100 + v.AddGas(58) + require.NoError(t, runtime.GasLeft(ic)) + require.EqualValues(t, 42, v.Estack().Pop().BigInt().Int64()) +} - actual := v.Estack().Pop().Item() - compareContractStates(t, cs, actual) +func TestRuntimeGetNotifications(t *testing.T) { + v, ic, chain := createVM(t) + defer chain.Close() + + ic.Notifications = []state.NotificationEvent{ + {ScriptHash: util.Uint160{1}, Name: "Event1", Item: stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte{11})})}, + {ScriptHash: util.Uint160{2}, Name: "Event2", Item: stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte{22})})}, + {ScriptHash: util.Uint160{1}, Name: "Event1", Item: stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte{33})})}, + } + + t.Run("NoFilter", func(t *testing.T) { + v.Estack().PushVal(stackitem.Null{}) + require.NoError(t, runtime.GetNotifications(ic)) + + arr := v.Estack().Pop().Array() + require.Equal(t, len(ic.Notifications), len(arr)) + for i := range arr { + elem := arr[i].Value().([]stackitem.Item) + require.Equal(t, ic.Notifications[i].ScriptHash.BytesBE(), elem[0].Value()) + name, err := stackitem.ToString(elem[1]) + require.NoError(t, err) + require.Equal(t, ic.Notifications[i].Name, name) + require.Equal(t, ic.Notifications[i].Item, elem[2]) + } }) - t.Run("uncknown contract state", func(t *testing.T) { - v.Estack().PushVal(util.Uint160{1, 2, 3}.BytesBE()) - require.NoError(t, bcGetContract(ic)) + t.Run("WithFilter", func(t *testing.T) { + h := util.Uint160{2}.BytesBE() + v.Estack().PushVal(h) + require.NoError(t, runtime.GetNotifications(ic)) - actual := v.Estack().Pop().Item() - require.Equal(t, stackitem.Null{}, actual) + arr := v.Estack().Pop().Array() + require.Equal(t, 1, len(arr)) + elem := arr[0].Value().([]stackitem.Item) + require.Equal(t, h, elem[0].Value()) + name, err := stackitem.ToString(elem[1]) + require.NoError(t, err) + require.Equal(t, ic.Notifications[1].Name, name) + require.Equal(t, ic.Notifications[1].Item, elem[2]) + }) +} + +func TestRuntimeGetInvocationCounter(t *testing.T) { + v, ic, chain := createVM(t) + defer chain.Close() + + ic.VM.Invocations[hash.Hash160([]byte{2})] = 42 + + t.Run("No invocations", func(t *testing.T) { + v.LoadScript([]byte{1}) + // do not return an error in this case. + require.NoError(t, runtime.GetInvocationCounter(ic)) + require.EqualValues(t, 1, v.Estack().Pop().BigInt().Int64()) + }) + t.Run("NonZero", func(t *testing.T) { + v.LoadScript([]byte{2}) + require.NoError(t, runtime.GetInvocationCounter(ic)) + require.EqualValues(t, 42, v.Estack().Pop().BigInt().Int64()) }) } @@ -292,7 +339,7 @@ func TestStoragePut(t *testing.T) { _, cs, ic, bc := createVMAndContractState(t) defer bc.Close() - require.NoError(t, ic.DAO.PutContractState(cs)) + require.NoError(t, bc.contracts.Management.PutContractState(ic.DAO, cs)) initVM := func(t *testing.T, key, value []byte, gas int64) { v := ic.SpawnVM() @@ -304,23 +351,23 @@ func TestStoragePut(t *testing.T) { } t.Run("create, not enough gas", func(t *testing.T) { - initVM(t, []byte{1}, []byte{2, 3}, 2*StoragePrice) + initVM(t, []byte{1}, []byte{2, 3}, 2*native.StoragePrice) err := storagePut(ic) require.True(t, errors.Is(err, errGasLimitExceeded), "got: %v", err) }) - initVM(t, []byte{4}, []byte{5, 6}, 3*StoragePrice) + initVM(t, []byte{4}, []byte{5, 6}, 3*native.StoragePrice) require.NoError(t, storagePut(ic)) t.Run("update", func(t *testing.T) { t.Run("not enough gas", func(t *testing.T) { - initVM(t, []byte{4}, []byte{5, 6, 7, 8}, StoragePrice) + initVM(t, []byte{4}, []byte{5, 6, 7, 8}, native.StoragePrice) err := storagePut(ic) require.True(t, errors.Is(err, errGasLimitExceeded), "got: %v", err) }) - initVM(t, []byte{4}, []byte{5, 6, 7, 8}, 3*StoragePrice) + initVM(t, []byte{4}, []byte{5, 6, 7, 8}, 3*native.StoragePrice) require.NoError(t, storagePut(ic)) - initVM(t, []byte{4}, []byte{5, 6}, StoragePrice) + initVM(t, []byte{4}, []byte{5, 6}, native.StoragePrice) require.NoError(t, storagePut(ic)) }) @@ -365,7 +412,7 @@ func TestStorageDelete(t *testing.T) { v, cs, ic, bc := createVMAndContractState(t) defer bc.Close() - require.NoError(t, ic.DAO.PutContractState(cs)) + require.NoError(t, bc.contracts.Management.PutContractState(ic.DAO, cs)) v.LoadScriptWithHash(cs.Script, cs.Hash, smartcontract.All) put := func(key, value string, flag int) { v.Estack().PushVal(flag) @@ -403,7 +450,9 @@ func TestStorageDelete(t *testing.T) { } // getTestContractState returns 2 contracts second of which is allowed to call the first. -func getTestContractState() (*state.Contract, *state.Contract) { +func getTestContractState(bc *Blockchain) (*state.Contract, *state.Contract) { + mgmtHash := bc.ManagementContractHash() + w := io.NewBufBinWriter() emit.Opcodes(w.BinWriter, opcode.ABORT) addOff := w.Len() @@ -446,6 +495,17 @@ func getTestContractState() (*state.Contract, *state.Contract) { emit.String(w.BinWriter, "LastPayment") emit.Syscall(w.BinWriter, interopnames.SystemRuntimeNotify) emit.Opcodes(w.BinWriter, opcode.RET) + updateOff := w.Len() + emit.Int(w.BinWriter, 2) + emit.Opcodes(w.BinWriter, opcode.PACK) + emit.String(w.BinWriter, "update") + emit.AppCall(w.BinWriter, mgmtHash) + emit.Opcodes(w.BinWriter, opcode.RET) + destroyOff := w.Len() + emit.Opcodes(w.BinWriter, opcode.NEWARRAY0) + emit.String(w.BinWriter, "destroy") + emit.AppCall(w.BinWriter, mgmtHash) + emit.Opcodes(w.BinWriter, opcode.RET) script := w.Bytes() h := hash.Hash160(script) @@ -530,6 +590,20 @@ func getTestContractState() (*state.Contract, *state.Contract) { }, ReturnType: smartcontract.VoidType, }, + { + Name: "update", + Offset: updateOff, + Parameters: []manifest.Parameter{ + manifest.NewParameter("nef", smartcontract.ByteArrayType), + manifest.NewParameter("manifest", smartcontract.ByteArrayType), + }, + ReturnType: smartcontract.VoidType, + }, + { + Name: "destroy", + Offset: destroyOff, + ReturnType: smartcontract.VoidType, + }, } cs := &state.Contract{ Script: script, @@ -579,9 +653,9 @@ func TestContractCall(t *testing.T) { _, ic, bc := createVM(t) defer bc.Close() - cs, currCs := getTestContractState() - require.NoError(t, ic.DAO.PutContractState(cs)) - require.NoError(t, ic.DAO.PutContractState(currCs)) + cs, currCs := getTestContractState(bc) + require.NoError(t, bc.contracts.Management.PutContractState(ic.DAO, cs)) + require.NoError(t, bc.contracts.Management.PutContractState(ic.DAO, currCs)) currScript := currCs.Script h := hash.Hash160(cs.Script) @@ -682,325 +756,6 @@ func TestContractCall(t *testing.T) { }) } -func TestContractCreate(t *testing.T) { - v, cs, ic, bc := createVMAndContractState(t) - v.GasLimit = -1 - defer bc.Close() - - // nef.NewFile() cares about version a lot. - config.Version = "0.90.0-test" - - ne, err := nef.NewFile(cs.Script) - require.NoError(t, err) - neb, err := ne.Bytes() - require.NoError(t, err) - priv, err := keys.NewPrivateKey() - require.NoError(t, err) - sender := util.Uint160{1, 2, 3} - h := state.CreateContractHash(sender, ne.Script) - sig := priv.Sign(h.BytesBE()) - cs.Manifest.Groups = []manifest.Group{{ - PublicKey: priv.PublicKey(), - Signature: sig, - }} - m, err := json.Marshal(cs.Manifest) - require.NoError(t, err) - putArgsOnStack := func() { - v.Estack().PushVal(m) - v.Estack().PushVal(neb) - } - - t.Run("no tx", func(t *testing.T) { - putArgsOnStack() - - require.Error(t, contractCreate(ic)) - }) - - ic.Tx = transaction.New(netmode.UnitTestNet, []byte{1}, 0) - ic.Tx.Signers = append(ic.Tx.Signers, transaction.Signer{Account: sender}) - cs.ID = 1 - cs.Hash = state.CreateContractHash(sender, cs.Script) - - t.Run("missing NEF", func(t *testing.T) { - v.Estack().PushVal(m) - v.Estack().PushVal(stackitem.Null{}) - require.Error(t, contractCreate(ic)) - }) - t.Run("missing manifest", func(t *testing.T) { - v.Estack().PushVal(stackitem.Null{}) - v.Estack().PushVal(neb) - require.Error(t, contractCreate(ic)) - }) - t.Run("invalid manifest (empty)", func(t *testing.T) { - v.Estack().PushVal([]byte{}) - v.Estack().PushVal(neb) - require.Error(t, contractCreate(ic)) - }) - - t.Run("invalid manifest (group signature)", func(t *testing.T) { - cs.Manifest.Groups[0].Signature = make([]byte, 11) - rawManif, err := json.Marshal(cs.Manifest) - require.NoError(t, err) - v.Estack().PushVal(rawManif) - v.Estack().PushVal(neb) - require.Error(t, contractCreate(ic)) - }) - - cs.Manifest.Groups[0].Signature = sig - t.Run("positive", func(t *testing.T) { - putArgsOnStack() - - require.NoError(t, contractCreate(ic)) - actual := v.Estack().Pop().Item() - compareContractStates(t, cs, actual) - }) - - t.Run("contract already exists", func(t *testing.T) { - putArgsOnStack() - - require.Error(t, contractCreate(ic)) - }) -} - -func compareContractStates(t *testing.T, expected *state.Contract, actual stackitem.Item) { - act, ok := actual.Value().([]stackitem.Item) - require.True(t, ok) - - expectedManifest, err := json.Marshal(expected.Manifest) - require.NoError(t, err) - - require.Equal(t, 5, len(act)) - require.Equal(t, expected.ID, int32(act[0].Value().(*big.Int).Int64())) - require.Equal(t, expected.UpdateCounter, uint16(act[1].Value().(*big.Int).Int64())) - require.Equal(t, expected.Hash.BytesBE(), act[2].Value().([]byte)) - require.Equal(t, expected.Script, act[3].Value().([]byte)) - require.Equal(t, expectedManifest, act[4].Value().([]byte)) -} - -func TestContractUpdate(t *testing.T) { - v, cs, ic, bc := createVMAndContractState(t) - defer bc.Close() - v.GasLimit = -1 - - putArgsOnStack := func(script, manifest interface{}) { - v.Estack().PushVal(manifest) - b, ok := script.([]byte) - if ok { - ne, err := nef.NewFile(b) - require.NoError(t, err) - script, err = ne.Bytes() - require.NoError(t, err) - } - v.Estack().PushVal(script) - } - - t.Run("no args", func(t *testing.T) { - require.NoError(t, ic.DAO.PutContractState(cs)) - v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.Hash, smartcontract.All) - putArgsOnStack(stackitem.Null{}, stackitem.Null{}) - require.Error(t, contractUpdate(ic)) - }) - - t.Run("no contract", func(t *testing.T) { - require.NoError(t, ic.DAO.PutContractState(cs)) - v.LoadScriptWithHash([]byte{byte(opcode.RET)}, util.Uint160{8, 9, 7}, smartcontract.All) - putArgsOnStack([]byte{1}, stackitem.Null{}) - require.Error(t, contractUpdate(ic)) - }) - - t.Run("too large script", func(t *testing.T) { - require.NoError(t, ic.DAO.PutContractState(cs)) - v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.Hash, smartcontract.All) - putArgsOnStack(make([]byte, MaxContractScriptSize+1), stackitem.Null{}) - require.Error(t, contractUpdate(ic)) - }) - - t.Run("too large manifest", func(t *testing.T) { - require.NoError(t, ic.DAO.PutContractState(cs)) - v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.Hash, smartcontract.All) - putArgsOnStack(stackitem.Null{}, make([]byte, manifest.MaxManifestSize+1)) - require.Error(t, contractUpdate(ic)) - }) - - t.Run("gas limit exceeded", func(t *testing.T) { - require.NoError(t, ic.DAO.PutContractState(cs)) - v.GasLimit = 0 - v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.Hash, smartcontract.All) - putArgsOnStack([]byte{1}, []byte{2}) - require.Error(t, contractUpdate(ic)) - }) - - v.GasLimit = -1 - t.Run("update script, positive", func(t *testing.T) { - require.NoError(t, ic.DAO.PutContractState(cs)) - t.Run("empty manifest", func(t *testing.T) { - v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.Hash, smartcontract.All) - newScript := []byte{9, 8, 7, 6, 5} - putArgsOnStack(newScript, []byte{}) - require.Error(t, contractUpdate(ic)) - }) - - v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.Hash, smartcontract.All) - newScript := []byte{9, 8, 7, 6, 5} - putArgsOnStack(newScript, stackitem.Null{}) - - require.NoError(t, contractUpdate(ic)) - - // updated contract should have the same scripthash - actual, err := ic.DAO.GetContractState(cs.Hash) - require.NoError(t, err) - expected := &state.Contract{ - ID: cs.ID, - UpdateCounter: 1, - Hash: cs.Hash, - Script: newScript, - Manifest: cs.Manifest, - } - require.Equal(t, expected, actual) - }) - - t.Run("update manifest, bad manifest", func(t *testing.T) { - require.NoError(t, ic.DAO.PutContractState(cs)) - v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.Hash, smartcontract.All) - putArgsOnStack(stackitem.Null{}, []byte{1, 2, 3}) - - require.Error(t, contractUpdate(ic)) - }) - - t.Run("update manifest, positive", func(t *testing.T) { - require.NoError(t, ic.DAO.PutContractState(cs)) - manifest := &manifest.Manifest{ - ABI: manifest.ABI{}, - } - manifestBytes, err := json.Marshal(manifest) - require.NoError(t, err) - - t.Run("empty script", func(t *testing.T) { - v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.Hash, smartcontract.All) - putArgsOnStack([]byte{}, manifestBytes) - require.Error(t, contractUpdate(ic)) - }) - - v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.Hash, smartcontract.All) - putArgsOnStack(stackitem.Null{}, manifestBytes) - require.NoError(t, contractUpdate(ic)) - - // updated contract should have old scripthash - actual, err := ic.DAO.GetContractState(cs.Hash) - require.NoError(t, err) - expected := &state.Contract{ - ID: cs.ID, - UpdateCounter: 2, - Hash: cs.Hash, - Script: cs.Script, - Manifest: *manifest, - } - require.Equal(t, expected, actual) - }) - - t.Run("update both script and manifest", func(t *testing.T) { - require.NoError(t, ic.DAO.PutContractState(cs)) - v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.Hash, smartcontract.All) - newScript := []byte{12, 13, 14} - newManifest := manifest.Manifest{ - ABI: manifest.ABI{}, - } - newManifestBytes, err := json.Marshal(newManifest) - require.NoError(t, err) - - putArgsOnStack(newScript, newManifestBytes) - - require.NoError(t, contractUpdate(ic)) - - // updated contract should have new script and manifest - actual, err := ic.DAO.GetContractState(cs.Hash) - require.NoError(t, err) - expected := &state.Contract{ - ID: cs.ID, - UpdateCounter: 3, - Hash: cs.Hash, - Script: newScript, - Manifest: newManifest, - } - require.Equal(t, expected, actual) - }) -} - -func TestContractDestroy(t *testing.T) { - v, cs, ic, bc := createVMAndContractState(t) - defer bc.Close() - - v.LoadScriptWithHash(cs.Script, cs.Hash, smartcontract.All) - require.NoError(t, contractDestroy(ic)) // silent error when contract is missing - require.NoError(t, ic.DAO.PutContractState(cs)) - - v.Estack().PushVal("value") - v.Estack().PushVal("key") - require.NoError(t, storageGetContext(ic)) - require.NoError(t, storagePut(ic)) - require.NotNil(t, ic.DAO.GetStorageItem(cs.ID, []byte("key"))) - require.NoError(t, contractDestroy(ic)) - require.Nil(t, ic.DAO.GetStorageItem(cs.ID, []byte("key"))) - require.Error(t, storageGetContext(ic)) -} - -// TestContractCreateDeploy checks that `_deploy` method was called -// during contract creation or update. -func TestContractCreateDeploy(t *testing.T) { - v, ic, bc := createVM(t) - defer bc.Close() - v.GasLimit = -1 - - putArgs := func(cs *state.Contract) { - rawManifest, err := json.Marshal(cs.Manifest) - require.NoError(t, err) - v.Estack().PushVal(rawManifest) - ne, err := nef.NewFile(cs.Script) - require.NoError(t, err) - b, err := ne.Bytes() - require.NoError(t, err) - v.Estack().PushVal(b) - } - cs, currCs := getTestContractState() - - ic.Tx = transaction.New(netmode.UnitTestNet, []byte{1}, 0) - var sender = util.Uint160{1, 2, 3} - ic.Tx.Signers = append(ic.Tx.Signers, transaction.Signer{Account: sender}) - v.LoadScriptWithFlags([]byte{byte(opcode.RET)}, smartcontract.All) - putArgs(cs) - require.NoError(t, contractCreate(ic)) - require.NoError(t, ic.VM.Run()) - - cs.Hash = state.CreateContractHash(sender, cs.Script) - v.LoadScriptWithHash(currCs.Script, cs.Hash, smartcontract.All) - err := contract.CallExInternal(ic, cs, "getValue", nil, smartcontract.All, vm.EnsureNotEmpty) - require.NoError(t, err) - require.NoError(t, v.Run()) - require.Equal(t, "create", v.Estack().Pop().String()) - - v.LoadScriptWithHash(cs.Script, cs.Hash, smartcontract.All) - md := cs.Manifest.ABI.GetMethod("justReturn") - v.Jump(v.Context(), md.Offset) - - t.Run("Update", func(t *testing.T) { - newCs := &state.Contract{ - ID: cs.ID, - Hash: cs.Hash, - Script: append(cs.Script, byte(opcode.RET)), - Manifest: cs.Manifest, - } - putArgs(newCs) - require.NoError(t, contractUpdate(ic)) - require.NoError(t, v.Run()) - - v.LoadScriptWithHash(currCs.Script, cs.Hash, smartcontract.All) - err = contract.CallExInternal(ic, newCs, "getValue", nil, smartcontract.All, vm.EnsureNotEmpty) - require.NoError(t, err) - require.NoError(t, v.Run()) - require.Equal(t, "update", v.Estack().Pop().String()) - }) -} - func TestContractGetCallFlags(t *testing.T) { v, ic, bc := createVM(t) defer bc.Close() @@ -1049,12 +804,12 @@ func TestMethodCallback(t *testing.T) { _, ic, bc := createVM(t) defer bc.Close() - cs, currCs := getTestContractState() - require.NoError(t, ic.DAO.PutContractState(cs)) - require.NoError(t, ic.DAO.PutContractState(currCs)) + cs, currCs := getTestContractState(bc) + require.NoError(t, bc.contracts.Management.PutContractState(ic.DAO, cs)) + require.NoError(t, bc.contracts.Management.PutContractState(ic.DAO, currCs)) ic.Functions = append(ic.Functions, systemInterops) - rawHash := hash.Hash160(cs.Script).BytesBE() + rawHash := cs.Hash.BytesBE() t.Run("Invalid", func(t *testing.T) { runInvalid := func(args ...interface{}) func(t *testing.T) { @@ -1318,7 +1073,7 @@ func TestRuntimeCheckWitness(t *testing.T) { Groups: []manifest.Group{{PublicKey: pk.PublicKey()}}, }, } - require.NoError(t, ic.DAO.PutContractState(contractState)) + require.NoError(t, bc.contracts.Management.PutContractState(ic.DAO, contractState)) loadScriptWithHashAndFlags(ic, contractScript, contractScriptHash, smartcontract.All) ic.VM.LoadScriptWithHash([]byte{0x1}, random.Uint160(), smartcontract.ReadStates) ic.Container = tx diff --git a/pkg/core/interops.go b/pkg/core/interops.go index bb7eda1ed..aa69c2769 100644 --- a/pkg/core/interops.go +++ b/pkg/core/interops.go @@ -43,8 +43,6 @@ var systemInterops = []interop.Function{ {Name: interopnames.SystemBinarySerialize, Func: binary.Serialize, Price: 100000, ParamCount: 1}, {Name: interopnames.SystemBlockchainGetBlock, Func: bcGetBlock, Price: 2500000, RequiredFlags: smartcontract.ReadStates, ParamCount: 1}, - {Name: interopnames.SystemBlockchainGetContract, Func: bcGetContract, Price: 1000000, - RequiredFlags: smartcontract.ReadStates, ParamCount: 1}, {Name: interopnames.SystemBlockchainGetHeight, Func: bcGetHeight, Price: 400, RequiredFlags: smartcontract.ReadStates}, {Name: interopnames.SystemBlockchainGetTransaction, Func: bcGetTransaction, Price: 1000000, @@ -61,16 +59,11 @@ var systemInterops = []interop.Function{ RequiredFlags: smartcontract.AllowCall, ParamCount: 3, DisallowCallback: true}, {Name: interopnames.SystemContractCallEx, Func: contract.CallEx, Price: 1000000, RequiredFlags: smartcontract.AllowCall, ParamCount: 4, DisallowCallback: true}, - {Name: interopnames.SystemContractCreate, Func: contractCreate, Price: 0, - RequiredFlags: smartcontract.WriteStates, ParamCount: 2, DisallowCallback: true}, {Name: interopnames.SystemContractCreateStandardAccount, Func: contractCreateStandardAccount, Price: 10000, ParamCount: 1, DisallowCallback: true}, - {Name: interopnames.SystemContractDestroy, Func: contractDestroy, Price: 1000000, RequiredFlags: smartcontract.WriteStates, DisallowCallback: true}, {Name: interopnames.SystemContractIsStandard, Func: contractIsStandard, Price: 30000, RequiredFlags: smartcontract.ReadStates, ParamCount: 1}, {Name: interopnames.SystemContractGetCallFlags, Func: contractGetCallFlags, Price: 30000, DisallowCallback: true}, {Name: interopnames.SystemContractNativeOnPersist, Func: native.OnPersist, Price: 0, DisallowCallback: true}, {Name: interopnames.SystemContractNativePostPersist, Func: native.PostPersist, Price: 0, DisallowCallback: true}, - {Name: interopnames.SystemContractUpdate, Func: contractUpdate, Price: 0, - RequiredFlags: smartcontract.WriteStates, ParamCount: 2, DisallowCallback: true}, {Name: interopnames.SystemEnumeratorConcat, Func: enumerator.Concat, Price: 400, ParamCount: 2, DisallowCallback: true}, {Name: interopnames.SystemEnumeratorCreate, Func: enumerator.Create, Price: 400, ParamCount: 1, DisallowCallback: true}, {Name: interopnames.SystemEnumeratorNext, Func: enumerator.Next, Price: 1000000, ParamCount: 1, DisallowCallback: true}, @@ -98,7 +91,7 @@ var systemInterops = []interop.Function{ {Name: interopnames.SystemRuntimeNotify, Func: runtime.Notify, Price: 1000000, RequiredFlags: smartcontract.AllowNotify, ParamCount: 2, DisallowCallback: true}, {Name: interopnames.SystemRuntimePlatform, Func: runtime.Platform, Price: 250}, - {Name: interopnames.SystemStorageDelete, Func: storageDelete, Price: StoragePrice, + {Name: interopnames.SystemStorageDelete, Func: storageDelete, Price: native.StoragePrice, RequiredFlags: smartcontract.WriteStates, ParamCount: 2, DisallowCallback: true}, {Name: interopnames.SystemStorageFind, Func: storageFind, Price: 1000000, RequiredFlags: smartcontract.ReadStates, ParamCount: 2, DisallowCallback: true}, diff --git a/pkg/core/native/management.go b/pkg/core/native/management.go index 9df29fb97..e4afa20f4 100644 --- a/pkg/core/native/management.go +++ b/pkg/core/native/management.go @@ -1,10 +1,23 @@ package native import ( + "encoding/json" + "errors" "fmt" + "math" + "math/big" + "github.com/nspcc-dev/neo-go/pkg/core/dao" "github.com/nspcc-dev/neo-go/pkg/core/interop" + "github.com/nspcc-dev/neo-go/pkg/core/interop/contract" "github.com/nspcc-dev/neo-go/pkg/core/state" + "github.com/nspcc-dev/neo-go/pkg/encoding/bigint" + "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) // Management is contract-managing native contract. @@ -12,19 +25,288 @@ type Management struct { interop.ContractMD } +// StoragePrice is the price to pay for 1 byte of storage. +const StoragePrice = 100000 + const ( - managementName = "Neo Contract Management" - prefixContract = 8 - prefixNextAvailableId = 15 + managementName = "Neo Contract Management" + prefixContract = 8 ) +var errGasLimitExceeded = errors.New("gas limit exceeded") +var keyNextAvailableID = []byte{15} + +// makeContractKey creates a key from account script hash. +func makeContractKey(h util.Uint160) []byte { + return makeUint160Key(prefixContract, h) +} + // newManagement creates new Management native contract. func newManagement() *Management { var m = &Management{ContractMD: *interop.NewContractMD(managementName)} + desc := newDescriptor("getContract", smartcontract.ArrayType, + manifest.NewParameter("hash", smartcontract.Hash160Type)) + md := newMethodAndPrice(m.getContract, 1000000, smartcontract.ReadStates) + m.AddMethod(md, desc) + + desc = newDescriptor("deploy", smartcontract.ArrayType, + manifest.NewParameter("script", smartcontract.ByteArrayType), + manifest.NewParameter("manifest", smartcontract.ByteArrayType)) + md = newMethodAndPrice(m.deploy, 0, smartcontract.WriteStates) + m.AddMethod(md, desc) + + desc = newDescriptor("update", smartcontract.VoidType, + manifest.NewParameter("script", smartcontract.ByteArrayType), + manifest.NewParameter("manifest", smartcontract.ByteArrayType)) + md = newMethodAndPrice(m.update, 0, smartcontract.WriteStates) + m.AddMethod(md, desc) + + desc = newDescriptor("destroy", smartcontract.VoidType) + md = newMethodAndPrice(m.destroy, 10000000, smartcontract.WriteStates) + m.AddMethod(md, desc) + return m } +// getContract is an implementation of public getContract method, it's run under +// VM protections, so it's OK for it to panic instead of returning errors. +func (m *Management) getContract(ic *interop.Context, args []stackitem.Item) stackitem.Item { + hashBytes, err := args[0].TryBytes() + if err != nil { + panic(err) + } + hash, err := util.Uint160DecodeBytesBE(hashBytes) + if err != nil { + panic(err) + } + ctr, err := m.GetContract(ic.DAO, hash) + if err != nil { + panic(err) + } + return contractToStack(ctr) +} + +// GetContract returns contract with given hash from given DAO. +func (m *Management) GetContract(d dao.DAO, hash util.Uint160) (*state.Contract, error) { + contract := new(state.Contract) + key := makeContractKey(hash) + err := getSerializableFromDAO(m.ContractID, d, key, contract) + if err != nil { + return nil, err + } + return contract, nil +} + +func getLimitedSlice(arg stackitem.Item, max int) ([]byte, error) { + _, isNull := arg.(stackitem.Null) + if isNull { + return nil, nil + } + b, err := arg.TryBytes() + if err != nil { + return nil, err + } + l := len(b) + if l == 0 { + return nil, errors.New("empty") + } else if l > max { + return nil, fmt.Errorf("len is %d (max %d)", l, max) + } + + return b, nil +} + +// getNefAndManifestFromItems converts input arguments into NEF and manifest +// adding appropriate deployment GAS price and sanitizing inputs. +func getNefAndManifestFromItems(args []stackitem.Item, v *vm.VM) (*nef.File, *manifest.Manifest, error) { + nefBytes, err := getLimitedSlice(args[0], math.MaxInt32) // Upper limits are checked during NEF deserialization. + if err != nil { + return nil, nil, fmt.Errorf("invalid NEF file: %w", err) + } + manifestBytes, err := getLimitedSlice(args[1], manifest.MaxManifestSize) + if err != nil { + return nil, nil, fmt.Errorf("invalid manifest: %w", err) + } + + if !v.AddGas(int64(StoragePrice * (len(nefBytes) + len(manifestBytes)))) { + return nil, nil, errGasLimitExceeded + } + var resManifest *manifest.Manifest + var resNef *nef.File + if nefBytes != nil { + nf, err := nef.FileFromBytes(nefBytes) + if err != nil { + return nil, nil, fmt.Errorf("invalid NEF file: %w", err) + } + resNef = &nf + } + if manifestBytes != nil { + resManifest = new(manifest.Manifest) + err := json.Unmarshal(manifestBytes, resManifest) + if err != nil { + return nil, nil, fmt.Errorf("invalid manifest: %w", err) + } + } + return resNef, resManifest, nil +} + +// deploy is an implementation of public deploy method, it's run under +// VM protections, so it's OK for it to panic instead of returning errors. +func (m *Management) deploy(ic *interop.Context, args []stackitem.Item) stackitem.Item { + neff, manif, err := getNefAndManifestFromItems(args, ic.VM) + if err != nil { + panic(err) + } + if neff == nil { + panic(errors.New("no valid NEF provided")) + } + if manif == nil { + panic(errors.New("no valid manifest provided")) + } + if ic.Tx == nil { + panic(errors.New("no transaction provided")) + } + newcontract, err := m.Deploy(ic.DAO, ic.Tx.Sender(), neff, manif) + if err != nil { + panic(err) + } + callDeploy(ic, newcontract, false) + return contractToStack(newcontract) + +} + +// Deploy creates contract's hash/ID and saves new contract into the given DAO. +// It doesn't run _deploy method. +func (m *Management) Deploy(d dao.DAO, sender util.Uint160, neff *nef.File, manif *manifest.Manifest) (*state.Contract, error) { + h := state.CreateContractHash(sender, neff.Script) + key := makeContractKey(h) + si := d.GetStorageItem(m.ContractID, key) + if si != nil { + return nil, errors.New("contract already exists") + } + id, err := m.getNextContractID(d) + if err != nil { + return nil, err + } + if !manif.IsValid(h) { + return nil, errors.New("invalid manifest for this contract") + } + newcontract := &state.Contract{ + ID: id, + Hash: h, + Script: neff.Script, + Manifest: *manif, + } + err = m.PutContractState(d, newcontract) + if err != nil { + return nil, err + } + return newcontract, nil +} + +// update is an implementation of public update method, it's run under +// VM protections, so it's OK for it to panic instead of returning errors. +func (m *Management) update(ic *interop.Context, args []stackitem.Item) stackitem.Item { + neff, manif, err := getNefAndManifestFromItems(args, ic.VM) + if err != nil { + panic(err) + } + if neff == nil && manif == nil { + panic(errors.New("both NEF and manifest are nil")) + } + contract, err := m.Update(ic.DAO, ic.VM.GetCallingScriptHash(), neff, manif) + if err != nil { + panic(err) + } + callDeploy(ic, contract, true) + return stackitem.Null{} +} + +// Update updates contract's script and/or manifest in the given DAO. +// It doesn't run _deploy method. +func (m *Management) Update(d dao.DAO, hash util.Uint160, neff *nef.File, manif *manifest.Manifest) (*state.Contract, error) { + contract, err := m.GetContract(d, hash) + if err != nil { + return nil, errors.New("contract doesn't exist") + } + // if NEF was provided, update the contract script + if neff != nil { + contract.Script = neff.Script + } + // if manifest was provided, update the contract manifest + if manif != nil { + contract.Manifest = *manif + if !contract.Manifest.IsValid(contract.Hash) { + return nil, errors.New("invalid manifest for this contract") + } + } + contract.UpdateCounter++ + err = m.PutContractState(d, contract) + if err != nil { + return nil, err + } + return contract, nil +} + +// destroy is an implementation of destroy update method, it's run under +// VM protections, so it's OK for it to panic instead of returning errors. +func (m *Management) destroy(ic *interop.Context, sis []stackitem.Item) stackitem.Item { + hash := ic.VM.GetCallingScriptHash() + err := m.Destroy(ic.DAO, hash) + if err != nil { + panic(err) + } + return stackitem.Null{} +} + +// Destroy drops given contract from DAO along with its storage. +func (m *Management) Destroy(d dao.DAO, hash util.Uint160) error { + contract, err := m.GetContract(d, hash) + if err != nil { + return err + } + key := makeContractKey(hash) + err = d.DeleteStorageItem(m.ContractID, key) + if err != nil { + return err + } + err = d.DeleteContractID(contract.ID) + if err != nil { + return err + } + siMap, err := d.GetStorageItems(contract.ID) + if err != nil { + return err + } + for k := range siMap { + err := d.DeleteStorageItem(contract.ID, []byte(k)) + if err != nil { + return err + } + } + return nil +} + +func callDeploy(ic *interop.Context, cs *state.Contract, isUpdate bool) { + md := cs.Manifest.ABI.GetMethod(manifest.MethodDeploy) + if md != nil { + err := contract.CallExInternal(ic, cs, manifest.MethodDeploy, + []stackitem.Item{stackitem.NewBool(isUpdate)}, smartcontract.All, vm.EnsureIsEmpty) + if err != nil { + panic(err) + } + } +} + +func contractToStack(cs *state.Contract) stackitem.Item { + si, err := cs.ToStackItem() + if err != nil { + panic(fmt.Errorf("contract to stack item: %w", err)) + } + return si +} + // Metadata implements Contract interface. func (m *Management) Metadata() *interop.ContractMD { return &m.ContractMD @@ -45,7 +327,8 @@ func (m *Management) OnPersist(ic *interop.Context) error { Script: md.Script, Manifest: md.Manifest, } - if err := ic.DAO.PutContractState(cs); err != nil { + err := m.PutContractState(ic.DAO, cs) + if err != nil { return err } if err := native.Initialize(ic); err != nil { @@ -65,3 +348,30 @@ func (m *Management) PostPersist(_ *interop.Context) error { func (m *Management) Initialize(_ *interop.Context) error { return nil } + +// PutContractState saves given contract state into given DAO. +func (m *Management) PutContractState(d dao.DAO, cs *state.Contract) error { + key := makeContractKey(cs.Hash) + if err := putSerializableToDAO(m.ContractID, d, key, cs); err != nil { + return err + } + if cs.UpdateCounter != 0 { // Update. + return nil + } + return d.PutContractID(cs.ID, cs.Hash) +} + +func (m *Management) getNextContractID(d dao.DAO) (int32, error) { + var id = big.NewInt(1) + si := d.GetStorageItem(m.ContractID, keyNextAvailableID) + if si != nil { + id = bigint.FromBytes(si.Value) + } else { + si = new(state.StorageItem) + si.Value = make([]byte, 0, 2) + } + ret := int32(id.Int64()) + id.Add(id, big.NewInt(1)) + si.Value = bigint.ToPreallocatedBytes(id, si.Value) + return ret, d.PutStorageItem(m.ContractID, keyNextAvailableID, si) +} diff --git a/pkg/core/native/management_test.go b/pkg/core/native/management_test.go new file mode 100644 index 000000000..e708654a4 --- /dev/null +++ b/pkg/core/native/management_test.go @@ -0,0 +1,63 @@ +package native + +import ( + "testing" + + "github.com/nspcc-dev/neo-go/pkg/config/netmode" + "github.com/nspcc-dev/neo-go/pkg/core/dao" + "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/smartcontract/manifest" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/stretchr/testify/require" +) + +func TestDeployGetUpdateDestroyContract(t *testing.T) { + mgmt := newManagement() + d := dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false) + script := []byte{1} + sender := util.Uint160{1, 2, 3} + h := state.CreateContractHash(sender, script) + + ne, err := nef.NewFile(script) + require.NoError(t, err) + manif := manifest.NewManifest("Test") + require.NoError(t, err) + + contract, err := mgmt.Deploy(d, sender, ne, manif) + require.NoError(t, err) + require.Equal(t, int32(1), contract.ID) + require.Equal(t, uint16(0), contract.UpdateCounter) + require.Equal(t, h, contract.Hash) + require.Equal(t, script, contract.Script) + require.Equal(t, *manif, contract.Manifest) + + // Double deploy. + _, err = mgmt.Deploy(d, sender, ne, manif) + require.Error(t, err) + + // Different sender. + sender2 := util.Uint160{3, 2, 1} + contract2, err := mgmt.Deploy(d, sender2, ne, manif) + require.NoError(t, err) + require.Equal(t, int32(2), contract2.ID) + require.Equal(t, uint16(0), contract2.UpdateCounter) + require.Equal(t, state.CreateContractHash(sender2, script), contract2.Hash) + require.Equal(t, script, contract2.Script) + require.Equal(t, *manif, contract2.Manifest) + + refContract, err := mgmt.GetContract(d, h) + require.NoError(t, err) + require.Equal(t, contract, refContract) + + upContract, err := mgmt.Update(d, h, ne, manif) + refContract.UpdateCounter++ + require.NoError(t, err) + require.Equal(t, refContract, upContract) + + err = mgmt.Destroy(d, h) + require.NoError(t, err) + _, err = mgmt.GetContract(d, h) + require.Error(t, err) +} diff --git a/pkg/core/native/native_nep17.go b/pkg/core/native/native_nep17.go index 9049bcbb7..9a2e8b1ca 100644 --- a/pkg/core/native/native_nep17.go +++ b/pkg/core/native/native_nep17.go @@ -23,10 +23,7 @@ const prefixAccount = 20 // makeAccountKey creates a key from account script hash. func makeAccountKey(h util.Uint160) []byte { - k := make([]byte, util.Uint160Size+1) - k[0] = prefixAccount - copy(k[1:], h.BytesBE()) - return k + return makeUint160Key(prefixAccount, h) } // nep17TokenNative represents NEP-17 token contract. @@ -132,7 +129,7 @@ func (c *nep17TokenNative) postTransfer(ic *interop.Context, from, to *util.Uint if to == nil || !callOnPayment { return } - cs, err := ic.DAO.GetContractState(*to) + cs, err := ic.GetContract(*to) if err != nil { return } diff --git a/pkg/core/native/notary.go b/pkg/core/native/notary.go index 9a087b721..3fecfe7ef 100644 --- a/pkg/core/native/notary.go +++ b/pkg/core/native/notary.go @@ -276,7 +276,7 @@ func (n *Notary) withdraw(ic *interop.Context, args []stackitem.Item) stackitem. if ic.Chain.BlockHeight() < deposit.Till { return stackitem.NewBool(false) } - cs, err := ic.DAO.GetContractState(n.GAS.Hash) + cs, err := ic.GetContract(n.GAS.Hash) if err != nil { panic(fmt.Errorf("failed to get GAS contract state: %w", err)) } diff --git a/pkg/core/native/oracle.go b/pkg/core/native/oracle.go index a2f1e8ac7..b97beb0bc 100644 --- a/pkg/core/native/oracle.go +++ b/pkg/core/native/oracle.go @@ -251,7 +251,7 @@ func (o *Oracle) FinishInternal(ic *interop.Context) error { stackitem.Make(resp.Code), stackitem.Make(resp.Result), } - cs, err := ic.DAO.GetContractState(req.CallbackContract) + cs, err := ic.GetContract(req.CallbackContract) if err != nil { return err } @@ -307,7 +307,7 @@ func (o *Oracle) RequestInternal(ic *interop.Context, url string, filter *string } // Should be executed from contract. - _, err := ic.DAO.GetContractState(ic.VM.GetCallingScriptHash()) + _, err := ic.GetContract(ic.VM.GetCallingScriptHash()) if err != nil { return err } diff --git a/pkg/core/native/util.go b/pkg/core/native/util.go index 38a17e58a..d70d88799 100644 --- a/pkg/core/native/util.go +++ b/pkg/core/native/util.go @@ -9,6 +9,7 @@ import ( "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/io" + "github.com/nspcc-dev/neo-go/pkg/util" ) func getSerializableFromDAO(id int32, d dao.DAO, key []byte, item io.Serializable) error { @@ -71,3 +72,11 @@ func checkValidators(ic *interop.Context) (bool, error) { } return runtime.CheckHashedWitness(ic, prevBlock.NextConsensus) } + +// makeUint160Key creates a key from account script hash. +func makeUint160Key(prefix byte, h util.Uint160) []byte { + k := make([]byte, util.Uint160Size+1) + k[0] = prefix + copy(k[1:], h.BytesBE()) + return k +} diff --git a/pkg/core/native_contract_test.go b/pkg/core/native_contract_test.go index 0e1f9069a..074a0c7ce 100644 --- a/pkg/core/native_contract_test.go +++ b/pkg/core/native_contract_test.go @@ -136,12 +136,11 @@ func toUint160(item stackitem.Item) util.Uint160 { } func (tn *testNative) call(ic *interop.Context, args []stackitem.Item, checkReturn vm.CheckReturnState) { - h := toUint160(args[0]) - bs, err := args[1].TryBytes() + cs, err := ic.GetContract(toUint160(args[0])) if err != nil { panic(err) } - cs, err := ic.DAO.GetContractState(h) + bs, err := args[1].TryBytes() if err != nil { panic(err) } @@ -169,7 +168,8 @@ func TestNativeContract_Invoke(t *testing.T) { tn := newTestNative() chain.registerNative(tn) - err := chain.dao.PutContractState(&state.Contract{ + err := chain.contracts.Management.PutContractState(chain.dao, &state.Contract{ + ID: 1, Script: tn.meta.Script, Hash: tn.meta.Hash, Manifest: tn.meta.Manifest, @@ -203,7 +203,8 @@ func TestNativeContract_InvokeInternal(t *testing.T) { tn := newTestNative() chain.registerNative(tn) - err := chain.dao.PutContractState(&state.Contract{ + err := chain.contracts.Management.PutContractState(chain.dao, &state.Contract{ + ID: 1, Script: tn.meta.Script, Manifest: tn.meta.Manifest, }) @@ -243,7 +244,8 @@ func TestNativeContract_InvokeOtherContract(t *testing.T) { tn := newTestNative() chain.registerNative(tn) - err := chain.dao.PutContractState(&state.Contract{ + err := chain.contracts.Management.PutContractState(chain.dao, &state.Contract{ + ID: 1, Hash: tn.meta.Hash, Script: tn.meta.Script, Manifest: tn.meta.Manifest, @@ -258,8 +260,8 @@ func TestNativeContract_InvokeOtherContract(t *testing.T) { } } - cs, _ := getTestContractState() - require.NoError(t, chain.dao.PutContractState(cs)) + cs, _ := getTestContractState(chain) + require.NoError(t, chain.contracts.Management.PutContractState(chain.dao, cs)) t.Run("non-native, no return", func(t *testing.T) { res, err := invokeContractMethod(chain, testSumPrice*4+10000, tn.Metadata().Hash, "callOtherContractNoReturn", cs.Hash, "justReturn", []interface{}{}) diff --git a/pkg/core/native_management_test.go b/pkg/core/native_management_test.go new file mode 100644 index 000000000..43204e451 --- /dev/null +++ b/pkg/core/native_management_test.go @@ -0,0 +1,362 @@ +package core + +import ( + "encoding/json" + "math/big" + "testing" + + "github.com/nspcc-dev/neo-go/internal/testchain" + "github.com/nspcc-dev/neo-go/pkg/config" + "github.com/nspcc-dev/neo-go/pkg/core/state" + "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/smartcontract/nef" + "github.com/nspcc-dev/neo-go/pkg/vm" + "github.com/nspcc-dev/neo-go/pkg/vm/opcode" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "github.com/stretchr/testify/require" +) + +func TestContractDeploy(t *testing.T) { + bc := newTestChain(t) + defer bc.Close() + + // nef.NewFile() cares about version a lot. + config.Version = "0.90.0-test" + mgmtHash := bc.ManagementContractHash() + cs1, _ := getTestContractState(bc) + cs1.ID = 1 + cs1.Hash = state.CreateContractHash(testchain.MultisigScriptHash(), cs1.Script) + manif1, err := json.Marshal(cs1.Manifest) + require.NoError(t, err) + nef1, err := nef.NewFile(cs1.Script) + require.NoError(t, err) + nef1b, err := nef1.Bytes() + require.NoError(t, err) + + t.Run("no NEF", func(t *testing.T) { + res, err := invokeContractMethod(bc, 10_00000000, mgmtHash, "deploy", nil, manif1) + require.NoError(t, err) + checkFAULTState(t, res) + }) + t.Run("no manifest", func(t *testing.T) { + res, err := invokeContractMethod(bc, 10_00000000, mgmtHash, "deploy", nef1b, nil) + require.NoError(t, err) + checkFAULTState(t, res) + }) + t.Run("int for NEF", func(t *testing.T) { + res, err := invokeContractMethod(bc, 10_00000000, mgmtHash, "deploy", int64(1), manif1) + require.NoError(t, err) + checkFAULTState(t, res) + }) + t.Run("zero-length NEF", func(t *testing.T) { + res, err := invokeContractMethod(bc, 10_00000000, mgmtHash, "deploy", []byte{}, manif1) + require.NoError(t, err) + checkFAULTState(t, res) + }) + t.Run("array for NEF", func(t *testing.T) { + res, err := invokeContractMethod(bc, 10_00000000, mgmtHash, "deploy", []interface{}{int64(1)}, manif1) + require.NoError(t, err) + checkFAULTState(t, res) + }) + t.Run("int for manifest", func(t *testing.T) { + res, err := invokeContractMethod(bc, 10_00000000, mgmtHash, "deploy", nef1b, int64(1)) + require.NoError(t, err) + checkFAULTState(t, res) + }) + t.Run("zero-length manifest", func(t *testing.T) { + res, err := invokeContractMethod(bc, 10_00000000, mgmtHash, "deploy", nef1b, []byte{}) + require.NoError(t, err) + checkFAULTState(t, res) + }) + t.Run("too long manifest", func(t *testing.T) { + res, err := invokeContractMethod(bc, 100_00000000, mgmtHash, "deploy", nef1b, append(manif1, make([]byte, manifest.MaxManifestSize)...)) + require.NoError(t, err) + checkFAULTState(t, res) + }) + t.Run("array for manifest", func(t *testing.T) { + res, err := invokeContractMethod(bc, 10_00000000, mgmtHash, "deploy", nef1b, []interface{}{int64(1)}) + require.NoError(t, err) + checkFAULTState(t, res) + }) + t.Run("invalid manifest", func(t *testing.T) { + pkey, err := keys.NewPrivateKey() + require.NoError(t, err) + + var badManifest = cs1.Manifest + badManifest.Groups = []manifest.Group{manifest.Group{PublicKey: pkey.PublicKey(), Signature: make([]byte, 64)}} + manifB, err := json.Marshal(badManifest) + require.NoError(t, err) + + res, err := invokeContractMethod(bc, 10_00000000, mgmtHash, "deploy", nef1b, manifB) + require.NoError(t, err) + checkFAULTState(t, res) + }) + t.Run("not enough GAS", func(t *testing.T) { + res, err := invokeContractMethod(bc, 1_00000000, mgmtHash, "deploy", nef1b, manif1) + require.NoError(t, err) + checkFAULTState(t, res) + }) + t.Run("positive", func(t *testing.T) { + res, err := invokeContractMethod(bc, 10_00000000, mgmtHash, "deploy", nef1b, manif1) + require.NoError(t, err) + require.Equal(t, vm.HaltState, res.VMState) + require.Equal(t, 1, len(res.Stack)) + compareContractStates(t, cs1, res.Stack[0]) + t.Run("_deploy called", func(t *testing.T) { + res, err := invokeContractMethod(bc, 1_00000000, cs1.Hash, "getValue") + require.NoError(t, err) + require.Equal(t, 1, len(res.Stack)) + require.Equal(t, []byte("create"), res.Stack[0].Value()) + }) + }) + t.Run("contract already exists", func(t *testing.T) { + res, err := invokeContractMethod(bc, 10_00000000, mgmtHash, "deploy", nef1b, manif1) + require.NoError(t, err) + checkFAULTState(t, res) + }) + t.Run("failed _deploy", func(t *testing.T) { + deployScript := []byte{byte(opcode.ABORT)} + m := manifest.NewManifest("TestDeployAbort") + m.ABI.Methods = []manifest.Method{ + { + Name: manifest.MethodDeploy, + Offset: 0, + Parameters: []manifest.Parameter{ + manifest.NewParameter("isUpdate", smartcontract.BoolType), + }, + ReturnType: smartcontract.VoidType, + }, + } + nefD, err := nef.NewFile(deployScript) + require.NoError(t, err) + nefDb, err := nefD.Bytes() + require.NoError(t, err) + manifD, err := json.Marshal(m) + require.NoError(t, err) + res, err := invokeContractMethod(bc, 10_00000000, mgmtHash, "deploy", nefDb, manifD) + require.NoError(t, err) + checkFAULTState(t, res) + }) + t.Run("bad _deploy", func(t *testing.T) { // invalid _deploy signature + deployScript := []byte{byte(opcode.RET)} + m := manifest.NewManifest("TestBadDeploy") + m.ABI.Methods = []manifest.Method{ + { + Name: manifest.MethodDeploy, + Offset: 0, + Parameters: []manifest.Parameter{ + manifest.NewParameter("isUpdate", smartcontract.BoolType), + manifest.NewParameter("param", smartcontract.IntegerType), + }, + ReturnType: smartcontract.VoidType, + }, + } + nefD, err := nef.NewFile(deployScript) + require.NoError(t, err) + nefDb, err := nefD.Bytes() + require.NoError(t, err) + manifD, err := json.Marshal(m) + require.NoError(t, err) + res, err := invokeContractMethod(bc, 10_00000000, mgmtHash, "deploy", nefDb, manifD) + require.NoError(t, err) + checkFAULTState(t, res) + }) +} + +func TestContractUpdate(t *testing.T) { + bc := newTestChain(t) + defer bc.Close() + + // nef.NewFile() cares about version a lot. + config.Version = "0.90.0-test" + mgmtHash := bc.ManagementContractHash() + cs1, _ := getTestContractState(bc) + // Allow calling management contract. + cs1.Manifest.Permissions = []manifest.Permission{*manifest.NewPermission(manifest.PermissionWildcard)} + err := bc.contracts.Management.PutContractState(bc.dao, cs1) + require.NoError(t, err) + manif1, err := json.Marshal(cs1.Manifest) + require.NoError(t, err) + nef1, err := nef.NewFile(cs1.Script) + require.NoError(t, err) + nef1b, err := nef1.Bytes() + require.NoError(t, err) + + t.Run("no contract", func(t *testing.T) { + res, err := invokeContractMethod(bc, 10_00000000, mgmtHash, "update", nef1b, manif1) + require.NoError(t, err) + checkFAULTState(t, res) + }) + t.Run("zero-length NEF", func(t *testing.T) { + res, err := invokeContractMethod(bc, 10_00000000, cs1.Hash, "update", []byte{}, manif1) + require.NoError(t, err) + checkFAULTState(t, res) + }) + t.Run("zero-length manifest", func(t *testing.T) { + res, err := invokeContractMethod(bc, 10_00000000, cs1.Hash, "update", nef1b, []byte{}) + require.NoError(t, err) + checkFAULTState(t, res) + }) + t.Run("not enough GAS", func(t *testing.T) { + res, err := invokeContractMethod(bc, 1_00000000, cs1.Hash, "update", nef1b, manif1) + require.NoError(t, err) + checkFAULTState(t, res) + }) + t.Run("no real params", func(t *testing.T) { + res, err := invokeContractMethod(bc, 10_00000000, cs1.Hash, "update", nil, nil) + require.NoError(t, err) + checkFAULTState(t, res) + }) + t.Run("invalid manifest", func(t *testing.T) { + pkey, err := keys.NewPrivateKey() + require.NoError(t, err) + + var badManifest = cs1.Manifest + badManifest.Groups = []manifest.Group{manifest.Group{PublicKey: pkey.PublicKey(), Signature: make([]byte, 64)}} + manifB, err := json.Marshal(badManifest) + require.NoError(t, err) + + res, err := invokeContractMethod(bc, 10_00000000, cs1.Hash, "update", nef1b, manifB) + require.NoError(t, err) + checkFAULTState(t, res) + }) + + cs1.Script = append(cs1.Script, byte(opcode.RET)) + nef1, err = nef.NewFile(cs1.Script) + require.NoError(t, err) + nef1b, err = nef1.Bytes() + require.NoError(t, err) + cs1.UpdateCounter++ + + t.Run("update script, positive", func(t *testing.T) { + res, err := invokeContractMethod(bc, 10_00000000, cs1.Hash, "update", nef1b, nil) + require.NoError(t, err) + require.Equal(t, vm.HaltState, res.VMState) + t.Run("_deploy called", func(t *testing.T) { + res, err := invokeContractMethod(bc, 1_00000000, cs1.Hash, "getValue") + require.NoError(t, err) + require.Equal(t, 1, len(res.Stack)) + require.Equal(t, []byte("update"), res.Stack[0].Value()) + }) + t.Run("check contract", func(t *testing.T) { + res, err := invokeContractMethod(bc, 1_00000000, mgmtHash, "getContract", cs1.Hash.BytesBE()) + require.NoError(t, err) + require.Equal(t, 1, len(res.Stack)) + compareContractStates(t, cs1, res.Stack[0]) + }) + }) + + cs1.Manifest.Extra = "update me" + manif1, err = json.Marshal(cs1.Manifest) + require.NoError(t, err) + cs1.UpdateCounter++ + + t.Run("update manifest, positive", func(t *testing.T) { + res, err := invokeContractMethod(bc, 10_00000000, cs1.Hash, "update", nil, manif1) + require.NoError(t, err) + require.Equal(t, vm.HaltState, res.VMState) + t.Run("check contract", func(t *testing.T) { + res, err := invokeContractMethod(bc, 1_00000000, mgmtHash, "getContract", cs1.Hash.BytesBE()) + require.NoError(t, err) + require.Equal(t, 1, len(res.Stack)) + compareContractStates(t, cs1, res.Stack[0]) + }) + }) + + cs1.Script = append(cs1.Script, byte(opcode.ABORT)) + nef1, err = nef.NewFile(cs1.Script) + require.NoError(t, err) + nef1b, err = nef1.Bytes() + require.NoError(t, err) + cs1.Manifest.Extra = "update me once more" + manif1, err = json.Marshal(cs1.Manifest) + require.NoError(t, err) + cs1.UpdateCounter++ + + t.Run("update both script and manifest", func(t *testing.T) { + res, err := invokeContractMethod(bc, 10_00000000, cs1.Hash, "update", nef1b, manif1) + require.NoError(t, err) + require.Equal(t, vm.HaltState, res.VMState) + t.Run("check contract", func(t *testing.T) { + res, err := invokeContractMethod(bc, 1_00000000, mgmtHash, "getContract", cs1.Hash.BytesBE()) + require.NoError(t, err) + require.Equal(t, 1, len(res.Stack)) + compareContractStates(t, cs1, res.Stack[0]) + }) + }) +} + +func TestGetContract(t *testing.T) { + bc := newTestChain(t) + defer bc.Close() + + mgmtHash := bc.ManagementContractHash() + cs1, _ := getTestContractState(bc) + err := bc.contracts.Management.PutContractState(bc.dao, cs1) + require.NoError(t, err) + + t.Run("bad parameter type", func(t *testing.T) { + res, err := invokeContractMethod(bc, 1_00000000, mgmtHash, "getContract", []interface{}{int64(1)}) + require.NoError(t, err) + checkFAULTState(t, res) + }) + t.Run("not a hash", func(t *testing.T) { + res, err := invokeContractMethod(bc, 1_00000000, mgmtHash, "getContract", []byte{1, 2, 3}) + require.NoError(t, err) + checkFAULTState(t, res) + }) + t.Run("positive", func(t *testing.T) { + res, err := invokeContractMethod(bc, 1_00000000, mgmtHash, "getContract", cs1.Hash.BytesBE()) + require.NoError(t, err) + require.Equal(t, 1, len(res.Stack)) + compareContractStates(t, cs1, res.Stack[0]) + }) +} + +func TestContractDestroy(t *testing.T) { + bc := newTestChain(t) + defer bc.Close() + + mgmtHash := bc.ManagementContractHash() + cs1, _ := getTestContractState(bc) + // Allow calling management contract. + cs1.Manifest.Permissions = []manifest.Permission{*manifest.NewPermission(manifest.PermissionWildcard)} + err := bc.contracts.Management.PutContractState(bc.dao, cs1) + require.NoError(t, err) + err = bc.dao.PutStorageItem(cs1.ID, []byte{1, 2, 3}, &state.StorageItem{Value: []byte{3, 2, 1}}) + require.NoError(t, err) + + t.Run("no contract", func(t *testing.T) { + res, err := invokeContractMethod(bc, 1_00000000, mgmtHash, "destroy") + require.NoError(t, err) + checkFAULTState(t, res) + }) + t.Run("positive", func(t *testing.T) { + res, err := invokeContractMethod(bc, 1_00000000, cs1.Hash, "destroy") + require.NoError(t, err) + require.Equal(t, vm.HaltState, res.VMState) + t.Run("check contract", func(t *testing.T) { + res, err := invokeContractMethod(bc, 1_00000000, mgmtHash, "getContract", cs1.Hash.BytesBE()) + require.NoError(t, err) + checkFAULTState(t, res) + }) + + }) +} + +func compareContractStates(t *testing.T, expected *state.Contract, actual stackitem.Item) { + act, ok := actual.Value().([]stackitem.Item) + require.True(t, ok) + + expectedManifest, err := json.Marshal(expected.Manifest) + require.NoError(t, err) + + require.Equal(t, 5, len(act)) + require.Equal(t, expected.ID, int32(act[0].Value().(*big.Int).Int64())) + require.Equal(t, expected.UpdateCounter, uint16(act[1].Value().(*big.Int).Int64())) + require.Equal(t, expected.Hash.BytesBE(), act[2].Value().([]byte)) + require.Equal(t, expected.Script, act[3].Value().([]byte)) + require.Equal(t, expectedManifest, act[4].Value().([]byte)) +} diff --git a/pkg/core/native_neo_test.go b/pkg/core/native_neo_test.go index 0e2da1511..4ef8759d4 100644 --- a/pkg/core/native_neo_test.go +++ b/pkg/core/native_neo_test.go @@ -309,8 +309,8 @@ func TestNEO_TransferOnPayment(t *testing.T) { bc := newTestChain(t) defer bc.Close() - cs, _ := getTestContractState() - require.NoError(t, bc.dao.PutContractState(cs)) + cs, _ := getTestContractState(bc) + require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs)) const amount = 2 tx := transferTokenFromMultisigAccount(t, bc, cs.Hash, bc.contracts.NEO.Hash, amount) diff --git a/pkg/core/native_oracle_test.go b/pkg/core/native_oracle_test.go index 7cea35cea..d914d579b 100644 --- a/pkg/core/native_oracle_test.go +++ b/pkg/core/native_oracle_test.go @@ -109,7 +109,7 @@ func TestOracle_Request(t *testing.T) { orc := bc.contracts.Oracle cs := getOracleContractState(orc.Hash) - require.NoError(t, bc.dao.PutContractState(cs)) + require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs)) gasForResponse := int64(2000_1234) var filter = "flt" diff --git a/pkg/core/state/contract.go b/pkg/core/state/contract.go index f616d4d79..743713fce 100644 --- a/pkg/core/state/contract.go +++ b/pkg/core/state/contract.go @@ -1,12 +1,18 @@ package state import ( + "encoding/json" + "errors" + "math" + "math/big" + "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/manifest" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) // Contract holds information about a smart contract in the NEO blockchain. @@ -19,21 +25,81 @@ type Contract struct { } // DecodeBinary implements Serializable interface. -func (cs *Contract) DecodeBinary(br *io.BinReader) { - cs.ID = int32(br.ReadU32LE()) - cs.UpdateCounter = br.ReadU16LE() - cs.Hash.DecodeBinary(br) - cs.Script = br.ReadVarBytes() - cs.Manifest.DecodeBinary(br) +func (c *Contract) DecodeBinary(r *io.BinReader) { + si := stackitem.DecodeBinaryStackItem(r) + if r.Err != nil { + return + } + r.Err = c.FromStackItem(si) } // EncodeBinary implements Serializable interface. -func (cs *Contract) EncodeBinary(bw *io.BinWriter) { - bw.WriteU32LE(uint32(cs.ID)) - bw.WriteU16LE(cs.UpdateCounter) - cs.Hash.EncodeBinary(bw) - bw.WriteVarBytes(cs.Script) - cs.Manifest.EncodeBinary(bw) +func (c *Contract) EncodeBinary(w *io.BinWriter) { + si, err := c.ToStackItem() + if err != nil { + w.Err = err + return + } + stackitem.EncodeBinaryStackItem(si, w) +} + +// ToStackItem converts state.Contract to stackitem.Item +func (c *Contract) ToStackItem() (stackitem.Item, error) { + manifest, err := json.Marshal(c.Manifest) + if err != nil { + return nil, err + } + return stackitem.NewArray([]stackitem.Item{ + stackitem.Make(c.ID), + stackitem.Make(c.UpdateCounter), + stackitem.NewByteArray(c.Hash.BytesBE()), + stackitem.NewByteArray(c.Script), + stackitem.NewByteArray(manifest), + }), nil +} + +// FromStackItem fills Contract's data from given stack itemized contract +// representation. +func (c *Contract) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + bi, ok := arr[0].Value().(*big.Int) + if !ok { + return errors.New("ID is not an integer") + } + if !bi.IsInt64() || bi.Int64() > math.MaxInt32 || bi.Int64() < math.MinInt32 { + return errors.New("ID not in int32 range") + } + c.ID = int32(bi.Int64()) + bi, ok = arr[1].Value().(*big.Int) + if !ok { + return errors.New("UpdateCounter is not an integer") + } + if !bi.IsInt64() || bi.Int64() > math.MaxUint16 || bi.Int64() < 0 { + return errors.New("UpdateCounter not in uint16 range") + } + c.UpdateCounter = uint16(bi.Int64()) + bytes, err := arr[2].TryBytes() + if err != nil { + return err + } + c.Hash, err = util.Uint160DecodeBytesBE(bytes) + if err != nil { + return err + } + bytes, err = arr[3].TryBytes() + if err != nil { + return err + } + c.Script = make([]byte, len(bytes)) + copy(c.Script, bytes) + bytes, err = arr[4].TryBytes() + if err != nil { + return err + } + return json.Unmarshal(bytes, &c.Manifest) } // CreateContractHash creates deployed contract hash from transaction sender diff --git a/pkg/core/state/contract_test.go b/pkg/core/state/contract_test.go index 5283b5acc..73d4e5ecf 100644 --- a/pkg/core/state/contract_test.go +++ b/pkg/core/state/contract_test.go @@ -1,6 +1,8 @@ package state import ( + "encoding/json" + "math" "testing" "github.com/nspcc-dev/neo-go/internal/testserdes" @@ -8,6 +10,7 @@ import ( "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/stackitem" "github.com/stretchr/testify/require" ) @@ -58,3 +61,41 @@ func TestCreateContractHash(t *testing.T) { require.NoError(t, err) require.Equal(t, "e56e4ee87f89a70e9138432c387ad49f2ee5b55f", CreateContractHash(sender, script).StringLE()) } + +func TestContractFromStackItem(t *testing.T) { + var ( + id = stackitem.Make(42) + counter = stackitem.Make(11) + chash = stackitem.Make(util.Uint160{1, 2, 3}.BytesBE()) + script = stackitem.Make([]byte{0, 9, 8}) + manifest = manifest.DefaultManifest("stack item") + manifestB, _ = json.Marshal(manifest) + manifItem = stackitem.Make(manifestB) + + badCases = []struct { + name string + item stackitem.Item + }{ + {"not an array", stackitem.Make(1)}, + {"id is not a number", stackitem.Make([]stackitem.Item{manifItem, counter, chash, script, manifItem})}, + {"id is out of range", stackitem.Make([]stackitem.Item{stackitem.Make(math.MaxUint32), counter, chash, script, manifItem})}, + {"counter is not a number", stackitem.Make([]stackitem.Item{id, manifItem, chash, script, manifItem})}, + {"counter is out of range", stackitem.Make([]stackitem.Item{id, stackitem.Make(100500), chash, script, manifItem})}, + {"hash is not a byte string", stackitem.Make([]stackitem.Item{id, counter, stackitem.NewArray(nil), script, manifItem})}, + {"hash is not a hash", stackitem.Make([]stackitem.Item{id, counter, stackitem.Make([]byte{1, 2, 3}), script, manifItem})}, + {"script is not a byte string", stackitem.Make([]stackitem.Item{id, counter, chash, stackitem.NewArray(nil), manifItem})}, + {"manifest is not a byte string", stackitem.Make([]stackitem.Item{id, counter, chash, script, stackitem.NewArray(nil)})}, + {"manifest is not correct", stackitem.Make([]stackitem.Item{id, counter, chash, script, stackitem.Make(100500)})}, + } + ) + for _, cs := range badCases { + t.Run(cs.name, func(t *testing.T) { + var c = new(Contract) + err := c.FromStackItem(cs.item) + require.Error(t, err) + }) + } + var c = new(Contract) + err := c.FromStackItem(stackitem.Make([]stackitem.Item{id, counter, chash, script, manifItem})) + require.NoError(t, err) +} diff --git a/pkg/core/storage/store.go b/pkg/core/storage/store.go index 4e099498c..485d45104 100644 --- a/pkg/core/storage/store.go +++ b/pkg/core/storage/store.go @@ -13,7 +13,6 @@ const ( DataMPT KeyPrefix = 0x03 STAccount KeyPrefix = 0x40 STNotification KeyPrefix = 0x4d - STContract KeyPrefix = 0x50 STContractID KeyPrefix = 0x51 STStorage KeyPrefix = 0x70 STNEP17Transfers KeyPrefix = 0x72 @@ -21,7 +20,6 @@ const ( IXHeaderHashList KeyPrefix = 0x80 SYSCurrentBlock KeyPrefix = 0xc0 SYSCurrentHeader KeyPrefix = 0xc1 - SYSContractID KeyPrefix = 0xc2 SYSVersion KeyPrefix = 0xf0 ) diff --git a/pkg/core/storage/store_test.go b/pkg/core/storage/store_test.go index 9b1e579e0..c20904bbd 100644 --- a/pkg/core/storage/store_test.go +++ b/pkg/core/storage/store_test.go @@ -11,7 +11,6 @@ var ( DataBlock, DataTransaction, STAccount, - STContract, STStorage, IXHeaderHashList, SYSCurrentBlock, @@ -23,7 +22,6 @@ var ( 0x01, 0x02, 0x40, - 0x50, 0x70, 0x80, 0xc0, diff --git a/pkg/interop/blockchain/blockchain.go b/pkg/interop/blockchain/blockchain.go index a8d8e1659..fffd667fa 100644 --- a/pkg/interop/blockchain/blockchain.go +++ b/pkg/interop/blockchain/blockchain.go @@ -5,7 +5,6 @@ package blockchain import ( "github.com/nspcc-dev/neo-go/pkg/interop" - "github.com/nspcc-dev/neo-go/pkg/interop/contract" ) // Transaction represents a NEO transaction. It's similar to Transaction class @@ -95,11 +94,3 @@ func GetTransactionFromBlock(heightOrHash interface{}, index int) interop.Hash25 func GetTransactionHeight(hash interop.Hash256) int { return 0 } - -// GetContract returns contract found by the given script hash (160 bit in BE -// format represented as a slice of 20 bytes). Refer to the `contract` package -// for details on how to use the returned structure. This function uses -// `System.Blockchain.GetContract` syscall. -func GetContract(scriptHash interop.Hash160) *contract.Contract { - return &contract.Contract{} -} diff --git a/pkg/interop/contract/contract.go b/pkg/interop/contract/contract.go index cd8ab67e3..a42be36c5 100644 --- a/pkg/interop/contract/contract.go +++ b/pkg/interop/contract/contract.go @@ -5,15 +5,6 @@ package contract import "github.com/nspcc-dev/neo-go/pkg/interop" -// Contract represents a Neo contract and is used in interop functions. It's -// a data structure that you can manipulate with using functions from -// this package. It's similar in function to the Contract class in the Neo .net -// framework. -type Contract struct { - Script []byte - Manifest []byte -} - // CallFlag specifies valid call flags. type CallFlag byte @@ -30,30 +21,6 @@ const ( NoneFlag CallFlag = 0 ) -// Create creates a new contract using a set of input parameters: -// script contract's bytecode (limited in length by 1M) -// manifest contract's manifest (limited in length by 2 KiB) -// It returns this new created Contract when successful (and fails transaction -// if not). It uses `System.Contract.Create` syscall. -func Create(script []byte, manifest []byte) *Contract { - return &Contract{} -} - -// Update updates script and manifest of the calling contract (that is the one that calls Update) -// to the new ones. Its parameters have exactly the same semantics as for -// Create. The old contract will be deleted by this call, if it has any storage -// associated it will be migrated to the new contract. New contract is returned. -// This function uses `System.Contract.Update` syscall. -func Update(script []byte, manifest []byte) { - return -} - -// Destroy deletes calling contract (the one that calls Destroy) from the -// blockchain, so it's only possible to do that from the contract itself and -// not by any outside code. When contract is deleted all associated storage -// items are deleted too. This function uses `System.Contract.Destroy` syscall. -func Destroy() {} - // IsStandard checks if contract with provided hash is a standard signature/multisig contract. // This function uses `System.Contract.IsStandard` syscall. func IsStandard(h interop.Hash160) bool { diff --git a/pkg/network/helper_test.go b/pkg/network/helper_test.go index 346266f62..764e0efe5 100644 --- a/pkg/network/helper_test.go +++ b/pkg/network/helper_test.go @@ -264,6 +264,9 @@ func (chain *testChain) GetUtilityTokenBalance(uint160 util.Uint160) *big.Int { } panic("TODO") } +func (chain testChain) ManagementContractHash() util.Uint160 { + panic("TODO") +} func (chain *testChain) PoolTx(tx *transaction.Transaction, _ ...*mempool.Pool) error { return chain.poolTx(tx) diff --git a/pkg/rpc/request/txBuilder.go b/pkg/rpc/request/txBuilder.go index a405d7de1..0c5f58336 100644 --- a/pkg/rpc/request/txBuilder.go +++ b/pkg/rpc/request/txBuilder.go @@ -1,40 +1,18 @@ package request import ( - "encoding/json" "errors" "fmt" "strconv" - "github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/smartcontract" - "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" - "github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" ) -// CreateDeploymentScript returns a script that deploys given smart contract -// with its metadata. -func CreateDeploymentScript(ne *nef.File, manif *manifest.Manifest) ([]byte, error) { - script := io.NewBufBinWriter() - rawManifest, err := json.Marshal(manif) - if err != nil { - return nil, err - } - neb, err := ne.Bytes() - if err != nil { - return nil, err - } - emit.Bytes(script.BinWriter, rawManifest) - emit.Bytes(script.BinWriter, neb) - emit.Syscall(script.BinWriter, interopnames.SystemContractCreate) - return script.Bytes(), nil -} - // expandArrayIntoScript pushes all FuncParam parameters from the given array // into the given buffer in reverse order. func expandArrayIntoScript(script *io.BinWriter, slice []Param) error { diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index d8cf65ed5..c01db8a29 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -57,7 +57,7 @@ type rpcTestCase struct { } const testContractHash = "743ed26f78e29ecd595535b74a943b1f9ccbc444" -const deploymentTxHash = "a72dfaebf9543964d74e803723dae6a86196e0915ae9d76b3cc57c3b2e3e8c49" +const deploymentTxHash = "8ec2d061ecb22115f1e25a8bf79e536134d0cd7f0ac31b89cc40960a21dc8171" const genesisBlockHash = "0542f4350c6e236d0509bcd98188b0034bfbecc1a0c7fcdb8e4295310d468b70" const verifyContractHash = "a2eb22340979804cb10cc1add0b8822c201f4d8a" @@ -1347,7 +1347,7 @@ func checkNep17Balances(t *testing.T, e *executor, acc interface{}) { }, { Asset: e.chain.UtilityTokenHash(), - Amount: "80009698770", + Amount: "80009634770", LastUpdated: 7, }}, Address: testchain.PrivateKeyByID(0).GetScriptHash().StringLE(), diff --git a/pkg/rpc/server/testdata/testblocks.acc b/pkg/rpc/server/testdata/testblocks.acc index 1d0bc04ede489d2e417c2f34643f8265cb7f1a7f..a3fc08b76bf72cd9e55d55e9047e786e55b4d758 100644 GIT binary patch delta 2154 zcma*ni$BwSAIEWiz@n?|<-py+4m9Rl5>#+nh<2NI+*>!SF@OE0yV@?3PsnJxj`6 zx6bYvme=`A$Oqy9d?Fz!{5%MYe4Bs1<-ch7bVmURn{*NEsL0|K80<;IUUUoU79pnSL+&kMceF~id1zvDg z2d_aYmRa~vDPh|XB=ZBhQ!TOIMEoJmALGRu8r_8vH9oyc()}iM((~i&R$bOT@y+_nd97PL;%7OSUcgX7BoYLfP1!GbS)`eBPA@AUeb<0-tFvF4A zainq}l9dZ=FAr4kz`*q9Ks~E6@PUm8%&=Yrj5%C^K=wI6l5GI^Ad=Cp#wh~!;TwRk zcwmG5gQ6h<>;Jj&$XOVNr8?l6rT~Yi*~60Q%6= z@-^VUCWtJdmk-bM43G zepU#YZ^GlLi{F-L^jHS|@m!h3Vu*H)4}QcjJsY1}J19yg;qJ>Ytq*^z+o4XSM^&R- z!I|`LYQM8|LSguxB`tLM-SjQ?eB;!l&lM`uJJxr;UJs3}@L6kEch+A%s25ULo7Yn5 zNPF2yL|Fz@53CpW9#B6%xm4y2MNris$URquu`SY+i~y@=>QERI69-6yrDzBDtcr&& zro!(ev7#)ZX#ZYJpRmew^q#vk^6oBb=RMc`$6ITrHrPMz-~p zXn^HdKMUMUeXo>O_-XvUe}HJ1aqnVXN1ks*c&sOdfQ>)tufeZ0Xx@bCvR$K3ry+U& zWI^y9!Nynu%mtjujN>sSJoXom2I|*j`R1?#(B}6l9xmveB*1|m#-#rH?>K%Ko~kjV z_t{z4Dz$h$5@* zyIQuDdJFjPgUVdceerO!U9S+7uGe!2vTBGeZlJ|D>R{(~eLc4UUBqFMxSO7$ms-^x z@$;6(K?K?Z9x-oYDlr%(x-A$K`L&XrvXF!>`1ZA4H8GzS`x`Z248++EKJf%(itC=P z-YCH5E5rEPa$R=gH0#1MQ^vPrf4DRED;?^-&%|T*AIpR5c;kgJXN&M}aM8qWYBj~* z{encJ#~l{r`Tv2&@&`1}tuODT5geexzz@hZ-c{a{gq~O7jIM9VMFl~!p9MUO1Pw_P z@IFSDAh)MpHzT!*l~}{l@zBYQ>qIb7;WPv}OCYhN_AIzuZq3hAI4P@pqguG+6}S0j zgDg`uB%1l>RVF~HKNqu>q4R3^p)LQTTb~3?!r?Z%=KkTg;sYW`H{DVjDgnCVGup967(g57@Q19y}gu@uSLn+!fOk?$Wqq@ymY!AFG zYx>V|qzmqu(;5G|ai#p|i%nw?2E`AhfsQ`*JN+tIe5|CWAa{kIN@+Gc1#}QT0&DOZk!=H6j$)v#juHjsn5Cln8@+=%g z49M-)_ubYDwfJb0ccM!naywowN$S~TBXLx=d1DmhgG4^vh%8(}wqpQNRS*AMX$9bB;v#0)ApWU7y5&-)DJwF^>oEX;rnH-AY&Tug* z{#?6cb?UC<(;N+>&m~$i-or`@BJhn5hD$=%I`yez5^(#R?pkBrL5DDi;mt||^0nWd F{}*Q>`@;YL delta 2094 zcmV+}2+{YVJK{Q!Oazg}8rx+_~sqsG}I63k)fdpM|c zu1L*F0w)EwMbqk&(HfPr$pPL2Q5OgR000000Fpiyv&y))9k)ofZw<+lA^eu%nSz~6 z9>(FXplm@K$((i5EF!42P(*-NVMKRV_=oQyQ~uQQ_AAt|<=2(1uF?N#6;Y3>^iY#vCUrPmuCs=EqI!|^nNLZh*F;C=;X6eA# z#}9wUM2nGsl%~QQ{jq+qBn0Qh^{4RS(~EedMPUMt{q>Z_R4ocC~h&oC<-E#p-tgvX&vxUl_J6j27=;eenAv0^Os0T*`8cJ)tRPeLO zq9?H9URp{Z;ga-R^E5@W(~Jy2LW&Vsse5&jlEfs<-H7JMU)^qS|J}f~%@U#S-}wiL zPuIcq8bQ-6;E=~k`RSh{KpOTX1KagRlhGQWlUox1Am5c;Af}7Zt`%zl ze9VkZ`}YCx4Ylsim?ha-q`Q(I(vx}l2zp+Yr&5`v*h6fD$+>@k)Y3|25%UsW0OB$MW*cK$U*j70AF%c3G!nK9K)>$e zZxXHwP$@@)s5^$GL%9|0Pi`P1T&F7QaiZ-6r)S(ph*Y@LUL1|xSAZ*h<3_t_imkd! zi|i#+YW7hZZqf(RerHLQ)>OdF4PUmIOwQxJA8G6BV7mB*ZYgDKe5ey+ zypz!ypp&Z@_DtOa00000008BG^0mZkO%xld_oAchbfms>QYRt!>I`HM?aHGqDhhu) zfU}59RT?v?{!#Zz48=Qz*H{#qzv%M- Date: Sun, 13 Dec 2020 19:08:47 +0300 Subject: [PATCH 04/14] rpc/client: get policy contract hash in Init() Drop hardcoded value. --- pkg/rpc/client/client.go | 5 +++++ pkg/rpc/client/policy.go | 13 ++++++++----- pkg/rpc/client/rpc_test.go | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 5 deletions(-) diff --git a/pkg/rpc/client/client.go b/pkg/rpc/client/client.go index 628f342a2..904b0dff4 100644 --- a/pkg/rpc/client/client.go +++ b/pkg/rpc/client/client.go @@ -127,6 +127,11 @@ func (c *Client) Init() error { return fmt.Errorf("failed to get GAS contract scripthash: %w", err) } c.cache.nativeHashes["gas"] = gasContractHash.Hash + policyContractHash, err := c.GetContractStateByAddressOrName("policy") + if err != nil { + return fmt.Errorf("failed to get Policy contract scripthash: %w", err) + } + c.cache.nativeHashes["policy"] = policyContractHash.Hash c.initDone = true return nil } diff --git a/pkg/rpc/client/policy.go b/pkg/rpc/client/policy.go index efffcd492..38fafd97b 100644 --- a/pkg/rpc/client/policy.go +++ b/pkg/rpc/client/policy.go @@ -8,9 +8,6 @@ import ( "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) -// PolicyContractHash represents a hash of native Policy contract. -var PolicyContractHash, _ = util.Uint160DecodeStringBE("e9ff4ca7cc252e1dfddb26315869cd79505906ce") - // GetMaxTransactionsPerBlock invokes `getMaxTransactionsPerBlock` method on a // native Policy contract. func (c *Client) GetMaxTransactionsPerBlock() (int64, error) { @@ -28,7 +25,10 @@ func (c *Client) GetFeePerByte() (int64, error) { } func (c *Client) invokeNativePolicyMethod(operation string) (int64, error) { - result, err := c.InvokeFunction(PolicyContractHash, operation, []smartcontract.Parameter{}, nil) + if !c.initDone { + return 0, errNetworkNotInitialized + } + result, err := c.InvokeFunction(c.cache.nativeHashes["policy"], operation, []smartcontract.Parameter{}, nil) if err != nil { return 0, err } @@ -42,7 +42,10 @@ func (c *Client) invokeNativePolicyMethod(operation string) (int64, error) { // IsBlocked invokes `isBlocked` method on native Policy contract. func (c *Client) IsBlocked(hash util.Uint160) (bool, error) { - result, err := c.InvokeFunction(PolicyContractHash, "isBlocked", []smartcontract.Parameter{{ + if !c.initDone { + return false, errNetworkNotInitialized + } + result, err := c.InvokeFunction(c.cache.nativeHashes["policy"], "isBlocked", []smartcontract.Parameter{{ Type: smartcontract.Hash160Type, Value: hash, }}, nil) diff --git a/pkg/rpc/client/rpc_test.go b/pkg/rpc/client/rpc_test.go index 3065538e5..a73e1db72 100644 --- a/pkg/rpc/client/rpc_test.go +++ b/pkg/rpc/client/rpc_test.go @@ -1467,6 +1467,8 @@ func wrapInitResponse(r *request.In, resp string) string { response = `{"id":1,"jsonrpc":"2.0","result":{"id":-1,"script":"DANORU9Ba2d4Cw==","manifest":{"name":"NEO","abi":{"hash":"0xde5f57d430d3dece511cf975a8d37848cb9e0525","methods":[{"name":"name","offset":0,"parameters":null,"returntype":"String"},{"name":"symbol","offset":0,"parameters":null,"returntype":"String"},{"name":"decimals","offset":0,"parameters":null,"returntype":"Integer"},{"name":"totalSupply","offset":0,"parameters":null,"returntype":"Integer"},{"name":"balanceOf","offset":0,"parameters":[{"name":"account","type":"Hash160"}],"returntype":"Integer"},{"name":"transfer","offset":0,"parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"}],"returntype":"Boolean"},{"name":"onPersist","offset":0,"parameters":null,"returntype":"Void"},{"name":"postPersist","offset":0,"parameters":null,"returntype":"Void"},{"name":"unclaimedGas","offset":0,"parameters":[{"name":"account","type":"Hash160"},{"name":"end","type":"Integer"}],"returntype":"Integer"},{"name":"registerCandidate","offset":0,"parameters":[{"name":"pubkey","type":"PublicKey"}],"returntype":"Boolean"},{"name":"unregisterCandidate","offset":0,"parameters":[{"name":"pubkey","type":"PublicKey"}],"returntype":"Boolean"},{"name":"vote","offset":0,"parameters":[{"name":"account","type":"Hash160"},{"name":"pubkey","type":"PublicKey"}],"returntype":"Boolean"},{"name":"getCandidates","offset":0,"parameters":null,"returntype":"Array"},{"name":"getŠ”ommittee","offset":0,"parameters":null,"returntype":"Array"},{"name":"getNextBlockValidators","offset":0,"parameters":null,"returntype":"Array"},{"name":"getGasPerBlock","offset":0,"parameters":null,"returntype":"Integer"},{"name":"setGasPerBlock","offset":0,"parameters":[{"name":"gasPerBlock","type":"Integer"}],"returntype":"Boolean"}],"events":[{"name":"Transfer","parameters":null}]},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":["NEP-5"],"trusts":[],"safemethods":["name","symbol","decimals","totalSupply","balanceOf","unclaimedGas","getCandidates","getŠ”ommittee","getNextBlockValidators"],"extra":null},"hash":"0xde5f57d430d3dece511cf975a8d37848cb9e0525"}}` case "gas": response = `{"id":1,"jsonrpc":"2.0","result":{"id":-2,"script":"DANHQVNBa2d4Cw==","manifest":{"name":"GAS","abi":{"hash":"0x668e0c1f9d7b70a99dd9e06eadd4c784d641afbc","methods":[{"name":"name","offset":0,"parameters":null,"returntype":"String"},{"name":"symbol","offset":0,"parameters":null,"returntype":"String"},{"name":"decimals","offset":0,"parameters":null,"returntype":"Integer"},{"name":"totalSupply","offset":0,"parameters":null,"returntype":"Integer"},{"name":"balanceOf","offset":0,"parameters":[{"name":"account","type":"Hash160"}],"returntype":"Integer"},{"name":"transfer","offset":0,"parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"}],"returntype":"Boolean"},{"name":"onPersist","offset":0,"parameters":null,"returntype":"Void"},{"name":"postPersist","offset":0,"parameters":null,"returntype":"Void"}],"events":[{"name":"Transfer","parameters":null}]},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":["NEP-5"],"trusts":[],"safemethods":["name","symbol","decimals","totalSupply","balanceOf"],"extra":null},"hash":"0x668e0c1f9d7b70a99dd9e06eadd4c784d641afbc"}}` + case "policy": + response = `{"id":1,"jsonrpc":"2.0","result":{"id":-3,"updatecounter":0,"hash":"0xac593e6183643940a9193f87c64ccf55ef19c529","script":"DAZQb2xpY3lBGvd7Zw==","manifest":{"name":"Policy","abi":{"methods":[{"name":"getMaxTransactionsPerBlock","offset":0,"parameters":null,"returntype":"Integer"},{"name":"getMaxBlockSize","offset":0,"parameters":null,"returntype":"Integer"},{"name":"getFeePerByte","offset":0,"parameters":null,"returntype":"Integer"},{"name":"isBlocked","offset":0,"parameters":[{"name":"account","type":"Hash160"}],"returntype":"Boolean"},{"name":"getMaxBlockSystemFee","offset":0,"parameters":null,"returntype":"Integer"},{"name":"setMaxBlockSize","offset":0,"parameters":[{"name":"value","type":"Integer"}],"returntype":"Boolean"},{"name":"setMaxTransactionsPerBlock","offset":0,"parameters":[{"name":"value","type":"Integer"}],"returntype":"Boolean"},{"name":"setFeePerByte","offset":0,"parameters":[{"name":"value","type":"Integer"}],"returntype":"Boolean"},{"name":"setMaxBlockSystemFee","offset":0,"parameters":[{"name":"value","type":"Integer"}],"returntype":"Boolean"},{"name":"blockAccount","offset":0,"parameters":[{"name":"account","type":"Hash160"}],"returntype":"Boolean"},{"name":"unblockAccount","offset":0,"parameters":[{"name":"account","type":"Hash160"}],"returntype":"Boolean"}],"events":[]},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":[],"trusts":[],"safemethods":["getMaxTransactionsPerBlock","getMaxBlockSize","getFeePerByte","isBlocked","getMaxBlockSystemFee"],"extra":null}}}` default: response = resp } @@ -1555,3 +1557,34 @@ func TestGetNetwork(t *testing.T) { require.Equal(t, netmode.UnitTestNet, c.GetNetwork()) }) } + +func TestUninitedClient(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + r := request.NewRequest() + err := r.DecodeData(req.Body) + require.NoErrorf(t, err, "Cannot decode request body: %s", req.Body) + // request handler already have `getversion` response wrapper + requestHandler(t, r.In, w, "") + })) + defer srv.Close() + endpoint := srv.URL + opts := Options{} + + c, err := New(context.TODO(), endpoint, opts) + require.NoError(t, err) + + _, err = c.GetBlockByIndex(0) + require.Error(t, err) + _, err = c.GetBlockByIndexVerbose(0) + require.Error(t, err) + _, err = c.GetBlockHeader(util.Uint256{}) + require.Error(t, err) + _, err = c.GetRawTransaction(util.Uint256{}) + require.Error(t, err) + _, err = c.GetRawTransactionVerbose(util.Uint256{}) + require.Error(t, err) + _, err = c.IsBlocked(util.Uint160{}) + require.Error(t, err) + _, err = c.GetFeePerByte() + require.Error(t, err) +} From e97cd9c032cfa3697c09afd52251ec823c47102c Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Sun, 13 Dec 2020 19:09:41 +0300 Subject: [PATCH 05/14] core: fail TestCreateBasicChain when saving the chain When regenerating RPC server test chain I usually need metadata this test outputs and the easiest way to get it is make it fail. --- pkg/core/helper_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/core/helper_test.go b/pkg/core/helper_test.go index 76d91f05f..319094690 100644 --- a/pkg/core/helper_test.go +++ b/pkg/core/helper_test.go @@ -213,6 +213,7 @@ func TestCreateBasicChain(t *testing.T) { bw := io.NewBufBinWriter() txSendRaw.EncodeBinary(bw.BinWriter) t.Logf("sendrawtransaction: %s", hex.EncodeToString(bw.Bytes())) + require.False(t, saveChain) } func initBasicChain(t *testing.T, bc *Blockchain) { From 1e9253f1f067650c1b6ca159405117c6042e2e12 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Sun, 13 Dec 2020 19:10:59 +0300 Subject: [PATCH 06/14] interop: rename Neo.Native.Call to System.Contract.CallNative --- cli/testdata/chain50x2.acc | Bin 45734 -> 45766 bytes pkg/core/interop/context.go | 2 +- pkg/core/interop/interopnames/names.go | 4 ++-- pkg/core/interops.go | 2 +- pkg/core/native/oracle.go | 2 +- pkg/rpc/server/server_test.go | 2 +- pkg/rpc/server/testdata/testblocks.acc | Bin 7586 -> 7586 bytes 7 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/testdata/chain50x2.acc b/cli/testdata/chain50x2.acc index 43ee863186792eb93b161deaa8e1b707d56bddfc..a9e5a8104fcef6e2b28b5e7f23fdc69d833aace7 100644 GIT binary patch literal 45766 zcmd44by!r<-}XC{z%Xtq;!LXH1fjl(dT{7 zbL2ha`Ae>gnTuh~-gEE!{;a+B+TXp06#xJ@-TgxHqiXm~DT{>Q@~L8jo>5=T>DSil ztL+VYf9-g^2CP2dYs2wIn@^wqmcHHyRyO!;yI0aM)e(VT-yy=;lG}mUPlVv89e?Ic#w?1GBJZd+ zxiiX5WkIdxqqnTECP#imFSl{%Xq?q?yIbr1ivPIQUJzJAS(}YII5i=CHZY#Q?J|Cu z02$>%$P2o(5}1VhtrF$L8ji56#~g4Nf)QZ6LRstcjp?MTgC$xz+}vt6@B&c;l1hqs)w{C3g6>?UQe}T{mkS zw#0!J!zVm(LJ-(o?F4-QC+kGx^eY>4Sqt-6rLTne>2k%h8|MbnLO5~g!N-Aa3ul~C zg>@M|Ua=R^FV}eMb(YXD6zu>5bCY>cBQ}^KHbIE_TXkJ%T z-I0dk2ZOLsvJm9G#E6>WS&Rg!VcDpAejULH+M0I2Fgm3_4Kn%T$Ql*L@HF1NH%2} zm~_uTEoRVF{4~#gpUj|=U`7|jv_7VRldf&ItgzfRoM9*4*ZY%r4+KVG#eUlJjvy5K zQeR3uXN?$1Z-S+HLv5&k@{=TdR8$D8g?L(_P&G=P{XR5gWPoMRWU;u!Zk~jG9fdBtqJGAy&f{_O3+}1N3%VijDzcO?m|8K8x zJcRMv;FbuXg1P#3)t@*uAh^MxxXKrjz~hd0zY1-3W1gOK8E;nzk1TkFGEPhZM zEA@0h$R*UHC932W1h$R8QLqWo?%}sn65Af=#*VA)a&JB}SV&F<&e{e`1EH)7s{xXzpKM+(jHPuPbk{G< zz~C^$UNScdlP3eZteeWsBdX!>0)VUW9emj*UsL|%-Ph1 zjGKv$%)!vByW+U0j{)Ts$4_7q4b-X+q{p_uor#aJ^s4?{_o*&$s_~ zJ8M(VyLP`8BIM~}D(K{7==pyx$<5Hl_5PFmzuWxzGu*vu>Sk)^@}E!XOGjqzWbf+m z>mxguI@ww}J6qb@{g-6p-y4LR?AhHa{(EcO{pEYN9A{St2YV+MQxj(wLpu{gr~hoI zyVqTuT%G@G`FCv$%}oFMefOKi)Z=a&lX2U*+SvGl4CO?{SnW*h>CNr`)Aj!Gfr){O ziGhjU#njp5kN?M0gAz`g^+?{u0C8~bbub2m^#?ldxEeC zc~~{`8yKp)5zl$}j?8VB5X@`-)6qf+fxuyG;FCArDx1hG!IjN?vr}~H?buko%_5s! zp5;ZsZ{LaX?M-E&u<4_5n`X0p#OWegXt@#nj{Jy|Bxgtas}S<`jQgM6T$azS#MSV{YsWS%U;X_* zU<9*X>M7?WX6a~rFk$5#gK2={Z8>S>-#q7_D>9=!N4bp#J%1FEjX3XYmWt%i^{eB# zU))0-kFE@C#5gsK@!Pf*6kXhRKl#YTaRn>Nwi%G{`+C+sjtx;-MILd9rdQFDNaf}! zlEvLR*7^u+Y};cNWg8+V{RDx_H_xK+Vj1}sk;|GhEIY@2PP(eLM84}37d!DAyl!7+ z+eJ_PIOSk8E2gLY#l}gj(X6b3VGvs;Yr(Xgp};!-$800o}@02is1i);t;F0Io*S#<&0Tr`vbszPWLa$&y#6kE-thmx+Ty&!39 zm)dtpmn7GIpSwj2x5|A~@1cK=AvCK_`HIwxN~93pr%b-L9@#3(4mr#%YVnDmC)~+1 z+7G_~ydOwB1mJb1Nwj_!b_;oe7HloCAxMs=E1f(8 zm8ah~=-~6f7p2yzK=I)jr&w;p;S&&;Gg^Dw&&xFyl~Y7RdvBYGm48cE0KrcafYQK(-02~|w@$Y!ti-<# z;D7ql{{i5=Cl3SYu1vloNmj&=)dd0~RI{>1&|&7$*Fct2FBq{T{Dj!<8PWnbU z>J{ed^P zCpaQ3vc5NtKB}CRLs%^?^Z{7=g%)7-Gx$^CP%mA-gP)i$LCMdGyE-5j_8Y+WC>F&% z^(LyQ9hPzR(3#WDCEpQr8*X|Rkt2W*Vrg=Ua^&+c*uE^bpM9tz?hFts(d{;Uyu zLlohh(|$tP{R_bR$(Dxz8ZVQ;=um$)v=a z<3?M!{5AE&kS;y7NBo)zv@WqYF36$?nMCiE_(5QYbUewl2pKQ?em0X#CzzFNh@j_3N z33jtp&I7#`m3a@;jjas`aH`@4BfwW-5U29Wh$WO+Ot$E3CD zR5br4!S%*b)A2y(jW8|gt)~TuxfPpnEIP?RRVrk~ zBG}#WIpjG7Jh~I{d(Zd#`Ts8L%d|+Z!U#T`;5G;t&XsFC^cLoYi#%k)44j?cc`JK4 z@x54~rA3^Y;jTjN$f5BNuL%aROmDc=JyS*IV4v;cT-{e`17eNKwlDP4V0}t*2@URm zUEZ^+gIw5e08`Cphi{9d0?Yi$Ux&5t9O)--`0ZIy+6BJ9c|Q4$-H??5;V2urXR;Q9 zUuFMnFwWaudUSP`@D=g6oj|X-u6NHb0PnZcLjaOtlAc}qy%t{gYD_Z=C<>9k+!DQl zo?4*6m+xQrpeW9Gkx8|xqAH6B(vsg&A?&)sJH%%m8Aq&tTN39vq7e%MQ)CfSLmTKy zv={Fg*q(jLj^OvHMxeu*&Wg)+p{S;-`$Ccvcy^MUp)471XJsH0tzu;*A2z}Iwm;Ofbf6F>Afcp11Jy`a`5r33?|Ja2X{uzxQW8&H@^;PCYxEDBpnc&F;@TXM zm46?Al_PtP5}siEzEGm_kB;cW|xX72)MKTd@4x3-*?ynGGXeQKT1Wk zKV|(e6gBqUZCfZ@Wh>l|cSkReXr{$KYht>jvgmc)2EGtFqidye>c6C$x!4KJ;nHLa zGVY4r)%^*%u-^a%MSVmXfFsuKkH^pmtm!^kEcQVgH=6#M$Y@8-oVr+sl|uI(K|OK0 z-{U<>&Q4@Vl>Va|!gY2Fh6GBVBW{x1-d_OT2jC$9#ewEaS2y*s8VoCDQ9q+h_cU9h zR3`GkDmA4g{A*&;{XT)XNUtTy2rRgxP4$Y76*N?)6}r|o1id;YFmRt{R8`g{8VV#Aq_X-k*M}TU@*Cl3RNTC1JFk){+MC=LQT5Awf%A;MrI zWeHhMW6Wb^k+xpAuji@CmOR&$tws5XI=vRAfjT5eDaeYrB3kr=;+A9o<))-%=-6{* zlW;NgFghqh)5r;^5zkdnE95x^0RLK;!14FQFHszm&FCN?bULS7^5#J<_YP#js>vJj zJ_RJYTaf8~ZO3H5+HzvD2Xs??M>G!Avf>D8-75RSQzob>*7>5KpxRw@m2&qLKB_?I zk3@RY)vFjx3dn{122h>Py5KZqspUL5XOlBhSB9WtGOEJjZIM*Zo{doo@jy~f910N!_04*@8s#9EoL;u9zf?CLe9OghI2dBHEB=Wf?sc?Qj7|u)cc1agiUN4J^umz@LfK$EyOHoe*Ap=*BD9;*?T1ie()+>Zs0{tMidY>6cW`m!Mtfwc~uoMC)j zNMR#Y6(8KTqCjA*ZPAlJk66N!%e6t%vcRzK`vDPFG2Z?#Ygj?Hx5Aqg^wJe9H<%a} zUeeXZ82Kf#`>PxEs`QBS?W*u+_=V&DI)F%j$mzW&4+A&{r{`AAlBt?50s^uXbs_@? zjO2t4AReC z;^YSN-e}o|*J(O4`QJj%RUpKtmuZ3Z@BThMYBKYg?fq>FdDIe-^UqAB>hD=zQ zptg=~LLQo0N=}RV?dJw7e%zBI?C7FCILnSQlu>T{QUm6X;)l708SG`KB&ZIkED^}V7^3R%_{BTK>47^ zo?n|)P20+PVpnrrkC3k!mORaf;BKZ6K+%cb_Y1)L9{3>uKkh(B7LC*wA7odw2j(1y zjnz=Jewp6>Sg3GeR%|0QK%x^E$6+KjN!vInR~p(bg8@XAPN^x416DtgD`@bJ1A)oU zZkxpslw1?)Boi6P0fTLlL_aEi;(-zUjt6ZgD6%RsMH1HR+)!4Y<2cV9aRp#8p4=+Q zyKDBo!2do;+q4D(=Omn6Rew10mB;YrmuubS@b61xgjC%8{Q`8*YH zV2DR-PK-)=Do#&G@#P{kuX|Uryh5QV;a>+3`42h0_vB#!wKxYCR-p~$i%LO2;l^(y zoW5C|Hz$zgln#KO`r#SY`W(X9ym7#10%YE|EVv865eLi=wko`3gSRI1pR6gK@^R&z zObQdkSY8rbeNM+|L&r!>Wupr4YKJ_h0F=KLHe#%y^E1jec#HuAw8cLbjeDlXy99zv zSV&5x2^^8GL!i^)qvKL0x@SDG^wR4r?smiS>3MTRr`q!lehf3gG>^YF9F3Eplcg_B zDXyj3Qt><@BnwddP6oNK-vEBAX!}~2wp;rd#<3w2V0#fhqI>pXm-_G(r>Su{HgPSP z>>7g1Vhe5lW4I|1I(o`b#@CO0sx8+ePbHc3BZ*!cNiIqd;l7SY z1A#*xX;r}o`2$uG7g5qCX-4Ni8|fnX9utOomcB@ADbSq7?}b7G4XXGbxkoG0fz_$i zTm!!JTK0U_?Mu=A?#K=T&mMnbju2@Egdr0VPxS1_F5HH`;eCFF>ql;~vw_rU*IFfj zDNV4^^IcOXIJX7LaG~O{-lWO8KeT$#VAxALhJPJE)Ia3(-jjy`)P(I|g)S)kniK*8 zPVBX|6ML0YkCs7}Qy!YP7ZC@^Cb?D|^a*oUA3=3xK`{+}(-mPm7LS==xtCymu0YR{ ze6XDKi*a4dcDSrhSM`gODMNMITh$7rYl1wd0JOgrmP@``1HKd-o|Xgw`-I1QvwdM+ zepZA`7%W*0p^{Yh_8@fdj2PiH5FbFko^4oTVfH$t;dI;#FekTG?8e*FD>hk(xyNG+ z^YZTWJ*vcN0_^oR-Pf??6Uc@A1`w&yYS;U1t%ZHJQsX`GO`J{p~6NQmdx4l0M zOPz!c1g2$(c|{OyjaUjhjbNRcvN)I%>8`Vj_n3~55w@B)nw*%Y#z4sWRTY=$m9re2&u)KXZK0>Ar0c|~X{#QnIHY?EM!EWo-G z^h2vcW1Os76*)p3J#A8wFJR~~?^Qmjs37h;@3}Bzy&8LfVjgJUjTb@`<*VI}3OehT5SN1Wa zZs!;rw3M@{&}sD@!O5o?#5zrOgk0Ef0B=dE_dKPsf4rN5HzK!G^&W9pORgWIs!2F7 zeVXaLw{!X|RP$9#;+BRR_~VLlgjVXSO{>k6#GzLoWpm*lD<%y50`PvQ{t$rB$I=6g z9HssA1;#uVY&D+x+=F>(@*83!%FIWmh+PbXb*=Np$YPFiPn3|pqB37I_8R3C53r}7 znp8-&(MchIz_CzgxYZ#C-~!=`*lI_%JvY*39}Y0OQ69-FNdrqvDB$`W(KTQzIa>h$ zPh!mDwl|E9@EZHW1uU`%+_-V}A_#nxm_qs%rPNx-Od-?fruZpavA#|wo!~YSd&Q(Y zhXQW$Tm{S03(x!)G>AC)Yj4+PU~N>I){o;q?l;JWdQ4>h>i}Z@A*c7AJPe>p$mx}5 zC5@3p7zk)nZaAi6_J%ZT3$mQ@e2GkiLfSKrBi!J13Fz{djpJ>e4`N24EpHGrvk!CW zYC6x&m3Vq-bUz>12|y8@1(UZl9M`9OKJ!Tu~Q^6k&qcnY#tx5 zZqw7-jGG&Mj7jj~39N@h5tXM${!P_F{KR z_VomWwro=nSSLIioyW@|;?hy-t~~tAJDTiy{x^*{YV3$Sa^Ov6ydHfVh+OPBH9QiQ z+NQ1*Sh`-o+ZQ2|ig4AL+VSlS37!j%ptV{NwQue_Nd zqk&Z$2tjqD)~Fh^MOyWw$2x*VV=Y3R6;y1lEGcBdJiuF_qaPg-dQ^|)XKUln)?W8unq`j=#V#DDZLutcy?}X>n;$N{ zrXZer;duDg+Oir9{XiizKme;RDKwM`%< zk;c%;%jUY! z6AGsH8wqwvS?=b0oQ@;yhmxk1!B8!*Cl3Qy zl8k5`NnmOTbvFxI)jz{Aa?T4fSXq%S{$MF}6uwCLNau9ppI$;Qh5Q6yC6tfyUDM(}p4jWH(sCpunbty>FuT`RQ zV~>UBtrJ;ic{jKv5{#u3#ms%f+oy!A)#NKCAQ$!D3gTA=7B0Ima9Jot@O1xCB$l&z`J7 z6~cV0PphLwpO*D~dyokN&ruuMHi|YZ^GI_Lba@m6` zgCm)K8s$w@$-vZL@u^@0eL&j^uQ;-)BQF0t@t2;_%9{bK!eL6KfUBl|9l*zb$mzW& z4+A)Rp^L_KWzEs`9s~roSVuZ*SS?@<|`xklXe%tI8OOH2#U z72chQ_DIe?sB~Q?vxL`SB4<+lW<7m$0-4$V@%-ZQ;#(F+SK>Lya|*!!Yhig`Fu!!m zWg5tcf`H9mcG~bKgaS(BAQJ}O_$jfe^;9#9tDvZ29&jW(IZwwsIX?YKI3qGj8}w(^-A|zIQbaUH`=Ag?nru>P(!aR7{t#Us@Lp|qyMi1NbrZ8 z-h1*efc#@wVxOgNoIuxil`!Xt@oKL=lHr0O%jr{<*vY{m6xKnu-0Ts|HxtAr`!jwj z(!Ih$7;pL=d{F(d=`tN&>~Pohhj#S#=<%*Jmn<nZYhpOB9#$|0NTAr`&j5o0J z##uuFdmUg2mZs;0uhYfPNJb<`L6)g73ADb|pBm-r@E{lV8^9qL{nX?E1xlL40)BqQ z*JzP>nYP`1#6j0tT2xlXVS6{WA~L+bFJVMt9`iz>H(SpHmU?LoRP91Bf9VIz4UvBN z1>pTG+(Q5sq`qonvR?4^PT}KZ%vv^$eVG_b$?qg_BRq{dBz|Vf6c^c((sr0})iEQB zTN3Ttr10Epdk(fVEmjF@Srqkd2%699Bi_dSAz)T2754N8bdL4GP`E#(;I4k;X!}n| zv(I{Ner}mN6maxGYJ9RudRQ_WnoeLFiHd5_v*|a_-(Y~iPk3R<=0}{;M`$x{Lzu>3 zHvQ22?KTHP4p9%kzP>=DF7@`jy_p@i9h#yD^GH37vtbwLL~|;(#Pg9$rCTlj=Zmok z|B%yrPaXzvCT=lWWOw|nh6Xo_lz*Y3 z#tMtIXXe2Yd4xwqu{K5@r}rFlQ6TqGXDZF1s*a*xrp!0gRl{$-&9OicN!nftilZ!^A9RB>KIRS4$oVP_rMKfU01 z8bY7~t| zQfL&)#L<)*x*9KGPjD}RgdT>Zgh?u|wZkSu5}t@nQugx`Gn|O4G8O$*ijL9wc&8|Q zyRDI50N&3QKLlXjflF^fmo4K?XBDmr(~~&c8~)`V!R3R=rTnZ~R3aHU7T`fAC!7oH zZb1@xl-a^gn1Qr~V?~@9YO=a7=to!}@T8SRG3$V*zPgKaaQR7cAQrcgZ3tYP77n4` z4}`g~HA9)~RBsR4pS-VJS~Xjt^p*i|ZB@Yci0uReUNfINv_ar+iqDo$)K`<3Sqyc! zCe3_nhdMtT&?bF7vQNY=nzGB*)U>Nd8Ngh;E`Bi>xF-R9g7AJE`Vm)1a;M_SMRga{ zzrF_~{zFdhJ$V?w{i`;EN)^_Lno|%ErdV(_h~wnhHZJb6HF_hmTG-MWz$3 z3>oXIhEt6G0`Pts{2>6dcF_5CA6a!A!xA4)6Zgv>ZNK)E>jJ0Agp6tu`#xD( zcX%aDq(%Q0$hJ`#93;*cUC0uz3AZf$3Ob4b1g<<$b1uu%KQwMvkoL)Uf}-{-6zV-? zvM`_HmbeHc@*T73vaTo4TUuY)heE*TqH>IreCp?(S-cZ=vH;t4~2YRChvAMSGN`C4%ZY*tYNy4imHnMpI611b#T% z7``$}{nr5`{X%(m7bt;KwtYQ{~j`D5~(4GE7;YTQ&vEy&P zMaXjsAp2`!1GRXaSm(6xns-A`@my1z&M=1dazn_3y)g%54$dnLOQ;_$B?)_YW+7Ev zG?l`npBq(O=A1K)6DVpjQv}kIQ09j3-#bAKrZYzfJ#G_ zj?)ny$j`x0>VnD50l22`tf|;xnInsyNHMg}TO9ZXrYGMO+kUPj6aF#mYPrfDl!Z4L zD$G?9yD*W=@p9}JfcK|s9s=-@H8Ah`7FXt||GhFShW$2?V(8}`>}Pwb#T%#g9o$+> z^zMr1#CdI>*}YTW7l|`i=jNH4a_d_&&Ice1ig&Bt&4OC{A+@n~c!y9uqpSBZuH)%3 zej(ASdC--E-C~>mlY~JqFFR$uF!s_Sh-dDiOS$RwIwDoNzRxw($WyJo^E-s0KUvfN zJfFA}z&>3Ood7IJdwQzBah@$woQXrmjTY;b?AOY?NAAkj#>1bdDOsT z0+|~L{h0dFe;q*bKjie@lZOGUqi)%J!q$$k?g|3FV9Vs#DB<=lt%NM6%gRpEi>P%G zEA+ImuM=f7#}1ykF>B8JTjQ2_n3DuYuW_6hF*+_5|y* z1bfCtQC8E&sM}7lG-Yx!gB9QIpp#`J)I=`_~-TVMak77o@jSVfjHK*8a4X<|(aFKVSkuV12!(fVf?RLmfO`BqE`> zk58NF*vB#rm}b|8#oACg&)atN0;IjX-*_@aLir zsc1DpOI4Vdu{~tuGL3c<)&hI0&&4yj>X^PDib1fq|JFKKDvn$Vnvz ztCwA7(OcKUTQmCozYZYfA98x{$-@9j5RYv4g)?8F!GnM(+13ZB=QjSf#gOHcl;=2l zBl}`0sT%LC(EFh9KbgQ{UA9o+CV}I>>~b%JKemkBDqC(hL|rakwizh9ws8v& zxv<{=+Rzx{AwcC82!I7!G%7Se26VXk(c0ur%wh0qzL;PZ9r9kbjs1qDRfRCbNz*<_ z$4AHnb)FXelm$I41%~e76TbkwKVSL~fO39<^_IXyW{&yK{uJB%!_Z6GW)lV22ko(+ zKi0STT~RUlOY$(ORixYAoFQ(g=-vkLMLhqC-505OGx}uxHr~FOPVk__Z-uk%K;7tjju}zmC|;0-bq_z z&DP-9Y#I=#c`RrEc}@W|e=RJb!?EVd2G>|30|ZQE=$9#7K?(>wfDAxkSes;uw3M7k z!}0k`L-Z3HJb&_CxLGf8llSkeJKmHe7qZ!{rnJ0R44^USX61ACj`iGZR*kQA%PK(< zM`H1UT-a{_b#zb>i$KREBtEc++L({p`P`$9YQ99QE(r51w-h~<-B*|0sv$Pm#k}4v z=gpAcY5rMQT3ibh;6SV!TJjg2nfwLd{fXd*0Hi`vp$LaZIPIY9aH{OUkw_+K?*f+! zVqPUs!nk>{8z((tpzlYMo;23c z81(hCc#b6o{WLQi9ZUe#F%K^Wr}GFi*-%;+4(W+Ug{x2NUst_FncPICvwp*UdRWzzxbHX#9+SHK#{a~ zknbVm{`wN+!hQqDBFM)4ej(~H_d9a?Dy1tL&dlKah0?FW2IEmgZ{$q)%VI0GDx)g^ zN~Pi#GGuVbYIVi{XW&An&!h{}+JZ*G)Gq+<3l<&%P>8dH&gy3VOBA<ga(w*ygLlTVj{qA;~sb4Y; zQV2igHIiNwd-#aogs4%gIm5o0Bx1OfE+~^R|G=H|x<4;LdA5bk`<#T5{rTdY`x!Wh zgMa>jKwbL|z~>nU655G)=qi1Dnf?bb<)4)456#!NH|=Q|uJ8aUd~9mp#+UNPud(}e zhTeR$J}8?tflloNm0Z%Wl^a8;9slb9(*7Z*_ntfqpa**#f64P_w4R?pK>DBZJ4%kv zPmQY~%PCD4=Oj%q(i{z}*8tDgtwA2Gms&%8VINAPD_uV%j}bpcT=Qi5QT({w{hBA6 zHd?9>N-!m9kb1vL(TbnNE0YiMoC4_nS{PQIjUIN3ZiXcc2uSTkH*AZj|KS9}OZ~^h zV(Ok>=|lNmzm+0hG)V9#Y@0__L^C%Gr5X=zDiIr#8AWU7$az9L-4Zq2C`qTe@&Ft*GVzt!hQpou5K_eGnjInAErA6$JhaIcucr}@jd1#Yew8o=6h*vq|GTV zs8cut-<9CrQLS89-a0{qp+|jRH|3oBYjSc{$o%R;4`<5X z)$&~?Oreq_=>XU5$~iZ`-HgY>No|H zx0gSmeEgAiwGHWVsCF|iI{LYPV@?tbGWNd?ApIY5dhf}@0PdN2k~19Ujknf=fUC>q zoZM8HQVLG zPDi|p`ixieh@aoS%^2Hj&=h^2f3X00P5}&mEo_Slgp=X?y@eDO1l%&90!3Pe()UAH zVW3`ZkN7IUT)TeWT9Hj1Gi;+#O=@X55DJ=aei3W&iQy}q)c)mJQwjqtSK2)*bZU;{ zGoI!r1TNtdDMA_6^0SZ&`wif^A@S*W(KUtzb=Q3E%QY;?O`*ZfI*u$bR*N-17P61X z?!jp5`~lyfA8p_Fry4F(eJ?BNX(lQI_r~B1ktp20{sQp6V(1|Nw=EOM4gy39&mFHh zjziJ?J=4yE#nq*GF{?rn0>_haLqe#5_{rs8PziILJ;m(53q1EG(T~`c%13VvCf!kg z2?Ddxg~bT_xCUaLddY9Y#~&p$Ipk1I>>bR}@0p#@Tau~~Hp3(ThABa&D^`Us+pfp7XkvnOErkVMr^U+%7beXWf>+Iig`i9? z;Cj(miV+>2+RX`XPF`uE?HLeVAWUh(IeEUa{MP|w{6kLfJ$V?wh{eGJyzr6tDIY+< z0dMU}Xn;a5vk+te)=}hp(}`cl$gfjH72G6Z=PXG5aG|pidD`>60OZ$b`CJ(*@iK^E zBcBQOH63gLnOh6fmCWppA?zGuOMF4p-`tjv>92*&q+47cb?%*4l!1UoHfr#}mF1L1 z5MB>>ORANh`xu*fOs_OkCpy_umw|En@sGIn%2d5hvYy?9uJc_57+v;LXZZ`|sPfPi zBc)B3lUXhv5pu4KKtTD{-vekV!yRZjcInFY_B z_AwjEdZshAP-E28JXL1br^E@V(;31x-Rw=l6qaU$oJvgZwiQys_J!^65=BXsX}$`g zgTR4AUl|F6NIspo+Xsc!{*Vhw+fSb*qq~WJ7t}wg9rld0`!M-<2aROMNA$dPXX+rk zm!=51@x8&ZMCU|^ldR9(eL+1!!9O#H1Jq)KkmdHv*g3t}g5H&@m^~FQ!*5lk5fY41 zos6V++HPoDb#zxSXLkzsm^9GHwQXygx<;}ct^L;lWd1`=?>%`KK;#XHty#n$-W?zi zaHhhM4dZojf+;p+0P0TgnXGHS=E?k;Rrov@CU!%Q*u1##ZQJK&HHMh26n=rw{ItQM&y!ASCck=s)>Rz`mhpbZ#C*L4+H8|f82b*anp7hSOta9$O?u?5bi*{ z*|+K+x3vW1sQ$dltA~LyI&jS!VygMAMz@D6MY!Z^H?zM0yf4{%2*8G_f|fF~*k_*1 zoQu$&j4s~%=_yne1E2I}WyVY-6(yCl7rCXM(_N@!u>Y**P+=Z);Tk$WTGT5iHO z`$j=vnf`9KOpXZBYXi~M?UnIkJ6!vsm1}la(q_zYSej_9K<1Uv-D_MGx$u#=tKDk? ziQum@Ai?oAE@yG{!TI_t5V*$s*ht-2`?f5G*@dlqQTNec!*^7V zwIeiK_3SF%moIT-Epn<@+CM6(_$}dVC^1v} z-CPBp6s2dUW6H$`$N-EVc^a@&mbUx$bxOX*#K?*qzW;ih9ZwX8?jow46~?>KFdc1A z{)-sZcV9_96JKtZUkmHP;VZm5Qj*qLxVs!Fw0ZJl zhDQJ`gr_0KZVsNv<7!~%kn5JV+ps0W@xDTEBfqeB{v?kUlH=?@b){&Qrb7e>CW%ro zaw##i^NluKt_OGo<|Q4YR_&8OF6{RZv=1qL=B*(5*9+<0=N=yf!a})k59>vzzH`qb zk?|C^OrwQkB@pv|XSWwHyJg+Rh&^GXQ(iFMFICl*+=Bwf>CXKE@V*xDApqr|keQw{ zJeLwyiU~v*wspPeE%uPhm6yPt;2D5;oBU~`{)F(*@jJTCw0rG9jC_{gBq#) zk)4B?I}2ifmZI@AuFDPuSzs)R=DD%o7oz5TW5rx9_p1wKHjyca+npHbYl z&_M0nUu-TTokxf*pFC@wDq~>ry1ONX_xb7T#An<(L)Z~J+=eg8!fwCiDZAhDlCf@S zenOBWAn6Ddl>Cv?`C7LPSryv6vB8SUW$)O(P$hZ!}crKo}k675q4S_h?>)21NYdsyib+* zRka;-jR$1skL%;U1qPmJd+_mWnI6iO0?hb9y!ZDopl;r`nv5l96$#bz8V^p7Jyv`=)e*t)3Gy4#L zDiPBwZDElPU-;h~6E%v3i4daZ6C;n4P;Ot*+^}-X!$@w(d_NgPNZ`XEycAQPM0X`V z(@40n4+yTJ<>X2F3<5(nTz|V^1J6Fe!BBX)ils|hvPN^^e{$hB!QB$c5|Ig%jXz9D zh39Y#8?lwpDTyy2h%6<4fq(PEWQ)4weNGk#Jl%CfzbfR~@yX37Pq?a|3%XDEoi6DQw|^Z#jz8q|-jjy` zwCHA*q)g3voaPMzdP`2HZ6@3Gui8MC)2^uO{%_=^66rMFiB-4Sa6@0XUu@R|6c8%X zJr47D(UH&5j_q92(?s)zi%T7uuoO>Zk0WifT@BEtM2p3J!U1_s0i1s=EFv-r?;!G% z0^i;Au2Wpzc3pMIslp~?!qS^WQLd+w1Ou)+CeS?VyTjc=_Z4La_znC-E%q)PbuBq* z&`A%6=E$P`sc{km1- z_vB#!wK)l#W~^hYT<-3Q=0`i&B1~kgqzB<-;kRI??@s#{J*V}mdZ*N!3yQ5opqLsS zDGO7?CY#hb-U?qv_*k6{Y2Vv#F+Uet04HCjBzz}FsKaL$nPzpHC?U@&fcvk7Z3h?s zctfZJP5AAu7~A*4y#n7Ty5}ck09I00d@#6Bxx3=1PLYg%XuHrX-K@mWU+Bq6O07jw zNN&(dfCBUxW%|p{J{dxu@c5P~lhgp{$zk!HWm>VB;J>)i_&0!X*-$X$E|G5QwX|QM zo}r)0Em#j-wGQa&>Bu)fAt7LzBG}!{ypYGDHO-3hwhoR6c|wdxfx&>0_oACnHjyrU z@fUz3ciZV907r@WaBq~}N{V#t*hj8H4Xfn^*uNp8wCXt#1zWlLM`W#M2u78M=k-xi z?CJaE!vL3)Bs4?IIi_VaSe(xS96;c!-7M(#vWY$9^ZaiX$EhEy$=P8(uDLlctR)hj z(<3W7tHUXnPT#7N(~g^)biS-Ztf#)B)4zsyKISgQa3U)QfgiED5f+c~u*#0=adt&% z`V_e>$rtSFlrO*8cJCae7oSJN$C8u0S&SW7#K(VMF>!hFBBYXzxdyEB@+Sk~&#ZqP zK%PJ3^xl()0en}|m%PMiZI#mp0)FDn?D20;v>LXBET`kmM!3CCOy=LB#AfA?BPA)J zEM>yyoJ3C;39g}0&4cmxb|XVpslI9ja_Nk%EHdK51-e`EcF!!GG_1QtIv)K6fV_V# ztQJA~dnEJ)23-{h2)kuy#&B&h6$YWOYjnLqtoUBOSMzgu@n>cO!xSr>E+r)UtGiAl zvmQVJwU1kZgLN4q#fM`T(*3xKvvX=f# zQ77}~U(N?g%_?A(*t163%d-Zyk9C)FvgBE9y@=SDnAaM($EAj@6GHHuu%afA_T57j z*uMP&@V*-VApmv2hjg20vmXrz>pv}EBWg$Hk#O>yVSyCI8!gj+j8&_LPQ>6kZZ?8W zF~@|F*eB=dM5P=tsW9xrBa=~|0;56TrC2!~8a*o`cPm}bZ?>s=)c}I6>og&r8VY~k zI`pI@_|r`vNdPaQnS70h6TDb%lpmg+S{3@7T#JwjE12~86Ok{nijlJ6g0@{<0zPNtEYqd%Y8Gx0BWnl_KPHs-1C_HsD za~2vzcJU>e4~CjuL$yef3p%)ySEZPB97uE6$z~aGrc+-c$^D|`JOQtm625JCy!(rs z0{H&`;Qw7%F8rre=3BYV5j_yl6q603;JgX*DTL=&dK@!I=wfMZ&&TH9>?`zPC`Dz= zvkC^hwJbUN#Gaiz>UEPQSi!;@G+$bUuXvnI6-kjfheN@b)QBaE^^+?c?(YHI7Lmg{ z=6$XHdOejGhq0lr`OT}dWzrR({deQUhCMZc4g{O#txmfDNfl(=Gb}ubI?va6dV9=` zWf;)@3smNbOTPfTziQ+m01;C*;IDugO>a3o(fWu8^c+&(VW)`7)3(slyUht$JyO9V z!?_g}HIR3rH#fq{iBpV+ZTXu+UL_&H~l_uz{nw0r*Kc_y%%~eDu!_-woSrvDFFYyJPeoNX@aL)6LyMe%NE99jfJ*(ufEvZygNt^eaa`2X`k? zJBC4?Qvmp{h21J^A|UA2_o`Ce?b>Hx*F)9gxs@3p6DC4;p?)OdH>XE;5_WZnNW7IE zW?J(2S!Yms+ehfnU)woo2f}aNGbp}?$E{Ztb#p~sxEs}N6H;<&%oojnlGcG-*zY}{ z^e4r0u!U0Megpy|VhYCG=SKQy=DzuW#_{NAuykuENOlDT?X)1+TFMMFSf@r|s7Lxf?IS1?@T!OY2x z;^N7l$)=&B>zM$>b>X{CPI+S|x0SAZ=uH(?E%u}Vu31*E7jVPc%`}h3cI+~L+*QJ` z49#Aa2(tuYSUCW@OI@{sdU&E<_LF71`O=bqbL2^(O;n;D5cE!WdvCf)nC0(x1blz8 zZi!T|@-10+JrL$!2T<@2IlcGfVE~I9YV9cU>|Kjk@4iGxt`>@sj#p7H1hSlxrrL^1 zoVJRQLup8m>G82PnAu}EnXsJ|OctZg8YT^9QYGengm3wRdy58RMO#=9jIHPPIQ{J_ zO@r>tr_&0DkmnR2^w+{VJn|3!S953C6=mDDVLHa4yStm4Mqo(kZfTT8K)OLdx42=y1VOzwe*|3YkWTW6Sj5i+gvl(Imdp!dkrNe|8NkWH0!_YMV)_TRRSJZ z)ORfHhAUMa6q5f)<+9{xnTfyj-&AQ@_Ilk#x#+dRm=M0^jkP4-PRmA2D;il^qOZr$ zK#vr}y|yh2Ft7w7fDiUtzyL0zUw z;D{~G)tQdg=&iFLfD+Z-Auhcs_IX?dpPtAN_wp|RAJ?{?60lfBi_T}ONYJ#^n4o9f zt_zYck@T>@S(!5o_biM=e{5MIH5s7K?SEl?0%aRkk7H6;c)l0WFPqmPJJ(971`7lY zzhYgu@&Z)b09Xvn!=?&ZI6gM^rgyGlqDQ~T;pmvmuBO#<_jqQ}bMo%~p0cjbLXy#8 z<))rE7uhQH+c@{bGjx=#Fe#1;^=87eN%=d$EL&N>;!s?r(AUD;%q^LyyK?Bi|e zIpGvM^`wRu$dcsH7Ira$!UWxQ#+OI?bnO4SfTDlM>EleE7VriOI|*eRfsC(uigeQ*Kf54VuM%LD$%g^!#V z0S_#M8{FcVVIiLMofOCG2b zPh&h&wQ;a{TT_A1wzUpE*lz((p{Q4p8&|gi%kiWuimnen{*={G;=lbKi{dTPuTMxA zZZv?vY*-pf7*Q9F5i}@pa$|!5WqpRCIC7sy%G}4j@=L(SeYB?pEN4s4fGp?Txd>eU zRvBB8R#i>zJE2;1v?nU>&%J$8s%c&-Go<(yAVu++X3&@cUZk_XFvH0dSz|)t9tLB# z8wl#YtBy=Y+3JxlHqxQ%FvVtcAGK8cucCMKYs19GrGAjynUU|NAnoOg5cV-k{;(=v z4DXMw$J~+Z5G0|<&ci?;2#1}aVL2`)Y6ra8lHeZIlR@0!QKt6#u$_;(y5L<4m3wP&Z{ZrcGEL7Eu5Q zAP$jQNJL$oh6l4cW#`AqSRGWBq^>>n%CTmFCgNN6IPBJB`gk(KY^Rc3)4A>n1+{vj z&q6aP$qM(_Qy3iU^-Q0Ptjo=+-EDkolfcg@gv4Ki)tg0o_9wIs$umZ~kaECd1ru=`-w=kE>h?+DjC*Jz%;nlJm z;tejs=u^!V+7fBYMz3L?;#lUZQcjK!U#bljv-Z%dt^N}5ac}Y|0Wq14}-!Ff%S)ua`iM%L>h_l+7^}VluvQ zd{{?XLMGLA^|GrKTauHrfZ+Z9|GGL5vXJ&o>=mi@}~| zf~Z+z2y4JXDf*QE5@RPhq4LIR=!}_la_~H*aeVobvBjBom8}5HT*pAge zfIJT89;y$S{jLpoIpskWE~d1YOM<%H=a0EQ9>ui-24z@E8M8h2Ghc@_PpzWZVx| zE0)AfCkivZEmGrWr_xiM10U@76A(`hy{nCjOqa2J9*vB)8B@W1!9*LxbEKM(PY%dDrOOv3Y5szDeL$ zkx|mhP-YkbxfF0wMB%^@yzy|mM0}>zf;4_`yRS&IZar6u<{;~3(2 zyz7j)az**TNcuJgU+DeKCi?Z)Z0ml*2k{-Bla%Cq{&fMR{*cqhnLI6EPvp4QMM8P2 z=EEH(^kwzN`R%YPyj$SqG`|JebnZnTT@kXA)qXfdQK+~yfz%37mB{3YpSx%QGt?R1 zPYbM6=yOz}t<0{?BX}htLGO`j9Po1rA^q22%zNe9=!hb&c@Ni{^Ef1w}7Wz+0HoqJ@n}ee3L&$$gHT6Se%r2wEPjI zd;LA|oHP8|Ep}b^&9m$+IqRhyg9qmdSn0~7w+}BcLC#d?PBQDi1bkdveo8>sj+f#E zhA6LFN%G@ZSW78dhQ*9lGs@Gh`H~oY*6r!?N01~g36FwYGRkVYDI~jl%w))ENzv;x z;SwRaW1Jt3A(|qLkIjiY8N0YhoqAVKacF#fR=+6w(yXa7F(cCio6Q>d?g(?Zcy2>W zsQdf+=+j>LAE~YyBwms=AT8-%;XK^2+lWy2eLSV$+*(BU-L^0bo#*uPh3RR}T*SMP%CKYz4ICSsmPohIhSIRs zU_UHEj`%ou#SXxA>!Sb`yq2Q7y__z%ey(-qpx}A0{)K?De+{N2sy_TxPKE=N0|cxZ zcEOsL;FZ7U1rH2<-H1!3Zn!c17PCn$K_IO^b^J4flci>ZCF}K&yl(=qr-~d*rt_ z3u}%y@Rem11_v-d(HPV)w{NpjnF7pf1FEey<}zSOn(w0N*PE(@4f%_MlJn~T_+R!EZ-FjZTu4OaU=UF0S&WP zVyu=KQLDd&+V`~b??Yruqh0x>zSI-^U??w_^3x!d1+G+B+Z~UI347<{c$wD1mTZ9% zd(?}(clATE>I)zUWBt|_(3&7lnE!z9AZ`XQd2pJz6%GSh)HY*+OG-A z@d#US#K*%hkN3w}=CZ0A4WWa-J2f+UIEZ(r-sDtT4?xO>!#7PUHjMW*h&ZdOdmA}= z<-eA$%^EDbtLH@ScIn3%I(T zYu9Lm1~Cc&1ejr-S}FSMTCst-wSyV$#L%gJhz}c_r{{utUeUE#S(9r_>vc~}sUD8G zVH}y*R*vh@p3c!}_=fxbTO%K*ukLltGJy%l(G2PU+z;?`3L*d3U{)eZ=P|TW?*)B; zfUSn`!MIu<2TWh^1XM5pWsQnG10}nGDA`-_{N?((k|>&NJ!hS@$n`SQFZy(k+uo!j zPm!eEGv66ED*K|5zOVYxzyHIg7ZF8t-3)xN-vYk>*lX3b7C3&wj-$k%vp{!zAewNe zSVFWGxvw>5fPOqeQu|yUcucncoJIR}X8CjC1#$j!s01xqIFMoOT>bdwF99E$1D+BP zK+C0geYH48Zq;6$IqO#5DMy`Vp67To4$U|j=Qp|_&9I)pTBPPkcr-dM78gCb#uLjnU@|`|JMan_(M(~XY#aw z6t879;61$Y#^iv2VI6bN!&xq-9dht;in=zVwDPets6}FuRg1_Mu4AF2*tk!vxi*Dy)L(# zkLLse#PjIZq176r$kf3D%h`Wsnl?lp^L<5e;*Nh~Du?7UVm}^n#M7((KazL(K*CzT z76Cgh3-xxKji@_)pW2JMT00`$ffH9kY8)B&MDW3W3)qR-c*68fdZR&;zaG}MV*tQe zg)wliKvYNC(~yp7`<5zq>d!gtge)E49kRLx5g)PSqp-QnB?*z{g62 zrv&sa-5*g9rwh!m&7qC2yaB4a)^cqjZ$UVRxCLqu6;z6)8RM^6D z$xxE>N|4(8U*xT3=0yC{bJX9rDujbZob1Z>~wvDt{- zZaXakP$Vg>>)O{NAIqFAC(;}9i>M#L%roJ5F(elV<-Z2oI@uBU$Zt>Q5b@BAO-p3a zw@{(p848|&dKxoK-#{D*nwMV)wpg%8C?XFg(b_eTVr{5kW#ur9{!o70aSJRdJ zcV7g~wqHNjci7Z>Yr0!9a;HjLyFFzwLK$)!zKRdN zg<_AhuH=M?aztozHo3+4UVnexe3eOXg%=3_x*WsMWgqoNr&GC)o-2RbVKd($BTUv ztQLDz(BPZw=b|C*7k&0#93RjZX@H>K)l}?nDVcgY@6nd>+I0Ul&m24>^6D$89RS&b@CWvN67!>}LTPg}$^ z$~I%n;tJKAm+TW#TSgWh$zkYHSv}jQ$*v#)w9q{=@N){G`qyAEycPW6Mr?kI4nTlf zN-a)4f_*b*A$VYy94OVuE`~)-s2wYwZ`x4nFN&6^_BXk<_t_(;8GY+(Vhbi4g>wfi zQ)KCnbe>Jg`nH?b%1Rh4AW&N4Qx`*l5B6I?X)^ci2C-+Ic?+D#F!1@Nro^my_O!_b z-pqOkfM=9@CvVF(GhVRhP*%B3H_t|HA*QJR6hGrA^Zyv`zZ1xWxbsWE$4-)`1pM-~ z54u*xMCR7!lMt~_5)XS`Ls@&?(r&N{>+?N+tp(I*Q=C!=0k-b?k}flz_BFc608z-7 zwkUU{ds^gB+gU)+uBzhr`=9RrEoCCa$xNgos8787+BTenaH*sd(RE(Up{zaZ!QR|s zLZ!V<^I~aWvAsWTDKeT(1SDlo682dv7zmnhXz) z^cXnQxxZJzB`<-n7w1vEyURar9OXZR?o=NowRb-?#WzD3Uv&T11yuV(P9JCTw1B$j zjuB%On(o?|KtS|ad(RJTD(fp!@N$asjNx)CKP3!^;D%>MtIVtEhZM~Vq%mnXV>qer zVw>6*TY33;Imr3s#R6u%Amu7rkAQU*yZ5PKTVoo9@kbZ%a|)sU*I+uixwWRoM0aAVFwE+J&EPi`RWw&a(oZIWpA>{_&>p{D|i40Ky_L&=( zkB)t{&FadcL)5E>eRhUxLr5W7>9j~CX?27a(|M!DOSxxoFQR_1D<>a78x8klzQ3|2 zuhtUWD7Lm%m5wNnU&%~p$E&mJW|OLLL48oTof~Tw+LL*C>GZwG{vasfOf0n3|4TsW zJd;)NCW+0&A_cD3red%3kflR_uu(G#msl%FD$RHRVsppryatiZzb>H0A9DIQlcxp5 znCGIw!-dywJq7|4GofPlhs=p7i@+1mi=Cix=q~iaW5U0vl}gCU{1t!8^N1BB8OD$t zLQ9`uul8oml!FB0x!V2d0v~)$6CiS$poGMjb-43{49TPj_&J5p{A;i<=$^wtp3u=8 zbRd9Ggq380`{NbK7x2L1IHcUZ;0Fid4D%Oo2B)FZdqoc+K`9@s1q8Vg2r2G5x!053 zLsS6`Vp!IAOm}p;`x=i3#=cM?jVS4t;~d$55B6I?4mC&zs4!8;$?rj{sJx z!1h9pZ;dVv>X(}Vtxj4mZZE$YZ;N5Hy=TLTH(K>y5ll+h{Aqh$UOkUT%swHrV3D5d z*sHnLz^+`i#l%*#^YBlYmX_66LwcjBe&;*`jNxvNRMZ<}RT^{4grtfm8kHe=^nhfn`?0k!^+)5n=S zEub#lGlkwys9(DXfq+jTTJR;->0w1+?ybVuR28i$BP${am<*&AG0!3;)J=CD<`Zta zR1C1KAXqS|tj)*duTOn!Zm%+Zcb?CQA;lnaXazkS;PClvH(v<&Ifc;vYp`jTWM|Wq zY!j6LAYk@Il5v;@^)vw=cmiVFoLwW{aeB_AvT-c(?zWSv_Y5GxI-((aW~(eztYgX( zCIv1}Cx)$Ix@ql*xHO`NR?L0(QuXQbOgAHsO#dG?;*n=r;z(YMHVXHrV@6kQRbi>b z2&pOxUJZ;nX$_E`A}X_lVq%MbIu_QtgkF#r%6kKf4Smjl(AfVa;A45! zQv$*sL1W&X$UA3E!M`J=wqY#CTSk)%oNrn)?y-GCY7+Et2;baBg7I~e`m`Q34GXTX z_{QB-51Y;LR?G5m*e3x9+EB;ko3@j!IIU($IuR=HJ%jOVLhvWry!6lIDV=q^J<%3v ztAl1A$J^wH@Ac$c8$(?xC79tY(%tl!{gKQ6u+ocOEaaC)RC=gdplq+|g^bU8X zeBAZyExpC(09uQMEW`iaiJhI**6wy}SKyttPYCIq;&V1A^KJNXUQv4d>jJ*~Lrx!O z^0a`Jq|rn5EV$}9^*}(}^HrtrA8Z%_6X4}kxtyUTQ7Nwd5Et}%{>|}BdxLR+jS_p# zGJE6{ypX-?Gq2WSUIun$biHaFrM@}|0Y}j(A)IAYVPUQKc_Gxls7`^<`D?I{l-IPx zp&&B)0U&^CtqAEy`+R6YFL+=yyomCB)1NTV^RCVzx|fkftt)!?K!Z+|qxI6do3TN4 zM7DNTX@pifppT~V09szc5ndL=ai&BMZ5w%85f^vxKUsc10fiPrB-JdxpUup}*o}m} zZ2ZC9M*uiNX}qo7i%B$z+O!>i^{^!XFS}Is7IW&=(g0-&HEw%cW+b#-qiQ4$ci_P< z0Uukro)S1VB7GJB>kTc*-aAgch0(hN4xoX?2UqOL4oshjM#$hq>zVQ^{hTC5Oeb!g75B zQlu1pgz}#nF}3$2r8E9{@0Cr%1ZnUw`i+w`((YzYIJNwE?(sQK58(@CLdXv?S5$H1s<56$#{nB*6ZP>P(JL5<$wl$Q$>Cc<%@V85VSrY zw7p0wGo{Fl=ad0{8yvbCQv^wihMMnZF9MM|kKP|8;#QjAgZ&opeJf>Zjdm#gg8bXp zZ7SNmQKabELV~jsVfk9266Jw*0n=8s6h*EIJv5fP!E$klVm=`V1Zvsbh*^DH@oD_5 zhra}TY>#_NK+&``UJ zfck&P>EleE7SPeHeV~0bVg9rX2+(_b4!3I-Qb`Wx71eLGuF5`+<8}}+m{=}gWh4b$ zuesi8z~8G{&ci|&0X5e?9`gv*O_v7KWe2Dv@F0-)!^aC}FmI&%$PEx(Nx+ipmdwg%(l6OR*2CjdWbZ`R-NkU-*x&+6@A`*V(;EyyZ$tkZmSJzsb)b z-Q!n9mJ-=5V6@y2{yNX;ce&nzJKDGV66X)sP`*|cjjp-%rK_+4|-Tni3YM=TbI0vesq+jAIKH6Y6?wV8?Fdjj< zFRtKqh3x3cIth_mde+6m+T=|-HhyaL4(|X_z)XV>77f>;246?Bpcg3>_leb*=~21s zg(*b7LHa?u_8uEq7%Ti|84N%j_P@&bpIe{!#Q)p}`iTAK9{TPU>W@VMXdy!bmX6^- z*p3Vi)HeRst6Sa%{0I(sXiX~D+92#dJqf!Fgy?9mZ|z`WY!AYwflKXw<}#Y^bWW@^ zxG>5#rmnjnY~cCgIWN>*#jg^`efR1RG`rOIiE_rLU;12Sa)yVAlEPAL=sl$q%ql&E zMstu8Fmmp~l;Q759w#B?eA2UYjbO{B!$Y38ZU6$SF*;JXtaGhfm|9GbYLX2Z_)EmB zsyT}J^jIi%mj?qh>o8yYTk^deo#Kl}`>rC>U9L&!(hN<2Xn!!@u1O*b0uy64i`diZ zw`1cxjzDG4_s+hdmnjYxCSy<5Y3#FsKT2Sdl3;+-=bxnu>`2Sz)rZV9P|DLfD>u=K^3T)j!kbxph z{&%fU!Q<9Vb0~O6U$noYq@8f!L#5m*_%>s;Y2pEf#Ds#$};M$>xbenB$0diBy_ttKclgNWTG zS1B$Gt$ZVejBdp5l;dTht3S=KgiHi}km$H{vI*kLlBwd*jxhmHeh-@M{3mOz`pfNf z>4yAyN})Vrv|Uonuy?VXPOFQhepF>$>ml5y(l(YW0?jG;+AoI&CMsG#9AJq=d`DJa ztr4OWaC>ot_v%Qe{Z|)#-yQr(?0aF&oCHqf6J{r#&jyHS?9JOpeuCNDHxA@P?@mg) z;u>HwuiJ}))v158NV?|_{q#!$;W4q}&4~yAPwV_y9F+AQ2rN63;$<2U^*Cyz4qATe zqDyN4*@+D_@VqQ&E9O=8Y*}citN-?MJzXu&Qr|g#ZNvV72tb#xvIVjz!MZ{7f)fb5 zv>Wd)inno#gnV9ADMagFH6^#VZQXiOlA1zg6DO&kfZhu;R~Ec3vg4gu^k}%=D1y0; zJgDj0++0Kgwj=+>KUqZR|J_BWddBGjR*_Zi>+rj){!bVEr#=_}xZZ$5@q%)vwAQyW zrsSg(bF#7(wXweIfejrgnJD!Q%qjWYDXokh&1{SuDEVIecSBusYja0){a5C0#&_*( zOiUb%9Vz+Pm?&-a?e7-lXl#Gq{3R2my|JT{y|tsO?fv4_Y|M=)J(>P{DYj1cuk!oZ z?EmxW|J}~g*!8a6uZ4)XIvNYx+v~gjpG$Jqf8})lPX6C*{`?;99yNA0ws!o_yYytD zG_|*Jvi;3E}%wfFmwD&xHrMnF63h zKwzK8B_YGkh&tv6oNq-a8I~@*ztZY`o!xys?LT_?9f++Qf4q*&17yc}{e%Ca^klnA zAf<7Mv^3{DZsfxZQc*Yk-L8dSK}ZhH%_A#8b^6qU%K*YELFK}eNtE1)kE+vk2$xvM z!f|3KE}nPWg!`X0lqoW{-KQ?+m?~4%+UO^u$R{M(gb0mcE5z^0*)r|gkZuk96&*A1 zY~{bLc9dgA%m%xU`W0yzUw|~n6jL=jeswJOi+iYJL1Vrc`Eqq^>Jak%C&|jqcU^vH zI_yH3^V`Yo0bY7$=$OZJX}K9(96|;{q7q;zsYv@sWja?&@__H8 zE`m)+CYWf?xALPv6vMQ2KL!)clmZ5sujOpoOS)ns*pZ}I2j2BQ+}+%yUW;5-(+M}Q z*p_)&{z()FOkXwI_dL$X*awYMy&r#&ep<8sQh#-5Xe@}2aP(72@9yrGvx8$%M(MrB zesH?wuJWcQ?988mQu(+<6beu6^bEcCGwYz!-!!bBc+ehh~Ht{GSc} zeNOeqpDIzq1p6TNv-fe26XCf97CcT*=z2^YcC)~2x;ZGSA|H}l-0EePfKyZRh5J35 zv^3in=*)ef!WtRz;T0z2IR)JJj=zC^-~Zpil$h+(zSiK7d!2!RD5rZ_FDDq4h=3*)U z3Dm?SRE!jSHoLn3fE?_%fXeDL*AfKv=xiR14jSnjYE2=ou8 zTqe#nQh;2D#N%=twfW#8fvlpGBbbQT#2jN*Twqk~2KDP%#R_cm^=H$zsUa}xc?0Yq za8`#qHp~;1F_#=8GbP0BTf0Joyw4q!m1zyNd2ka&k4j%X2d9%58%f(^vQ<&epwiXq z2dyJceVZzH)u*=K_5=j}e)g)0=o!3YOAV@cEz#zMfuW1=Q$Yz}gKvB)Knp?it*$5L z2}XI#^{HGM!C3R``*s~o+4hD*`!3ePBHaere_g=;^r!zr!26m!Ea1@^J|SMK#S&~B z2&l>$%c0oo@|Y39x4wvRlQ z%uTSF|JmIyB}J0-2~5P*q3m1*7Rx`$Jk*0cr-1+Lf&LZPGuJ%{_1sFTCNdC^x3d88_Rirr~DoJ%}p!lrN@@#BKt9GgrB zz9r&rqxyIcOa7s<9~mcsX1bn4F^Q#2;R|s9nM{#S96=zCu1~)NydO+?NI;yH8q&^D zr(3e)`gJ%%RpOCl&7EjQO@I0R)T!1?eMMqRjJHEQaWQPkdvy)cr+&9FP{zy0f{ArC zabQ3(aV`j~@F-D%UoPw{KJMN3baqORTOF(uiqpl0eWJ0!>5^z>t&x;_vkb3HYSjvp zTGntOHxwzft-zpnNf7<#G=4@c5V%fXgXz(Ff&sq(;^n9qqDN~uVM?@N3!GG%1$lkj z&6{z(?!XbQ4Mj&LzPYi`PZwcuO(6$$t+O1s@R?7@qYnOc0sk|Y^*?fY|JcI<9-c8f zdeS2rznupGN2dLt);Ub$DDWW5X}fXuI)b8~OdK*%#GCWLPIx#+-&e0r?Z8FjEH*s$xXETdte7DUaZ7L;X$iaRK2uJUW6YKW+mM=cToj&ao zDgJEvM)T{NVO>(LitPN!n`0x!`EGpbcwW;!UAEOCJ@#e%`gi{Dd$nOM-@`tk$9DY^ z@P0i#B%q|u54v{^at7J}zvp>-ecViIH&m3E(I3MtK0?j5itRS`lb8?sWK^+1<((KS z$inTBcqgx4d1%%TD3A6EBNT$b_#+DycJ9S~ud?8pf`sfa8kl4^Mm+@5Z&t;6viYC> zcDBWd68W>7SObCbv_a0jYh|NJ1khGW#>O*I=5o`AOb5pMqvIDLyYj#clbpa9n zkkk8`JS-p+cc8YUcV; z{9qPuo*rDEcCTtio!;rWiIwl%7ah|VGC0!~Ewk!u{PeFFKfdovn<)X|L7r0p;$MTY zy|>inG%u8!KWTr2|1 z$#ELOSgleESyCyINBlbU(=!StC22ho+PT!w)5qx&E{26ok)Y5lEJXGrBZN(&UXZFa zUH2~m?*s6VfEzWWxBe}jXFeNTt(997`JXuT;!u436`TOMVLA@PY_;EPx!Y71-wv{v z%zoHkL@njY+~;ZAw&_ik8Kxu7LIZ($y`0a~&~v(<(R#Of^A4v!-%_AsZMUK~@Khsx z=|lSgS&G!-Ezio%5V7v_>o4r7Ob75=pZIVpuO49{ z=EYfyU5T)RX`h4Lc}a{zpYf%y)i1s$)MGD-=mbgRd_oTLn^QjiWTSrY)bi?2yMhEubYb=9>imZSq4OHTg=M(_^ zYp^jE#e&W<^nP7u5Ks~=+0q*@v^l~F8Q6`SvA-5~+~?8~YJuEH-+2cLhSA3vOw_M@ z3F_KL3KIdG=ejg}8Yk_aZMjz5y-dV7~=q;6wX#W>@~W zTa-7M1vGqtj@8P?C z#G7R4r=wh7gciE4mMhhHjhT@SO|{7yCqUpbAi}A8TYAdE%!Szb7j(z9qIS(AMjSD7 zfk#C*H2uCKPU(2v;aTL)Yjt9TsDZ9;=@PO7KGa|Pd6UKPa+lhGz>31HeY2mkZU;T zP5sveME*lg?`!g~fKR7R-j>IqS%{y4fbDPxuSeEr3W(hx%c+bdG;>X?GHH)pXiJUc zW(0m=-S}vsm^V7ov599vwz-5G?MGpZtt#+n87!bSg65~_6q+RRRVClo@siQ$MO4Uh z3IP2zn97u+C&NTF_A@gOP)%g&;0AW7B0&-|Fmt>1%qpMTCtVxN^Fz_?i=)!VvnV0b z%2Aq;6jPp1Kfjmf*JLgPSJt@x$m@_^0|b*TgpH`8A1LB~$4QYLrGgynw}9dKlH8*? z2UGcsrt8l`5|&Dh^jy4d9Wr$m`VuHq_6xqmkAF?WeeNL30hNjH<2rj~npa`oUj{lR(Ri{JB zk0DpN(|=t+lt1M3z9tU~7?Ja6NM$e;w|NuDq3<})fx3K z;Pwkqg40Bjd8BpSIhY3t_25X+W@vWwkn;HRvjfLwrKlf&Geh847kCmh-KGH^Oo+rM z%Z5Cs0Mx$*yIq>Mex2V5Z(aogBHCLFM z*Z_4-9PeO(GmazOzic-pg$HdgH6i1E^m6-k*y`xr4rHdc7pV%q*n)zl;c2#OELDiH zJc_-yU**n6_QG7fx$982P95|FHk0utb;U2ohJvPokv}pN-1St{$K=HDTh2k?=Wflz zT9IbdYFS7H6|zvClC!rJ$Wc)nG6MB=?=F0?vTaP)E2u*32)AfA!wv0RfQTu|nigW@ zFVYUmZzPUL|8)V;{*crAnmjC^phTL0`GUkV&2bPg@0%uq*kM4BAPrj>${5z-C&^QD*jpMY#`li(EBJvWrI|LS&Ile7m=b!8iQew@Q+%X1Y@#&O@kyWLqJxJnxjO}N>qO)91`l{m1j6wcSa-4M8!H|Rf z7I5Il^jRi)Ic7mvR$I=5#V|P&tX<)HuW?`!fAO#lnxk}TbK>ib73?#$EhOp*!O(r1 z*LsLiSr(WIBJPbw1z0%wJgO`GF=*AATG?Oh9Hl}5CK(q` z$;edZTA!Y*Y$i|Tzb+ugA98wMlZOQydqrSh;w|l!j|~Fqe>TIG=Z-&kv<6vDLoFSi zOh(y3^FNxDx2ncrhf7ZEppx`mM$2d9lt6qH#;kv7p=_d+lRi+7aT~@{YWXTnMscn8 z7|}>>`RnlUUu-ArsF8~7eZIQJihKi<#9zq5NPIzPhKCaJMB|!2` zloJZ0!S2vA+v1Nh82Lu=(Ln|8Yv9uZ?_|NSMSPdi=`7N`L&^Xn_h8B|JSUzTg;^;eE%F4cp=9*wG&?Bm45cMs!Kvo@*<%#h zCIVA5vr;94Cb6b`%k+-^Ujp9msy`&)XZKGUIdjQczLVX)r*`J zcd(+YF&4RJaEHG+l2C>D=OXEs$2QAJ(Y>0wv>BolIq!6KZ4guF_q4t1n{RS|ww{5& zhusK^6~ZBThQ>=eW@(&5mTlBsiq}t-o|OVr&6)AjPpV2#4I5n;BIGHACPPzoiEW|3 z4NHo;A{MQA<`a@3dYx1yw01v_3JYIbw>{}3UZa3j2!$uJMupVSNrFx-s zyLu(gnvgu=G$^RSEPNw}yHl#K<68bcTbV8caOObxq#Uo-!;ek;C(EkQycfN>m@gu8 zPZ!Bm^$c9X6UcK4!2WBnaCLlv9RpoS$+F-6mPa+ zDU)*em0~9A5h}+jc}u>5BX^H&tXQwL1>4NP|wa+m$;XPpWBGp3~JkWaqUSyhZpXYsIcL}gTU2CZ@`buKdHx5 zV%LsBweoiT6u3z3tB_t+ByrgpN9ksTGlAwbw~Qv(zJkLque*smcXv$}87v69imdM2 z_$~(mOPgM_HeW4890dERrTalWEygc-z1RJnq`P1Ab3&3tyi60*PbotdeT^9e>WF3L z??W($saboijjvb;YCDpG!~b;wasH6g`G~@QtI?V{d2-hDVPe z6Y%(@u}A4oPKherBDm>bj_;FC{nWo`*MHs;g7(7A^L_KyVjj&!l|UbDryIlDMy6Z= zk-6NT@fI6n%Wk7lp8Hnn`A<)KCKzcD?Zsfftb;C|C5q-Z>ufUzFfnKi?2Il+3uN_aL-cdNy= zDIqrsGZ6#v}E0@6%O~K0-U0Km8zAL+U1ztItIa zC)t*H4Q`_rR33%?c3QyVKP(RyC%4w~$6w57+K2quQi^~K1Ud~e>(L~y7gV)x0 zvG_lDls!=}FCho}EnxP(zjNF^pQ97?WG`|bShKxq>a$6wcM^}(5^i&cT|NSme5@rM zzdMDnWf)!3K3G^5u7=qiPuAkm%h3ok!Jxrk0^Sb-JS3pE2ElYR#?I{P(AU&_H^z@b z4=(Euh4^aiN4k$gYNquxmwe+tb@Fb1I z3_YjDz|A*c5>d04c#CcgF#pF?&wm zHG6o55D!OaH*

D^`)$j$OHs_P+Yy?`?0a6hN6kq&Q+Z7ZEYCO zW(eG3n2)*j8rt9a^_Qq#S)BwXUC46^ApC1E!N#=Bh=_8w12GVglwpsns5%zhZ0bZWImT`hW-y@IQ_(zHopp*!x=L|7;Ef zhI)#wgDccazk2YJ40nsxxpY`tkJxsFWA2RGc8{p?DCQLX%atXGZo(#gTtl`J*C1Jy zPN_gZe***BE`Sc*>TWVyJ+qV!!^+qz=Yb*=bV0VYJ`R?0)tVy(^RKxn<%{^Qy}QLQ zJ*5C%!toysuL7iB^>p$W_`e#<*kz8wH6u6v*99c{Lr(8&^00se3RyxIsWM{7JRsl< zwA)uNd$kZHz9FwfRSZQ4TV z{sp~BtlyiDWo^yOMfz^)$$ESaBCpKBkmnRY{MTSf#&UC$i$9rF?zVuwLcW%1FU+u$ zgdhWJx)}BR25&w)=m+!RtI;9R;8uAXlt1l0gC&?`>9lmlBEDOp$kOtM4ZLgyv|P(( zGt8^X=0aQJktEm)3$JlC$iaRKNIn}VU*=eiy>>BBszWNGiPmNL{M#Y0lE*Xtc%IQH z8R{&KwuI_H%X&PH z)3q?pyqt0!jLDS8+Eh)X>}Hl(mp}y*$ID=pm}p%nbl<1KiA_EOaP*T>CD8ymtyRyS z#GiMEA;@LC_}vn)YN3?Ih~XN+YAP2XhA04r??fgTOMktLc-SEk87G0*8D}hKxeq?YUw8uVkAdD11ni=OF6CI#{!))P?xjnGbb;% z>5uHL>sI~2TP$!x7e3u{HN+y(ftCzoxnCg9DS-5^!T18=XNhziDHm-(Ks3e6ggoRy zMA2`Mfn^$W`1C0&e9S2IjbYV0r|3EsYj$YOV|t@!cR_mZvj0K0a1$s&tnaWyoP32H&E4dH1wsyDjH{@wB7p! zSM#i$mZB2QF9qgJ(yC=X!npTW4r;G@5dOF}C<8r{-{T$mCE)#N@k0Vu($o8f^t|w- z#-(0lLUH-HgM%K0x}4#_`Z|Jf>V$Dg7g;~45Y|{uO$&~pj(?~wG5(99`-k`65@Dg% z0g>BLNpLV%=)j!z`U)A|D2RGQ1ukj9Dyi`t*Gidf&BnWd{4H0u5 z_sFiaPeClPW-K!PEM={1fqQ+o1q1~yVy21O<-B!K>2(!TbMgLJnU74c-Hi}iavrZ( zl|in2T_I0-0}irBuG;OMLkyyZ;i7yBqX45kNZzE3`>zW~_J^F_*W_UV+tiTH7>oEg zyqiJ5XBD&VuI`4<0^dQF(~G&q9M$|Rm{C=Xp9Di;$j5q7?9Wq02eF%8ma%>S3SZG7lhqGVav&HA;(0ppVh8 zLQgZ)0`aRX#IpqtH@0pO8YcR$Dv*Qy7Vw4^&g_e|P5hMQ>CrwPuG=OY|3#MPIUQ+ab#n!KR`ME8NC5irZ;%zUl)+#4>`TB$-@FpN?o%d zJ6?T$?shj}L`!`pcHc7F;RR$lwRs^)zHJon7Rp5KqmLd62TUC9xtDtV8CLV`d* zls$Cptt@N0(jP5Ua@Lh@ zy(k70??(Ml!Ap0Ud;5e2mA|oB2s%0CBW(Z6Yhh=;kGFz;UlZ~Q%nU|<33z|F<{<&g zbXHA+KLqWLO`W*BLotHJ7a7WWN&@TVBtQEz{AR@6iqYQcOsrJ#WQ79zvF_&0Lcg|q z$jsD2#LIH=<~qvCyGyB1nu0BV67f!Elc8FEY}v81z+lAFws0pIt*B(Rc`V^v{K$Cn z)a=cUj06NtwIL*<9MI8(RzxJa|C0l3u)*O&@Mpi(IX5BYgPkywO z+hGxEF@}k-gO8P5Nhyra_?4S(dVAmvN0`B`F7xjEId_+J;0>JK@+ugSv# zYE0yO!Y1I~%KZrfx_tUHx}O%Vm@*8RfOw~?uULC-gI5h6m0WW_jhhL#4AaJV?-#x` zxnSI&tW%%SK$D~5Oo^{+-X!)oAD?-?7u|8T8Xn-2kif^YGzEE10n~pDmQn9F{CGR5 zuy+*%9Qx!}@D^1+7cmqvFvK>c7Gc=0;ad zNUocH<%b;Xw}4s|n}Jc};nZ%t9!)=V+E00i zYkkB0UhL4BVxYRLXiMw$JU`j&wiKLfp@))WV81rG+Ew`!=>u9y;q~mfOWglPWNgoBcaO)7%yv(;Q%?lv|3sx#g;(O-m7^ZtB6PlZYQDa zt1+Q`GZ3oEoK;`*D7J+0ZXU~df|xkBiiB;ceWZhyE><=QcK+&zX=pP$E1pE@nG{{4 zU>L6)Rc_a@4x%?fF-6YweiA&z=X!4j_IGhj5vxGpi7}hv?`DY|Z5v2GTKMr?EBo4L zY`A#}{NIt_uOSbwMSkVFxx`A$MnWWh_sy&|8Uu-#S z9wS&=+3W%V&+KygK4XsM=59d-<{%m)PG3VSEI#g^91V_v>S0bBiG*!BTwqC%OnwIC zY3rp^5eqEG>wP>?ul+G`YjNqp@PS!;_(2EXlw6q3 z)9_h{#vE9Yo*!Diw^0ht7U?6)27x_D@~wA;F3a`IN6!{JXDDG&g?g#YrCWXII5m)9 z7`iIa-rf|tK3ZudnUrnuVzih5u**+|8)Ur z|B%!BnmjDvM-jvf%CJ07Q+^Pzb@r(UzA*4D9)zbO;mXq4I(HP*%73ZU7Bx6)T(R8H z1~FR>3B~tHaJ}#S;#O#>)xzCR^XhF(YX8`LS!MhhN6O|r{||5fdiSS|e=$}8p!;hu z*|B;1A_pJEJWmj?K)v&2F;jET+&N?dRvZkp!v#l%m@9S6E^*iFRjcV&^tHa9eN&f; zxJCGuiQh+CN|W*yQ<8$*|EGY!1k78`J!5a z!zRkhWqrID=$|{-u+!M<*QNo3EIQP^=rTXX&+Rr}s5^SiqHalqY1R6`uJz*dZlMIAEY~a1PNs1KlCH7 z{G=B!C~HGn=Nx&KKV<)`w0sV3Fx1*ru4e~M`o4Y%zoRx9<|x#t%ST0X0pN%hkV0$PTl8s1^m?f zSKVDgKO!Td6L&EwN2AD!HEBK^=gx(I9}9!Py&OT0R=kzki?c3*OdhkxBEnlA$sF+3 z@$UyMli4HaOYm@HbGq2Aj~oS3G<_$PYw{(2ZkCc0pjEG4i7o3fxX*eG$rIpL7s;iTQ<0$#H!xe##xX zJRHk*XBv}fYwDMP_cJjb5^%U~$sZeLS0vo}8JoDVUo0b4Sd=|{Y*45K0!K!HXJyTt z-gL-l1_!*Pz6BHLj5+FJ+A5r)4+rz@$7Y_6G`qV?DqVfUXy$cdZKVO1qsY1l6?{2J z^Gi0VAQ@Ka&Dq&?$mW1n`)0FZBE>**$R$v|vGKm?XqJ{13R!op zml_~N1`EiR3SwT=%ff_27(S&}Com1x-oW%2xR%7hnquZ85^~JbSf|{VAAE8ZSeqZd zjd^fvlLYwJ1!Vq1PVZ~-uz;#q-_Z>7l~=sLAfTwN&}JzIkgGNevYZ+{sWIzZAvd5S z{aK|M7$Ac%5s$C299!$<&$*EL!v&?MOBwn@idM&RKBg`Y^NYb@q_gq~E7PijBz`Uv z28DXaa|&SjYp`_{D=W5yA}SXL5O8t{S2Koo{(H+3WMJ+y?S-Wn@IKt9Q--uDM%9Fj zIh=$YzFr#WI{rPeNBd?9Bv-g6&~Zlv1BER(>1l34p6}CFTd@tR$b*@owh|!+`z_#- z=k*18Xd_gcy-I-MWc)_kyb$532TEspUa`R!9R}-w4dd?%`}N&fWZE}t-~G#0(_!`D zq5HG)3sR6fR`z~O{}S+i#?V6oA{lHmvhlr=8LLEej66`!O)$q}GS` zGam^QRbO@}QqY1ulHQO>S1Xv&JA9^b;}rXH4wP5zdv`mBxvNk2ilT>_oK$j zp;5NBPr9Pw(C(2tn?*9=d3`g!bHGbxNw;C`(Ns5l!+cLEC_D98m;hzZ2&P#Om@?fQ zkcfdPT6ue=gHt+Iw!cQua*P?4GLHHZZjLMB$01I6(hs{N^`Im_RE46^6keQF3Ni61 z*x?<`ik_0>{(oIS)<5L*z9tU~xB(XML$Y4@+_(z@cFP7v+-m#EQOH1+Q!RT}E!SpF zGE*l4WXViN%WV>hIaI8nTPF43Rpa>gh+T_|f+g3`&P17|Wm@nt@BH*%;16jBrMk~s zJzKoL3m9M0vx2esoVCol^TC`izza)TV~ zw}97eUtN5V43)FfNVL9564v566V{4fzi<=!sDXTU5p<|+eGSuZx%~6|Ggr^_KXaCj zun*sL2^9%A{m>_&td#F&ehGL#73v`Ylj=Lj=o-hII(1g0^k*OI24YNY9KDyFG4dc!!IQOQC_XZH*Wo?jJX>A*c5>d04>lm~@(FUpSvri{71%bj(^6hdkott_7KZ zK~dqsZdEXLYZwvY4Ifi111$`msql4xjz6Y=y#}HhQEv&cr{wdtVwIUX5z81?m0hAy z3!W!*YK^JBWwmRKfjp-Gj=u(@+Xx>~8$%?gySpfpxXL!3NA%^KoH%4))4f{))3_lH z?cx>#uz?r1tKZGw;7+@t=GyDtdp#N@U6GtF{3!hCJM%gvJz{CjHrFzu1^O&m90 zjiW;h8{}ZW1#Eik;x=0MVpF3JAn5tAkHa0`WAVV4TpOy3m)_Hp)L|E8dt`H&I#G(H~N>cRU!3}&!Zxh zp!8{Oj>i-c+6g03t_z6dK`QCqL=t|*-(5Lqf8zTnqr9Q)KW6&)esA9m1f~J4*R(fe zxS?sU{6;&9MP-@$E`N7M2{F^)-=zw>E8`-%=~GUN&KV5JXA(w zx5u^a>~0%s6ktki1tT(1EK-H#;FFDrdy35Z87DOUL@yRooSyavY*j&LpPA-%l>wE% zdgF^eLQo3Ni)V@c{Nh8`W)g2${&fL4|B%!BnmjC^G(|nVZ>m-QDG&t2>P$LN>XEj? z#e^)UI}{%k7H>c6Re#KVD$9L}FRVR=+%kKtNV-c}9tRA{S>j+$q~yUja@}~8%T?YW zRxlVIqcIi;`p{Dwj>fcd2zgEcTz?Jr`4LU_ffaiQal+k!c-RA14Uma0oB(8CP}{e! zT&^+-xP^Q*rqAZ5F>=TgBQD-c%A)oV(SCP-%yY4ACo0CWzK>r#^1}Q4$@NFu8n%id zQmHp&$Imrb7a#}wEueksbVUD)l}?rqRNZ*r7GzDbEaVSjLaOBk%=>>PFgmA{^a-^Y zJGW%~NKr*QW5ubBxRo~iqIn!Bzvj}oA$pP6G?~Jk zx@Rgc*vFOM!Lvqqx}zXnJ6bjc2vZ6+vz5Po=@*JXw#8o?kHd-a+R`z=Q33M@2+X8E zgJl|$W+*;#A!$l$7`TD)Lp$DOE57>KrUvpyj` zR!n-RrQQ4Dbw|#NdL0rn(-f@R$E}%*z5lv^+<(aFeN7$~Fu;af^l=dJd$2nQ$jqK#?gjcqD(UKLgOj%rwoJ%ykEVsf+sjx+rh7DyqXqJu0v`W0SlE+B48QzcdbGPy&}Z(7I+F7PT+6+X zfx)Uim;WdqNvRKbb-WEI+iL)A%hP{I3@|FoKY~0vB82HwQ)rah>{(Ot? zkE~$No5zIZnMk00wn$EYK`0rz^zv=);^oBamwwFizXZIWGy9N$NX`axBNcm;iRRa{ zgw2(41&FAw(17CYwcG|8)<{>wxeZ*JqQms?ngObGzdUX4W+AXVp$$_>o-3IxKMm5| zWeHcXd$}=!mS2QjyHJWJDtM)R$QPhhK|Y&=N3i$YO2vzL9pvXWfQI?rE~x47qR54O z#!9=>*W<}C`>~hylN$HCii?u{0{*0>7GjgjTrQnklbZisH*)BM*3cWUD6r!ym zy*NU&U+38gjWkLO>g`Bc9|w?8To+tZUo;<(BzPJTRAs;LOThcdzz+#{9PFVW+YJ0bV%m7HcfPTCGoo=JP&M@$5T$E|>+`{UUzmdkZ*W_UV^FpV?LWLPx z+>1fLl26)xLoFBIu9_jsX;7qk*ASnHf(Fi(EU5-rZ2(CiT}pYJhGHpcW6S4CE1)ZzVy!kav^odK1Ay0E)Mk|paH)U<}wnD=H1DoJADqkGC=sWuOlg_OxgICaB zc06|+h4ACaP+d^`ny?a>#~3OE|6V{#I`9{}QeWOg3Q=HVJG}e)(i!T`SOYT#*3-ep zks}~{;^0pgq(J%kgc!%k9g6-~SIN{~o0sm4dR(MR>WhYpzXT+|TTc%OxR$a8Yr?PT zRd`gEv7XF9u`JDi+BO}4on0|^zLon3yG}Vz;Zh`7-9*4l1QscUwyKM*!vE{OIoxI` z)}Yl7Q4si>R25&zezg0-B;Lff{ks)Q|F%W}`u^wSg8ti=+i9XNBu0~y!?Q6yRq zz*bf>*GwObX5q>%@U}=(>X$fg_9*?u41?cWz*Q-Vo9FSYF`1V51iAjWcFdUcp$a{Q z%aX|${OqR-ux($pR`VXw2%|d>d0N<*_{X%pds$dJhm@FAG>Nz6!o<+filV^*+gJdhQUo-rQ`B;bMD^7F~ zOKH;D8BL+R>ropB+;1f=#Wx`A!tbacTmzqX<_L3xHAHh6U-np71ME1gW`om-HV2eK zRf-X$aeu4xE}@#1$T=Z)v=lZX_<$Jx?v!4i{Zl_ggzf=5a}-a}mY7)i}e;3SMVydUg638TzE3fQ}# zE>j7Ds^Ia)o=43JMz2A8SJ8%A7*>8A1UP0m?b_l`OQ8{?=)$Z2o6mqx{~+N19qd4E z2o6pnnp#^21k8m=kt%%mqEbEuGB662s*EVCAposx5!V`e^MzcK=Itnp*JljWlPRpS zCHO*qY@k8=bKGpSio%yNoF>-QhnjROp5C^f2V>l14aOnwS$+#hhlrZ-!~>9L{i*UX zIe8pUGRJKUtAa|9Aenlr z^trHZUN|ZB}&q(d|G#qlU-va7Z$VG#ndRy{* zoUSnH`arxxHrNCAG6f2%5n3DXF}0wOJWCTljl zp)LFJF9GjQhj~cAp4=tum`M2VqJAw0pXWP-b*YR#a>fgh=FfMvZEl5*E!M$y=C1ra z7G@+kiYf>f6ln8rd~Rv3Y@7J`s-lVPZV={a&S@ewK7U~qV#rd(JLf>E3QL}qNW)I9 zv-q(9gXu|HD%{(jijAn84B@b)TuVPD)WPtv_9~uIfG6109q2raw@=BrK$;E1R3rT4hEQ`MjSI3!3^%EP z)c?AG!hgu=eN7$~u(r;ZS{cXEM=KEoyxH!?N#?@;p2GrJPT!J2w>OVmFjqfckM$3N zbF&pjQKYp;n+5Mm;^=VJ6$}K`30sh`$-~9wnLs% zfXH8i?Na6lga=52)#*S$yOw0+mhAu4+*y7_*>!PvXdH&21*D|A5eb2zyCkF=BqT+o z5hSHS8j+9z;i0=*knT?Dk}jzi-lgAUt>K%0!nOAATIXKpK6`K7g&qRfzz}43Iv=+U zFGQe)?WZC5qPu-5C%46AY}dE0%`x|7vVAQ6v@a*ds!W66r~Hp*1P1VC1ymUyhh@m< z-43>}+C9Mj2ID{)$t=tRhmZd1$zx9rbW6QYoE$Ca$A zeZS$J$w;oWbF@Uxn1BbVsm$LJhAkI=3wYbMb(etfi!|_N5_Z<))pUnh-$Kp(e-$~~ zb`D~(7fF&m|0pyn0$-i8~@Xn@9rNqJ`x`E4*H&vzfvxk?vSN*_%aYha$`UB zTEcPAnm7xaDz~Jt(Av>#8DEn7Ul&mHFFC!f$=w1D%%U|HzY&6t<${2j>Y=><8Q2t6 z`@)vfym(Jnq{6_9+Uj;CAtY#+@Hze{!NP;V9SlYrGUauD!^3t~SCa8yS#?X!)eJPn zaBR_Dg1ChGCFmD4QKsO+dk83YZ?OEzc*htgu%mG@2-qr0=OMruiP2<+KQW(R>zHDqN|zvP_7pMD8&ij3FMy%F3-}oRDA7j= z(c3f9n~`JR)yt*Dg$ZjothL)!fPqWD1-$J?yGuYtq_>&f%KYZ99oGAl@;_Qe+D)($j zo3N4d1BEj0;c6ZQ*Z*jSn0u-)N7uY7DCnfXolT6)1M`<5kKK1*gP`wn>w57>m1akwseL0d?sx?tXDll^BSwTeNna^>)2jqM#v%M zi3!(xwg>$jC~pcY>&pG_@n8fXac?l^bUVVa*ISbIDL4Iq1hVsp8Rw~wVC*#JOuO>p z%&RR_WNTqC@2+0i^}F_oCCz=yo#rT7^)1+M)Ml`CYBQ$@8q(3SkN=T-M@*34C@~8O zjW>c|pRC{W{sMyh>&z}CnJ9TYd^fw4rg>5*iFh|cgsNr%)B$LsnSkORNLtn^&Hl-D zPf$ORRZsu03$p}2CB+X4n-#v0!yh+Wq(z+I0RQisyi35VoQH|fM&9Z1+7#rV>+I=_ zZla+vATPJN1a1oJ`HsAIO(tvR63>(i6fV zMAbXgRZxWbnJh_Ha%8LI0;1pZX;_ns1c9(MnV@I9+{1KG@FXO~hgH8%ELWk#PY~YO z7=leB?aL``1%d#U0zc!vM=wjVI>6CJH+JjDfRjL^(+0i+A#O!(D7mhW=rMw$ z*WG_fB=UWon19&MyEg$iBT_W$>HO#S14{lSr?yTqHy3*U47=&+|!i1E}INk_;+4 zKJBVhg3(cq_jJyln`*OTfHC8#T->@;AReNr{aU zCWt-kzc?2gJwP=2;vL_2#zRdZ%iG6>|Kg2j%$K=H^HpREXj&Mdk&9^jDpgh*t~ugO zF9VZF%f4`GuR_yKseav%xyHyG+_&at6zE!&20+!bnQHI)JHbI#v7L+nJjK^1M*75h z^QN|u(K35E6s4;JH~#^pU$XJ<67LAIBTX%{JnH!1EtaB>G>I*>6P{@B z(TOKxpuD)9X>Eq-m3EBLqb+&?$Z%3b8oQI%~8u3Exq{MVGwX6?dxpyCDSxA zj4P@&kMTbg9rO233S&iKlZWGPYW|Q~nGLKg;(I}lTE=A_OKeEY>j{%^ehpl23dD_w{EYBSFlO(ovI#}Z? zU#n3zMNjHJHbnL51UG>op;6si%Y$RI0`LPRLv<8q%69eD(fh*w_&;7oum*y=%lsYb zsK!piQ(_}(%Bg&hiJWlYt1UA2$E~?$SH7KI{_6tD{w1fkHMv_rOv*O} ziRZSww!eVc=?RgzP5qWGEQQ*LlmEl^fjy@HxqE|Mu;vAa)orghiGzUFeSM;27Bw`} zFupSXq_S+3ps+*x?&-R7bXV7u0r=}u%$%ypQp!7HqthNnvT?EbZ;I<-MlCbX)tf%l zCE`SMd|#x0Rkhm_#Bxb_kJZ0_PC)e@NKj;BwTv-kQx6lK@kj+-{~Lwx$Nn-KU9#g z6We?RLFy<%Dmzo!Gb0|O8GjpT7RDR3{@yEoEo@9}=KmHEi*VRjouYFg=tA!l?ZCn* zv>ER(w?D7G>UDwXMHzrtdIpIfPI`^kbUi-2L8A}73;aaaIQ=|>6SZ3|Q-J#HJRLb~dY|zk_|cDZ z)J1mE3u%H`QDMX=;r<;{fVPzi>|lQcJVTB~oXJI|DV6G+xaMpXoD$a4*1K#QwxdwMn z6Qi8-p%@S(2t0*E7y0mIN5M9E{GJ&O-fL^JbIE*{Ax(yKFYVMG9AT#sGfesCgHCn> zExA<>diOd4^rnA&J$}}oh<@rv0D=IQ@+l(;i-!wUYOd%2+fB=gXAzZM-ZR^*{~^Kq zGfFiwIJC1oW1T8DX7f)t9C}-bMLq{yZJe#!rY-r_1JC=fpMZ*g$?0uP?iTQugND;m z=OHpCB?x$uJoXuL6;p^G#`(49ARTSe$CTR>sW=OdUMefDKPsMtA5YYuYR7gFj7b2s zjeDoRtF^PP+d{-KWPDTO<>b)Qy6jQD8%aiP>u!_>drkpL_XcAfNCBv~x?FkR+=2?F zFQO0s(i(Gv@r8jySlCXWgxVaOmH8m|b*#i#DsS0%<89kYQG~d(|#>ai$DtpxBZ(cFp9R^`{-S{ow?MQ^X1f+If z);+4<8%uwMq1^$E`iy>$+xK6Kf%OT~qEJ*!ex8nRWt z!OkYLNbllYm71N=c&T8B@--6x)YByaL7dv`Y%~@6DP)`rQi8DRE8d&Qu8a246p0f* z+7ZxEpGBqyPsHNpU}2^ zxGQq@!ZM{I+hGVcu%EcO7p0qgW`$*T#`HSNue$cr&%$4Gh!@UOQ!6AwT|rRbsdKTfp1V9d`-1 zXV`$ohT~J>hN8^$Gw3Al7nTmicmSN7Si%sqh)}h8YMMEb0EZuy+SI$HkS%d8n~5~O zmA6k8vUPbvC8#B&ZdR-Cg$O84Uro9OQa9@ahSYNVT)h7Tm!6;MZQ}|KdpQ)=SHFTb zFsb#pIf}}f!G63TxKB=Nhbp}A%%-c2-9!xpnGzrBuo(u#x1>~*yksVrQ1D-s`aZr! zKK~XD=~<=xB3I+d3p5$ku9U_QE92^tF64Jar+ya={kkXKWd9_;1VH5o(JK~Ave;Gy2;T;+lu!H>(a3SYC=6TDxnT9lC9qY-p(Ys zOTdB*V=}FVV(>F-v+m&aYgWeY?$8d80qC-%bfG2Cc0F->rCtk)NgEQ2tfP`prt0!6 z5A$)LZ-%)uh$?P2^X5OGnLU;6prdu%r&wH1`ZzK8gy!iLW-+>X@Z-+L?Zzhy>5oV2 z4O=t1c;sWVVxf{8y%nrjbbbdrOiCEM?>JL{AV^x1gopDZ8x2f5vI2y>aQ9b@?1E|y z?yr>hzAm4FhBFeLoR&+>J}oSLNn-A_9~j2RlGZ?8s-cnq2(*8de_Hjg3#j&&oZi;t zZUNb{zokXQ`sW2Ffq;v80itumi+q%Ru;sK{+$8m7ws$tpY`6gqo4n;=Z!!9_>IbCL zY^W8wO5zq~6*+?5yHWuErL;9iHmCvZVoyV69%o8vD+#poq$U&goC4JE4MyEV1QpRI z2vZsd0fVq~){sX1vehDB15++pd9MmjsBDp}_n|ZiI&Tu`HR6h`IzfI8bi?7Vvg< z&Rqh=k_Vz~1sF7HPt1*wQVBw6$R=ykDK_Xw@K@K^1>9TY*A<@6VHRRCzzKKpj9+OK zi^WJM3B1hOI`0zk=NNwuf|OwEcTxlxb>{k4Iy$#jz$YBOnV+P`a4*#gz--aCeD}Qd z*A%w;I-StB%4*N=VmroPfyYxEEx8DHz4ADCY-QyjVo#trfYDVvCqLc5FCT&;^{)%~;x9S9 zt;yX2wk7Y^9EHZabIpQ)v$!nLb;*9-$~&;-)cjqXuWV}~2bGTIR_JFfrJ4d+imz@P z=5guozbWwJFKq}h1mI}%=%6D%aP7pJaG0EA3zX759EH5aPNU^FB!NAr0F8Tt4Xva> zz!u)32{&73_?>zQ$-17VNS?w52GyDGerZIOz))t*nZ$Ofw5MX*=2UM(o}?;iNc`di z;cHL_oA)EJd{MXksLrAkI_Zybnm_rfNlPZ1U#9hUIKmG0N5K5c3_f@1@wK^3JN@vk z`K-Ee`hpOAzpxqWHLUC}VI)ZGyI-E?)m6V#-vh^)_&VG1yY(OaM64b+V(n1#Fm(I* zTfp0)Kz9jPhmM|Mzd(~_(bQ|?>)g@IJN?al#-4+_+&Q8tS5zA9@_YK;+h_bu1jGo= z-2<-;V=V>_sf%?$t5KT8$Spm7AjnXG7NavRVRznZo7@X~>cXIDKQ;a^p1P0FkLIfM z6t5zNae3GH2(XPVsN-!Hx$zkt@Tk&@IX!5y4Y|+*$#2?`PUh_d;(TTH?5&}ZDM3fE zlN+HEl!y-r-}u*)as0#v5l>@)O7`_uyA)nnyoUpak+&05wzyNYOhl>sr&9W0|LX#3 z{w1fkHMv{B%moX2nM-zo{!btv3jQzu^NmguB?j1XiVl)=)<^^wK=To&s;?>|#3rHmKeQf)J4oa?b47O0?lfJ`gD`XO292nB-# z{TPUoN*okEulYpP1zNaS;>q3LUbTPI4dFFCP_ittuQQl}?=a^ONAF}smd>B= z0kXg~+It!tR<$$}YMK^iP{azc7X~>1vQa8^0d~TjL)u3-qkXGZRN5$lKmY-e@NpD$GnG2946U1n`s5#Q9$ge(PLHZ6%86RvRU>~XdD~E4|q6~JhKLQdxp0G6;dSPH> z#n4;L@G%(Sp)r_{m87|kMbq6Q-klP8wTqn7rDYYsUrZEDDrp~THk}Yqec7oV1k~6l zNNV2wE#U1`uDb*jp5+~LX}7Wo9+bnB-;fbGjtbQm?o8wo5IEVirh6|#r5kl{ov&Rs z)`0v0iFlsC;?<(wl89;i$qN^%@i*-^4Y_R*Yc@VJxJ24qork*ig!PrbjEuYuAACcX zz8ak9$w!&>SzCg#O>YPz2bce_CCX`0urwlmheRa*Q})mpmz(OQBOV+o)4|Swo#Ir! zjk6%M$g$j(CV6#Frm5iUF}|gr zSO2IZ9dIfwPIiT?4#X>PLm;C~9yHJZ=)itIQLoR=2>Z8cgEn-iOu6L;dE>|1r^*%ghe zozUR^!ljU_<(Hynx$e|2jn`?}%TBF^DTa~;n&p9Cf^;XYrxHw!@GPU_i*u0L%dYwLCNPMwHA(56UXq8v0^_>Z$3d4$YzQ`s!9e>`!0-$N5dx<{;$K~q z&OU_HL`VcN| zxYq|`=6_v4{lDb&wkCHA*qKPLsVrSDHg^O97HoV*#ghNI>;Z-?r>Coy;k{9eM)_o` zI(U6=5T;nCbQj62ccr$zyNFI6pyUT6T3|3eB~TK{lzN)uiI2lIZa~+N%8!ZPD2iF+ z2ZlYT0E2sj{m_}!1F9%&*$IMxwnQIQbQx^-xu3%ZmKD>(nH?#tkEA-N3~DmCFsAYh z{t{2nL6KOqUzIkKjk(8%^18l3WYr&`_NlMJdVBzQ8H}1jBlv?**Ie=IJw8GI2SikX A`~Uy| diff --git a/pkg/core/interop/context.go b/pkg/core/interop/context.go index 2e26d349f..a489dbf69 100644 --- a/pkg/core/interop/context.go +++ b/pkg/core/interop/context.go @@ -116,7 +116,7 @@ func NewContractMD(name string) *ContractMD { w := io.NewBufBinWriter() emit.String(w.BinWriter, c.Name) - emit.Syscall(w.BinWriter, interopnames.NeoNativeCall) + emit.Syscall(w.BinWriter, interopnames.SystemContractCallNative) c.Script = w.Bytes() c.Hash = hash.Hash160(c.Script) diff --git a/pkg/core/interop/interopnames/names.go b/pkg/core/interop/interopnames/names.go index ae1167e2f..a96b8dee1 100644 --- a/pkg/core/interop/interopnames/names.go +++ b/pkg/core/interop/interopnames/names.go @@ -21,6 +21,7 @@ const ( SystemCallbackInvoke = "System.Callback.Invoke" SystemContractCall = "System.Contract.Call" SystemContractCallEx = "System.Contract.CallEx" + SystemContractCallNative = "System.Contract.CallNative" SystemContractCreateStandardAccount = "System.Contract.CreateStandardAccount" SystemContractIsStandard = "System.Contract.IsStandard" SystemContractGetCallFlags = "System.Contract.GetCallFlags" @@ -64,7 +65,6 @@ const ( NeoCryptoCheckMultisigWithECDsaSecp256k1 = "Neo.Crypto.CheckMultisigWithECDsaSecp256k1" NeoCryptoSHA256 = "Neo.Crypto.SHA256" NeoCryptoRIPEMD160 = "Neo.Crypto.RIPEMD160" - NeoNativeCall = "Neo.Native.Call" ) var names = []string{ @@ -87,6 +87,7 @@ var names = []string{ SystemCallbackInvoke, SystemContractCall, SystemContractCallEx, + SystemContractCallNative, SystemContractCreateStandardAccount, SystemContractIsStandard, SystemContractGetCallFlags, @@ -130,5 +131,4 @@ var names = []string{ NeoCryptoCheckMultisigWithECDsaSecp256k1, NeoCryptoSHA256, NeoCryptoRIPEMD160, - NeoNativeCall, } diff --git a/pkg/core/interops.go b/pkg/core/interops.go index aa69c2769..8558e4797 100644 --- a/pkg/core/interops.go +++ b/pkg/core/interops.go @@ -59,6 +59,7 @@ var systemInterops = []interop.Function{ RequiredFlags: smartcontract.AllowCall, ParamCount: 3, DisallowCallback: true}, {Name: interopnames.SystemContractCallEx, Func: contract.CallEx, Price: 1000000, RequiredFlags: smartcontract.AllowCall, ParamCount: 4, DisallowCallback: true}, + {Name: interopnames.SystemContractCallNative, Func: native.Call, Price: 0, ParamCount: 1, DisallowCallback: true}, {Name: interopnames.SystemContractCreateStandardAccount, Func: contractCreateStandardAccount, Price: 10000, ParamCount: 1, DisallowCallback: true}, {Name: interopnames.SystemContractIsStandard, Func: contractIsStandard, Price: 30000, RequiredFlags: smartcontract.ReadStates, ParamCount: 1}, {Name: interopnames.SystemContractGetCallFlags, Func: contractGetCallFlags, Price: 30000, DisallowCallback: true}, @@ -118,7 +119,6 @@ var neoInterops = []interop.Function{ {Name: interopnames.NeoCryptoCheckMultisigWithECDsaSecp256k1, Func: crypto.ECDSASecp256k1CheckMultisig, Price: 0, ParamCount: 3}, {Name: interopnames.NeoCryptoSHA256, Func: crypto.Sha256, Price: 1000000, ParamCount: 1}, {Name: interopnames.NeoCryptoRIPEMD160, Func: crypto.RipeMD160, Price: 1000000, ParamCount: 1}, - {Name: interopnames.NeoNativeCall, Func: native.Call, Price: 0, RequiredFlags: smartcontract.AllowCall, ParamCount: 1, DisallowCallback: true}, } // initIDinInteropsSlice initializes IDs from names in one given diff --git a/pkg/core/native/oracle.go b/pkg/core/native/oracle.go index b97beb0bc..162fa9ca9 100644 --- a/pkg/core/native/oracle.go +++ b/pkg/core/native/oracle.go @@ -59,7 +59,7 @@ var ( func init() { w := io.NewBufBinWriter() emit.String(w.BinWriter, oracleName) - emit.Syscall(w.BinWriter, interopnames.NeoNativeCall) + emit.Syscall(w.BinWriter, interopnames.SystemContractCallNative) oracleInvokeScript = w.Bytes() h := hash.Hash160(oracleInvokeScript) diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index c01db8a29..3d812813f 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -57,7 +57,7 @@ type rpcTestCase struct { } const testContractHash = "743ed26f78e29ecd595535b74a943b1f9ccbc444" -const deploymentTxHash = "8ec2d061ecb22115f1e25a8bf79e536134d0cd7f0ac31b89cc40960a21dc8171" +const deploymentTxHash = "7e60be17078d05135c359805beece3ea4c5140f55adec8170179713f2124eb14" const genesisBlockHash = "0542f4350c6e236d0509bcd98188b0034bfbecc1a0c7fcdb8e4295310d468b70" const verifyContractHash = "a2eb22340979804cb10cc1add0b8822c201f4d8a" diff --git a/pkg/rpc/server/testdata/testblocks.acc b/pkg/rpc/server/testdata/testblocks.acc index a3fc08b76bf72cd9e55d55e9047e786e55b4d758..f7b74765d291f3bd205dfa1b718231adeb0e1ac6 100644 GIT binary patch delta 2708 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 MX!{~A{XbvxUsj_N2><{9 delta 2706 zcmai#_dnH-9><+yo*bLZkX9ffmFLD?hOEg{9x zG2$4v(;*=ww|wv8aeuq_{U5wuujl*qD%B{}NR^Y&4F5@k{kbJgeWs6A4@upv+m37s zC@6E>-Y$vGXa1h0h>L@#nTcX@<(Es7gdnD6eI1MPZN%64@$n+|4L(NP2#G|as0~XE z_fFs?{37QE$F328Kc;vop_=!s2;9R4)}!aiu3rcRA_G?#7dQP5^P35YHYME7>ZCXu zp7_0)seUix%iJ5!D&4KqM_P#cN78dvMLMJ-5QM zUJ>qhh60UwBi4~y!>sSmhX1y*>Nq!J_>u{^#%CJK`YL$mnW4)ynu5zi-}Rkww4s^V z@DxFgeGj4@iq$`MWSKTytzm)<3RvL9w6Jeda()3^B4Kb4BeJzF7%4?rfGk=g~yYFFf=C`m~R@BmMeaNc}CJ7tf@}0 zYy%@58HH;y_rIGrQ0@=X92@=drx=f7bii9cVEAGt1AGqT27Zmn8~+6|2$I9UVI0LX z3q5L9VwMKmt-gQI=^%hcF4>~%Ch@kRv9&DQC%Axgp+IYy$&-SCHE>ZkSH6Y?ITTKC zU+xWqd-ZjhKE)qTk6rg*r8zJWD^T$i=Oe5I$?Ym4&Z?>vn$2Z}HztGYonMHKE-X7bTo86>zJd^9tPlM)I!(dSl;_ctBeb;`MI5s>G^k)V=x=yvg zpRuBXz-@e9P+%eLi~W>|DJ|$3pVg3mDW6W=z$*umE$30&^Tmyvv{|nR5T#uE;)Hr4 zxGaA~Gj8b@+lYcQp=?ado5KR8#dr@2Y`(SCb73Qai)VX|S~S*T?ZUA+;>RK>$&rDt zZ6b*wIwC`N({9arpZS_f^}KRU<5a<;V9AH%(oJuzL>dUf;UtJAwne>tiK-8G4%YWl z0^yvmU0*tSQ-to1KEm|Q6qzgps#kbohV_!tFd3EoLPrGjQwh4&!JVqjrOBkwa+ni1 z`DjP!uPqFbM|MIP)LOG;2+*GYDiLYS1`W^_v!2xdn_;zfev)dMkao|3@$Xq3<~T-B0BONx2ID(bjuTRTn21BpVn>ZrNn7oFtuyl4%{|fr;y= zheLehrikT?&%B1(`qSp`p%4|!y738pSlWN+8)fh{=5`>`j)fJl*=Pc~%6B9=XOMmD z^`8}79pRf;`~xmgI_1AM7jlVWmQ)7R+%_^hRYBI{+G!PqLX#~4xw$EZTY}3imZl7T z(cpa!rMDa;A2)~PIHo!mi&4c|COH8c^_!|_QAHMeD3Ie5s$@6aG(6JddkxQ=%rv`(Lhx!L}bTimYK`Sb`R@E$@dPI_0lYFcasDMCgbVc5sl?&82W+4E4~ zV;q4Yp<~|hR%2GC>}g4@2jv0>b?WbUUR?D2TTM4SF$iOg%&hiC2;xHL!ImCsrp1m2>{)9qj-?M$`v z9b75LrF62ab2XQX_@Thk1-G1jjXvq^>h4tSAd{~anLKT>C~A~6R%~dz7B?bUzu4bu zFrPRkAd@}3S_N0!A7f`s-rGFL@0Oy6WywGMGav4oG_;y!72{MH0Fb(cLm_QP;v26U z%*fKHh@j}WzjUZDum~rxZ%@uRC8^J6huO?rm508l8Cendm|}RMV_lNZF*fSL$o^ R+Xh4RB1(0McmBTSzX9p$6B_^k From 8e2df8902c1a2d0b19f004a3e79dc8eb7af7632b Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Sun, 13 Dec 2020 21:03:25 +0300 Subject: [PATCH 07/14] cli/wallet: don't mangle token symbol in output It's important to show it as is because it identifies token. --- cli/wallet/nep17.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/wallet/nep17.go b/cli/wallet/nep17.go index dd9006bd8..e988a0a30 100644 --- a/cli/wallet/nep17.go +++ b/cli/wallet/nep17.go @@ -197,7 +197,7 @@ func getNEP17Balance(ctx *cli.Context) error { } tokenSymbol = "UNKNOWN" } - fmt.Fprintf(ctx.App.Writer, "%s: %s (%s)\n", strings.ToUpper(tokenSymbol), tokenName, asset.StringLE()) + fmt.Fprintf(ctx.App.Writer, "%s: %s (%s)\n", tokenSymbol, tokenName, asset.StringLE()) fmt.Fprintf(ctx.App.Writer, "\tAmount : %s\n", balances.Balances[i].Amount) fmt.Fprintf(ctx.App.Writer, "\tUpdated: %d\n", balances.Balances[i].LastUpdated) } From e3dfb5d165510944cb381a3313a3e8a61614d6af Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Sun, 13 Dec 2020 21:04:10 +0300 Subject: [PATCH 08/14] cli/wallet: swap name/address checks for token identification Address identification must have a priority, another token can have this address in its name, but when there is a hash specified we should take the one with proper hash. --- cli/wallet/nep17.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/wallet/nep17.go b/cli/wallet/nep17.go index e988a0a30..80269f06e 100644 --- a/cli/wallet/nep17.go +++ b/cli/wallet/nep17.go @@ -228,7 +228,7 @@ func getMatchingTokenAux(ctx *cli.Context, get func(i int) *wallet.Token, n int, var count int for i := 0; i < n; i++ { t := get(i) - if t != nil && (t.Name == name || t.Symbol == name || t.Address() == name || t.Hash.StringLE() == name) { + if t != nil && (t.Hash.StringLE() == name || t.Address() == name || t.Symbol == name || t.Name == name) { if count == 1 { printTokenInfo(ctx, token) printTokenInfo(ctx, t) From aff14694820ba093a0aca34b0432b1c2b6b12a83 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Sun, 13 Dec 2020 21:05:49 +0300 Subject: [PATCH 09/14] native: uppercase token symbols Follow neo-project/neo#2136. --- cli/candidate_test.go | 4 ++-- cli/multisig_test.go | 6 +++--- cli/nep17_test.go | 12 ++++++------ cli/wallet_test.go | 10 +++++----- pkg/core/native/native_gas.go | 2 +- pkg/core/native/native_neo.go | 2 +- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/cli/candidate_test.go b/cli/candidate_test.go index 3018754d9..262e05e5a 100644 --- a/cli/candidate_test.go +++ b/cli/candidate_test.go @@ -20,8 +20,8 @@ func TestRegisterCandidate(t *testing.T) { "--rpc-endpoint", "http://"+e.RPC.Addr, "--wallet", validatorWallet, "--from", validatorAddr, - "neo:"+validatorPriv.Address()+":10", - "gas:"+validatorPriv.Address()+":100") + "NEO:"+validatorPriv.Address()+":10", + "GAS:"+validatorPriv.Address()+":100") e.checkTxPersisted(t) e.In.WriteString("one\r") diff --git a/cli/multisig_test.go b/cli/multisig_test.go index 7694d0c73..e3a9fd304 100644 --- a/cli/multisig_test.go +++ b/cli/multisig_test.go @@ -55,8 +55,8 @@ func TestSignMultisigTx(t *testing.T) { "--rpc-endpoint", "http://"+e.RPC.Addr, "--wallet", validatorWallet, "--from", validatorAddr, - "neo:"+multisigAddr+":4", - "gas:"+multisigAddr+":1") + "NEO:"+multisigAddr+":4", + "GAS:"+multisigAddr+":1") e.checkTxPersisted(t) // Sign and transfer funds to another account. @@ -69,7 +69,7 @@ func TestSignMultisigTx(t *testing.T) { e.Run(t, "neo-go", "wallet", "nep17", "transfer", "--rpc-endpoint", "http://"+e.RPC.Addr, "--wallet", wallet1Path, "--from", multisigAddr, - "--to", priv.Address(), "--token", "neo", "--amount", "1", + "--to", priv.Address(), "--token", "NEO", "--amount", "1", "--out", txPath) e.In.WriteString("pass\r") diff --git a/cli/nep17_test.go b/cli/nep17_test.go index a5ca20494..fbabec187 100644 --- a/cli/nep17_test.go +++ b/cli/nep17_test.go @@ -34,7 +34,7 @@ func TestNEP17Balance(t *testing.T) { e.checkEOF(t) } t.Run("Alias", func(t *testing.T) { - e.Run(t, append(cmd, "--token", "neo")...) + e.Run(t, append(cmd, "--token", "NEO")...) checkResult(t) }) t.Run("Hash", func(t *testing.T) { @@ -43,7 +43,7 @@ func TestNEP17Balance(t *testing.T) { }) }) t.Run("GAS", func(t *testing.T) { - e.Run(t, append(cmd, "--token", "gas")...) + e.Run(t, append(cmd, "--token", "GAS")...) e.checkNextLine(t, "^\\s*Account\\s+"+validatorAddr) e.checkNextLine(t, "^\\s*GAS:\\s+GAS \\("+e.Chain.UtilityTokenHash().StringLE()+"\\)") b := e.Chain.GetUtilityTokenBalance(validatorHash) @@ -112,7 +112,7 @@ func TestNEP17Transfer(t *testing.T) { "--wallet", validatorWallet, "--from", validatorAddr, "--to", w.Accounts[0].Address, - "--token", "neo", + "--token", "NEO", "--amount", "1", } @@ -144,7 +144,7 @@ func TestNEP17MultiTransfer(t *testing.T) { "--rpc-endpoint", "http://" + e.RPC.Addr, "--wallet", validatorWallet, "--from", validatorAddr, - "neo:" + privs[0].Address() + ":42", + "NEO:" + privs[0].Address() + ":42", "GAS:" + privs[1].Address() + ":7", neoContractHash.StringLE() + ":" + privs[2].Address() + ":13", } @@ -186,7 +186,7 @@ func TestNEP17ImportToken(t *testing.T) { t.Run("Info", func(t *testing.T) { checkGASInfo := func(t *testing.T) { e.checkNextLine(t, "^Name:\\s*GAS") - e.checkNextLine(t, "^Symbol:\\s*gas") + e.checkNextLine(t, "^Symbol:\\s*GAS") e.checkNextLine(t, "^Hash:\\s*"+gasContractHash.StringLE()) e.checkNextLine(t, "^Decimals:\\s*8") e.checkNextLine(t, "^Address:\\s*"+address.Uint160ToString(gasContractHash)) @@ -203,7 +203,7 @@ func TestNEP17ImportToken(t *testing.T) { _, err := e.Out.ReadString('\n') require.NoError(t, err) e.checkNextLine(t, "^Name:\\s*NEO") - e.checkNextLine(t, "^Symbol:\\s*neo") + e.checkNextLine(t, "^Symbol:\\s*NEO") e.checkNextLine(t, "^Hash:\\s*"+neoContractHash.StringLE()) e.checkNextLine(t, "^Decimals:\\s*0") e.checkNextLine(t, "^Address:\\s*"+address.Uint160ToString(neoContractHash)) diff --git a/cli/wallet_test.go b/cli/wallet_test.go index 986adf4ac..6fbb2a9cc 100644 --- a/cli/wallet_test.go +++ b/cli/wallet_test.go @@ -182,8 +182,8 @@ func TestClaimGas(t *testing.T) { "--rpc-endpoint", "http://" + e.RPC.Addr, "--wallet", validatorWallet, "--from", validatorAddr, - "neo:" + w.Accounts[0].Address + ":1000", - "gas:" + w.Accounts[0].Address + ":1000", // for tx send + "NEO:" + w.Accounts[0].Address + ":1000", + "GAS:" + w.Accounts[0].Address + ":1000", // for tx send } e.In.WriteString("one\r") e.Run(t, args...) @@ -261,8 +261,8 @@ func TestImportDeployed(t *testing.T) { e.Run(t, "neo-go", "wallet", "nep17", "multitransfer", "--rpc-endpoint", "http://"+e.RPC.Addr, "--wallet", validatorWallet, "--from", validatorAddr, - "neo:"+contractAddr+":10", - "gas:"+contractAddr+":10") + "NEO:"+contractAddr+":10", + "GAS:"+contractAddr+":10") e.checkTxPersisted(t) privTo, err := keys.NewPrivateKey() @@ -272,7 +272,7 @@ func TestImportDeployed(t *testing.T) { e.Run(t, "neo-go", "wallet", "nep17", "transfer", "--rpc-endpoint", "http://"+e.RPC.Addr, "--wallet", walletPath, "--from", contractAddr, - "--to", privTo.Address(), "--token", "neo", "--amount", "1") + "--to", privTo.Address(), "--token", "NEO", "--amount", "1") e.checkTxPersisted(t) b, _ := e.Chain.GetGoverningTokenBalance(h) diff --git a/pkg/core/native/native_gas.go b/pkg/core/native/native_gas.go index 2d6122712..28714bf62 100644 --- a/pkg/core/native/native_gas.go +++ b/pkg/core/native/native_gas.go @@ -28,7 +28,7 @@ const initialGAS = 30000000 func newGAS() *GAS { g := &GAS{} nep17 := newNEP17Native(gasName) - nep17.symbol = "gas" + nep17.symbol = "GAS" nep17.decimals = 8 nep17.factor = GASFactor nep17.incBalance = g.increaseBalance diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index fd255a47b..77d31f225 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -94,7 +94,7 @@ func makeValidatorKey(key *keys.PublicKey) []byte { func newNEO() *NEO { n := &NEO{} nep17 := newNEP17Native(neoName) - nep17.symbol = "neo" + nep17.symbol = "NEO" nep17.decimals = 0 nep17.factor = 1 nep17.incBalance = n.increaseBalance From cf8cf93e7abf3676030ec476e774909ffde661a7 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Sun, 13 Dec 2020 21:25:04 +0300 Subject: [PATCH 10/14] native: change contract names, move them to separate package Follow neo-project/neo#2138 and make RPC client's GetNativeContractHash case-sensitive. --- cli/nep17_test.go | 21 +++++++++++---------- cli/smartcontract/smart_contract.go | 3 ++- cli/testdata/chain50x2.acc | Bin 45766 -> 45766 bytes cli/wallet/validator.go | 5 +++-- cli/wallet/wallet.go | 3 ++- pkg/core/native/designate.go | 4 ++-- pkg/core/native/management.go | 4 ++-- pkg/core/native/native_gas.go | 4 ++-- pkg/core/native/native_neo.go | 4 ++-- pkg/core/native/nativenames/names.go | 12 ++++++++++++ pkg/core/native/notary.go | 4 ++-- pkg/core/native/oracle.go | 11 ++++------- pkg/core/native/policy.go | 4 ++-- pkg/rpc/client/client.go | 13 +++++++------ pkg/rpc/client/policy.go | 5 +++-- pkg/rpc/client/rpc.go | 8 +++----- pkg/rpc/client/rpc_test.go | 6 +++--- pkg/rpc/server/client_test.go | 3 ++- pkg/rpc/server/server_test.go | 4 ++-- pkg/rpc/server/testdata/testblocks.acc | Bin 7586 -> 7586 bytes 20 files changed, 66 insertions(+), 52 deletions(-) create mode 100644 pkg/core/native/nativenames/names.go diff --git a/cli/nep17_test.go b/cli/nep17_test.go index fbabec187..35c60e253 100644 --- a/cli/nep17_test.go +++ b/cli/nep17_test.go @@ -9,6 +9,7 @@ import ( "strings" "testing" + "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" "github.com/nspcc-dev/neo-go/pkg/wallet" @@ -28,7 +29,7 @@ func TestNEP17Balance(t *testing.T) { b, index := e.Chain.GetGoverningTokenBalance(validatorHash) checkResult := func(t *testing.T) { e.checkNextLine(t, "^\\s*Account\\s+"+validatorAddr) - e.checkNextLine(t, "^\\s*NEO:\\s+NEO \\("+e.Chain.GoverningTokenHash().StringLE()+"\\)") + e.checkNextLine(t, "^\\s*NEO:\\s+NeoToken \\("+e.Chain.GoverningTokenHash().StringLE()+"\\)") e.checkNextLine(t, "^\\s*Amount\\s*:\\s*"+b.String()) e.checkNextLine(t, "^\\s*Updated\\s*:\\s*"+strconv.FormatUint(uint64(index), 10)) e.checkEOF(t) @@ -45,7 +46,7 @@ func TestNEP17Balance(t *testing.T) { t.Run("GAS", func(t *testing.T) { e.Run(t, append(cmd, "--token", "GAS")...) e.checkNextLine(t, "^\\s*Account\\s+"+validatorAddr) - e.checkNextLine(t, "^\\s*GAS:\\s+GAS \\("+e.Chain.UtilityTokenHash().StringLE()+"\\)") + e.checkNextLine(t, "^\\s*GAS:\\s+GasToken \\("+e.Chain.UtilityTokenHash().StringLE()+"\\)") b := e.Chain.GetUtilityTokenBalance(validatorHash) e.checkNextLine(t, "^\\s*Amount\\s*:\\s*"+fixedn.Fixed8(b.Int64()).String()) }) @@ -54,7 +55,7 @@ func TestNEP17Balance(t *testing.T) { addr1, err := address.StringToUint160("NbTiM6h8r99kpRtb428XcsUk1TzKed2gTc") require.NoError(t, err) e.checkNextLine(t, "^Account "+address.Uint160ToString(addr1)) - e.checkNextLine(t, "^\\s*GAS:\\s+GAS \\("+e.Chain.UtilityTokenHash().StringLE()+"\\)") + e.checkNextLine(t, "^\\s*GAS:\\s+GasToken \\("+e.Chain.UtilityTokenHash().StringLE()+"\\)") balance := e.Chain.GetUtilityTokenBalance(addr1) e.checkNextLine(t, "^\\s*Amount\\s*:\\s*"+fixedn.Fixed8(balance.Int64()).String()) e.checkNextLine(t, "^\\s*Updated:") @@ -72,13 +73,13 @@ func TestNEP17Balance(t *testing.T) { for i := 0; i < 2; i++ { line := e.getNextLine(t) if strings.Contains(line, "GAS") { - e.checkLine(t, line, "^\\s*GAS:\\s+GAS \\("+e.Chain.UtilityTokenHash().StringLE()+"\\)") + e.checkLine(t, line, "^\\s*GAS:\\s+GasToken \\("+e.Chain.UtilityTokenHash().StringLE()+"\\)") balance = e.Chain.GetUtilityTokenBalance(addr3) e.checkNextLine(t, "^\\s*Amount\\s*:\\s*"+fixedn.Fixed8(balance.Int64()).String()) e.checkNextLine(t, "^\\s*Updated:") } else { balance, index := e.Chain.GetGoverningTokenBalance(validatorHash) - e.checkLine(t, line, "^\\s*NEO:\\s+NEO \\("+e.Chain.GoverningTokenHash().StringLE()+"\\)") + e.checkLine(t, line, "^\\s*NEO:\\s+NeoToken \\("+e.Chain.GoverningTokenHash().StringLE()+"\\)") e.checkNextLine(t, "^\\s*Amount\\s*:\\s*"+balance.String()) e.checkNextLine(t, "^\\s*Updated\\s*:\\s*"+strconv.FormatUint(uint64(index), 10)) } @@ -137,7 +138,7 @@ func TestNEP17MultiTransfer(t *testing.T) { e := newExecutor(t, true) defer e.Close(t) - neoContractHash, err := e.Chain.GetNativeContractScriptHash("neo") + neoContractHash, err := e.Chain.GetNativeContractScriptHash(nativenames.Neo) require.NoError(t, err) args := []string{ "neo-go", "wallet", "nep17", "multitransfer", @@ -169,9 +170,9 @@ func TestNEP17ImportToken(t *testing.T) { walletPath := path.Join(tmpDir, "walletForImport.json") defer os.Remove(walletPath) - neoContractHash, err := e.Chain.GetNativeContractScriptHash("neo") + neoContractHash, err := e.Chain.GetNativeContractScriptHash(nativenames.Neo) require.NoError(t, err) - gasContractHash, err := e.Chain.GetNativeContractScriptHash("gas") + gasContractHash, err := e.Chain.GetNativeContractScriptHash(nativenames.Gas) require.NoError(t, err) e.Run(t, "neo-go", "wallet", "init", "--wallet", walletPath) e.Run(t, "neo-go", "wallet", "nep17", "import", @@ -185,7 +186,7 @@ func TestNEP17ImportToken(t *testing.T) { t.Run("Info", func(t *testing.T) { checkGASInfo := func(t *testing.T) { - e.checkNextLine(t, "^Name:\\s*GAS") + e.checkNextLine(t, "^Name:\\s*GasToken") e.checkNextLine(t, "^Symbol:\\s*GAS") e.checkNextLine(t, "^Hash:\\s*"+gasContractHash.StringLE()) e.checkNextLine(t, "^Decimals:\\s*8") @@ -202,7 +203,7 @@ func TestNEP17ImportToken(t *testing.T) { checkGASInfo(t) _, err := e.Out.ReadString('\n') require.NoError(t, err) - e.checkNextLine(t, "^Name:\\s*NEO") + e.checkNextLine(t, "^Name:\\s*NeoToken") e.checkNextLine(t, "^Symbol:\\s*NEO") e.checkNextLine(t, "^Hash:\\s*"+neoContractHash.StringLE()) e.checkNextLine(t, "^Decimals:\\s*0") diff --git a/cli/smartcontract/smart_contract.go b/cli/smartcontract/smart_contract.go index b9ccc3032..bb130466c 100644 --- a/cli/smartcontract/smart_contract.go +++ b/cli/smartcontract/smart_contract.go @@ -16,6 +16,7 @@ import ( "github.com/nspcc-dev/neo-go/cli/options" "github.com/nspcc-dev/neo-go/cli/paramcontext" "github.com/nspcc-dev/neo-go/pkg/compiler" + "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/encoding/address" @@ -771,7 +772,7 @@ func contractDeploy(ctx *cli.Context) error { return err } - mgmtHash, err := c.GetNativeContractHash("Neo Contract Management") + mgmtHash, err := c.GetNativeContractHash(nativenames.Management) if err != nil { return cli.NewExitError(fmt.Errorf("failed to get management contract's hash: %w", err), 1) } diff --git a/cli/testdata/chain50x2.acc b/cli/testdata/chain50x2.acc index a9e5a8104fcef6e2b28b5e7f23fdc69d833aace7..c8f6d39823df96b6b054cf044bc9b606fbae7f9e 100644 GIT binary patch literal 45766 zcmd44bzD@@!|qFi!yw(=pma#X&H`NFDgS`kr&& zE9Z{)FZq0AK8$Pj%(I{8yVlxkueF910s_MJ{vS9$%DM}$(n;`t(kfJG>2;T%P1av8 z4U!8k)$Y~`JFarR4Bo=sSBCtrBWa;E<-2ajtNEW@4mgX3_1C&H#X~|sqC!FtetPcy zP$nJrze-w%iXkz_3q?#{f6Zv!xsrx13-+08cmiudZV6;R5rWPB+=NHt6JDX?J`~O5 zZ}X|S>$~}LDiJ)6ib36>G)qnIBsERP`*(eK<3GM@7Z8M9Sz0k$zz8(aQr#S{zAiS= zO^r`%@QQ4dy*ka4I2EVA{MHofhaqd&SfhpYv46RN2Qj%Ts5>n|Q|_F3B^4bAO8Ooz zDa{_J{;c9l-dx1Xen(MwZF{I@&u(;!@_=}2rF4CPs(`$K;rOJrNxO~nRIFzky2LnT zm+wp!#5EdDGk~BNdPwCMZz|g(P3&uFJWGleM5T35L{j=psf4tS@0No`!`Ox#y2nOG zBiS&#(#NGu4)NAY=FX3q^x0CFhs?%t=s;2^6mBn%AuQ#9#Pb9s>Q1a2F8N85+BF5` zZD~k;5C~*>U*uzh@@=610&1ekFiahji^fd15$=XT@rbU9E&p)9H@*8JpY@)fK)TH@ zV|Rw^YRfnXki7M%Nr18XIALGk(d-r81|)@^1j5hjuSE?wMKhojDSYY?sW#Cechd=z zkMw)8PPCScfhdR`zF!Irp$hX~HvZ?gcS6#C9s|8a{__ZR{~Pj;ra-78LP0E^!U8c= zGUi36&K1vLY|g0&;a-PyoSz8+G5+)-j20k*y{)dLospp}5Tn*DT-^@U$LhSixeSA3 z=`~VzD}|t*hsG;T$a}|KuZN*_N+IjfDTBmcgu2otXTAOO6p=M6qqj|SfRgaokmr&^ zdb*;cf#VA^zNV>`bdLs>Mj+_QYok#mmp>pA2r}jfA4DE^3O-8+s?c|uRj>w%2>1YA zQ@zj}CNNBiAWjO`F2qGeH!6IRdyI-WWzw%138f`*w5aZmVX>@ODg^``=Tl%e@EE0R zz70mszev8i6kJl?QApn625kAwnPE>GJpC@P-1`>AAFpHAF(h8cjD8Fc&L1lPgcwJG zD6L)hPdfhp@R=&=|I=qJL}&Z1UXwhdA3e+4I2o-frTTa~S10W~U-iGe z-W9;;P8|~1TS7X>v1V;yaefi$4R6G3wadoR_+CS;F!!NcYIj)sH z;Rye@MQJU9*zOxQXH<9~%9Wk-=s?G*?%*&)Rl zmQtB9=Ag;;`Bg{T{TpE39IA`hG#{%jX6NPkWV1kIyInU2IrCuwucsmImHN(%%raAwl9 zJB}_yf<%wcBde4c5RRSdw(cRJ`G1%B^Jln!*3i+=(*8f6(u0o7*w)Iy z`qxLcHng=cwX-v|vivW}`oB*IH`%lMxA^a~asQXc{c-FZtgWqV?F|j=>~$>-bZ!50 zqVAu!w{@`lujAjB(KRys@7Fz?7DMOzb40oZ|0o0Wf6=StDw4yh*`cKvS$6rhg zTucm1^!A2!_J90;92H2x_h-ffq&gr~2q-mY7e&D}0UUo)P{dXHC0Yv*&cM*x+{y)r zu?+Z3@Robr;3gIQh^yerR$wuw>^~JPq!1AFlRfLTa)n5@T;}`Zac{pBp)RHBr`il7 zTD@tpq@DGl($e+YhuxqrY)(~JLQlRX2z7c9E~{A!`1bFbmn*45PMmpBi z=nT+&sK|&-gQ8x2XU!QuPjDW;+vU_l4czEmbU?1|i=liST zc{to-6;H0L&}Lv^sap(;NGmF3BQdL#Rn84&qu3j2pe;o<$iBJgcCefMZJ_xIwk_>6 zkAtvq{h>lfLPdm%#Ej>wTNWUwfNIOAknwfd)pF_AP8{@j^5b(ljQI`2EBq6n%lM# z`!}Cgf*xtY0qxt&Imn|gNl>RLQiybT+#Oivl}+R|mX{cU8SuBxDhXbUHtGD670>_C zH~h~9`(%r{Ra&6knEB{0`P>5cQLSP)ErS`2ufwgS|;~P@&-T>>S?YbLoQdg zQIm{CR9dAB{~SEVWVB?Lqfd9Jj0Lnr@Kj~w5}L`{yr4knp;Bjky~4@N3j~=q_A)(F^lEgfuQK+B zBp!M3>G_dPuzn4hUBHXRR7G9l{aEYKLoJSPn22IYYuO(OE;03908uc?n6#;3vYguf zbpZcUpZ*U3A3S*+Ku{>Yb~l!$HViWmu-lXwdXA^rG#luT?> zd5&k#bR*<@vX#-aBP-gE_I($Xb#-uArOa*y{URZso(6iO9syU&L)E{d?Wlgf`HO}E z;y*pqzY^w8c|ntrx%l-c7YIm5Vzv=+jJ2df15emmBK4R^=Y)A;8>+Sd<+ja>I#P@4 z&KJpL##sv)$Ce=7%0txKiYGkg)Lon&g*#2$q2(B@UO0jvp!?!3enscs11Rxa6yRyu zbmdushxk?UC2%k>_}xvC-I<)OYVx)#uk%3a#1J=EbClc1EnQ=YARZXS7gD@D z5mLdbvC~I+GaP_#nEndVSXavNc}<{Jx`5%gw0=4GUiEq#1Zb*5y>1|gKpwXQL5gG$ z2PwFBYWdplH7bQw zpzWdFzYgGk2D|=8P9L6o96&aSPd%5OXzz+_fB@DXdup;ANIEi>;N`T2q#-jr1MkkH zwTKfrFR|g>XVmL!x^!EAPF8i8*w|EYnc=wLAN@%)Ma&k_}%jTe*C`+YdO)6jJMULgpvdTwwaeo0@#^+5p2N}wy9IqH$RAh;hFnd zKh$wUc*)CTb;aTHKG`QnPSOB_koj6QYs#lwZX0zjJ~(-~T%d-X zzXy;+AiABr-n=k%mQrI`xN-DmxonUfUpsJkRUk!a=zPn8ynbp6G7HOntR$E_p4&{MmG`hjp28Kk0<5QD~7H2nt#|_JShv#f;&WwkLji zW6}Q!9^us8TpPZa>jRMN%~5LMyWt==FC0xTkc4o#EbKYwlXem0_I53OC7GbDuhl@% z1bQZ<96!x9iZVnzNEXi#RrVWTDcb_g57l{U%W`)OJVKH?RcYQNu z`if?@u~R$!;ZWyg)V~fO{2y}q;K}0vuJvOWeQZmQ=fDI45Z*GgLRb|eYbt=3(^OJJ zDHzdQH@12=R(wjM;|M7|?&vm~98(5fMd>{FlkCexR)(M_p%D7zPpYA|aV78e*ZRKF z6Z89QW*^6-x`Urn2!y{Drr1`CblW>46{P|MbZ3n2;w{a!Gd6)IOr~LhIt3lu&OSnO zZaZ>gC|qo~J}nk4xofBr5mslij{_Rg2hep8u3|tv-xmn->{*b}nHEV6Id*qMoiK9$ z0lu)`UC@<%uA|n|aPhky5Ht|p>=s7ud2;SX zwICxk4c{HudPGe%I(VTcYLjaxz{;CTY_L*w+T#a-CoZ1YEr!))0!I{JPJ!h_u5QKa zbwA#GcwGG+q_V`#0^kEO4)>i|CaLrxz&c^trC3W(YeOGVn1Js@BQ-I+qC1ojIc1-zWT zk8}pg@5}c_cOD|oGMLsBLgCn;m!ELXRJ0tEyab{4H^DevYNBi5{7~|RCmAz{bWxL) zciV*IB^rSGuye8reoi3(e=TgiJn;qYUPZeeFAyNbH&xOdYoPG_96VvRatB}hl2=97 zZOGf}sj{Ob1;!+}TsLv!lXnb{nxw_es0m9)f99<9&_);12vjb>zABrfc^P{!MYGi6t&u+5a z;s zeDd&qK#<~BiI0x-*in$1ZIUtAxu{slKO{5DSRxAfXYsYonkhX*q!lbApbHqRd zyqpHofU4TAPwP@COz{9oPSrKe4U(+27U8JrEm6;#1cQ&iM4Zf1)6^ zfOOU=d!R}`m9s>@BxV3Vrx3uu7Ul|{#qC!bdX*Cm1YkVLHaPg{B&3H8o-hYmU#c0U zp`Kl5PC@#9J^0PNU3aRlO!(z~OPXIWiK$;LcFdAPGG$VV^E@4w${v`)_wKmuVMyKq z;B;}CJJo|P>^Fcq-Gi6fU-qI}tr<{*U;`k_}@~9s-@fd4X;TTE$+C< z3`AIa8iq{Q#++<;RO5*G>L9t-vAN_2{{rx#27U}cq-(yW-f1TOOLzkdtPF$%8#fyy z5eb%f|Ld!3H_^eDgB?rmfgjZM=}vcdL>p(AiYyX}tGLLt`%n#8al=ewfS@=o9Z4bq zL(j$y6*%vBHX;hOiFMUHRMksT3F&b^5x6?y;FwK?0QZe+_6`LfiRuNLN8Jqq6?2U{%ALKx5y|-~OB*gF}yR%cv!&OzAT8K7@ z?Bgf{i=5s#3L}A5E}H~X;O7+L>0b-08(L|RMr#(%ZU6#M7=TPOb^j+(rSNQEQU@>c(55ra_>pG;aaU=C`eK?Npa! zR$2D9rV(Sx;0yZ=ATFu0=WF!3B>d5*qhVjI@$e40-&tF0&J|j<`as~2TXD^6e=)i% z{}M25UA0GA)!X%~e2~eBnRN;Yq-0Sgk^Sx$fDb)|#{jJGta8VwcIpvhBZq&F!>1w^ zQ7()Mc}u}`k(pgYZXbz=H}b~RaN{t~C7N?fT_n_oWj!PJ+eW~14A>m6^!?QPD>!L( zL&8Zu0EdX-J0~q<<(vsZMI00RN{2Uwtn#}y%x2A&kYA}Xgn@J`pRHt&6I;q;_g-H= zV;%HqjKs;MQ3QfKHr1)MKyY)%(bTgUbC4#dwKN0HWT*|vT{MMV$`H;Tp7mXu7|<=8 zgKg<+BR@?on(NaX(&*AJqz6vCbCl@*bpVn7kkbcG9tSW6Lvf(jlHdv64G^Hj9+)p^ zKYm$Q1zt|OE;HZN=sOpu37zIMHMhU~_PrZNW!yI{<(zn{8&_@DRtc`}vwI8)IsI)! zUfN~nKH9ryFj{g+O7Y$Pb~NX2z|ScJ%3llngx0b#KlNNqa{>q$V1H6GoS5WSHwd0E zT8209c4;H;G}zxNa|z!L6~Y_K$)!X`8t`<`SNO|tP*uifnsD2Gyi8h>>zA87|D+hW zEHX}w%ejB)TI4z22fnc10QNqsM@k^9E>iii(`JZ^IVH_wR|Y^JiZdgaEtrMx$PYB+ zzTrD93r`VIMT~)$gkm=!JGC&IDBk$)Rvq7pc^>i$z=tl-V*vV|^fBD9WUh1)5mr}| z#G>e+lccdgnz$$oO*Fw-#s#HRzKUFe8@_#V9@1fehHBP?bb~ zbxm6l8%1r*P@vReoM?q2lkkbcG9tUu;Qmw0|3DRAo0tkp` zF8e^te`s2X0$xr#v?GX4zDiV`|0oYUjf;qlcVD>P&i93GdL=4Shun?f+d_gf+1#Mo z*_^BF%&H=TiByy5%E{??M!<`V+vK_oeoi6K{#sZq21~X<^{YAhDIg&Jh1#-<-du89 z8+gKYlwu$#1slSr2&B}TL`P3Q7n|yg*?z=SB7`XTvSaq0jV@(qWDXF{`ti$4Ez1Q+ zGd@)+?WAyd2G^?)PKKI&@P+*baN!~&a&FE&qKo2nDxXb_6S*{izgA4yc^y}wPL10@ zgVtaPMct}bjY$eR2j$IXmB+wM$jgF}(G_t=-crK!w9sDwK6KR|1CVpn3w4AkU$56h zLzAPCq&_I$_tebh>gS@FrnM}e`46=B%RvQbM>XtzWz*XYKj_Ouf_SW<57|Fsjy0J& zR^9jh+kl8$H}3lbPFEUqw_Fs5y7R(YSyt?aSVOy#+-7Lrxz&c^ts<17yVSy0#G; zLO_7kW~;!LnQt#Yse_l($SG3zX+*XT`OwDZzHV5R6RoRe=77uRG(k@TR}0_X5dR=@ zZ@C~9mZ2{+hr{t*F#cMYJ%E02WCB%W76}M||Jn&$ z9+iA!Qwp9iuXpjSQ?W~t(*&&+ZlRFjXns1o?-=L=gkKN8$h`3@O^Rso>qYPKV&!mj4c#Zi%e8G zIlyyT^U(D@PEv2#Ml>JsUUsLJ&0u_di?b*rQ52SFCLSw}K_G9Zt+ulmG%r8;y z%NPml918cNpfwJA(QFm9A92L%kct#8aR?H75Gr15FQ1B*kJPat6OzmP^bzm>1V|Vl?rmya*7)-La2itKgXVQ4kY)SX9`7qy~1{kB46_F5)l( z`H1g(Wdp;U5NEFQD9n4<@VUxnP(Q4SHe%Pn71r*J)w2rCe;q)~Kjiemlg9zXw^ZbM z-#Bb;RLBo2c5b~F8ST)8wzjhp0?nF#Phz|ScJ)?W*IHVxZgAY@BrSq20& zn!JDdT7|ch`ZajML|)3vxlA^~K+Vk9#s$LM75HQ==kH{%tn6^k>EsEXSL5`PpXl@x zOzJ{!f5R`aGNc(XK^>(v)`E0`)Uu%a1irA}0NVKOqAfuW@Xs~byzH~wyt}eQo`$2h z)3YHVdru5X7hNgZe$&WVJ>ucXeLl%dwxV=3^5q>igQm;2ne(?VoEzc40DQPsehk2N zna*0eB{>O0yMQ=BpF2~?_>i?+61jjn(L+Pa=;{`XBH`flz@fY+x3l&^2pv_i1t%X- zFWm9^8Y_vrZ))yu#A{#(Rug)<;yJ*vm%$k9)6Jh4@N&enqLaXUzwF6Gm_6lAeLb){ z2I;2bUlZ^&dE_V2GX-3Wenan&j%Q*xtCv7ff}bO$?vEr0wF#O0lP^|Aja0$gAifgk zT%V=QBELBIFjVu#@8?fGif{N_OS925u}qJl8uRq9zoOJl|E3&1@~;Dk{fC@Bc=9-a zOgCcHOt+vR4Qe2uG(JE)@H`an=o-A7mb@OJn!aX~)_yDTB@%v?do=2CpE z*TI)j9-UHS#pbJ}2gq=-7&(;A_%ETQn1{Ek-8rWy^Lo3*d%@2s1kPUzt8+yV=vpfX zIA8<<_{{xlantykM$5qyX3ccj7EG1*)zV?sHu)-{*Qv(VRW;RYG-!wyJyH_T=KLJ5 z?P^1Q6c)X?xV>`X5Z>3*)VtI;_~k7zil4x_KKR0Z1IWM${VXeATtVRlcV0G`VY}vT zvx?NhOhJayrg?M{t41d)lECe#43cgIHp@HLAxpc1NmvP&o&f7(ZxTm4?w=9A0DKq( zcnrXR_jn^dask8d87pwqrUP@-abN1R)$^CGRJDO zgk9uPS{gbzYZYoA9DKO z$>RWeYpi%d_CHN(H30$~^apnMi8uD!hQQ0IZgfkCqg0wu*bWquc%xkaN1Vr;IUF<7 z?XJBGtAHsa`nGlA9%oct{YTZ_(*tyg(n<-xxpy6Hcw67TI4!7mScqH{BtE8_Q*bcZ0Rvg_H8ErXzD^k$i zR>ogb9kpI#_#ivKrHfQi=WrHA$Pp!*jTK@w#=53E2VdB40QqA(R{EhV!cRV-ua4#K zoF^cXpb$Bh^QOs{S0NeR5nhb>!AA{$Fog3Gr&03KmDo>F2)&W}p&8CCyWd^QTod^V zz=y%A#{dj#QD)NM&we7y^fWkl8m3N`Uu-UEc^-C=nUdp0hqkMlpqUlF0Ez z$k5ZsSRVr@7c^K+0jk=oXmQ`bDauToca^%wtE1m-v49}?AmWYF5I?R$VdcYw+gPUo zsm@ohXCGM#vt&wj8t~h)5~*F$07`-hkW=bv_Adezi7HQ&&9yn_9r>siM1aAwTy0iIrOqOky5^oPEm$1{@N)`5@Ylj} zecMGgXq1Vww}F675Z0u@`>`{-X7Ge5&-J`fu%7416&Yu>TWXRr>Io&;E?JYTUABLZ z6)8}MOelvkp89Gk(48E!rYd||%<#5)m}<_%ob!y!)2 zAK{a%&RZ=GBb?@{CEjzY3E(0{#QQEf$iRTzM*33z7~wl60#VQ*B-2n;Yig)V#vAeT zj;LP%K8(UW24L(RmsdV*l%hV{>FgJDVY-iBeyByD91~>Wu-iE_ig6|MYC#z;!mCP* z0?${n_(jxb_!d#a$KNc_OwJZ`}#almg=eEXhrkc z8Opa_6M*|9Iq-Vjbo&e3jar_;WV#f~b?h@D?2WUVmzYHFZGa$2mxTqaB^CFJP>}ep zniVz5m=Z}ChCv*m-1eZ{t*vlZPQVE4bGyL{;`SxT<>w-8jIZQ<#TQ%Xdb(}Kep+n* z>i`n|A*Tc&&yDZQ59ux7-5zE?~6%XMZFi`dQzi%bV!VEFz^=@N)`5 z^w+|$i!6Ne)hYUJ27!Q$DhI!~H}cD5ao`D~b!Yp z$1Z4<{5|{$*YQPDdP@RnACd45rS~U{*fQ5Ao^m#Z%zU=hI`mH4;0yZ=pxSq1L||@Y8J9Jz*_d$8o_LP351XMFd{2(*l$Oy$PlC8BDI6 z9dnA%`A=>edc-bc`b>!?gh1^kKSotK67N?r4jEwwujJDulV=IqC^%OhPZ2Rw#+j%E zOJBvV8I13Ksi?9I<&$m1RfQVeaaGg_KtAU|f48+-!$-X-JaCXETf}n`_W%Dj<&boj(r*zOdf_em^m> z`({>xCQ83VjYj@T;~G1q*#1ZSJn<}2wUU{%o#=wUi6_&Z%bZo?Wb3gjZx+QT-)+qf z5d96Q48S&zAibAVD>OhBkl6sgd{Z3-Vy6TV<8c_#)!a7@_909r znD72TxxrVo2iV>wm{37^@&?Hsg8)oOYkHG08&V)B*b}$t+BawT-59rWFy@-}?HR9$ zydY*`G*pKbGLM+70tl#QBV=hJpZG#G);mf$5~9Plvs7=nur6C^FW>J52%hBw>O|k1$*o_@%Ib7iHI^s5A`y4pFmAG*i81j(e23jWL-=zlf~A6y zh>w-Z&$)jcK+-?t^ud$I0jyI$&pL-wEVX7Jlnv$5BL)I+u)w%rT+Hh z_a6(rI5vz!x&i@3I?DHp+Y-5!%$Ut$x@LUMDQ1LOyn(MU2##gaUK9>7j&ms_tq#TRAwX?4Iy5ikk0v~ zM7^(Na$hUpCi3ExUcb*4_w0Fml_&x-$B)V^bW%8M8YZG!_;3a=YU|sV9knq zUM54*H0dTl)|Er`dBD3Dw2T4>P*PAW2c7qm$D5sYOk~2%AY7Zm95MT-|}q--|c*3q3F-Q-#(;Q z1NxKop6sGfdI7rLohh}#&uX=LF2Ta?^~nI~=?lYTTr~VRYtHV%SfEsU9$Q#54|%L$ zIhzdZCb5rUDdWEmAo(A1`rygq0A{gmD>gsRhBegy0u)SNC)4o>>g+mz*HAo1%mrPF z!*M7?^=^p)euizHjl6oXy5BVAKR0-(Sz4n1g{KGNyY21Qewrd*?5+1vg2s%N9)ta% zZgAZe1?p?y=M;kCuZ8JQVWd|!0b)lvfB;0OGABE1j0F||c*69$7?C9H{059_7HE$c zCiTxZu%v3#ylJoRsvwm^pTK?NZWU0IDHDX(3Fzqjd?2^%UH+P{2< z+ohQ4zJ*?RHV%0)gPjAPoL+;bq~XIXGU-c#4q@}v$bjEHrWgq7eyjQ;+caP&Xma!F zbyRTYlrNIu3I*fbD?XkDz{i>ZJDAJceKtTA%tW(*D`9pT_gz3Tb15A|l;_(;B zI@dlkTa~U2e~1wAJxC9OLS%BoJh2_;nm$0re$B(1UbuYxWmvUK5f$$(a$f@)YU8U* z6XU-JFighhNn2|L=WVN=)$Rz)JiJuj%D}FoR`WqN;Cs#VllSR%%Twg}5OU5X#XMJ& zgxMsjRhO~j_j{X^Xwatx#mD~w@Zo;xV*pN-?xWtKr_ic7Hpb*!We9$b?ycBWr|lg2 zj$3a!Z14#cDugfyrJk#t9)V{kea<-Sh2~j^judvz8-7)FHq{0oNQGMTAeyT_GhTok zq@C=-G}$>qFmj^3OfA_H@oBxKEli8wTe6xfd?g8gTwg3!I(*k!72Z=cWxZ2Ss1qS^ z1qcE@CCf1WpbjNjqA5|9TD}fjZBiSb*PkFfs$U?vfTUTW5kwtj?{-&0w}RiFszs_KbHDelz24j}a(a{A!O;{dvD=oH${lJsQb0Rd0Nayl8|j!ll0!ON+M z2t-yuZ10!Sjp$%nAm7l0s$PEWQ^{Y=qR5bwuJ#>uZ{0Nj}*yX z(1Tb{`zNM{NO{K~Y?NgoxdAegW@(y@xeDi=*asjZ| z&7y&JOb34tU{|Y>Tq`H; z3DIIQEEe{H26@SLuC%s^&X|L;W5O>0A8rIc2B1ByleViyI@}>GqA}A>T7gIopSEGyGynVjehwC_8Y(?%zlQRCYo1i>g0T0WaogAG5?@rz+17FLTPp!U9fgqsm?J)# z-O$xT(=Ez?TI2AmK49GHx3NT_w|Y?u`gSSpF+E5Eg>OUVeRl+4h9n4+&AupoJ>?kJ zkqDW;UlFexQW@)z!bQ?B%E{L29y=409H#H(IrmZ%9r^i|Z04wPNfYBrCK(@2{R(-i8brT)MW8G!O z!rjODN3A5g$WiSJ_(FExfuB=|XMZh>odepsNX~v#;=XC07gro&U1^O-xdTra|3q9& zQ;`JbRr2u_0dAH8$jH*ahit5Ui;0lVSmtn~f3Mx>a>0j-{5&>;clzf5ue~=Lkax=udQ_SRd@NpY4&`QbvQ7`&AjGlRasT}KopX~Xy ztI?-WDU(x=+uCtS@z<0Clz~~EZ~@DmeGQ4f0DPFR@ECwV*{QHuw^TAO zlt$efW$l_mVf*Q%5V3AfJr^~jB60V&J>NhD7;X3=`PSITb79i!LXRTU6G`$T8@ zj)BfF)&;zrK2sY@P3_$a+oS(@<&j@tcRZ3*zzyvp9sr}Ol>)?OH&ERPha+H0RPi5i zzi=3o42=3{T0-+ZR5kcuA}?P?3H+Qw(EYWr9cdo!kQ%p*S}`Etq>CQM z-*A3k@iqZt__F(;8GK>C0UY0$D6YF&c!(Hwk->U=aYT`_%IXWF#zXG4Sa<=i;dWB=ir{k zn1PUf*1%4TP!|nBQQB7n=aJ{yY>M= z>XWB@kWESsr`dj5JTJcDOF=U#%DXLJhjEUN3^lVx@5Ixoa7)mdidCEk1hgeZ;vyI4 zWW_mk2V0QZL)kP!0zq!L(o>ynjf3&shcB03-!HaUz^{jR>lnG!KQy%{mNQk#k?o`B zD^_3AW7YeUOmJ(zUB)5@9sh@|cKF*osF=Ke9YFd&19yVefE>WAhZz%W> zae!nhk@MMt;N|pqE~Yeji#hvykv;C)1yFI+0Rwbe1pkw)vy*TH@CY=QPMzJBqX{?4n&)>~SPoI5FKUshQ!c^GK-< zindEl8qs*!?hZxy0`g(-h5ZJwxJx0->Kf-M{g8#d)=0z#mUaAIg?CxjQD^h462lYW zY}K|fNi`lWH=)kkPng}lCZCq(8ELaNBZG4nS3W71t+>uDXH*|m7lC6FR)le}p z3J(O;9tIKEZ6*x+ZnEr>;Q~;22_o!0_iJPNoo=?iyHzPvjJvrfh_5r$VW4|`%faw* z`&hW`*C#+;((v`DR9BY_2zmzDEO~OhpsZ-mlzk$|>w1IIZ|RXqs8K%KdyMT!JOcldHA|uD^3A^vAmbl$`rygq07iRNm-RHd!foF1z0MTBzG8qxwMQg(g>>gH`syH>9VV4F#Qd zv>3)wE~6CTA^SqSdqAG@HvNcgr)`)q_{c8J5#!E#%Z&x3p;WKj>^M2ua zbb2|4P2nOS2){^X^0h<=+`$V(Zl08dsTZH6Ld{`^@F0B$nG!Hnm#UkAF?q(eVj~|G z^iwGhlle?LK;7&&uM9Zmo;p+AUkrc{KYE4{qZrema@0moG3SiZz_%yItMQ5D&TEl$ zu?NXm*QMp{N%p0Td#)F^Op55V-e#zT-omF@t>tX49xC_$>i{zUA*T;0yZ=prxUvp*MAxs-CW{&eKq?=JjsB=Nxz` zdMkNq>0R~_RY+TQ;H@L|f{V*n0} z=|96e;=H37ZUwaYI9J2*8l~@kVJ~nPIVZTIGM3lB%dnQ?S>2;(8+N{)voP-4e0v&2 z!SCLgjVoC?9elr-h@KlDmG*XPa1FvwavCb)nl8nxc8o`y2jn6$B*5pZdLCdwON=9V z!~uxLJtsh8X?uYtFR=;N8J-aD0Ncs)5eRBSvRL~x-@}ejXgdUVH^O^^?-I_3u8N}i zev9td#|}3sWX{CYLRqoi*SJawXFjVLVyd+PU)w31bBvOO+;8?@2axp-IeqZtaR4h9 zn17waUpZupha$8a!c!${rieIJZIqT~bZ4QWa}6WqN(k3Yb}P0uvy_x zerT~bIIyNQH`o7|`Du|^G1KPeV1%2?jwA6|0|@dZ5LFmyhUlQqW0WJv_@Pv3iLCt8 z8!dPx>6{+YTgB$&NN?~bulHM8rI)Lnd$mxS8 zj|1o(WKz4%qF-uy4Ft6L9kQKF^QWJKxp3UC?ksP&Gew>qSVK4U`N~lHC8dYj1+!jP zF237$cFO+nb*K6D+9++@`b)UJ`c}azqOkO1hJ2E$aD|PJQUxgB=M>__Uke-Mm9y&- zzL~7QzYqBNz;f@!5%TrkbMOFkCj#i7I#FZGdS!N6S3dpBLS*}1rEoeXDA-mpGhXPa z24#T?t4j*xI!2H)wD`ot?RJ|(ZC74mjBHP>RdzBm_`-ez7>H<+3PUikSt(Rj_NHa( zy<$t*dx9)vToXALvYbskjN)|K%rhxi4vg`uXtOlJZ-y_89UIkHvZ^PN-WX&Z)}{Rd z@L|sEV*q+gvcm{4)hn^juG+G4VY6S&Zyvo&OZs+%!s7O}TpvRRv?4x|Pa@Ddvo0%o z$AMlQQ=*@JaW>v4&4Q-E6N~`_rDRpoYP{AijQ4Z7pYV#r{ifkG@~J&Xm5F@-?&Ryb z!q<3y64m+J&7}pgRNTlGxtF3deJ}x=^*b<(9}+Pd@4v+6W!SUgyFTJGYV}Gwvk~W} zA>3YJ6!*c;RR_-%2y@cj%y9kb)VdLr79(-Wx6t{ObU6 z{2`|go;(iV7j5Q1IaDk*7*8M|ON`ct`9?&nY6`rZYJW6nv(cWt{j|S!0b)9O?sHK6 zRzRC-go;z3hz7>V3+vXFhJ=qCRI2Cw1;6J*6|-bhjS%*GDGH6Ny9DN`zv!t!aQ?M0 zbDG=Jt&trN>iw>0jYbW@=uT|p1-{@3BT&0~Vb;l<-RJUjMK=kcvys=yq^`Y^3)*IR z1CNIC{S;28m<9(%m+r9l)*e#0Ym@QGC)f}NiRK7|+xN>fLw^sTJ**=RV8XU#vuGz>w=KfG)``l+HT#0XLmZL+O-l z?X|2sT^N<(#U#}0;(!uQA-otk#C}ilTn)6*{MX*JA?y%P9eDe zTG)n}5sCTpgbXd_``xDAdjF_g_pXfW08iNI$0q~(KR(hn=m6|!4QFH}-*UPv;?s(F zRvM3e4xv9Nt3^i5Bqe#O*EqZxR7qCw*c0&Ee2#Y6uxXBt8QAx3r;ZM2f0)46l z2~8ceyEtT@rgaJRuP>q?ld2G$irF16PF+Rb13}YG=}Mb{JPYytQ<@|dgJ_i|988=A zD${JsUOY0hCbh1nqcn{ObVn{2`|go;(hqu?amhm6rp}fiDoyf(ta8xT*H(fCVq7sJ$JQyv8LY$6Ln1 z>c>V{RO5R0!&X6C7-=j#KkRca1be&4!z)8++{icFIX#*&H<{MQiZF#^FTHsRJKx>J z{{?`&e=Up`lZ=&~d+<)=esrmoKh-yjD-3$}8F<2u%y-TVp%~0OpH7668$-qem>UX% zd|q1FUN5SxS`&W_7GV-&=k)Hv)xM2Ouix81bv8FzE1?mmanF@x%m$HwFYGsfN^9wH zSUYf&rcJNyz3K^(fFd^+WLkGd>Vvq(@mwV)fdcvET_R2bxO-gD*)14 z2Fj}dF0(nu8NUF0n2rA!fH7Qiq>0`>c#>aQZJ+|u8ANIfZOEQTJ|~sToPlVVPINOw zi0YknDyzo{u@dL_uFp{NOU#zG5NXL^rB@N)Vw0;%DX zPq`7lZQ+Nf&ogzThxkyxMxPU@fuf#|Lmk4kY8LdqfEqrZ2wd?>p9<0niCwfHlV+24 zvrvrqz}Wp4n}Bbvj_Q!4Q;eU^VOu5&+<6(VbfZ$O+l_U- zKIQy-0Fj;)4zh3fDN_U-PtfKrU`5}Gb~LhBL-zC#|3q$36zn{s&sSQY=rFTHeFID2 zqs)LGN}@JoPV80+tO)9?u*>`f;KQns#{k4niNfl3o=AS?vHL+#|4TX` zYgL}?N)f>vOA9^n&r?n7{XP62&u4sdd#jx$JMxA|_nnE?nfLCOBjFwFay(O=VCjf? zL7$1Ugc7S7`W4yN!>0V;X!I5&Drax{6mzQl+gnvvvDptGS!lGaWQhynR}mMw>HI0- zG;To9dFLy>le-x4@NxUi&->!mM&6DV&}N0^&6 zfVkPI#y84R(-xDgR=Z;SpT7|=@Q0i}c=9-aTu?BUYP2vSaq9Q$R*BMINF-d)qd9|@ zQ_pdSkJh@>(w9$4iDq{9#I~`0!wD{U&xze_MMzmXcLC-y*J3WVlf6gpRiZRf&Av78 z_5GM|#HSHJt;~{wFXDu0e=<(Iq^> ztsdR*hk;r@fyGXlF{YCX6#Awki@Hmp4tQk&6RR4IKvM(`s8foZ`t6@U5F--TQ8N2h zT9P=AGF3F8GG!rtO}Y#9w!Wuld&p`)Xdauie>9r|kMvh82);N`xK}{_ zujLw?OF+1xHLn|Wm@7R~ha(?O=r8a(8TwMhNY`!8Y+)_9{E&GKfv z2VU8$00n8k)dj2C_YkQOqkHu7UvB{g|B%xMPaX$Q9VfBU$Fg1CZwm+*efND2*PjK3 zU=%z6-x)Or5JCq@*cT#%&9iavta(axLoa5ZuX$Fxe5vPVf=du^6?LgCbz-!8XSDu7 zjD%j*z9CXgnSCGG$nmTT4g8!!2>rFNweb^sJhG#_Y69T@YVIt*qU^djJTwf0ba#t% z4kZkYbhm(XcY}0;l9EbycXuNoAfa?B291=|3v2m&leLETlYhc*?dw|S+~+=fAK-Ks z*8)M!rtzcOX}!=~#vTpz-wE%sZ-fkZf8-RI_<;5zx3#8wt8Q4mu~vb* zUGr0xJDZ}h^C3YM&*OgngTi{O$sG^((HQ81{U;#GD^Dj?{6xmS6la`{r~MhfHMMH6 zd_73@(TaQK(EX4!S6NPB3>pbVDtB%moz`M8Q4YQ7yX{X`)2>mJPb^_`{|I>3wsoI? z$Wy{#fb<)EkJZD-PodR>Izpg^IC&4Gt*EDwgSad2A}!ELjvAgH>cv*#`=w-jUJXH6 z;dj~}XZ-vcYf zUQKuhlE7EI^Hdh3S~k{;>Fr0Lu4UlTi^*ohj@XELTJy#8^{dlt&VOA%vA=M7SCjh% zB+)A@GIjZCVTAz##(aCq{r+4Nc^xTWF>*eqvD# z1vxq@C@(KuJ1f#h+|;R@8{?KejTB6An6?;&b8Q;o`j)46_<+MgfcS%hwGI-Y)NV_Q zk{8|nhNwGZu@jyS{2dBiFk-mwn^L9shami&>qAjFkd*l6>IrvW@go(~aqcM20N0BK z5KgKG{WjBH2|7dCSB8V@*PrIa_5ePxcrIzeVhylZl+SI2Uw&wEvzL+1!lQUPz&ifsMu?b_kZowVYc zy^tkwJ`z3R$Fgs~O34G3GOr|wGonwea<*eqoiC~L3Y}+XFX76gTmJbMawY!4>0M3k z7m!gZZw9@77jxtGQwbK=jR|%ebEFi?%hd(r!H92jqSnHzvq{rWjoQgWg_=Z?JQ;1#x_yms{9PsVoB|{t91IVn_sSkt zf?FRA1mxxZzG@a6D5doiIsvbb^HZhIH_0{46#IBIU<4R03(?V%TPcRl{eenUjHDx+UfLb#C z%~ZEH@Ob1?FeNVIuuGvj#3{_+>R z>!50$3<>8z(?r?^5r+2AEC?d%hsD7sO!wQhrcE`|8DSW&RIw;}Pi(~FL#o`%ke#Y} zVJlpkY&Dm5*P^qejMhBaigcCX{&XIC!hq<}1T&Z0*OAySP;fczvi(y~8?+`>MOY57 z@E1~_S=7Ao&38@-dnB@WBTg0(>!a02znnfOCQg91eey}J{n49_`T;2sKdyg$1(f;= zr*}2EUqB}oRYL2m!9d?L5Kux*$_q2Zmpy<3I!^bW$_~QIVb`rkR=Sn(g@Zhc1&FM6 z#$hKiDu@;452q=*Uvz5&x-W_lWpX4phFPVF&_a-iI=D%YSoYm0{)}Kj|8wS7pc;SkE3AgtuA1MGPI1XiSSQnn&fAj=6$A z0^T)*-X|agkm}PnmHG8sQ1@;q`s-m+7pl+CZ&E01g<5fF*6a_pzI%BrVH>4#e~R$u zvS*HzPf2n&o$V^&NFJ}HIAXj8LDs)!0U->LyT@2m{r;3)rgV6pB$yu^BBnJp=MA(|8V>G;1 z`bNJR$QV~jTXNMaW`R!iD&A{P1JLmcESiBfI0cq$sF~d+S9+dm1C~tgMPZZwx`5CA z!s%U2?icXqSijPRnV_j;P% zq*C}tz`Mrf`ve?^D;|jQpSpPInlR<6ckumeoY`rw#7|9?EHAY|#Vw?x!U~7?HByqZ zB?MlpkSEsYGFjRqr-tb=GO17ciN@_A%m*!9_GfSN#<7vGQR(wr`1{mSvp6C;so0yX z5uFQfn&1m$ccxJVQ+SP9UH4d?2_++uvC`4aW%PV~d?0~?bGrrA$*Tn@EIPalJKFtv z&?Wem?ck966QgljnT7)svz|-(E;vsy5FWm|Vt!SfdRsCPh2EkHLWBlC*o5XlukuX# zUl&mJFPz@hBotte57`| zJE!c0UpBQ>h0dfxx zCfh|%f;qMv7ZY{c5f9#rvJI{}I)pM24O$Wh^uhiUa3C-Iu>Ps>a3hAN z)BHCL{vuf*vLx}5po(@=-jvo-Jf{Z5xj>5+0-ZaiKqdtR$r*R&n+eMfVZmvw*C)Nw zWkr7kyz9umPr&%#d@1e^{MQt*EF_*sOQK>jAvdgUd3XT+Jj)+rUdVwvR~qhF^copHwV8#@a#W%v;U zo{$xNXyHYoD+5|Tul>zaKbY&d$NlTk!dvl!a76k5^{6HUClKU&?UzKsz+6dS!o*`d zmdfi=UyIZwP_9Az6m~5b3(s(f7a-_6M3OM#p*Q;6hOmZHDYW`D_uNJ+;<1NgX)WQu zE};BhIK8XM{Q?3P=^c7aJU2p-RVPY_IZks7M`w#V%Bf>Y{2k~1!Fe!oB|Xc z94s$1=R>xfWd!xu?PTme7OdnCdAVQ%=vKg$Rn=6@7J7>#yJFr)TrL*lbcws{HJaBZ zs0s^f1Xhif^~d=)1;1eF&jLO-_T^hXZVxDg`;DUJq2t+QfhEukeX#!o%#!fRH05*Z z$^iXVuIdyA-_#%;16ghgW>a{*WY{avG9y!t?E77a&9<6XPsMaYIv))y3!vxlMrZQ~ zt)a<~7XK0OZgRkV0uJq!{XQp72$!N!=t*PauHtMHDxU-$!RL?$0yQ@_+$(vD8+%~B z`szB2iMRfmvBYd5Bk+H2!~$?=L!@n}6umve9JwY(!0*|uT!GC99bwjSO?*EKu?;(t z{_S7&3WH;R?~EG+|~{@Jay>`&`egScW%7VbzVasw>`h6ej0~P*0qV@ajM_PF1_$oXHW1_7xuNn zP!JwR$!PHWtRYb)&oyL*!7~6Uy2vD-9*KSoKFD=oaS}Xi=BX-VKKTpvCky{VB@XnQ z0+b#cOgdy8u~4+6n(uZCdPsvl_H3islp4yJFdUAtWr6}6>H6^qu$%}+Nb(n7{0V?# z>~a2@L5xS!W3;Bt7nAJ*`q@X=L@a?x|et;O3;bCS%SA5MjS<) z#7PK0ZBt^C8xg~xhx6sn9_7iZ7C0$tVqBiv7ji$&JVixHlV==O5&^qG&?Z-=M^QA> ze1lKVEE}&$l-nVqaB~>>-%%y-l2Li{Xwrw(>*y^7a7YHr;V-^uETgLX2RB{m6@ zwJMpj|8)VC|HA2AP3{+P8VT^TYfgaX@;wMBctYA?mSQ?b{1rL@+ti=BEVb(IUnKLU zR{e~?rb^?@4vg`cClaNI=fjU^#!PBEEV7SI$KyhBTr?4-!SLEQS7Gt3G@LROd-d`c zIrN+YR301*pN`a!Aay;O1@U%oefoNF{tRBB2NSwr7C5>JB%Y2YV;A4HwVrK zfgY~GIreb!e_?~!(M2}8-ywjvOG1P+tqu=?Bv$vSPfG##IU z6Fiq>)GbK*Z(y}TU%8TfD-e3J6+kg+T1dgYa*Y9koY+&Xd+o@tqgtxlIdkWzd04+G zXy^%ouukzA$~~7E_?`Y~e@y$>YQwLeutLs6aPiW^KkcX~v<9Zlz?%qg0D>%!#h%t7 z)4mX2NH)o+bgEz=`jwdU>SHx>W3vZW5_WH(6Nj2WbzS_;QcNKTub893PxNY{q?_a^ zPT?Md6r$X}E}-gPIK8XM{Q|ByrxmibYV4lRfPfjFXHfQxiChh!JgsMz%52c+H>F+Mh8SlJLyncdme3^zAiCK4Qec@8f zQy7>vHm1o3X6dJ-b3&iZq+jZ>!;P~@`xA(sPvM$1hN>*8I5_RPq>X8|zrKM!*na}P z$Yq>f`z{~8p>g{du65|ozB8f<)j_$2O?8oN^1Z%t%UBZvp?oN&>6$DSJHJ^g^InE(iargeQz_tPS`plrOviOP?? zsb;qosYqm|n)NBerKGZnQy#aUfuysA`kQeJjF$*_$d)ojbl^C~ty9m8y^%7=?T-vx zC4M;aqwu2Oq)CI1F05(Hple33UMfFj)+PkMaZd42U?wbu`|Oo)4Q77FCbZ+#wf!pITqQ`+byWQ_Nc9lm`bZ6bezs$#xx8I z7TYOb5V!kFnCDp1`CFti=aFf2Q=}XgO*Kq-1Z``Rxy0#S6fY8w0f!i*^qeSuRDEP5 z%H1gyef5By8bIU0!PenQl{&0HmQz&UPDL{?D$fpD++=zOU9jF(&^5|OxF;8n5eT!~ zcCdcGLle!Fe`hqME@%K6=--cs)@XIwO$!ntQ1bzJ=m4iDpzb_CnQ z1#DyUDf^b4EuOyB>}4}4i+YtM^bE0JST2)x;gO2aCBGG z=$iVg7)sipcFAXGg1hB^1iYJ_bDx0X3aV%Oth!k6pUrR15WeJ`NPvgSi8`+64jZnX zO|C2w>{d1+-EfT1i5wC8Tp5b0nlMfbuMX(oB{ncojyBMNAZ!Y`Z1r#blmdDZ9=-i> z6@U99hJS`{zM;r2TD%xOPv8IJ5L1V~!;SZA6Uve2^T#S9AKC5_P&Px#H!V9L+d;D;`)JB`;_^5h2sMqnh7j3YormgSduPq?I4R z{CcZ2X3Cgv=F0M8!bdcbnm?-IkAQbWf$kH~Ou_~Q_p1vtE5tlGdR$IpYP-M*HpIgb zIo>u2eO&Nz$~d^pkl?Xyh4DowM7^M!`)6?~h3StR-A(3!zL-&R5X5&DzQrfpO^%-n zdmv9TK!3Qu?u6oHe8F(ZN}^`31D}r5s9c@Ml=FzAv8>RpJI%h)%-(tLfX?F#_Ia*q zT@MhXKqd49bzM!L{v-;#*Je1iK{r!yK%#8VK0;vg!rR^s)9Y|03t0=#@|Q(#knv+G zb4N;NNAE8zwLhq~16|xr|8)Vi|HA2AP3{+vM5zPc1nDa)w;BjYGGua{Pz{`XRRA5Q z*k8}n*|v=LW_eM0t(YDyJHfz!o~X%|>3b>;`1eo>SphiNMZ8if@+?-DK-I&QoR)$O zm&g@bRtbn@p7&~pmVd2lc{sc}ikj7Mv->9=hjJ_p;vM$LgDRnP@j_4RFm45|^U zw)WjVT?Rp`87%O7CJ;sLCD5pax0n2Li@N5xSxc;O(@!ap>v=g7Hd9n#T+?Ry@A^gZ zO-3XSyw|6;tT`PYHIcH~SjnIM>jLWjh10v5+%MpzgX;%5VWsdRF%U3zf){(C#>1u^ z89GiEU4f?)v=W9ThFckELYpfmL&#=GwhZ;h*22!8;VQ}_TnsAE8mR>@eCdH*Np1Mi zm~QYI`mk@1cf-Kqo)!<;g6cgu*!ICY01~9t0-G5G%mTmRA=x4IX>@@u*znK|eWFOY zA!IxbkvYk$RH8lXBafE*QV*g{wo`yhn2f|VwrEIl2>)eCuR=T7Ge9_2Nty+14dv(v zv&ycG(8C3ES#4CJ;{$wsW*)Rhg{fGO3iLg~wlR=KlIT2=nMF~;U+&t3Cuvx~;WEaw z-^In?kzefg1Jx$9PDNqZ(@qXn{SokPD%X7i#??HjBTMgvk8g-=u(9??F~L2FjY>Gu z>^WW|m|9fD9N)H;b$RdUnTWw}H@wb~FSd(Gt1XXL9v4g+!*6i*?zWwl92X%NbBY>s z0k@NTk!A#EUgw==DDCL;DYu27%OQ9pZhfnOA=>jSc((+k*zBg*1!-195l}enN&8j*f)ia z(|BxG9`CA>nE4w@zD8w(W3gx1T{|0FYf{@YzBv*2)dNo^ILD0a*0g`B*LI|zy~kc* zyXrL;c>&oAO3$OI{v+Vs^tk&3d>JC>!nXILvTkuR?+c#&yjbF;_oE4? zx1aGR1Ohl`Ctu^Zp;BvJ#U_i^uY&C!Eo!P~o00pTg7B!FR{PjF_CS!a+#(SL0C>)M z>$XqV>6&tCUwgw!mgMr9___TP+@=B!ecEo}YPXztWXH0CcwGb^5g`)|9hp{hmRE95 zFhf6pAQe7TR`9q4JL|t>V>sSruv@Q$3VAk`&SFJ7^Co%u=Z5cMJu zS&rTTj}#jz<{78BWNFz01T=hbu<=HSxMdd$xDo~gBuKK1P!LCaW3CTfFt+GHVg&7g zGKtk!HhF4ZI4W*M-fjpOww(=0H~X9szBFo;>Mb-eLqyj`Z=>H;Sn4LT*yxDwJnOow N1NoQfK42)*{{X6jIj#Tz literal 45766 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 F`7i#l`Zxdp delta 2694 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 Date: Sun, 13 Dec 2020 23:17:19 +0300 Subject: [PATCH 11/14] core: don't allow calls in verification context Follow neo-project/neo#2144. System.Contract.CallNative already has "None" flag, so tests work fine. --- pkg/core/blockchain.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 0604f1847..99cd714dd 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -1672,7 +1672,7 @@ func (bc *Blockchain) initVerificationVM(ic *interop.Context, hash util.Uint160, return ErrInvalidVerificationContract } initMD := cs.Manifest.ABI.GetMethod(manifest.MethodInit) - v.LoadScriptWithHash(cs.Script, hash, smartcontract.ReadStates|smartcontract.AllowCall) + v.LoadScriptWithHash(cs.Script, hash, smartcontract.ReadStates) v.Jump(v.Context(), md.Offset) if cs.ID <= 0 { From cb5ecaefe7c220409c93c96bee2ffc780524e0b7 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Sun, 13 Dec 2020 23:30:21 +0300 Subject: [PATCH 12/14] native: drop OnPersistEnd Now that PostPersist is being run for every native contract we can do our dirty caching tricks right there instead of OnPersistEnd. --- pkg/core/blockchain.go | 15 --------------- pkg/core/blockchain_test.go | 2 -- pkg/core/native/designate.go | 7 +------ pkg/core/native/native_neo.go | 21 ++++++++------------- pkg/core/native/notary.go | 15 +++++---------- pkg/core/native/policy.go | 19 +++++++------------ pkg/core/native_designate_test.go | 3 --- pkg/core/native_neo_test.go | 2 -- pkg/core/native_oracle_test.go | 1 - 9 files changed, 21 insertions(+), 64 deletions(-) diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 99cd714dd..443f8609c 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -724,21 +724,6 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error bc.lock.Unlock() return err } - if err := bc.contracts.Policy.OnPersistEnd(bc.dao); err != nil { - bc.lock.Unlock() - return fmt.Errorf("failed to call OnPersistEnd for Policy native contract: %w", err) - } - if bc.P2PSigExtensionsEnabled() { - err := bc.contracts.Notary.OnPersistEnd(bc.dao) - if err != nil { - bc.lock.Unlock() - return fmt.Errorf("failed to call OnPersistEnd for Notary native contract: %w", err) - } - } - if err := bc.contracts.Designate.OnPersistEnd(bc.dao); err != nil { - bc.lock.Unlock() - return err - } bc.dao.MPT.Flush() // Every persist cycle we also compact our in-memory MPT. persistedHeight := atomic.LoadUint32(&bc.persistedHeight) diff --git a/pkg/core/blockchain_test.go b/pkg/core/blockchain_test.go index ea3fbb437..d8b892ffb 100644 --- a/pkg/core/blockchain_test.go +++ b/pkg/core/blockchain_test.go @@ -540,7 +540,6 @@ func TestVerifyTx(t *testing.T) { ic.SpawnVM() ic.VM.LoadScript([]byte{byte(opcode.RET)}) require.NoError(t, bc.contracts.Designate.DesignateAsRole(ic, native.RoleOracle, oraclePubs)) - require.NoError(t, bc.contracts.Designate.OnPersistEnd(ic.DAO)) _, err = ic.DAO.Persist() require.NoError(t, err) @@ -747,7 +746,6 @@ func TestVerifyTx(t *testing.T) { ic.SpawnVM() ic.VM.LoadScript([]byte{byte(opcode.RET)}) require.NoError(t, bc.contracts.Designate.DesignateAsRole(ic, native.RoleP2PNotary, keys.PublicKeys{notary.PrivateKey().PublicKey()})) - require.NoError(t, bc.contracts.Designate.OnPersistEnd(ic.DAO)) _, err = ic.DAO.Persist() require.NoError(t, err) getNotaryAssistedTx := func(signaturesCount uint8, serviceFee int64) *transaction.Transaction { diff --git a/pkg/core/native/designate.go b/pkg/core/native/designate.go index 57c726cfa..ecfbdbd77 100644 --- a/pkg/core/native/designate.go +++ b/pkg/core/native/designate.go @@ -102,16 +102,11 @@ func (s *Designate) OnPersist(ic *interop.Context) error { // PostPersist implements Contract interface. func (s *Designate) PostPersist(ic *interop.Context) error { - return nil -} - -// OnPersistEnd updates cached values if they've been changed. -func (s *Designate) OnPersistEnd(d dao.DAO) error { if !s.rolesChanged() { return nil } - nodeKeys, height, err := s.GetDesignatedByRole(d, RoleOracle, math.MaxUint32) + nodeKeys, height, err := s.GetDesignatedByRole(ic.DAO, RoleOracle, math.MaxUint32) if err != nil { return err } diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index ed5ce11da..739264680 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -320,7 +320,14 @@ func (n *NEO) PostPersist(ic *interop.Context) error { } } - n.OnPersistEnd(ic.DAO) + if n.gasPerBlockChanged.Load().(bool) { + gr, err := n.getSortedGASRecordFromDAO(ic.DAO) + if err != nil { + panic(err) + } + n.gasPerBlock.Store(gr) + n.gasPerBlockChanged.Store(false) + } return nil } @@ -347,18 +354,6 @@ func (n *NEO) getGASPerVote(d dao.DAO, key []byte, index ...uint32) []big.Int { return reward } -// OnPersistEnd updates cached values if they've been changed. -func (n *NEO) OnPersistEnd(d dao.DAO) { - if n.gasPerBlockChanged.Load().(bool) { - gr, err := n.getSortedGASRecordFromDAO(d) - if err != nil { - panic(err) - } - n.gasPerBlock.Store(gr) - n.gasPerBlockChanged.Store(false) - } -} - func (n *NEO) increaseBalance(ic *interop.Context, h util.Uint160, si *state.StorageItem, amount *big.Int) error { acc, err := state.NEOBalanceStateFromBytes(si.Value) if err != nil { diff --git a/pkg/core/native/notary.go b/pkg/core/native/notary.go index 5ac5a393f..110355688 100644 --- a/pkg/core/native/notary.go +++ b/pkg/core/native/notary.go @@ -158,24 +158,19 @@ func (n *Notary) OnPersist(ic *interop.Context) error { return nil } -// OnPersistEnd updates cached Policy values if they've been changed -func (n *Notary) OnPersistEnd(dao dao.DAO) error { +// PostPersist implements Contract interface. +func (n *Notary) PostPersist(ic *interop.Context) error { + n.lock.Lock() + defer n.lock.Unlock() if n.isValid { return nil } - n.lock.Lock() - defer n.lock.Unlock() - n.maxNotValidBeforeDelta = getUint32WithKey(n.ContractID, dao, maxNotValidBeforeDeltaKey, defaultMaxNotValidBeforeDelta) + n.maxNotValidBeforeDelta = getUint32WithKey(n.ContractID, ic.DAO, maxNotValidBeforeDeltaKey, defaultMaxNotValidBeforeDelta) n.isValid = true return nil } -// PostPersist implements Contract interface. -func (n *Notary) PostPersist(ic *interop.Context) error { - return nil -} - // onPayment records deposited amount as belonging to "from" address with a lock // till the specified chain's height. func (n *Notary) onPayment(ic *interop.Context, args []stackitem.Item) stackitem.Item { diff --git a/pkg/core/native/policy.go b/pkg/core/native/policy.go index 33d5f14a9..192aa7728 100644 --- a/pkg/core/native/policy.go +++ b/pkg/core/native/policy.go @@ -152,25 +152,20 @@ func (p *Policy) OnPersist(ic *interop.Context) error { // PostPersist implements Contract interface. func (p *Policy) PostPersist(ic *interop.Context) error { - return nil -} - -// OnPersistEnd updates cached Policy values if they've been changed -func (p *Policy) OnPersistEnd(dao dao.DAO) error { + p.lock.Lock() + defer p.lock.Unlock() if p.isValid { return nil } - p.lock.Lock() - defer p.lock.Unlock() - p.maxTransactionsPerBlock = getUint32WithKey(p.ContractID, dao, maxTransactionsPerBlockKey, defaultMaxTransactionsPerBlock) - p.maxBlockSize = getUint32WithKey(p.ContractID, dao, maxBlockSizeKey, defaultMaxBlockSize) - p.feePerByte = getInt64WithKey(p.ContractID, dao, feePerByteKey, defaultFeePerByte) - p.maxBlockSystemFee = getInt64WithKey(p.ContractID, dao, maxBlockSystemFeeKey, defaultMaxBlockSystemFee) + p.maxTransactionsPerBlock = getUint32WithKey(p.ContractID, ic.DAO, maxTransactionsPerBlockKey, defaultMaxTransactionsPerBlock) + p.maxBlockSize = getUint32WithKey(p.ContractID, ic.DAO, maxBlockSizeKey, defaultMaxBlockSize) + p.feePerByte = getInt64WithKey(p.ContractID, ic.DAO, feePerByteKey, defaultFeePerByte) + p.maxBlockSystemFee = getInt64WithKey(p.ContractID, ic.DAO, maxBlockSystemFeeKey, defaultMaxBlockSystemFee) p.maxVerificationGas = defaultMaxVerificationGas p.blockedAccounts = make([]util.Uint160, 0) - siMap, err := dao.GetStorageItemsWithPrefix(p.ContractID, []byte{blockedAccountPrefix}) + siMap, err := ic.DAO.GetStorageItemsWithPrefix(p.ContractID, []byte{blockedAccountPrefix}) if err != nil { return fmt.Errorf("failed to get blocked accounts from storage: %w", err) } diff --git a/pkg/core/native_designate_test.go b/pkg/core/native_designate_test.go index d318c8772..b381a923f 100644 --- a/pkg/core/native_designate_test.go +++ b/pkg/core/native_designate_test.go @@ -134,7 +134,6 @@ func TestDesignate_DesignateAsRole(t *testing.T) { setSigner(tx, testchain.CommitteeScriptHash()) err = des.DesignateAsRole(ic, native.RoleOracle, keys.PublicKeys{pub}) require.NoError(t, err) - require.NoError(t, des.OnPersistEnd(ic.DAO)) pubs, index, err = des.GetDesignatedByRole(ic.DAO, native.RoleOracle, bl.Index+1) require.NoError(t, err) @@ -152,7 +151,6 @@ func TestDesignate_DesignateAsRole(t *testing.T) { pub1 := priv.PublicKey() err = des.DesignateAsRole(ic, native.RoleStateValidator, keys.PublicKeys{pub1}) require.NoError(t, err) - require.NoError(t, des.OnPersistEnd(ic.DAO)) pubs, index, err = des.GetDesignatedByRole(ic.DAO, native.RoleOracle, 255) require.NoError(t, err) @@ -172,7 +170,6 @@ func TestDesignate_DesignateAsRole(t *testing.T) { err = des.DesignateAsRole(ic, native.RoleP2PNotary, keys.PublicKeys{pub1}) require.NoError(t, err) - require.NoError(t, des.OnPersistEnd(ic.DAO)) pubs, index, err = des.GetDesignatedByRole(ic.DAO, native.RoleP2PNotary, 255) require.NoError(t, err) diff --git a/pkg/core/native_neo_test.go b/pkg/core/native_neo_test.go index 4ef8759d4..ee8be24e7 100644 --- a/pkg/core/native_neo_test.go +++ b/pkg/core/native_neo_test.go @@ -225,7 +225,6 @@ func TestNEO_SetGasPerBlock(t *testing.T) { ok, err := neo.SetGASPerBlock(ic, 10, big.NewInt(native.GASFactor*2)) require.NoError(t, err) require.True(t, ok) - neo.OnPersistEnd(ic.DAO) _, err = ic.DAO.Persist() require.NoError(t, err) @@ -242,7 +241,6 @@ func TestNEO_SetGasPerBlock(t *testing.T) { }) }) - neo.OnPersistEnd(ic.DAO) g := neo.GetGASPerBlock(ic.DAO, 9) require.EqualValues(t, 5*native.GASFactor, g.Int64()) diff --git a/pkg/core/native_oracle_test.go b/pkg/core/native_oracle_test.go index d914d579b..80555bb15 100644 --- a/pkg/core/native_oracle_test.go +++ b/pkg/core/native_oracle_test.go @@ -144,7 +144,6 @@ func TestOracle_Request(t *testing.T) { ic.VM.LoadScript([]byte{byte(opcode.RET)}) err = bc.contracts.Designate.DesignateAsRole(ic, native.RoleOracle, keys.PublicKeys{pub}) require.NoError(t, err) - require.NoError(t, bc.contracts.Designate.OnPersistEnd(ic.DAO)) tx = transaction.New(netmode.UnitTestNet, native.GetOracleResponseScript(), 0) ic.Tx = tx From 2e0fe370cfcd8c0329554ddcfb01b5cc35bd91f3 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 14 Dec 2020 13:30:55 +0300 Subject: [PATCH 13/14] nef: lower MaxScriptLength Follow neo-project/neo#2119 changes. --- pkg/smartcontract/nef/nef.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/smartcontract/nef/nef.go b/pkg/smartcontract/nef/nef.go index af2a42024..9d1088af8 100644 --- a/pkg/smartcontract/nef/nef.go +++ b/pkg/smartcontract/nef/nef.go @@ -29,7 +29,7 @@ const ( // Magic is a magic File header constant. Magic uint32 = 0x3346454E // MaxScriptLength is the maximum allowed contract script length. - MaxScriptLength = 1024 * 1024 + MaxScriptLength = 512 * 1024 // compilerFieldSize is the length of `Compiler` and `Version` File header fields in bytes. compilerFieldSize = 32 ) From 7ba1b168546172c69451ae9b982ba090a8d86c5e Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 14 Dec 2020 15:21:23 +0300 Subject: [PATCH 14/14] state: drop unused UTXO remnant --- pkg/core/state/coin.go | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 pkg/core/state/coin.go diff --git a/pkg/core/state/coin.go b/pkg/core/state/coin.go deleted file mode 100644 index 650d3f7fa..000000000 --- a/pkg/core/state/coin.go +++ /dev/null @@ -1,12 +0,0 @@ -package state - -// Coin represents the state of a coin. -type Coin uint8 - -// Viable Coin constants. -const ( - CoinConfirmed Coin = 0 - CoinSpent Coin = 1 << 1 - CoinClaimed Coin = 1 << 2 - CoinFrozen Coin = 1 << 5 -)