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:
parent
954c8ff8d6
commit
d81d826bfc
9 changed files with 67 additions and 30 deletions
|
@ -236,7 +236,11 @@ type Token struct {
|
|||
|
||||
func (t Token) AddToCirculation(amount int) bool {
|
||||
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
|
||||
storage.Put(ctx, "in_circ", inCirc)
|
||||
return true
|
||||
|
|
|
@ -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.
|
||||
func (t TokenConfig) InCirculation(ctx storage.Context) int {
|
||||
amount := storage.Get(ctx, t.CirculationKey)
|
||||
return amount.(int)
|
||||
return getIntFromDB(ctx, t.CirculationKey)
|
||||
}
|
||||
|
||||
// AddToCirculation sets the given amount as "in circulation" in the storage.
|
||||
func (t TokenConfig) AddToCirculation(ctx storage.Context, amount int) bool {
|
||||
supply := storage.Get(ctx, t.CirculationKey).(int)
|
||||
supply := getIntFromDB(ctx, t.CirculationKey)
|
||||
supply += amount
|
||||
storage.Put(ctx, t.CirculationKey, supply)
|
||||
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
|
||||
// to be distributed.
|
||||
func (t TokenConfig) TokenSaleAvailableAmount(ctx storage.Context) int {
|
||||
inCirc := storage.Get(ctx, t.CirculationKey)
|
||||
return t.TotalSupply - inCirc.(int)
|
||||
inCirc := getIntFromDB(ctx, t.CirculationKey)
|
||||
return t.TotalSupply - inCirc
|
||||
}
|
||||
|
||||
// Main smart contract entry point.
|
||||
|
@ -128,11 +138,11 @@ func handleOperation(op string, args []interface{}, ctx storage.Context, cfg Tok
|
|||
return cfg.Symbol
|
||||
}
|
||||
if op == "totalSupply" {
|
||||
return storage.Get(ctx, cfg.CirculationKey)
|
||||
return getIntFromDB(ctx, cfg.CirculationKey)
|
||||
}
|
||||
if op == "balanceOf" {
|
||||
if len(args) == 1 {
|
||||
return storage.Get(ctx, args[0].([]byte))
|
||||
return getIntFromDB(ctx, args[0].([]byte))
|
||||
}
|
||||
}
|
||||
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) {
|
||||
return false
|
||||
}
|
||||
amountFrom := storage.Get(ctx, from).(int)
|
||||
amountFrom := getIntFromDB(ctx, from)
|
||||
if amountFrom < amount {
|
||||
return false
|
||||
}
|
||||
|
@ -187,7 +197,7 @@ func transfer(cfg TokenConfig, ctx storage.Context, from, to []byte, amount int)
|
|||
diff := amountFrom - amount
|
||||
storage.Put(ctx, from, diff)
|
||||
}
|
||||
amountTo := storage.Get(ctx, to).(int)
|
||||
amountTo := getIntFromDB(ctx, to)
|
||||
totalAmountTo := amountTo + amount
|
||||
storage.Put(ctx, to, totalAmountTo)
|
||||
return true
|
||||
|
@ -201,15 +211,15 @@ func transferFrom(cfg TokenConfig, ctx storage.Context, from, to []byte, amount
|
|||
if len(availableKey) != 40 {
|
||||
return false
|
||||
}
|
||||
availableTo := storage.Get(ctx, availableKey).(int)
|
||||
availableTo := getIntFromDB(ctx, availableKey)
|
||||
if availableTo < amount {
|
||||
return false
|
||||
}
|
||||
fromBalance := storage.Get(ctx, from).(int)
|
||||
fromBalance := getIntFromDB(ctx, from)
|
||||
if fromBalance < amount {
|
||||
return false
|
||||
}
|
||||
toBalance := storage.Get(ctx, to).(int)
|
||||
toBalance := getIntFromDB(ctx, to)
|
||||
newFromBalance := fromBalance - amount
|
||||
newToBalance := toBalance + amount
|
||||
storage.Put(ctx, to, newToBalance)
|
||||
|
@ -231,7 +241,7 @@ func approve(ctx storage.Context, owner, spender []byte, amount int) bool {
|
|||
if len(spender) != 20 {
|
||||
return false
|
||||
}
|
||||
toSpend := storage.Get(ctx, owner).(int)
|
||||
toSpend := getIntFromDB(ctx, owner)
|
||||
if toSpend < amount {
|
||||
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 {
|
||||
key := append(from, to...)
|
||||
return storage.Get(ctx, key).(int)
|
||||
return getIntFromDB(ctx, key)
|
||||
}
|
||||
|
|
|
@ -22,14 +22,25 @@ type Token struct {
|
|||
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
|
||||
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
|
||||
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
|
||||
|
@ -48,7 +59,7 @@ func (t Token) Transfer(ctx storage.Context, from []byte, to []byte, amount int)
|
|||
storage.Put(ctx, from, diff)
|
||||
}
|
||||
|
||||
amountTo := storage.Get(ctx, to).(int)
|
||||
amountTo := getIntFromDB(ctx, to)
|
||||
totalAmountTo := amountTo + amount
|
||||
storage.Put(ctx, to, totalAmountTo)
|
||||
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
|
||||
}
|
||||
|
||||
amountFrom := storage.Get(ctx, from).(int)
|
||||
amountFrom := getIntFromDB(ctx, from)
|
||||
if amountFrom < amount {
|
||||
return -1
|
||||
}
|
||||
|
@ -98,8 +109,8 @@ func (t Token) Mint(ctx storage.Context, to []byte) bool {
|
|||
if !IsUsableAddress(t.Owner) {
|
||||
return false
|
||||
}
|
||||
minted := storage.Get(ctx, []byte("minted")).(bool)
|
||||
if minted {
|
||||
minted := storage.Get(ctx, []byte("minted"))
|
||||
if minted != nil && minted.(bool) == true {
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
|
@ -289,7 +289,7 @@ func storageGet(ic *interop.Context, v *vm.VM) error {
|
|||
if si != nil && si.Value != nil {
|
||||
v.Estack().PushVal(si.Value)
|
||||
} else {
|
||||
v.Estack().PushVal([]byte{})
|
||||
v.Estack().PushVal(stackitem.Null{})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -38,9 +38,9 @@ func GetReadOnlyContext() Context { return Context{} }
|
|||
func Put(ctx Context, key interface{}, value interface{}) {}
|
||||
|
||||
// Get retrieves value stored for the given key using given Context. See Put
|
||||
// documentation on possible key and value types. This function uses
|
||||
// `System.Storage.Get` syscall.
|
||||
func Get(ctx Context, key interface{}) interface{} { return 0 }
|
||||
// documentation on possible key and value types. If the value is not present in
|
||||
// the database it returns nil. This function uses `System.Storage.Get` syscall.
|
||||
func Get(ctx Context, key interface{}) interface{} { return nil }
|
||||
|
||||
// Delete removes key-value pair from storage by the given key using given
|
||||
// Context. See Put documentation on possible key types. This function uses
|
||||
|
|
|
@ -50,8 +50,8 @@ type rpcTestCase struct {
|
|||
check func(t *testing.T, e *executor, result interface{})
|
||||
}
|
||||
|
||||
const testContractHash = "e65ff7b3a02d207b584a5c27057d4e9862ef01da"
|
||||
const deploymentTxHash = "b0428600383ec7f7b06734978a24dbe81edc87b781f58c0614f122c735d8cf6a"
|
||||
const testContractHash = "10e262ef80c76bdecca287a2c047841fc02c3129"
|
||||
const deploymentTxHash = "ad8b149c799d4b2337162b0ad23e0ba8845cddb9cfca8a45587ee207015d2a7c"
|
||||
|
||||
var rpcTestCases = map[string][]rpcTestCase{
|
||||
"getapplicationlog": {
|
||||
|
|
BIN
pkg/rpc/server/testdata/test_contract.avm
vendored
BIN
pkg/rpc/server/testdata/test_contract.avm
vendored
Binary file not shown.
18
pkg/rpc/server/testdata/test_contract.go
vendored
18
pkg/rpc/server/testdata/test_contract.go
vendored
|
@ -32,7 +32,11 @@ func Main(operation string, args []interface{}) interface{} {
|
|||
runtime.Log("invalid address")
|
||||
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)
|
||||
return amount
|
||||
case "transfer":
|
||||
|
@ -53,7 +57,11 @@ func Main(operation string, args []interface{}) interface{} {
|
|||
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 {
|
||||
runtime.Log("insufficient funds")
|
||||
return false
|
||||
|
@ -61,7 +69,11 @@ func Main(operation string, args []interface{}) interface{} {
|
|||
fromBalance -= amount
|
||||
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
|
||||
storage.Put(ctx, to, toBalance)
|
||||
|
||||
|
|
BIN
pkg/rpc/server/testdata/testblocks.acc
vendored
BIN
pkg/rpc/server/testdata/testblocks.acc
vendored
Binary file not shown.
Loading…
Reference in a new issue