Skip to content

Commit bd68082

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 97f9696 commit bd68082

File tree

12 files changed

+1210
-2
lines changed

12 files changed

+1210
-2
lines changed

pkg/innerring/internal/blockchain/blockchain.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
1212
"github.com/nspcc-dev/neo-go/pkg/consensus"
1313
"github.com/nspcc-dev/neo-go/pkg/core"
14+
"github.com/nspcc-dev/neo-go/pkg/core/interop"
1415
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
1516
"github.com/nspcc-dev/neo-go/pkg/core/storage"
1617
"github.com/nspcc-dev/neo-go/pkg/core/storage/dbconfig"
@@ -48,7 +49,7 @@ type Blockchain struct {
4849
// New returns new Blockchain configured by the specified Config. New panics if
4950
// any required Config field is zero or unset. Resulting Blockchain is ready to
5051
// run. Launched Blockchain should be finally stopped.
51-
func New(cfg *config.Consensus, wallet *config.Wallet, errChan chan<- error, log *zap.Logger) (res *Blockchain, err error) {
52+
func New(cfg *config.Consensus, wallet *config.Wallet, errChan chan<- error, log *zap.Logger, customNatives ...func(cfg neogoconfig.ProtocolConfiguration) []interop.Contract) (res *Blockchain, err error) {
5253
switch {
5354
case cfg.Storage.Type == "":
5455
panic("uninitialized storage config")
@@ -259,7 +260,7 @@ func New(cfg *config.Consensus, wallet *config.Wallet, errChan chan<- error, log
259260
}
260261
}()
261262

262-
bc, err := core.NewBlockchain(bcStorage, cfgBase.Blockchain(), log)
263+
bc, err := core.NewBlockchain(bcStorage, cfgBase.Blockchain(), log, customNatives...)
263264
if err != nil {
264265
return nil, fmt.Errorf("init core blockchain component: %w", err)
265266
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package metachain
2+
3+
import (
4+
"github.com/nspcc-dev/neofs-node/pkg/innerring/config"
5+
"github.com/nspcc-dev/neofs-node/pkg/innerring/internal/blockchain"
6+
"go.uber.org/zap"
7+
)
8+
9+
// NewMetaChain returns side chain with redefined/custom native contracts.
10+
// See [contracts.NewCustomNatives] for details.
11+
func NewMetaChain(cfg *config.Consensus, wallet *config.Wallet, errChan chan<- error, log *zap.Logger) (*blockchain.Blockchain, error) {
12+
return blockchain.New(cfg, wallet, errChan, log, NewCustomNatives)
13+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package metachain
2+
3+
import (
4+
"fmt"
5+
"maps"
6+
"slices"
7+
8+
neogoconfig "github.com/nspcc-dev/neo-go/pkg/config"
9+
"github.com/nspcc-dev/neo-go/pkg/core/interop"
10+
"github.com/nspcc-dev/neo-go/pkg/core/native"
11+
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
12+
"github.com/nspcc-dev/neofs-node/pkg/innerring/internal/metachain/contracts/gas"
13+
"github.com/nspcc-dev/neofs-node/pkg/innerring/internal/metachain/contracts/meta"
14+
)
15+
16+
// NewCustomNatives returns custom list of native contracts for metadata
17+
// side chain. Returned contracts:
18+
// - NEO
19+
// - Management
20+
// - Ledger
21+
// - Policy
22+
// - Designate
23+
// - Notary
24+
// - redefined GAS (see [gas.NewGAS] for details)
25+
// - new native metadata contract (see [meta.NewMetadata] for details).
26+
func NewCustomNatives(cfg neogoconfig.ProtocolConfiguration) []interop.Contract {
27+
var (
28+
defaultContracts = native.NewDefaultContracts(cfg)
29+
newContracts = make([]interop.Contract, 0)
30+
31+
neoContract native.INEO
32+
)
33+
34+
var requiredContracts = map[string]struct{}{
35+
nativenames.Gas: {},
36+
nativenames.Neo: {},
37+
nativenames.Management: {},
38+
nativenames.Ledger: {},
39+
nativenames.Policy: {},
40+
nativenames.Designation: {},
41+
nativenames.Notary: {},
42+
}
43+
44+
for _, contract := range defaultContracts {
45+
delete(requiredContracts, contract.Metadata().Name)
46+
47+
switch contract.(type) {
48+
case *native.NEO:
49+
neoContract = contract.(native.INEO)
50+
newContracts = append(newContracts, neoContract)
51+
case *native.GAS:
52+
newContracts = append(newContracts, gas.NewGAS(int64(cfg.InitialGASSupply)))
53+
case *native.Management, *native.Ledger, *native.Policy, *native.Designate, *native.Notary:
54+
newContracts = append(newContracts, contract)
55+
case *native.Std, *native.Crypto, *native.Oracle, *native.Treasury:
56+
default:
57+
panic(fmt.Sprintf("unexpected native contract found: %T", contract))
58+
}
59+
}
60+
61+
if len(requiredContracts) != 0 {
62+
panic(fmt.Sprintf("not found required default native contracts: %v", slices.Collect(maps.Keys(requiredContracts))))
63+
}
64+
65+
return append(newContracts, meta.NewMetadata(neoContract))
66+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package gas
2+
3+
import (
4+
"errors"
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/native"
11+
"github.com/nspcc-dev/neo-go/pkg/core/native/nativeids"
12+
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
13+
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
14+
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
15+
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
16+
"github.com/nspcc-dev/neo-go/pkg/util"
17+
)
18+
19+
// DefaultBalance is a balance of every account in redefined [GAS] native
20+
// contract
21+
const DefaultBalance = 100
22+
23+
// GAS represents GAS custom native contract. It always returns [DefaultBalance] as a
24+
// balance, has no-op `Burn`, `Mint`, `Transfer` operations.
25+
type GAS struct {
26+
nep17TokenNative
27+
initialSupply int64
28+
}
29+
30+
// NewGAS returns [GAS] custom native contract.
31+
func NewGAS(init int64) *GAS {
32+
g := &GAS{
33+
initialSupply: init,
34+
}
35+
defer g.BuildHFSpecificMD(g.ActiveIn())
36+
37+
nep17 := newNEP17Native(nativenames.Gas, nativeids.GasToken, nil)
38+
nep17.symbol = "GAS"
39+
nep17.decimals = 8
40+
nep17.factor = native.GASFactor
41+
42+
g.nep17TokenNative = *nep17
43+
44+
return g
45+
}
46+
47+
// Initialize initializes a GAS contract.
48+
func (g *GAS) Initialize(ic *interop.Context, hf *config.Hardfork, newMD *interop.HFSpecificContractMD) error {
49+
if hf != g.ActiveIn() {
50+
return nil
51+
}
52+
53+
if err := g.nep17TokenNative.Initialize(ic); err != nil {
54+
return err
55+
}
56+
_, totalSupply := g.getTotalSupply(ic.DAO)
57+
if totalSupply.Sign() != 0 {
58+
return errors.New("already initialized")
59+
}
60+
h, err := getStandbyValidatorsHash(ic)
61+
if err != nil {
62+
return err
63+
}
64+
g.Mint(ic, h, big.NewInt(g.initialSupply), false)
65+
return nil
66+
}
67+
68+
// InitializeCache implements the [interop.Contract] interface.
69+
func (g *GAS) InitializeCache(_ interop.IsHardforkEnabled, blockHeight uint32, d *dao.Simple) error {
70+
return nil
71+
}
72+
73+
// OnPersist implements the [interop.Contract] interface.
74+
func (g *GAS) OnPersist(ic *interop.Context) error {
75+
return nil
76+
}
77+
78+
// PostPersist implements the [interop.Contract] interface.
79+
func (g *GAS) PostPersist(ic *interop.Context) error {
80+
return nil
81+
}
82+
83+
// ActiveIn implements the [interop.Contract] interface.
84+
func (g *GAS) ActiveIn() *config.Hardfork {
85+
return nil
86+
}
87+
88+
// BalanceOf returns native GAS token balance for the acc.
89+
func (g *GAS) BalanceOf(d *dao.Simple, acc util.Uint160) *big.Int {
90+
return g.balanceOfInternal(d, acc)
91+
}
92+
93+
func getStandbyValidatorsHash(ic *interop.Context) (util.Uint160, error) {
94+
cfg := ic.Chain.GetConfig()
95+
committee, err := keys.NewPublicKeysFromStrings(cfg.StandbyCommittee)
96+
if err != nil {
97+
return util.Uint160{}, err
98+
}
99+
s, err := smartcontract.CreateDefaultMultiSigRedeemScript(committee[:cfg.GetNumOfCNs(0)])
100+
if err != nil {
101+
return util.Uint160{}, err
102+
}
103+
return hash.Hash160(s), nil
104+
}
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/innerring/internal/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+
}

0 commit comments

Comments
 (0)