github.com/klaytn/klaytn@v1.10.2/consensus/istanbul/backend/kip103.go (about) 1 package backend 2 3 import ( 4 "context" 5 "errors" 6 "math/big" 7 8 "github.com/klaytn/klaytn" 9 "github.com/klaytn/klaytn/accounts/abi/bind" 10 "github.com/klaytn/klaytn/blockchain" 11 "github.com/klaytn/klaytn/blockchain/state" 12 "github.com/klaytn/klaytn/blockchain/types" 13 "github.com/klaytn/klaytn/blockchain/vm" 14 "github.com/klaytn/klaytn/common" 15 "github.com/klaytn/klaytn/consensus" 16 "github.com/klaytn/klaytn/contracts/kip103" 17 ) 18 19 var ( 20 errNotEnoughRetiredBal = errors.New("the sum of retired accounts' balance is smaller than the distributing amount") 21 errNotProperStatus = errors.New("cannot read a proper status value") 22 ) 23 24 // Kip103ContractCaller is an implementation of contractCaller only for KIP-103. 25 // The caller interacts with a KIP-103 contract on a read only basis. 26 type Kip103ContractCaller struct { 27 state *state.StateDB // the state that is under process 28 chain consensus.ChainReader // chain containing the blockchain information 29 header *types.Header // the header of a new block that is under process 30 } 31 32 func (caller *Kip103ContractCaller) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) { 33 return caller.state.GetCode(contract), nil 34 } 35 36 func (caller *Kip103ContractCaller) CallContract(ctx context.Context, call klaytn.CallMsg, blockNumber *big.Int) ([]byte, error) { 37 gasPrice := big.NewInt(0) // execute call regardless of the balance of the sender 38 gasLimit := uint64(1e8) // enough gas limit to execute kip103 contract functions 39 intrinsicGas := uint64(0) // read operation doesn't require intrinsicGas 40 41 // call.From: zero address will be assigned if nothing is specified 42 // call.To: the target contract address will be assigned by `BoundContract` 43 // call.Value: nil value is acceptable for `types.NewMessage` 44 // call.Data: a proper value will be assigned by `BoundContract` 45 msg := types.NewMessage(call.From, call.To, caller.state.GetNonce(call.From), 46 call.Value, gasLimit, gasPrice, call.Data, false, intrinsicGas) 47 48 context := blockchain.NewEVMContext(msg, caller.header, caller.chain, nil) 49 context.GasPrice = gasPrice // set gasPrice again if baseFee is assigned 50 evm := vm.NewEVM(context, caller.state, caller.chain.Config(), &vm.Config{}) // no additional vm config required 51 52 res, _, kerr := blockchain.ApplyMessage(evm, msg) 53 return res, kerr.ErrTxInvalid 54 } 55 56 type kip103result struct { 57 Retired map[common.Address]*big.Int `json:"retired"` 58 Newbie map[common.Address]*big.Int `json:"newbie"` 59 Burnt *big.Int `json:"burnt"` 60 Success bool `json:"success"` 61 } 62 63 func newKip103Receipt() *kip103result { 64 return &kip103result{ 65 Retired: make(map[common.Address]*big.Int), 66 Newbie: make(map[common.Address]*big.Int), 67 Burnt: big.NewInt(0), 68 Success: false, 69 } 70 } 71 72 func (result *kip103result) fillRetired(contract *kip103.TreasuryRebalanceCaller, state *state.StateDB) error { 73 numRetiredBigInt, err := contract.GetRetiredCount(nil) 74 if err != nil { 75 logger.Error("Failed to get RetiredCount from TreasuryRebalance contract", "err", err) 76 return err 77 } 78 79 for i := 0; i < int(numRetiredBigInt.Int64()); i++ { 80 ret, err := contract.Retirees(nil, big.NewInt(int64(i))) 81 if err != nil { 82 logger.Error("Failed to get Retirees from TreasuryRebalance contract", "err", err) 83 return err 84 } 85 result.Retired[ret] = state.GetBalance(ret) 86 } 87 return nil 88 } 89 90 func (result *kip103result) fillNewbie(contract *kip103.TreasuryRebalanceCaller) error { 91 numNewbieBigInt, err := contract.GetNewbieCount(nil) 92 if err != nil { 93 logger.Error("Failed to get NewbieCount from TreasuryRebalance contract", "err", err) 94 return nil 95 } 96 97 for i := 0; i < int(numNewbieBigInt.Int64()); i++ { 98 ret, err := contract.Newbies(nil, big.NewInt(int64(i))) 99 if err != nil { 100 logger.Error("Failed to get Newbies from TreasuryRebalance contract", "err", err) 101 return err 102 } 103 result.Newbie[ret.Newbie] = ret.Amount 104 } 105 return nil 106 } 107 108 func (result *kip103result) totalRetriedBalance() *big.Int { 109 total := big.NewInt(0) 110 for _, bal := range result.Retired { 111 total.Add(total, bal) 112 } 113 return total 114 } 115 116 func (result *kip103result) totalNewbieBalance() *big.Int { 117 total := big.NewInt(0) 118 for _, bal := range result.Newbie { 119 total.Add(total, bal) 120 } 121 return total 122 } 123 124 // RebalanceTreasury reads data from a contract, validates stored values, and executes treasury rebalancing (KIP-103). 125 // It can change the global state by removing old treasury balances and allocating new treasury balances. 126 // The new allocation can be larger than the removed amount, and the difference between two amounts will be burnt. 127 func RebalanceTreasury(state *state.StateDB, chain consensus.ChainReader, header *types.Header, c bind.ContractCaller) (*kip103result, error) { 128 result := newKip103Receipt() 129 130 caller, err := kip103.NewTreasuryRebalanceCaller(chain.Config().Kip103ContractAddress, c) 131 if err != nil { 132 return result, err 133 } 134 135 // Retrieve 1) Get Retired 136 if err := result.fillRetired(caller, state); err != nil { 137 return result, err 138 } 139 140 // Retrieve 2) Get Newbie 141 if err := result.fillNewbie(caller); err != nil { 142 return result, err 143 } 144 145 // Validation 1) Check the target block number 146 if blockNum, err := caller.RebalanceBlockNumber(nil); err != nil || blockNum.Cmp(header.Number) != 0 { 147 return result, errors.New("cannot find a proper target block number") 148 } 149 150 // Validation 2) Check whether status is approved. It should be 2 meaning approved 151 if status, err := caller.Status(nil); err != nil || status != 2 { 152 return result, errNotProperStatus 153 } 154 155 // Validation 3) Check approvals from retirees 156 if err := caller.CheckRetiredsApproved(nil); err != nil { 157 return result, err 158 } 159 160 // Validation 4) Check the total balance of retirees are bigger than the distributing amount 161 totalRetiredAmount := result.totalRetriedBalance() 162 totalNewbieAmount := result.totalNewbieBalance() 163 if totalRetiredAmount.Cmp(totalNewbieAmount) < 0 { 164 return result, errNotEnoughRetiredBal 165 } 166 167 // Execution 1) Clear all balances of retirees 168 for addr := range result.Retired { 169 state.SetBalance(addr, big.NewInt(0)) 170 } 171 // Execution 2) Distribute KLAY to all newbies 172 for addr, balance := range result.Newbie { 173 // if newbie has KLAY before the allocation, it will be burnt 174 currentBalance := state.GetBalance(addr) 175 result.Burnt.Add(result.Burnt, currentBalance) 176 177 state.SetBalance(addr, balance) 178 } 179 180 // Fill the remaining fields of the result 181 remainder := new(big.Int).Sub(totalRetiredAmount, totalNewbieAmount) 182 result.Burnt.Add(result.Burnt, remainder) 183 result.Success = true 184 185 return result, nil 186 }