native: move required balance check to token contracts

Which duplicates the check, but deduplicates error path. This check forced
double balance deserialization which is quite costly operation, so we better
do it once.

It's hardly noticeable as of TPS metrics though, maybe some 1-2%%.
This commit is contained in:
Roman Khimov 2021-08-02 23:16:12 +03:00
parent 85936de254
commit 5c65d33439
3 changed files with 13 additions and 26 deletions

View file

@ -54,13 +54,17 @@ func newGAS(init int64) *GAS {
return g return g
} }
func (g *GAS) increaseBalance(_ *interop.Context, _ util.Uint160, si *state.StorageItem, amount *big.Int) error { func (g *GAS) increaseBalance(_ *interop.Context, _ util.Uint160, si *state.StorageItem, amount *big.Int, checkBal *big.Int) error {
acc, err := state.NEP17BalanceFromBytes(*si) acc, err := state.NEP17BalanceFromBytes(*si)
if err != nil { if err != nil {
return err return err
} }
if sign := amount.Sign(); sign == 0 { if sign := amount.Sign(); sign == 0 {
return nil // Requested self-transfer amount can be higher than actual balance.
if checkBal != nil && acc.Balance.Cmp(checkBal) < 0 {
err = errors.New("insufficient funds")
}
return err
} else if sign == -1 && acc.Balance.Cmp(new(big.Int).Neg(amount)) == -1 { } else if sign == -1 && acc.Balance.Cmp(new(big.Int).Neg(amount)) == -1 {
return errors.New("insufficient funds") return errors.New("insufficient funds")
} }

View file

@ -391,12 +391,13 @@ func (n *NEO) getGASPerVote(d dao.DAO, key []byte, index ...uint32) []big.Int {
return reward return reward
} }
func (n *NEO) increaseBalance(ic *interop.Context, h util.Uint160, si *state.StorageItem, amount *big.Int) error { func (n *NEO) increaseBalance(ic *interop.Context, h util.Uint160, si *state.StorageItem, amount *big.Int, checkBal *big.Int) error {
acc, err := state.NEOBalanceFromBytes(*si) acc, err := state.NEOBalanceFromBytes(*si)
if err != nil { if err != nil {
return err return err
} }
if amount.Sign() == -1 && acc.Balance.Cmp(new(big.Int).Neg(amount)) == -1 { if (amount.Sign() == -1 && acc.Balance.Cmp(new(big.Int).Neg(amount)) == -1) ||
(amount.Sign() == 0 && checkBal != nil && acc.Balance.Cmp(checkBal) == -1) {
return errors.New("insufficient funds") return errors.New("insufficient funds")
} }
if err := n.distributeGas(ic, h, acc); err != nil { if err := n.distributeGas(ic, h, acc); err != nil {

View file

@ -33,7 +33,7 @@ type nep17TokenNative struct {
symbol string symbol string
decimals int64 decimals int64
factor int64 factor int64
incBalance func(*interop.Context, util.Uint160, *state.StorageItem, *big.Int) error incBalance func(*interop.Context, util.Uint160, *state.StorageItem, *big.Int, *big.Int) error
balFromBytes func(item *state.StorageItem) (*big.Int, error) balFromBytes func(item *state.StorageItem) (*big.Int, error)
} }
@ -173,29 +173,11 @@ func (c *nep17TokenNative) updateAccBalance(ic *interop.Context, acc util.Uint16
return errors.New("insufficient funds") return errors.New("insufficient funds")
} }
si = state.StorageItem{} si = state.StorageItem{}
} else if amount.Sign() == 0 && requiredBalance != nil {
// If amount == 0 then it's either a round trip or an empty transfer. In
// case of a round trip account's balance may still be less than actual
// transfer's amount, so we need to check it. Other cases are handled by
// `incBalance` method.
balance, err := c.balFromBytes(&si)
if err != nil {
return fmt.Errorf("failed to deserialise balance: %w", err)
}
if balance.Cmp(requiredBalance) < 0 {
// Firstly, need to put it back to storage as it affects dumps.
err = ic.DAO.PutStorageItem(c.ID, key, si)
if err != nil {
return err
}
// Finally, return an error.
return errors.New("insufficient funds")
}
} }
err := c.incBalance(ic, acc, &si, amount) err := c.incBalance(ic, acc, &si, amount, requiredBalance)
if err != nil { if err != nil {
if si != nil && amount.Sign() < 0 { if si != nil && amount.Sign() <= 0 {
_ = ic.DAO.PutStorageItem(c.ID, key, si) _ = ic.DAO.PutStorageItem(c.ID, key, si)
} }
return err return err
@ -288,7 +270,7 @@ func (c *nep17TokenNative) addTokens(ic *interop.Context, h util.Uint160, amount
if si == nil { if si == nil {
si = state.StorageItem{} si = state.StorageItem{}
} }
if err := c.incBalance(ic, h, &si, amount); err != nil { if err := c.incBalance(ic, h, &si, amount, nil); err != nil {
panic(err) panic(err)
} }
var err error var err error