core: fix Storage.Get to return Null when there is no value

Match C# implementation and fix state inconsistency at block 249920 of
preview2 testnet. Make our Go Storage.Get return nil and adapt
examples/tests.
This commit is contained in:
Roman Khimov 2020-06-24 00:06:42 +03:00
parent 954c8ff8d6
commit d81d826bfc
9 changed files with 67 additions and 30 deletions

View file

@ -236,7 +236,11 @@ type Token struct {
func (t Token) AddToCirculation(amount int) bool { func (t Token) AddToCirculation(amount int) bool {
ctx := storage.Context() ctx := storage.Context()
inCirc := storage.Get(ctx, "in_circ").(int) var inCirc int
val := storage.Get(ctx, "in_circ")
if val != nil {
inCirc = val.(int)
}
inCirc += amount inCirc += amount
storage.Put(ctx, "in_circ", inCirc) storage.Put(ctx, "in_circ", inCirc)
return true return true

View file

@ -71,15 +71,25 @@ func NewTokenConfig() TokenConfig {
} }
} }
// getIntFromDB is a helper that checks for nil result of storage.Get and returns
// zero as the default value.
func getIntFromDB(ctx storage.Context, key []byte) int {
var res int
val := storage.Get(ctx, key)
if val != nil {
res = val.(int)
}
return res
}
// InCirculation return the amount of total tokens that are in circulation. // InCirculation return the amount of total tokens that are in circulation.
func (t TokenConfig) InCirculation(ctx storage.Context) int { func (t TokenConfig) InCirculation(ctx storage.Context) int {
amount := storage.Get(ctx, t.CirculationKey) return getIntFromDB(ctx, t.CirculationKey)
return amount.(int)
} }
// AddToCirculation sets the given amount as "in circulation" in the storage. // AddToCirculation sets the given amount as "in circulation" in the storage.
func (t TokenConfig) AddToCirculation(ctx storage.Context, amount int) bool { func (t TokenConfig) AddToCirculation(ctx storage.Context, amount int) bool {
supply := storage.Get(ctx, t.CirculationKey).(int) supply := getIntFromDB(ctx, t.CirculationKey)
supply += amount supply += amount
storage.Put(ctx, t.CirculationKey, supply) storage.Put(ctx, t.CirculationKey, supply)
return true return true
@ -88,8 +98,8 @@ func (t TokenConfig) AddToCirculation(ctx storage.Context, amount int) bool {
// TokenSaleAvailableAmount returns the total amount of available tokens left // TokenSaleAvailableAmount returns the total amount of available tokens left
// to be distributed. // to be distributed.
func (t TokenConfig) TokenSaleAvailableAmount(ctx storage.Context) int { func (t TokenConfig) TokenSaleAvailableAmount(ctx storage.Context) int {
inCirc := storage.Get(ctx, t.CirculationKey) inCirc := getIntFromDB(ctx, t.CirculationKey)
return t.TotalSupply - inCirc.(int) return t.TotalSupply - inCirc
} }
// Main smart contract entry point. // Main smart contract entry point.
@ -128,11 +138,11 @@ func handleOperation(op string, args []interface{}, ctx storage.Context, cfg Tok
return cfg.Symbol return cfg.Symbol
} }
if op == "totalSupply" { if op == "totalSupply" {
return storage.Get(ctx, cfg.CirculationKey) return getIntFromDB(ctx, cfg.CirculationKey)
} }
if op == "balanceOf" { if op == "balanceOf" {
if len(args) == 1 { if len(args) == 1 {
return storage.Get(ctx, args[0].([]byte)) return getIntFromDB(ctx, args[0].([]byte))
} }
} }
if op == "transfer" { if op == "transfer" {
@ -177,7 +187,7 @@ func transfer(cfg TokenConfig, ctx storage.Context, from, to []byte, amount int)
if amount <= 0 || len(to) != 20 || !runtime.CheckWitness(from) { if amount <= 0 || len(to) != 20 || !runtime.CheckWitness(from) {
return false return false
} }
amountFrom := storage.Get(ctx, from).(int) amountFrom := getIntFromDB(ctx, from)
if amountFrom < amount { if amountFrom < amount {
return false return false
} }
@ -187,7 +197,7 @@ func transfer(cfg TokenConfig, ctx storage.Context, from, to []byte, amount int)
diff := amountFrom - amount diff := amountFrom - amount
storage.Put(ctx, from, diff) storage.Put(ctx, from, diff)
} }
amountTo := storage.Get(ctx, to).(int) amountTo := getIntFromDB(ctx, to)
totalAmountTo := amountTo + amount totalAmountTo := amountTo + amount
storage.Put(ctx, to, totalAmountTo) storage.Put(ctx, to, totalAmountTo)
return true return true
@ -201,15 +211,15 @@ func transferFrom(cfg TokenConfig, ctx storage.Context, from, to []byte, amount
if len(availableKey) != 40 { if len(availableKey) != 40 {
return false return false
} }
availableTo := storage.Get(ctx, availableKey).(int) availableTo := getIntFromDB(ctx, availableKey)
if availableTo < amount { if availableTo < amount {
return false return false
} }
fromBalance := storage.Get(ctx, from).(int) fromBalance := getIntFromDB(ctx, from)
if fromBalance < amount { if fromBalance < amount {
return false return false
} }
toBalance := storage.Get(ctx, to).(int) toBalance := getIntFromDB(ctx, to)
newFromBalance := fromBalance - amount newFromBalance := fromBalance - amount
newToBalance := toBalance + amount newToBalance := toBalance + amount
storage.Put(ctx, to, newToBalance) storage.Put(ctx, to, newToBalance)
@ -231,7 +241,7 @@ func approve(ctx storage.Context, owner, spender []byte, amount int) bool {
if len(spender) != 20 { if len(spender) != 20 {
return false return false
} }
toSpend := storage.Get(ctx, owner).(int) toSpend := getIntFromDB(ctx, owner)
if toSpend < amount { if toSpend < amount {
return false return false
} }
@ -246,5 +256,5 @@ func approve(ctx storage.Context, owner, spender []byte, amount int) bool {
func allowance(ctx storage.Context, from, to []byte) int { func allowance(ctx storage.Context, from, to []byte) int {
key := append(from, to...) key := append(from, to...)
return storage.Get(ctx, key).(int) return getIntFromDB(ctx, key)
} }

View file

@ -22,14 +22,25 @@ type Token struct {
CirculationKey string CirculationKey string
} }
// getIntFromDB is a helper that checks for nil result of storage.Get and returns
// zero as the default value.
func getIntFromDB(ctx storage.Context, key []byte) int {
var res int
val := storage.Get(ctx, key)
if val != nil {
res = val.(int)
}
return res
}
// GetSupply gets the token totalSupply value from VM storage // GetSupply gets the token totalSupply value from VM storage
func (t Token) GetSupply(ctx storage.Context) interface{} { func (t Token) GetSupply(ctx storage.Context) interface{} {
return storage.Get(ctx, t.CirculationKey) return getIntFromDB(ctx, []byte(t.CirculationKey))
} }
// BalanceOf gets the token balance of a specific address // BalanceOf gets the token balance of a specific address
func (t Token) BalanceOf(ctx storage.Context, hodler []byte) interface{} { func (t Token) BalanceOf(ctx storage.Context, hodler []byte) interface{} {
return storage.Get(ctx, hodler) return getIntFromDB(ctx, hodler)
} }
// Transfer token from one user to another // Transfer token from one user to another
@ -48,7 +59,7 @@ func (t Token) Transfer(ctx storage.Context, from []byte, to []byte, amount int)
storage.Put(ctx, from, diff) storage.Put(ctx, from, diff)
} }
amountTo := storage.Get(ctx, to).(int) amountTo := getIntFromDB(ctx, to)
totalAmountTo := amountTo + amount totalAmountTo := amountTo + amount
storage.Put(ctx, to, totalAmountTo) storage.Put(ctx, to, totalAmountTo)
runtime.Notify("transfer", from, to, amount) runtime.Notify("transfer", from, to, amount)
@ -61,7 +72,7 @@ func (t Token) CanTransfer(ctx storage.Context, from []byte, to []byte, amount i
return -1 return -1
} }
amountFrom := storage.Get(ctx, from).(int) amountFrom := getIntFromDB(ctx, from)
if amountFrom < amount { if amountFrom < amount {
return -1 return -1
} }
@ -98,8 +109,8 @@ func (t Token) Mint(ctx storage.Context, to []byte) bool {
if !IsUsableAddress(t.Owner) { if !IsUsableAddress(t.Owner) {
return false return false
} }
minted := storage.Get(ctx, []byte("minted")).(bool) minted := storage.Get(ctx, []byte("minted"))
if minted { if minted != nil && minted.(bool) == true {
return false return false
} }

View file

@ -289,7 +289,7 @@ func storageGet(ic *interop.Context, v *vm.VM) error {
if si != nil && si.Value != nil { if si != nil && si.Value != nil {
v.Estack().PushVal(si.Value) v.Estack().PushVal(si.Value)
} else { } else {
v.Estack().PushVal([]byte{}) v.Estack().PushVal(stackitem.Null{})
} }
return nil return nil
} }

View file

@ -38,9 +38,9 @@ func GetReadOnlyContext() Context { return Context{} }
func Put(ctx Context, key interface{}, value interface{}) {} func Put(ctx Context, key interface{}, value interface{}) {}
// Get retrieves value stored for the given key using given Context. See Put // Get retrieves value stored for the given key using given Context. See Put
// documentation on possible key and value types. This function uses // documentation on possible key and value types. If the value is not present in
// `System.Storage.Get` syscall. // the database it returns nil. This function uses `System.Storage.Get` syscall.
func Get(ctx Context, key interface{}) interface{} { return 0 } func Get(ctx Context, key interface{}) interface{} { return nil }
// Delete removes key-value pair from storage by the given key using given // Delete removes key-value pair from storage by the given key using given
// Context. See Put documentation on possible key types. This function uses // Context. See Put documentation on possible key types. This function uses

View file

@ -50,8 +50,8 @@ type rpcTestCase struct {
check func(t *testing.T, e *executor, result interface{}) check func(t *testing.T, e *executor, result interface{})
} }
const testContractHash = "e65ff7b3a02d207b584a5c27057d4e9862ef01da" const testContractHash = "10e262ef80c76bdecca287a2c047841fc02c3129"
const deploymentTxHash = "b0428600383ec7f7b06734978a24dbe81edc87b781f58c0614f122c735d8cf6a" const deploymentTxHash = "ad8b149c799d4b2337162b0ad23e0ba8845cddb9cfca8a45587ee207015d2a7c"
var rpcTestCases = map[string][]rpcTestCase{ var rpcTestCases = map[string][]rpcTestCase{
"getapplicationlog": { "getapplicationlog": {

Binary file not shown.

View file

@ -32,7 +32,11 @@ func Main(operation string, args []interface{}) interface{} {
runtime.Log("invalid address") runtime.Log("invalid address")
return false return false
} }
amount := storage.Get(ctx, addr).(int) var amount int
val := storage.Get(ctx, addr)
if val != nil {
amount = val.(int)
}
runtime.Notify("balanceOf", addr, amount) runtime.Notify("balanceOf", addr, amount)
return amount return amount
case "transfer": case "transfer":
@ -53,7 +57,11 @@ func Main(operation string, args []interface{}) interface{} {
return false return false
} }
fromBalance := storage.Get(ctx, from).(int) var fromBalance int
val := storage.Get(ctx, from)
if val != nil {
fromBalance = val.(int)
}
if fromBalance < amount { if fromBalance < amount {
runtime.Log("insufficient funds") runtime.Log("insufficient funds")
return false return false
@ -61,7 +69,11 @@ func Main(operation string, args []interface{}) interface{} {
fromBalance -= amount fromBalance -= amount
storage.Put(ctx, from, fromBalance) storage.Put(ctx, from, fromBalance)
toBalance := storage.Get(ctx, to).(int) var toBalance int
val = storage.Get(ctx, to)
if val != nil {
toBalance = val.(int)
}
toBalance += amount toBalance += amount
storage.Put(ctx, to, toBalance) storage.Put(ctx, to, toBalance)

Binary file not shown.