Skip to content

Commit

Permalink
Address trail of bits issues (#4)
Browse files Browse the repository at this point in the history
fix: address trail of bits issues - register_refund overwrite and incorrect refund registering for wrong tokens

Closes #5

---------

Co-authored-by: Braqzen <103777923+Braqzen@users.noreply.github.com>
  • Loading branch information
DefiCake and Braqzen authored Jul 14, 2023
1 parent 8436936 commit 3df9609
Show file tree
Hide file tree
Showing 5 changed files with 336 additions and 10 deletions.
4 changes: 2 additions & 2 deletions packages/fungible-token/Forc.lock
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ dependencies = ['std']

[[package]]
name = 'core'
source = 'path+from-root-D62A8C57DD8D2801'
source = 'path+from-root-9BFE6D5D0CBDF740'

[[package]]
name = 'fungible_bridge_abi'
Expand All @@ -35,7 +35,7 @@ dependencies = ['std']

[[package]]
name = 'std'
source = 'git+https://github.com/fuellabs/sway?tag=v0.40.1#48104d0bde0d343154a5bc39a310092532883235'
source = 'git+https://github.com/fuellabs/sway?tag=v0.42.1#3b66f8e424bd21e3ba467783b10b36e808cfa6ee'
dependencies = ['core']

[[package]]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ mod data;
mod errors;
mod events;
mod utils;
mod cast;

use fungible_bridge_abi::FungibleBridge;
use FRC20_abi::FRC20;
Expand Down Expand Up @@ -39,6 +40,8 @@ use utils::{
parse_message_data,
};

use cast::*;

storage {
refund_amounts: StorageMap<b256, StorageMap<b256, b256>> = StorageMap {},
tokens_minted: u64 = 0,
Expand Down Expand Up @@ -129,7 +132,7 @@ impl FungibleBridge for Contract {
storage.refund_amounts.get(originator).insert(asset, ZERO_B256);

// send a message to unlock this amount on the base layer gateway contract
send_message(BRIDGED_TOKEN_GATEWAY, encode_data(originator, stored_amount, BRIDGED_TOKEN), 0);
send_message(BRIDGED_TOKEN_GATEWAY, encode_data(originator, stored_amount, asset), 0);
}

#[payable]
Expand Down Expand Up @@ -190,7 +193,11 @@ impl FRC20 for Contract {
// Storage-dependant private functions
#[storage(write)]
fn register_refund(from: b256, asset: b256, amount: b256) {
storage.refund_amounts.get(from).insert(asset, amount);
let previous_amount = U256::from(storage.refund_amounts.get(from).get(asset).try_read().unwrap_or(ZERO_B256));
let new_amount = U256::from(amount).add(previous_amount); // U256 has overflow checks built in;
let new_amount_b256 = <U256 as From<b256>>::into(new_amount);

storage.refund_amounts.get(from).insert(asset, new_amount_b256);
log(RefundRegisteredEvent {
from,
asset,
Expand Down
55 changes: 55 additions & 0 deletions packages/fungible-token/bridge-fungible-token/src/cast.sw
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
library;

use std::u256::U256;
use std::constants::ZERO_B256;

impl From<b256> for U256 {
fn from(value: b256) -> Self {
let (word1, word2, word3, word4) = asm(r1: value) { r1: (u64, u64, u64, u64) };
let result = U256::from((word1, word2, word3, word4));

result
}

fn into(self) -> b256 {
let result = b256::min();

asm(output: result, r1: self.a, r2: self.b, r3: self.c, r4: self.d) {
sw output r1 i0; // store the word in r1 in output + 0 words
sw output r2 i1; // store the word in r2 in output + 1 word
sw output r3 i2; // store the word in r3 in output + 1 word
sw output r4 i3; // store the word in r4 in output + 1 word
}

result
}
}

#[test]
fn test_b256_from() {
// Test the boundary conditions: min, middle, max
let min = b256::min();
let middle = 0x000000000000000000000000000000000000000000000000000000000000000a;
let max = b256::max();

// Alternatively, compare each field of the U256
assert_eq(U256::from(min), U256::from((0, 0, 0, 0)));
assert_eq(U256::from(middle), U256::from((0, 0, 0, 10)));
assert_eq(U256::from(max), U256::from((u64::max(), u64::max(), u64::max(), u64::max())));
}

#[test]
fn test_b256_into() {
// Test the boundary conditions: min, middle, max
let min = U256::from((0, 0, 0, 0));
let middle = U256::from((0, 0, 0, 10));
let max = U256::from((u64::max(), u64::max(), u64::max(), u64::max()));

let min_into_b256: b256 = min.into();
let middle_into_b256: b256 = middle.into();
let max_into_b256: b256 = max.into();

assert(min_into_b256 == b256::min());
assert(middle_into_b256 == 0x000000000000000000000000000000000000000000000000000000000000000a);
assert(max_into_b256 == b256::max());
}
261 changes: 261 additions & 0 deletions packages/fungible-token/bridge-fungible-token/tests/harness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use fuels::{
tx::Receipt,
types::Bits256,
};
use primitive_types::U256 as Unsigned256;

pub const BRIDGED_TOKEN: &str =
"0x00000000000000000000000000000000000000000000000000000000deadbeef";
Expand Down Expand Up @@ -266,6 +267,141 @@ mod success {
assert_eq!(amount, config.overflow_2);
}

#[tokio::test]
async fn claim_refund_of_wrong_token_deposit() {
// Send a message informing about a deposit with a random token address, different from the bridged token
// Upon sending this message, the contract will register a refund for the deposit and random token
// - Verify that the contract state has correctly changed: new refund record inserted for the correct amount and the random token
// - Verify that the contract emits the correct logs
// - Verify that the the receipt of the transaction contains a message for the L1 Portal that allows to withdraw the above mentioned deposit
let mut wallet = env::setup_wallet();

let configurables: Option<BridgeFungibleTokenContractConfigurables> = None;
let config = env::generate_test_config((BRIDGED_TOKEN_DECIMALS, PROXY_TOKEN_DECIMALS));
let wrong_token_value: &str =
"0x1111110000000000000000000000000000000000000000000000000000111111";

let (message, coin, deposit_contract) = env::construct_msg_data(
wrong_token_value,
FROM,
*wallet.address().hash(),
config.overflow_2,
configurables.clone(),
false,
None,
)
.await;

// Set up the environment
let (
test_contract,
contract_inputs,
coin_inputs,
message_inputs,
test_contract_id,
provider,
) = env::setup_environment(
&mut wallet,
vec![coin],
vec![message],
deposit_contract,
None,
configurables,
)
.await;

// Relay the test message to the test contract
let receipts = env::relay_message_to_contract(
&wallet,
message_inputs[0].clone(),
contract_inputs,
&coin_inputs[..],
&env::generate_variable_output(),
)
.await;

let log_decoder = test_contract.log_decoder();
let refund_registered_event = log_decoder
.decode_logs_with_type::<RefundRegisteredEvent>(&receipts)
.unwrap();

// Verify the message value was received by the test contract
let test_contract_balance = provider
.get_contract_asset_balance(test_contract.contract_id(), AssetId::default())
.await
.unwrap();
let balance = wallet
.get_asset_balance(&AssetId::new(*test_contract_id.hash()))
.await
.unwrap();

assert_eq!(test_contract_balance, 100);
assert_eq!(
refund_registered_event[0].amount,
Bits256(env::encode_hex(config.overflow_2))
);
assert_eq!(
refund_registered_event[0].asset,
Bits256::from_hex_str(wrong_token_value).unwrap()
);
assert_eq!(
refund_registered_event[0].from,
Bits256::from_hex_str(FROM).unwrap()
);

// verify that no tokens were minted for message.data.to
assert_eq!(balance, 0);

// verify that trying to claim funds from
// the correct token fails
let error_response = test_contract
.methods()
.claim_refund(
Bits256::from_hex_str(FROM).unwrap(),
Bits256::from_hex_str(BRIDGED_TOKEN).unwrap(),
)
.call()
.await;
assert!(error_response.is_err());

let call_response = test_contract
.methods()
.claim_refund(
Bits256::from_hex_str(FROM).unwrap(),
Bits256::from_hex_str(wrong_token_value).unwrap(),
)
.call()
.await
.unwrap();

// verify correct message was sent
let message_receipt = call_response
.receipts
.iter()
.find(|&r| matches!(r, Receipt::MessageOut { .. }))
.unwrap();

assert_eq!(
*test_contract_id.hash(),
**message_receipt.sender().unwrap()
);
assert_eq!(
&Address::from_str(BRIDGED_TOKEN_GATEWAY).unwrap(),
message_receipt.recipient().unwrap()
);
assert_eq!(message_receipt.amount().unwrap(), 0);
assert_eq!(message_receipt.len().unwrap(), 104);

// message data
let (selector, to, token, amount) =
env::parse_output_message_data(message_receipt.data().unwrap());
assert_eq!(selector, env::decode_hex("0x53ef1461").to_vec());
assert_eq!(to, Bits256::from_hex_str(FROM).unwrap());
assert_eq!(token, Bits256::from_hex_str(wrong_token_value).unwrap());
// Compare the value output in the message with the original value sent
assert_eq!(amount, config.overflow_2);
}

#[tokio::test]
async fn withdraw_from_bridge() {
// perform successful deposit first, verify it, then withdraw and verify balances
Expand Down Expand Up @@ -1326,4 +1462,129 @@ mod revert {
// verify that no tokens were minted for message.data.to
assert_eq!(balance, 0);
}

#[tokio::test]
async fn deposit_with_wrong_token_twice_registers_two_refunds() {
let mut wallet = env::setup_wallet();
let configurables: Option<BridgeFungibleTokenContractConfigurables> = None;
let wrong_token_value: &str =
"0x1111110000000000000000000000000000000000000000000000000000111111";

let config = env::generate_test_config((BRIDGED_TOKEN_DECIMALS, PROXY_TOKEN_DECIMALS));

let (message, coin, deposit_contract) = env::construct_msg_data(
wrong_token_value,
FROM,
*Address::from_str(TO).unwrap(),
config.min_amount,
configurables.clone(),
false,
None,
)
.await;

let one = Unsigned256::from(1);

let (message2, _, _) = env::construct_msg_data(
wrong_token_value,
FROM,
*Address::from_str(TO).unwrap(),
config.min_amount + one,
configurables.clone(),
false,
None,
)
.await;

// Set up the environment
let (
test_contract,
contract_inputs,
coin_inputs,
message_inputs,
test_contract_id,
provider,
) = env::setup_environment(
&mut wallet,
vec![coin],
vec![message, message2],
deposit_contract,
None,
configurables,
)
.await;

// Relay the test message to the test contract
let receipts = env::relay_message_to_contract(
&wallet,
message_inputs[0].clone(),
contract_inputs.clone(),
&coin_inputs[..],
&env::generate_variable_output(),
)
.await;

// Relay the test message to the test contract
let receipts_second = env::relay_message_to_contract(
&wallet,
message_inputs[1].clone(),
contract_inputs.clone(),
&coin_inputs[..],
&env::generate_variable_output(),
)
.await;

let log_decoder = test_contract.log_decoder();
let refund_registered_event = log_decoder
.decode_logs_with_type::<RefundRegisteredEvent>(&receipts)
.unwrap();

// Verify the message value was received by the test contract
let test_contract_balance = provider
.get_contract_asset_balance(test_contract.contract_id(), AssetId::default())
.await
.unwrap();

let balance = wallet
.get_asset_balance(&AssetId::new(*test_contract_id.hash()))
.await
.unwrap();

// Verify the message value was received by the test contract
assert_eq!(test_contract_balance, 200);

// check that the RefundRegisteredEvent receipt is populated correctly
assert_eq!(
refund_registered_event[0].amount,
Bits256(env::encode_hex(config.min_amount))
);
assert_eq!(
refund_registered_event[0].asset,
Bits256::from_hex_str(wrong_token_value).unwrap()
);
assert_eq!(
refund_registered_event[0].from,
Bits256::from_hex_str(FROM).unwrap()
);

// verify that no tokens were minted for message.data.to
assert_eq!(balance, 0);

// check that the RefundRegisteredEvent receipt is populated correctly
let second_refund_registered_event = log_decoder
.decode_logs_with_type::<RefundRegisteredEvent>(&receipts_second)
.unwrap();
assert_eq!(
second_refund_registered_event[0].amount,
Bits256(env::encode_hex(config.min_amount + one))
);
assert_eq!(
second_refund_registered_event[0].asset,
Bits256::from_hex_str(wrong_token_value).unwrap()
);
assert_eq!(
second_refund_registered_event[0].from,
Bits256::from_hex_str(FROM).unwrap()
);
}
}
Loading

0 comments on commit 3df9609

Please sign in to comment.