Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 5 additions & 13 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,11 @@ cargo build --release # Release build
### Testing

```bash
cargo test -p aptos-sdk # Run unit tests (lib)
cargo test -p aptos-sdk --lib # Same: run lib unit tests only
cargo test -p aptos-sdk # Run unit tests
cargo test -p aptos-sdk --all-features # Test with all features
cargo test -p aptos-sdk --features "e2e" -- --ignored # E2E tests (requires localnet)
```

**Full test** means: (1) all lib unit tests pass (`cargo test -p aptos-sdk --lib`), and optionally (2) E2E tests when a localnet is available. E2E tests are marked `#[ignore]` and require a running Aptos node. To run them:

- Start localnet first: `aptos node run-localnet --with-faucet`, or
- Use the script: `./scripts/run-e2e.sh` (starts localnet and runs E2E), or
- With an already-running node: `cargo test -p aptos-sdk --features "e2e,full" -- --ignored`

### Linting and Formatting

```bash
Expand Down Expand Up @@ -119,8 +112,7 @@ The SDK follows a client-centric design with `Aptos` as the main entry point:

## Testing Strategy

- Unit tests are co-located with source code or in `src/tests/` directories.
- **Full test pass**: Run `cargo test -p aptos-sdk --lib`; all tests (including simulation and fullnode query-param tests) should pass.
- E2E tests require a running Aptos localnet (`aptos node run-localnet --with-faucet`). They are ignored by default; run with `-- --ignored` and the `e2e` feature, or use `./scripts/run-e2e.sh`.
- Behavioral tests in `crates/aptos-sdk/tests/behavioral/`.
- Property-based testing with `proptest` for crypto components (via `fuzzing` feature).
- Unit tests are co-located with source code or in `src/tests/` directories
- E2E tests require running Aptos localnet (`aptos node run-localnet`)
- Behavioral tests in `crates/aptos-sdk/tests/behavioral/`
- Property-based testing with `proptest` for crypto components (via `fuzzing` feature)
208 changes: 1 addition & 207 deletions crates/aptos-sdk/src/api/fullnode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ use crate::api::response::{
use crate::config::AptosConfig;
use crate::error::{AptosError, AptosResult};
use crate::retry::{RetryConfig, RetryExecutor};
use crate::transaction::simulation::SimulateQueryOptions;
use crate::transaction::types::SignedTransaction;
use crate::types::{AccountAddress, HashValue};
use reqwest::Client;
Expand Down Expand Up @@ -398,32 +397,15 @@ impl FullnodeClient {

/// Simulates a transaction.
///
/// Optionally pass [`SimulateQueryOptions`] to request gas estimation behavior
/// (e.g. `estimate_gas_unit_price`, `estimate_max_gas_amount`) as query
/// parameters to the `/transactions/simulate` endpoint.
///
/// # Errors
///
/// Returns an error if the transaction cannot be serialized to BCS, the HTTP request fails,
/// the API returns an error status code, or the response cannot be parsed as JSON.
pub async fn simulate_transaction(
&self,
signed_txn: &SignedTransaction,
options: impl Into<Option<SimulateQueryOptions>>,
) -> AptosResult<AptosResponse<Vec<serde_json::Value>>> {
let mut url = self.build_url("transactions/simulate");
if let Some(opts) = options.into() {
let mut pairs = url.query_pairs_mut();
if opts.estimate_gas_unit_price {
pairs.append_pair("estimate_gas_unit_price", "true");
}
if opts.estimate_max_gas_amount {
pairs.append_pair("estimate_max_gas_amount", "true");
}
if opts.estimate_prioritized_gas_unit_price {
pairs.append_pair("estimate_prioritized_gas_unit_price", "true");
}
}
let url = self.build_url("transactions/simulate");
let bcs_bytes = signed_txn.to_bcs()?;
let client = self.client.clone();
let retry_config = self.retry_config.clone();
Expand Down Expand Up @@ -859,10 +841,6 @@ impl FullnodeClient {
#[cfg(test)]
mod tests {
use super::*;
use crate::transaction::simulation::SimulateQueryOptions;
use crate::transaction::types::{RawTransaction, SignedTransaction};
use crate::transaction::authenticator::{Ed25519PublicKey, Ed25519Signature, TransactionAuthenticator};
use crate::types::ChainId;
use wiremock::{
Mock, MockServer, ResponseTemplate,
matchers::{method, path, path_regex},
Expand All @@ -882,45 +860,6 @@ mod tests {
FullnodeClient::new(config).unwrap()
}

/// Creates a minimal SignedTransaction for use in simulate_transaction tests.
fn create_minimal_signed_transaction() -> SignedTransaction {
use crate::transaction::payload::{EntryFunction, TransactionPayload};

let raw = RawTransaction::new(
AccountAddress::ONE,
0,
TransactionPayload::EntryFunction(
EntryFunction::apt_transfer(AccountAddress::ONE, 0).unwrap(),
),
100_000,
100,
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs()
.saturating_add(600),
ChainId::testnet(),
);
let auth = TransactionAuthenticator::Ed25519 {
public_key: Ed25519PublicKey([0u8; 32]),
signature: Ed25519Signature([0u8; 64]),
};
SignedTransaction::new(raw, auth)
}

fn simulate_response_json() -> serde_json::Value {
serde_json::json!([{
"success": true,
"vm_status": "Executed successfully",
"gas_used": "100",
"max_gas_amount": "200000",
"gas_unit_price": "100",
"hash": "0x1",
"changes": [],
"events": []
}])
}

#[tokio::test]
async fn test_get_ledger_info() {
let server = MockServer::start().await;
Expand Down Expand Up @@ -1268,149 +1207,4 @@ mod tests {

assert_eq!(result.data.len(), 1);
}

#[tokio::test]
async fn test_simulate_transaction_with_estimate_gas_unit_price() {
let server = MockServer::start().await;

Mock::given(method("POST"))
.and(path("/v1/transactions/simulate"))
.and(|req: &wiremock::Request| {
req.url
.query()
.map_or(false, |q| q.contains("estimate_gas_unit_price=true"))
})
.respond_with(
ResponseTemplate::new(200).set_body_json(simulate_response_json()),
)
.expect(1)
.mount(&server)
.await;

let client = create_mock_client(&server);
let signed = create_minimal_signed_transaction();
let opts = SimulateQueryOptions::new().estimate_gas_unit_price(true);
let result = client
.simulate_transaction(&signed, Some(opts))
.await
.unwrap();
assert!(!result.data.is_empty());
}

#[tokio::test]
async fn test_simulate_transaction_with_estimate_max_gas_amount() {
let server = MockServer::start().await;

Mock::given(method("POST"))
.and(path("/v1/transactions/simulate"))
.and(|req: &wiremock::Request| {
req.url
.query()
.map_or(false, |q| q.contains("estimate_max_gas_amount=true"))
})
.respond_with(
ResponseTemplate::new(200).set_body_json(simulate_response_json()),
)
.expect(1)
.mount(&server)
.await;

let client = create_mock_client(&server);
let signed = create_minimal_signed_transaction();
let opts = SimulateQueryOptions::new().estimate_max_gas_amount(true);
let result = client
.simulate_transaction(&signed, Some(opts))
.await
.unwrap();
assert!(!result.data.is_empty());
}

#[tokio::test]
async fn test_simulate_transaction_with_estimate_prioritized_gas_unit_price() {
let server = MockServer::start().await;

Mock::given(method("POST"))
.and(path("/v1/transactions/simulate"))
.and(|req: &wiremock::Request| {
req.url.query().map_or(false, |q| {
q.contains("estimate_prioritized_gas_unit_price=true")
})
})
.respond_with(
ResponseTemplate::new(200).set_body_json(simulate_response_json()),
)
.expect(1)
.mount(&server)
.await;

let client = create_mock_client(&server);
let signed = create_minimal_signed_transaction();
let opts = SimulateQueryOptions::new().estimate_prioritized_gas_unit_price(true);
let result = client
.simulate_transaction(&signed, Some(opts))
.await
.unwrap();
assert!(!result.data.is_empty());
}

#[tokio::test]
async fn test_simulate_transaction_with_all_options() {
let server = MockServer::start().await;

Mock::given(method("POST"))
.and(path("/v1/transactions/simulate"))
.and(|req: &wiremock::Request| {
req.url.query().map_or(false, |q| {
q.contains("estimate_gas_unit_price=true")
&& q.contains("estimate_max_gas_amount=true")
&& q.contains("estimate_prioritized_gas_unit_price=true")
})
})
.respond_with(
ResponseTemplate::new(200).set_body_json(simulate_response_json()),
)
.expect(1)
.mount(&server)
.await;

let client = create_mock_client(&server);
let signed = create_minimal_signed_transaction();
let opts = SimulateQueryOptions::new()
.estimate_gas_unit_price(true)
.estimate_max_gas_amount(true)
.estimate_prioritized_gas_unit_price(true);
let result = client
.simulate_transaction(&signed, Some(opts))
.await
.unwrap();
assert!(!result.data.is_empty());
}

#[tokio::test]
async fn test_simulate_transaction_without_options() {
let server = MockServer::start().await;

// Mock must NOT match if query contains any of the simulate options (so we use path only and expect no query param)
Mock::given(method("POST"))
.and(path("/v1/transactions/simulate"))
.and(|req: &wiremock::Request| {
// URL must not contain the simulate query params when options is None
req.url.query().map_or(true, |q| {
!q.contains("estimate_gas_unit_price=")
&& !q.contains("estimate_max_gas_amount=")
&& !q.contains("estimate_prioritized_gas_unit_price=")
})
})
.respond_with(
ResponseTemplate::new(200).set_body_json(simulate_response_json()),
)
.expect(1)
.mount(&server)
.await;

let client = create_mock_client(&server);
let signed = create_minimal_signed_transaction();
let result = client.simulate_transaction(&signed, None).await.unwrap();
assert!(!result.data.is_empty());
}
}
Loading
Loading