Skip to content

Commit 5fcefb9

Browse files
authored
Merge branch 'master' into forge/master
2 parents 04edd89 + 4079946 commit 5fcefb9

68 files changed

Lines changed: 5737 additions & 538 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.config/nextest.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ default-filter = "not test(/flaky_/)"
1010
filter = "test(/ext_integration/)"
1111
slow-timeout = { period = "5m", terminate-after = 4 }
1212

13+
# `cast vaddr` end-to-end tests mine a 4-byte TIP-1022 PoW salt (CPU-bound,
14+
# minutes in debug mode) before exercising register/resolve/forward/watch
15+
# against an in-process Anvil Tempo node.
16+
[[profile.default.overrides]]
17+
filter = "test(/vaddr_e2e::/)"
18+
slow-timeout = { period = "5m", terminate-after = 4 }
19+
1320
# Do not re-run so that `cargo cheats` is ran locally.
1421
[[profile.default.overrides]]
1522
filter = "package(foundry-cheatcodes-spec)"

.github/workflows/benchmarks.yml

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ on:
66
workflow_dispatch:
77
inputs:
88
versions:
9-
description: "Comma-separated list of Foundry versions to benchmark (optional, defaults to 'v1.5.1,v1.7.0')"
9+
description: "Comma-separated list of Foundry versions to benchmark (optional, defaults to 'stable,local'). Use 'local' to bench HEAD of the current branch."
1010
required: false
1111
type: string
1212
repos:
@@ -15,7 +15,7 @@ on:
1515
type: string
1616

1717
env:
18-
DEFAULT_VERSIONS: "v1.5.1,v1.7.0"
18+
DEFAULT_VERSIONS: "stable,local"
1919

2020
# Repos to benchmark per step. Each comma-separated entry has the form
2121
# org/repo[:rev][ <extra args>]
@@ -24,7 +24,7 @@ env:
2424
# `--nmc BrokenTest`, so a single failing test does not fail the whole CI).
2525
TEST_REPOS: >-
2626
ithacaxyz/account:v0.5.7,
27-
vectorized/solady:v0.1.26 --nmc 'LifebuoyTest|LibBitTest|Base58Test',
27+
vectorized/solady:v0.1.26 --nmc 'LifebuoyTest|LibBitTest|Base58Test|LibStringTest',
2828
uniswap/v4-core:46c6834698c48bc4a463a86d8420f4eb1d7f3b75 --nmc 'TickMathTestTest',
2929
sparkdotfi/spark-psm:v1.0.0 --nmc PSMInvariants_TimeBasedRateSetting_WithTransfers_WithPocketSetting
3030
@@ -145,6 +145,9 @@ jobs:
145145
- name: Combine benchmark results
146146
run: ./.github/scripts/combine-benchmarks.sh benches
147147

148+
- name: Print benchmark results
149+
run: cat benches/LATEST.md
150+
148151
- name: Upload benchmark results as artifacts
149152
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
150153
with:
@@ -179,6 +182,7 @@ jobs:
179182

180183
- name: Commit benchmark results
181184
id: commit_results
185+
if: "!contains(github.event.inputs.versions || env.DEFAULT_VERSIONS, 'local')"
182186
env:
183187
GITHUB_HEAD_REF: ${{ github.head_ref }}
184188
run: ./.github/scripts/commit-benchmark-results.sh benches "${{ github.event_name }}" "${{ github.repository }}"
@@ -208,3 +212,37 @@ jobs:
208212
});
209213
210214
console.log(`Created PR #${pr.number}: ${pr.html_url}`);
215+
216+
- name: Comment on PR
217+
if: contains(github.event.inputs.versions || env.DEFAULT_VERSIONS, 'local')
218+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
219+
with:
220+
script: |
221+
const { execSync } = require('child_process');
222+
223+
const formatted = execSync('./.github/scripts/format-pr-comment.sh benches/LATEST.md').toString();
224+
225+
const branch = context.ref.replace('refs/heads/', '');
226+
const { data: prs } = await github.rest.pulls.list({
227+
owner: context.repo.owner,
228+
repo: context.repo.repo,
229+
head: `${context.repo.owner}:${branch}`,
230+
state: 'open',
231+
});
232+
233+
if (prs.length === 0) {
234+
console.log(`No open PR found for branch '${branch}', skipping comment.`);
235+
return;
236+
}
237+
238+
const prNumber = prs[0].number;
239+
const body = `## Benchmark Results (stable vs this branch)\n\n${formatted}\n\n---\n\n🤖 This comment was automatically generated by the [Foundry Benchmarks workflow](https://github.com/${{ github.repository }}/actions/workflows/benchmarks.yml).`;
240+
241+
await github.rest.issues.createComment({
242+
issue_number: prNumber,
243+
owner: context.repo.owner,
244+
repo: context.repo.repo,
245+
body,
246+
});
247+
248+
console.log(`Posted benchmark results to PR #${prNumber}`);

.github/workflows/stale-prs.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Marks pull requests as stale.
2+
3+
name: stale PRs
4+
5+
on:
6+
workflow_dispatch: {}
7+
schedule:
8+
- cron: "30 1 * * *"
9+
10+
permissions: {}
11+
12+
jobs:
13+
stale-prs:
14+
if: github.repository == 'foundry-rs/foundry'
15+
runs-on: ubuntu-latest
16+
permissions:
17+
issues: write
18+
pull-requests: write
19+
steps:
20+
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0
21+
with:
22+
days-before-issue-stale: -1
23+
days-before-issue-close: -1
24+
days-before-pr-stale: 14
25+
days-before-pr-close: 7
26+
stale-pr-label: "stale"
27+
exempt-pr-labels: "stop-stale"
28+
stale-pr-message: "This pull request is stale because it has been open for 14 days with no activity. It will be closed in 7 days if there is no further activity."
29+
close-pr-message: "This pull request was closed because it has been inactive for 7 days since being marked as stale."
30+
labels-to-add-when-unstale: "stop-stale"
31+
exempt-all-milestones: true
32+
exempt-all-assignees: true

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

benches/src/lib.rs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -455,9 +455,20 @@ impl BenchmarkProject {
455455
}
456456
}
457457

458-
/// Switch to a specific foundry version
458+
/// The workspace root, embedded at compile time.
459+
/// `benches/` is one level below the workspace root.
460+
const WORKSPACE_ROOT: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/..");
461+
462+
/// Switch to a specific foundry version.
463+
///
464+
/// The special keyword `local` builds and activates the current workspace via
465+
/// `foundryup --path <workspace>` instead of `foundryup --use`.
459466
#[allow(unused_must_use)]
460467
pub fn switch_foundry_version(version: &str) -> Result<()> {
468+
if version == "local" {
469+
return install_local_version();
470+
}
471+
461472
let output = Command::new("foundryup")
462473
.args(["--use", version])
463474
.output()
@@ -480,6 +491,27 @@ pub fn switch_foundry_version(version: &str) -> Result<()> {
480491
Ok(())
481492
}
482493

494+
/// Build and activate the local workspace via `foundryup --path`.
495+
/// Uses cargo's incremental compilation so re-runs are fast.
496+
#[allow(unused_must_use)]
497+
pub fn install_local_version() -> Result<()> {
498+
let workspace =
499+
std::fs::canonicalize(WORKSPACE_ROOT).wrap_err("Failed to resolve workspace root")?;
500+
sh_println!(" Building local workspace at {}", workspace.display());
501+
502+
let status = Command::new("foundryup")
503+
.args(["--path", workspace.to_str().unwrap()])
504+
.status()
505+
.wrap_err("Failed to run foundryup --path")?;
506+
507+
if !status.success() {
508+
eyre::bail!("foundryup --path failed for local workspace");
509+
}
510+
511+
sh_println!(" Successfully activated local build");
512+
Ok(())
513+
}
514+
483515
/// Get the current forge version
484516
pub fn get_forge_version() -> Result<String> {
485517
let output = Command::new("forge")

benches/src/main.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use clap::Parser;
22
use eyre::{Result, WrapErr};
33
use foundry_bench::{
44
BENCHMARK_REPOS, BenchmarkProject, FOUNDRY_VERSIONS, RUNS, RepoConfig, get_forge_version,
5-
get_forge_version_details,
5+
get_forge_version_details, install_local_version,
66
results::{BenchmarkResults, HyperfineResult},
77
switch_foundry_version,
88
};
@@ -255,6 +255,11 @@ fn install_foundry_versions(versions: &[String]) -> Result<()> {
255255
for version in versions {
256256
sh_println!("Installing {version}...");
257257

258+
if version == "local" {
259+
install_local_version()?;
260+
continue;
261+
}
262+
258263
let status = Command::new("foundryup")
259264
.args(["--install", version, "--force"])
260265
.status()

crates/anvil/src/eth/api.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3642,13 +3642,11 @@ fn tempo_parallel_nonce_markers(
36423642
) -> Option<(Vec<TxMarker>, Vec<TxMarker>)> {
36433643
// Tempo txs with non-zero nonce_key use a 2D nonce system and should not
36443644
// be sequenced by account nonce markers.
3645-
if let FoundryTxEnvelope::Tempo(aa_tx) = pending_transaction.transaction.as_ref()
3646-
&& !aa_tx.tx().nonce_key.is_zero()
3647-
{
3648-
Some((vec![], vec![pending_transaction.hash().to_vec()]))
3649-
} else {
3650-
None
3651-
}
3645+
pending_transaction
3646+
.transaction
3647+
.as_ref()
3648+
.has_nonzero_tempo_nonce_key()
3649+
.then(|| (vec![], vec![pending_transaction.hash().to_vec()]))
36523650
}
36533651

36543652
fn convert_transact_out(out: &Option<Output>) -> Bytes {

crates/anvil/src/eth/backend/mem/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4326,7 +4326,10 @@ fn get_pool_transactions_nonce(
43264326
) -> Option<u64> {
43274327
if let Some(highest_nonce) = pool_transactions
43284328
.iter()
4329-
.filter(|tx| *tx.pending_transaction.sender() == address)
4329+
.filter(|tx| {
4330+
*tx.pending_transaction.sender() == address
4331+
&& !tx.pending_transaction.transaction.as_ref().has_nonzero_tempo_nonce_key()
4332+
})
43304333
.map(|tx| tx.pending_transaction.nonce())
43314334
.max()
43324335
{

crates/anvil/tests/it/tempo.rs

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use alloy_eips::eip2718::Encodable2718;
1313
use alloy_network::{ReceiptResponse, TransactionBuilder, TransactionResponse};
1414
use alloy_primitives::{Address, Bytes, TxKind, U256, address};
1515
use alloy_provider::Provider;
16-
use alloy_rpc_types::{BlockNumberOrTag, TransactionRequest};
16+
use alloy_rpc_types::{BlockId, BlockNumberOrTag, TransactionRequest};
1717
use alloy_serde::WithOtherFields;
1818
use alloy_signer::Signer;
1919
use alloy_signer_local::PrivateKeySigner;
@@ -616,6 +616,61 @@ async fn test_tempo_aa_transaction_with_2d_nonce() {
616616
}
617617
}
618618

619+
#[tokio::test(flavor = "multi_thread")]
620+
async fn test_tempo_nonzero_lane_pending_tx_does_not_advance_scalar_nonce() {
621+
let (api, handle) = spawn(NodeConfig::test_tempo()).await;
622+
let provider = handle.http_provider();
623+
624+
api.anvil_set_auto_mine(false).await.unwrap();
625+
626+
let accounts: Vec<Address> = handle.dev_accounts().collect();
627+
let recipient = accounts[1];
628+
let signer = dev_key(0);
629+
let from = signer.address();
630+
631+
let pending_nonce =
632+
provider.get_transaction_count(from).block_id(BlockId::pending()).await.unwrap();
633+
assert_eq!(pending_nonce, 0);
634+
635+
let token = IERC20::new(PATH_USD, &provider);
636+
let transfer_call = token.transfer(recipient, U256::from(50_000));
637+
let calldata: Bytes = transfer_call.calldata().clone();
638+
639+
let chain_id = provider.get_chain_id().await.unwrap();
640+
let base_fee = provider.get_gas_price().await.unwrap();
641+
642+
let tempo_tx = TempoTransaction {
643+
chain_id,
644+
fee_token: Some(ALPHA_USD),
645+
max_priority_fee_per_gas: base_fee / 10,
646+
max_fee_per_gas: base_fee * 2,
647+
gas_limit: TIP20_TRANSFER_GAS,
648+
calls: vec![Call { to: TxKind::Call(PATH_USD), value: U256::ZERO, input: calldata }],
649+
access_list: Default::default(),
650+
nonce_key: U256::from(42),
651+
nonce: 7,
652+
fee_payer_signature: None,
653+
valid_before: None,
654+
valid_after: None,
655+
key_authorization: None,
656+
tempo_authorization_list: vec![],
657+
};
658+
659+
let sig_hash = tempo_tx.signature_hash();
660+
let signature = signer.sign_hash(&sig_hash).await.unwrap();
661+
let tempo_sig = TempoSignature::Primitive(PrimitiveSignature::Secp256k1(signature));
662+
let signed_tx = AASigned::new_unhashed(tempo_tx, tempo_sig);
663+
let envelope = TempoTxEnvelope::AA(signed_tx);
664+
665+
let mut encoded = Vec::new();
666+
envelope.encode_2718(&mut encoded);
667+
let _ = provider.send_raw_transaction(&encoded).await.unwrap();
668+
669+
let pending_nonce =
670+
provider.get_transaction_count(from).block_id(BlockId::pending()).await.unwrap();
671+
assert_eq!(pending_nonce, 0);
672+
}
673+
619674
#[tokio::test(flavor = "multi_thread")]
620675
async fn test_tempo_aa_transaction_with_valid_before() {
621676
let (_api, handle) = spawn(NodeConfig::test_tempo()).await;

crates/cast/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ alloy-serde.workspace = true
9898
dirs.workspace = true
9999
anvil.workspace = true
100100
foundry-test-utils.workspace = true
101+
tempo-chainspec.workspace = true
101102

102103
[features]
103104
default = ["jemalloc", "asm-keccak", "optimism"]

0 commit comments

Comments
 (0)