[#74] balance: Support notary disabled work flow

Signed-off-by: Alex Vanin <alexey@nspcc.ru>
This commit is contained in:
Alex Vanin 2021-04-29 16:12:41 +03:00 committed by Alex Vanin
parent 07617dd083
commit 6e8bef671a
2 changed files with 166 additions and 17 deletions

View file

@ -126,10 +126,43 @@ func Transfer(from, to interop.Hash160, amount int, data interface{}) bool {
func TransferX(from, to interop.Hash160, amount int, details []byte) bool { func TransferX(from, to interop.Hash160, amount int, details []byte) bool {
ctx := storage.GetContext() ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
multiaddr := common.AlphabetAddress() var ( // for invocation collection without notary
if !runtime.CheckWitness(multiaddr) { alphabet []common.IRNode
panic("transferX: this method must be invoked from inner ring") nodeKey []byte
inderectCall bool
)
if notaryDisabled {
alphabet = common.AlphabetNodes()
nodeKey = common.InnerRingInvoker(alphabet)
if len(nodeKey) == 0 {
panic("transferX: this method must be invoked from inner ring")
}
inderectCall = common.FromKnownContract(
ctx,
runtime.GetCallingScriptHash(),
containerContractKey,
)
} else {
multiaddr := common.AlphabetAddress()
if !runtime.CheckWitness(multiaddr) {
panic("transferX: this method must be invoked from inner ring")
}
}
if notaryDisabled && !inderectCall {
threshold := len(alphabet)*2/3 + 1
id := common.InvokeID([]interface{}{from, to, amount}, []byte("transfer"))
n := common.Vote(ctx, id, nodeKey)
if n < threshold {
return true
}
common.RemoveVotes(ctx, id)
} }
result := token.transfer(ctx, from, to, amount, true, details) result := token.transfer(ctx, from, to, amount, true, details)
@ -145,20 +178,47 @@ func TransferX(from, to interop.Hash160, amount int, details []byte) bool {
func Lock(txDetails []byte, from, to interop.Hash160, amount, until int) bool { func Lock(txDetails []byte, from, to interop.Hash160, amount, until int) bool {
ctx := storage.GetContext() ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
multiaddr := common.AlphabetAddress() var ( // for invocation collection without notary
if !runtime.CheckWitness(multiaddr) { alphabet []common.IRNode
panic("lock: this method must be invoked from inner ring") nodeKey []byte
)
if notaryDisabled {
alphabet = common.AlphabetNodes()
nodeKey = common.InnerRingInvoker(alphabet)
if len(nodeKey) == 0 {
panic("lock: this method must be invoked from inner ring")
}
} else {
multiaddr := common.AlphabetAddress()
if !runtime.CheckWitness(multiaddr) {
panic("lock: this method must be invoked from inner ring")
}
} }
details := common.LockTransferDetails(txDetails)
lockAccount := Account{ lockAccount := Account{
Balance: 0, Balance: 0,
Until: until, Until: until,
Parent: from, Parent: from,
} }
common.SetSerialized(ctx, to, lockAccount)
details := common.LockTransferDetails(txDetails) if notaryDisabled {
threshold := len(alphabet)*2/3 + 1
id := common.InvokeID([]interface{}{txDetails}, []byte("lock"))
n := common.Vote(ctx, id, nodeKey)
if n < threshold {
return true
}
common.RemoveVotes(ctx, id)
}
common.SetSerialized(ctx, to, lockAccount)
result := token.transfer(ctx, from, to, amount, true, details) result := token.transfer(ctx, from, to, amount, true, details)
if !result { if !result {
@ -174,10 +234,22 @@ func Lock(txDetails []byte, from, to interop.Hash160, amount, until int) bool {
func NewEpoch(epochNum int) bool { func NewEpoch(epochNum int) bool {
ctx := storage.GetContext() ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
multiaddr := common.AlphabetAddress() if notaryDisabled {
if !runtime.CheckWitness(multiaddr) { indirectCall := common.FromKnownContract(
panic("newEpoch: this method must be invoked from inner ring") ctx,
runtime.GetCallingScriptHash(),
netmapContractKey,
)
if !indirectCall {
panic("newEpoch: this method must be invoked from inner ring")
}
} else {
multiaddr := common.AlphabetAddress()
if !runtime.CheckWitness(multiaddr) {
panic("newEpoch: this method must be invoked from inner ring")
}
} }
it := storage.Find(ctx, []byte{}, storage.KeysOnly) it := storage.Find(ctx, []byte{}, storage.KeysOnly)
@ -204,14 +276,40 @@ func NewEpoch(epochNum int) bool {
func Mint(to interop.Hash160, amount int, txDetails []byte) bool { func Mint(to interop.Hash160, amount int, txDetails []byte) bool {
ctx := storage.GetContext() ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
multiaddr := common.AlphabetAddress() var ( // for invocation collection without notary
if !runtime.CheckWitness(multiaddr) { alphabet []common.IRNode
panic("mint: this method must be invoked from inner ring") nodeKey []byte
)
if notaryDisabled {
alphabet = common.AlphabetNodes()
nodeKey = common.InnerRingInvoker(alphabet)
if len(nodeKey) == 0 {
panic("mint: this method must be invoked from inner ring")
}
} else {
multiaddr := common.AlphabetAddress()
if !runtime.CheckWitness(multiaddr) {
panic("mint: this method must be invoked from inner ring")
}
} }
details := common.MintTransferDetails(txDetails) details := common.MintTransferDetails(txDetails)
if notaryDisabled {
threshold := len(alphabet)*2/3 + 1
id := common.InvokeID([]interface{}{txDetails}, []byte("mint"))
n := common.Vote(ctx, id, nodeKey)
if n < threshold {
return true
}
common.RemoveVotes(ctx, id)
}
ok := token.transfer(ctx, nil, to, amount, true, details) ok := token.transfer(ctx, nil, to, amount, true, details)
if !ok { if !ok {
panic("mint: can't transfer assets") panic("mint: can't transfer assets")
@ -228,14 +326,40 @@ func Mint(to interop.Hash160, amount int, txDetails []byte) bool {
func Burn(from interop.Hash160, amount int, txDetails []byte) bool { func Burn(from interop.Hash160, amount int, txDetails []byte) bool {
ctx := storage.GetContext() ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
multiaddr := common.AlphabetAddress() var ( // for invocation collection without notary
if !runtime.CheckWitness(multiaddr) { alphabet []common.IRNode
panic("burn: this method must be invoked from inner ring") nodeKey []byte
)
if notaryDisabled {
alphabet = common.AlphabetNodes()
nodeKey = common.InnerRingInvoker(alphabet)
if len(nodeKey) == 0 {
panic("burn: this method must be invoked from inner ring")
}
} else {
multiaddr := common.AlphabetAddress()
if !runtime.CheckWitness(multiaddr) {
panic("burn: this method must be invoked from inner ring")
}
} }
details := common.BurnTransferDetails(txDetails) details := common.BurnTransferDetails(txDetails)
if notaryDisabled {
threshold := len(alphabet)*2/3 + 1
id := common.InvokeID([]interface{}{txDetails}, []byte("burn"))
n := common.Vote(ctx, id, nodeKey)
if n < threshold {
return true
}
common.RemoveVotes(ctx, id)
}
ok := token.transfer(ctx, from, nil, amount, true, details) ok := token.transfer(ctx, from, nil, amount, true, details)
if !ok { if !ok {
panic("burn: can't transfer assets") panic("burn: can't transfer assets")

View file

@ -120,3 +120,28 @@ func InvokeID(args []interface{}, prefix []byte) []byte {
return crypto.Sha256(prefix) return crypto.Sha256(prefix)
} }
/*
Check if invocation made from known container or audit contracts.
This is necessary because calls from these contracts require to do transfer
without signature collection (1 invoke transfer).
IR1, IR2, IR3, IR4 -(4 invokes)-> [ Container Contract ] -(1 invoke)-> [ Balance Contract ]
We can do 1 invoke transfer if:
- invoke happened from inner ring node,
- it is indirect invocation from other smart-contract.
However there is a possible attack, when malicious inner ring node creates
malicious smart-contract in morph chain to do inderect call.
MaliciousIR -(1 invoke)-> [ Malicious Contract ] -(1 invoke) -> [ Balance Contract ]
To prevent that, we have to allow 1 invoke transfer from authorised well known
smart-contracts, that will be set up at `Init` method.
*/
func FromKnownContract(ctx storage.Context, caller interop.Hash160, key string) bool {
addr := storage.Get(ctx, key).(interop.Hash160)
return BytesEqual(caller, addr)
}