Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: add generic extraction mapping of mappings to integration test (part 5, last part of generic extraction) #404

Open
wants to merge 8 commits into
base: generic-extraction-integration-test
Choose a base branch
from
35 changes: 34 additions & 1 deletion mp2-common/src/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ mod test {
types::MAX_BLOCK_LEN,
utils::{Endianness, Packer},
};
use mp2_test::eth::get_sepolia_url;
use mp2_test::eth::{get_mainnet_url, get_sepolia_url};

#[tokio::test]
#[ignore]
Expand Down Expand Up @@ -532,6 +532,39 @@ mod test {
Ok(())
}

#[tokio::test]
async fn test_pidgy_pinguin_mapping_slot() -> Result<()> {
// first pinguin holder https://dune.com/queries/2450476/4027653
// holder: 0x188b264aa1456b869c3a92eeed32117ebb835f47
// NFT id https://opensea.io/assets/ethereum/0xbd3531da5cf5857e7cfaa92426877b022e612cf8/1116
let mapping_value =
Address::from_str("0xee5ac9c6db07c26e71207a41e64df42e1a2b05cf").unwrap();
let nft_id: u32 = 1116;
let mapping_key = left_pad32(&nft_id.to_be_bytes());
let url = get_mainnet_url();
let provider = ProviderBuilder::new().on_http(url.parse().unwrap());

// extracting from
// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol
// assuming it's using ERC731Enumerable that inherits ERC721
let mapping_slot = 2;
// pudgy pinguins
let pudgy_address = Address::from_str("0xBd3531dA5CF5857e7CfAA92426877b022e612cf8")?;
let query = ProofQuery::new_mapping_slot(pudgy_address, mapping_slot, mapping_key.to_vec());
let res = query
.query_mpt_proof(&provider, BlockNumberOrTag::Latest)
.await?;
let raw_address = ProofQuery::verify_storage_proof(&res)?;
// the value is actually RLP encoded !
let decoded_address: Vec<u8> = rlp::decode(&raw_address).unwrap();
let leaf_node: Vec<Vec<u8>> = rlp::decode_list(res.storage_proof[0].proof.last().unwrap());
println!("leaf_node[1].len() = {}", leaf_node[1].len());
// this is read in the same order
let found_address = Address::from_slice(&decoded_address.into_iter().collect::<Vec<u8>>());
assert_eq!(found_address, mapping_value);
Ok(())
}

#[tokio::test]
async fn test_kashish_contract_proof_query() -> Result<()> {
// https://sepolia.etherscan.io/address/0xd6a2bFb7f76cAa64Dad0d13Ed8A9EFB73398F39E#code
Expand Down
2 changes: 1 addition & 1 deletion mp2-v1/src/values_extraction/leaf_mapping_of_mappings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,9 +186,9 @@ where
.collect();
let hash = b.hash_n_to_hash_no_pad::<CHasher>(inputs);
let row_id = hash_to_int_target(b, hash);
let row_id = b.biguint_to_nonnative(&row_id);

// values_digest = values_digest * row_id
let row_id = b.biguint_to_nonnative(&row_id);
let values_digest = b.curve_scalar_mul(values_digest, &row_id);

// Only one leaf in this node.
Expand Down
1 change: 1 addition & 0 deletions mp2-v1/src/values_extraction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,7 @@ pub type MappingKey = Vec<u8>;
pub type ColumnId = u64;

/// Compute the values digest for mapping of mappings leaf.
#[allow(clippy::too_many_arguments)]
pub fn compute_leaf_mapping_of_mappings_values_digest<const MAX_FIELD_PER_EVM: usize>(
table_info: Vec<ColumnInfo>,
extracted_column_identifiers: &[ColumnId],
Expand Down
69 changes: 67 additions & 2 deletions mp2-v1/test-contracts/src/Simple.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,22 @@ contract Simple {
MappingOperation operation;
}

struct MappingOfSingleValueMappingsChange {
uint256 outerKey;
uint256 innerKey;
uint256 value;
MappingOperation operation;
}

struct MappingOfStructMappingsChange {
uint256 outerKey;
uint256 innerKey;
uint256 field1;
uint128 field2;
uint128 field3;
MappingOperation operation;
}

struct LargeStruct {
// This field should live in one EVM word
uint256 field1;
Expand All @@ -48,9 +64,13 @@ contract Simple {
// Test mapping struct (slot 8)
mapping(uint256 => LargeStruct) public structMapping;

// Test mapping of mappings (slot 9)
// Test mapping of single value mappings (slot 9)
mapping(uint256 => mapping(uint256 => uint256))
public mappingOfSingleValueMappings;

// Test mapping of struct mappings (slot 10)
mapping(uint256 => mapping(uint256 => LargeStruct))
public mappingOfMappings;
public mappingOfStructMappings;

// Set the simple slots.
function setSimples(
Expand Down Expand Up @@ -125,4 +145,49 @@ contract Simple {
}
}

// Set mapping of single value mappings.
function setMappingOfSingleValueMappings(
uint256 outerKey,
uint256 innerKey,
uint256 value
) public {
mappingOfSingleValueMappings[outerKey][innerKey] = value;
}

function changeMappingOfSingleValueMappings(MappingOfSingleValueMappingsChange[] memory changes) public {
for (uint256 i = 0; i < changes.length; i++) {
if (changes[i].operation == MappingOperation.Deletion) {
delete mappingOfSingleValueMappings[changes[i].outerKey][changes[i].innerKey];
} else if (
changes[i].operation == MappingOperation.Insertion ||
changes[i].operation == MappingOperation.Update
) {
setMappingOfSingleValueMappings(changes[i].outerKey, changes[i].innerKey, changes[i].value);
}
}
}

// Set mapping of struct mappings.
function setMappingOfStructMappings(
uint256 outerKey,
uint256 innerKey,
uint256 field1,
uint128 field2,
uint128 field3
) public {
mappingOfStructMappings[outerKey][innerKey] = LargeStruct(field1, field2, field3);
}

function changeMappingOfStructMappings(MappingOfStructMappingsChange[] memory changes) public {
for (uint256 i = 0; i < changes.length; i++) {
if (changes[i].operation == MappingOperation.Deletion) {
delete mappingOfStructMappings[changes[i].outerKey][changes[i].innerKey];
} else if (
changes[i].operation == MappingOperation.Insertion ||
changes[i].operation == MappingOperation.Update
) {
setMappingOfStructMappings(changes[i].outerKey, changes[i].innerKey, changes[i].field1, changes[i].field2, changes[i].field3);
}
}
}
}
2,231 changes: 1,920 additions & 311 deletions mp2-v1/tests/common/bindings/simple.rs

Large diffs are not rendered by default.

171 changes: 166 additions & 5 deletions mp2-v1/tests/common/cases/contract.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
use super::{
indexing::MappingUpdate,
storage_slot_value::{LargeStruct, StorageSlotValue},
slot_info::{LargeStruct, MappingKey, MappingOfMappingsKey, StorageSlotValue},
table_source::DEFAULT_ADDRESS,
};
use crate::common::{
bindings::simple::{
Simple,
Simple::{MappingChange, MappingOperation, MappingStructChange},
Simple::{
MappingChange, MappingOfSingleValueMappingsChange, MappingOfStructMappingsChange,
MappingOperation, MappingStructChange,
},
},
TestContext,
};
Expand Down Expand Up @@ -121,7 +123,27 @@ impl ContractController for LargeStruct {
}
}

impl ContractController for Vec<MappingUpdate<Address>> {
#[derive(Clone, Debug)]
pub enum MappingUpdate<K, V> {
// key and value
Insertion(K, V),
// key and value
Deletion(K, V),
// key, previous value and new value
Update(K, V, V),
}

impl<K, V> From<&MappingUpdate<K, V>> for MappingOperation {
fn from(update: &MappingUpdate<K, V>) -> Self {
Self::from(match update {
MappingUpdate::Deletion(_, _) => 0,
MappingUpdate::Update(_, _, _) => 1,
MappingUpdate::Insertion(_, _) => 2,
})
}
}

impl ContractController for Vec<MappingUpdate<MappingKey, Address>> {
async fn current_values(_ctx: &TestContext, _contract: &Contract) -> Self {
unimplemented!("Unimplemented for fetching the all mapping values")
}
Expand Down Expand Up @@ -178,7 +200,7 @@ impl ContractController for Vec<MappingUpdate<Address>> {
}
}

impl ContractController for Vec<MappingUpdate<LargeStruct>> {
impl ContractController for Vec<MappingUpdate<MappingKey, LargeStruct>> {
async fn current_values(_ctx: &TestContext, _contract: &Contract) -> Self {
unimplemented!("Unimplemented for fetching the all mapping values")
}
Expand Down Expand Up @@ -232,3 +254,142 @@ impl ContractController for Vec<MappingUpdate<LargeStruct>> {
log::info!("Updated simple contract for mapping values of LargeStruct");
}
}

impl ContractController for Vec<MappingUpdate<MappingOfMappingsKey, U256>> {
async fn current_values(_ctx: &TestContext, _contract: &Contract) -> Self {
unimplemented!("Unimplemented for fetching the all mapping of mappings")
}

async fn update_contract(&self, ctx: &TestContext, contract: &Contract) {
let provider = ProviderBuilder::new()
.with_recommended_fillers()
.wallet(ctx.wallet())
.on_http(ctx.rpc_url.parse().unwrap());
let contract = Simple::new(contract.address, &provider);

let changes = self
.iter()
.map(|tuple| {
let operation: MappingOperation = tuple.into();
let operation = operation.into();
let (k, v) = match tuple {
MappingUpdate::Insertion(k, v)
| MappingUpdate::Deletion(k, v)
| MappingUpdate::Update(k, _, v) => (k, v),
};

MappingOfSingleValueMappingsChange {
operation,
outerKey: k.outer_key,
innerKey: k.inner_key,
value: *v,
}
})
.collect_vec();

let call = contract.changeMappingOfSingleValueMappings(changes);
call.send().await.unwrap().watch().await.unwrap();
// Sanity check
for update in self.iter() {
match update {
MappingUpdate::Insertion(k, v) => {
let res = contract
.mappingOfSingleValueMappings(k.outer_key, k.inner_key)
.call()
.await
.unwrap();
assert_eq!(&res._0, v, "Insertion is wrong on contract");
}
MappingUpdate::Deletion(k, _) => {
let res = contract
.mappingOfSingleValueMappings(k.outer_key, k.inner_key)
.call()
.await
.unwrap();
assert_eq!(res._0, U256::ZERO, "Deletion is wrong on contract");
}
MappingUpdate::Update(k, _, v) => {
let res = contract
.mappingOfSingleValueMappings(k.outer_key, k.inner_key)
.call()
.await
.unwrap();
assert_eq!(&res._0, v, "Update is wrong on contract");
}
}
}
log::info!("Updated simple contract for mapping of single value mappings");
}
}

impl ContractController for Vec<MappingUpdate<MappingOfMappingsKey, LargeStruct>> {
async fn current_values(_ctx: &TestContext, _contract: &Contract) -> Self {
unimplemented!("Unimplemented for fetching the all mapping of mappings")
}

async fn update_contract(&self, ctx: &TestContext, contract: &Contract) {
let provider = ProviderBuilder::new()
.with_recommended_fillers()
.wallet(ctx.wallet())
.on_http(ctx.rpc_url.parse().unwrap());
let contract = Simple::new(contract.address, &provider);

let changes = self
.iter()
.map(|tuple| {
let operation: MappingOperation = tuple.into();
let operation = operation.into();
let (k, v) = match tuple {
MappingUpdate::Insertion(k, v)
| MappingUpdate::Deletion(k, v)
| MappingUpdate::Update(k, _, v) => (k, v),
};

MappingOfStructMappingsChange {
operation,
outerKey: k.outer_key,
innerKey: k.inner_key,
field1: v.field1,
field2: v.field2,
field3: v.field3,
}
})
.collect_vec();

let call = contract.changeMappingOfStructMappings(changes);
call.send().await.unwrap().watch().await.unwrap();
// Sanity check
for update in self.iter() {
match update {
MappingUpdate::Insertion(k, v) => {
let res = contract
.mappingOfStructMappings(k.outer_key, k.inner_key)
.call()
.await
.unwrap();
let res = LargeStruct::from(res);
assert_eq!(&res, v, "Insertion is wrong on contract");
}
MappingUpdate::Deletion(k, _) => {
let res = contract
.mappingOfStructMappings(k.outer_key, k.inner_key)
.call()
.await
.unwrap();
let res = LargeStruct::from(res);
assert_eq!(res, LargeStruct::default(), "Deletion is wrong on contract");
}
MappingUpdate::Update(k, _, v) => {
let res = contract
.mappingOfStructMappings(k.outer_key, k.inner_key)
.call()
.await
.unwrap();
let res = LargeStruct::from(res);
assert_eq!(&res, v, "Update is wrong on contract");
}
}
}
log::info!("Updated simple contract for mapping of LargeStruct mappings");
}
}
Loading