8000 Add BlockchainContractCaller by blukat29 · Pull Request #1828 · klaytn/klaytn · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content
This repository was archived by the owner on Aug 19, 2024. It is now read-only.

Add BlockchainContractCaller #1828

Merged
merged 11 commits into from
Jul 14, 2023
140 changes: 140 additions & 0 deletions accounts/abi/bind/backends/blockchain.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// Copyright 2023 The klaytn Authors
// This file is part of the klaytn library.
//
// The klaytn library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The klaytn library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the klaytn library. If not, see <http://www.gnu.org/licenses/>.

package backends

import (
"context"
"math/big"

"github.com/klaytn/klaytn"
"github.com/klaytn/klaytn/accounts/abi/bind"
"github.com/klaytn/klaytn/blockchain"
"github.com/klaytn/klaytn/blockchain/state"
"github.com/klaytn/klaytn/blockchain/types"
"github.com/klaytn/klaytn/blockchain/vm"
"github.com/klaytn/klaytn/common"
"github.com/klaytn/klaytn/params"
)

// Maintain separate minimal interfaces of blockchain.BlockChain because ContractBackend are used
// in various situations. BlockChain instances are often passed down as different interfaces such as
// consensus.ChainReader, governance.blockChain, work.BlockChain.
type BlockChainForCaller interface {
// Required by NewEVMContext
blockchain.ChainContext

// Below is a subset of consensus.ChainReader
// Only using the vocabulary of consensus.ChainReader for potential
// usability within consensus package.
Config() *params.ChainConfig
CurrentHeader() *types.Header
GetHeaderByNumber(number uint64) *types.Header
GetBlock(hash common.Hash, number uint64) *types.Block
State() (*state.StateDB, error)
StateAt(root common.Hash) (*state.StateDB, error)
}

// BlockchainContractCaller implements bind.ContractCaller, based on
// a user-supplied blockchain.BlockChain instance.
// Its intended purpose is reading system contracts during block processing.
//
// Note that SimulatedBackend creates a new temporary BlockChain for testing,
// whereas BlockchainContractCaller uses an existing BlockChain with existing database.
type BlockchainContractCaller struct {
bc BlockChainForCaller
}

// This nil assignment ensures at compile time that BlockchainContractCaller implements bind.ContractCaller.
var _ bind.ContractCaller = (*BlockchainContractCaller)(nil)

func NewBlockchainContractCaller(bc BlockChainForCaller) *BlockchainContractCaller {
return &BlockchainContractCaller{
bc: bc,
}
}

func (b *BlockchainContractCaller) CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error) {
if _, state, err := b.getBlockAndState(blockNumber); err != nil {
return nil, err
} else {
return state.GetCode(account), nil
}
}

// Executes a read-only function call with respect to the specified block's state, or latest state if not specified.
//
// Returns call result in []byte.
// Returns error when:
// - cannot find the corresponding block or stateDB
// - VM revert error
// - VM other errors (e.g. NotProgramAccount, OutOfGas)
// - Error outside VM
func (b *BlockchainContractCaller) CallContract(ctx context.Context, call klaytn.CallMsg, blockNumber *big.Int) ([]byte, error) {
block, state, err := b.getBlockAndState(blockNumber)
if err != nil {
return nil, err
}

res, err := b.callContract(call, block, state)
if err != nil {
return nil, err
}
if len(res.Revert()) > 0 {
return nil, newRevertError(res)
}
return res.Return(), res.Unwrap()
}

func (b *BlockchainContractCaller) callContract(call klaytn.CallMsg, block *types.Block, state *state.StateDB) (*blockchain.ExecutionResult, error) {
if call.Gas == 0 {
call.Gas = uint64(3e8) // enough gas for ordinary contract calls
}

intrinsicGas, err := types.IntrinsicGas(call.Data, nil, call.To == nil, b.bc.Config().Rules(block.Number()))
if err != nil {
return nil, err
}

msg := types.NewMessage(call.From, call.To, 0, call.Value, call.Gas, call.GasPrice, call.Data,
false, intrinsicGas)

evmContext := blockchain.NewEVMContext(msg, block.Header(), b.bc, nil)
// EVM demands the sender to have enough KLAY balance (gasPrice * gasLimit) in buyGas()
// After KIP-71, gasPrice is nonzero baseFee, regardless of the msg.gasPrice (usually 0)
// But our sender (usually 0x0) won't have enough balance. Instead we override gasPrice = 0 here
evmContext.GasPrice = big.NewInt(0)
evm := vm.NewEVM(evmContext, state, b.bc.Config(), &vm.Config{})

return blockchain.ApplyMessage(evm, msg)
}

func (b *BlockchainContractCaller) getBlockAndState(num *big.Int) (*types.Block, *state.StateDB, error) {
var header *types.Header
if num == nil {
header = b.bc.CurrentHeader()
} else {
header = b.bc.GetHeaderByNumber(num.Uint64())
}

if header == nil {
return nil, nil, errBlockDoesNotExist
}

block := b.bc.GetBlock(header.Hash(), header.Number.Uint64())
state, err := b.bc.StateAt(block.Root())
return block, state, err
}
149 changes: 149 additions & 0 deletions accounts/abi/bind/backends/blockchain_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// Copyright 2023 The klaytn Authors
// This file is part of the klaytn library.
//
// The klaytn library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The klaytn library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the klaytn library. If not, see <http://www.gnu.org/licenses/>.

package backends

import (
"context"
"errors"
"math/big"
"strings"
"testing"

"github.com/klaytn/klaytn"
"github.com/klaytn/klaytn/accounts/abi"
"github.com/klaytn/klaytn/blockchain"
"github.com/klaytn/klaytn/blockchain/vm"
"github.com/klaytn/klaytn/common"
"github.com/klaytn/klaytn/consensus/gxhash"
"github.com/klaytn/klaytn/crypto"
"github.com/klaytn/klaytn/params"
"github.com/klaytn/klaytn/storage/database"
"github.com/stretchr/testify/assert"
)

var (
testAddr = crypto.PubkeyToAddress(testKey.PublicKey)
code1Addr = common.HexToAddress("0x1111111111111111111111111111111111111111")
code2Addr = common.HexToAddress("0x2222222222222222222222222222222222222222")

parsedAbi1, _ = abi.JSON(strings.NewReader(abiJSON))
parsedAbi2, _ = abi.JSON(strings.NewReader(reverterABI))
code1Bytes = common.FromHex(deployedCode)
code2Bytes = common.FromHex(reverterDeployedBin)
)

func newTestBlockchain() *blockchain.BlockChain {
config := params.TestChainConfig.Copy()
alloc := blockchain.GenesisAlloc{
testAddr: {Balance: big.NewInt(10000000000)},
code1Addr: {Balance: big.NewInt(0), Code: code1Bytes},
code2Addr: {Balance: big.NewInt(0), Code: code2Bytes},
}

db := database.NewMemoryDBManager()
genesis := blockchain.Genesis{Config: config, Alloc: alloc}
genesis.MustCommit(db)

bc, _ := blockchain.NewBlockChain(db, nil, genesis.Config, gxhash.NewFaker(), vm.Config{})

// Append 10 blocks to test with block numbers other than 0
block := bc.CurrentBlock()
blocks, _ := blockchain.GenerateChain(config, block, gxhash.NewFaker(), db, 10, func(i int, b *blockchain.BlockGen) {})
bc.InsertChain(blocks)

return bc
}

func TestBlockchainCodeAt(t *testing.T) {
bc := newTestBlockchain()
c := NewBlockchainContractCaller(bc)

// Normal cases
code, err := c.CodeAt(context.Background(), code1Addr, nil)
assert.Nil(t, err)
assert.Equal(t, code1Bytes, code)

code, err = c.CodeAt(context.Background(), code2Addr, nil)
assert.Nil(t, err)
assert.Equal(t, code2Bytes, code)

code, err = c.CodeAt(context.Background(), code1Addr, common.Big0)
assert.Nil(t, err)
assert.Equal(t, code1Bytes, code)

code, err = c.CodeAt(context.Background(), code1Addr, common.Big1)
assert.Nil(t, err)
assert.Equal(t, code1Bytes, code)

code, err = c.CodeAt(context.Background(), code1Addr, big.NewInt(10))
assert.Nil(t, err)
assert.Equal(t, code1Bytes, code)

// Non-code address
code, err = c.CodeAt(context.Background(), testAddr, nil)
assert.True(t, code == nil && err == nil)

// Invalid block number
code, err = c.CodeAt(context.Background(), code1Addr, big.NewInt(11))
assert.True(t, code == nil && err == errBlockDoesNotExist)
}

func TestBlockchainCallContract(t *testing.T) {
bc := newTestBlockchain()
c := NewBlockchainContractCaller(bc)

data_receive, _ := parsedAbi1.Pack("receive", []byte("X"))
data_revertString, _ := parsedAbi2.Pack("revertString")
data_revertNoString, _ := parsedAbi2.Pack("revertNoString")

// Normal case
ret, err := c.CallContract(context.Background(), klaytn.CallMsg{
From: testAddr,
To: &code1Addr,
Gas: 1000000,
Data: data_receive,
}, nil)
assert.Nil(t, err)
assert.Equal(t, expectedReturn, ret)

// Error outside VM - Intrinsic Gas
ret, err = c.CallContract(context.Background(), klaytn.CallMsg{
From: testAddr,
To: &code1Addr,
Gas: 20000,
Data: data_receive,
}, nil)
assert.True(t, errors.Is(err, blockchain.ErrIntrinsicGas))

// VM revert error - empty reason
ret, err = c.CallContract(context.Background(), klaytn.CallMsg{
From: testAddr,
To: &code2Addr,
Gas: 100000,
Data: data_revertNoString,
}, nil)
assert.Equal(t, "execution reverted: ", err.Error())

// VM revert error - string reason
ret, err = c.CallContract(context.Background(), klaytn.CallMsg{
From: testAddr,
To: &code2Addr,
Gas: 100000,
Data: data_revertString,
}, nil)
assert.Equal(t, "execution reverted: some error", err.Error())
}
9 changes: 6 additions & 3 deletions accounts/abi/bind/backends/simulated_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -964,15 +964,18 @@ contract Reverter {
}
}
}*/
var (
reverterABI = `[{"inputs": [],"name": "noRevert","outputs": [],"stateMutability": "pure","type": "function"},{"inputs": [],"name": "revertASM","outputs": [],"stateMutability": "pure","type": "function"},{"inputs": [],"name": "revertNoString","outputs": [],"stateMutability": "pure","type": "function"},{"inputs": [],"name": "revertString","outputs": [],"stateMutability": "pure","type": "function"}]`
reverterBin = "608060405234801561001057600080fd5b506101d3806100206000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c80634b409e01146100515780639b340e361461005b5780639bd6103714610065578063b7246fc11461006f575b600080fd5b610059610079565b005b6100636100ca565b005b61006d6100cf565b005b610077610145565b005b60006100c8576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526000815260200160200191505060405180910390fd5b565b600080fd5b6000610143576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600a8152602001807f736f6d65206572726f720000000000000000000000000000000000000000000081525060200191505060405180910390fd5b565b7f08c379a0000000000000000000000000000000000000000000000000000000006000526020600452600a6024527f736f6d65206572726f720000000000000000000000000000000000000000000060445260646000f3fea2646970667358221220cdd8af0609ec4996b7360c7c780bad5c735740c64b1fffc3445aa12d37f07cb164736f6c63430006070033"
reverterDeployedBin = "608060405234801561001057600080fd5b506004361061004c5760003560e01c80634b409e01146100515780639b340e361461005b5780639bd6103714610065578063b7246fc11461006f575b600080fd5b610059610079565b005b6100636100ca565b005b61006d6100cf565b005b610077610145565b005b60006100c8576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526000815260200160200191505060405180910390fd5b565b600080fd5b6000610143576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600a8152602001807f736f6d65206572726f720000000000000000000000000000000000000000000081525060200191505060405180910390fd5b565b7f08c379a0000000000000000000000000000000000000000000000000000000006000526020600452600a6024527f736f6d65206572726f720000000000000000000000000000000000000000000060445260646000f3fea2646970667358221220cdd8af0609ec4996b7360c7c780bad5c735740c64b1fffc3445aa12d37f07cb164736f6c63430006070033"
)

func TestSimulatedBackend_CallContractRevert(t *testing.T) {
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
sim := simTestBackend(testAddr)
defer sim.Close()
bgCtx := context.Background()

reverterABI := `[{"inputs": [],"name": "noRevert","outputs": [],"stateMutability": "pure","type": "function"},{"inputs": [],"name": "revertASM","outputs": [],"stateMutability": "pure","type": "function"},{"inputs": [],"name": "revertNoString","outputs": [],"stateMutability": "pure","type": "function"},{"inputs": [],"name": "revertString","outputs": [],"stateMutability": "pure","type": "function"}]`
reverterBin := "608060405234801561001057600080fd5b506101d3806100206000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c80634b409e01146100515780639b340e361461005b5780639bd6103714610065578063b7246fc11461006f575b600080fd5b610059610079565b005b6100636100ca565b005b61006d6100cf565b005b610077610145565b005b60006100c8576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526000815260200160200191505060405180910390fd5b565b600080fd5b6000610143576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600a8152602001807f736f6d65206572726f720000000000000000000000000000000000000000000081525060200191505060405180910390fd5b565b7f08c379a0000000000000000000000000000000000000000000000000000000006000526020600452600a6024527f736f6d65206572726f720000000000000000000000000000000000000000000060445260646000f3fea2646970667358221220cdd8af0609ec4996b7360c7c780bad5c735740c64b1fffc3445aa12d37f07cb164736f6c63430006070033"

parsed, err := abi.JSON(strings.NewReader(reverterABI))
if err != nil {
t.Errorf("could not get code at test addr: %v", err)
Expand Down
4 changes: 4 additions & 0 deletions blockchain/headerchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -446,3 +446,7 @@ func (hc *HeaderChain) GetBlock(hash common.Hash, number uint64) *types.Block {
func (hc *HeaderChain) State() (*state.StateDB, error) {
return nil, errors.New("HeaderChain does not support State() method")
}

func (hc *HeaderChain) StateAt(root common.Hash) (*state.StateDB, error) {
return nil, errors.New("HeaderChain does not support StateAt() method")
}
3 changes: 3 additions & 0 deletions consensus/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ type ChainReader interface {

// State() retrieves statedb
State() (*state.StateDB, error)

// StateAt() retrieves statedb on a particular point in time
StateAt(root common.Hash) (*state.StateDB, error)
}

//go:generate mockgen -destination=consensus/mocks/engine_mock.go -package=mocks github.com/klaytn/klaytn/consensus Engine
Expand Down
3 changes: 2 additions & 1 deletion consensus/istanbul/backend/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"time"

lru "github.com/hashicorp/golang-lru"
"github.com/klaytn/klaytn/accounts/abi/bind/backends"
"github.com/klaytn/klaytn/blockchain/state"
"github.com/klaytn/klaytn/blockchain/types"
"github.com/klaytn/klaytn/common"
Expand Down Expand Up @@ -509,7 +510,7 @@ func (sb *backend) Finalize(chain consensus.ChainReader, header *types.Header, s
if chain.Config().IsKIP103ForkBlock(header.Number) {
// RebalanceTreasury can modify the global state (state),
// so the existing state db should be used to apply the rebalancing result.
c := &Kip103ContractCaller{state, chain, header}
c := backends.NewBlockchainContractCaller(chain)
result, err := RebalanceTreasury(state, chain, header, c)
if err != nil {
logger.Error("failed to execute treasury rebalancing (KIP-103). State not changed", "err", err)
Expand Down
Loading
0