Skip to content

Commit

Permalink
feat: --mode contract-call added
Browse files Browse the repository at this point in the history
This introduced new flags. In order to use `--mode contract-call`, the following must be passed in:
- `--contract-address`
- `--calldata`
- if the contract function is `payable`, then the `--contract-call-payable` should also be passed in
  • Loading branch information
IdrisHanafi committed Dec 4, 2023
1 parent afcaed6 commit 6abdaae
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 53 deletions.
10 changes: 9 additions & 1 deletion cmd/loadtest/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ type (
LegacyTransactionMode *bool
SendOnly *bool
RecallLength *uint64
ContractAddress *string
ContractCallData *string
ContractCallPayable *bool

// Computed
CurrentGasPrice *big.Int
Expand All @@ -80,6 +83,7 @@ type (
ECDSAPrivateKey *ecdsa.PrivateKey
FromETHAddress *ethcommon.Address
ToETHAddress *ethcommon.Address
ContractETHAddress *ethcommon.Address
SendAmount *big.Int
CurrentBaseFee *big.Int
ChainSupportBaseFee bool
Expand Down Expand Up @@ -237,14 +241,18 @@ r - random modes
7 - ERC721 mints
v3 - UniswapV3 swaps
R - total recall
rpc - call random rpc methods`)
rpc - call random rpc methods
cc, contract-call - call a contract method`)
ltp.Function = LoadtestCmd.Flags().Uint64P("function", "f", 1, "A specific function to be called if running with `--mode f` or a specific precompiled contract when running with `--mode a`")
ltp.ByteCount = LoadtestCmd.Flags().Uint64P("byte-count", "b", 1024, "If we're in store mode, this controls how many bytes we'll try to store in our contract")
ltp.LtAddress = LoadtestCmd.Flags().String("lt-address", "", "The address of a pre-deployed load test contract")
ltp.ERC20Address = LoadtestCmd.Flags().String("erc20-address", "", "The address of a pre-deployed ERC20 contract")
ltp.ERC721Address = LoadtestCmd.Flags().String("erc721-address", "", "The address of a pre-deployed ERC721 contract")
ltp.ForceContractDeploy = LoadtestCmd.Flags().Bool("force-contract-deploy", false, "Some load test modes don't require a contract deployment. Set this flag to true to force contract deployments. This will still respect the --lt-address flags.")
ltp.RecallLength = LoadtestCmd.Flags().Uint64("recall-blocks", 50, "The number of blocks that we'll attempt to fetch for recall")
ltp.ContractAddress = LoadtestCmd.Flags().String("contract-address", "", "The address of the contract that will be used in `--mode contract-call`. This must be paired up with `--mode contract-call` and `--calldata`")
ltp.ContractCallData = LoadtestCmd.Flags().String("calldata", "", "The hex encoded calldata passed in. The format is function signature + arguments encoded together. This must be paired up with `--mode contract-call` and `--contract-address`")
ltp.ContractCallPayable = LoadtestCmd.Flags().Bool("contract-call-payable", false, "Use this flag if the `--function-sig` is a `payable` function, the value amount passed will be from `--eth-amount`. This must be paired up with `--mode contract-call` and `--contract-address`")

inputLoadTestParams = *ltp

Expand Down
92 changes: 92 additions & 0 deletions cmd/loadtest/loadtest.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package loadtest
import (
"context"
_ "embed"
"encoding/hex"
"errors"
"fmt"
"io"
Expand Down Expand Up @@ -56,6 +57,7 @@ const (
loadTestModeRandom
loadTestModeRecall
loadTestModeRPC
loadTestModeContractCall
loadTestModeUniswapV3

codeQualitySeed = "code code code code code code code code code code code quality"
Expand Down Expand Up @@ -92,6 +94,8 @@ func characterToLoadTestMode(mode string) (loadTestMode, error) {
return loadTestModeUniswapV3, nil
case "rpc":
return loadTestModeRPC, nil
case "cc", "contract-call":
return loadTestModeContractCall, nil
default:
return 0, fmt.Errorf("unrecognized load test mode: %s", mode)
}
Expand Down Expand Up @@ -210,6 +214,9 @@ func initializeLoadTestParams(ctx context.Context, c *ethclient.Client) error {
return errors.New("the adaptive rate limit is based on the pending transaction pool. It doesn't use this feature while also using call only")
}

contractAddr := ethcommon.HexToAddress(*inputLoadTestParams.ContractAddress)
inputLoadTestParams.ContractETHAddress = &contractAddr

inputLoadTestParams.ToETHAddress = &toAddr
inputLoadTestParams.SendAmount = amt
inputLoadTestParams.CurrentGasPrice = gas
Expand All @@ -236,6 +243,7 @@ func initializeLoadTestParams(ctx context.Context, c *ethclient.Client) error {
}

if len(modes) > 1 {
log.Debug().Msgf("FOUND MORE THAN 1 MODE %+v", modes)
inputLoadTestParams.MultiMode = true
} else {
inputLoadTestParams.MultiMode = false
Expand All @@ -251,6 +259,9 @@ func initializeLoadTestParams(ctx context.Context, c *ethclient.Client) error {
log.Trace().Msg("Setting call only mode since we're doing RPC testing")
*inputLoadTestParams.CallOnly = true
}
if hasMode(loadTestModeContractCall, inputLoadTestParams.ParsedModes) && (*inputLoadTestParams.ContractAddress == "" || *inputLoadTestParams.ContractCallData == "") {
return errors.New("`--contract-call` and `--contract-send` requires both `--contract-address` and `--calldata` args.")
}
// TODO check for duplicate modes?

if *inputLoadTestParams.CallOnly && *inputLoadTestParams.AdaptiveRateLimit {
Expand Down Expand Up @@ -295,6 +306,7 @@ func completeLoadTest(ctx context.Context, c *ethclient.Client, rpc *ethrpc.Clie
log.Error().Err(err).Msg("There was an issue waiting for all transactions to be mined")
}
if len(loadTestResults) == 0 {
log.Info().Msg("CallOnly mode enabled - blocks aren't mined")
return errors.New("no transactions observed")
}

Expand Down Expand Up @@ -600,6 +612,8 @@ func mainLoop(ctx context.Context, c *ethclient.Client, rpc *ethrpc.Client) erro
startReq, endReq, tErr = runUniswapV3Loadtest(ctx, c, myNonceValue, uniswapV3Config, poolConfig, swapAmountIn)
case loadTestModeRPC:
startReq, endReq, tErr = loadTestRPC(ctx, c, myNonceValue, indexedActivity)
case loadTestModeContractCall:
startReq, endReq, tErr = loadTestContractCall(ctx, c, myNonceValue)
default:
log.Error().Str("mode", mode.String()).Msg("We've arrived at a load test mode that we don't recognize")
}
Expand Down Expand Up @@ -1233,6 +1247,80 @@ func loadTestRPC(ctx context.Context, c *ethclient.Client, nonce uint64, ia *Ind
return
}

func loadTestContractCall(ctx context.Context, c *ethclient.Client, nonce uint64) (t1 time.Time, t2 time.Time, err error) {
ltp := inputLoadTestParams

to := ltp.ContractETHAddress

chainID := new(big.Int).SetUint64(*ltp.ChainID)
privateKey := ltp.ECDSAPrivateKey

tops, err := bind.NewKeyedTransactorWithChainID(privateKey, chainID)
if err != nil {
log.Error().Err(err).Msg("Unable create transaction signer")
return
}

amount := big.NewInt(0)
if *ltp.ContractCallPayable {
amount = ltp.SendAmount
}

tops = configureTransactOpts(tops)
gasPrice, gasTipCap := getSuggestedGasPrices(ctx, c)

calldata, err := hex.DecodeString(strings.TrimPrefix(*ltp.ContractCallData, "0x"))

Check failure on line 1272 in cmd/loadtest/loadtest.go

View workflow job for this annotation

GitHub Actions / Lint

ineffectual assignment to err (ineffassign)
estimateInput := ethereum.CallMsg{
To: to,
Value: amount,
Data: calldata,
}
tops.GasLimit, err = c.EstimateGas(ctx, estimateInput)
if err != nil {
log.Error().Err(err).Msg("Unable to estimate gas for transaction")
return
}

var tx *ethtypes.Transaction
if *ltp.LegacyTransactionMode {
tx = ethtypes.NewTx(&ethtypes.LegacyTx{
Nonce: nonce,
To: to,
Value: amount,
Gas: tops.GasLimit,
GasPrice: gasPrice,
Data: calldata,
})
} else {
tx = ethtypes.NewTx(&ethtypes.DynamicFeeTx{
ChainID: chainID,
Nonce: nonce,
To: to,
Gas: tops.GasLimit,
GasFeeCap: gasPrice,
GasTipCap: gasTipCap,
Data: calldata,
Value: amount,
})
}
log.Trace().Interface("tx", tx).Msg("Contract call data")

stx, err := tops.Signer(*ltp.FromETHAddress, tx)
if err != nil {
log.Error().Err(err).Msg("Unable to sign transaction")
return
}

t1 = time.Now()
defer func() { t2 = time.Now() }()
if *ltp.CallOnly {
_, err = c.CallContract(ctx, txToCallMsg(stx), nil)
} else {
err = c.SendTransaction(ctx, stx)
}
return
}

func recordSample(goRoutineID, requestID int64, err error, start, end time.Time, nonce uint64) {
s := loadTestSample{}
s.GoRoutineID = goRoutineID
Expand Down Expand Up @@ -1317,6 +1405,9 @@ func waitForFinalBlock(ctx context.Context, c *ethclient.Client, rpc *ethrpc.Cli
if err != nil {
return 0, err
}
if *ltp.CallOnly {
return lastBlockNumber, nil
}
currentNonceForFinalBlock, err = c.NonceAt(ctx, *ltp.FromETHAddress, new(big.Int).SetUint64(lastBlockNumber))
if err != nil {
return 0, err
Expand All @@ -1328,6 +1419,7 @@ func waitForFinalBlock(ctx context.Context, c *ethclient.Client, rpc *ethrpc.Cli
maxWaitCount = maxWaitCount - 1 // only decrement if currentNonceForFinalBlock doesn't progress
}
prevNonceForFinalBlock = currentNonceForFinalBlock
log.Trace().Int("Remaining Attempts", maxWaitCount).Msg("Retrying...")
continue
}
if maxWaitCount <= 0 {
Expand Down
7 changes: 4 additions & 3 deletions cmd/loadtest/loadtestmode_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 6abdaae

Please sign in to comment.