Skip to content

Commit

Permalink
feat: add arbitrary to sov-accounts (#1000)
Browse files Browse the repository at this point in the history
* feat: add arbitrary to sov-accounts

This commit introduces arbitrary implementations for sov-accounts. It
will generate primitives such as `Account` transparently, while will use
a `Mutex` for `WorkingSet` mutation while the accounts are being
generated.

* fix lint fmt

* simplify arbitrary accounts

* move arbitrary implementations to dedicated module

* fix native requirement for sov accounts fuzz
  • Loading branch information
vlopes11 authored Oct 13, 2023
1 parent 484a0e3 commit 255a382
Show file tree
Hide file tree
Showing 11 changed files with 194 additions and 79 deletions.
5 changes: 5 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 10 additions & 1 deletion module-system/module-implementations/sov-accounts/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ resolver = "2"
anyhow = { workspace = true }
arbitrary = { workspace = true, optional = true }
borsh = { workspace = true, features = ["rc"] }
proptest = { workspace = true, optional = true }
proptest-derive = { workspace = true, optional = true }
schemars = { workspace = true, optional = true }
serde = { workspace = true }
serde_json = { workspace = true, optional = true }
Expand All @@ -32,5 +34,12 @@ tempfile = { workspace = true }

[features]
default = []
arbitrary = ["dep:arbitrary", "sov-state/arbitrary", "sov-modules-api/arbitrary"]
arbitrary = [
"dep:arbitrary",
"dep:proptest",
"dep:proptest-derive",
"sov-state/arbitrary",
"sov-modules-api/arbitrary",
"sov-state/arbitrary"
]
native = ["serde_json", "jsonrpsee", "schemars", "clap", "sov-state/native", "sov-modules-api/native"]
20 changes: 0 additions & 20 deletions module-system/module-implementations/sov-accounts/src/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,23 +71,3 @@ impl<C: Context> Accounts<C> {
Ok(())
}
}

#[cfg(all(feature = "arbitrary", feature = "native"))]
impl<'a, C> arbitrary::Arbitrary<'a> for CallMessage<C>
where
C: Context,
C::PrivateKey: arbitrary::Arbitrary<'a>,
{
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
use sov_modules_api::PrivateKey;

let secret = C::PrivateKey::arbitrary(u)?;
let public = secret.pub_key();

let payload_len = u.arbitrary_len::<u8>()?;
let payload = u.bytes(payload_len)?;
let signature = secret.sign(payload);

Ok(Self::UpdatePublicKey(public, signature))
}
}
122 changes: 122 additions & 0 deletions module-system/module-implementations/sov-accounts/src/fuzz.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
use arbitrary::{Arbitrary, Unstructured};
use proptest::arbitrary::any;
use proptest::strategy::{BoxedStrategy, Strategy};
use sov_modules_api::{Context, Module, PrivateKey, WorkingSet};

use crate::{Account, AccountConfig, Accounts, CallMessage};

impl<'a, C> Arbitrary<'a> for CallMessage<C>
where
C: Context,
C::PrivateKey: Arbitrary<'a>,
{
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
let secret = C::PrivateKey::arbitrary(u)?;
let public = secret.pub_key();

let payload_len = u.arbitrary_len::<u8>()?;
let payload = u.bytes(payload_len)?;
let signature = secret.sign(payload);

Ok(Self::UpdatePublicKey(public, signature))
}
}

impl<C> proptest::arbitrary::Arbitrary for CallMessage<C>
where
C: Context,
C::PrivateKey: proptest::arbitrary::Arbitrary,
{
type Parameters = ();
type Strategy = BoxedStrategy<Self>;

fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
(any::<C::PrivateKey>(), any::<Vec<u8>>())
.prop_map(|(secret, payload)| {
let public = secret.pub_key();
let signature = secret.sign(&payload);
Self::UpdatePublicKey(public, signature)
})
.boxed()
}
}

impl<'a, C> Arbitrary<'a> for Account<C>
where
C: Context,
C::Address: Arbitrary<'a>,
{
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
let addr = u.arbitrary()?;
let nonce = u.arbitrary()?;
Ok(Self { addr, nonce })
}
}

impl<C> proptest::arbitrary::Arbitrary for Account<C>
where
C: Context,
C::Address: proptest::arbitrary::Arbitrary,
{
type Parameters = ();
type Strategy = BoxedStrategy<Self>;

fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
(any::<C::Address>(), any::<u64>())
.prop_map(|(addr, nonce)| Account { addr, nonce })
.boxed()
}
}

impl<'a, C> Arbitrary<'a> for AccountConfig<C>
where
C: Context,
C::PublicKey: Arbitrary<'a>,
{
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
// TODO we might want a dedicated struct that will generate the private key counterpart so
// payloads can be signed and verified
Ok(Self {
pub_keys: u.arbitrary_iter()?.collect::<Result<_, _>>()?,
})
}
}

impl<C> proptest::arbitrary::Arbitrary for AccountConfig<C>
where
C: Context,
C::PrivateKey: proptest::arbitrary::Arbitrary,
{
type Parameters = ();
type Strategy = BoxedStrategy<Self>;

fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
any::<Vec<C::PrivateKey>>()
.prop_map(|keys| AccountConfig {
pub_keys: keys.into_iter().map(|k| k.pub_key()).collect(),
})
.boxed()
}
}

impl<'a, C> Accounts<C>
where
C: Context,
C::Address: Arbitrary<'a>,
C::PublicKey: Arbitrary<'a>,
{
/// Creates an arbitrary set of accounts and stores it under `working_set`.
pub fn arbitrary_workset(
u: &mut Unstructured<'a>,
working_set: &mut WorkingSet<C>,
) -> arbitrary::Result<Self> {
let config: AccountConfig<C> = u.arbitrary()?;
let accounts = Accounts::default();

accounts
.genesis(&config, working_set)
.map_err(|_| arbitrary::Error::IncorrectFormat)?;

Ok(accounts)
}
}
56 changes: 3 additions & 53 deletions module-system/module-implementations/sov-accounts/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#![deny(missing_docs)]
#![doc = include_str!("../README.md")]
mod call;
#[cfg(all(feature = "arbitrary", feature = "native"))]
mod fuzz;
mod genesis;
mod hooks;
pub use genesis::*;
Expand Down Expand Up @@ -34,6 +36,7 @@ pub struct Account<C: Context> {
/// A module responsible for managing accounts on the rollup.
#[cfg_attr(feature = "native", derive(sov_modules_api::ModuleCallJsonSchema))]
#[derive(ModuleInfo, Clone)]
#[cfg_attr(feature = "arbitrary", derive(Debug))]
pub struct Accounts<C: Context> {
/// The address of the sov-accounts module.
#[address]
Expand Down Expand Up @@ -72,56 +75,3 @@ impl<C: Context> sov_modules_api::Module for Accounts<C> {
}
}
}

#[cfg(feature = "arbitrary")]
impl<'a, C> arbitrary::Arbitrary<'a> for Account<C>
where
C: Context,
C::Address: arbitrary::Arbitrary<'a>,
{
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let addr = u.arbitrary()?;
let nonce = u.arbitrary()?;
Ok(Self { addr, nonce })
}
}

#[cfg(feature = "arbitrary")]
impl<'a, C> arbitrary::Arbitrary<'a> for AccountConfig<C>
where
C: Context,
C::PublicKey: arbitrary::Arbitrary<'a>,
{
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
// TODO we might want a dedicated struct that will generate the private key counterpart so
// payloads can be signed and verified
Ok(Self {
pub_keys: u.arbitrary_iter()?.collect::<Result<_, _>>()?,
})
}
}

#[cfg(feature = "arbitrary")]
impl<'a, C> Accounts<C>
where
C: Context,
C::Address: arbitrary::Arbitrary<'a>,
C::PublicKey: arbitrary::Arbitrary<'a>,
{
/// Creates an arbitrary set of accounts and stores it under `working_set`.
pub fn arbitrary_workset(
u: &mut arbitrary::Unstructured<'a>,
working_set: &mut WorkingSet<C>,
) -> arbitrary::Result<Self> {
use sov_modules_api::Module;

let config: AccountConfig<C> = u.arbitrary()?;
let accounts = Accounts::default();

accounts
.genesis(&config, working_set)
.map_err(|_| arbitrary::Error::IncorrectFormat)?;

Ok(accounts)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ use crate::{Account, Accounts};

/// This is the response returned from the accounts_getAccount endpoint.
#[derive(Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, Clone)]
#[cfg_attr(
feature = "arbitrary",
derive(arbitrary::Arbitrary, proptest_derive::Arbitrary)
)]
pub enum Response {
/// The account corresponding to the given public key exists.
AccountExists {
Expand Down
9 changes: 8 additions & 1 deletion module-system/sov-modules-api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ sov-rollup-interface = { path = "../../rollup-interface", version = "0.2" }
sov-modules-macros = { path = "../sov-modules-macros", version = "0.2", optional = true }
serde = { workspace = true }
borsh = { workspace = true }
proptest = { workspace = true, optional = true }
proptest-derive = { workspace = true, optional = true }
thiserror = { workspace = true }
sha2 = { workspace = true }
bech32 = { workspace = true }
Expand All @@ -45,7 +47,12 @@ sov-bank = { path = "../module-implementations/sov-bank", features = ["native"]
tempfile = { workspace = true }

[features]
arbitrary = ["dep:arbitrary", "sov-state/arbitrary"]
arbitrary = [
"dep:arbitrary",
"dep:proptest",
"dep:proptest-derive",
"sov-state/arbitrary",
]
bench = ["sov-zk-cycle-macros", "risc0-zkvm", "risc0-zkvm-platform"]
default = ["macros"]
native = [
Expand Down
4 changes: 4 additions & 0 deletions module-system/sov-modules-api/src/bech32.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ const HRP: &str = "sov";
Into,
Display,
)]
#[cfg_attr(
feature = "arbitrary",
derive(arbitrary::Arbitrary, proptest_derive::Arbitrary)
)]
#[serde(try_from = "String", into = "String")]
#[display(fmt = "{}", "value")]
pub struct AddressBech32 {
Expand Down
7 changes: 7 additions & 0 deletions module-system/sov-state/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ anyhow = { workspace = true }
arbitrary = { workspace = true, optional = true }
borsh = { workspace = true }
bcs = { workspace = true }
proptest = { workspace = true, optional = true }
proptest-derive = { workspace = true, optional = true }
serde = { workspace = true }
serde_json = { workspace = true }
thiserror = { workspace = true }
Expand All @@ -35,6 +37,11 @@ tempfile = { workspace = true }
proptest = { workspace = true }

[features]
arbitrary = [
"dep:arbitrary",
"dep:proptest",
"dep:proptest-derive"
]
bench = ["sov-zk-cycle-macros", "risc0-zkvm", "risc0-zkvm-platform"]
default = []
native = ["sov-db"]
5 changes: 4 additions & 1 deletion module-system/sov-state/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,10 @@ pub use crate::witness::{ArrayWitness, Witness};
serde::Serialize,
serde::Deserialize,
)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
feature = "arbitrary",
derive(arbitrary::Arbitrary, proptest_derive::Arbitrary)
)]
pub struct Prefix {
prefix: AlignedVec,
}
Expand Down
30 changes: 27 additions & 3 deletions module-system/sov-state/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,32 @@ impl AsRef<Vec<u8>> for AlignedVec {
}

#[cfg(feature = "arbitrary")]
impl<'a> arbitrary::Arbitrary<'a> for AlignedVec {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
u.arbitrary().map(Self::new)
mod arbitrary_impls {
use arbitrary::{Arbitrary, Unstructured};
use proptest::arbitrary::any;
use proptest::strategy::{BoxedStrategy, Strategy};

use super::*;

impl<'a> Arbitrary<'a> for AlignedVec {
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
u.arbitrary().map(|v: Vec<u8>| {
// we re-allocate so the capacity is also guaranteed to be aligned
Self::new(v[..(v.len() / Self::ALIGNMENT) * Self::ALIGNMENT].to_vec())
})
}
}

impl proptest::arbitrary::Arbitrary for AlignedVec {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;

fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
any::<Vec<u8>>()
.prop_map(|v| {
Self::new(v[..(v.len() / Self::ALIGNMENT) * Self::ALIGNMENT].to_vec())
})
.boxed()
}
}
}

0 comments on commit 255a382

Please sign in to comment.