-
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,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/core/metachain/gas" | ||
| "github.com/nspcc-dev/neofs-node/pkg/core/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,174 @@ | ||
| package gas | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "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/interop/contract" | ||
| "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 * native.GASFactor | ||
|
|
||
| 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 | ||
| } | ||
|
|
||
| // 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 | ||
|
|
||
| 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) | ||
| 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) | ||
| } | ||
|
|
||
| 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)) | ||
| } | ||
|
|
||
| func toUint160(s stackitem.Item) util.Uint160 { | ||
| u, err := stackitem.ToUint160(s) | ||
| if err != nil { | ||
| panic(err) | ||
| } | ||
| return u | ||
| } | ||
|
|
||
| func toBigInt(s stackitem.Item) *big.Int { | ||
| bi, err := s.TryInteger() | ||
| if err != nil { | ||
| panic(err) | ||
| } | ||
| return bi | ||
| } | ||
|
|
||
| func (g *GAS) Transfer(ic *interop.Context, args []stackitem.Item) stackitem.Item { | ||
| from := toUint160(args[0]) | ||
| to := toUint160(args[1]) | ||
| amount := toBigInt(args[2]) | ||
|
|
||
| paymentArgs := []stackitem.Item{ | ||
| stackitem.NewByteArray(from.BytesBE()), | ||
| stackitem.NewBigInteger(amount), | ||
| args[3], | ||
| } | ||
| cs, err := ic.GetContract(to) | ||
| if err == nil { | ||
| err = contract.CallFromNative(ic, g.Hash, cs, manifest.MethodOnNEP17Payment, paymentArgs, false) | ||
| if err != nil { | ||
| panic(fmt.Errorf("failed to call %s: %w", manifest.MethodOnNEP17Payment, err)) | ||
| } | ||
| } | ||
|
|
||
| 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.
Member
Author
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. notary deposits work this way now |
||
| } | ||
|
|
||
| // 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(g.BalanceOf(nil, util.Uint160{})) | ||
| } | ||
|
|
||
| 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/core/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 * native.GASFactor | ||
|
|
||
| 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,28 @@ | ||
| 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. | ||
| objectPutEvent = "ObjectPut" | ||
| objectDeletedEvent = "ObjectDeleted" | ||
| objectLockedEvent = "ObjectLocked" | ||
| ) |
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.
A hot path. Is it calculated every time on a call to
balanceOf? If not, then move to a separate constant.