-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for bytes and string fixtures, fixture strategy macro. So…
…lidity test
- Loading branch information
Showing
7 changed files
with
313 additions
and
40 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
use crate::strategies::fixture_strategy; | ||
use alloy_dyn_abi::{DynSolType, DynSolValue}; | ||
use alloy_primitives::B256; | ||
use proptest::{ | ||
arbitrary::any, | ||
prelude::{prop, BoxedStrategy}, | ||
strategy::Strategy, | ||
}; | ||
|
||
/// The bytes strategy combines 2 different strategies: | ||
/// 1. A random bytes strategy if no fixture defined for current parameter. | ||
/// 2. A fixture based strategy if configured values for current parameters. | ||
/// If fixture is not a valid type then an error is raised and test suite will continue to execute | ||
/// with random values. | ||
/// | ||
/// | ||
/// For example: | ||
/// To define fixture for `backup` fuzzed parameter, return an array of possible values from | ||
/// `function fixture_backup() external pure returns (bytes[] memory)`. | ||
/// Use `backup` named parameter in fuzzed test in order to create a custom strategy | ||
/// `function testFuzz_backupValue(bytes memory backup)`. | ||
#[derive(Debug, Default)] | ||
pub struct BytesStrategy {} | ||
|
||
impl BytesStrategy { | ||
/// Create a new bytes strategy. | ||
pub fn init(fixtures: Option<&[DynSolValue]>) -> BoxedStrategy<DynSolValue> { | ||
let value_from_fixture = |fixture: Option<&DynSolValue>| { | ||
if let Some(fixture) = fixture { | ||
if let Some(fixture) = fixture.as_bytes() { | ||
return DynSolValue::Bytes(fixture.to_vec()); | ||
} | ||
} | ||
error!("{:?} is not a valid bytes fixture, generate random value", fixture); | ||
let random: [u8; 32] = rand::random(); | ||
DynSolValue::Bytes(random.to_vec()) | ||
}; | ||
fixture_strategy!( | ||
fixtures, | ||
value_from_fixture, | ||
DynSolValue::type_strategy(&DynSolType::Bytes).boxed() | ||
) | ||
} | ||
} | ||
|
||
/// The fixed bytes strategy combines 2 different strategies: | ||
/// 1. A random fixed bytes strategy if no fixture defined for current parameter. | ||
/// 2. A fixture based strategy if configured values for current parameters. | ||
/// If fixture is not a valid type then an error is raised and test suite will continue to execute | ||
/// with random values. | ||
/// | ||
/// | ||
/// For example: | ||
/// To define fixture for `key` fuzzed parameter, return an array of possible values from | ||
/// `function fixture_key() external pure returns (bytes32[] memory)`. | ||
/// Use `key` named parameter in fuzzed test in order to create a custom strategy | ||
/// `function testFuzz_keyValue(bytes32 key)`. | ||
#[derive(Debug, Default)] | ||
pub struct FixedBytesStrategy {} | ||
|
||
impl FixedBytesStrategy { | ||
/// Create a new fixed bytes strategy. | ||
pub fn init(size: usize, fixtures: Option<&[DynSolValue]>) -> BoxedStrategy<DynSolValue> { | ||
let value_from_fixture = move |fixture: Option<&DynSolValue>| { | ||
if let Some(fixture) = fixture { | ||
if let Some(fixture) = fixture.as_fixed_bytes() { | ||
if fixture.1 == size { | ||
return DynSolValue::FixedBytes(B256::from_slice(fixture.0), fixture.1); | ||
} | ||
} | ||
} | ||
error!("{:?} is not a valid fixed bytes fixture, generate random value", fixture); | ||
DynSolValue::FixedBytes(B256::random(), size) | ||
}; | ||
fixture_strategy!( | ||
fixtures, | ||
value_from_fixture, | ||
any::<B256>() | ||
.prop_map(move |mut v| { | ||
v[size..].fill(0); | ||
DynSolValue::FixedBytes(v, size) | ||
}) | ||
.boxed() | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
use crate::strategies::fixture_strategy; | ||
use alloy_dyn_abi::{DynSolType, DynSolValue}; | ||
use proptest::{ | ||
arbitrary::any, | ||
prelude::{prop, BoxedStrategy}, | ||
strategy::Strategy, | ||
}; | ||
use rand::{distributions::Alphanumeric, thread_rng, Rng}; | ||
|
||
/// The address strategy combines 2 different strategies: | ||
/// 1. A random addresses strategy if no fixture defined for current parameter. | ||
/// 2. A fixture based strategy if configured values for current parameters. | ||
/// If fixture is not a valid type then an error is raised and test suite will continue to execute | ||
// with random strings. | ||
/// | ||
/// | ||
/// For example: | ||
/// To define fixture for `person` fuzzed parameter, return an array of possible values from | ||
/// `function fixture_person() public returns (string[] memory)`. | ||
/// Use `person` named parameter in fuzzed test in order to create a custom strategy | ||
/// `function testFuzz_personValue(string memory person)`. | ||
#[derive(Debug, Default)] | ||
pub struct StringStrategy {} | ||
|
||
impl StringStrategy { | ||
/// Create a new string strategy. | ||
pub fn init(fixtures: Option<&[DynSolValue]>) -> BoxedStrategy<DynSolValue> { | ||
let value_from_fixture = |fixture: Option<&DynSolValue>| { | ||
if let Some(fixture) = fixture { | ||
if let Some(fixture) = fixture.as_str() { | ||
return DynSolValue::String(fixture.to_string()); | ||
} | ||
} | ||
error!("{:?} is not a valid string fixture, generate random value", fixture); | ||
let mut rng = thread_rng(); | ||
let string_len = rng.gen_range(0..128); | ||
let random: String = | ||
(&mut rng).sample_iter(Alphanumeric).map(char::from).take(string_len).collect(); | ||
DynSolValue::String(random) | ||
}; | ||
fixture_strategy!( | ||
fixtures, | ||
value_from_fixture, | ||
DynSolValue::type_strategy(&DynSolType::String) | ||
.prop_map(move |value| { | ||
DynSolValue::String( | ||
value.as_str().unwrap().trim().trim_end_matches('\0').to_string(), | ||
) | ||
}) | ||
.boxed() | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
92 changes: 92 additions & 0 deletions
92
testdata/default/fuzz/invariant/common/InvariantFixtures.t.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
// SPDX-License-Identifier: MIT OR Apache-2.0 | ||
pragma solidity ^0.8.0; | ||
|
||
import "ds-test/test.sol"; | ||
|
||
contract Target { | ||
bool ownerFound; | ||
bool amountFound; | ||
bool magicFound; | ||
bool keyFound; | ||
bool backupFound; | ||
bool extraStringFound; | ||
|
||
function fuzzWithFixtures( | ||
address owner, | ||
uint256 amount, | ||
int32 magic, | ||
bytes32 key, | ||
bytes memory backup, | ||
string memory extra | ||
) external { | ||
if (owner == 0x6B175474E89094C44Da98b954EedeAC495271d0F) | ||
ownerFound = true; | ||
if (amount == 1122334455) amountFound = true; | ||
if (magic == -777) magicFound = true; | ||
if (key == "abcd1234") keyFound = true; | ||
if (keccak256(backup) == keccak256("qwerty1234")) backupFound = true; | ||
if ( | ||
keccak256(abi.encodePacked(extra)) == | ||
keccak256(abi.encodePacked("112233aabbccdd")) | ||
) extraStringFound = true; | ||
} | ||
|
||
function isCompromised() public view returns (bool) { | ||
return | ||
ownerFound && | ||
amountFound && | ||
magicFound && | ||
keyFound && | ||
backupFound && | ||
extraStringFound; | ||
} | ||
} | ||
|
||
/// Try to compromise target contract by finding all accepted values using fixtures. | ||
contract InvariantFixtures is DSTest { | ||
Target target; | ||
|
||
function setUp() public { | ||
target = new Target(); | ||
} | ||
|
||
function fixture_owner() external pure returns (address[] memory) { | ||
address[] memory addressFixture = new address[](1); | ||
addressFixture[0] = 0x6B175474E89094C44Da98b954EedeAC495271d0F; | ||
return addressFixture; | ||
} | ||
|
||
function fixture_amount() external pure returns (uint256[] memory) { | ||
uint256[] memory amountFixture = new uint256[](1); | ||
amountFixture[0] = 1122334455; | ||
return amountFixture; | ||
} | ||
|
||
function fixture_magic() external pure returns (int32[] memory) { | ||
int32[] memory magicFixture = new int32[](1); | ||
magicFixture[0] = -777; | ||
return magicFixture; | ||
} | ||
|
||
function fixture_key() external pure returns (bytes32[] memory) { | ||
bytes32[] memory keyFixture = new bytes32[](1); | ||
keyFixture[0] = "abcd1234"; | ||
return keyFixture; | ||
} | ||
|
||
function fixture_backup() external pure returns (bytes[] memory) { | ||
bytes[] memory backupFixture = new bytes[](1); | ||
backupFixture[0] = "qwerty1234"; | ||
return backupFixture; | ||
} | ||
|
||
function fixture_extra() external pure returns (string[] memory) { | ||
string[] memory extraFixture = new string[](1); | ||
extraFixture[0] = "112233aabbccdd"; | ||
return extraFixture; | ||
} | ||
|
||
function invariant_target_not_compromised() public { | ||
assertEq(target.isCompromised(), false); | ||
} | ||
} |