Repository: OpenZeppelin/rust-contracts-stylus (rust)

Issue:
[Feature]: Add a macro that implements `Default` for `solidity_storage` types
### What is the feature you would like to see?

We currently implement `Default` by hand for contract structs like this:

```rust
impl Default for ERC20 {
    fn default() -> Self {
        let root = U256::ZERO;
        ERC20 {
            _balances: unsafe { StorageMap::new(root, 0) },
            _allowances: unsafe {
                StorageMap::new(root + U256::from(32), 0)
            },
            _total_supply: unsafe {
                StorageU256::new(root + U256::from(64), 0)
            },
        }
    }
}
```

This is a repetitive and burdensome process, so we should provide a macro to ease our lives.

### Contribution Guidelines

- [X] I agree to follow this project's Contribution Guidelines

Pull request:
feat: custom derive macro for Default
Resolves #51 

This PR implements a custom derive macro for Default inside `motsu-proc` crate that makes it available inside all unit tests. This follows the rules of solidity storage and only works with  `sol_storage!` structs for now.
- All places where Default was implemented were updated and tests are passing

#### PR Checklist

- [x] Tests
- [x] Documentation


Patch:
diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml
index 7df92c0..84e1ee6 100644
--- a/.github/workflows/check.yml
+++ b/.github/workflows/check.yml
@@ -106,4 +106,4 @@ jobs:
       - name: Checkout Actions Repository
         uses: actions/checkout@v4
       - name: Check spelling of files in the workspace
-        uses: crate-ci/typos@v1.22.4
+        uses: crate-ci/typos@v1.22.7
diff --git a/Cargo.lock b/Cargo.lock
index bc8be96..2a80a39 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2548,8 +2548,13 @@ dependencies = [
 name = "motsu-proc"
 version = "0.1.0"
 dependencies = [
+ "alloy-primitives 0.3.3",
+ "alloy-sol-types 0.3.1",
+ "motsu",
  "proc-macro2",
  "quote",
+ "stylus-proc",
+ "stylus-sdk",
  "syn 2.0.66",
 ]
 
diff --git a/contracts/src/access/control.rs b/contracts/src/access/control.rs
index 54fc180..3c816e5 100644
--- a/contracts/src/access/control.rs
+++ b/contracts/src/access/control.rs
@@ -112,6 +112,7 @@ sol_storage! {
     }
 
     /// State of an `AccessControl` contract.
+    #[cfg_attr(all(test, feature = "std"), derive(motsu::DefaultStorageLayout))]
     pub struct AccessControl {
         /// Role identifier -> Role information.
         mapping(bytes32 => RoleData) _roles;
@@ -391,12 +392,6 @@ mod tests {
 
     use super::{AccessControl, Error};
 
-    impl Default for AccessControl {
-        fn default() -> Self {
-            AccessControl { _roles: unsafe { StorageMap::new(U256::ZERO, 0) } }
-        }
-    }
-
     /// Shorthand for declaring variables converted from a hex literla to a
     /// fixed 32-byte slice;
     macro_rules! roles {
diff --git a/contracts/src/access/ownable.rs b/contracts/src/access/ownable.rs
index 82ea292..fed86eb 100644
--- a/contracts/src/access/ownable.rs
+++ b/contracts/src/access/ownable.rs
@@ -48,6 +48,7 @@ pub enum Error {
 
 sol_storage! {
     /// State of an `Ownable` contract.
+    #[cfg_attr(all(test, feature = "std"), derive(motsu::DefaultStorageLayout))]
     pub struct Ownable {
         /// The current owner of this contract.
         address _owner;
@@ -149,13 +150,6 @@ mod tests {
 
     use super::{Error, Ownable};
 
-    impl Default for Ownable {
-        fn default() -> Self {
-            let root = U256::ZERO;
-            Ownable { _owner: unsafe { StorageAddress::new(root, 0) } }
-        }
-    }
-
     const ALICE: Address = address!("A11CEacF9aa32246d767FCCD72e02d6bCbcC375d");
 
     #[motsu::test]
diff --git a/contracts/src/token/erc20/extensions/capped.rs b/contracts/src/token/erc20/extensions/capped.rs
index fafaab1..175aa17 100644
--- a/contracts/src/token/erc20/extensions/capped.rs
+++ b/contracts/src/token/erc20/extensions/capped.rs
@@ -37,6 +37,7 @@ pub enum Error {
 sol_storage! {
     /// State of a Capped Contract.
     #[allow(clippy::pub_underscore_fields)]
+    #[cfg_attr(all(test, feature = "std"), derive(motsu::DefaultStorageLayout))]
     pub struct Capped {
         /// A cap to the supply of tokens.
         uint256 _cap;
@@ -58,13 +59,6 @@ mod tests {
 
     use super::Capped;
 
-    impl Default for Capped {
-        fn default() -> Self {
-            let root = U256::ZERO;
-            Capped { _cap: unsafe { StorageU256::new(root, 0) } }
-        }
-    }
-
     #[motsu::test]
     fn cap_works(contract: Capped) {
         let value = U256::from(2024);
diff --git a/contracts/src/token/erc20/mod.rs b/contracts/src/token/erc20/mod.rs
index c7b6134..d3c1903 100644
--- a/contracts/src/token/erc20/mod.rs
+++ b/contracts/src/token/erc20/mod.rs
@@ -93,6 +93,7 @@ pub enum Error {
 
 sol_storage! {
     /// State of an `Erc20` token.
+    #[cfg_attr(all(test, feature = "std"), derive(motsu::DefaultStorageLayout))]
     pub struct Erc20 {
         /// Maps users to balances.
         mapping(address => uint256) _balances;
@@ -505,21 +506,6 @@ mod tests {
 
     use super::{Erc20, Error, IErc20};
 
-    impl Default for Erc20 {
-        fn default() -> Self {
-            let root = U256::ZERO;
-            Erc20 {
-                _balances: unsafe { StorageMap::new(root, 0) },
-                _allowances: unsafe {
-                    StorageMap::new(root + U256::from(32), 0)
-                },
-                _total_supply: unsafe {
-                    StorageU256::new(root + U256::from(64), 0)
-                },
-            }
-        }
-    }
-
     #[motsu::test]
     fn reads_balance(contract: Erc20) {
         let balance = contract.balance_of(Address::ZERO);
diff --git a/contracts/src/token/erc721/extensions/enumerable.rs b/contracts/src/token/erc721/extensions/enumerable.rs
index 27b0996..cb5e5d2 100644
--- a/contracts/src/token/erc721/extensions/enumerable.rs
+++ b/contracts/src/token/erc721/extensions/enumerable.rs
@@ -45,6 +45,7 @@ pub enum Error {
 
 sol_storage! {
     /// State of an Enumerable extension.
+    #[cfg_attr(all(test, feature = "std"), derive(motsu::DefaultStorageLayout))]
     pub struct Erc721Enumerable {
         /// Maps owners to a mapping of indices to tokens ids.
         mapping(address => mapping(uint256 => uint256)) _owned_tokens;
@@ -319,25 +320,6 @@ mod tests {
 
     const BOB: Address = address!("F4EaCDAbEf3c8f1EdE91b6f2A6840bc2E4DD3526");
 
-    impl Default for Erc721Enumerable {
-        fn default() -> Self {
-            let root = U256::ZERO;
-
-            Erc721Enumerable {
-                _owned_tokens: unsafe { StorageMap::new(root, 0) },
-                _owned_tokens_index: unsafe {
-                    StorageMap::new(root + U256::from(32), 0)
-                },
-                _all_tokens: unsafe {
-                    StorageVec::new(root + U256::from(64), 0)
-                },
-                _all_tokens_index: unsafe {
-                    StorageMap::new(root + U256::from(96), 0)
-                },
-            }
-        }
-    }
-
     #[motsu::test]
     fn total_supply_no_tokens(contract: Erc721Enumerable) {
         assert_eq!(U256::ZERO, contract.total_supply());
diff --git a/contracts/src/token/erc721/extensions/uri_storage.rs b/contracts/src/token/erc721/extensions/uri_storage.rs
index 78a686b..6cb6fa1 100644
--- a/contracts/src/token/erc721/extensions/uri_storage.rs
+++ b/contracts/src/token/erc721/extensions/uri_storage.rs
@@ -25,6 +25,7 @@ sol! {
 
 sol_storage! {
     /// Uri Storage.
+    #[cfg_attr(all(test, feature = "std"), derive(motsu::DefaultStorageLayout))]
     pub struct Erc721UriStorage {
         /// Optional mapping for token URIs.
         mapping(uint256 => string) _token_uris;
@@ -69,16 +70,6 @@ mod tests {
 
     use super::Erc721UriStorage;
 
-    impl Default for Erc721UriStorage {
-        fn default() -> Self {
-            let root = U256::ZERO;
-
-            Erc721UriStorage {
-                _token_uris: unsafe { StorageMap::new(root, 0) },
-            }
-        }
-    }
-
     fn random_token_id() -> U256 {
         let num: u32 = rand::random();
         U256::from(num)
diff --git a/contracts/src/token/erc721/mod.rs b/contracts/src/token/erc721/mod.rs
index 32c58e5..f9bf368 100644
--- a/contracts/src/token/erc721/mod.rs
+++ b/contracts/src/token/erc721/mod.rs
@@ -169,6 +169,7 @@ sol_interface! {
 
 sol_storage! {
     /// State of an [`Erc721`] token.
+    #[cfg_attr(all(test, feature = "std"), derive(motsu::DefaultStorageLayout))]
     pub struct Erc721 {
         /// Maps tokens to owners.
         mapping(uint256 => address) _owners;
@@ -1121,23 +1122,6 @@ mod tests {
 
     const BOB: Address = address!("F4EaCDAbEf3c8f1EdE91b6f2A6840bc2E4DD3526");
 
-    impl Default for Erc721 {
-        fn default() -> Self {
-            let root = U256::ZERO;
-
-            Erc721 {
-                _owners: unsafe { StorageMap::new(root, 0) },
-                _balances: unsafe { StorageMap::new(root + U256::from(32), 0) },
-                _token_approvals: unsafe {
-                    StorageMap::new(root + U256::from(64), 0)
-                },
-                _operator_approvals: unsafe {
-                    StorageMap::new(root + U256::from(96), 0)
-                },
-            }
-        }
-    }
-
     pub(crate) fn random_token_id() -> U256 {
         let num: u32 = rand::random();
         U256::from(num)
diff --git a/contracts/src/utils/pausable.rs b/contracts/src/utils/pausable.rs
index 5d926f7..347419c 100644
--- a/contracts/src/utils/pausable.rs
+++ b/contracts/src/utils/pausable.rs
@@ -51,7 +51,7 @@ pub enum Error {
 
 sol_storage! {
     /// State of a Pausable Contract.
-    #[allow(missing_docs)]
+    #[cfg_attr(all(test, feature = "std"), derive(motsu::DefaultStorageLayout))]
     pub struct Pausable {
         /// Indicates whether the contract is `Paused`.
         bool _paused;
@@ -142,18 +142,8 @@ impl Pausable {
 
 #[cfg(all(test, feature = "std"))]
 mod tests {
-    use alloy_primitives::U256;
-    use stylus_sdk::storage::{StorageBool, StorageType};
-
     use crate::utils::pausable::{Error, Pausable};
 
-    impl Default for Pausable {
-        fn default() -> Self {
-            let root = U256::ZERO;
-            Pausable { _paused: unsafe { StorageBool::new(root, 0) } }
-        }
-    }
-
     #[motsu::test]
     fn paused_works(contract: Pausable) {
         contract._paused.set(false);
diff --git a/lib/motsu-proc/Cargo.toml b/lib/motsu-proc/Cargo.toml
index 1bc687f..84bc7f8 100644
--- a/lib/motsu-proc/Cargo.toml
+++ b/lib/motsu-proc/Cargo.toml
@@ -12,5 +12,12 @@ proc-macro2 = "1.0.79"
 quote = "1.0.35"
 syn = { version = "2.0.58", features = ["full"] }
 
+[dev-dependencies]
+motsu = { path = "../motsu" }
+alloy-primitives.workspace = true
+alloy-sol-types.workspace = true
+stylus-sdk.workspace = true
+stylus-proc.workspace = true
+
 [lib]
 proc-macro = true
diff --git a/lib/motsu-proc/src/default_storage_layout.rs b/lib/motsu-proc/src/default_storage_layout.rs
new file mode 100644
index 0000000..9edfe25
--- /dev/null
+++ b/lib/motsu-proc/src/default_storage_layout.rs
@@ -0,0 +1,56 @@
+//! Defines the `#[derive(motsu::DefaultStorageLayout)]` procedural macro.
+use proc_macro::TokenStream;
+use quote::quote;
+use syn::{Data, DeriveInput};
+
+const STORAGE_WORD_BYTES: u8 = 32;
+
+pub fn impl_default_storage_layout(ast: &DeriveInput) -> TokenStream {
+    let name = &ast.ident;
+
+    let Data::Struct(ref data_struct) = ast.data else {
+        error!(ast, "DefaultStorageLayout can only be derived for structs");
+    };
+
+    let fields = match &data_struct.fields {
+        syn::Fields::Named(fields) => &fields.named,
+        syn::Fields::Unnamed(fields) => &fields.unnamed,
+        syn::Fields::Unit => return TokenStream::new(),
+    };
+
+    let mut field_initializations = Vec::new();
+    for field in fields {
+        let field_name = &field.ident;
+        let field_type = &field.ty;
+
+        let ty = quote! { <#field_type as stylus_sdk::storage::StorageType> };
+        let field_init = quote! {
+            {
+                if offset + #ty::SLOT_BYTES as u8 > #STORAGE_WORD_BYTES {
+                    next_slot += 1;
+                    offset = 0;
+                }
+                let instance = unsafe { #ty::new(alloy_primitives::U256::from(next_slot), offset) };
+                offset += #ty::SLOT_BYTES as u8;
+                instance
+            }
+        };
+
+        field_initializations.push(quote! {
+            #field_name: #field_init
+        });
+    }
+
+    quote! {
+        impl Default for #name {
+            fn default() -> Self {
+                let mut next_slot: i32 = 0;
+                let mut offset: u8 = 0;
+                #name {
+                    #(#field_initializations),*
+                }
+            }
+        }
+    }
+    .into()
+}
diff --git a/lib/motsu-proc/src/lib.rs b/lib/motsu-proc/src/lib.rs
index e06a7c0..7c0daf2 100644
--- a/lib/motsu-proc/src/lib.rs
+++ b/lib/motsu-proc/src/lib.rs
@@ -1,6 +1,21 @@
 //! Procedural macro definitions used in `motsu`.
 use proc_macro::TokenStream;
+use syn::{parse_macro_input, DeriveInput};
 
+/// Shorthand to print nice errors.
+///
+/// Note that it's defined before the module declarations.
+macro_rules! error {
+    ($tokens:expr, $($msg:expr),+ $(,)?) => {{
+        let error = syn::Error::new(syn::spanned::Spanned::span(&$tokens), format!($($msg),+));
+        return error.to_compile_error().into();
+    }};
+    (@ $tokens:expr, $($msg:expr),+ $(,)?) => {{
+        return Err(syn::Error::new(syn::spanned::Spanned::span(&$tokens), format!($($msg),+)))
+    }};
+}
+
+mod default_storage_layout;
 mod test;
 
 /// Defines a unit test that provides access to Stylus' execution context.
@@ -42,3 +57,50 @@ mod test;
 pub fn test(attr: TokenStream, input: TokenStream) -> TokenStream {
     test::test(attr, input)
 }
+
+/// Automatically implements the `Default` trait for a struct that uses
+/// `sol_storage!`.
+///
+/// This macro initializes the struct fields based on how they are laid out in
+/// the EVM state trie. It is intended to be a helper for tests to avoid having
+/// to implement `Default` for each contract.
+///
+/// # Usage
+///
+/// To use this macro, simply add `#[derive(motsu::DefaultStorageLayout)]` to
+/// your `sol_storage!` struct. Make sure all the fields in your struct are
+/// compatible with Stylus' storage, that means they implement the `StorageType`
+/// trait.
+///
+/// # Examples
+///
+/// ```rust,ignore
+/// sol_storage! {
+///    #[derive(motsu::DefaultStorageLayout)]
+///    pub struct Erc20 {
+///        /// Maps users to balances.
+///        mapping(address => uint256) _balances;
+///        /// Maps users to a mapping of each spender's allowance.
+///        mapping(address => mapping(address => uint256)) _allowances;
+///        /// The total supply of the token.
+///        uint256 _total_supply;
+///    }
+/// }
+/// ```
+///
+/// ## Notice
+///
+/// For now this macro only works with structs that use the
+/// `sol_storage!` macro, which allows you to use the solidity syntax.
+/// If you want to write your contracts using `#[solidity_storage]`
+/// and the Rust syntax, you will have to implement `Default` yourself.
+///
+/// ## See Also
+///
+/// - [Layout of State Variables in Storage](https://docs.soliditylang.org/en/latest/internals/layout_in_storage.html)
+
+#[proc_macro_derive(DefaultStorageLayout)]
+pub fn derive_stylus_default(input: TokenStream) -> TokenStream {
+    let input = parse_macro_input!(input as DeriveInput);
+    default_storage_layout::impl_default_storage_layout(&input)
+}
diff --git a/lib/motsu/src/lib.rs b/lib/motsu/src/lib.rs
index d18f15c..0f8594d 100644
--- a/lib/motsu/src/lib.rs
+++ b/lib/motsu/src/lib.rs
@@ -74,4 +74,4 @@ pub mod prelude;
 mod shims;
 mod storage;
 
-pub use motsu_proc::test;
+pub use motsu_proc::{test, DefaultStorageLayout};


Test patch:
diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml
index 5a821fd..8c6015c 100644
--- a/.github/workflows/e2e-tests.yml
+++ b/.github/workflows/e2e-tests.yml
@@ -1,4 +1,4 @@
-# This workflow runs our end-to-end tests suite and collects coverage.
+# This workflow runs our end-to-end tests suite.
 #
 # It roughly follows these steps:
 # - Install Rust
@@ -6,8 +6,6 @@
 # - Install `solc`
 # - Spin up `nitro-testnode`
 #
-# And then we either run the test suite or run `cargo llvm-cov`.
-#
 # Contract deployments and account funding happen on a per-test basis.
 name: e2e
 permissions:
@@ -69,63 +67,3 @@ jobs:
           export NIGHTLY_TOOLCHAIN=${{steps.toolchain.outputs.name}}
           ./scripts/e2e-tests.sh
 
-  coverage:
-    name: coverage
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v4
-      - uses: actions/cache@v4
-        with:
-          path: |
-            ~/.cargo/registry/index/
-            ~/.cargo/registry/cache/
-            ~/.cargo/git/db/
-            target/
-          key: ${{ runner.os }}-cargo-${{ hashFiles('Cargo.lock') }}
-
-      - name: cache cargo-stylus
-        uses: actions/cache@v4
-        with:
-          path: |
-            ~/.cargo/bin/
-            ~/.cargo/.crates.toml
-          key: ${{ runner.os }}-cargo-bin-cargo-stylus@0.2.1
-          save-always: true
-
-      - name: set up rust
-        uses: dtolnay/rust-toolchain@master
-        id: toolchain
-        with:
-          target: wasm32-unknown-unknown
-          components: rust-src, llvm-tools-preview
-          toolchain: nightly-2024-01-01
-
-      - name: install cargo-stylus
-        run: RUSTFLAGS="-C link-args=-rdynamic" cargo install cargo-stylus@0.2.1
-
-      - name: install solc
-        run: |
-          curl -LO https://github.com/ethereum/solidity/releases/download/v0.8.21/solc-static-linux
-          sudo mv solc-static-linux /usr/bin/solc
-          sudo chmod a+x /usr/bin/solc
-
-      - name: setup nitro node
-        run: ./scripts/nitro-testnode.sh -d -i
-
-      - name: compile contracts
-        run: cargo build --release --target wasm32-unknown-unknown -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort
-      - name: cargo install cargo-llvm-cov
-        uses: taiki-e/install-action@cargo-llvm-cov
-      - name: cargo generate-lockfile
-        if: hashFiles('Cargo.lock') == ''
-        run: cargo generate-lockfile
-      - name: cargo llvm-cov
-        run: RPC_URL=http://localhost:8547 cargo llvm-cov --locked --features std,e2e --test '*' --lcov --output-path lcov.info
-      - name: Record Rust version
-        run: echo "RUST=$(rustc --version)" >> "$GITHUB_ENV"
-      - name: Upload to codecov.io
-        uses: codecov/codecov-action@v4
-        with:
-          fail_ci_if_error: true
-          token: ${{ secrets.CODECOV_TOKEN }}
-          env_vars: OS,RUST
diff --git a/lib/motsu-proc/src/test.rs b/lib/motsu-proc/src/test.rs
index 4f9b4e7..0fe7699 100644
--- a/lib/motsu-proc/src/test.rs
+++ b/lib/motsu-proc/src/test.rs
@@ -3,17 +3,6 @@ use proc_macro::TokenStream;
 use quote::quote;
 use syn::{parse_macro_input, FnArg};
 
-/// Shorthand to print nice errors.
-macro_rules! error {
-    ($tokens:expr, $($msg:expr),+ $(,)?) => {{
-        let error = syn::Error::new(syn::spanned::Spanned::span(&$tokens), format!($($msg),+));
-        return error.to_compile_error().into();
-    }};
-    (@ $tokens:expr, $($msg:expr),+ $(,)?) => {{
-        return Err(syn::Error::new(syn::spanned::Spanned::span(&$tokens), format!($($msg),+)))
-    }};
-}
-
 /// Defines a unit test that provides access to Stylus' execution context.
 ///
 /// For more information see [`crate::test`].
diff --git a/lib/motsu-proc/tests/default.rs b/lib/motsu-proc/tests/default.rs
new file mode 100644
index 0000000..3fe4b10
--- /dev/null
+++ b/lib/motsu-proc/tests/default.rs
@@ -0,0 +1,130 @@
+use alloy_primitives::{address, Address, U128, U16, U256, U32, U64, U8};
+use stylus_sdk::stylus_proc::sol_storage;
+
+sol_storage! {
+    #[derive(motsu_proc::DefaultStorageLayout)]
+    pub struct Erc20 {
+        mapping(address => uint256) _balances;
+        mapping(address => mapping(address => uint256)) _allowances;
+        uint256 _total_supply;
+    }
+}
+
+#[motsu::test]
+fn mapping_initializes_and_updates(contract: Erc20) {
+    let key = address!("a935CEC3c5Ef99D7F1016674DEFd455Ef06776C5");
+    let value = U256::from(100);
+    contract._balances.insert(key, value);
+    let balance = contract._balances.get(key);
+    assert_eq!(balance, value);
+}
+
+sol_storage! {
+    #[derive(motsu_proc::DefaultStorageLayout)]
+    pub struct UintSorted {
+        uint256 a;
+        uint128 b;
+        uint64 c;
+        uint32 d;
+        uint16 e;
+        uint8 f;
+        uint8 g;
+    }
+}
+
+#[motsu::test]
+fn uint_sorted_initializes(contract: UintSorted) {
+    assert_eq!(contract.a.get(), U256::ZERO);
+    assert_eq!(contract.b.get(), U128::ZERO);
+    assert_eq!(contract.c.get(), U64::ZERO);
+    assert_eq!(contract.d.get(), U32::ZERO);
+    assert_eq!(contract.e.get(), U16::ZERO);
+    assert_eq!(contract.f.get(), U8::ZERO);
+    assert_eq!(contract.g.get(), U8::ZERO);
+}
+
+#[motsu::test]
+fn uint_sorted_updates(contract: UintSorted) {
+    contract.a.set(U256::from(1));
+    contract.b.set(U128::from(2));
+    contract.c.set(U64::from(3));
+    contract.d.set(U32::from(4));
+    contract.e.set(U16::from(5));
+    contract.f.set(U8::from(6));
+    contract.g.set(U8::from(7));
+
+    assert_eq!(contract.a.get(), U256::from(1));
+    assert_eq!(contract.b.get(), U128::from(2));
+    assert_eq!(contract.c.get(), U64::from(3));
+    assert_eq!(contract.d.get(), U32::from(4));
+    assert_eq!(contract.e.get(), U16::from(5));
+    assert_eq!(contract.f.get(), U8::from(6));
+    assert_eq!(contract.g.get(), U8::from(7));
+}
+
+sol_storage! {
+    #[derive(motsu_proc::DefaultStorageLayout)]
+    pub struct UintUnsorted {
+        uint256 a;
+        uint16 b;
+        uint64 c;
+        uint8 d;
+        uint32 e;
+        uint128 f;
+        uint8 g;
+    }
+}
+
+#[motsu::test]
+fn uint_unsorted_initializes(contract: UintUnsorted) {
+    assert_eq!(contract.a.get(), U256::ZERO);
+    assert_eq!(contract.b.get(), U16::ZERO);
+    assert_eq!(contract.c.get(), U64::ZERO);
+    assert_eq!(contract.d.get(), U8::ZERO);
+    assert_eq!(contract.e.get(), U32::ZERO);
+    assert_eq!(contract.f.get(), U128::ZERO);
+    assert_eq!(contract.g.get(), U8::ZERO);
+}
+
+#[motsu::test]
+fn uint_unsorted_updates(contract: UintUnsorted) {
+    contract.a.set(U256::from(1));
+    contract.b.set(U16::from(5));
+    contract.c.set(U64::from(3));
+    contract.d.set(U8::from(6));
+    contract.e.set(U32::from(4));
+    contract.f.set(U128::from(2));
+    contract.g.set(U8::from(7));
+
+    assert_eq!(contract.a.get(), U256::from(1));
+    assert_eq!(contract.b.get(), U16::from(5));
+    assert_eq!(contract.c.get(), U64::from(3));
+    assert_eq!(contract.d.get(), U8::from(6));
+    assert_eq!(contract.e.get(), U32::from(4));
+    assert_eq!(contract.f.get(), U128::from(2));
+    assert_eq!(contract.g.get(), U8::from(7));
+}
+
+sol_storage! {
+    #[derive(motsu_proc::DefaultStorageLayout)]
+    pub struct AddressContract {
+        address lender;
+        address borrower;
+    }
+}
+
+#[motsu::test]
+fn address_initializes(contract: AddressContract) {
+    assert_eq!(contract.lender.get(), Address::ZERO);
+    assert_eq!(contract.borrower.get(), Address::ZERO);
+}
+
+#[motsu::test]
+fn address_updates(contract: AddressContract) {
+    let lender = address!("a935CEC3c5Ef99D7F1016674DEFd455Ef06776C5");
+    let borrower = address!("DeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF");
+    contract.lender.set(lender);
+    contract.borrower.set(borrower);
+    assert_eq!(contract.lender.get(), lender);
+    assert_eq!(contract.borrower.get(), borrower);
+}