Skip to content

Commit

Permalink
Slots documentation (#315)
Browse files Browse the repository at this point in the history
 - Added README about Slots library
 - Added documentation of U256 in main README
 - Moved `uin256.nr `from misc to main part of library
- Small refactoring to `slot_test.nr` and fixed wrong order of arguments
given to `dynamic_array` function
- Moved `field_to_bytes32` function from `field.nr` to `bytes32.nr` file
  • Loading branch information
aajj999 authored May 31, 2024
2 parents b673607 + 049fdf6 commit 2149904
Show file tree
Hide file tree
Showing 19 changed files with 161 additions and 67 deletions.
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@
"typescript.tsdk": ".yarn/sdks/typescript/lib",
"prettier.prettierPath": ".yarn/sdks/prettier/index.cjs",
"typescript.enablePromptUseWorkspaceTsdk": true,
"cSpell.words": ["acir", "Barretenberg", "keccaks", "Nargo", "noirc", "viem"]
"cSpell.words": ["acir", "Barretenberg", "keccak", "keccaks", "Nargo", "noirc", "struct", "structs", "viem"]
}
27 changes: 27 additions & 0 deletions ethereum/circuits/lib/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ All the function in this library prove that the objects are contained within som
├── account.nr
├── account_with_storage.nr
├── transaction.nr
├── uint256.nr
└── verifiers
   ├── header.nr
   ├── receipt.nr
Expand Down Expand Up @@ -204,3 +205,29 @@ struct TransactionWithinBlock<MAX_DATA_LEN> {
## RLP decoding

[rlp folder](./src/rlp/README.md) contains tools to work with RLP encoded data

## U256

U256 is a structure to use as a type for big numbers.
It is used when dealing with numbers up to 2<sup>256</sup>. They can exceed Field maximum value.
In particular it is a word size in ETH and therefore it is a basic type used in both storage and slot values calculations.

[There](.src/uint256.nr) is an unoptimized implementation of this type using two U128 structures. Optimized version will appear in Noir.

Traits implemented for U256:

- Add
- Eq
- Serde

```rust
global u128_number = 0x10000000000000000000000000000000;

let big_number = U256::new(u128_number, u128_number);

let sum = big_number + U256::one();
assert_eq(sum, U256 { high: u128_number, low: u128_number + U128::one()});

let serialized: [Field; 4] = big_number.serialize();
assert_eq(U256::deserialize(serialized), big_number);
```
2 changes: 2 additions & 0 deletions ethereum/circuits/lib/src/lib.nr
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ mod misc;
mod chain;
mod serde;
mod serde_test;
mod uint256;
mod uint256_test;

mod fixtures;
mod verifiers;
Expand Down
5 changes: 1 addition & 4 deletions ethereum/circuits/lib/src/misc.nr
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,5 @@ mod fragment_test;
mod fragment_int_test;
mod types;
mod option;
mod field;
mod field_test;
mod bytes32;
mod uint256;
mod uint256_test;
mod bytes32_test;
5 changes: 5 additions & 0 deletions ethereum/circuits/lib/src/misc/bytes32.nr
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,8 @@ pub fn address_to_bytes32(address: [u8; 20]) -> Bytes32 {
}
bytes32
}

pub fn field_to_bytes32(value: Field) -> [u8; 32] {
let byte_slice = value.to_be_bytes(32);
byte_slice.as_array()
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
global MAX_FIELD_VALUE = 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000;

mod test_field_to_bytes32 {
use crate::misc::field::field_to_bytes32;
use crate::misc::field_test::MAX_FIELD_VALUE;
mod field_to_bytes32 {
use crate::misc::bytes32::field_to_bytes32;
use crate::misc::bytes32_test::MAX_FIELD_VALUE;

#[test]
fn zero() {
Expand Down
8 changes: 0 additions & 8 deletions ethereum/circuits/lib/src/misc/field.nr

This file was deleted.

2 changes: 1 addition & 1 deletion ethereum/circuits/lib/src/misc/fragment_int_test.nr
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ mod u8_fragment {
}

mod Field_fragment {
use crate::misc::{fragment::Fragment, field_test::MAX_FIELD_VALUE};
use crate::misc::{fragment::Fragment, bytes32_test::MAX_FIELD_VALUE};

#[test]
fn success() {
Expand Down
3 changes: 2 additions & 1 deletion ethereum/circuits/lib/src/serde.nr
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ use crate::account_with_storage::{
MAX_PREFIXED_KEY_NIBBLE_LEN as STORAGE_MAX_PREFIXED_KEY_NIBBLE_LEN, MAX_STORAGE_DEPTH_NO_LEAF_M,
MAX_STORAGE_VALUE_LEN, MAX_STORAGE_LEAF_LEN
};
use crate::misc::{fragment::Fragment, types::{BYTES32_LENGTH, Bytes32, ADDRESS_LENGTH, Address}, uint256::U256};
use crate::misc::{fragment::Fragment, types::{BYTES32_LENGTH, Bytes32, ADDRESS_LENGTH, Address}};
use crate::uint256::U256;
use crate::merkle_patricia_proofs::proof::{ProofInput, Proof, Node, MAX_NODE_LEN};
use dep::std::unsafe::zeroed;

Expand Down
2 changes: 1 addition & 1 deletion ethereum/circuits/lib/src/serde_test.nr
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ mod U128_ {
}

mod U256 {
use crate::misc::uint256::U256;
use crate::uint256::U256;
use crate::serde::U256_SERIALIZED_LEN;

global u128_number = 0x10000000000000000000000000000000;
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::misc::uint256::U256;
use crate::uint256::U256;

global high = U128::from_integer(0x10000000000000000000000000000000);
global low = U128::zero();
Expand Down
5 changes: 5 additions & 0 deletions vlayer/ethereum/circuits/lib/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# vlayer libraries

## Storage slots

[Docs](./STORAGE_SLOTS.md)
82 changes: 82 additions & 0 deletions vlayer/ethereum/circuits/lib/STORAGE_SLOTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Solidity storage slots

This is a [Noir](https://noir-lang.org) library for calculating storage locations (later: slots) of Solidity variables.

The library consists of the following functions:

- ```rust
pub(crate) fn mapping(slot: Bytes32, key: Bytes32) -> Bytes32;
```

Takes in the mapping `slot`, the `key` and outputs the respective value `slot`.

- ```rust
pub(crate) fn dynamic_array(slot: Bytes32, size: Field, index: Field) -> Bytes32;
```

Takes in the array `slot`, the element `size` and `index` and outputs the respective element `slot`.

```rust
pub(crate) fn dynamic_array_with_precalculated_slot(slot: Bytes32, size: Field, index: Field) -> Bytes32;
```

Similar to a function above, but instead of taking in the array slot it takes in the slot of it's first element which is equal to the `keccak` of the array slot.

This is useful in cases like EIP 1967 where storage slot is computed manually from variable name instead of numeric storage slot to avoid collisions and achieve storage slots stability throughout code upgrades.
Example:
Storage slot `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc` (obtained as `bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1))`.

- ```rust
pub(crate) fn struct_slot(slot: Bytes32, offset: Field) -> Bytes32;
```

Takes in the struct `slot`, the element `offset` and outputs the respective struct field `slot`.

## Examples

Below is the example of how we can use this library to find a storage slot of an NFT owner by `token_id`.

### [CryptoPunks](https://etherscan.io/address/0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb#code)

Owners of tokens are stored in a map at position 10.

```solidity
mapping (uint => address) public punkIndexToAddress;
```

```rust
let owner_slot = mapping(field_to_bytes32(10), token_id)
```

### [Bored Ape Yacht Club](https://etherscan.io/token/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d#code)

This contract is harder to read, but we can use [evm.storage](https://evm.storage/eth/19967215/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d#table) to find storage layout.

```solidity
struct MapEntry {
_key bytes32;
_value bytes32; // This contains padded owner address
};
struct EnumerableMap {
MapEntry[] _entries;
mapping(bytes32 => uint256) _indexes;
};
EnumerableMap _tokenOwners;
```

`_tokenOwners` is a struct that occupies slots 2 and 3, but we are only interested in it's first field.

`_entries` is a dynamic array of structs (of size 2) and we are interested in the second field (with offset 1).
`_entries` array is indexed by `token_id`.

```rust
let array_elem_size = 2; // This element is a struct with two fields. Owner is the second field
let array_elem_idx = bytes32_to_field(token_id);
let struct_owner_field_offset = 1;
let owner_slot = struct_slot(
dynamic_array(2, array_elem_size, array_elem_idx),
struct_owner_field_offset
);
```
4 changes: 2 additions & 2 deletions vlayer/ethereum/circuits/lib/src/nft_list.nr
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ mod mainnet {
use crate::chain_id;
use crate::slot::{struct_slot, dynamic_array_with_precalculated_slot, mapping};
use dep::std::field::bytes32_to_field;
use dep::ethereum::misc::{types::Bytes32, field::field_to_bytes32};
use dep::ethereum::misc::{types::Bytes32, bytes32::field_to_bytes32};

pub fn BORED_APE_YACHT_CLUB() -> ERC721Token {
ERC721Token {
Expand Down Expand Up @@ -47,7 +47,7 @@ mod sepolia {
use crate::chain_id;
use crate::slot::{struct_slot, dynamic_array_with_precalculated_slot};
use dep::std::field::bytes32_to_field;
use dep::ethereum::misc::{types::Bytes32, field::field_to_bytes32};
use dep::ethereum::misc::{types::Bytes32, bytes32::field_to_bytes32};

// free mint: https://sepolia.etherscan.io/address/0x80d97726548fedae6ad7cf8df4f2b514fd24afba#readContract
fn FAKE_BORED_APE_YACHT_CLUB() -> ERC721Token {
Expand Down
5 changes: 4 additions & 1 deletion vlayer/ethereum/circuits/lib/src/slot.nr
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use dep::ethereum::misc::{types::{Address, Bytes32, ADDRESS_LENGTH, BYTES32_LENGTH}, field::field_to_bytes32, bytes::add_bigint};
use dep::ethereum::misc::{
types::{Address, Bytes32, ADDRESS_LENGTH, BYTES32_LENGTH}, bytes32::field_to_bytes32,
bytes::add_bigint
};
use dep::std::hash::keccak256;

global STORAGE_KEY_HASH_INPUT_LENGTH = 64;
Expand Down
64 changes: 22 additions & 42 deletions vlayer/ethereum/circuits/lib/src/slot_test.nr
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
mod mapping {
use crate::slot::mapping;
use dep::ethereum::{misc::bytes32::address_to_bytes32};
use dep::ethereum::misc::{field::field_to_bytes32};
use dep::ethereum::misc::bytes32::address_to_bytes32;
use dep::ethereum::misc::bytes32::field_to_bytes32;

#[test]
fn adress_mapping() {
let slot = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9
];
let slot = field_to_bytes32(9);
let address = [
0x55, 0xfe, 0x00, 0x2a, 0xef, 0xf0, 0x2f, 0x77, 0x36, 0x4d, 0xe3, 0x39, 0xa1, 0x29, 0x29, 0x23, 0xa1, 0x58, 0x44, 0xb8
];
Expand All @@ -20,9 +18,7 @@ mod mapping {

#[test]
fn mapping_of_mapping() {
let slot = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1
];
let slot = field_to_bytes32(1);
let owner = [
0x5d, 0x82, 0x72, 0x63, 0x05, 0x2a, 0x88, 0x64, 0x23, 0xdc, 0xb1, 0x75, 0xef, 0x9b, 0x74, 0x91, 0xcc, 0x92, 0xed, 0x24
];
Expand Down Expand Up @@ -53,60 +49,48 @@ mod mapping {

mod dynamic_array {
use crate::slot::dynamic_array;
use dep::ethereum::misc::field_test::MAX_FIELD_VALUE;
use dep::ethereum::misc::{bytes32_test::MAX_FIELD_VALUE, bytes32::field_to_bytes32};

#[test]
fn index_zero() {
let slot = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2
];
let slot = field_to_bytes32(2);
let expected_slot = [
0x40, 0x57, 0x87, 0xfa, 0x12, 0xa8, 0x23, 0xe0, 0xf2, 0xb7, 0x63, 0x1c, 0xc4, 0x1b, 0x3b, 0xa8, 0x82, 0x8b, 0x33, 0x21, 0xca, 0x81, 0x11, 0x11, 0xfa, 0x75, 0xcd, 0x3a, 0xa3, 0xbb, 0x5a, 0xce
];
assert_eq(expected_slot, dynamic_array(slot, 0, 2));
assert_eq(expected_slot, dynamic_array(slot, 2, 0));
}

#[test]
fn index_positive() {
let slot = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2
];
let slot = field_to_bytes32(2);
let expected_slot = [
0x40, 0x57, 0x87, 0xfa, 0x12, 0xa8, 0x23, 0xe0, 0xf2, 0xb7, 0x63, 0x1c, 0xc4, 0x1b, 0x3b, 0xa8, 0x82, 0x8b, 0x33, 0x21, 0xca, 0x81, 0x11, 0x11, 0xfa, 0x75, 0xcd, 0x3a, 0xa3, 0xbb, 0x5a, 0xdc
];
assert_eq(expected_slot, dynamic_array(slot, 7, 2));
assert_eq(expected_slot, dynamic_array(slot, 2, 7));
}

#[test(should_fail_with="Addition overflow")]
fn fail_overflow() {
let slot = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6
];
let _ = dynamic_array(slot, MAX_FIELD_VALUE, 2);
let slot = field_to_bytes32(6);
let _ = dynamic_array(slot, 2, MAX_FIELD_VALUE);
}
}

mod dynamic_array_with_precalculated_slot {
use crate::slot::dynamic_array_with_precalculated_slot;
use dep::ethereum::misc::field_test::MAX_FIELD_VALUE;
use dep::ethereum::misc::{bytes32_test::MAX_FIELD_VALUE, bytes32::field_to_bytes32};

#[test]
fn index_zero() {
let slot = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2
];
assert_eq(slot, dynamic_array_with_precalculated_slot(slot, 0, 2));
let slot = field_to_bytes32(2);
assert_eq(slot, dynamic_array_with_precalculated_slot(slot, 2, 0));
}

#[test]
fn index_positive() {
let slot = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2
];
let expected_slot = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 + 7 * 2
];
assert_eq(expected_slot, dynamic_array_with_precalculated_slot(slot, 7, 2));
let slot = field_to_bytes32(2);
let expected_slot = field_to_bytes32(2 + 2 * 7);
assert_eq(expected_slot, dynamic_array_with_precalculated_slot(slot, 2, 7));
}

#[test]
Expand All @@ -115,24 +99,23 @@ mod dynamic_array_with_precalculated_slot {
let expected_slot = [
0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x0b, 0x5d, 0x14, 0xed, 9
];
assert_eq(expected_slot, dynamic_array_with_precalculated_slot(slot, 1_000_000_000, 10));
assert_eq(expected_slot, dynamic_array_with_precalculated_slot(slot, 10, 1_000_000_000));
}

#[test(should_fail_with="Addition overflow")]
fn fail_overflow() {
let slot = [0xfe; 32];
let _ = dynamic_array_with_precalculated_slot(slot, MAX_FIELD_VALUE, 2);
let _ = dynamic_array_with_precalculated_slot(slot, 2, MAX_FIELD_VALUE);
}
}

mod struct_slot {
use crate::slot::struct_slot;
use dep::ethereum::misc::bytes32::field_to_bytes32;

#[test]
fn success() {
let slot = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2
];
let slot = field_to_bytes32(2);
let expected_slot = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2
];
Expand All @@ -142,9 +125,6 @@ mod struct_slot {
#[test(should_fail_with="Addition overflow")]
fn overflow() {
let slot = [0xff; 32];
let expected_slot = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1
];
assert_eq(expected_slot, struct_slot(slot, 256));
let _ = struct_slot(slot, 256);
}
}
2 changes: 1 addition & 1 deletion vlayer/examples/circuits/is_ape_owner/src/main.nr
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use dep::ethereum::misc::types::Address;
use dep::token::nft_list::mainnet::BORED_APE_YACHT_CLUB;
use dep::std::field::bytes32_to_field;
use dep::ethereum::misc::field::field_to_bytes32;
use dep::ethereum::misc::bytes32::field_to_bytes32;

mod main_test;

Expand Down
Loading

0 comments on commit 2149904

Please sign in to comment.