Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(cast): Harden cast send and provide more accurate error messages #4874

Merged
merged 7 commits into from
May 9, 2023

Conversation

Evalir
Copy link
Member

@Evalir Evalir commented May 4, 2023

Motivation

Closes #4831.

cast send right now assumes that if a signer can't be constructed, it must fall back to eth_sendTransaction in all cases. This is not exactly true, and will result in misleading error messages depending on the combination of flags passed in for the wanted signing method. Particular highlights were:

  • If a path to a folder that contains a keystore was passed in, it would try and walk the directory to try and find it. However, keystores no longer contain the address field that was being used to identify the correct keystore, so they were never found. This caused cast send to fall back to eth_sendTransaction hoping the remote RPC had the unlocked account to sign the tx.
  • If the ledger option was passed in but the ledger was not available or connected, cast send would again fall back to eth_sendTransaction. This would happen similarly with other available local signing methods.
  • If a --from address is passed in and no other information is provided, cast send would again fall back to eth_sendTransaction. This is actually "correct" behavior, but the user is kept in the dark as to why this is and is not given enough context.

Solution

The send command was lightly refactored and commented for future reference. The main changes are:

  • Defaulting to eth_sendTransaction is now only possible by passing the --unlocked flag, matching forge script's behavior. This flag also requires the --from flag to be passed, to ensure enough information is passed in by the user.
  • If not defaulting to eth_sendTransaction, we must now be able to construct a signer with the flags passed in, or we will bail and inform the user something is wrong.
  • THIS BREAKS COMPATIBILITY WITH DAPPTOOLS: We now don't walk directories looking for keystores if only a directory is passed in with the --keystore flag, and instead bail and tell the user to provide the keystore file directly. This is because keystores are no longer generated with the address inside of them. (This is a big discussion point, and while I favor being more strict, we could also try and decrypt all the keystores found and use the first one to succeed, but this has edge cases that could lead to bad scenarios).
  • If a ledger is not available for whatever reason, instead of defaulting to eth_sendTransaction, we bail and inform the user that they must only have the ledger unlocked and connected with no other conflicting wallet apps open.

EDIT: @DaniPopes @0xMelkor can't seem to be able to add you as reviewers, but as always, feel free to review!

@Evalir Evalir requested review from gakonst and mattsse May 4, 2023 05:53
/// If the path is a directory then we try to find the first keystore file with the correct
/// sender address
/// if the path is a directory, it bails and asks the user to specify the keystore file
/// directly.
fn find_keystore_file(&self, path: impl AsRef<Path>) -> Result<PathBuf> {
let path = path.as_ref();
if !path.exists() {
bail!("Keystore file `{path:?}` does not exist")
}

if path.is_dir() {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that this is where compatibility with dapptools on this functionality is broken. Happy to accomodate suggestions on how we want to handle this as keystores do not have the expected address property.

@0xMelkor
Copy link
Contributor

0xMelkor commented May 4, 2023

EDIT: @DaniPopes @0xMelkor can't seem to be able to add you as reviewers, but as always, feel free to review!

On it

Copy link
Contributor

@0xMelkor 0xMelkor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Evalir ty, this is my review

cli/src/opts/wallet/mod.rs Show resolved Hide resolved
cli/src/cmd/cast/send.rs Show resolved Hide resolved
cli/src/cmd/cast/send.rs Outdated Show resolved Hide resolved
cli/src/cmd/cast/send.rs Outdated Show resolved Hide resolved
// Checking if signer isn't the default value
// 00a329c0648769A73afAc7F9381E08FB43dBEA72.
if resend {
tx.nonce = Some(provider.get_transaction_count(config.sender, None).await?);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is duplicated, can we extract a function?

async fn update_nonce(tx: &mut Tx...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tx.nonce = Some(provider.get_transaction_count(config.sender, None).await?);
}

cast_send(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cast_send is called twice.
Since cast_send is our final intent, can we refactor to have just a call at the end of the fn block?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah the reason this is written like so is that provider actually takes different types depending if you have a signer or not, which will also affect from and the eth_* method used. We want to clearly differentiate when we're falling back to eth_sendTransaction vs using a signer and using eth_sendRawTransaction, and so we have these two code paths. I actually think abstracting it all away so it looks like only one path could make this harder to reason about and could allow misleading error messages due to fall-through cases. Let me know what you think!

@@ -109,19 +157,6 @@ impl SendTxArgs {
tx.nonce = Some(provider.get_transaction_count(from, None).await?);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicated, see above

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Answered in #4874 (comment) — essentially want to keep these code paths simple instead of further abstracting them.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

@Evalir Evalir added the C-cast Command: cast label May 4, 2023
Copy link
Member

@mattsse mattsse left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changes lgtm, haven't tested it though,

pending @0xMelkor for final approval

@@ -109,19 +157,6 @@ impl SendTxArgs {
tx.nonce = Some(provider.get_transaction_count(from, None).await?);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

@Evalir Evalir requested a review from 0xMelkor May 4, 2023 16:08
cli/src/cmd/cast/send.rs Outdated Show resolved Hide resolved
@Evalir Evalir requested a review from DaniPopes May 6, 2023 14:19
@Evalir Evalir requested a review from mattsse May 8, 2023 22:39
@mattsse mattsse merged commit f0a42cc into master May 9, 2023
@mattsse mattsse deleted the evalir/cast_send_issue branch May 9, 2023 10:02
agostbiro added a commit to agostbiro/book that referenced this pull request Jun 12, 2023
Since foundry-rs/foundry#4874 the `--from` flag only works in combination with the `--unlocked` flag. 
This commit adds the missing `--unlocked` flag to an example that is using `--from`.
Evalir pushed a commit to foundry-rs/book that referenced this pull request Jun 12, 2023
Since foundry-rs/foundry#4874 the `--from` flag only works in combination with the `--unlocked` flag. 
This commit adds the missing `--unlocked` flag to an example that is using `--from`.
togetherAll7 pushed a commit to togetherAll7/foundry-book that referenced this pull request Nov 17, 2024
Since foundry-rs/foundry#4874 the `--from` flag only works in combination with the `--unlocked` flag. 
This commit adds the missing `--unlocked` flag to an example that is using `--from`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-cast Command: cast
Projects
None yet
Development

Successfully merging this pull request may close these issues.

cast send is using eth_sendTransaction when it should use eth_sendRawTransaction
4 participants