github.com/iotexproject/iotex-core@v1.14.1-rc1/action/protocol/staking/bucket_pool.go (about)

     1  // Copyright (c) 2022 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 staking
     7  
     8  import (
     9  	"math/big"
    10  
    11  	"github.com/iotexproject/go-pkgs/hash"
    12  	"google.golang.org/protobuf/proto"
    13  
    14  	"github.com/iotexproject/iotex-core/action/protocol"
    15  	"github.com/iotexproject/iotex-core/action/protocol/staking/stakingpb"
    16  	"github.com/iotexproject/iotex-core/state"
    17  )
    18  
    19  // const
    20  const (
    21  	_stakingBucketPool = "bucketPool"
    22  )
    23  
    24  var (
    25  	_bucketPoolAddr    = hash.Hash160b([]byte(_stakingBucketPool))
    26  	_bucketPoolAddrKey = append([]byte{_const}, _bucketPoolAddr[:]...)
    27  )
    28  
    29  // when a bucket is created, the amount of staked IOTX token is deducted from user, but does not transfer to any address
    30  // in the same way when a bucket are withdrawn, bucket amount is added back to user, but does not come out of any address
    31  //
    32  // for better accounting/auditing, we take protocol's address as the 'bucket pool' address
    33  // 1. at Greenland height we sum up all existing bucket's amount and set the total amount to bucket pool address
    34  // 2. for future bucket creation/deposit/registration, the amount of staked IOTX token will be added to bucket pool (so
    35  //    the pool is 'receiving' token)
    36  // 3. for future bucket withdrawal, the bucket amount will be deducted from bucket pool (so the pool is 'releasing' token)
    37  
    38  type (
    39  	// BucketPool implements the bucket pool
    40  	BucketPool struct {
    41  		enableSMStorage bool
    42  		total           *totalAmount
    43  	}
    44  
    45  	totalAmount struct {
    46  		amount *big.Int
    47  		count  uint64
    48  	}
    49  )
    50  
    51  func (t *totalAmount) Serialize() ([]byte, error) {
    52  	gen := stakingpb.TotalAmount{
    53  		Amount: t.amount.String(),
    54  		Count:  t.count,
    55  	}
    56  	return proto.Marshal(&gen)
    57  }
    58  
    59  func (t *totalAmount) Deserialize(data []byte) error {
    60  	gen := stakingpb.TotalAmount{}
    61  	if err := proto.Unmarshal(data, &gen); err != nil {
    62  		return err
    63  	}
    64  
    65  	var ok bool
    66  	if t.amount, ok = new(big.Int).SetString(gen.Amount, 10); !ok {
    67  		return state.ErrStateDeserialization
    68  	}
    69  
    70  	if t.amount.Cmp(big.NewInt(0)) == -1 {
    71  		return state.ErrNotEnoughBalance
    72  	}
    73  	t.count = gen.Count
    74  	return nil
    75  }
    76  
    77  func (t *totalAmount) AddBalance(amount *big.Int, newBucket bool) {
    78  	t.amount.Add(t.amount, amount)
    79  	if newBucket {
    80  		t.count++
    81  	}
    82  }
    83  
    84  func (t *totalAmount) SubBalance(amount *big.Int) error {
    85  	if amount.Cmp(t.amount) == 1 || t.count == 0 {
    86  		return state.ErrNotEnoughBalance
    87  	}
    88  	t.amount.Sub(t.amount, amount)
    89  	t.count--
    90  	return nil
    91  }
    92  
    93  // Total returns the total amount staked in bucket pool
    94  func (bp *BucketPool) Total() *big.Int {
    95  	return new(big.Int).Set(bp.total.amount)
    96  }
    97  
    98  // Count returns the total number of buckets in bucket pool
    99  func (bp *BucketPool) Count() uint64 {
   100  	return bp.total.count
   101  }
   102  
   103  // Copy returns a copy of the bucket pool
   104  func (bp *BucketPool) Copy(enableSMStorage bool) *BucketPool {
   105  	pool := BucketPool{}
   106  	pool.enableSMStorage = enableSMStorage
   107  	pool.total = &totalAmount{
   108  		amount: new(big.Int).Set(bp.total.amount),
   109  		count:  bp.total.count,
   110  	}
   111  	return &pool
   112  }
   113  
   114  // Sync syncs the data from state manager
   115  func (bp *BucketPool) Sync(sm protocol.StateManager) error {
   116  	if bp.enableSMStorage {
   117  		_, err := sm.State(bp.total, protocol.NamespaceOption(_stakingNameSpace), protocol.KeyOption(_bucketPoolAddrKey))
   118  		return err
   119  	}
   120  	// get stashed total amount
   121  	err := sm.Unload(_protocolID, _stakingBucketPool, bp.total)
   122  	if err != nil && err != protocol.ErrNoName {
   123  		return err
   124  	}
   125  	return nil
   126  }
   127  
   128  // Commit is called upon workingset commit
   129  func (bp *BucketPool) Commit(sr protocol.StateReader) error {
   130  	return nil
   131  }
   132  
   133  // CreditPool subtracts staked amount out of the pool
   134  func (bp *BucketPool) CreditPool(sm protocol.StateManager, amount *big.Int) error {
   135  	if err := bp.total.SubBalance(amount); err != nil {
   136  		return err
   137  	}
   138  
   139  	if bp.enableSMStorage {
   140  		_, err := sm.PutState(bp.total, protocol.NamespaceOption(_stakingNameSpace), protocol.KeyOption(_bucketPoolAddrKey))
   141  		return err
   142  	}
   143  	return sm.Load(_protocolID, _stakingBucketPool, bp.total)
   144  }
   145  
   146  // DebitPool adds staked amount into the pool
   147  func (bp *BucketPool) DebitPool(sm protocol.StateManager, amount *big.Int, newBucket bool) error {
   148  	bp.total.AddBalance(amount, newBucket)
   149  	if bp.enableSMStorage {
   150  		_, err := sm.PutState(bp.total, protocol.NamespaceOption(_stakingNameSpace), protocol.KeyOption(_bucketPoolAddrKey))
   151  		return err
   152  	}
   153  	return sm.Load(_protocolID, _stakingBucketPool, bp.total)
   154  }