-
Notifications
You must be signed in to change notification settings - Fork 50
Feat/meta data native contract #3759
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package metachain | ||
|
|
||
| import ( | ||
| "github.com/nspcc-dev/neofs-node/pkg/innerring/config" | ||
| "github.com/nspcc-dev/neofs-node/pkg/innerring/internal/blockchain" | ||
| "go.uber.org/zap" | ||
| ) | ||
|
|
||
| // NewMetaChain returns side chain with redefined/custom native contracts. | ||
| // See [contracts.NewCustomNatives] for details. | ||
| func NewMetaChain(cfg *config.Consensus, wallet *config.Wallet, errChan chan<- error, log *zap.Logger) (*blockchain.Blockchain, error) { | ||
| return blockchain.New(cfg, wallet, errChan, log, NewCustomNatives) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| package metachain | ||
|
|
||
| import ( | ||
| neogoconfig "github.com/nspcc-dev/neo-go/pkg/config" | ||
| "github.com/nspcc-dev/neo-go/pkg/core/interop" | ||
| "github.com/nspcc-dev/neo-go/pkg/core/native" | ||
| "github.com/nspcc-dev/neofs-node/pkg/innerring/internal/metachain/gas" | ||
| "github.com/nspcc-dev/neofs-node/pkg/innerring/internal/metachain/meta" | ||
| ) | ||
|
|
||
| // NewCustomNatives returns custom list of native contracts for metadata | ||
| // side chain. Returned contracts: | ||
| // - Management | ||
| // - Ledger | ||
| // - NEO | ||
| // - redefined GAS (see [gas.NewGAS] for details) | ||
| // - Policy | ||
| // - Designate | ||
| // - Notary | ||
| // - new native metadata contract (see [meta.NewMetadata] for details). | ||
| func NewCustomNatives(cfg neogoconfig.ProtocolConfiguration) []interop.Contract { | ||
| mgmt := native.NewManagement() | ||
| ledger := native.NewLedger() | ||
|
|
||
| g := gas.NewGAS() | ||
| n := native.NewNEO(cfg) | ||
| p := native.NewPolicy() | ||
|
|
||
| n.GAS = g | ||
| n.Policy = p | ||
|
|
||
| mgmt.NEO = n | ||
| mgmt.Policy = p | ||
| ledger.Policy = p | ||
|
|
||
| desig := native.NewDesignate(cfg.Genesis.Roles) | ||
| desig.NEO = n | ||
|
|
||
| notary := native.NewNotary() | ||
| notary.Policy = p | ||
| notary.GAS = g | ||
| notary.NEO = n | ||
| notary.Desig = desig | ||
|
|
||
| return []interop.Contract{ | ||
| mgmt, | ||
| ledger, | ||
| n, | ||
| g, | ||
| p, | ||
| desig, | ||
| notary, | ||
| meta.NewMetadata(n), | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,142 @@ | ||
| package gas | ||
|
|
||
| import ( | ||
| "math/big" | ||
|
|
||
| "github.com/nspcc-dev/neo-go/pkg/config" | ||
| "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/native/nativeids" | ||
| "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" | ||
| "github.com/nspcc-dev/neo-go/pkg/smartcontract" | ||
| "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" | ||
| "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" | ||
| ) | ||
|
|
||
| // DefaultBalance is a balance of every account in redefined [GAS] native | ||
| // contract. | ||
| const DefaultBalance = 100 | ||
|
|
||
| var _ = (native.IGAS)(&GAS{}) | ||
|
|
||
| func (g *GAS) Metadata() *interop.ContractMD { | ||
| return &g.ContractMD | ||
| } | ||
|
|
||
| // GAS represents GAS custom native contract. It always returns [DefaultBalance] as a | ||
| // balance, has no-op `Burn`, `Mint`, `Transfer` operations. | ||
| type GAS struct { | ||
| interop.ContractMD | ||
| symbol string | ||
| decimals int64 | ||
| factor int64 | ||
| } | ||
|
|
||
| // NewGAS returns [GAS] custom native contract. | ||
| func NewGAS() *GAS { | ||
| g := &GAS{} | ||
| defer g.BuildHFSpecificMD(g.ActiveIn()) | ||
|
|
||
| g.ContractMD = *interop.NewContractMD(nativenames.Gas, nativeids.GasToken, func(m *manifest.Manifest, hf config.Hardfork) { | ||
| m.SupportedStandards = []string{manifest.NEP17StandardName} | ||
| }) | ||
| g.symbol = "GAS" | ||
| g.decimals = 8 | ||
| g.factor = native.GASFactor | ||
|
|
||
| desc := native.NewDescriptor("symbol", smartcontract.StringType) | ||
| md := native.NewMethodAndPrice(g.Symbol, 0, callflag.NoneFlag) | ||
| g.AddMethod(md, desc) | ||
|
|
||
| desc = native.NewDescriptor("decimals", smartcontract.IntegerType) | ||
| md = native.NewMethodAndPrice(g.Decimals, 0, callflag.NoneFlag) | ||
| g.AddMethod(md, desc) | ||
|
|
||
| desc = native.NewDescriptor("totalSupply", smartcontract.IntegerType) | ||
| md = native.NewMethodAndPrice(g.TotalSupply, 1<<15, callflag.ReadStates) | ||
| g.AddMethod(md, desc) | ||
|
|
||
| desc = native.NewDescriptor("balanceOf", smartcontract.IntegerType, | ||
| manifest.NewParameter("account", smartcontract.Hash160Type)) | ||
| md = native.NewMethodAndPrice(g.balanceOf, 1<<15, callflag.ReadStates) | ||
| g.AddMethod(md, desc) | ||
|
|
||
| transferParams := []manifest.Parameter{ | ||
| manifest.NewParameter("from", smartcontract.Hash160Type), | ||
| manifest.NewParameter("to", smartcontract.Hash160Type), | ||
| manifest.NewParameter("amount", smartcontract.IntegerType), | ||
| } | ||
| desc = native.NewDescriptor("transfer", smartcontract.BoolType, | ||
| append(transferParams, manifest.NewParameter("data", smartcontract.AnyType))..., | ||
| ) | ||
| md = native.NewMethodAndPrice(g.Transfer, 1<<17, callflag.States|callflag.AllowCall|callflag.AllowNotify) | ||
| md.StorageFee = 50 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove it, no storage fee. This method doesn't store anything, we don't have accounts management. |
||
| g.AddMethod(md, desc) | ||
|
|
||
| eDesc := native.NewEventDescriptor("Transfer", transferParams...) | ||
| eMD := native.NewEvent(eDesc) | ||
| g.AddEvent(eMD) | ||
|
|
||
| return g | ||
| } | ||
|
|
||
| // Initialize initializes a GAS contract. | ||
| func (g *GAS) Initialize(ic *interop.Context, hf *config.Hardfork, newMD *interop.HFSpecificContractMD) error { | ||
| return nil | ||
| } | ||
|
|
||
| // InitializeCache implements the [interop.Contract] interface. | ||
| func (g *GAS) InitializeCache(_ interop.IsHardforkEnabled, blockHeight uint32, d *dao.Simple) error { | ||
| return nil | ||
| } | ||
|
|
||
| // OnPersist implements the [interop.Contract] interface. | ||
| func (g *GAS) OnPersist(ic *interop.Context) error { | ||
| return nil | ||
| } | ||
|
|
||
| // PostPersist implements the [interop.Contract] interface. | ||
| func (g *GAS) PostPersist(ic *interop.Context) error { | ||
| return nil | ||
| } | ||
|
|
||
| // ActiveIn implements the [interop.Contract] interface. | ||
| func (g *GAS) ActiveIn() *config.Hardfork { | ||
| return nil | ||
| } | ||
|
|
||
| // BalanceOf returns native GAS token balance for the acc. | ||
| func (g *GAS) BalanceOf(d *dao.Simple, acc util.Uint160) *big.Int { | ||
| return big.NewInt(DefaultBalance * native.GASFactor) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A hot path. Is it calculated every time on a call to |
||
| } | ||
|
|
||
| func (g *GAS) Symbol(_ *interop.Context, _ []stackitem.Item) stackitem.Item { | ||
| return stackitem.NewByteArray([]byte(g.symbol)) | ||
| } | ||
|
|
||
| func (g *GAS) Decimals(_ *interop.Context, _ []stackitem.Item) stackitem.Item { | ||
| return stackitem.NewBigInteger(big.NewInt(g.decimals)) | ||
| } | ||
|
|
||
| func (g *GAS) TotalSupply(ic *interop.Context, _ []stackitem.Item) stackitem.Item { | ||
| return stackitem.NewBigInteger(big.NewInt(DefaultBalance * native.GASFactor)) | ||
| } | ||
|
|
||
| func (g *GAS) Transfer(ic *interop.Context, args []stackitem.Item) stackitem.Item { | ||
| return stackitem.NewBool(true) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @roman-khimov there's a pitfall: if we don't parse the arguments, then anyone can pass a garbage as arguments to this method. It will result in HALTed transactions with wrong parameters. Are we interested in proper parsing in meta chain?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @roman-khimov these transfers won't be tracked neither by Blockchain nor by corresponding RPC APIs. I suppose we don't need them anyway, but better ask than sorry. ACK?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It can just return false then. These transfers are meaningless, we can let them all fail. |
||
| } | ||
|
|
||
| // balanceOf is the only difference with default native GAS implementation: | ||
| // it always returns fixed number of tokens. | ||
| func (g *GAS) balanceOf(ic *interop.Context, args []stackitem.Item) stackitem.Item { | ||
| return stackitem.NewBigInteger(big.NewInt(DefaultBalance * native.GASFactor)) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Still not fixed: this method and Extremely easy to change one and forget about another. |
||
| } | ||
|
|
||
| func (g *GAS) Mint(ic *interop.Context, h util.Uint160, amount *big.Int, callOnPayment bool) { | ||
| } | ||
|
|
||
| func (g *GAS) Burn(ic *interop.Context, h util.Uint160, amount *big.Int) { | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| package gas_test | ||
|
|
||
| import ( | ||
| "math/big" | ||
| "testing" | ||
|
|
||
| "github.com/nspcc-dev/neo-go/pkg/core/native" | ||
| "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" | ||
| "github.com/nspcc-dev/neo-go/pkg/neotest" | ||
| "github.com/nspcc-dev/neo-go/pkg/neotest/chain" | ||
| "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" | ||
| "github.com/nspcc-dev/neofs-node/pkg/innerring/internal/metachain" | ||
| ) | ||
|
|
||
| func newGasClient(t *testing.T) (*neotest.ContractInvoker, *neotest.ContractInvoker) { | ||
| ch, validators, committee := chain.NewMultiWithOptions(t, &chain.Options{ | ||
| NewNatives: metachain.NewCustomNatives, | ||
| }) | ||
| e := neotest.NewExecutor(t, ch, validators, committee) | ||
|
|
||
| return e.ValidatorInvoker(e.NativeHash(t, nativenames.Gas)), e.CommitteeInvoker(e.NativeHash(t, nativenames.Gas)) | ||
| } | ||
|
|
||
| const defaultBalance = 100 | ||
|
|
||
| func TestGAS(t *testing.T) { | ||
| gasValidatorsI, gasCommitteeI := newGasClient(t) | ||
| hardcodedBalance := stackitem.NewBigInteger(big.NewInt(defaultBalance * native.GASFactor)) | ||
|
|
||
| t.Run("committee balance", func(t *testing.T) { | ||
| gasCommitteeI.Invoke(t, hardcodedBalance, "balanceOf", gasCommitteeI.Hash) | ||
| }) | ||
|
|
||
| t.Run("new account balance", func(t *testing.T) { | ||
| s := gasValidatorsI.NewAccount(t, defaultBalance*native.GASFactor+1) | ||
| gasCommitteeI.WithSigners(s).Invoke(t, hardcodedBalance, "balanceOf", s.ScriptHash()) | ||
| }) | ||
|
|
||
| t.Run("transfer does not change balance", func(t *testing.T) { | ||
| newAcc := gasValidatorsI.NewAccount(t, defaultBalance*native.GASFactor+1) | ||
| gasCommitteeI.Invoke(t, stackitem.Bool(true), "transfer", gasCommitteeI.Hash, newAcc.ScriptHash(), 1, stackitem.Null{}) | ||
| gasCommitteeI.Invoke(t, hardcodedBalance, "balanceOf", newAcc.ScriptHash()) | ||
| }) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| package meta | ||
|
|
||
| import ( | ||
| "math" | ||
| ) | ||
|
|
||
| const ( | ||
| // Metadata contract identifiers. | ||
| MetaDataContractID = math.MinInt32 | ||
| MetaDataContractName = "MetaData" | ||
| ) | ||
|
|
||
| const ( | ||
| // storage prefixes. | ||
| metaContainersPrefix = iota | ||
| containerPlacementPrefix | ||
|
|
||
| // object prefixes. | ||
| addrIndex | ||
| lockedByIndex | ||
| ) | ||
|
|
||
| const ( | ||
| // event names. | ||
| putObjectEvent = "ObjectPut" | ||
|
|
||
| // limits. | ||
| maxREPsClauses = 255 | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't need this. Return constants instead of these fields. Keep the implementation as simple as possible.
Originally,
symbol,decimalsandfactorwere a part of nep17 native token structure because this implementation is shared between Neo/Gas tokens. Now you just don't have this demand.