frostfs-contract/neofs/neofs_contract.go
Alex Vanin e850d4fc78 [#74] neofs: Remove unused code
All ballots and voting methods are gone. Multi signature
checks are used in all contracts.

Default global config values are also removed. Configuration
must be provided by initialization script.

Signed-off-by: Alex Vanin <alexey@nspcc.ru>
2021-05-05 12:27:24 +03:00

486 lines
12 KiB
Go

package smart_contract
/*
NeoFS Smart Contract for NEO3.0.
Utility methods, executed once in deploy stage:
- Init
- InitConfig
User related methods:
- Deposit
- Withdraw
- Bind
- Unbind
Inner ring list related methods:
- AlphabetList
- AlphabetAddress
- InnerRingCandidates
- InnerRingCandidateAdd
- InnerRingCandidateRemove
- AlphabetUpdate
Config methods:
- Config
- ListConfig
- SetConfig
Other utility methods:
- Migrate
- Version
- Cheque
*/
import (
"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/gas"
"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/runtime"
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
"github.com/nspcc-dev/neofs-contract/common"
)
type (
record struct {
key []byte
val []byte
}
)
const (
candidateFeeConfigKey = "InnerRingCandidateFee"
version = 3
alphabetKey = "alphabet"
candidatesKey = "candidates"
publicKeySize = 33
maxBalanceAmount = 9000 // Max integer of Fixed12 in JSON bound (2**53-1)
// hardcoded value to ignore deposit notification in onReceive
ignoreDepositNotification = "\x57\x0b"
)
var (
configPrefix = []byte("config")
)
// Init set up initial alphabet node keys.
func Init(owner interop.PublicKey, args []interop.PublicKey) bool {
ctx := storage.GetContext()
if !common.HasUpdateAccess(ctx) {
panic("only owner can reinitialize contract")
}
var irList []common.IRNode
if len(args) == 0 {
panic("neofs: at least one alphabet key must be provided")
}
for i := 0; i < len(args); i++ {
pub := args[i]
if len(pub) != publicKeySize {
panic("neofs: incorrect public key length")
}
irList = append(irList, common.IRNode{PublicKey: pub})
}
// initialize all storage slices
common.SetSerialized(ctx, alphabetKey, irList)
common.SetSerialized(ctx, candidatesKey, []common.IRNode{})
storage.Put(ctx, common.OwnerKey, owner)
runtime.Log("neofs: contract initialized")
return true
}
// Migrate updates smart contract execution script and manifest.
func Migrate(script []byte, manifest []byte) bool {
ctx := storage.GetReadOnlyContext()
if !common.HasUpdateAccess(ctx) {
runtime.Log("only owner can update contract")
return false
}
management.Update(script, manifest)
runtime.Log("neofs contract updated")
return true
}
// AlphabetList returns array of alphabet node keys.
func AlphabetList() []common.IRNode {
ctx := storage.GetReadOnlyContext()
return getNodes(ctx, alphabetKey)
}
// AlphabetAddress returns 2\3n+1 multi signature address of alphabet nodes.
func AlphabetAddress() interop.Hash160 {
ctx := storage.GetReadOnlyContext()
return multiaddress(getNodes(ctx, alphabetKey))
}
// InnerRingCandidates returns array of inner ring candidate node keys.
func InnerRingCandidates() []common.IRNode {
ctx := storage.GetReadOnlyContext()
return getNodes(ctx, candidatesKey)
}
// InnerRingCandidateRemove removes key from the list of inner ring candidates.
func InnerRingCandidateRemove(key interop.PublicKey) bool {
ctx := storage.GetContext()
multiaddr := AlphabetAddress()
if !runtime.CheckWitness(key) && !runtime.CheckWitness(multiaddr) {
panic("irCandidateRemove: this method must be invoked by candidate or alphabet")
}
nodes := []common.IRNode{} // it is explicit declaration of empty slice, not nil
candidates := getNodes(ctx, candidatesKey)
for i := range candidates {
c := candidates[i]
if !common.BytesEqual(c.PublicKey, key) {
nodes = append(nodes, c)
} else {
runtime.Log("irCandidateRemove: candidate has been removed")
}
}
common.SetSerialized(ctx, candidatesKey, nodes)
return true
}
// InnerRingCandidateAdd adds key to the list of inner ring candidates.
func InnerRingCandidateAdd(key interop.PublicKey) bool {
ctx := storage.GetContext()
if !runtime.CheckWitness(key) {
panic("irCandidateAdd: this method must be invoked by candidate")
}
c := common.IRNode{PublicKey: key}
candidates := getNodes(ctx, candidatesKey)
list, ok := addNode(candidates, c)
if !ok {
panic("irCandidateAdd: candidate already in the list")
}
from := contract.CreateStandardAccount(key)
to := runtime.GetExecutingScriptHash()
fee := getConfig(ctx, candidateFeeConfigKey).(int)
transferred := gas.Transfer(from, to, fee, []byte(ignoreDepositNotification))
if !transferred {
panic("irCandidateAdd: failed to transfer funds, aborting")
}
runtime.Log("irCandidateAdd: candidate has been added")
common.SetSerialized(ctx, candidatesKey, list)
return true
}
// OnNEP17Payment is a callback for NEP-17 compatible native GAS contract.
func OnNEP17Payment(from interop.Hash160, amount int, data interface{}) {
rcv := data.(interop.Hash160)
if common.BytesEqual(rcv, []byte(ignoreDepositNotification)) {
return
}
caller := runtime.GetCallingScriptHash()
if !common.BytesEqual(caller, interop.Hash160(gas.Hash)) {
panic("onNEP17Payment: only GAS can be accepted for deposit")
}
switch len(rcv) {
case 20:
case 0:
rcv = from
default:
panic("onNEP17Payment: invalid data argument, expected Hash160")
}
runtime.Log("onNEP17Payment: funds have been transferred")
tx := runtime.GetScriptContainer()
runtime.Notify("Deposit", from, amount, rcv, tx.Hash)
}
// Deposit gas assets to this script-hash address in NeoFS balance contract.
func Deposit(from interop.Hash160, amount int, rcv interop.Hash160) bool {
if !runtime.CheckWitness(from) {
panic("deposit: you should be the owner of the wallet")
}
if amount > maxBalanceAmount {
panic("deposit: out of max amount limit")
}
if amount <= 0 {
return false
}
amount = amount * 100000000
to := runtime.GetExecutingScriptHash()
transferred := gas.Transfer(from, to, amount, rcv)
if !transferred {
panic("deposit: failed to transfer funds, aborting")
}
return true
}
// Withdraw initialize gas asset withdraw from NeoFS balance.
func Withdraw(user []byte, amount int) bool {
if !runtime.CheckWitness(user) {
panic("withdraw: you should be the owner of the wallet")
}
if amount < 0 {
panic("withdraw: non positive amount number")
}
if amount > maxBalanceAmount {
panic("withdraw: out of max amount limit")
}
amount = amount * 100000000
tx := runtime.GetScriptContainer()
runtime.Notify("Withdraw", user, amount, tx.Hash)
return true
}
// Cheque sends gas assets back to the user if they were successfully
// locked in NeoFS balance contract.
func Cheque(id []byte, user interop.Hash160, amount int, lockAcc []byte) bool {
multiaddr := AlphabetAddress()
if !runtime.CheckWitness(multiaddr) {
panic("cheque: this method must be invoked by alphabet")
}
from := runtime.GetExecutingScriptHash()
transferred := gas.Transfer(from, user, amount, nil)
if !transferred {
panic("cheque: failed to transfer funds, aborting")
}
runtime.Log("cheque: funds have been transferred")
runtime.Notify("Cheque", id, user, amount, lockAcc)
return true
}
// Bind public key with user's account to use it in NeoFS requests.
func Bind(user []byte, keys []interop.PublicKey) bool {
if !runtime.CheckWitness(user) {
panic("binding: you should be the owner of the wallet")
}
for i := 0; i < len(keys); i++ {
pubKey := keys[i]
if len(pubKey) != publicKeySize {
panic("binding: incorrect public key size")
}
}
runtime.Notify("Bind", user, keys)
return true
}
// Unbind public key from user's account
func Unbind(user []byte, keys []interop.PublicKey) bool {
if !runtime.CheckWitness(user) {
panic("unbinding: you should be the owner of the wallet")
}
for i := 0; i < len(keys); i++ {
pubKey := keys[i]
if len(pubKey) != publicKeySize {
panic("unbinding: incorrect public key size")
}
}
runtime.Notify("Unbind", user, keys)
return true
}
// AlphabetUpdate updates list of alphabet nodes with provided list of
// public keys.
func AlphabetUpdate(chequeID []byte, args []interop.PublicKey) bool {
ctx := storage.GetContext()
if len(args) == 0 {
panic("alphabetUpdate: bad arguments")
}
multiaddr := AlphabetAddress()
if !runtime.CheckWitness(multiaddr) {
panic("alphabetUpdate: this method must be invoked by alphabet")
}
newAlphabet := []common.IRNode{}
for i := 0; i < len(args); i++ {
pubKey := args[i]
if len(pubKey) != publicKeySize {
panic("alphabetUpdate: invalid public key in alphabet list")
}
newAlphabet = append(newAlphabet, common.IRNode{
PublicKey: pubKey,
})
}
common.SetSerialized(ctx, alphabetKey, newAlphabet)
runtime.Notify("AlphabetUpdate", chequeID, newAlphabet)
runtime.Log("alphabetUpdate: alphabet list has been updated")
return true
}
// Config returns value of NeoFS configuration with provided key.
func Config(key []byte) interface{} {
ctx := storage.GetReadOnlyContext()
return getConfig(ctx, key)
}
// SetConfig key-value pair as a NeoFS runtime configuration value.
func SetConfig(id, key, val []byte) bool {
ctx := storage.GetContext()
multiaddr := AlphabetAddress()
if !runtime.CheckWitness(multiaddr) {
panic("setConfig: this method must be invoked by alphabet")
}
setConfig(ctx, key, val)
runtime.Notify("SetConfig", id, key, val)
runtime.Log("setConfig: configuration has been updated")
return true
}
// ListConfig returns array of all key-value pairs of NeoFS configuration.
func ListConfig() []record {
ctx := storage.GetReadOnlyContext()
var config []record
it := storage.Find(ctx, configPrefix, storage.None)
for iterator.Next(it) {
pair := iterator.Value(it).([]interface{})
key := pair[0].([]byte)
val := pair[1].([]byte)
r := record{key: key[len(configPrefix):], val: val}
config = append(config, r)
}
return config
}
// InitConfig set up initial NeoFS key-value configuration.
func InitConfig(args [][]byte) bool {
ctx := storage.GetContext()
if getConfig(ctx, candidateFeeConfigKey) != nil {
panic("neofs: configuration already installed")
}
ln := len(args)
if ln%2 != 0 {
panic("initConfig: bad arguments")
}
for i := 0; i < ln/2; i++ {
key := args[i*2]
val := args[i*2+1]
setConfig(ctx, key, val)
}
runtime.Log("neofs: config has been installed")
return true
}
// Version of contract.
func Version() int {
return version
}
// getNodes returns deserialized slice of nodes from storage.
func getNodes(ctx storage.Context, key string) []common.IRNode {
data := storage.Get(ctx, key)
if data != nil {
return std.Deserialize(data.([]byte)).([]common.IRNode)
}
return []common.IRNode{}
}
// getConfig returns installed neofs configuration value or nil if it is not set.
func getConfig(ctx storage.Context, key interface{}) interface{} {
postfix := key.([]byte)
storageKey := append(configPrefix, postfix...)
return storage.Get(ctx, storageKey)
}
// setConfig sets neofs configuration value in the contract storage.
func setConfig(ctx storage.Context, key, val interface{}) {
postfix := key.([]byte)
storageKey := append(configPrefix, postfix...)
storage.Put(ctx, storageKey, val)
}
// addNode returns slice of nodes with appended node 'n' and bool flag
// that set to false if node 'n' is already presented in the slice 'lst'.
func addNode(lst []common.IRNode, n common.IRNode) ([]common.IRNode, bool) {
for i := 0; i < len(lst); i++ {
if common.BytesEqual(n.PublicKey, lst[i].PublicKey) {
return nil, false
}
}
lst = append(lst, n)
return lst, true
}
// multiaddress returns multi signature address from list of IRNode structures
// with m = 2/3n+1.
func multiaddress(n []common.IRNode) []byte {
threshold := len(n)*2/3 + 1
keys := []interop.PublicKey{}
for _, node := range n {
key := node.PublicKey
keys = append(keys, key)
}
return contract.CreateMultisigAccount(threshold, keys)
}