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  }