Skip to content

Commit

Permalink
test: add unit test for reentrancy attack (#221)
Browse files Browse the repository at this point in the history
  • Loading branch information
DefiCake authored Jul 25, 2024
1 parent e3e673e commit d0f7dd5
Show file tree
Hide file tree
Showing 8 changed files with 197 additions and 2 deletions.
5 changes: 5 additions & 0 deletions .changeset/hip-pans-bow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@fuel-bridge/fungible-token': patch
---

Add reentrancy unit test for l2 proxy-bridge
9 changes: 9 additions & 0 deletions Forc.lock
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@ dependencies = [
"std",
]

[[package]]
name = "reentrancy-attacker"
source = "member"
dependencies = [
"contract_message_receiver",
"standards git+https://github.com/FuelLabs/sway-standards?tag=v0.5.0#348f7175df4c012b23c86cdb18aab79025ca1f18",
"std",
]

[[package]]
name = "standards"
source = "git+https://github.com/FuelLabs/sway-standards?tag=v0.4.3#6f63eb7dff2458a7d976184e565b5cbf26f61da2"
Expand Down
1 change: 1 addition & 0 deletions Forc.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ members = [
"packages/fungible-token/bridge-fungible-token/interface",
"packages/fungible-token/bridge-fungible-token/proxy",
"packages/fungible-token/bridge-fungible-token/implementation",
"packages/fungible-token/bridge-fungible-token/reentrancy-attacker",
"packages/message-predicates/contract-message-predicate",
"packages/message-predicates/contract-message-receiver",
"packages/base-asset",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[project]
authors = ["Fuel Labs <contact@fuel.sh>"]
entry = "reentrancy-attacker.sw"
license = "Apache-2.0"
name = "reentrancy-attacker"

[dependencies]
contract_message_receiver = { path = "../../../message-predicates/contract-message-receiver" }
standards = { git = "https://github.com/FuelLabs/sway-standards", tag = "v0.5.0" }
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
contract;

use std::execution::run_external;
use standards::{src14::SRC14, src5::{AccessError, State}};
use contract_message_receiver::MessageReceiver;

abi ReentrancyAttacker {
#[storage(read, write)]
fn process_message(msg_idx: u64);

#[storage(read)]
fn get_success() -> bool;
}

pub enum AttackStage {
Attacking: (),
Success: (),
Finished: (),
}

configurable {
TARGET: ContractId = ContractId::zero(),
}

#[namespace(SRC14)]
storage {
attacking: bool = false,
success: bool = false,
}

impl ReentrancyAttacker for Contract {
#[storage(read, write)]
fn process_message(msg_idx: u64) {
if storage.success.read() {
log(AttackStage::Finished);
return;
}

log(AttackStage::Attacking);
storage.attacking.write(true);

let target = abi(MessageReceiver, TARGET.into());
target.process_message(msg_idx);

storage.success.write(true);
log(AttackStage::Success);
}

#[storage(read)]
fn get_success() -> bool {
storage.success.read()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1101,9 +1101,16 @@ mod success {
}

mod revert {
use fuels::types::tx_status::TxStatus;
use fuels::{
accounts::wallet::WalletUnlocked,
programs::contract::SettableContract,
types::{tx_status::TxStatus, U256},
};

use crate::utils::setup::get_contract_ids;
use crate::utils::setup::{
create_reentrancy_attacker_contract, get_contract_ids, precalculate_reentrant_attacker_id,
AttackStage, ReentrancyAttacker,
};

use super::*;

Expand Down Expand Up @@ -1159,4 +1166,74 @@ mod revert {
}
}
}

#[tokio::test]
async fn rejects_reentrancy_attempts() {
let mut wallet = create_wallet();
let configurables: Option<BridgeFungibleTokenContractConfigurables> = None;
let (proxy_id, _implementation_contract_id) =
get_contract_ids(&wallet, configurables.clone());
let deposit_contract_id = precalculate_reentrant_attacker_id(proxy_id.clone()).await;
let amount = u64::MAX;

let (message, coin, deposit_contract) = create_deposit_message(
BRIDGED_TOKEN,
BRIDGED_TOKEN_ID,
FROM,
*deposit_contract_id,
U256::from(amount),
BRIDGED_TOKEN_DECIMALS,
proxy_id,
true,
Some(vec![11u8, 42u8, 69u8]),
)
.await;

let (_, _, utxo_inputs) = setup_environment(
&mut wallet,
vec![coin],
vec![message],
deposit_contract,
None,
configurables,
)
.await;

let provider = wallet.provider().expect("Needs provider");

let reentrant_attacker: ReentrancyAttacker<WalletUnlocked> =
create_reentrancy_attacker_contract(wallet.clone(), proxy_id.clone()).await;

// Relay the test message to the bridge contract
let tx_id = relay_message_to_contract(
&wallet,
utxo_inputs.message[0].clone(),
utxo_inputs.contract,
)
.await;

let tx_status = provider.tx_status(&tx_id).await.unwrap();
assert!(matches!(tx_status, TxStatus::Revert { .. }));

let receipts: Vec<fuels::tx::Receipt> =
provider.tx_status(&tx_id).await.unwrap().take_receipts();

let attack_stages = reentrant_attacker
.log_decoder()
.decode_logs_with_type::<AttackStage>(&receipts)
.unwrap();

assert_eq!(attack_stages.len(), 1);
assert_eq!(attack_stages[0], AttackStage::Attacking);

let attack_successful = reentrant_attacker
.methods()
.get_success()
.call()
.await
.unwrap()
.value;

assert!(!attack_successful);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ pub(crate) const BRIDGE_FUNGIBLE_TOKEN_CONTRACT_BINARY: &str =
pub(crate) const DEPOSIT_RECIPIENT_CONTRACT_BINARY: &str =
"../test-deposit-recipient-contract/out/release/test_deposit_recipient_contract.bin";
pub(crate) const BRIDGE_PROXY_BINARY: &str = "../bridge-fungible-token/proxy/out/release/proxy.bin";
pub(crate) const REENTRANCY_ATTACKER_BINARY: &str =
"../bridge-fungible-token/reentrancy-attacker/out/release/reentrancy-attacker.bin";

pub(crate) const BRIDGED_TOKEN: &str =
"0x00000000000000000000000000000000000000000000000000000000deadbeef";
Expand Down
39 changes: 39 additions & 0 deletions packages/fungible-token/bridge-fungible-token/tests/utils/setup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ use std::{mem::size_of, num::ParseIntError, result::Result as StdResult, str::Fr
use super::constants::{
BRIDGED_TOKEN, BRIDGED_TOKEN_ID, BRIDGE_PROXY_BINARY, DEPOSIT_TO_ADDRESS_FLAG,
DEPOSIT_TO_CONTRACT_FLAG, DEPOSIT_WITH_DATA_FLAG, FROM, METADATA_MESSAGE_FLAG,
REENTRANCY_ATTACKER_BINARY,
};

abigen!(
Expand All @@ -43,6 +44,10 @@ abigen!(
Contract(
name = "BridgeProxy",
abi = "packages/fungible-token/bridge-fungible-token/proxy/out/release/proxy-abi.json",
),
Contract(
name = "ReentrancyAttacker",
abi = "packages/fungible-token/bridge-fungible-token/reentrancy-attacker/out/release/reentrancy-attacker-abi.json",
)
);

Expand Down Expand Up @@ -344,6 +349,20 @@ pub(crate) async fn precalculate_deposit_id() -> ContractId {
compiled.contract_id()
}

pub(crate) async fn precalculate_reentrant_attacker_id(target: ContractId) -> ContractId {
let configurables = ReentrancyAttackerConfigurables::default()
.with_TARGET(target)
.unwrap();

let compiled = Contract::load_from(
REENTRANCY_ATTACKER_BINARY,
LoadConfiguration::default().with_configurables(configurables),
)
.unwrap();

compiled.contract_id()
}

/// Prefixes the given bytes with the test contract ID
pub(crate) fn prefix_contract_id(mut data: Vec<u8>, contract_id: ContractId) -> Vec<u8> {
// Turn contract id into array with the given data appended to it
Expand All @@ -368,6 +387,26 @@ pub(crate) async fn create_recipient_contract(
DepositRecipientContract::new(id, wallet)
}

pub(crate) async fn create_reentrancy_attacker_contract(
wallet: WalletUnlocked,
target: ContractId,
) -> ReentrancyAttacker<WalletUnlocked> {
let configurables = ReentrancyAttackerConfigurables::default()
.with_TARGET(target)
.unwrap();

let id = Contract::load_from(
REENTRANCY_ATTACKER_BINARY,
LoadConfiguration::default().with_configurables(configurables),
)
.unwrap()
.deploy(&wallet, TxPolicies::default())
.await
.unwrap();

ReentrancyAttacker::new(id, wallet)
}

/// Quickly converts the given hex string into a u8 vector
pub(crate) fn decode_hex(s: &str) -> Vec<u8> {
let data: StdResult<Vec<u8>, ParseIntError> = (2..s.len())
Expand Down

0 comments on commit d0f7dd5

Please sign in to comment.