github.com/iotexproject/iotex-core@v1.14.1-rc1/action/protocol/rewarding/fund.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 rewarding
     7  
     8  import (
     9  	"context"
    10  	"math/big"
    11  
    12  	"github.com/pkg/errors"
    13  	"google.golang.org/protobuf/proto"
    14  
    15  	"github.com/iotexproject/iotex-address/address"
    16  	"github.com/iotexproject/iotex-proto/golang/iotextypes"
    17  
    18  	"github.com/iotexproject/iotex-core/action"
    19  	"github.com/iotexproject/iotex-core/action/protocol"
    20  	accountutil "github.com/iotexproject/iotex-core/action/protocol/account/util"
    21  	"github.com/iotexproject/iotex-core/action/protocol/rewarding/rewardingpb"
    22  	"github.com/iotexproject/iotex-core/state"
    23  )
    24  
    25  // fund stores the balance of the rewarding fund. The difference between total and available balance should be
    26  // equal to the unclaimed balance in all reward accounts
    27  type fund struct {
    28  	totalBalance     *big.Int
    29  	unclaimedBalance *big.Int
    30  }
    31  
    32  // Serialize serializes fund state into bytes
    33  func (f fund) Serialize() ([]byte, error) {
    34  	gen := rewardingpb.Fund{
    35  		TotalBalance:     f.totalBalance.String(),
    36  		UnclaimedBalance: f.unclaimedBalance.String(),
    37  	}
    38  	return proto.Marshal(&gen)
    39  }
    40  
    41  // Deserialize deserializes bytes into fund state
    42  func (f *fund) Deserialize(data []byte) error {
    43  	gen := rewardingpb.Fund{}
    44  	if err := proto.Unmarshal(data, &gen); err != nil {
    45  		return err
    46  	}
    47  	totalBalance, ok := new(big.Int).SetString(gen.TotalBalance, 10)
    48  	if !ok {
    49  		return errors.New("failed to set total balance")
    50  	}
    51  	unclaimedBalance, ok := new(big.Int).SetString(gen.UnclaimedBalance, 10)
    52  	if !ok {
    53  		return errors.New("failed to set unclaimed balance")
    54  	}
    55  	f.totalBalance = totalBalance
    56  	f.unclaimedBalance = unclaimedBalance
    57  	return nil
    58  }
    59  
    60  // Deposit deposits token into the rewarding fund
    61  func (p *Protocol) Deposit(
    62  	ctx context.Context,
    63  	sm protocol.StateManager,
    64  	amount *big.Int,
    65  	transactionLogType iotextypes.TransactionLogType,
    66  ) (*action.TransactionLog, error) {
    67  	// fallback to regular case by setting sgdAmount = nil
    68  	return p.deposit(ctx, sm, nil, amount, nil, transactionLogType)
    69  }
    70  
    71  func (p *Protocol) deposit(
    72  	ctx context.Context,
    73  	sm protocol.StateManager,
    74  	receiver address.Address,
    75  	amount, sgdAmount *big.Int,
    76  	transactionLogType iotextypes.TransactionLogType,
    77  ) (*action.TransactionLog, error) {
    78  	actionCtx := protocol.MustGetActionCtx(ctx)
    79  	accountCreationOpts := []state.AccountCreationOption{}
    80  	if protocol.MustGetFeatureCtx(ctx).CreateLegacyNonceAccount {
    81  		accountCreationOpts = append(accountCreationOpts, state.LegacyNonceAccountTypeOption())
    82  	}
    83  	// Subtract balance from caller
    84  	acc, err := accountutil.LoadAccount(sm, actionCtx.Caller, accountCreationOpts...)
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  	if err := acc.SubBalance(amount); err != nil {
    89  		return nil, err
    90  	}
    91  	if err := accountutil.StoreAccount(sm, actionCtx.Caller, acc); err != nil {
    92  		return nil, err
    93  	}
    94  	// Add balance to fund
    95  	f := fund{}
    96  	if _, err := p.state(ctx, sm, _fundKey, &f); err != nil {
    97  		return nil, err
    98  	}
    99  	f.totalBalance.Add(f.totalBalance, amount)
   100  	f.unclaimedBalance.Add(f.unclaimedBalance, amount)
   101  	if !isZero(sgdAmount) {
   102  		f.unclaimedBalance.Sub(f.unclaimedBalance, sgdAmount)
   103  		if f.unclaimedBalance.Sign() == -1 {
   104  			return nil, errors.New("no enough available balance")
   105  		}
   106  		// grant sgd amount to receiver
   107  		if err := p.grantToAccount(ctx, sm, receiver, sgdAmount); err != nil {
   108  			return nil, err
   109  		}
   110  	}
   111  	if err := p.putState(ctx, sm, _fundKey, &f); err != nil {
   112  		return nil, err
   113  	}
   114  	return &action.TransactionLog{
   115  		Type:      transactionLogType,
   116  		Sender:    actionCtx.Caller.String(),
   117  		Recipient: address.RewardingPoolAddr,
   118  		Amount:    amount,
   119  	}, nil
   120  }
   121  
   122  // TotalBalance returns the total balance of the rewarding fund
   123  func (p *Protocol) TotalBalance(
   124  	ctx context.Context,
   125  	sm protocol.StateReader,
   126  ) (*big.Int, uint64, error) {
   127  	f := fund{}
   128  	height, err := p.state(ctx, sm, _fundKey, &f)
   129  	if err != nil {
   130  		return nil, height, err
   131  	}
   132  	return f.totalBalance, height, nil
   133  }
   134  
   135  // AvailableBalance returns the available balance of the rewarding fund
   136  func (p *Protocol) AvailableBalance(
   137  	ctx context.Context,
   138  	sm protocol.StateReader,
   139  ) (*big.Int, uint64, error) {
   140  	f := fund{}
   141  	height, err := p.state(ctx, sm, _fundKey, &f)
   142  	if err != nil {
   143  		return nil, height, err
   144  	}
   145  	return f.unclaimedBalance, height, nil
   146  }
   147  
   148  // DepositGas deposits gas into the rewarding fund
   149  func DepositGas(ctx context.Context, sm protocol.StateManager, amount *big.Int) (*action.TransactionLog, error) {
   150  	// If the gas fee is 0, return immediately
   151  	if amount.Cmp(big.NewInt(0)) == 0 {
   152  		return nil, nil
   153  	}
   154  	// TODO: we bypass the gas deposit for the actions in genesis block. Later we should remove this after we remove
   155  	// genesis actions
   156  	blkCtx := protocol.MustGetBlockCtx(ctx)
   157  	if blkCtx.BlockHeight == 0 {
   158  		return nil, nil
   159  	}
   160  	reg, ok := protocol.GetRegistry(ctx)
   161  	if !ok {
   162  		return nil, nil
   163  	}
   164  	rp := FindProtocol(reg)
   165  	if rp == nil {
   166  		return nil, nil
   167  	}
   168  	return rp.Deposit(ctx, sm, amount, iotextypes.TransactionLogType_GAS_FEE)
   169  }
   170  
   171  // DepositGasWithSGD deposits gas into the rewarding fund with Sharing of Gas-fee with DApps
   172  func DepositGasWithSGD(ctx context.Context, sm protocol.StateManager, sgdReceiver address.Address, totalAmount, sgdAmount *big.Int,
   173  ) (*action.TransactionLog, error) {
   174  	if isZero(sgdAmount) {
   175  		// fallback to regular case if SGD amount is zero
   176  		return DepositGas(ctx, sm, totalAmount)
   177  	}
   178  	if sgdReceiver == nil {
   179  		// a valid SGD amount but no valid receiver address
   180  		return nil, errors.New("no valid receiver address to receive the Sharing of Gas-fee with DApps")
   181  	}
   182  	// TODO: we bypass the gas deposit for the actions in genesis block. Later we should remove this after we remove
   183  	// genesis actions
   184  	blkCtx := protocol.MustGetBlockCtx(ctx)
   185  	if blkCtx.BlockHeight == 0 {
   186  		return nil, nil
   187  	}
   188  	reg, ok := protocol.GetRegistry(ctx)
   189  	if !ok {
   190  		return nil, nil
   191  	}
   192  	rp := FindProtocol(reg)
   193  	if rp == nil {
   194  		return nil, nil
   195  	}
   196  	return rp.deposit(ctx, sm, sgdReceiver, totalAmount, sgdAmount, iotextypes.TransactionLogType_GAS_FEE)
   197  }
   198  
   199  func isZero(a *big.Int) bool {
   200  	return a == nil || len(a.Bytes()) == 0
   201  }