mirror of https://github.com/poanetwork/gecko.git
156 lines
4.6 KiB
Go
156 lines
4.6 KiB
Go
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package spchainvm
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
|
|
"github.com/ava-labs/gecko/ids"
|
|
"github.com/ava-labs/gecko/snow"
|
|
"github.com/ava-labs/gecko/utils/crypto"
|
|
)
|
|
|
|
var (
|
|
errOutOfSpends = errors.New("ran out of spends")
|
|
errInsufficientFunds = errors.New("insufficient funds")
|
|
errOverflow = errors.New("math overflowed")
|
|
errInvalidID = errors.New("invalid ID")
|
|
errInvalidAddress = errors.New("invalid address")
|
|
)
|
|
|
|
// Account represents the balance and nonce of a user's funds
|
|
type Account struct {
|
|
id ids.ShortID
|
|
nonce, balance uint64
|
|
}
|
|
|
|
// ID of this account
|
|
func (a Account) ID() ids.ShortID { return a.id }
|
|
|
|
// Balance contained in this account
|
|
func (a Account) Balance() uint64 { return a.balance }
|
|
|
|
// Nonce this account was last spent with
|
|
func (a Account) Nonce() uint64 { return a.nonce }
|
|
|
|
// CreateTx creates a transaction from this account
|
|
// that sends [amount] to the address [destination]
|
|
func (a Account) CreateTx(amount uint64, destination ids.ShortID, ctx *snow.Context, key *crypto.PrivateKeySECP256K1R) (*Tx, Account, error) {
|
|
builder := Builder{
|
|
NetworkID: ctx.NetworkID,
|
|
ChainID: ctx.ChainID,
|
|
}
|
|
// If nonce overflows, Send will return an error
|
|
tx, err := builder.NewTx(key, a.nonce+1, amount, destination)
|
|
if err != nil {
|
|
return nil, a, err
|
|
}
|
|
newAccount, err := a.Send(tx, ctx)
|
|
return tx, newAccount, err
|
|
}
|
|
|
|
// Send generates a new account state from sending the transaction
|
|
func (a Account) Send(tx *Tx, ctx *snow.Context) (Account, error) {
|
|
return a.send(tx, ctx, &crypto.FactorySECP256K1R{})
|
|
}
|
|
|
|
// send generates the new account state from sending the transaction
|
|
func (a Account) send(tx *Tx, ctx *snow.Context, factory *crypto.FactorySECP256K1R) (Account, error) {
|
|
return Account{
|
|
id: a.id,
|
|
// guaranteed not to overflow due to VerifySend
|
|
nonce: a.nonce + 1,
|
|
// guaranteed not to underflow due to VerifySend
|
|
balance: a.balance - tx.amount,
|
|
}, a.verifySend(tx, ctx, factory)
|
|
}
|
|
|
|
// VerifySend returns if the provided transaction can send this transaction
|
|
func (a Account) VerifySend(tx *Tx, ctx *snow.Context) error {
|
|
return a.verifySend(tx, ctx, &crypto.FactorySECP256K1R{})
|
|
}
|
|
|
|
func (a Account) verifySend(tx *Tx, ctx *snow.Context, factory *crypto.FactorySECP256K1R) error {
|
|
// Verify the account is in a valid state and the transaction is valid
|
|
if err := a.Verify(); err != nil {
|
|
return err
|
|
}
|
|
if err := tx.verify(ctx, factory); err != nil {
|
|
return err
|
|
}
|
|
switch {
|
|
case a.nonce == math.MaxUint64:
|
|
// For this error to occur, a user would need to be issuing transactions
|
|
// at 10k tps for ~ 80 million years
|
|
return errOutOfSpends
|
|
case a.nonce+1 != tx.nonce:
|
|
return fmt.Errorf("wrong tx nonce used, %d != %d", a.nonce+1, tx.nonce)
|
|
case a.balance < tx.amount:
|
|
return fmt.Errorf("%s %d < %d", errInsufficientFunds, a.balance, tx.amount)
|
|
case a.nonce+1 == math.MaxUint64 && a.balance != tx.amount:
|
|
return errOutOfSpends
|
|
case !a.id.Equals(tx.key(ctx, factory).Address()):
|
|
return errInvalidAddress
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// Receive generates a new account state from receiving the transaction
|
|
func (a Account) Receive(tx *Tx, ctx *snow.Context) (Account, error) {
|
|
return a.receive(tx, ctx, &crypto.FactorySECP256K1R{})
|
|
}
|
|
|
|
func (a Account) receive(tx *Tx, ctx *snow.Context, factory *crypto.FactorySECP256K1R) (Account, error) {
|
|
return Account{
|
|
id: a.id,
|
|
nonce: a.nonce,
|
|
// guaranteed not to overflow due to VerifyReceive
|
|
balance: a.balance + tx.amount,
|
|
}, a.verifyReceive(tx, ctx, factory)
|
|
}
|
|
|
|
// VerifyReceive returns if the provided transaction can receive this
|
|
// transaction
|
|
func (a Account) VerifyReceive(tx *Tx, ctx *snow.Context) error {
|
|
return a.verifyReceive(tx, ctx, &crypto.FactorySECP256K1R{})
|
|
}
|
|
|
|
func (a Account) verifyReceive(tx *Tx, ctx *snow.Context, factory *crypto.FactorySECP256K1R) error {
|
|
if err := a.Verify(); err != nil {
|
|
return err
|
|
}
|
|
if err := tx.verify(ctx, factory); err != nil {
|
|
return err
|
|
}
|
|
switch {
|
|
case a.nonce == math.MaxUint64:
|
|
// For this error to occur, a user would need to be issuing transactions
|
|
// at 10k tps for ~ 80 million years
|
|
return errOutOfSpends
|
|
case a.balance > math.MaxUint64-tx.amount:
|
|
return errOverflow
|
|
case !a.id.Equals(tx.to):
|
|
return errInvalidID
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// Verify that this account is well formed
|
|
func (a Account) Verify() error {
|
|
switch {
|
|
case a.id.IsZero():
|
|
return errInvalidID
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (a Account) String() string {
|
|
return fmt.Sprintf("Account[%s]: Balance=%d, Nonce=%d", a.ID(), a.Balance(), a.Nonce())
|
|
}
|