github.com/iotexproject/iotex-core@v1.14.1-rc1/state/account.go (about)

     1  // Copyright (c) 2019 IoTeX Foundation
     2  // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability
     3  // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed.
     4  // This source code is governed by Apache License 2.0 that can be found in the LICENSE file.
     5  
     6  package state
     7  
     8  import (
     9  	"math/big"
    10  
    11  	"github.com/iotexproject/go-pkgs/hash"
    12  	"github.com/pkg/errors"
    13  	"google.golang.org/protobuf/proto"
    14  
    15  	"github.com/iotexproject/iotex-core/action/protocol/account/accountpb"
    16  )
    17  
    18  var (
    19  	// ErrNotEnoughBalance is the error that the balance is not enough
    20  	ErrNotEnoughBalance = errors.New("not enough balance")
    21  	// ErrInvalidAmount is the error that the amount to add is negative
    22  	ErrInvalidAmount = errors.New("invalid amount")
    23  	// ErrAccountCollision is the error that the account already exists
    24  	ErrAccountCollision = errors.New("account already exists")
    25  	// ErrInvalidNonce is the error that the nonce to set is invalid
    26  	ErrInvalidNonce = errors.New("invalid nonce")
    27  	// ErrUnknownAccountType is the error that the account type is unknown
    28  	ErrUnknownAccountType = errors.New("unknown account type")
    29  	// ErrNonceOverflow is the error that the nonce overflow
    30  	ErrNonceOverflow = errors.New("nonce overflow")
    31  )
    32  
    33  // LegacyNonceAccountTypeOption is an option to create account with new account type
    34  func LegacyNonceAccountTypeOption() AccountCreationOption {
    35  	return func(account *Account) error {
    36  		account.accountType = 0
    37  		return nil
    38  	}
    39  }
    40  
    41  // DelegateCandidateOption is an option to create a delegate candidate account
    42  func DelegateCandidateOption() AccountCreationOption {
    43  	return func(account *Account) error {
    44  		account.isCandidate = true
    45  		return nil
    46  	}
    47  }
    48  
    49  type (
    50  	// AccountCreationOption is to create new account with specific settings
    51  	AccountCreationOption func(*Account) error
    52  
    53  	// Account is the canonical representation of an account.
    54  	Account struct {
    55  		// for Type 0, nonce 0 is reserved from actions in genesis block and coinbase transfers nonces
    56  		// other actions' nonces start from 1
    57  		nonce        uint64
    58  		Balance      *big.Int
    59  		Root         hash.Hash256 // storage trie root for contract account
    60  		CodeHash     []byte       // hash of the smart contract byte-code for contract account
    61  		isCandidate  bool
    62  		votingWeight *big.Int
    63  		accountType  int32
    64  	}
    65  )
    66  
    67  // ToProto converts to protobuf's Account
    68  func (st *Account) ToProto() *accountpb.Account {
    69  	acPb := &accountpb.Account{}
    70  	acPb.Nonce = st.nonce
    71  	if _, ok := accountpb.AccountType_name[st.accountType]; !ok {
    72  		panic("unknown account type")
    73  	}
    74  	acPb.Type = accountpb.AccountType(st.accountType)
    75  	if st.Balance != nil {
    76  		acPb.Balance = st.Balance.String()
    77  	}
    78  	acPb.Root = make([]byte, len(st.Root))
    79  	copy(acPb.Root, st.Root[:])
    80  	acPb.CodeHash = make([]byte, len(st.CodeHash))
    81  	copy(acPb.CodeHash, st.CodeHash)
    82  	acPb.IsCandidate = st.isCandidate
    83  	if st.votingWeight != nil {
    84  		acPb.VotingWeight = st.votingWeight.Bytes()
    85  	}
    86  	return acPb
    87  }
    88  
    89  // Serialize serializes account state into bytes
    90  func (st Account) Serialize() ([]byte, error) {
    91  	return proto.Marshal(st.ToProto())
    92  }
    93  
    94  // FromProto converts from protobuf's Account
    95  func (st *Account) FromProto(acPb *accountpb.Account) {
    96  	st.nonce = acPb.Nonce
    97  	if _, ok := accountpb.AccountType_name[int32(acPb.Type.Number())]; !ok {
    98  		panic("unknown account type")
    99  	}
   100  	st.accountType = int32(acPb.Type.Number())
   101  	if acPb.Balance == "" {
   102  		st.Balance = big.NewInt(0)
   103  	} else {
   104  		balance, ok := new(big.Int).SetString(acPb.Balance, 10)
   105  		if !ok {
   106  			panic(errors.Errorf("invalid balance %s", acPb.Balance))
   107  		}
   108  		st.Balance = balance
   109  	}
   110  	copy(st.Root[:], acPb.Root)
   111  	st.CodeHash = nil
   112  	if acPb.CodeHash != nil {
   113  		st.CodeHash = make([]byte, len(acPb.CodeHash))
   114  		copy(st.CodeHash, acPb.CodeHash)
   115  	}
   116  	st.isCandidate = acPb.IsCandidate
   117  	st.votingWeight = big.NewInt(0)
   118  	if acPb.VotingWeight != nil {
   119  		st.votingWeight.SetBytes(acPb.VotingWeight)
   120  	}
   121  }
   122  
   123  // Deserialize deserializes bytes into account state
   124  func (st *Account) Deserialize(buf []byte) error {
   125  	acPb := &accountpb.Account{}
   126  	if err := proto.Unmarshal(buf, acPb); err != nil {
   127  		return err
   128  	}
   129  	st.FromProto(acPb)
   130  	return nil
   131  }
   132  
   133  // IsNewbieAccount returns true if the account has not sent any actions
   134  func (st *Account) IsNewbieAccount() bool {
   135  	return st.nonce == 0
   136  }
   137  
   138  // IsLegacyFreshAccount returns true if a legacy account has not sent any actions
   139  func (st *Account) IsLegacyFreshAccount() bool {
   140  	return st.accountType == 0 && st.nonce == 0
   141  }
   142  
   143  // AccountType returns the account type
   144  func (st *Account) AccountType() int32 {
   145  	return st.accountType
   146  }
   147  
   148  // SetPendingNonce sets the pending nonce
   149  func (st *Account) SetPendingNonce(nonce uint64) error {
   150  	// this is a legacy account that had never initiated an outgoing transaction
   151  	// so we can convert it to zero-nonce account
   152  	if st.accountType == 0 && st.nonce == 0 && nonce == 1 {
   153  		st.accountType = 1
   154  	}
   155  
   156  	switch st.accountType {
   157  	case 1:
   158  		if st.nonce+1 < st.nonce {
   159  			return errors.Wrapf(ErrNonceOverflow, "current value %d", st.nonce)
   160  		}
   161  		if nonce != st.nonce+1 {
   162  			return errors.Wrapf(ErrInvalidNonce, "actual value %d, %d expected", nonce, st.nonce+1)
   163  		}
   164  		st.nonce++
   165  	case 0:
   166  		if st.nonce+2 < st.nonce {
   167  			return errors.Wrapf(ErrNonceOverflow, "current value %d", st.nonce)
   168  		}
   169  		if nonce != st.nonce+2 {
   170  			return errors.Wrapf(ErrInvalidNonce, "actual value %d, %d expected", nonce, st.nonce+2)
   171  		}
   172  		st.nonce++
   173  	default:
   174  		return errors.Wrapf(ErrUnknownAccountType, "account type %d", st.accountType)
   175  	}
   176  
   177  	return nil
   178  }
   179  
   180  // ConvertFreshAccountToZeroNonceType converts a fresh legacy account to zero-nonce account
   181  func (st *Account) ConvertFreshAccountToZeroNonceType(nonce uint64) bool {
   182  	if st.accountType == 0 && st.nonce == 0 && nonce == 0 {
   183  		// this is a legacy account that had never initiated an outgoing transaction
   184  		// so we can convert it to zero-nonce account
   185  		st.accountType = 1
   186  		return true
   187  	}
   188  	return false
   189  }
   190  
   191  // PendingNonce returns the pending nonce of the account
   192  func (st *Account) PendingNonce() uint64 {
   193  	switch st.accountType {
   194  	case 1:
   195  		return st.nonce
   196  	case 0:
   197  		return st.nonce + 1
   198  	default:
   199  		panic(errors.Wrapf(ErrUnknownAccountType, "account type %d", st.accountType))
   200  	}
   201  }
   202  
   203  // PendingNonceConsideringFreshAccount return the pending nonce considering fresh legacy account
   204  func (st *Account) PendingNonceConsideringFreshAccount() uint64 {
   205  	if st.accountType == 0 && st.nonce == 0 {
   206  		// this is a legacy account that had never initiated an outgoing transaction
   207  		// so we can use 0 as the nonce for its very first transaction
   208  		return 0
   209  	}
   210  	return st.PendingNonce()
   211  }
   212  
   213  // MarkAsCandidate marks the account as a candidate
   214  func (st *Account) MarkAsCandidate() {
   215  	st.isCandidate = true
   216  }
   217  
   218  // HasSufficientBalance returns true if balance is larger than amount
   219  func (st *Account) HasSufficientBalance(amount *big.Int) bool {
   220  	if amount == nil {
   221  		return true
   222  	}
   223  	return amount.Cmp(st.Balance) <= 0
   224  }
   225  
   226  // AddBalance adds balance for account state
   227  func (st *Account) AddBalance(amount *big.Int) error {
   228  	if amount == nil || amount.Sign() < 0 {
   229  		return errors.Wrapf(ErrInvalidAmount, "amount %s shouldn't be negative", amount.String())
   230  	}
   231  	if st.Balance != nil {
   232  		st.Balance.Add(st.Balance, amount)
   233  	} else {
   234  		st.Balance = new(big.Int).Set(amount)
   235  	}
   236  	return nil
   237  }
   238  
   239  // SubBalance subtracts balance for account state
   240  func (st *Account) SubBalance(amount *big.Int) error {
   241  	if amount == nil || amount.Cmp(big.NewInt(0)) < 0 {
   242  		return errors.Wrapf(ErrInvalidAmount, "amount %s shouldn't be negative", amount.String())
   243  	}
   244  	// make sure there's enough fund to spend
   245  	if amount.Cmp(st.Balance) == 1 {
   246  		return ErrNotEnoughBalance
   247  	}
   248  	st.Balance.Sub(st.Balance, amount)
   249  	return nil
   250  }
   251  
   252  // IsContract returns true for contract account
   253  func (st *Account) IsContract() bool {
   254  	return len(st.CodeHash) > 0
   255  }
   256  
   257  // Clone clones the account state
   258  func (st *Account) Clone() *Account {
   259  	s := *st
   260  	s.Balance = nil
   261  	s.Balance = new(big.Int).Set(st.Balance)
   262  	s.votingWeight = nil
   263  	if st.votingWeight != nil {
   264  		s.votingWeight = new(big.Int).Set(st.votingWeight)
   265  	}
   266  	if st.CodeHash != nil {
   267  		s.CodeHash = nil
   268  		s.CodeHash = make([]byte, len(st.CodeHash))
   269  		copy(s.CodeHash, st.CodeHash)
   270  	}
   271  	return &s
   272  }
   273  
   274  // NewAccount creates a new account with options
   275  func NewAccount(opts ...AccountCreationOption) (*Account, error) {
   276  	account := &Account{
   277  		Balance:      big.NewInt(0),
   278  		votingWeight: big.NewInt(0),
   279  		accountType:  1,
   280  	}
   281  	for _, opt := range opts {
   282  		if err := opt(account); err != nil {
   283  			return nil, errors.Wrap(err, "failed to apply account creation option")
   284  		}
   285  	}
   286  	return account, nil
   287  }