github.com/klaytn/klaytn@v1.10.2/tests/fee_payer_contract_test.go (about)

     1  // Copyright 2019 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 tests
    18  
    19  import (
    20  	"math/big"
    21  	"strings"
    22  	"testing"
    23  	"time"
    24  
    25  	"github.com/klaytn/klaytn/accounts/abi"
    26  	"github.com/klaytn/klaytn/blockchain/types"
    27  	"github.com/klaytn/klaytn/common"
    28  	"github.com/klaytn/klaytn/common/profile"
    29  	"github.com/stretchr/testify/assert"
    30  )
    31  
    32  // TestFeePayerContract tests a direct call of precompiled contract 0xa (feepayer).
    33  // - It tests a contract `FeePayer` in fee_payer_test.sol.
    34  // - It directly calls the precompiled contract 0xa.
    35  func TestFeePayerContract(t *testing.T) {
    36  	contractFunctions := []string{"GetFeePayerDirect", "GetFeePayer"}
    37  
    38  	for _, c := range contractFunctions {
    39  		t.Run(c, func(t *testing.T) {
    40  			testFeePayerContract(t, c)
    41  		})
    42  	}
    43  }
    44  
    45  // TestFeePayerContract tests an indirect call of precompiled contract 0xa (feepayer).
    46  // - It tests a contract `FeePayerIndirect` in fee_payer_indirect_test.sol.
    47  // - It calls a deployed contract calling the precompiled contract 0xa.
    48  // TODO-Klaytn-FeePayer: need more test cases for other calls such as delegatecall, etc.
    49  func TestFeePayerContractIndirect(t *testing.T) {
    50  	contractFunctions := []string{"TestCall"}
    51  
    52  	for _, c := range contractFunctions {
    53  		t.Run(c, func(t *testing.T) {
    54  			testFeePayerContractIndirect(t, c)
    55  		})
    56  	}
    57  }
    58  
    59  // testFeePayerContract tests the fee-payer precompiled contract.
    60  // 1. Deploy the FeePayer contract.
    61  // 2. Make an input data for the contract call. The function name is given as a parameter.
    62  // 3. Call the given function `fn`.
    63  // 4. Check the returned value with the specified fee payer.
    64  func testFeePayerContract(t *testing.T, fn string) {
    65  	prof := profile.NewProfiler()
    66  
    67  	// Initialize blockchain
    68  	start := time.Now()
    69  	bcdata, err := NewBCData(2000, 4)
    70  	if err != nil {
    71  		t.Fatal(err)
    72  	}
    73  	prof.Profile("main_init_blockchain", time.Now().Sub(start))
    74  	defer bcdata.Shutdown()
    75  
    76  	// Initialize address-balance map for verification
    77  	start = time.Now()
    78  	accountMap := NewAccountMap()
    79  	if err := accountMap.Initialize(bcdata); err != nil {
    80  		t.Fatal(err)
    81  	}
    82  	prof.Profile("main_init_accountMap", time.Now().Sub(start))
    83  
    84  	// 1. Deploy the contract `FeePayer`.
    85  	start = time.Now()
    86  	filepath := "../contracts/feepayer/fee_payer_test.sol"
    87  	contracts, err := deployContract(filepath, bcdata, accountMap, prof)
    88  	if err != nil {
    89  		t.Fatal(err)
    90  	}
    91  	prof.Profile("main_deployContract", time.Now().Sub(start))
    92  
    93  	signer := types.MakeSigner(bcdata.bc.Config(), bcdata.bc.CurrentHeader().Number)
    94  	for _, c := range contracts {
    95  		abii, err := abi.JSON(strings.NewReader(c.abi))
    96  
    97  		// 2. Make an input data for the contract call. The function name is given as a parameter.
    98  		data, err := abii.Pack(fn)
    99  		if err != nil {
   100  			t.Fatal(err)
   101  		}
   102  
   103  		n := accountMap.GetNonce(*bcdata.addrs[0])
   104  
   105  		tx, err := types.NewTransactionWithMap(types.TxTypeFeeDelegatedSmartContractExecution, map[types.TxValueKeyType]interface{}{
   106  			types.TxValueKeyNonce:    n,
   107  			types.TxValueKeyFeePayer: *bcdata.addrs[1],
   108  			types.TxValueKeyGasPrice: big.NewInt(0),
   109  			types.TxValueKeyGasLimit: uint64(5000000),
   110  			types.TxValueKeyFrom:     *bcdata.addrs[0],
   111  			types.TxValueKeyAmount:   big.NewInt(0),
   112  			types.TxValueKeyTo:       c.address,
   113  			types.TxValueKeyData:     data,
   114  		})
   115  		assert.Equal(t, nil, err)
   116  
   117  		err = tx.Sign(signer, bcdata.privKeys[0])
   118  		assert.Equal(t, nil, err)
   119  
   120  		err = tx.SignFeePayer(signer, bcdata.privKeys[1])
   121  		assert.Equal(t, nil, err)
   122  
   123  		// 3. Call the given function `fn`.
   124  		ret, err := callContract(bcdata, tx)
   125  		assert.Equal(t, nil, err)
   126  
   127  		// 4. Check the returned value with the specified fee payer.
   128  		var feePayer common.Address
   129  		if err := abii.Unpack(&feePayer, fn, ret); err != nil {
   130  			t.Fatal(err)
   131  		}
   132  		assert.Equal(t, *bcdata.addrs[1], feePayer)
   133  	}
   134  }
   135  
   136  // testFeePayerContract tests an indirect call of the fee-payer precompiled contract.
   137  // 1. Deploy the FeePayer contract.
   138  // 2. Deploy the FeePayerIndirect contract.
   139  // 3. Make an input data for the contract call. The function name is given as a parameter.
   140  // 4. Call the given function `fn`.
   141  // 5. Check the returned value with the specified fee payer.
   142  func testFeePayerContractIndirect(t *testing.T, fn string) {
   143  	prof := profile.NewProfiler()
   144  
   145  	callee_path := "../contracts/feepayer/fee_payer_test.sol"
   146  	caller_path := "../contracts/feepayer/fee_payer_indirect_test.sol"
   147  
   148  	// Initialize blockchain
   149  	start := time.Now()
   150  	bcdata, err := NewBCData(2000, 4)
   151  	if err != nil {
   152  		t.Fatal(err)
   153  	}
   154  	prof.Profile("main_init_blockchain", time.Now().Sub(start))
   155  	defer bcdata.Shutdown()
   156  
   157  	// Initialize address-balance map for verification
   158  	start = time.Now()
   159  	accountMap := NewAccountMap()
   160  	if err := accountMap.Initialize(bcdata); err != nil {
   161  		t.Fatal(err)
   162  	}
   163  	prof.Profile("main_init_accountMap", time.Now().Sub(start))
   164  
   165  	// 1. Deploy the FeePayer contract.
   166  	callee_contracts, err := deployContract(callee_path, bcdata, accountMap, prof)
   167  	assert.Equal(t, nil, err)
   168  	var callee deployedContract
   169  	for _, v := range callee_contracts {
   170  		callee = *v
   171  		break
   172  	}
   173  
   174  	// 2. Deploy the FeePayerIndirect contract.
   175  	start = time.Now()
   176  	caller_contracts, err := deployContract(caller_path, bcdata, accountMap, prof)
   177  	assert.Equal(t, nil, err)
   178  
   179  	prof.Profile("main_deployContract", time.Now().Sub(start))
   180  
   181  	signer := types.MakeSigner(bcdata.bc.Config(), bcdata.bc.CurrentHeader().Number)
   182  	{
   183  		var c deployedContract
   184  		for k, v := range caller_contracts {
   185  			if strings.Contains(k, "FeePayerIndirect") {
   186  				c = *v
   187  				break
   188  			}
   189  		}
   190  		abii, err := abi.JSON(strings.NewReader(c.abi))
   191  
   192  		// 3. Make an input data for the contract call. The function name is given as a parameter.
   193  		data, err := abii.Pack(fn, callee.address)
   194  		if err != nil {
   195  			t.Fatal(err)
   196  		}
   197  
   198  		n := accountMap.GetNonce(*bcdata.addrs[0])
   199  
   200  		tx, err := types.NewTransactionWithMap(types.TxTypeFeeDelegatedSmartContractExecution, map[types.TxValueKeyType]interface{}{
   201  			types.TxValueKeyNonce:    n,
   202  			types.TxValueKeyFeePayer: *bcdata.addrs[1],
   203  			types.TxValueKeyGasPrice: big.NewInt(0),
   204  			types.TxValueKeyGasLimit: uint64(5000000),
   205  			types.TxValueKeyFrom:     *bcdata.addrs[0],
   206  			types.TxValueKeyAmount:   big.NewInt(0),
   207  			types.TxValueKeyTo:       c.address,
   208  			types.TxValueKeyData:     data,
   209  		})
   210  		assert.Equal(t, nil, err)
   211  
   212  		err = tx.Sign(signer, bcdata.privKeys[0])
   213  		assert.Equal(t, nil, err)
   214  
   215  		err = tx.SignFeePayer(signer, bcdata.privKeys[1])
   216  		assert.Equal(t, nil, err)
   217  
   218  		// 4. Call the given function `fn`.
   219  		ret, err := callContract(bcdata, tx)
   220  		assert.Equal(t, nil, err)
   221  
   222  		// 5. Check the returned value with the specified fee payer.
   223  		var feePayer common.Address
   224  		if err := abii.Unpack(&feePayer, fn, ret); err != nil {
   225  			t.Fatal(err)
   226  		}
   227  		assert.Equal(t, *bcdata.addrs[1], feePayer)
   228  	}
   229  }