[#51] balance: Support notary contract

Signed-off-by: Alex Vanin <alexey@nspcc.ru>
enable-notary-in-public-chains
Alex Vanin 2021-02-19 18:27:25 +03:00 committed by Alex Vanin
parent 299c888266
commit c6816193d3
1 changed files with 64 additions and 160 deletions

View File

@ -113,181 +113,115 @@ 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 {
var ( multiaddr := common.InnerRingMultiAddressViaStorage(ctx, netmapContractKey)
n int // number of votes for inner ring invoke if !runtime.CheckWitness(multiaddr) {
hashTxID []byte // ballot key of the inner ring invocation
)
innerRing := irList()
threshold := len(innerRing)/3*2 + 1
irKey := common.InnerRingInvoker(innerRing)
if len(irKey) == 0 {
panic("transferX: this method must be invoked from inner ring") panic("transferX: this method must be invoked from inner ring")
} }
fromKnownContract := fromKnownContract(runtime.GetCallingScriptHash()) result := token.transfer(ctx, from, to, amount, true, details)
if fromKnownContract { if result {
n = threshold runtime.Log("transferX: success")
runtime.Log("transferX: processed indirect invoke")
} else { } else {
hashTxID = common.InvokeID([]interface{}{from, to, amount}, []byte("transfer")) // consider panic there
n = common.Vote(ctx, hashTxID, irKey) runtime.Log("transferX: fail")
} }
if n >= threshold { return result
common.RemoveVotes(ctx, hashTxID)
result := token.transfer(ctx, from, to, amount, true, details)
if result {
runtime.Log("transferX: success")
} else {
// consider panic there
runtime.Log("transferX: fail")
}
return result
}
return false
} }
func Lock(txID []byte, from, to interop.Hash160, amount, until int) bool { func Lock(txID []byte, from, to interop.Hash160, amount, until int) bool {
innerRing := irList() multiaddr := common.InnerRingMultiAddressViaStorage(ctx, netmapContractKey)
threshold := len(innerRing)/3*2 + 1 if !runtime.CheckWitness(multiaddr) {
irKey := common.InnerRingInvoker(innerRing)
if len(irKey) == 0 {
panic("lock: this method must be invoked from inner ring") panic("lock: this method must be invoked from inner ring")
} }
hashTxID := common.InvokeID([]interface{}{txID, from, to, amount, until}, []byte("lock")) lockAccount := Account{
Balance: 0,
Until: until,
Parent: from,
}
common.SetSerialized(ctx, to, lockAccount)
n := common.Vote(ctx, hashTxID, irKey) result := token.transfer(ctx, from, to, amount, true, lockTransferMsg)
if n >= threshold { if !result {
common.RemoveVotes(ctx, hashTxID) // consider using `return false` to remove votes
panic("lock: can't lock funds")
lockAccount := Account{
Balance: 0,
Until: until,
Parent: from,
}
common.SetSerialized(ctx, to, lockAccount)
result := token.transfer(ctx, from, to, amount, true, lockTransferMsg)
if !result {
// consider using `return false` to remove votes
panic("lock: can't lock funds")
}
runtime.Log("lock: created lock account")
runtime.Notify("Lock", txID, from, to, amount, until)
} }
runtime.Log("lock: processed invoke from inner ring") runtime.Log("lock: created lock account")
runtime.Notify("Lock", txID, from, to, amount, until)
return true return true
} }
func NewEpoch(epochNum int) bool { func NewEpoch(epochNum int) bool {
innerRing := irList() multiaddr := common.InnerRingMultiAddressViaStorage(ctx, netmapContractKey)
threshold := len(innerRing)/3*2 + 1 if !runtime.CheckWitness(multiaddr) {
irKey := common.InnerRingInvoker(innerRing)
if len(irKey) == 0 {
panic("epochNum: this method must be invoked from inner ring") panic("epochNum: this method must be invoked from inner ring")
} }
epochID := common.InvokeID([]interface{}{epochNum}, []byte("epoch")) it := storage.Find(ctx, []byte{}, storage.KeysOnly)
for iterator.Next(it) {
addr := iterator.Value(it).([]byte) // it MUST BE `storage.KeysOnly`
if len(addr) != 20 {
continue
}
n := common.Vote(ctx, epochID, irKey) acc := getAccount(ctx, addr)
if n >= threshold { if acc.Until == 0 {
common.RemoveVotes(ctx, epochID) continue
it := storage.Find(ctx, []byte{}, storage.KeysOnly) }
for iterator.Next(it) {
addr := iterator.Value(it).([]byte) // it MUST BE `storage.KeysOnly`
if len(addr) != 20 {
continue
}
acc := getAccount(ctx, addr) if epochNum >= acc.Until {
if acc.Until == 0 { // return assets back to the parent
continue token.transfer(ctx, addr, acc.Parent, acc.Balance, true, unlockTransferMsg)
}
if epochNum >= acc.Until {
// return assets back to the parent
token.transfer(ctx, addr, acc.Parent, acc.Balance, true, unlockTransferMsg)
}
} }
} }
runtime.Log("newEpoch: processed invoke from inner ring")
return true return true
} }
func Mint(to interop.Hash160, amount int, details []byte) bool { func Mint(to interop.Hash160, amount int, details []byte) bool {
innerRing := irList() multiaddr := common.InnerRingMultiAddressViaStorage(ctx, netmapContractKey)
threshold := len(innerRing)/3*2 + 1 if !runtime.CheckWitness(multiaddr) {
panic("mint: this method must be invoked from inner ring")
irKey := common.InnerRingInvoker(innerRing)
if len(irKey) == 0 {
panic("burn: this method must be invoked from inner ring")
} }
mintID := common.InvokeID([]interface{}{to, amount, details}, []byte("mint")) ok := token.transfer(ctx, nil, to, amount, true, details)
if !ok {
n := common.Vote(ctx, mintID, irKey) panic("mint: can't transfer assets")
if n >= threshold {
common.RemoveVotes(ctx, mintID)
ok := token.transfer(ctx, nil, to, amount, true, details)
if !ok {
panic("mint: can't transfer assets")
}
supply := token.getSupply(ctx)
supply = supply + amount
storage.Put(ctx, token.CirculationKey, supply)
runtime.Log("mint: assets were minted")
runtime.Notify("Mint", to, amount)
} }
supply := token.getSupply(ctx)
supply = supply + amount
storage.Put(ctx, token.CirculationKey, supply)
runtime.Log("mint: assets were minted")
runtime.Notify("Mint", to, amount)
return true return true
} }
func Burn(from interop.Hash160, amount int, details []byte) bool { func Burn(from interop.Hash160, amount int, details []byte) bool {
innerRing := irList() multiaddr := common.InnerRingMultiAddressViaStorage(ctx, netmapContractKey)
threshold := len(innerRing)/3*2 + 1 if !runtime.CheckWitness(multiaddr) {
irKey := common.InnerRingInvoker(innerRing)
if len(irKey) == 0 {
panic("burn: this method must be invoked from inner ring") panic("burn: this method must be invoked from inner ring")
} }
burnID := common.InvokeID([]interface{}{from, amount, details}, []byte("burn")) ok := token.transfer(ctx, from, nil, amount, true, details)
if !ok {
n := common.Vote(ctx, burnID, irKey) panic("burn: can't transfer assets")
if n >= threshold {
common.RemoveVotes(ctx, burnID)
ok := token.transfer(ctx, from, nil, amount, true, details)
if !ok {
panic("burn: can't transfer assets")
}
supply := token.getSupply(ctx)
if supply < amount {
panic("panic, negative supply after burn")
}
supply = supply - amount
storage.Put(ctx, token.CirculationKey, supply)
runtime.Log("burn: assets were burned")
runtime.Notify("Burn", from, amount)
} }
supply := token.getSupply(ctx)
if supply < amount {
panic("panic, negative supply after burn")
}
supply = supply - amount
storage.Put(ctx, token.CirculationKey, supply)
runtime.Log("burn: assets were burned")
runtime.Notify("Burn", from, amount)
return true return true
} }
@ -389,33 +323,3 @@ func getAccount(ctx storage.Context, key interface{}) Account {
return Account{} return Account{}
} }
/*
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(caller interop.Hash160) bool {
containerContractAddr := storage.Get(ctx, containerContractKey).([]byte)
return common.BytesEqual(caller, containerContractAddr)
}
func irList() []common.IRNode {
return common.InnerRingListViaStorage(ctx, netmapContractKey)
}