github.com/klaytn/klaytn@v1.12.1/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 // No need to handle acccess list here 46 msg := types.NewMessage(call.From, call.To, caller.state.GetNonce(call.From), 47 call.Value, gasLimit, gasPrice, call.Data, false, intrinsicGas, nil) 48 49 blockContext := blockchain.NewEVMBlockContext(caller.header, caller.chain, nil) 50 txContext := blockchain.NewEVMTxContext(msg, caller.header) 51 txContext.GasPrice = gasPrice // set gasPrice again if baseFee is assigned 52 evm := vm.NewEVM(blockContext, txContext, caller.state, caller.chain.Config(), &vm.Config{}) // no additional vm config required 53 54 result, err := blockchain.ApplyMessage(evm, msg) 55 return result.Return(), err 56 } 57 58 type kip103result struct { 59 Retired map[common.Address]*big.Int `json:"retired"` 60 Newbie map[common.Address]*big.Int `json:"newbie"` 61 Burnt *big.Int `json:"burnt"` 62 Success bool `json:"success"` 63 } 64 65 func newKip103Receipt() *kip103result { 66 return &kip103result{ 67 Retired: make(map[common.Address]*big.Int), 68 Newbie: make(map[common.Address]*big.Int), 69 Burnt: big.NewInt(0), 70 Success: false, 71 } 72 } 73 74 func (result *kip103result) fillRetired(contract *kip103.TreasuryRebalanceCaller, state *state.StateDB) error { 75 numRetiredBigInt, err := contract.GetRetiredCount(nil) 76 if err != nil { 77 logger.Error("Failed to get RetiredCount from TreasuryRebalance contract", "err", err) 78 return err 79 } 80 81 for i := 0; i < int(numRetiredBigInt.Int64()); i++ { 82 ret, err := contract.Retirees(nil, big.NewInt(int64(i))) 83 if err != nil { 84 logger.Error("Failed to get Retirees from TreasuryRebalance contract", "err", err) 85 return err 86 } 87 result.Retired[ret] = state.GetBalance(ret) 88 } 89 return nil 90 } 91 92 func (result *kip103result) fillNewbie(contract *kip103.TreasuryRebalanceCaller) error { 93 numNewbieBigInt, err := contract.GetNewbieCount(nil) 94 if err != nil { 95 logger.Error("Failed to get NewbieCount from TreasuryRebalance contract", "err", err) 96 return nil 97 } 98 99 for i := 0; i < int(numNewbieBigInt.Int64()); i++ { 100 ret, err := contract.Newbies(nil, big.NewInt(int64(i))) 101 if err != nil { 102 logger.Error("Failed to get Newbies from TreasuryRebalance contract", "err", err) 103 return err 104 } 105 result.Newbie[ret.Newbie] = ret.Amount 106 } 107 return nil 108 } 109 110 func (result *kip103result) totalRetriedBalance() *big.Int { 111 total := big.NewInt(0) 112 for _, bal := range result.Retired { 113 total.Add(total, bal) 114 } 115 return total 116 } 117 118 func (result *kip103result) totalNewbieBalance() *big.Int { 119 total := big.NewInt(0) 120 for _, bal := range result.Newbie { 121 total.Add(total, bal) 122 } 123 return total 124 } 125 126 // RebalanceTreasury reads data from a contract, validates stored values, and executes treasury rebalancing (KIP-103). 127 // It can change the global state by removing old treasury balances and allocating new treasury balances. 128 // The new allocation can be larger than the removed amount, and the difference between two amounts will be burnt. 129 func RebalanceTreasury(state *state.StateDB, chain consensus.ChainReader, header *types.Header, c bind.ContractCaller) (*kip103result, error) { 130 result := newKip103Receipt() 131 132 caller, err := kip103.NewTreasuryRebalanceCaller(chain.Config().Kip103ContractAddress, c) 133 if err != nil { 134 return result, err 135 } 136 137 // Retrieve 1) Get Retired 138 if err := result.fillRetired(caller, state); err != nil { 139 return result, err 140 } 141 142 // Retrieve 2) Get Newbie 143 if err := result.fillNewbie(caller); err != nil { 144 return result, err 145 } 146 147 // Validation 1) Check the target block number 148 if blockNum, err := caller.RebalanceBlockNumber(nil); err != nil || blockNum.Cmp(header.Number) != 0 { 149 return result, errors.New("cannot find a proper target block number") 150 } 151 152 // Validation 2) Check whether status is approved. It should be 2 meaning approved 153 if status, err := caller.Status(nil); err != nil || status != 2 { 154 return result, errNotProperStatus 155 } 156 157 // Validation 3) Check approvals from retirees 158 if err := caller.CheckRetiredsApproved(nil); err != nil { 159 return result, err 160 } 161 162 // Validation 4) Check the total balance of retirees are bigger than the distributing amount 163 totalRetiredAmount := result.totalRetriedBalance() 164 totalNewbieAmount := result.totalNewbieBalance() 165 if totalRetiredAmount.Cmp(totalNewbieAmount) < 0 { 166 return result, errNotEnoughRetiredBal 167 } 168 169 // Execution 1) Clear all balances of retirees 170 for addr := range result.Retired { 171 state.SetBalance(addr, big.NewInt(0)) 172 } 173 // Execution 2) Distribute KLAY to all newbies 174 for addr, balance := range result.Newbie { 175 // if newbie has KLAY before the allocation, it will be burnt 176 currentBalance := state.GetBalance(addr) 177 result.Burnt.Add(result.Burnt, currentBalance) 178 179 state.SetBalance(addr, balance) 180 } 181 182 // Fill the remaining fields of the result 183 remainder := new(big.Int).Sub(totalRetiredAmount, totalNewbieAmount) 184 result.Burnt.Add(result.Burnt, remainder) 185 result.Success = true 186 187 return result, nil 188 }