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 }