Skip to content

Commit

Permalink
Provide embedded compiled NeoFS contracts
Browse files Browse the repository at this point in the history
There is a need to embed NeoFS contracts' executables in NeoFS Inner
Ring application. To do this, `contracts` directory is created. The dir
contains compiled contracts (per-contract NEF and manifest). Create
eponymous Go package that provides embedded `fs.FS` with the contracts.
Add `Read` function which reads, decodes and validates all NeoFS
contracts from files and returns ready-to-go data for deployment.

Refs #2195.

Signed-off-by: Leonard Lyubich <leonard@morphbits.io>
  • Loading branch information
cthulhu-rider committed Jun 18, 2023
1 parent 93733ff commit 07f83f7
Show file tree
Hide file tree
Showing 23 changed files with 267 additions and 0 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ Changelog for NeoFS Node

## [Unreleased]

### Added
- Local files with compiled NeoFS contracts in `contracts` dir (#2391)

### Fixed

### Removed
Expand Down
1 change: 1 addition & 0 deletions contracts/alphabet-contract.manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"name":"NeoFS Alphabet","abi":{"methods":[{"name":"_initialize","offset":0,"parameters":[],"returntype":"Void","safe":false},{"name":"_deploy","offset":35,"parameters":[{"name":"data","type":"Any"},{"name":"isUpdate","type":"Boolean"}],"returntype":"Void","safe":false},{"name":"emit","offset":2900,"parameters":[],"returntype":"Void","safe":false},{"name":"gas","offset":2721,"parameters":[],"returntype":"Integer","safe":true},{"name":"name","offset":3531,"parameters":[],"returntype":"String","safe":true},{"name":"neo","offset":2735,"parameters":[],"returntype":"Integer","safe":true},{"name":"onNEP17Payment","offset":914,"parameters":[{"name":"from","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false},{"name":"update","offset":2589,"parameters":[{"name":"script","type":"ByteArray"},{"name":"manifest","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false},{"name":"version","offset":3547,"parameters":[],"returntype":"Integer","safe":true},{"name":"vote","offset":3359,"parameters":[{"name":"epoch","type":"Integer"},{"name":"candidates","type":"Array"}],"returntype":"Void","safe":false}],"events":[]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":["update","transfer","vote"]}],"supportedstandards":[],"trusts":[],"extra":null}
Binary file added contracts/alphabet-contract.nef
Binary file not shown.
1 change: 1 addition & 0 deletions contracts/audit-contract.manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"name":"NeoFS Audit","abi":{"methods":[{"name":"_deploy","offset":0,"parameters":[{"name":"data","type":"Any"},{"name":"isUpdate","type":"Boolean"}],"returntype":"Void","safe":false},{"name":"get","offset":892,"parameters":[{"name":"id","type":"ByteArray"}],"returntype":"ByteArray","safe":true},{"name":"list","offset":917,"parameters":[],"returntype":"Array","safe":true},{"name":"listByCID","offset":983,"parameters":[{"name":"epoch","type":"Integer"},{"name":"cid","type":"ByteArray"}],"returntype":"Array","safe":true},{"name":"listByEpoch","offset":951,"parameters":[{"name":"epoch","type":"Integer"}],"returntype":"Array","safe":true},{"name":"listByNode","offset":1026,"parameters":[{"name":"epoch","type":"Integer"},{"name":"cid","type":"ByteArray"},{"name":"key","type":"PublicKey"}],"returntype":"Array","safe":true},{"name":"put","offset":745,"parameters":[{"name":"rawAuditResult","type":"ByteArray"}],"returntype":"Void","safe":false},{"name":"update","offset":616,"parameters":[{"name":"script","type":"ByteArray"},{"name":"manifest","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false},{"name":"version","offset":1104,"parameters":[],"returntype":"Integer","safe":true}],"events":[]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":["update"]}],"supportedstandards":[],"trusts":[],"extra":null}
Binary file added contracts/audit-contract.nef
Binary file not shown.
1 change: 1 addition & 0 deletions contracts/balance-contract.manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"name":"NeoFS Balance","abi":{"methods":[{"name":"_initialize","offset":0,"parameters":[],"returntype":"Void","safe":false},{"name":"_deploy","offset":93,"parameters":[{"name":"data","type":"Any"},{"name":"isUpdate","type":"Boolean"}],"returntype":"Void","safe":false},{"name":"balanceOf","offset":1104,"parameters":[{"name":"account","type":"Hash160"}],"returntype":"Integer","safe":true},{"name":"burn","offset":1656,"parameters":[{"name":"from","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"txDetails","type":"ByteArray"}],"returntype":"Void","safe":false},{"name":"decimals","offset":1082,"parameters":[],"returntype":"Integer","safe":true},{"name":"lock","offset":1250,"parameters":[{"name":"txDetails","type":"ByteArray"},{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"until","type":"Integer"}],"returntype":"Void","safe":false},{"name":"mint","offset":1516,"parameters":[{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"txDetails","type":"ByteArray"}],"returntype":"Void","safe":false},{"name":"newEpoch","offset":1387,"parameters":[{"name":"epochNum","type":"Integer"}],"returntype":"Void","safe":false},{"name":"symbol","offset":1078,"parameters":[],"returntype":"String","safe":true},{"name":"totalSupply","offset":1086,"parameters":[],"returntype":"Integer","safe":true},{"name":"transfer","offset":1123,"parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"data","type":"Any"}],"returntype":"Boolean","safe":false},{"name":"transferX","offset":1147,"parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"details","type":"ByteArray"}],"returntype":"Void","safe":false},{"name":"update","offset":947,"parameters":[{"name":"script","type":"ByteArray"},{"name":"manifest","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false},{"name":"version","offset":1826,"parameters":[],"returntype":"Integer","safe":true}],"events":[{"name":"Lock","parameters":[{"name":"txID","type":"ByteArray"},{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"until","type":"Integer"}]},{"name":"Transfer","parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"}]},{"name":"TransferX","parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"details","type":"ByteArray"}]},{"name":"Mint","parameters":[{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"}]},{"name":"Burn","parameters":[{"name":"from","type":"Hash160"},{"name":"amount","type":"Integer"}]}]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":["update"]}],"supportedstandards":["NEP-17"],"trusts":[],"extra":null}
Binary file added contracts/balance-contract.nef
Binary file not shown.
1 change: 1 addition & 0 deletions contracts/container-contract.manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"name":"NeoFS Container","abi":{"methods":[{"name":"_initialize","offset":0,"parameters":[],"returntype":"Void","safe":false},{"name":"_deploy","offset":83,"parameters":[{"name":"data","type":"Any"},{"name":"isUpdate","type":"Boolean"}],"returntype":"Void","safe":false},{"name":"containersOf","offset":3109,"parameters":[{"name":"owner","type":"ByteArray"}],"returntype":"InteropInterface","safe":true},{"name":"count","offset":3064,"parameters":[],"returntype":"Integer","safe":true},{"name":"delete","offset":2655,"parameters":[{"name":"containerID","type":"ByteArray"},{"name":"signature","type":"Signature"},{"name":"token","type":"ByteArray"}],"returntype":"Void","safe":false},{"name":"eACL","offset":3418,"parameters":[{"name":"containerID","type":"ByteArray"}],"returntype":"Array","safe":true},{"name":"get","offset":2951,"parameters":[{"name":"containerID","type":"ByteArray"}],"returntype":"Array","safe":true},{"name":"getContainerSize","offset":3678,"parameters":[{"name":"id","type":"ByteArray"}],"returntype":"Array","safe":true},{"name":"iterateAllContainerSizes","offset":4051,"parameters":[{"name":"epoch","type":"Integer"}],"returntype":"InteropInterface","safe":true},{"name":"iterateContainerSizes","offset":3953,"parameters":[{"name":"epoch","type":"Integer"},{"name":"cid","type":"Hash256"}],"returntype":"InteropInterface","safe":true},{"name":"list","offset":3163,"parameters":[{"name":"owner","type":"ByteArray"}],"returntype":"Array","safe":true},{"name":"listContainerSizes","offset":3792,"parameters":[{"name":"epoch","type":"Integer"}],"returntype":"Array","safe":true},{"name":"newEpoch","offset":4103,"parameters":[{"name":"epochNum","type":"Integer"}],"returntype":"Void","safe":false},{"name":"onNEP11Payment","offset":1137,"parameters":[{"name":"a","type":"Hash160"},{"name":"b","type":"Integer"},{"name":"c","type":"ByteArray"},{"name":"d","type":"Any"}],"returntype":"Void","safe":false},{"name":"owner","offset":3013,"parameters":[{"name":"containerID","type":"ByteArray"}],"returntype":"ByteArray","safe":true},{"name":"put","offset":1571,"parameters":[{"name":"container","type":"ByteArray"},{"name":"signature","type":"Signature"},{"name":"publicKey","type":"PublicKey"},{"name":"token","type":"ByteArray"}],"returntype":"Void","safe":false},{"name":"putContainerSize","offset":3476,"parameters":[{"name":"epoch","type":"Integer"},{"name":"cid","type":"ByteArray"},{"name":"usedSize","type":"Integer"},{"name":"pubKey","type":"PublicKey"}],"returntype":"Void","safe":false},{"name":"putNamed","offset":1587,"parameters":[{"name":"container","type":"ByteArray"},{"name":"signature","type":"Signature"},{"name":"publicKey","type":"PublicKey"},{"name":"token","type":"ByteArray"},{"name":"name","type":"String"},{"name":"zone","type":"String"}],"returntype":"Void","safe":false},{"name":"setEACL","offset":3259,"parameters":[{"name":"eACL","type":"ByteArray"},{"name":"signature","type":"Signature"},{"name":"publicKey","type":"PublicKey"},{"name":"token","type":"ByteArray"}],"returntype":"Void","safe":false},{"name":"startContainerEstimation","offset":4133,"parameters":[{"name":"epoch","type":"Integer"}],"returntype":"Void","safe":false},{"name":"stopContainerEstimation","offset":4214,"parameters":[{"name":"epoch","type":"Integer"}],"returntype":"Void","safe":false},{"name":"update","offset":1438,"parameters":[{"name":"script","type":"ByteArray"},{"name":"manifest","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false},{"name":"version","offset":4294,"parameters":[],"returntype":"Integer","safe":true}],"events":[{"name":"containerPut","parameters":[{"name":"container","type":"ByteArray"},{"name":"signature","type":"Signature"},{"name":"publicKey","type":"PublicKey"},{"name":"token","type":"ByteArray"}]},{"name":"PutSuccess","parameters":[{"name":"containerID","type":"Hash256"},{"name":"publicKey","type":"PublicKey"}]},{"name":"containerDelete","parameters":[{"name":"containerID","type":"ByteArray"},{"name":"signature","type":"Signature"},{"name":"token","type":"ByteArray"}]},{"name":"DeleteSuccess","parameters":[{"name":"containerID","type":"ByteArray"}]},{"name":"setEACL","parameters":[{"name":"eACL","type":"ByteArray"},{"name":"signature","type":"Signature"},{"name":"publicKey","type":"PublicKey"},{"name":"token","type":"ByteArray"}]},{"name":"SetEACLSuccess","parameters":[{"name":"containerID","type":"ByteArray"},{"name":"publicKey","type":"PublicKey"}]},{"name":"StartEstimation","parameters":[{"name":"epoch","type":"Integer"}]},{"name":"StopEstimation","parameters":[{"name":"epoch","type":"Integer"}]}]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":["update","addKey","transferX","register","addRecord","deleteRecords"]}],"supportedstandards":[],"trusts":[],"extra":null}
Binary file added contracts/container-contract.nef
Binary file not shown.
129 changes: 129 additions & 0 deletions contracts/contracts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
Package contracts embeds compiled NeoFS contracts and provides access to them.
Contracts' source code is located in https://github.com/nspcc-dev/neofs-contract.
*/
package contracts

import (
"embed"
"encoding/json"
"errors"
"fmt"
"io/fs"

"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
)

// Contract represents compiled NeoFS contract.
type Contract struct {
NEF nef.File
Manifest manifest.Manifest
}

// Contracts groups all compiled NeoFS contracts.
type Contracts struct {
Alphabet Contract
Audit Contract
Balance Contract
Container Contract
NeoFSID Contract
Netmap Contract
NNS Contract
Proxy Contract
Reputation Contract
Subnet Contract
}

//go:embed *-contract.nef *-contract.manifest.json
var _fs embed.FS

// Read reads compiled NeoFS contracts stored in the package directory into
// Contracts instance and returns it.
//
// File schema:
// - compiled executables (NEF) are named by pattern '*-contract.nef'
// - manifests are named by pattern '*-contract.manifest.json'
//
// where asterisk stands for contract names:
// - alphabet (NeoFS Alphabet)
// - audit (NeoFS Audit)
// - balance (NeoFS Balance)
// - container (NeoFS Container)
// - neofsid (NeoFS ID)
// - netmap (NeoFS Netmap)
// - nns (NameService)
// - proxy (NeoFS Notary Proxy)
// - reputation (NeoFS Reputation)
// - subnet (NeoFS Subnet)
//
// Read fails if any of the NeoFS contract files is missing, invalid or has
// different name in the manifest.
func Read() (Contracts, error) {
return read(_fs)
}

var (
errInvalidNEF = errors.New("invalid NEF")
errInvalidManifest = errors.New("invalid manifest")
errInvalidName = errors.New("invalid name")
)

// read same as Read by allows to override source fs.FS.
func read(_fs fs.FS) (res Contracts, err error) {
for filePrefix, item := range map[string]struct {
cPtr *Contract
name string
}{
"alphabet": {&res.Alphabet, "NeoFS Alphabet"},
"audit": {&res.Audit, "NeoFS Audit"},
"balance": {&res.Balance, "NeoFS Balance"},
"container": {&res.Container, "NeoFS Container"},
"neofsid": {&res.NeoFSID, "NeoFS ID"},
"netmap": {&res.Netmap, "NeoFS Netmap"},
"nns": {&res.NNS, "NameService"},
"proxy": {&res.Proxy, "NeoFS Notary Proxy"},
"reputation": {&res.Reputation, "NeoFS Reputation"},
"subnet": {&res.Subnet, "NeoFS Subnet"},
} {
err = readContractFromFiles(item.cPtr, _fs, filePrefix)
if err != nil {
return res, fmt.Errorf("read contract from '%s*' files: %w", filePrefix, err)
}

if item.cPtr.Manifest.Name != item.name {
return res, fmt.Errorf("%w: expected '%s', got '%s'", errInvalidName, item.name, item.cPtr.Manifest.Name)
}
}

return res, nil
}

func readContractFromFiles(c *Contract, _fs fs.FS, filePrefix string) error {
fNEF, err := _fs.Open(filePrefix + "-contract.nef")
if err != nil {
return fmt.Errorf("open file containing contract NEF: %w", err)
}
defer fNEF.Close()

fManifest, err := _fs.Open(filePrefix + "-contract.manifest.json")
if err != nil {
return fmt.Errorf("open file containing contract NEF: %w", err)
}
defer fManifest.Close()

bReader := io.NewBinReaderFromIO(fNEF)
c.NEF.DecodeBinary(bReader)
if bReader.Err != nil {
return fmt.Errorf("%w: %v", errInvalidNEF, bReader.Err)
}

err = json.NewDecoder(fManifest).Decode(&c.Manifest)
if err != nil {
return fmt.Errorf("%w: %v", errInvalidManifest, err)
}

return nil
}
125 changes: 125 additions & 0 deletions contracts/contracts_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package contracts

import (
"crypto/rand"
"encoding/json"
"io/fs"
"testing"
"testing/fstest"

"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
"github.com/stretchr/testify/require"
)

func TestGetDir(t *testing.T) {
cs, err := Read()
require.NoError(t, err)

for _, item := range []struct {
c Contract
name string
}{
{cs.Alphabet, "NeoFS Alphabet"},
{cs.Audit, "NeoFS Audit"},
{cs.Balance, "NeoFS Balance"},
{cs.Container, "NeoFS Container"},
{cs.NeoFSID, "NeoFS ID"},
{cs.Netmap, "NeoFS Netmap"},
{cs.NNS, "NameService"},
{cs.Proxy, "NeoFS Notary Proxy"},
{cs.Reputation, "NeoFS Reputation"},
{cs.Subnet, "NeoFS Subnet"},
} {
require.Equal(t, item.name, item.c.Manifest.Name)
}
}

func TestGet(t *testing.T) {
_fs := fstest.MapFS{}

cs := []struct {
filePrefix string
name string
}{
{"alphabet", "NeoFS Alphabet"},
{"audit", "NeoFS Audit"},
{"balance", "NeoFS Balance"},
{"container", "NeoFS Container"},
{"neofsid", "NeoFS ID"},
{"netmap", "NeoFS Netmap"},
{"nns", "NameService"},
{"proxy", "NeoFS Notary Proxy"},
{"reputation", "NeoFS Reputation"},
{"subnet", "NeoFS Subnet"},
}

nefFileWithPrefix := func(prefix string) string { return prefix + "-contract.nef" }
manifestFileWithPrefix := func(prefix string) string { return prefix + "-contract.manifest.json" }

for _, c := range cs {
script := make([]byte, 32)
rand.Read(script)

_nef, err := nef.NewFile(script)
require.NoError(t, err)

bNEF, err := _nef.Bytes()
require.NoError(t, err)

jManifest, err := json.Marshal(manifest.NewManifest(c.name))
require.NoError(t, err)

_fs[nefFileWithPrefix(c.filePrefix)] = &fstest.MapFile{Data: bNEF}
_fs[manifestFileWithPrefix(c.filePrefix)] = &fstest.MapFile{Data: jManifest}
}

_, err := read(_fs)
require.NoError(t, err)

for _, c := range cs {
filename := nefFileWithPrefix(c.filePrefix)
fCp := _fs[filename]

delete(_fs, filename)

_, err = read(_fs)
require.ErrorIs(t, err, fs.ErrNotExist)

_fs[filename] = &fstest.MapFile{Data: []byte("invalid NEF")}

_, err = read(_fs)
require.ErrorIs(t, err, errInvalidNEF)

_fs[filename] = fCp

_, err = read(_fs)
require.NoError(t, err)

filename = manifestFileWithPrefix(c.filePrefix)
fCp = _fs[filename]

delete(_fs, filename)

_, err = read(_fs)
require.ErrorIs(t, err, fs.ErrNotExist)

_fs[filename] = &fstest.MapFile{Data: []byte("invalid manifest")}

_, err = read(_fs)
require.ErrorIs(t, err, errInvalidManifest)

jWrongNameManifest, err := json.Marshal(manifest.NewManifest(c.name + "1"))
require.NoError(t, err)

_fs[filename] = &fstest.MapFile{Data: jWrongNameManifest}

_, err = read(_fs)
require.ErrorIs(t, err, errInvalidName)

_fs[filename] = fCp

_, err = read(_fs)
require.NoError(t, err)
}
}
1 change: 1 addition & 0 deletions contracts/neofsid-contract.manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"name":"NeoFS ID","abi":{"methods":[{"name":"_initialize","offset":0,"parameters":[],"returntype":"Void","safe":false},{"name":"_deploy","offset":35,"parameters":[{"name":"data","type":"Any"},{"name":"isUpdate","type":"Boolean"}],"returntype":"Void","safe":false},{"name":"addKey","offset":1030,"parameters":[{"name":"owner","type":"ByteArray"},{"name":"keys","type":"Array"}],"returntype":"Void","safe":false},{"name":"key","offset":1442,"parameters":[{"name":"owner","type":"ByteArray"}],"returntype":"Array","safe":true},{"name":"removeKey","offset":1235,"parameters":[{"name":"owner","type":"ByteArray"},{"name":"keys","type":"Array"}],"returntype":"Void","safe":false},{"name":"update","offset":899,"parameters":[{"name":"script","type":"ByteArray"},{"name":"manifest","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false},{"name":"version","offset":1513,"parameters":[],"returntype":"Integer","safe":true}],"events":[]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":["update"]}],"supportedstandards":[],"trusts":[],"extra":null}
Binary file added contracts/neofsid-contract.nef
Binary file not shown.
Loading

0 comments on commit 07f83f7

Please sign in to comment.