Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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
3,365 changes: 1,962 additions & 1,403 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ exclude = [".github/", "**/tests/", "**/contracts/", "**/cache/", "**/out/"]

[workspace.dependencies]
async-trait = "0.1.81"
ethers = "2.0.14"
alloy = { version = "1.0", features = ["full"] }
eyre = "0.6"
rand = "0.8.5"
serde = "1.0.204"
Expand Down
124 changes: 124 additions & 0 deletions MIGRATION_STATUS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Ethers to Alloy Migration Status

## ✅ **Completed Migration**

### **Core Infrastructure**
- ✅ **Dependencies**: Updated all `Cargo.toml` files to use `alloy = { version = "1.0", features = ["full"] }`
- ✅ **Nix Configuration**: Updated `flake.nix` to use latest tools and Rust toolchain
- ✅ **Provider Architecture**: Migrated `roles.rs` to use alloy's `RootProvider` with explicit address management
- ✅ **Core Types**: Updated `level.rs` trait definitions to use alloy types

### **Contract ABIs Converted (9/24)**
All converted from hundreds of lines of ethers-generated code to clean `sol!` macros:

1. ✅ **Fallback** (`ctf/src/abi/fallback.rs`) - 15 lines (was 532 lines)
2. ✅ **Fallout** (`ctf/src/abi/fallout.rs`) - 12 lines
3. ✅ **CoinFlip** (`ctf/src/abi/coin_flip.rs`) - 12 lines
4. ✅ **Telephone** (`ctf/src/abi/telephone.rs`) - 10 lines
5. ✅ **Token** (`ctf/src/abi/token.rs`) - 12 lines
6. ✅ **Delegate** (`ctf/src/abi/delegate.rs`) - 10 lines
7. ✅ **Delegation** (`ctf/src/abi/delegation.rs`) - 11 lines
8. ✅ **Force** (`ctf/src/abi/force.rs`) - 8 lines
9. ✅ **Vault** (`ctf/src/abi/vault.rs`) - 11 lines
10. ✅ **King** (`ctf/src/abi/king.rs`) - 12 lines

### **Level Implementations Migrated (9/23)**
1. ✅ **Level 01**: Fallback (`ctf/src/ethernaut/lvl01_fallback.rs`)
2. ✅ **Level 02**: Fallout (`ctf/src/ethernaut/lvl02_fallout.rs`)
3. ✅ **Level 03**: Coin Flip (`ctf/src/ethernaut/lvl03_coin_flip.rs`)
4. ✅ **Level 04**: Telephone (`ctf/src/ethernaut/lvl04_telephone.rs`)
5. ✅ **Level 05**: Token (`ctf/src/ethernaut/lvl05_token.rs`)
6. ✅ **Level 06**: Delegate (`ctf/src/ethernaut/lvl06_delegate.rs`)
7. ✅ **Level 07**: Force (`ctf/src/ethernaut/lvl07_force.rs`)
8. ✅ **Level 08**: Vault (`ctf/src/ethernaut/lvl08_vault.rs`)
9. ✅ **Level 09**: King (`ctf/src/ethernaut/lvl09_king.rs`)

### **Attack Implementation**
- ✅ **Attack Infrastructure**: Updated `attack/src/ethernaut/mod.rs` to use alloy patterns
- ✅ **Fallback Attack**: Already migrated (`attack/src/ethernaut/hack01_fallback.rs`)

## 🔄 **Remaining Work**

### **Level Implementations** (14 remaining)
The following files still use `ethers::prelude::*` and need migration:
- `ctf/src/ethernaut/lvl10_reentrancy.rs`
- `ctf/src/ethernaut/lvl11_elevator.rs`
- `ctf/src/ethernaut/lvl12_privacy.rs`
- `ctf/src/ethernaut/lvl13_gatekeeper_one.rs`
- `ctf/src/ethernaut/lvl14_gatekeeper_two.rs`
- `ctf/src/ethernaut/lvl15_naught_coin.rs`
- `ctf/src/ethernaut/lvl16_preservation.rs`
- `ctf/src/ethernaut/lvl17_recovery.rs`
- `ctf/src/ethernaut/lvl18_magic_number.rs`
- `ctf/src/ethernaut/lvl19_alien_codex.rs`
- `ctf/src/ethernaut/lvl20_denial.rs`
- `ctf/src/ethernaut/lvl21_shop.rs`
- `ctf/src/ethernaut/lvl22_dex.rs`
- `ctf/src/ethernaut/lvl23_dex_two.rs`

### **ABI Files** (14+ remaining)
Need to be converted to alloy `sol!` macros:
- All ABI files for levels 10-23
- Any additional contract ABIs used by those levels

### **Attack Files**
- Template files in `attack/src/ethernaut/` that still use ethers

## 🚧 **Temporary CI Fix**
Currently the unmigrated levels (10-23) are commented out in `ctf/src/ethernaut/mod.rs` to allow compilation of the migrated parts.

## 📋 **Migration Pattern**

For each remaining level file, follow this pattern:

### 1. **ABI Conversion**
Replace ethers-generated code with alloy `sol!` macro:
```rust
use alloy::sol;

sol! {
#[sol(rpc)]
contract ContractName {
// Add contract interface here
}
}
```

### 2. **Level Implementation**
Update imports and patterns:
```rust
// Replace
use ethers::prelude::*;

// With
use alloy::primitives::{Address, U256};
use alloy::rpc::types::TransactionRequest; // if needed
```

Update contract interactions:
```rust
// Deployment
let contract = Contract::deploy(deployer, constructor_args).await?;

// Function calls
let result = contract.function_name().call().await?._0;

// Transactions
let receipt = contract.function_name(args).send().await?.get_receipt().await?;

// Balance checks
let balance = provider.get_balance(address).await?;
```

## 🎯 **Next Steps**
1. **Complete ABI conversions** for remaining contracts
2. **Migrate level implementations** using the established patterns
3. **Uncomment levels** in `ctf/src/ethernaut/mod.rs` as they're completed
4. **Update any remaining attack files**
5. **Run comprehensive tests** to ensure all functionality works

## 📈 **Benefits Achieved**
- **~95% less ABI code** (from hundreds of lines to ~10-15 per contract)
- **Modern alloy patterns** with better performance and ergonomics
- **Type-safe** contract interactions
- **Cleaner codebase** with alloy's `sol!` macro approach
2 changes: 1 addition & 1 deletion attack/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ resolver = "2"
ctf.path = "../ctf"

async-trait.workspace = true
ethers.workspace = true
alloy.workspace = true
eyre.workspace = true
serde.workspace = true
tokio.workspace = true
29 changes: 18 additions & 11 deletions attack/src/ethernaut/hack01_fallback.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use alloy::{primitives::U256, rpc::types::TransactionRequest};
use async_trait::async_trait;
use ctf::ethernaut::lvl01_fallback::*;
use ethers::prelude::*;

pub(crate) struct Exploit;

Expand All @@ -13,22 +13,29 @@ impl ctf::Exploit for Exploit {
target: &Self::Target,
offender: &ctf::Actor,
) -> eyre::Result<()> {
let contract = Fallback::new(target.address, offender.clone());
let contract = Fallback::new(target.address, offender);

println!("Calling contribute()...");
contract.contribute().value(1).send().await?.await?;

println!("Calling receive()...");
offender
.send_transaction(
TransactionRequest::new().to(contract.address()).value(1),
None,
)
let receipt = contract
.contribute()
.value(U256::from(1))
.send()
.await?
.get_receipt()
.await?;
println!("Contribute tx: {:?}", receipt.transaction_hash);

println!("Calling receive()...");
let tx = TransactionRequest::default()
.to(contract.address())
.value(U256::from(1));
let receipt =
offender.send_transaction(tx).await?.get_receipt().await?;
println!("Receive tx: {:?}", receipt.transaction_hash);

println!("Calling withdraw()...");
contract.withdraw().send().await?.await?;
let receipt = contract.withdraw().send().await?.get_receipt().await?;
println!("Withdraw tx: {:?}", receipt.transaction_hash);

Ok(())
}
Expand Down
5 changes: 1 addition & 4 deletions attack/src/ethernaut/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,13 @@ pub mod hack01_fallback;
#[cfg(test)]
mod tests {
use super::*;
use ethers::prelude::*;

use hack01_fallback as hack01;

#[tokio::test]
async fn test() -> eyre::Result<()> {
let provider = Provider::<Http>::try_from("http://127.0.0.1:8545")?;

println!("Initializing accounts...");
let roles = ctf::Roles::new(provider)?;
let roles = ctf::Roles::new("http://127.0.0.1:8545").await?;

ctf::check_exploit(&roles, hack01::Exploit).await?;

Expand Down
63 changes: 42 additions & 21 deletions attack/src/roles.rs
Original file line number Diff line number Diff line change
@@ -1,43 +1,64 @@
use ethers::prelude::*;
use alloy::{
providers::{ProviderBuilder, RootProvider},
network::Ethereum,
signers::{local::PrivateKeySigner, wallet::EthereumWallet},
transports::http::{Http, Client},
primitives::Address,
};
use std::sync::Arc;

pub type Actor = Arc<SignerMiddleware<Provider<Http>, LocalWallet>>;
pub type Actor = Arc<RootProvider<Http<Client>>>;

#[derive(Debug, Clone)]
pub struct Roles {
pub deployer: Actor,
pub some_user: Actor,
pub deployer_address: Address,
pub some_user: Actor,
pub some_user_address: Address,
pub offender: Actor,
pub offender_address: Address,
}

impl Roles {
pub fn new() -> eyre::Result<Self> {
let provider = Provider::<Http>::try_from("http://localhost:8545")?;
pub async fn new() -> eyre::Result<Self> {
let rpc_url = "http://localhost:8545";

let deployer: LocalWallet =
let deployer_key: PrivateKeySigner =
"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80".parse()?;
let deployer =
mk_signer(provider.clone(), deployer, Chain::AnvilHardhat)?;
let deployer_address = deployer_key.address();
let deployer = mk_provider_with_signer(rpc_url, deployer_key).await?;

let some_user: LocalWallet =
let some_user_key: PrivateKeySigner =
"0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d".parse()?;
let some_user =
mk_signer(provider.clone(), some_user, Chain::AnvilHardhat)?;
let some_user_address = some_user_key.address();
let some_user = mk_provider_with_signer(rpc_url, some_user_key).await?;

let offender: LocalWallet =
let offender_key: PrivateKeySigner =
"0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6".parse()?;
let offender = mk_signer(provider, offender, Chain::AnvilHardhat)?;
let offender_address = offender_key.address();
let offender = mk_provider_with_signer(rpc_url, offender_key).await?;

Ok(Roles { deployer, some_user, offender })
Ok(Roles {
deployer,
deployer_address,
some_user,
some_user_address,
offender,
offender_address
})
}
}

fn mk_signer(
provider: Provider<Http>,
wallet: LocalWallet,
chain_id: impl Into<u64>,
async fn mk_provider_with_signer(
rpc_url: &str,
signer: PrivateKeySigner,
) -> eyre::Result<Actor> {
let wallet =
SignerMiddleware::new(provider, wallet.with_chain_id(chain_id));
Ok(Arc::new(wallet))
let wallet = EthereumWallet::from(signer);
let provider = ProviderBuilder::new()
.with_recommended_fillers()
.wallet(wallet)
.on_http(rpc_url.parse()?)
.await?;

Ok(Arc::new(provider))
}
2 changes: 1 addition & 1 deletion ctf/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ serde = { workspace = true }
serde_json = { workspace = true }
async-trait = { workspace = true }
tokio = { workspace = true }
ethers = { workspace = true }
alloy = { workspace = true }
Loading