Skip to content

Commit 2ecbf8a

Browse files
committed
ir/internal: add possibility to deploy custom native contract
Also, implement new MetaData contract and GAS contract implementation without economic. Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
1 parent 6ac061a commit 2ecbf8a

12 files changed

Lines changed: 1425 additions & 2 deletions

File tree

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,9 @@ require (
9191
go.yaml.in/yaml/v3 v3.0.4 // indirect
9292
golang.org/x/crypto v0.48.0 // indirect
9393
golang.org/x/exp v0.0.0-20250911091902-df9299821621 // indirect
94+
golang.org/x/mod v0.32.0 // indirect
9495
golang.org/x/text v0.34.0 // indirect
96+
golang.org/x/tools v0.41.0 // indirect
9597
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
9698
gopkg.in/yaml.v3 v3.0.1 // indirect
9799
lukechampine.com/blake3 v1.2.1 // indirect

pkg/core/metachain/contracts.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package metachain
2+
3+
import (
4+
neogoconfig "github.com/nspcc-dev/neo-go/pkg/config"
5+
"github.com/nspcc-dev/neo-go/pkg/core/interop"
6+
"github.com/nspcc-dev/neo-go/pkg/core/native"
7+
"github.com/nspcc-dev/neofs-node/pkg/core/metachain/gas"
8+
"github.com/nspcc-dev/neofs-node/pkg/core/metachain/meta"
9+
)
10+
11+
// NewCustomNatives returns custom list of native contracts for metadata
12+
// side chain. Returned contracts:
13+
// - Management
14+
// - Ledger
15+
// - NEO
16+
// - redefined GAS (see [gas.NewGAS] for details)
17+
// - Policy
18+
// - Designate
19+
// - Notary
20+
// - new native metadata contract (see [meta.NewMetadata] for details).
21+
func NewCustomNatives(cfg neogoconfig.ProtocolConfiguration) []interop.Contract {
22+
mgmt := native.NewManagement()
23+
ledger := native.NewLedger()
24+
25+
g := gas.NewGAS()
26+
n := native.NewNEO(cfg)
27+
p := native.NewPolicy()
28+
29+
n.GAS = g
30+
n.Policy = p
31+
32+
mgmt.NEO = n
33+
mgmt.Policy = p
34+
ledger.Policy = p
35+
36+
desig := native.NewDesignate(cfg.Genesis.Roles)
37+
desig.NEO = n
38+
39+
notary := native.NewNotary()
40+
notary.Policy = p
41+
notary.GAS = g
42+
notary.NEO = n
43+
notary.Desig = desig
44+
45+
return []interop.Contract{
46+
mgmt,
47+
ledger,
48+
n,
49+
g,
50+
p,
51+
desig,
52+
notary,
53+
meta.NewMetadata(n),
54+
}
55+
}

pkg/core/metachain/gas/gas.go

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
package gas
2+
3+
import (
4+
"fmt"
5+
"math/big"
6+
7+
"github.com/nspcc-dev/neo-go/pkg/config"
8+
"github.com/nspcc-dev/neo-go/pkg/core/dao"
9+
"github.com/nspcc-dev/neo-go/pkg/core/interop"
10+
"github.com/nspcc-dev/neo-go/pkg/core/interop/contract"
11+
"github.com/nspcc-dev/neo-go/pkg/core/native"
12+
"github.com/nspcc-dev/neo-go/pkg/core/native/nativeids"
13+
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
14+
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
15+
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
16+
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
17+
"github.com/nspcc-dev/neo-go/pkg/util"
18+
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
19+
)
20+
21+
// DefaultBalance is a balance of every account in redefined [GAS] native
22+
// contract.
23+
const DefaultBalance = 100 * native.GASFactor
24+
25+
var _ = (native.IGAS)(&GAS{})
26+
27+
func (g *GAS) Metadata() *interop.ContractMD {
28+
return &g.ContractMD
29+
}
30+
31+
// GAS represents GAS custom native contract. It always returns [DefaultBalance] as a
32+
// balance, has no-op `Burn`, `Mint`, `Transfer` operations.
33+
type GAS struct {
34+
interop.ContractMD
35+
symbol string
36+
decimals int64
37+
}
38+
39+
// NewGAS returns [GAS] custom native contract.
40+
func NewGAS() *GAS {
41+
g := &GAS{}
42+
defer g.BuildHFSpecificMD(g.ActiveIn())
43+
44+
g.ContractMD = *interop.NewContractMD(nativenames.Gas, nativeids.GasToken, func(m *manifest.Manifest, hf config.Hardfork) {
45+
m.SupportedStandards = []string{manifest.NEP17StandardName}
46+
})
47+
g.symbol = "GAS"
48+
g.decimals = 8
49+
50+
desc := native.NewDescriptor("symbol", smartcontract.StringType)
51+
md := native.NewMethodAndPrice(g.Symbol, 0, callflag.NoneFlag)
52+
g.AddMethod(md, desc)
53+
54+
desc = native.NewDescriptor("decimals", smartcontract.IntegerType)
55+
md = native.NewMethodAndPrice(g.Decimals, 0, callflag.NoneFlag)
56+
g.AddMethod(md, desc)
57+
58+
desc = native.NewDescriptor("totalSupply", smartcontract.IntegerType)
59+
md = native.NewMethodAndPrice(g.TotalSupply, 1<<15, callflag.ReadStates)
60+
g.AddMethod(md, desc)
61+
62+
desc = native.NewDescriptor("balanceOf", smartcontract.IntegerType,
63+
manifest.NewParameter("account", smartcontract.Hash160Type))
64+
md = native.NewMethodAndPrice(g.balanceOf, 1<<15, callflag.ReadStates)
65+
g.AddMethod(md, desc)
66+
67+
transferParams := []manifest.Parameter{
68+
manifest.NewParameter("from", smartcontract.Hash160Type),
69+
manifest.NewParameter("to", smartcontract.Hash160Type),
70+
manifest.NewParameter("amount", smartcontract.IntegerType),
71+
}
72+
desc = native.NewDescriptor("transfer", smartcontract.BoolType,
73+
append(transferParams, manifest.NewParameter("data", smartcontract.AnyType))...,
74+
)
75+
md = native.NewMethodAndPrice(g.Transfer, 1<<17, callflag.States|callflag.AllowCall|callflag.AllowNotify)
76+
g.AddMethod(md, desc)
77+
78+
eDesc := native.NewEventDescriptor("Transfer", transferParams...)
79+
eMD := native.NewEvent(eDesc)
80+
g.AddEvent(eMD)
81+
82+
return g
83+
}
84+
85+
// Initialize initializes a GAS contract.
86+
func (g *GAS) Initialize(ic *interop.Context, hf *config.Hardfork, newMD *interop.HFSpecificContractMD) error {
87+
return nil
88+
}
89+
90+
// InitializeCache implements the [interop.Contract] interface.
91+
func (g *GAS) InitializeCache(_ interop.IsHardforkEnabled, blockHeight uint32, d *dao.Simple) error {
92+
return nil
93+
}
94+
95+
// OnPersist implements the [interop.Contract] interface.
96+
func (g *GAS) OnPersist(ic *interop.Context) error {
97+
return nil
98+
}
99+
100+
// PostPersist implements the [interop.Contract] interface.
101+
func (g *GAS) PostPersist(ic *interop.Context) error {
102+
return nil
103+
}
104+
105+
// ActiveIn implements the [interop.Contract] interface.
106+
func (g *GAS) ActiveIn() *config.Hardfork {
107+
return nil
108+
}
109+
110+
// BalanceOf returns native GAS token balance for the acc.
111+
func (g *GAS) BalanceOf(d *dao.Simple, acc util.Uint160) *big.Int {
112+
return big.NewInt(DefaultBalance * native.GASFactor)
113+
}
114+
115+
func (g *GAS) Symbol(_ *interop.Context, _ []stackitem.Item) stackitem.Item {
116+
return stackitem.NewByteArray([]byte(g.symbol))
117+
}
118+
119+
func (g *GAS) Decimals(_ *interop.Context, _ []stackitem.Item) stackitem.Item {
120+
return stackitem.NewBigInteger(big.NewInt(g.decimals))
121+
}
122+
123+
func (g *GAS) TotalSupply(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
124+
return stackitem.NewBigInteger(big.NewInt(DefaultBalance))
125+
}
126+
127+
func toUint160(s stackitem.Item) util.Uint160 {
128+
u, err := stackitem.ToUint160(s)
129+
if err != nil {
130+
panic(err)
131+
}
132+
return u
133+
}
134+
135+
func toBigInt(s stackitem.Item) *big.Int {
136+
bi, err := s.TryInteger()
137+
if err != nil {
138+
panic(err)
139+
}
140+
return bi
141+
}
142+
143+
func (g *GAS) Transfer(ic *interop.Context, args []stackitem.Item) stackitem.Item {
144+
from := toUint160(args[0])
145+
to := toUint160(args[1])
146+
amount := toBigInt(args[2])
147+
148+
paymentArgs := []stackitem.Item{
149+
stackitem.NewByteArray(from.BytesBE()),
150+
stackitem.NewBigInteger(amount),
151+
args[3],
152+
}
153+
cs, err := ic.GetContract(to)
154+
if err == nil {
155+
err = contract.CallFromNative(ic, g.Hash, cs, manifest.MethodOnNEP17Payment, paymentArgs, false)
156+
if err != nil {
157+
panic(fmt.Errorf("failed to call %s: %w", manifest.MethodOnNEP17Payment, err))
158+
}
159+
}
160+
161+
return stackitem.NewBool(true)
162+
}
163+
164+
// balanceOf is the only difference with default native GAS implementation:
165+
// it always returns fixed number of tokens.
166+
func (g *GAS) balanceOf(ic *interop.Context, args []stackitem.Item) stackitem.Item {
167+
return stackitem.NewBigInteger(g.BalanceOf(nil, util.Uint160{}))
168+
}
169+
170+
func (g *GAS) Mint(ic *interop.Context, h util.Uint160, amount *big.Int, callOnPayment bool) {
171+
}
172+
173+
func (g *GAS) Burn(ic *interop.Context, h util.Uint160, amount *big.Int) {
174+
}

pkg/core/metachain/gas/gas_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package gas_test
2+
3+
import (
4+
"math/big"
5+
"testing"
6+
7+
"github.com/nspcc-dev/neo-go/pkg/core/native"
8+
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
9+
"github.com/nspcc-dev/neo-go/pkg/neotest"
10+
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
11+
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
12+
"github.com/nspcc-dev/neofs-node/pkg/core/metachain"
13+
)
14+
15+
func newGasClient(t *testing.T) (*neotest.ContractInvoker, *neotest.ContractInvoker) {
16+
ch, validators, committee := chain.NewMultiWithOptions(t, &chain.Options{
17+
NewNatives: metachain.NewCustomNatives,
18+
})
19+
e := neotest.NewExecutor(t, ch, validators, committee)
20+
21+
return e.ValidatorInvoker(e.NativeHash(t, nativenames.Gas)), e.CommitteeInvoker(e.NativeHash(t, nativenames.Gas))
22+
}
23+
24+
const defaultBalance = 100
25+
26+
func TestGAS(t *testing.T) {
27+
gasValidatorsI, gasCommitteeI := newGasClient(t)
28+
hardcodedBalance := stackitem.NewBigInteger(big.NewInt(defaultBalance * native.GASFactor))
29+
30+
t.Run("committee balance", func(t *testing.T) {
31+
gasCommitteeI.Invoke(t, hardcodedBalance, "balanceOf", gasCommitteeI.Hash)
32+
})
33+
34+
t.Run("new account balance", func(t *testing.T) {
35+
s := gasValidatorsI.NewAccount(t, defaultBalance*native.GASFactor+1)
36+
gasCommitteeI.WithSigners(s).Invoke(t, hardcodedBalance, "balanceOf", s.ScriptHash())
37+
})
38+
39+
t.Run("transfer does not change balance", func(t *testing.T) {
40+
newAcc := gasValidatorsI.NewAccount(t, defaultBalance*native.GASFactor+1)
41+
gasCommitteeI.Invoke(t, stackitem.Bool(true), "transfer", gasCommitteeI.Hash, newAcc.ScriptHash(), 1, stackitem.Null{})
42+
gasCommitteeI.Invoke(t, hardcodedBalance, "balanceOf", newAcc.ScriptHash())
43+
})
44+
}

pkg/core/metachain/meta/const.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package meta
2+
3+
import (
4+
"math"
5+
)
6+
7+
const (
8+
// Metadata contract identifiers.
9+
MetaDataContractID = math.MinInt32
10+
MetaDataContractName = "MetaData"
11+
)
12+
13+
const (
14+
// storage prefixes.
15+
metaContainersPrefix = iota
16+
containerPlacementPrefix
17+
18+
// object prefixes.
19+
addrIndex
20+
lockedByIndex
21+
)
22+
23+
const (
24+
// event names.
25+
putObjectEvent = "ObjectPut"
26+
)

0 commit comments

Comments
 (0)