Compare commits

..

13 commits

Author SHA1 Message Date
c6a7820363 container: Add DeletionInfo() method
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2023-08-10 17:13:07 +00:00
2b1fa53b67 tests: Move owner ID calculation to a separate function
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2023-08-10 17:13:07 +00:00
6e8ae742ae *: Use multiline lists in yaml config
It is quite hard to navigate in a single-line list of length 10.
Viewing diffs is also more pleasant when a single line has added.
Also, sort everything by alphabet.

Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2023-08-10 17:13:07 +00:00
077823ee84 go.mod: Update neo-go
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2023-08-09 10:09:18 +03:00
36f3d39c40 [#32] Add forgejo workflows
Signed-off-by: Alex Vanin <a.vanin@yadro.com>
2023-06-27 16:47:46 +03:00
bcf22c50a1 [#10] Use UpdateWithData call wrapper in Update methods
Signed-off-by: Anton Nikiforov <an.nikiforov@yadro.com>
2023-06-19 11:17:51 +03:00
2da0ff5750 [#27] Add public key as argument for container_contract.Delete() method
Signed-off-by: Anton Nikiforov <an.nikiforov@yadro.com>
2023-06-01 14:08:21 +03:00
b89c3fd584 [#23] Remove unused notifications from container contract
Signed-off-by: Anton Nikiforov <an.nikiforov@yadro.com>
2023-05-18 08:25:32 +00:00
6a19989aa6 [#1] tests: Update domain email after rebranding
Signed-off-by: Alex Vanin <a.vanin@yadro.com>
2023-05-17 10:52:10 +03:00
f786040322 [#1] container: Update domain email after rebranding
Signed-off-by: Alex Vanin <a.vanin@yadro.com>
2023-05-17 10:52:09 +03:00
a8029c71cd [#21] Document contract storage schemes
Signed-off-by: Airat Arifullin a.arifullin@yadro.com
2023-05-12 14:51:21 +03:00
8537293e11 [#20] subnet: Drop contract
Signed-off-by: Dmitrii Stepanov <d.stepanov@yadro.com>
2023-04-17 14:29:30 +03:00
Evgenii Stratonikov
e19fe15ed8 [#19] common: Update current version
Signed-off-by: Evgenii Stratonikov <evgeniy@morphbits.ru>
2023-04-13 11:24:52 +03:00
45 changed files with 1104 additions and 1163 deletions

View file

@ -0,0 +1,20 @@
on: [pull_request]
jobs:
dco:
name: DCO
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v3
with:
go-version: '1.20'
- name: Run commit format checker
uses: https://git.alexvan.in/alexvanin/dco-go@v1
with:
from: e19fe15e

View file

@ -0,0 +1,20 @@
on: [pull_request]
jobs:
tests:
name: Tests
runs-on: ubuntu-latest
strategy:
matrix:
go_versions: [ '1.19', '1.20' ]
fail-fast: false
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: '${{ matrix.go_versions }}'
- name: Run tests
run: make test

View file

@ -1,21 +0,0 @@
name: DCO check
on:
pull_request:
branches:
- master
jobs:
commits_check_job:
runs-on: ubuntu-latest
name: Commits Check
steps:
- name: Get PR Commits
id: 'get-pr-commits'
uses: tim-actions/get-pr-commits@master
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: DCO Check
uses: tim-actions/dco@master
with:
commits: ${{ steps.get-pr-commits.outputs.commits }}

View file

@ -1,20 +0,0 @@
name: Go
on:
pull_request:
branches: [ master ]
jobs:
tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.17
- name: Test
run: go test -v ./...

View file

@ -6,6 +6,8 @@ Changelog for FrostFS Contract
### Added ### Added
### Changed ### Changed
### Removed ### Removed
- `subnet` contract (#20)
### Updated ### Updated
### Fixed ### Fixed
### Updating from v0.17.0 ### Updating from v0.17.0

View file

@ -22,7 +22,7 @@ all: sidechain mainnet
sidechain: alphabet morph nns sidechain: alphabet morph nns
alphabet_sc = alphabet alphabet_sc = alphabet
morph_sc = audit balance container frostfsid netmap proxy reputation subnet morph_sc = audit balance container frostfsid netmap proxy reputation
mainnet_sc = frostfs processing mainnet_sc = frostfs processing
nns_sc = nns nns_sc = nns

View file

@ -29,7 +29,6 @@ Sidechain contracts:
- nns - nns
- proxy - proxy
- reputation - reputation
- subnet
# Getting started # Getting started
@ -55,7 +54,6 @@ $ make all
/home/user/go/bin/cli contract compile -i netmap -c netmap/config.yml -m netmap/config.json -o netmap/netmap_contract.nef /home/user/go/bin/cli contract compile -i netmap -c netmap/config.yml -m netmap/config.json -o netmap/netmap_contract.nef
/home/user/go/bin/cli contract compile -i proxy -c proxy/config.yml -m proxy/config.json -o proxy/proxy_contract.nef /home/user/go/bin/cli contract compile -i proxy -c proxy/config.yml -m proxy/config.json -o proxy/proxy_contract.nef
/home/user/go/bin/cli contract compile -i reputation -c reputation/config.yml -m reputation/config.json -o reputation/reputation_contract.nef /home/user/go/bin/cli contract compile -i reputation -c reputation/config.yml -m reputation/config.json -o reputation/reputation_contract.nef
/home/user/go/bin/cli contract compile -i subnet -c subnet/config.yml -m subnet/config.json -o subnet/subnet_contract.nef
/home/user/go/bin/cli contract compile -i nns -c nns/config.yml -m nns/config.json -o nns/nns_contract.nef /home/user/go/bin/cli contract compile -i nns -c nns/config.yml -m nns/config.json -o nns/nns_contract.nef
/home/user/go/bin/cli contract compile -i frostfs -c frostfs/config.yml -m frostfs/config.json -o frostfs/frostfs_contract.nef /home/user/go/bin/cli contract compile -i frostfs -c frostfs/config.yml -m frostfs/config.json -o frostfs/frostfs_contract.nef
/home/user/go/bin/cli contract compile -i processing -c processing/config.yml -m processing/config.json -o processing/processing_contract.nef /home/user/go/bin/cli contract compile -i processing -c processing/config.yml -m processing/config.json -o processing/processing_contract.nef

View file

@ -1 +1 @@
v0.17.0 v0.18.0

View file

@ -72,8 +72,7 @@ func Update(script []byte, manifest []byte, data interface{}) {
panic("only committee can update contract") panic("only committee can update contract")
} }
contract.Call(interop.Hash160(management.Hash), "update", management.UpdateWithData(script, manifest, common.AppendVersion(data))
contract.All, script, manifest, common.AppendVersion(data))
runtime.Log("alphabet contract updated") runtime.Log("alphabet contract updated")
} }

View file

@ -17,5 +17,16 @@ for each alphabet contract.
# Contract notifications # Contract notifications
Alphabet contract does not produce notifications to process. Alphabet contract does not produce notifications to process.
# Contract storage scheme
| Key | Value | Description |
|--------------------|------------|-------------------------------------------------|
| `netmapScriptHash` | Hash160 | netmap contract hash |
| `proxyScriptHash` | Hash160 | proxy contract hash |
| `name` | string | assigned glagolitic letter |
| `index` | int | the index of deployed alphabet contract |
| `threshold` | int | the total number of deployed alphabet contracts |
*/ */
package alphabet package alphabet

View file

@ -3,7 +3,6 @@ package audit
import ( import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/common" "git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"github.com/nspcc-dev/neo-go/pkg/interop" "github.com/nspcc-dev/neo-go/pkg/interop"
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
"github.com/nspcc-dev/neo-go/pkg/interop/iterator" "github.com/nspcc-dev/neo-go/pkg/interop/iterator"
"github.com/nspcc-dev/neo-go/pkg/interop/native/crypto" "github.com/nspcc-dev/neo-go/pkg/interop/native/crypto"
"github.com/nspcc-dev/neo-go/pkg/interop/native/management" "github.com/nspcc-dev/neo-go/pkg/interop/native/management"
@ -75,8 +74,7 @@ func Update(script []byte, manifest []byte, data interface{}) {
panic("only committee can update contract") panic("only committee can update contract")
} }
contract.Call(interop.Hash160(management.Hash), "update", management.UpdateWithData(script, manifest, common.AppendVersion(data))
contract.All, script, manifest, common.AppendVersion(data))
runtime.Log("audit contract updated") runtime.Log("audit contract updated")
} }

View file

@ -18,5 +18,13 @@ they make a list and get these AuditResultStructures from the audit contract.
# Contract notifications # Contract notifications
Audit contract does not produce notifications to process. Audit contract does not produce notifications to process.
# Contract storage scheme
| Key | Value | Description |
|--------------------|------------|-----------------------------------------------------------|
| `netmapScriptHash` | Hash160 | netmap contract hash |
| auditID | ByteArray | serialized DataAuditResult structure |
*/ */
package audit package audit

View file

@ -3,7 +3,6 @@ package balance
import ( import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/common" "git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"github.com/nspcc-dev/neo-go/pkg/interop" "github.com/nspcc-dev/neo-go/pkg/interop"
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
"github.com/nspcc-dev/neo-go/pkg/interop/iterator" "github.com/nspcc-dev/neo-go/pkg/interop/iterator"
"github.com/nspcc-dev/neo-go/pkg/interop/native/management" "github.com/nspcc-dev/neo-go/pkg/interop/native/management"
"github.com/nspcc-dev/neo-go/pkg/interop/native/std" "github.com/nspcc-dev/neo-go/pkg/interop/native/std"
@ -93,8 +92,7 @@ func Update(script []byte, manifest []byte, data interface{}) {
panic("only committee can update contract") panic("only committee can update contract")
} }
contract.Call(interop.Hash160(management.Hash), "update", management.UpdateWithData(script, manifest, common.AppendVersion(data))
contract.All, script, manifest, common.AppendVersion(data))
runtime.Log("balance contract updated") runtime.Log("balance contract updated")
} }

View file

@ -1,6 +1,11 @@
name: "Balance" name: "Balance"
supportedstandards: ["NEP-17"] supportedstandards: ["NEP-17"]
safemethods: ["balanceOf", "decimals", "symbol", "totalSupply", "version"] safemethods:
- "balanceOf"
- "decimals"
- "symbol"
- "totalSupply"
- "version"
permissions: permissions:
- methods: ["update"] - methods: ["update"]
events: events:

View file

@ -74,5 +74,14 @@ when FrostFS contract has transferred GAS assets back to the user.
type: Hash160 type: Hash160
- name: amount - name: amount
type: Integer type: Integer
# Contract storage scheme
| Key | Value | Description |
|-----------------------|------------|----------------------------------|
| `netmapScriptHash` | Hash160 | netmap contract hash |
| `containerScriptHash` | Hash160 | container contract hash |
| circulationKey | int | the token circulation key value |
*/ */
package balance package balance

View file

@ -4,7 +4,7 @@ import "github.com/nspcc-dev/neo-go/pkg/interop/native/std"
const ( const (
major = 0 major = 0
minor = 17 minor = 18
patch = 0 patch = 0
// Versions from which an update should be performed. // Versions from which an update should be performed.

View file

@ -1,47 +1,36 @@
name: "Container" name: "Container"
safemethods: ["count", "containersOf", "get", "owner", "list", "eACL", "getContainerSize", "listContainerSizes", "iterateContainerSizes", "version"] safemethods:
- "count"
- "containersOf"
- "deletionInfo"
- "eACL"
- "get"
- "getContainerSize"
- "iterateContainerSizes"
- "list"
- "listContainerSizes"
- "owner"
- "version"
permissions: permissions:
- methods: ["update", "addKey", "transferX", - methods:
"register", "addRecord", "deleteRecords"] - "addKey"
- "addRecord"
- "deleteRecords"
- "register"
- "transferX"
- "update"
events: events:
- name: containerPut
parameters:
- name: container
type: ByteArray
- name: signature
type: Signature
- name: publicKey
type: PublicKey
- name: token
type: ByteArray
- name: PutSuccess - name: PutSuccess
parameters: parameters:
- name: containerID - name: containerID
type: Hash256 type: Hash256
- name: publicKey - name: publicKey
type: PublicKey type: PublicKey
- name: containerDelete
parameters:
- name: containerID
type: ByteArray
- name: signature
type: Signature
- name: token
type: ByteArray
- name: DeleteSuccess - name: DeleteSuccess
parameters: parameters:
- name: containerID - name: containerID
type: ByteArray type: ByteArray
- name: setEACL
parameters:
- name: eACL
type: ByteArray
- name: signature
type: Signature
- name: publicKey
type: PublicKey
- name: token
type: ByteArray
- name: SetEACLSuccess - name: SetEACLSuccess
parameters: parameters:
- name: containerID - name: containerID

View file

@ -64,6 +64,7 @@ const (
estimateKeyPrefix = "cnr" estimateKeyPrefix = "cnr"
containerKeyPrefix = 'x' containerKeyPrefix = 'x'
ownerKeyPrefix = 'o' ownerKeyPrefix = 'o'
graveKeyPrefix = 'g'
estimatePostfixSize = 10 estimatePostfixSize = 10
// CleanupDelta contains the number of the last epochs for which container estimations are present. // CleanupDelta contains the number of the last epochs for which container estimations are present.
CleanupDelta = 3 CleanupDelta = 3
@ -171,13 +172,12 @@ func Update(script []byte, manifest []byte, data interface{}) {
panic("only committee can update contract") panic("only committee can update contract")
} }
contract.Call(interop.Hash160(management.Hash), "update", management.UpdateWithData(script, manifest, common.AppendVersion(data))
contract.All, script, manifest, common.AppendVersion(data))
runtime.Log("container contract updated") runtime.Log("container contract updated")
} }
// Put method creates a new container if it has been invoked by Alphabet nodes // Put method creates a new container if it has been invoked by Alphabet nodes
// of the Inner Ring. Otherwise, it produces containerPut notification. // of the Inner Ring.
// //
// Container should be a stable marshaled Container structure from API. // Container should be a stable marshaled Container structure from API.
// Signature is a RFC6979 signature of the Container. // Signature is a RFC6979 signature of the Container.
@ -303,15 +303,14 @@ func checkNiceNameAvailable(nnsContractAddr interop.Hash160, domain string) bool
} }
// Delete method removes a container from the contract storage if it has been // Delete method removes a container from the contract storage if it has been
// invoked by Alphabet nodes of the Inner Ring. Otherwise, it produces // invoked by Alphabet nodes of the Inner Ring.
// containerDelete notification.
// //
// Signature is a RFC6979 signature of the container ID. // Signature is a RFC6979 signature of the container ID.
// Token is optional and should be a stable marshaled SessionToken structure from // Token is optional and should be a stable marshaled SessionToken structure from
// API. // API.
// //
// If the container doesn't exist, it panics with NotFoundError. // If the container doesn't exist, it panics with NotFoundError.
func Delete(containerID []byte, signature interop.Signature, token []byte) { func Delete(containerID []byte, signature interop.Signature, publicKey interop.PublicKey, token []byte) {
ctx := storage.GetContext() ctx := storage.GetContext()
ownerID := getOwnerByID(ctx, containerID) ownerID := getOwnerByID(ctx, containerID)
@ -339,6 +338,25 @@ func Delete(containerID []byte, signature interop.Signature, token []byte) {
runtime.Notify("DeleteSuccess", containerID) runtime.Notify("DeleteSuccess", containerID)
} }
type DelInfo struct {
Owner interop.Hash160
Epoch int
}
// DeletionInfo method returns container deletion info.
// If the container had never existed, NotFoundError is throwed.
// It can be used to check whether non-existing container was indeed deleted
// or does not yet exist at some height.
func DeletionInfo(containerID []byte) DelInfo {
ctx := storage.GetReadOnlyContext()
graveKey := append([]byte{graveKeyPrefix}, containerID...)
data := storage.Get(ctx, graveKey).([]byte)
if data == nil {
panic(NotFoundError)
}
return std.Deserialize(data).(DelInfo)
}
// Get method returns a structure that contains a stable marshaled Container structure, // Get method returns a structure that contains a stable marshaled Container structure,
// the signature, the public key of the container creator and a stable marshaled SessionToken // the signature, the public key of the container creator and a stable marshaled SessionToken
// structure if it was provided. // structure if it was provided.
@ -407,8 +425,7 @@ func List(owner []byte) [][]byte {
} }
// SetEACL method sets a new extended ACL table related to the contract // SetEACL method sets a new extended ACL table related to the contract
// if it was invoked by Alphabet nodes of the Inner Ring. Otherwise, it produces // if it was invoked by Alphabet nodes of the Inner Ring.
// setEACL notification.
// //
// EACL should be a stable marshaled EACLTable structure from API. // EACL should be a stable marshaled EACLTable structure from API.
// Signature is a RFC6979 signature of the Container. // Signature is a RFC6979 signature of the Container.
@ -599,6 +616,9 @@ func addContainer(ctx storage.Context, id, owner []byte, container Container) {
idKey := append([]byte{containerKeyPrefix}, id...) idKey := append([]byte{containerKeyPrefix}, id...)
common.SetSerialized(ctx, idKey, container) common.SetSerialized(ctx, idKey, container)
graveKey := append([]byte{graveKeyPrefix}, id...)
storage.Delete(ctx, graveKey)
} }
func removeContainer(ctx storage.Context, id []byte, owner []byte) { func removeContainer(ctx storage.Context, id []byte, owner []byte) {
@ -607,6 +627,14 @@ func removeContainer(ctx storage.Context, id []byte, owner []byte) {
storage.Delete(ctx, containerListKey) storage.Delete(ctx, containerListKey)
storage.Delete(ctx, append([]byte{containerKeyPrefix}, id...)) storage.Delete(ctx, append([]byte{containerKeyPrefix}, id...))
graveKey := append([]byte{graveKeyPrefix}, id...)
netmapContractAddr := storage.Get(ctx, netmapContractKey).(interop.Hash160)
epoch := contract.Call(netmapContractAddr, "epoch", contract.ReadOnly).(int)
common.SetSerialized(ctx, graveKey, DelInfo{
Owner: owner,
Epoch: epoch,
})
} }
func getAllContainers(ctx storage.Context) [][]byte { func getAllContainers(ctx storage.Context) [][]byte {

View file

@ -9,47 +9,6 @@ the same arguments.
# Contract notifications # Contract notifications
containerPut notification. This notification is produced when a user wants to
create a new container. Alphabet nodes of the Inner Ring catch the notification and
validate container data, signature and token if present.
containerPut:
- name: container
type: ByteArray
- name: signature
type: Signature
- name: publicKey
type: PublicKey
- name: token
type: ByteArray
containerDelete notification. This notification is produced when a container owner
wants to delete a container. Alphabet nodes of the Inner Ring catch the notification
and validate container ownership, signature and token if present.
containerDelete:
- name: containerID
type: ByteArray
- name: signature
type: Signature
- name: token
type: ByteArray
setEACL notification. This notification is produced when a container owner wants
to update an extended ACL of a container. Alphabet nodes of the Inner Ring catch
the notification and validate container ownership, signature and token if
present.
setEACL:
- name: eACL
type: ByteArray
- name: signature
type: Signature
- name: publicKey
type: PublicKey
- name: token
type: ByteArray
StartEstimation notification. This notification is produced when Storage nodes StartEstimation notification. This notification is produced when Storage nodes
should exchange estimation values of container sizes among other Storage nodes. should exchange estimation values of container sizes among other Storage nodes.
@ -64,5 +23,22 @@ it in Container contract.
StopEstimation: StopEstimation:
- name: epoch - name: epoch
type: Integer type: Integer
# Contract storage scheme
| Key | Value | Description |
|-----------------------------------------------------------------------------------------------------|
| `netmapScriptHash` | Hash160 | netmap contract hash |
| `balanceScriptHash` | Hash160 | balance contract hash |
| `identityScriptHash` | Hash160 | frostfsID contract hash |
| `nnsContractKey` | Hash160 | nns contract hash |
| `nnsRoot` | string | default value for domain zone |
| `cnr` + epoch + containerID + publicKeyHash[:10] | ByteArray | estimated container size |
| `est` + containerID + publicKeyHash | ByteArray | serialized epochs array |
| `o` + ownerID + containerID | ByteArray | container ID |
| `x` + containerID | ByteArray | serialized container struct |
| `nnsHasAlias` + containerID | string | domain name |
*/ */
package container package container

1
debian/control vendored
View file

@ -31,4 +31,3 @@ Description: FrostFS-Contract contains all FrostFS related contracts.
- nns - nns
- proxy - proxy
- reputation - reputation
- subnet

View file

@ -1,5 +1,10 @@
name: "FrostFS" name: "FrostFS"
safemethods: ["alphabetAddress", "innerRingCandidates", "config", "listConfig", "version"] safemethods:
- "alphabetAddress"
- "config"
- "innerRingCandidates"
- "listConfig"
- "version"
permissions: permissions:
- methods: ["update", "transfer"] - methods: ["update", "transfer"]
events: events:

View file

@ -80,5 +80,15 @@ FrostFS network configuration value.
type: ByteArray type: ByteArray
- name: value - name: value
type: ByteArray type: ByteArray
# Contract storage scheme
| Key | Value | Description |
|-----------------------------------------------------------------------------|
| `processingScriptHash` | Hash160 | processing contract hash |
| `candidates` + candidateKey | ByteArray | it flags inner ring candidate |
| `config` + postfix | ByteArray | serialized config data |
*/ */
package frostfs package frostfs

View file

@ -109,8 +109,7 @@ func Update(script []byte, manifest []byte, data interface{}) {
panic(common.ErrAlphabetWitnessFailed) panic(common.ErrAlphabetWitnessFailed)
} }
contract.Call(interop.Hash160(management.Hash), "update", management.UpdateWithData(script, manifest, common.AppendVersion(data))
contract.All, script, manifest, common.AppendVersion(data))
runtime.Log("frostfs contract updated") runtime.Log("frostfs contract updated")
} }

View file

@ -16,5 +16,14 @@ contract.
# Contract notifications # Contract notifications
FrostFSID contract does not produce notifications to process. FrostFSID contract does not produce notifications to process.
# Contract storage scheme
| Key | Value | Description |
|-----------------------------|------------|----------------------------------|
| `processingScriptHash` | Hash160 | netmap contract hash |
| `containerScriptHash` | Hash160 | container contract hash |
| `o` + ownerID + publicKey | ByteArray | it flags owner's public key |
*/ */
package frostfsid package frostfsid

View file

@ -3,7 +3,6 @@ package frostfsid
import ( import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/common" "git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"github.com/nspcc-dev/neo-go/pkg/interop" "github.com/nspcc-dev/neo-go/pkg/interop"
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
"github.com/nspcc-dev/neo-go/pkg/interop/iterator" "github.com/nspcc-dev/neo-go/pkg/interop/iterator"
"github.com/nspcc-dev/neo-go/pkg/interop/native/management" "github.com/nspcc-dev/neo-go/pkg/interop/native/management"
"github.com/nspcc-dev/neo-go/pkg/interop/runtime" "github.com/nspcc-dev/neo-go/pkg/interop/runtime"
@ -62,8 +61,7 @@ func Update(script []byte, manifest []byte, data interface{}) {
panic("only committee can update contract") panic("only committee can update contract")
} }
contract.Call(interop.Hash160(management.Hash), "update", management.UpdateWithData(script, manifest, common.AppendVersion(data))
contract.All, script, manifest, common.AppendVersion(data))
runtime.Log("frostfsid contract updated") runtime.Log("frostfsid contract updated")
} }

6
go.mod
View file

@ -4,7 +4,7 @@ go 1.14
require ( require (
github.com/mr-tron/base58 v1.2.0 github.com/mr-tron/base58 v1.2.0
github.com/nspcc-dev/neo-go v0.99.4 github.com/nspcc-dev/neo-go v0.101.5-0.20230808195420-5fc61be5f6c5
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20220927123257-24c107e3a262 github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20230808195420-5fc61be5f6c5
github.com/stretchr/testify v1.7.0 github.com/stretchr/testify v1.8.1
) )

840
go.sum

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,13 @@
name: "Netmap" name: "Netmap"
safemethods: ["epoch", "netmap", "netmapCandidates", "snapshot", "snapshotByEpoch", "config", "listConfig", "version"] safemethods:
- "config"
- "epoch"
- "listConfig"
- "netmap"
- "netmapCandidates"
- "snapshot"
- "snapshotByEpoch"
- "version"
permissions: permissions:
- methods: ["update", "newEpoch"] - methods: ["update", "newEpoch"]
events: events:

View file

@ -29,5 +29,18 @@ in the network by invoking NewEpoch method.
NewEpoch NewEpoch
- name: epoch - name: epoch
type: Integer type: Integer
# Contract storage scheme
| Key | Value | Description |
|-----------------------------|------------|-----------------------------------|
| `snapshotCount` | int | snapshot count |
| `snapshotEpoch` | int | snapshot epoch |
| `snapshotBlock` | int | snapshot block |
| `snapshot_` + snapshotNum | ByteArray | serialized '[]Node' array |
| `snapshotCurrent` | int | current snapshot |
| `balanceScriptHash` | Hash160 | balance contract hash |
| `containerScriptHash` | Hash160 | container contract hash |
*/ */
package netmap package netmap

View file

@ -127,8 +127,7 @@ func Update(script []byte, manifest []byte, data interface{}) {
panic("only committee can update contract") panic("only committee can update contract")
} }
contract.Call(interop.Hash160(management.Hash), "update", management.UpdateWithData(script, manifest, common.AppendVersion(data))
contract.All, script, manifest, common.AppendVersion(data))
runtime.Log("netmap contract updated") runtime.Log("netmap contract updated")
} }

17
nns/doc.go Normal file
View file

@ -0,0 +1,17 @@
/*
# Contract storage scheme
| Key | Value | Description |
|--------------------------------------|------------|-----------------------------------|
| 0x0 | int | total supply of minted domains |
| 0x1 + accountAddr | int | account's balance |
| 0x2 + accountAddr + tokenKey | ByteArray | token ID |
| 0x10 | int | price for domain registration |
| 0x20 | int | set of roots |
| 0x21 + tokenKey | ByteArray | serialized NameState struct |
| 0x22 + tokenKey + Hash160(tokenName) | Hash160 | container contract hash |
*/
package nns

View file

@ -1,8 +1,20 @@
name: "NameService" name: "NameService"
supportedstandards: ["NEP-11"] supportedstandards: ["NEP-11"]
safemethods: ["balanceOf", "decimals", "symbol", "totalSupply", "tokensOf", "ownerOf", safemethods:
"tokens", "properties", "roots", "getPrice", "isAvailable", "getRecord", - "balanceOf"
"resolve", "getAllRecords"] - "decimals"
- "getAllRecords"
- "getPrice"
- "getRecord"
- "isAvailable"
- "ownerOf"
- "properties"
- "symbol"
- "totalSupply"
- "tokensOf"
- "tokens"
- "resolve"
- "roots"
events: events:
- name: Transfer - name: Transfer
parameters: parameters:

View file

@ -82,8 +82,7 @@ func Update(nef []byte, manifest string, data interface{}) {
// std and crypto contracts. This can be helpful on update // std and crypto contracts. This can be helpful on update
// thus we provide `AllowCall` to management.Update. // thus we provide `AllowCall` to management.Update.
// management.Update(nef, []byte(manifest)) // management.Update(nef, []byte(manifest))
contract.Call(interop.Hash160(management.Hash), "update", management.UpdateWithData(nef, []byte(manifest), common.AppendVersion(data))
contract.All, nef, manifest, common.AppendVersion(data))
runtime.Log("nns contract updated") runtime.Log("nns contract updated")
} }

View file

@ -18,5 +18,12 @@ execution.
# Contract notifications # Contract notifications
Processing contract does not produce notifications to process. Processing contract does not produce notifications to process.
# Contract storage scheme
| Key | Value | Description |
|-----------------------------|------------|----------------------------------|
| `frostfsScriptHash` | Hash160 | frostFS contract hash |
*/ */
package processing package processing

View file

@ -59,8 +59,7 @@ func Update(script []byte, manifest []byte, data interface{}) {
panic("only side chain committee can update contract") panic("only side chain committee can update contract")
} }
contract.Call(interop.Hash160(management.Hash), "update", management.UpdateWithData(script, manifest, common.AppendVersion(data))
contract.All, script, manifest, common.AppendVersion(data))
runtime.Log("processing contract updated") runtime.Log("processing contract updated")
} }

View file

@ -17,5 +17,10 @@ verified, Proxy contract pays for the execution.
# Contract notifications # Contract notifications
Proxy contract does not produce notifications to process. Proxy contract does not produce notifications to process.
# Contract storage scheme
Proxy contract does not use storage
*/ */
package proxy package proxy

View file

@ -3,7 +3,6 @@ package proxy
import ( import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/common" "git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"github.com/nspcc-dev/neo-go/pkg/interop" "github.com/nspcc-dev/neo-go/pkg/interop"
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
"github.com/nspcc-dev/neo-go/pkg/interop/native/gas" "github.com/nspcc-dev/neo-go/pkg/interop/native/gas"
"github.com/nspcc-dev/neo-go/pkg/interop/native/management" "github.com/nspcc-dev/neo-go/pkg/interop/native/management"
"github.com/nspcc-dev/neo-go/pkg/interop/native/neo" "github.com/nspcc-dev/neo-go/pkg/interop/native/neo"
@ -35,8 +34,7 @@ func Update(script []byte, manifest []byte, data interface{}) {
panic("only committee can update contract") panic("only committee can update contract")
} }
contract.Call(interop.Hash160(management.Hash), "update", management.UpdateWithData(script, manifest, common.AppendVersion(data))
contract.All, script, manifest, common.AppendVersion(data))
runtime.Log("proxy contract updated") runtime.Log("proxy contract updated")
} }

View file

@ -13,5 +13,13 @@ Inner Ring nodes if data audit succeeds.
# Contract notifications # Contract notifications
Reputation contract does not produce notifications to process. Reputation contract does not produce notifications to process.
# Contract storage scheme
| Key | Value | Description |
|-----------------------------|------------|-----------------------------------|
| `c` + epoch + peerID | int | peer reputation count |
| `r` + count | ByteArray | serialized DataAuditResult struct |
*/ */
package reputation package reputation

View file

@ -2,8 +2,6 @@ package reputation
import ( import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/common" "git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"github.com/nspcc-dev/neo-go/pkg/interop"
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
"github.com/nspcc-dev/neo-go/pkg/interop/convert" "github.com/nspcc-dev/neo-go/pkg/interop/convert"
"github.com/nspcc-dev/neo-go/pkg/interop/iterator" "github.com/nspcc-dev/neo-go/pkg/interop/iterator"
"github.com/nspcc-dev/neo-go/pkg/interop/native/management" "github.com/nspcc-dev/neo-go/pkg/interop/native/management"
@ -36,8 +34,7 @@ func Update(script []byte, manifest []byte, data interface{}) {
panic("only committee can update contract") panic("only committee can update contract")
} }
contract.Call(interop.Hash160(management.Hash), "update", management.UpdateWithData(script, manifest, common.AppendVersion(data))
contract.All, script, manifest, common.AppendVersion(data))
runtime.Log("reputation contract updated") runtime.Log("reputation contract updated")
} }

View file

@ -1,23 +0,0 @@
name: "Subnet"
safemethods: ["version"]
permissions:
- methods: ["update"]
events:
- name: Put
parameters:
- name: id
type: ByteArray
- name: ownerKey
type: PublicKey
- name: info
type: ByteArray
- name: Delete
parameters:
- name: id
type: ByteArray
- name: RemoveNode
parameters:
- name: subnetID
type: ByteArray
- name: node
type: PublicKey

View file

@ -1,37 +0,0 @@
/*
Subnet contract is a contract deployed in FrostFS sidechain.
Subnet contract stores and manages FrostFS subnetwork states. It allows registering
and deleting subnetworks, limiting access to them, and defining a list of the Storage
Nodes that can be included in them.
# Contract notifications
Put notification. This notification is produced when a new subnetwork is
registered by invoking Put method.
Put
- name: id
type: ByteArray
- name: ownerKey
type: PublicKey
- name: info
type: ByteArray
Delete notification. This notification is produced when some subnetwork is
deleted by invoking Delete method.
Delete
- name: id
type: ByteArray
RemoveNode notification. This notification is produced when some node is deleted
by invoking RemoveNode method.
RemoveNode
- name: subnetID
type: ByteArray
- name: node
type: PublicKey
*/
package subnet

View file

@ -1,559 +0,0 @@
package subnet
import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"github.com/nspcc-dev/neo-go/pkg/interop"
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
"github.com/nspcc-dev/neo-go/pkg/interop/iterator"
"github.com/nspcc-dev/neo-go/pkg/interop/native/management"
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
)
const (
// ErrInvalidSubnetID is thrown when subnet id is not a slice of 5 bytes.
ErrInvalidSubnetID = "invalid subnet ID"
// ErrInvalidGroupID is thrown when group id is not a slice of 5 bytes.
ErrInvalidGroupID = "invalid group ID"
// ErrInvalidOwner is thrown when owner has invalid format.
ErrInvalidOwner = "invalid owner"
// ErrInvalidAdmin is thrown when admin has invalid format.
ErrInvalidAdmin = "invalid administrator"
// ErrAlreadyExists is thrown when id already exists.
ErrAlreadyExists = "subnet id already exists"
// ErrNotExist is thrown when id doesn't exist.
ErrNotExist = "subnet id doesn't exist"
// ErrInvalidUser is thrown when user has invalid format.
ErrInvalidUser = "invalid user"
// ErrInvalidNode is thrown when node has invalid format.
ErrInvalidNode = "invalid node key"
// ErrAccessDenied is thrown when operation is denied for caller.
ErrAccessDenied = "access denied"
)
const (
nodeAdminPrefix = 'a'
infoPrefix = 'i'
clientAdminPrefix = 'm'
nodePrefix = 'n'
ownerPrefix = 'o'
userPrefix = 'u'
notaryDisabledKey = 'z'
)
const (
userIDSize = 27
subnetIDSize = 5
groupIDSize = 5
)
// _deploy function sets up initial list of inner ring public keys.
func _deploy(data interface{}, isUpdate bool) {
common.RmAndCheckNotaryDisabledKey(data, notaryDisabledKey)
if isUpdate {
args := data.([]interface{})
common.CheckVersion(args[len(args)-1].(int))
return
}
}
// Update method updates contract source code and manifest. It can be invoked
// only by committee.
func Update(script []byte, manifest []byte, data interface{}) {
if !common.HasUpdateAccess() {
panic("only committee can update contract")
}
contract.Call(interop.Hash160(management.Hash), "update", contract.All,
script, manifest, common.AppendVersion(data))
runtime.Log("subnet contract updated")
}
// Put creates a new subnet with the specified owner and info.
func Put(id []byte, ownerKey interop.PublicKey, info []byte) {
// V2 format
if len(id) != subnetIDSize {
panic(ErrInvalidSubnetID)
}
if len(ownerKey) != interop.PublicKeyCompressedLen {
panic(ErrInvalidOwner)
}
ctx := storage.GetContext()
stKey := append([]byte{ownerPrefix}, id...)
if storage.Get(ctx, stKey) != nil {
panic(ErrAlreadyExists)
}
common.CheckOwnerWitness(ownerKey)
common.CheckAlphabetWitness()
storage.Put(ctx, stKey, ownerKey)
stKey[0] = infoPrefix
storage.Put(ctx, stKey, info)
}
// Get returns info about the subnet with the specified id.
func Get(id []byte) []byte {
// V2 format
if len(id) != subnetIDSize {
panic(ErrInvalidSubnetID)
}
ctx := storage.GetReadOnlyContext()
key := append([]byte{infoPrefix}, id...)
raw := storage.Get(ctx, key)
if raw == nil {
panic(ErrNotExist)
}
return raw.([]byte)
}
// Delete deletes the subnet with the specified id.
func Delete(id []byte) {
// V2 format
if len(id) != subnetIDSize {
panic(ErrInvalidSubnetID)
}
ctx := storage.GetContext()
key := append([]byte{ownerPrefix}, id...)
raw := storage.Get(ctx, key)
if raw == nil {
return
}
owner := raw.([]byte)
common.CheckOwnerWitness(owner)
storage.Delete(ctx, key)
key[0] = infoPrefix
storage.Delete(ctx, key)
key[0] = nodeAdminPrefix
deleteByPrefix(ctx, key)
key[0] = nodePrefix
deleteByPrefix(ctx, key)
key[0] = clientAdminPrefix
deleteByPrefix(ctx, key)
key[0] = userPrefix
deleteByPrefix(ctx, key)
runtime.Notify("Delete", id)
}
// AddNodeAdmin adds a new node administrator to the specified subnetwork.
func AddNodeAdmin(subnetID []byte, adminKey interop.PublicKey) {
// V2 format
if len(subnetID) != subnetIDSize {
panic(ErrInvalidSubnetID)
}
if len(adminKey) != interop.PublicKeyCompressedLen {
panic(ErrInvalidAdmin)
}
ctx := storage.GetContext()
stKey := append([]byte{ownerPrefix}, subnetID...)
rawOwner := storage.Get(ctx, stKey)
if rawOwner == nil {
panic(ErrNotExist)
}
owner := rawOwner.([]byte)
common.CheckOwnerWitness(owner)
stKey[0] = nodeAdminPrefix
if keyInList(ctx, adminKey, stKey) {
return
}
putKeyInList(ctx, adminKey, stKey)
}
// RemoveNodeAdmin removes node administrator from the specified subnetwork.
// Must be called by the subnet owner only.
func RemoveNodeAdmin(subnetID []byte, adminKey interop.PublicKey) {
// V2 format
if len(subnetID) != subnetIDSize {
panic(ErrInvalidSubnetID)
}
if len(adminKey) != interop.PublicKeyCompressedLen {
panic(ErrInvalidAdmin)
}
ctx := storage.GetContext()
stKey := append([]byte{ownerPrefix}, subnetID...)
rawOwner := storage.Get(ctx, stKey)
if rawOwner == nil {
panic(ErrNotExist)
}
owner := rawOwner.([]byte)
common.CheckOwnerWitness(owner)
stKey[0] = nodeAdminPrefix
if !keyInList(ctx, adminKey, stKey) {
return
}
deleteKeyFromList(ctx, adminKey, stKey)
}
// AddNode adds a node to the specified subnetwork.
// Must be called by the subnet's owner or the node administrator
// only.
func AddNode(subnetID []byte, node interop.PublicKey) {
// V2 format
if len(subnetID) != subnetIDSize {
panic(ErrInvalidSubnetID)
}
if len(node) != interop.PublicKeyCompressedLen {
panic(ErrInvalidNode)
}
ctx := storage.GetContext()
stKey := append([]byte{ownerPrefix}, subnetID...)
rawOwner := storage.Get(ctx, stKey)
if rawOwner == nil {
panic(ErrNotExist)
}
stKey[0] = nodeAdminPrefix
owner := rawOwner.([]byte)
if !calledByOwnerOrAdmin(ctx, owner, stKey) {
panic(ErrAccessDenied)
}
stKey[0] = nodePrefix
if keyInList(ctx, node, stKey) {
return
}
putKeyInList(ctx, node, stKey)
}
// RemoveNode removes a node from the specified subnetwork.
// Must be called by the subnet's owner or the node administrator
// only.
func RemoveNode(subnetID []byte, node interop.PublicKey) {
// V2 format
if len(subnetID) != subnetIDSize {
panic(ErrInvalidSubnetID)
}
if len(node) != interop.PublicKeyCompressedLen {
panic(ErrInvalidNode)
}
ctx := storage.GetContext()
stKey := append([]byte{ownerPrefix}, subnetID...)
rawOwner := storage.Get(ctx, stKey)
if rawOwner == nil {
panic(ErrNotExist)
}
stKey[0] = nodeAdminPrefix
owner := rawOwner.([]byte)
if !calledByOwnerOrAdmin(ctx, owner, stKey) {
panic(ErrAccessDenied)
}
stKey[0] = nodePrefix
if !keyInList(ctx, node, stKey) {
return
}
storage.Delete(ctx, append(stKey, node...))
runtime.Notify("RemoveNode", subnetID, node)
}
// NodeAllowed checks if a node is included in the
// specified subnet.
func NodeAllowed(subnetID []byte, node interop.PublicKey) bool {
// V2 format
if len(subnetID) != subnetIDSize {
panic(ErrInvalidSubnetID)
}
if len(node) != interop.PublicKeyCompressedLen {
panic(ErrInvalidNode)
}
ctx := storage.GetReadOnlyContext()
stKey := append([]byte{ownerPrefix}, subnetID...)
rawOwner := storage.Get(ctx, stKey)
if rawOwner == nil {
panic(ErrNotExist)
}
stKey[0] = nodePrefix
return storage.Get(ctx, append(stKey, node...)) != nil
}
// AddClientAdmin adds a new client administrator of the specified group in the specified subnetwork.
// Must be called by the owner only.
func AddClientAdmin(subnetID []byte, groupID []byte, adminPublicKey interop.PublicKey) {
// V2 format
if len(subnetID) != subnetIDSize {
panic(ErrInvalidSubnetID)
}
// V2 format
if len(groupID) != groupIDSize {
panic(ErrInvalidGroupID)
}
if len(adminPublicKey) != interop.PublicKeyCompressedLen {
panic(ErrInvalidAdmin)
}
ctx := storage.GetContext()
stKey := append([]byte{ownerPrefix}, subnetID...)
rawOwner := storage.Get(ctx, stKey)
if rawOwner == nil {
panic(ErrNotExist)
}
owner := rawOwner.([]byte)
common.CheckOwnerWitness(owner)
stKey[0] = clientAdminPrefix
stKey = append(stKey, groupID...)
if keyInList(ctx, adminPublicKey, stKey) {
return
}
putKeyInList(ctx, adminPublicKey, stKey)
}
// RemoveClientAdmin removes client administrator from the
// specified group in the specified subnetwork.
// Must be called by the owner only.
func RemoveClientAdmin(subnetID []byte, groupID []byte, adminPublicKey interop.PublicKey) {
// V2 format
if len(subnetID) != subnetIDSize {
panic(ErrInvalidSubnetID)
}
// V2 format
if len(groupID) != groupIDSize {
panic(ErrInvalidGroupID)
}
if len(adminPublicKey) != interop.PublicKeyCompressedLen {
panic(ErrInvalidAdmin)
}
ctx := storage.GetContext()
stKey := append([]byte{ownerPrefix}, subnetID...)
rawOwner := storage.Get(ctx, stKey)
if rawOwner == nil {
panic(ErrNotExist)
}
owner := rawOwner.([]byte)
common.CheckOwnerWitness(owner)
stKey[0] = clientAdminPrefix
stKey = append(stKey, groupID...)
if !keyInList(ctx, adminPublicKey, stKey) {
return
}
deleteKeyFromList(ctx, adminPublicKey, stKey)
}
// AddUser adds user to the specified subnetwork and group.
// Must be called by the owner or the group's admin only.
func AddUser(subnetID []byte, groupID []byte, userID []byte) {
// V2 format
if len(subnetID) != subnetIDSize {
panic(ErrInvalidSubnetID)
}
// V2 format
if len(userID) != userIDSize {
panic(ErrInvalidUser)
}
// V2 format
if len(groupID) != groupIDSize {
panic(ErrInvalidGroupID)
}
ctx := storage.GetContext()
stKey := append([]byte{ownerPrefix}, subnetID...)
rawOwner := storage.Get(ctx, stKey)
if rawOwner == nil {
panic(ErrNotExist)
}
stKey[0] = clientAdminPrefix
stKey = append(stKey, groupID...)
owner := rawOwner.([]byte)
if !calledByOwnerOrAdmin(ctx, owner, stKey) {
panic(ErrAccessDenied)
}
stKey[0] = userPrefix
if keyInList(ctx, userID, stKey) {
return
}
putKeyInList(ctx, userID, stKey)
}
// RemoveUser removes a user from the specified subnetwork and group.
// Must be called by the owner or the group's admin only.
func RemoveUser(subnetID []byte, groupID []byte, userID []byte) {
// V2 format
if len(subnetID) != subnetIDSize {
panic(ErrInvalidSubnetID)
}
// V2 format
if len(groupID) != groupIDSize {
panic(ErrInvalidGroupID)
}
// V2 format
if len(userID) != userIDSize {
panic(ErrInvalidUser)
}
ctx := storage.GetContext()
stKey := append([]byte{ownerPrefix}, subnetID...)
rawOwner := storage.Get(ctx, stKey)
if rawOwner == nil {
panic(ErrNotExist)
}
stKey[0] = clientAdminPrefix
stKey = append(stKey, groupID...)
owner := rawOwner.([]byte)
if !calledByOwnerOrAdmin(ctx, owner, stKey) {
panic(ErrAccessDenied)
}
stKey[0] = userPrefix
if !keyInList(ctx, userID, stKey) {
return
}
deleteKeyFromList(ctx, userID, stKey)
}
// UserAllowed returns bool that indicates if a node is included in the
// specified subnet.
func UserAllowed(subnetID []byte, user []byte) bool {
// V2 format
if len(subnetID) != subnetIDSize {
panic(ErrInvalidSubnetID)
}
ctx := storage.GetContext()
stKey := append([]byte{ownerPrefix}, subnetID...)
if storage.Get(ctx, stKey) == nil {
panic(ErrNotExist)
}
stKey[0] = userPrefix
prefixLen := len(stKey) + groupIDSize
iter := storage.Find(ctx, stKey, storage.KeysOnly)
for iterator.Next(iter) {
key := iterator.Value(iter).([]byte)
if common.BytesEqual(user, key[prefixLen:]) {
return true
}
}
return false
}
// Version returns the version of the contract.
func Version() int {
return common.Version
}
func keyInList(ctx storage.Context, searchedKey interop.PublicKey, prefix []byte) bool {
return storage.Get(ctx, append(prefix, searchedKey...)) != nil
}
func putKeyInList(ctx storage.Context, keyToPut interop.PublicKey, prefix []byte) {
storage.Put(ctx, append(prefix, keyToPut...), []byte{1})
}
func deleteKeyFromList(ctx storage.Context, keyToDelete interop.PublicKey, prefix []byte) {
storage.Delete(ctx, append(prefix, keyToDelete...))
}
func deleteByPrefix(ctx storage.Context, prefix []byte) {
iter := storage.Find(ctx, prefix, storage.KeysOnly)
for iterator.Next(iter) {
k := iterator.Value(iter).([]byte)
storage.Delete(ctx, k)
}
}
func calledByOwnerOrAdmin(ctx storage.Context, owner []byte, adminPrefix []byte) bool {
if runtime.CheckWitness(owner) {
return true
}
iter := storage.Find(ctx, adminPrefix, storage.KeysOnly|storage.RemovePrefix)
for iterator.Next(iter) {
key := iterator.Value(iter).([]byte)
if runtime.CheckWitness(key) {
return true
}
}
return false
}

View file

@ -58,8 +58,12 @@ func newContainerInvoker(t *testing.T) (*neotest.ContractInvoker, *neotest.Contr
} }
func setContainerOwner(c []byte, acc neotest.Signer) { func setContainerOwner(c []byte, acc neotest.Signer) {
copy(c[6:], signerToOwner(acc))
}
func signerToOwner(acc neotest.Signer) []byte {
owner, _ := base58.Decode(address.Uint160ToString(acc.ScriptHash())) owner, _ := base58.Decode(address.Uint160ToString(acc.ScriptHash()))
copy(c[6:], owner) return owner
} }
type testContainer struct { type testContainer struct {
@ -105,15 +109,15 @@ func TestContainerCount(t *testing.T) {
c.Invoke(t, stackitem.Null{}, "put", cnt3.value, cnt3.sig, cnt3.pub, cnt3.token) c.Invoke(t, stackitem.Null{}, "put", cnt3.value, cnt3.sig, cnt3.pub, cnt3.token)
checkContainerList(t, c, [][]byte{cnt1.id[:], cnt2.id[:], cnt3.id[:]}) checkContainerList(t, c, [][]byte{cnt1.id[:], cnt2.id[:], cnt3.id[:]})
c.Invoke(t, stackitem.Null{}, "delete", cnt1.id[:], cnt1.sig, cnt1.token) c.Invoke(t, stackitem.Null{}, "delete", cnt1.id[:], cnt1.sig, cnt1.pub, cnt1.token)
checkCount(t, 2) checkCount(t, 2)
checkContainerList(t, c, [][]byte{cnt2.id[:], cnt3.id[:]}) checkContainerList(t, c, [][]byte{cnt2.id[:], cnt3.id[:]})
c.Invoke(t, stackitem.Null{}, "delete", cnt2.id[:], cnt2.sig, cnt2.token) c.Invoke(t, stackitem.Null{}, "delete", cnt2.id[:], cnt2.sig, cnt2.pub, cnt2.token)
checkCount(t, 1) checkCount(t, 1)
checkContainerList(t, c, [][]byte{cnt3.id[:]}) checkContainerList(t, c, [][]byte{cnt3.id[:]})
c.Invoke(t, stackitem.Null{}, "delete", cnt3.id[:], cnt3.sig, cnt3.token) c.Invoke(t, stackitem.Null{}, "delete", cnt3.id[:], cnt3.sig, cnt3.pub, cnt3.token)
checkCount(t, 0) checkCount(t, 0)
checkContainerList(t, c, [][]byte{}) checkContainerList(t, c, [][]byte{})
} }
@ -201,7 +205,7 @@ func TestContainerPut(t *testing.T) {
c.InvokeFail(t, "name is already taken", "putNamed", putArgs...) c.InvokeFail(t, "name is already taken", "putNamed", putArgs...)
}) })
c.Invoke(t, stackitem.Null{}, "delete", cnt.id[:], cnt.sig, cnt.token) c.Invoke(t, stackitem.Null{}, "delete", cnt.id[:], cnt.sig, cnt.pub, cnt.token)
cNNS.Invoke(t, stackitem.Null{}, "resolve", "mycnt.frostfs", int64(nns.TXT)) cNNS.Invoke(t, stackitem.Null{}, "resolve", "mycnt.frostfs", int64(nns.TXT))
t.Run("register in advance", func(t *testing.T) { t.Run("register in advance", func(t *testing.T) {
@ -239,19 +243,43 @@ func addContainer(t *testing.T, c, cBal *neotest.ContractInvoker) (neotest.Signe
} }
func TestContainerDelete(t *testing.T) { func TestContainerDelete(t *testing.T) {
c, cBal, _ := newContainerInvoker(t) c, cBal, cNm := newContainerInvoker(t)
acc, cnt := addContainer(t, c, cBal) acc, cnt := addContainer(t, c, cBal)
cAcc := c.WithSigners(acc) cAcc := c.WithSigners(acc)
cAcc.InvokeFail(t, common.ErrAlphabetWitnessFailed, "delete", cAcc.InvokeFail(t, common.ErrAlphabetWitnessFailed, "delete",
cnt.id[:], cnt.sig, cnt.token) cnt.id[:], cnt.sig, cnt.pub, cnt.token)
c.Invoke(t, stackitem.Null{}, "delete", cnt.id[:], cnt.sig, cnt.token) newDelInfo := func(acc neotest.Signer, epoch int64) *stackitem.Struct {
return stackitem.NewStruct([]stackitem.Item{
stackitem.NewBuffer([]byte(signerToOwner(acc))),
stackitem.NewBigInteger(big.NewInt(epoch)),
})
}
c.InvokeFail(t, container.NotFoundError, "deletionInfo", cnt.id[:])
c.Invoke(t, stackitem.Null{}, "delete", cnt.id[:], cnt.sig, cnt.pub, cnt.token)
c.Invoke(t, newDelInfo(acc, 0), "deletionInfo", cnt.id[:])
t.Run("multi-epoch", func(t *testing.T) {
cNm.Invoke(t, stackitem.Null{}, "newEpoch", 1)
t.Run("epoch tick does not change deletion info", func(t *testing.T) {
c.Invoke(t, stackitem.Null{}, "delete", cnt.id[:], cnt.sig, cnt.pub, cnt.token)
c.Invoke(t, newDelInfo(acc, 0), "deletionInfo", cnt.id[:])
})
acc1, cnt1 := addContainer(t, c, cBal)
c.Invoke(t, stackitem.Null{}, "delete", cnt1.id[:], cnt1.sig, cnt1.pub, cnt1.token)
c.Invoke(t, newDelInfo(acc, 0), "deletionInfo", cnt.id[:])
c.Invoke(t, newDelInfo(acc1, 1), "deletionInfo", cnt1.id[:])
})
t.Run("missing container", func(t *testing.T) { t.Run("missing container", func(t *testing.T) {
id := cnt.id id := cnt.id
id[0] ^= 0xFF id[0] ^= 0xFF
c.Invoke(t, stackitem.Null{}, "delete", cnt.id[:], cnt.sig, cnt.token) c.Invoke(t, stackitem.Null{}, "delete", cnt.id[:], cnt.sig, cnt.pub, cnt.token)
c.InvokeFail(t, container.NotFoundError, "deletionInfo", id[:])
}) })
c.InvokeFail(t, container.NotFoundError, "get", cnt.id[:]) c.InvokeFail(t, container.NotFoundError, "get", cnt.id[:])
@ -268,7 +296,7 @@ func TestContainerOwner(t *testing.T) {
c.InvokeFail(t, container.NotFoundError, "owner", id[:]) c.InvokeFail(t, container.NotFoundError, "owner", id[:])
}) })
owner, _ := base58.Decode(address.Uint160ToString(acc.ScriptHash())) owner := signerToOwner(acc)
c.Invoke(t, stackitem.NewBuffer(owner), "owner", cnt.id[:]) c.Invoke(t, stackitem.NewBuffer(owner), "owner", cnt.id[:])
} }

View file

@ -7,9 +7,7 @@ import (
"testing" "testing"
"git.frostfs.info/TrueCloudLab/frostfs-contract/container" "git.frostfs.info/TrueCloudLab/frostfs-contract/container"
"github.com/mr-tron/base58"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/neotest" "github.com/nspcc-dev/neo-go/pkg/neotest"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
@ -57,7 +55,7 @@ func TestFrostFSID_AddKey(t *testing.T) {
pubs[i] = p.PublicKey().Bytes() pubs[i] = p.PublicKey().Bytes()
} }
acc := e.NewAccount(t) acc := e.NewAccount(t)
owner, _ := base58.Decode(address.Uint160ToString(acc.ScriptHash())) owner := signerToOwner(acc)
e.Invoke(t, stackitem.Null{}, "addKey", owner, e.Invoke(t, stackitem.Null{}, "addKey", owner,
[]interface{}{pubs[0], pubs[1]}) []interface{}{pubs[0], pubs[1]})

View file

@ -1,330 +0,0 @@
package tests
import (
"encoding/binary"
"path"
"testing"
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"git.frostfs.info/TrueCloudLab/frostfs-contract/subnet"
"github.com/nspcc-dev/neo-go/pkg/neotest"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/stretchr/testify/require"
)
const subnetPath = "../subnet"
func deploySubnetContract(t *testing.T, e *neotest.Executor) util.Uint160 {
c := neotest.CompileFile(t, e.CommitteeHash, subnetPath, path.Join(subnetPath, "config.yml"))
args := []interface{}{false}
e.DeployContract(t, c, args)
return c.Hash
}
func newSubnetInvoker(t *testing.T) *neotest.ContractInvoker {
e := newExecutor(t)
h := deploySubnetContract(t, e)
return e.CommitteeInvoker(h)
}
func TestSubnet_Version(t *testing.T) {
e := newSubnetInvoker(t)
e.Invoke(t, common.Version, "version")
}
func TestSubnet_Put(t *testing.T) {
e := newSubnetInvoker(t)
acc := e.NewAccount(t)
pub, ok := vm.ParseSignatureContract(acc.Script())
require.True(t, ok)
id := make([]byte, 5)
binary.LittleEndian.PutUint32(id, 123)
info := randomBytes(10)
e.InvokeFail(t, common.ErrWitnessFailed, "put", id, pub, info)
cAcc := e.WithSigners(acc)
cAcc.InvokeFail(t, common.ErrAlphabetWitnessFailed, "put", id, pub, info)
cBoth := e.WithSigners(e.Committee, acc)
cBoth.InvokeFail(t, subnet.ErrInvalidSubnetID, "put", []byte{1, 2, 3}, pub, info)
cBoth.InvokeFail(t, subnet.ErrInvalidOwner, "put", id, pub[10:], info)
cBoth.Invoke(t, stackitem.Null{}, "put", id, pub, info)
cAcc.Invoke(t, stackitem.NewBuffer(info), "get", id)
cBoth.InvokeFail(t, subnet.ErrAlreadyExists, "put", id, pub, info)
}
func TestSubnet_Delete(t *testing.T) {
e := newSubnetInvoker(t)
id, owner := createSubnet(t, e)
e.InvokeFail(t, common.ErrWitnessFailed, "delete", id)
cAcc := e.WithSigners(owner)
cAcc.InvokeFail(t, subnet.ErrInvalidSubnetID, "delete", []byte{1, 1, 1, 1})
cAcc.Invoke(t, stackitem.Null{}, "delete", []byte{1, 1, 1, 1, 1})
cAcc.Invoke(t, stackitem.Null{}, "delete", id)
cAcc.InvokeFail(t, subnet.ErrNotExist, "get", id)
}
func TestSubnet_AddNodeAdmin(t *testing.T) {
e := newSubnetInvoker(t)
id, owner := createSubnet(t, e)
adm := e.NewAccount(t)
admPub, ok := vm.ParseSignatureContract(adm.Script())
require.True(t, ok)
const method = "addNodeAdmin"
e.InvokeFail(t, subnet.ErrInvalidSubnetID, method, []byte{0, 0, 0, 0}, admPub)
e.InvokeFail(t, subnet.ErrInvalidAdmin, method, id, admPub[1:])
e.InvokeFail(t, subnet.ErrNotExist, method, []byte{0, 0, 0, 0, 0}, admPub)
cAdm := e.WithSigners(adm)
cAdm.InvokeFail(t, common.ErrOwnerWitnessFailed, method, id, admPub)
cOwner := e.WithSigners(owner)
cOwner.Invoke(t, stackitem.Null{}, method, id, admPub)
cOwner.Invoke(t, stackitem.Null{}, method, id, admPub)
}
func TestSubnet_RemoveNodeAdmin(t *testing.T) {
e := newSubnetInvoker(t)
id, owner := createSubnet(t, e)
adm := e.NewAccount(t)
admPub, ok := vm.ParseSignatureContract(adm.Script())
require.True(t, ok)
const method = "removeNodeAdmin"
e.InvokeFail(t, subnet.ErrInvalidSubnetID, method, []byte{0, 0, 0, 0}, admPub)
e.InvokeFail(t, subnet.ErrInvalidAdmin, method, id, admPub[1:])
e.InvokeFail(t, subnet.ErrNotExist, method, []byte{0, 0, 0, 0, 0}, admPub)
cAdm := e.WithSigners(adm)
cAdm.InvokeFail(t, common.ErrOwnerWitnessFailed, method, id, admPub)
cOwner := e.WithSigners(owner)
cOwner.Invoke(t, stackitem.Null{}, method, id, admPub)
cOwner.Invoke(t, stackitem.Null{}, "addNodeAdmin", id, admPub)
cOwner.Invoke(t, stackitem.Null{}, method, id, admPub)
cOwner.Invoke(t, stackitem.Null{}, method, id, admPub)
}
func TestSubnet_AddNode(t *testing.T) {
e := newSubnetInvoker(t)
id, owner := createSubnet(t, e)
node := e.NewAccount(t)
nodePub, ok := vm.ParseSignatureContract(node.Script())
require.True(t, ok)
const method = "addNode"
cOwn := e.WithSigners(owner)
cOwn.InvokeFail(t, subnet.ErrInvalidSubnetID, method, []byte{0, 0, 0, 0}, nodePub)
cOwn.InvokeFail(t, subnet.ErrInvalidNode, method, id, nodePub[1:])
cOwn.InvokeFail(t, subnet.ErrNotExist, method, []byte{0, 0, 0, 0, 0}, nodePub)
cOwn.Invoke(t, stackitem.Null{}, method, id, nodePub)
cOwn.Invoke(t, stackitem.Null{}, method, id, nodePub)
}
func TestSubnet_RemoveNode(t *testing.T) {
e := newSubnetInvoker(t)
id, owner := createSubnet(t, e)
node := e.NewAccount(t)
nodePub, ok := vm.ParseSignatureContract(node.Script())
require.True(t, ok)
adm := e.NewAccount(t)
admPub, ok := vm.ParseSignatureContract(adm.Script())
require.True(t, ok)
const method = "removeNode"
cOwn := e.WithSigners(owner)
cOwn.InvokeFail(t, subnet.ErrInvalidSubnetID, method, []byte{0, 0, 0, 0}, nodePub)
cOwn.InvokeFail(t, subnet.ErrInvalidNode, method, id, nodePub[1:])
cOwn.InvokeFail(t, subnet.ErrNotExist, method, []byte{0, 0, 0, 0, 0}, nodePub)
cOwn.Invoke(t, stackitem.Null{}, method, id, nodePub)
cOwn.Invoke(t, stackitem.Null{}, "addNode", id, nodePub)
cOwn.Invoke(t, stackitem.Null{}, method, id, nodePub)
cAdm := cOwn.WithSigners(adm)
cOwn.Invoke(t, stackitem.Null{}, "addNodeAdmin", id, admPub)
cAdm.Invoke(t, stackitem.Null{}, method, id, nodePub)
}
func TestSubnet_NodeAllowed(t *testing.T) {
e := newSubnetInvoker(t)
id, owner := createSubnet(t, e)
node := e.NewAccount(t)
nodePub, ok := vm.ParseSignatureContract(node.Script())
require.True(t, ok)
const method = "nodeAllowed"
cOwn := e.WithSigners(owner)
cOwn.InvokeFail(t, subnet.ErrInvalidSubnetID, method, []byte{0, 0, 0, 0}, nodePub)
cOwn.InvokeFail(t, subnet.ErrInvalidNode, method, id, nodePub[1:])
cOwn.InvokeFail(t, subnet.ErrNotExist, method, []byte{0, 0, 0, 0, 0}, nodePub)
cOwn.Invoke(t, stackitem.NewBool(false), method, id, nodePub)
cOwn.Invoke(t, stackitem.Null{}, "addNode", id, nodePub)
cOwn.Invoke(t, stackitem.NewBool(true), method, id, nodePub)
}
func TestSubnet_AddClientAdmin(t *testing.T) {
e := newSubnetInvoker(t)
id, owner := createSubnet(t, e)
adm := e.NewAccount(t)
admPub, ok := vm.ParseSignatureContract(adm.Script())
require.True(t, ok)
const method = "addClientAdmin"
groupId := randomBytes(5)
cOwn := e.WithSigners(owner)
cOwn.InvokeFail(t, subnet.ErrInvalidSubnetID, method, []byte{0, 0, 0, 0}, groupId, admPub)
cOwn.InvokeFail(t, subnet.ErrInvalidAdmin, method, id, groupId, admPub[1:])
cOwn.InvokeFail(t, subnet.ErrNotExist, method, []byte{0, 0, 0, 0, 0}, groupId, admPub)
cOwn.Invoke(t, stackitem.Null{}, method, id, groupId, admPub)
cOwn.Invoke(t, stackitem.Null{}, method, id, groupId, admPub)
}
func TestSubnet_RemoveClientAdmin(t *testing.T) {
e := newSubnetInvoker(t)
id, owner := createSubnet(t, e)
adm := e.NewAccount(t)
admPub, ok := vm.ParseSignatureContract(adm.Script())
require.True(t, ok)
const method = "removeClientAdmin"
groupId := randomBytes(5)
cOwn := e.WithSigners(owner)
cOwn.InvokeFail(t, subnet.ErrInvalidSubnetID, method, []byte{0, 0, 0, 0}, groupId, admPub)
cOwn.InvokeFail(t, subnet.ErrInvalidAdmin, method, id, groupId, admPub[1:])
cOwn.InvokeFail(t, subnet.ErrNotExist, method, []byte{0, 0, 0, 0, 0}, groupId, admPub)
cOwn.Invoke(t, stackitem.Null{}, method, id, groupId, admPub)
cOwn.Invoke(t, stackitem.Null{}, "addClientAdmin", id, groupId, admPub)
cOwn.Invoke(t, stackitem.Null{}, method, id, groupId, admPub)
}
func TestSubnet_AddUser(t *testing.T) {
e := newSubnetInvoker(t)
id, owner := createSubnet(t, e)
adm := e.NewAccount(t)
admPub, ok := vm.ParseSignatureContract(adm.Script())
require.True(t, ok)
user := randomBytes(27)
groupId := randomBytes(5)
const method = "addUser"
cOwn := e.WithSigners(owner)
cOwn.InvokeFail(t, subnet.ErrInvalidSubnetID, method, []byte{0, 0, 0, 0}, groupId, user)
cOwn.InvokeFail(t, subnet.ErrNotExist, method, []byte{0, 0, 0, 0, 0}, groupId, user)
cOwn.Invoke(t, stackitem.Null{}, "addClientAdmin", id, groupId, admPub)
cAdm := e.WithSigners(adm)
cAdm.Invoke(t, stackitem.Null{}, method, id, groupId, user)
cOwn.Invoke(t, stackitem.Null{}, method, id, groupId, user)
}
func TestSubnet_RemoveUser(t *testing.T) {
e := newSubnetInvoker(t)
id, owner := createSubnet(t, e)
groupId := randomBytes(5)
user := randomBytes(27)
adm := e.NewAccount(t)
admPub, ok := vm.ParseSignatureContract(adm.Script())
require.True(t, ok)
const method = "removeUser"
cOwn := e.WithSigners(owner)
cOwn.InvokeFail(t, subnet.ErrInvalidSubnetID, method, []byte{0, 0, 0, 0}, groupId, user)
cOwn.InvokeFail(t, subnet.ErrNotExist, method, []byte{0, 0, 0, 0, 0}, groupId, user)
cOwn.Invoke(t, stackitem.Null{}, method, id, groupId, user)
cOwn.Invoke(t, stackitem.Null{}, "addUser", id, groupId, user)
cOwn.Invoke(t, stackitem.Null{}, method, id, groupId, user)
cAdm := cOwn.WithSigners(adm)
cOwn.Invoke(t, stackitem.Null{}, "addClientAdmin", id, groupId, admPub)
cAdm.Invoke(t, stackitem.Null{}, method, id, groupId, user)
}
func TestSubnet_UserAllowed(t *testing.T) {
e := newSubnetInvoker(t)
id, owner := createSubnet(t, e)
groupId := randomBytes(5)
user := randomBytes(27)
const method = "userAllowed"
cOwn := e.WithSigners(owner)
cOwn.InvokeFail(t, subnet.ErrNotExist, method, []byte{0, 0, 0, 0, 0}, user)
cOwn.Invoke(t, stackitem.NewBool(false), method, id, user)
cOwn.Invoke(t, stackitem.Null{}, "addUser", id, groupId, user)
cOwn.Invoke(t, stackitem.NewBool(true), method, id, user)
}
func createSubnet(t *testing.T, e *neotest.ContractInvoker) (id []byte, owner neotest.Signer) {
var (
ok bool
pub []byte
)
owner = e.NewAccount(t)
pub, ok = vm.ParseSignatureContract(owner.Script())
require.True(t, ok)
id = make([]byte, 5)
binary.LittleEndian.PutUint32(id, 123)
info := randomBytes(10)
cBoth := e.WithSigners(e.Committee, owner)
cBoth.Invoke(t, stackitem.Null{}, "put", id, pub, info)
return
}