Update withdraw mechanism

Withdraw operation works a bit different with neo:morph.
User calls "Deposit" and "Withdraw" methods that produce
notifications for the inner ring nodes.

"Deposit" method does not produce feedback, but "Withdraw"
does. Inner ring nodes check if user eligible to withdraw
assets. If so, nodes invoke "Cheque" method. This method
collects invocations from inner ring nods. When there are
2/3n + 1 invocations, smart-contract transfers assets back
to the user and produces notifications.
This commit is contained in:
alexvanin 2020-05-28 17:04:32 +03:00
parent 88e73a04d7
commit cb00b6133e

View file

@ -20,7 +20,6 @@ type (
check struct {
id []byte
height []byte
}
)
@ -148,39 +147,74 @@ func Main(op string, args []interface{}) interface{} {
runtime.Log("funds have been transferred")
var rcv = []byte{}
if len(args) == 3 {
rcv = args[2].([]byte) // todo: check if rcv value is valid
}
runtime.Notify("Deposit", pk, amount, rcv)
return true
case "Withdraw":
from := engine.GetExecutingScriptHash()
data := args[0].([]byte)
message := data[0:66]
uuid := data[0:25]
owner := data[25:50]
value := data[50:58]
height := data[58:66]
offset := 68
usedList := getSerialized(ctx, "UsedVerifCheckList").([]check)
c := check{
id: uuid,
height: height,
}
if containsCheck(usedList, c) {
panic("verification check has already been used")
if len(args) != 2 {
panic("withdraw: bad arguments")
}
user := args[0].([]byte)
if !runtime.CheckWitness(user) {
// todo: consider something different with neoID
panic("withdraw: you should be the owner of the account")
}
amount := args[1].(int)
if amount > 0 {
amount = amount * 100000000
}
runtime.Notify("Withdraw", user, amount)
return true
case "Cheque":
if len(args) != 4 {
panic("cheque: bad arguments")
}
id := args[0].([]byte) // unique cheque id
user := args[1].([]byte) // GAS receiver
amount := args[2].(int) // amount of GAS
lockAcc := args[3].([]byte) // lock account from internal banking that must be cashed out
ctx := storage.GetContext()
hashID := crypto.Hash256(id)
irList := getSerialized(ctx, "InnerRingList").([]node)
if !verifySignatures(irList, data, message, offset) {
panic("can't verify signatures")
usedList := getSerialized(ctx, "UsedVerifCheckList").([]check)
threshold := len(irList)/3*2 + 1
if !isInnerRingRequest(irList) {
panic("cheque: invoked by non inner ring node")
}
h := owner[1:21]
params := []interface{}{from, h, value}
c := check{id: id} // todo: use different cheque id for inner ring update and withdraw
if containsCheck(usedList, c) {
panic("cheque: non unique id")
}
n := vote(ctx, hashID)
if n >= threshold {
removeVotes(ctx, hashID)
from := engine.GetExecutingScriptHash()
params := []interface{}{from, user, amount}
transferred := engine.AppCall([]byte(tokenHash), "transfer", params).(bool)
if !transferred {
panic("failed to transfer funds, aborting")
panic("cheque: failed to transfer funds, aborting")
}
putSerialized(ctx, "UsedVerifCheckList", c)
runtime.Notify("Cheque", id, user, amount, lockAcc)
}
return true
case "InnerRingUpdate":
@ -387,41 +421,6 @@ func delSerializedIR(ctx storage.Context, key string, value []byte) bool {
runtime.Log("target element has not been removed")
return false
}
func verifySignatures(irList []node, data []byte, message []byte, offset int) bool {
n := len(irList)
f := (n - 1) / 3
s := n - f
if s < 3 {
runtime.Log("not enough inner ring nodes for consensus")
return false
}
used := [][]byte{}
count := 0
for count < s && offset < len(data) {
pubkey := data[offset : offset+33]
signature := data[offset+33 : offset+97]
if containsPub(irList, pubkey) {
if crypto.VerifySignature(message, signature, pubkey) {
count++
for i := 0; i < len(used); i++ {
if util.Equals(used[i], pubkey) {
panic("duplicate public keys")
}
}
used = append(used, pubkey)
}
}
offset += 97
}
if count >= s {
return true
}
runtime.Log("not enough verified signatures")
return false
}
// isInnerRingRequest returns true if contract was invoked by inner ring node.
func isInnerRingRequest(irList []node) bool {