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  }