github.com/klaytn/klaytn@v1.12.1/accounts/abi/bind/backends/blockchain.go (about)

     1  // Copyright 2023 The klaytn Authors
     2  // This file is part of the klaytn library.
     3  //
     4  // The klaytn library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The klaytn library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the klaytn library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package backends
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"math/big"
    23  
    24  	"github.com/klaytn/klaytn"
    25  	"github.com/klaytn/klaytn/accounts/abi/bind"
    26  	"github.com/klaytn/klaytn/blockchain"
    27  	"github.com/klaytn/klaytn/blockchain/state"
    28  	"github.com/klaytn/klaytn/blockchain/types"
    29  	"github.com/klaytn/klaytn/blockchain/vm"
    30  	"github.com/klaytn/klaytn/common"
    31  	"github.com/klaytn/klaytn/event"
    32  	"github.com/klaytn/klaytn/node/cn/filters"
    33  	"github.com/klaytn/klaytn/params"
    34  )
    35  
    36  // Maintain separate minimal interfaces of blockchain.BlockChain because ContractBackend are used
    37  // in various situations. BlockChain instances are often passed down as different interfaces such as
    38  // consensus.ChainReader, governance.blockChain, work.BlockChain.
    39  type BlockChainForCaller interface {
    40  	// Required by NewEVMContext
    41  	blockchain.ChainContext
    42  
    43  	// Below is a subset of consensus.ChainReader
    44  	// Only using the vocabulary of consensus.ChainReader for potential
    45  	// usability within consensus package.
    46  	Config() *params.ChainConfig
    47  	GetHeaderByNumber(number uint64) *types.Header
    48  	GetBlock(hash common.Hash, number uint64) *types.Block
    49  	State() (*state.StateDB, error)
    50  	StateAt(root common.Hash) (*state.StateDB, error)
    51  	CurrentBlock() *types.Block
    52  }
    53  
    54  // Maintain separate minimal interfaces of blockchain.TxPool because ContractBackend are used
    55  // in various situations. TxPool instances are often passed down as work.TxPool.
    56  type TxPoolForCaller interface {
    57  	// Below is a subset of work.TxPool
    58  	GetPendingNonce(addr common.Address) uint64
    59  	AddLocal(tx *types.Transaction) error
    60  	GasPrice() *big.Int
    61  }
    62  
    63  // BlockchainContractBackend implements bind.Contract* and bind.DeployBackend, based on
    64  // a user-supplied blockchain.BlockChain instance.
    65  // Its intended purpose is reading system contracts during block processing.
    66  //
    67  // Note that SimulatedBackend creates a new temporary BlockChain for testing,
    68  // whereas BlockchainContractBackend uses an existing BlockChain with existing database.
    69  type BlockchainContractBackend struct {
    70  	bc     BlockChainForCaller
    71  	txPool TxPoolForCaller
    72  	events *filters.EventSystem
    73  }
    74  
    75  // This nil assignment ensures at compile time that BlockchainContractBackend implements bind.Contract* and bind.DeployBackend.
    76  var (
    77  	_ bind.ContractCaller     = (*BlockchainContractBackend)(nil)
    78  	_ bind.ContractTransactor = (*BlockchainContractBackend)(nil)
    79  	_ bind.ContractFilterer   = (*BlockchainContractBackend)(nil)
    80  	_ bind.DeployBackend      = (*BlockchainContractBackend)(nil)
    81  	_ bind.ContractBackend    = (*BlockchainContractBackend)(nil)
    82  )
    83  
    84  // `txPool` is required for bind.ContractTransactor methods and `events` is required for bind.ContractFilterer methods.
    85  // If `tp=nil`, bind.ContractTransactor methods could return errors.
    86  // If `es=nil`, bind.ContractFilterer methods could return errors.
    87  func NewBlockchainContractBackend(bc BlockChainForCaller, tp TxPoolForCaller, es *filters.EventSystem) *BlockchainContractBackend {
    88  	return &BlockchainContractBackend{bc, tp, es}
    89  }
    90  
    91  // bind.ContractCaller defined methods
    92  
    93  func (b *BlockchainContractBackend) CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error) {
    94  	if _, state, err := b.getBlockAndState(blockNumber); err != nil {
    95  		return nil, err
    96  	} else {
    97  		return state.GetCode(account), nil
    98  	}
    99  }
   100  
   101  // Executes a read-only function call with respect to the specified block's state, or latest state if not specified.
   102  //
   103  // Returns call result in []byte.
   104  // Returns error when:
   105  // - cannot find the corresponding block or stateDB
   106  // - VM revert error
   107  // - VM other errors (e.g. NotProgramAccount, OutOfGas)
   108  // - Error outside VM
   109  func (b *BlockchainContractBackend) CallContract(ctx context.Context, call klaytn.CallMsg, blockNumber *big.Int) ([]byte, error) {
   110  	block, state, err := b.getBlockAndState(blockNumber)
   111  	if err != nil {
   112  		return nil, err
   113  	}
   114  
   115  	res, err := b.callContract(call, block, state)
   116  	if err != nil {
   117  		return nil, err
   118  	}
   119  	if len(res.Revert()) > 0 {
   120  		return nil, blockchain.NewRevertError(res)
   121  	}
   122  	return res.Return(), res.Unwrap()
   123  }
   124  
   125  func (b *BlockchainContractBackend) callContract(call klaytn.CallMsg, block *types.Block, state *state.StateDB) (*blockchain.ExecutionResult, error) {
   126  	if call.Gas == 0 {
   127  		call.Gas = uint64(3e8) // enough gas for ordinary contract calls
   128  	}
   129  
   130  	intrinsicGas, err := types.IntrinsicGas(call.Data, nil, call.To == nil, b.bc.Config().Rules(block.Number()))
   131  	if err != nil {
   132  		return nil, err
   133  	}
   134  
   135  	var accessList types.AccessList
   136  	if call.AccessList != nil {
   137  		accessList = *call.AccessList
   138  	}
   139  	msg := types.NewMessage(call.From, call.To, 0, call.Value, call.Gas, call.GasPrice, call.Data,
   140  		false, intrinsicGas, accessList)
   141  
   142  	txContext := blockchain.NewEVMTxContext(msg, block.Header())
   143  	blockContext := blockchain.NewEVMBlockContext(block.Header(), b.bc, nil)
   144  
   145  	// EVM demands the sender to have enough KLAY balance (gasPrice * gasLimit) in buyGas()
   146  	// After KIP-71, gasPrice is nonzero baseFee, regardless of the msg.gasPrice (usually 0)
   147  	// But our sender (usually 0x0) won't have enough balance. Instead we override gasPrice = 0 here
   148  	txContext.GasPrice = big.NewInt(0)
   149  	evm := vm.NewEVM(blockContext, txContext, state, b.bc.Config(), &vm.Config{})
   150  
   151  	return blockchain.ApplyMessage(evm, msg)
   152  }
   153  
   154  func (b *BlockchainContractBackend) getBlockAndState(num *big.Int) (*types.Block, *state.StateDB, error) {
   155  	var block *types.Block
   156  	if num == nil {
   157  		block = b.bc.CurrentBlock()
   158  	} else {
   159  		header := b.bc.GetHeaderByNumber(num.Uint64())
   160  		if header == nil {
   161  			return nil, nil, errBlockDoesNotExist
   162  		}
   163  		block = b.bc.GetBlock(header.Hash(), header.Number.Uint64())
   164  	}
   165  	if block == nil {
   166  		return nil, nil, errBlockDoesNotExist
   167  	}
   168  
   169  	state, err := b.bc.StateAt(block.Root())
   170  	return block, state, err
   171  }
   172  
   173  // bind.ContractTransactor defined methods
   174  
   175  func (b *BlockchainContractBackend) PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error) {
   176  	// TODO-Klaytn this is not pending code but latest code
   177  	state, err := b.bc.State()
   178  	if err != nil {
   179  		return nil, err
   180  	}
   181  	return state.GetCode(account), nil
   182  }
   183  
   184  func (b *BlockchainContractBackend) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) {
   185  	if b.txPool != nil {
   186  		return b.txPool.GetPendingNonce(account), nil
   187  	}
   188  	// TODO-Klaytn this is not pending nonce but latest nonce
   189  	state, err := b.bc.State()
   190  	if err != nil {
   191  		return 0, err
   192  	}
   193  	return state.GetNonce(account), nil
   194  }
   195  
   196  func (b *BlockchainContractBackend) SuggestGasPrice(ctx context.Context) (*big.Int, error) {
   197  	if b.bc.Config().IsMagmaForkEnabled(b.bc.CurrentBlock().Number()) {
   198  		if b.txPool != nil {
   199  			return new(big.Int).Mul(b.txPool.GasPrice(), big.NewInt(2)), nil
   200  		} else {
   201  			return new(big.Int).Mul(b.bc.CurrentBlock().Header().BaseFee, big.NewInt(2)), nil
   202  		}
   203  	} else {
   204  		return new(big.Int).SetUint64(b.bc.Config().UnitPrice), nil
   205  	}
   206  }
   207  
   208  func (b *BlockchainContractBackend) EstimateGas(ctx context.Context, call klaytn.CallMsg) (uint64, error) {
   209  	state, err := b.bc.State()
   210  	if err != nil {
   211  		return 0, err
   212  	}
   213  	balance := state.GetBalance(call.From) // from can't be nil
   214  
   215  	// Create a helper to check if a gas allowance results in an executable transaction
   216  	executable := func(gas uint64) (bool, *blockchain.ExecutionResult, error) {
   217  		call.Gas = gas
   218  
   219  		currentState, err := b.bc.State()
   220  		if err != nil {
   221  			return true, nil, nil
   222  		}
   223  		res, err := b.callContract(call, b.bc.CurrentBlock(), currentState)
   224  		if err != nil {
   225  			if errors.Is(err, blockchain.ErrIntrinsicGas) {
   226  				return true, nil, nil // Special case, raise gas limit
   227  			}
   228  			return true, nil, err // Bail out
   229  		}
   230  		return res.Failed(), res, nil
   231  	}
   232  
   233  	estimated, err := blockchain.DoEstimateGas(ctx, call.Gas, 0, call.Value, call.GasPrice, balance, executable)
   234  	return uint64(estimated), err
   235  }
   236  
   237  func (b *BlockchainContractBackend) SendTransaction(ctx context.Context, tx *types.Transaction) error {
   238  	if b.txPool == nil {
   239  		return errors.New("tx pool not configured")
   240  	}
   241  	return b.txPool.AddLocal(tx)
   242  }
   243  
   244  func (b *BlockchainContractBackend) ChainID(ctx context.Context) (*big.Int, error) {
   245  	return b.bc.Config().ChainID, nil
   246  }
   247  
   248  // bind.ContractFilterer defined methods
   249  
   250  func (b *BlockchainContractBackend) FilterLogs(ctx context.Context, query klaytn.FilterQuery) ([]types.Log, error) {
   251  	// Convert the current block numbers into internal representations
   252  	if query.FromBlock == nil {
   253  		query.FromBlock = big.NewInt(b.bc.CurrentBlock().Number().Int64())
   254  	}
   255  	if query.ToBlock == nil {
   256  		query.ToBlock = big.NewInt(b.bc.CurrentBlock().Number().Int64())
   257  	}
   258  	from := query.FromBlock.Int64()
   259  	to := query.ToBlock.Int64()
   260  
   261  	state, err := b.bc.State()
   262  	if err != nil {
   263  		return nil, err
   264  	}
   265  	bc, ok := b.bc.(*blockchain.BlockChain)
   266  	if !ok {
   267  		return nil, errors.New("BlockChainForCaller is not blockchain.BlockChain")
   268  	}
   269  	filter := filters.NewRangeFilter(&filterBackend{state.Database().TrieDB().DiskDB(), bc}, from, to, query.Addresses, query.Topics)
   270  
   271  	logs, err := filter.Logs(ctx)
   272  	if err != nil {
   273  		return nil, err
   274  	}
   275  	res := make([]types.Log, len(logs))
   276  	for i, log := range logs {
   277  		res[i] = *log
   278  	}
   279  	return res, nil
   280  }
   281  
   282  func (b *BlockchainContractBackend) SubscribeFilterLogs(ctx context.Context, query klaytn.FilterQuery, ch chan<- types.Log) (klaytn.Subscription, error) {
   283  	// Subscribe to contract events
   284  	sink := make(chan []*types.Log)
   285  
   286  	if b.events == nil {
   287  		return nil, errors.New("events system not configured")
   288  	}
   289  	sub, err := b.events.SubscribeLogs(query, sink)
   290  	if err != nil {
   291  		return nil, err
   292  	}
   293  	// Since we're getting logs in batches, we need to flatten them into a plain stream
   294  	return event.NewSubscription(func(quit <-chan struct{}) error {
   295  		defer sub.Unsubscribe()
   296  		for {
   297  			select {
   298  			case logs := <-sink:
   299  				for _, log := range logs {
   300  					select {
   301  					case ch <- *log:
   302  					case err := <-sub.Err():
   303  						return err
   304  					case <-quit:
   305  						return nil
   306  					}
   307  				}
   308  			case err := <-sub.Err():
   309  				return err
   310  			case <-quit:
   311  				return nil
   312  			}
   313  		}
   314  	}), nil
   315  }
   316  
   317  // bind.DeployBackend defined methods
   318  
   319  func (b *BlockchainContractBackend) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) {
   320  	bc, ok := b.bc.(*blockchain.BlockChain)
   321  	if !ok {
   322  		return nil, errors.New("BlockChainForCaller is not blockchain.BlockChain")
   323  	}
   324  	receipt := bc.GetReceiptByTxHash(txHash)
   325  	if receipt != nil {
   326  		return receipt, nil
   327  	}
   328  	return nil, errors.New("receipt does not exist")
   329  }
   330  
   331  // sc.Backend requires BalanceAt and CurrentBlockNumber
   332  
   333  func (b *BlockchainContractBackend) BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error) {
   334  	if _, state, err := b.getBlockAndState(blockNumber); err != nil {
   335  		return nil, err
   336  	} else {
   337  		return state.GetBalance(account), nil
   338  	}
   339  }
   340  
   341  func (b *BlockchainContractBackend) CurrentBlockNumber(ctx context.Context) (uint64, error) {
   342  	return b.bc.CurrentBlock().NumberU64(), nil
   343  }