diff --git a/eth-bytecode-db/eth-bytecode-db-server/tests/verifier_alliance.rs b/eth-bytecode-db/eth-bytecode-db-server/tests/verifier_alliance.rs index 945824109..0c232f917 100644 --- a/eth-bytecode-db/eth-bytecode-db-server/tests/verifier_alliance.rs +++ b/eth-bytecode-db/eth-bytecode-db-server/tests/verifier_alliance.rs @@ -276,9 +276,9 @@ async fn insert_contract_deployment(txn: &DatabaseTransaction, test_case: &TestC chain_id: Set(test_case.chain_id.into()), address: Set(test_case.address.to_vec()), transaction_hash: Set(test_case.transaction_hash.to_vec()), - block_number: Set(Some(test_case.block_number.into())), - txindex: Set(Some(test_case.transaction_index.into())), - deployer: Set(Some(test_case.deployer.to_vec())), + block_number: Set(test_case.block_number.into()), + txindex: Set(test_case.transaction_index.into()), + deployer: Set(test_case.deployer.to_vec()), contract_id: Set(contract_id), } .insert(txn) @@ -386,17 +386,15 @@ async fn check_contract_deployment(db: &DatabaseConnection, test_case: &TestCase "Invalid contract_deployments.transaction_hash" ); assert_eq!( - Some(test_case_block_number), - contract_deployment.block_number, + test_case_block_number, contract_deployment.block_number, "Invalid contract_deployments.block_number" ); assert_eq!( - Some(test_case_transaction_index), - contract_deployment.txindex, + test_case_transaction_index, contract_deployment.txindex, "Invalid contract_deployments.txindex" ); assert_eq!( - Some(test_case.deployer.to_vec()), + test_case.deployer.to_vec(), contract_deployment.deployer, "Invalid contract_deployments.deployer" ); diff --git a/eth-bytecode-db/eth-bytecode-db/src/verification/db/verifier_alliance_db.rs b/eth-bytecode-db/eth-bytecode-db/src/verification/db/verifier_alliance_db.rs index 3cd7a6bc9..d1300f162 100644 --- a/eth-bytecode-db/eth-bytecode-db/src/verification/db/verifier_alliance_db.rs +++ b/eth-bytecode-db/eth-bytecode-db/src/verification/db/verifier_alliance_db.rs @@ -6,7 +6,7 @@ use anyhow::Context; use blockscout_display_bytes::Bytes as DisplayBytes; use sea_orm::{ entity::prelude::ColumnTrait, ActiveValue::Set, DatabaseConnection, DatabaseTransaction, - EntityTrait, JoinType, QueryFilter, QuerySelect, RelationTrait, TransactionTrait, + EntityTrait, QueryFilter, TransactionTrait, }; use verifier_alliance_entity::{ code, compiled_contracts, contract_deployments, contracts, verified_contracts, @@ -33,26 +33,35 @@ pub(crate) async fn insert_data( .await .context("begin database transaction")?; - let contract = retrieve_contract(&txn, &deployment_data) + let contract_deployment = retrieve_contract_deployment(&txn, &deployment_data) .await - .context("retrieve contract")? + .context("retrieve contract contract_deployment")? .ok_or_else(|| { anyhow::anyhow!( - "contract was not found: chain_id={}, address={}, transaction_hash={}", + "contract deployment was not found: chain_id={}, address={}, transaction_hash={}", deployment_data.chain_id, DisplayBytes::from(deployment_data.contract_address.clone()), DisplayBytes::from(deployment_data.transaction_hash.clone()) ) })?; + let contract = retrieve_contract(&txn, &contract_deployment) + .await + .context("retrieve contract")?; + let compiled_contract = insert_compiled_contract(&txn, source_response) .await .context("insert compiled_contract")?; - let _verified_contract = - insert_verified_contract(&deployment_data, &txn, &contract, &compiled_contract) - .await - .context("insert verified_contract")?; + let _verified_contract = insert_verified_contract( + &deployment_data, + &txn, + &contract, + &contract_deployment, + &compiled_contract, + ) + .await + .context("insert verified_contract")?; txn.commit().await.context("commit transaction")?; @@ -85,15 +94,11 @@ pub(crate) async fn insert_deployment_data( Ok(()) } -async fn retrieve_contract( +async fn retrieve_contract_deployment( txn: &DatabaseTransaction, deployment_data: &ContractDeploymentData, -) -> Result, anyhow::Error> { - contracts::Entity::find() - .join( - JoinType::Join, - contracts::Relation::ContractDeployments.def(), - ) +) -> Result, anyhow::Error> { + contract_deployments::Entity::find() .filter(contract_deployments::Column::ChainId.eq(deployment_data.chain_id)) .filter(contract_deployments::Column::Address.eq(deployment_data.contract_address.clone())) .filter( @@ -102,7 +107,23 @@ async fn retrieve_contract( ) .one(txn) .await - .context("select from \"contracts\" joined with \"contract_deployments\"") + .context("select from \"contract_deployments\"") +} + +async fn retrieve_contract( + txn: &DatabaseTransaction, + contract_deployment: &contract_deployments::Model, +) -> Result { + contracts::Entity::find_by_id(contract_deployment.contract_id) + .one(txn) + .await + .context("select from \"contracts\" by id")? + .ok_or_else(|| { + anyhow::anyhow!( + "contract was not found, though referring contract deployment exists; contract_id={}", + contract_deployment.contract_id + ) + }) } async fn retrieve_code( @@ -170,6 +191,7 @@ async fn insert_verified_contract( deployment_data: &ContractDeploymentData, txn: &DatabaseTransaction, contract: &contracts::Model, + contract_deployment: &contract_deployments::Model, compiled_contract: &compiled_contracts::Model, ) -> Result { let (creation_match, creation_values, creation_transformations) = check_code_match( @@ -201,8 +223,10 @@ async fn insert_verified_contract( let active_model = verified_contracts::ActiveModel { id: Default::default(), + created_at: Default::default(), + updated_at: Default::default(), + deployment_id: Set(contract_deployment.id), compilation_id: Set(compiled_contract.id), - contract_id: Set(contract.id), creation_match: Set(creation_match), creation_values: Set(creation_values), creation_transformations: Set(creation_transformations), @@ -218,7 +242,7 @@ async fn insert_verified_contract( false, [ (CompilationId, compiled_contract.id), - (ContractId, contract.id), + (DeploymentId, contract_deployment.id), ] )?; @@ -258,6 +282,8 @@ async fn insert_compiled_contract( let active_model = compiled_contracts::ActiveModel { id: Default::default(), + created_at: Default::default(), + updated_at: Default::default(), compiler: Set(compiler.to_string()), version: Set(source.compiler_version), language: Set(language.to_string()), @@ -297,9 +323,11 @@ async fn insert_contract_deployment( chain_id: Set(deployment_data.chain_id.into()), address: Set(deployment_data.contract_address.clone()), transaction_hash: Set(deployment_data.transaction_hash.clone()), - block_number: Set(deployment_data.block_number.map(|v| v.into())), - txindex: Set(deployment_data.transaction_index.map(|v| v.into())), - deployer: Set(deployment_data.deployer), + block_number: Set(deployment_data.block_number.unwrap_or(-1).into()), + txindex: Set(deployment_data.transaction_index.unwrap_or(-1).into()), + deployer: Set(deployment_data + .deployer + .unwrap_or(ethers_core::types::Address::zero().0.to_vec())), contract_id: Set(contract.id), }; let (contract_deployment, _inserted) = insert_then_select!( diff --git a/eth-bytecode-db/eth-bytecode-db/src/verification/handlers/mod.rs b/eth-bytecode-db/eth-bytecode-db/src/verification/handlers/mod.rs index d802540a3..c39fd98fd 100644 --- a/eth-bytecode-db/eth-bytecode-db/src/verification/handlers/mod.rs +++ b/eth-bytecode-db/eth-bytecode-db/src/verification/handlers/mod.rs @@ -333,10 +333,18 @@ async fn process_verifier_alliance_db_action( let database_source = DatabaseReadySource::try_from(source) .context("Converting source into database ready version")?; + // At least one of creation and runtime code should exist to add the contract into the database. + if creation_code.is_none() && runtime_code.is_none() { + anyhow::bail!("Both creation and runtime codes are nulls") + } + + // TODO: make transaction_hash input argument optional, and calculate it + // as `keccak256(creation_code || runtime_code)` in that case + let transaction_hash = transaction_hash.to_vec(); let deployment_data = db::verifier_alliance_db::ContractDeploymentData { chain_id, contract_address: contract_address.to_vec(), - transaction_hash: transaction_hash.to_vec(), + transaction_hash, block_number, transaction_index, deployer: deployer.map(|deployer| deployer.to_vec()), @@ -357,6 +365,7 @@ async fn process_verifier_alliance_db_action( .await .context("Insert deployment data into verifier alliance database")?; } + db::verifier_alliance_db::insert_data(db_client, database_source, deployment_data) .await .context("Insert data into verifier alliance database")?; diff --git a/eth-bytecode-db/eth-bytecode-db/tests/verifier_alliance.rs b/eth-bytecode-db/eth-bytecode-db/tests/verifier_alliance.rs index 3981e3b81..680295639 100644 --- a/eth-bytecode-db/eth-bytecode-db/tests/verifier_alliance.rs +++ b/eth-bytecode-db/eth-bytecode-db/tests/verifier_alliance.rs @@ -225,9 +225,9 @@ async fn insert_contract_deployment(txn: &DatabaseTransaction, test_case: &TestC chain_id: Set(test_case.chain_id.into()), address: Set(test_case.address.to_vec()), transaction_hash: Set(test_case.transaction_hash.to_vec()), - block_number: Set(Some(test_case.block_number.into())), - txindex: Set(Some(test_case.transaction_index.into())), - deployer: Set(Some(test_case.deployer.to_vec())), + block_number: Set(test_case.block_number.into()), + txindex: Set(test_case.transaction_index.into()), + deployer: Set(test_case.deployer.to_vec()), contract_id: Set(contract_id), } .insert(txn) @@ -335,17 +335,15 @@ async fn check_contract_deployment(db: &DatabaseConnection, test_case: &TestCase "Invalid contract_deployments.transaction_hash" ); assert_eq!( - Some(test_case_block_number), - contract_deployment.block_number, + test_case_block_number, contract_deployment.block_number, "Invalid contract_deployments.block_number" ); assert_eq!( - Some(test_case_transaction_index), - contract_deployment.txindex, + test_case_transaction_index, contract_deployment.txindex, "Invalid contract_deployments.txindex" ); assert_eq!( - Some(test_case.deployer.to_vec()), + test_case.deployer.to_vec(), contract_deployment.deployer, "Invalid contract_deployments.deployer" ); diff --git a/eth-bytecode-db/eth-bytecode-db/verifier-alliance-entity/src/code.rs b/eth-bytecode-db/eth-bytecode-db/verifier-alliance-entity/src/code.rs index 4cf699b19..15c1ac1ef 100644 --- a/eth-bytecode-db/eth-bytecode-db/verifier-alliance-entity/src/code.rs +++ b/eth-bytecode-db/eth-bytecode-db/verifier-alliance-entity/src/code.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.2 +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.11 use sea_orm::entity::prelude::*; diff --git a/eth-bytecode-db/eth-bytecode-db/verifier-alliance-entity/src/compiled_contracts.rs b/eth-bytecode-db/eth-bytecode-db/verifier-alliance-entity/src/compiled_contracts.rs index 635358cf6..228805d7e 100644 --- a/eth-bytecode-db/eth-bytecode-db/verifier-alliance-entity/src/compiled_contracts.rs +++ b/eth-bytecode-db/eth-bytecode-db/verifier-alliance-entity/src/compiled_contracts.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.2 +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.11 use sea_orm::entity::prelude::*; @@ -7,6 +7,8 @@ use sea_orm::entity::prelude::*; pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub id: Uuid, + pub created_at: DateTimeWithTimeZone, + pub updated_at: DateTimeWithTimeZone, pub compiler: String, pub version: String, pub language: String, diff --git a/eth-bytecode-db/eth-bytecode-db/verifier-alliance-entity/src/contract_deployments.rs b/eth-bytecode-db/eth-bytecode-db/verifier-alliance-entity/src/contract_deployments.rs index 6bc4e551d..e6a7c7a86 100644 --- a/eth-bytecode-db/eth-bytecode-db/verifier-alliance-entity/src/contract_deployments.rs +++ b/eth-bytecode-db/eth-bytecode-db/verifier-alliance-entity/src/contract_deployments.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.2 +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.11 use sea_orm::entity::prelude::*; @@ -12,10 +12,10 @@ pub struct Model { pub address: Vec, #[sea_orm(column_type = "Binary(BlobSize::Blob(None))")] pub transaction_hash: Vec, - pub block_number: Option, - pub txindex: Option, - #[sea_orm(column_type = "Binary(BlobSize::Blob(None))", nullable)] - pub deployer: Option>, + pub block_number: Decimal, + pub txindex: Decimal, + #[sea_orm(column_type = "Binary(BlobSize::Blob(None))")] + pub deployer: Vec, pub contract_id: Uuid, } @@ -29,6 +29,8 @@ pub enum Relation { on_delete = "NoAction" )] Contracts, + #[sea_orm(has_many = "super::verified_contracts::Entity")] + VerifiedContracts, } impl Related for Entity { @@ -37,4 +39,10 @@ impl Related for Entity { } } +impl Related for Entity { + fn to() -> RelationDef { + Relation::VerifiedContracts.def() + } +} + impl ActiveModelBehavior for ActiveModel {} diff --git a/eth-bytecode-db/eth-bytecode-db/verifier-alliance-entity/src/contracts.rs b/eth-bytecode-db/eth-bytecode-db/verifier-alliance-entity/src/contracts.rs index 78511ec7b..158b2bc0f 100644 --- a/eth-bytecode-db/eth-bytecode-db/verifier-alliance-entity/src/contracts.rs +++ b/eth-bytecode-db/eth-bytecode-db/verifier-alliance-entity/src/contracts.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.2 +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.11 use sea_orm::entity::prelude::*; @@ -33,8 +33,6 @@ pub enum Relation { Code1, #[sea_orm(has_many = "super::contract_deployments::Entity")] ContractDeployments, - #[sea_orm(has_many = "super::verified_contracts::Entity")] - VerifiedContracts, } impl Related for Entity { @@ -43,10 +41,4 @@ impl Related for Entity { } } -impl Related for Entity { - fn to() -> RelationDef { - Relation::VerifiedContracts.def() - } -} - impl ActiveModelBehavior for ActiveModel {} diff --git a/eth-bytecode-db/eth-bytecode-db/verifier-alliance-entity/src/lib.rs b/eth-bytecode-db/eth-bytecode-db/verifier-alliance-entity/src/lib.rs index 0cbb25acb..15b4f00a3 100644 --- a/eth-bytecode-db/eth-bytecode-db/verifier-alliance-entity/src/lib.rs +++ b/eth-bytecode-db/eth-bytecode-db/verifier-alliance-entity/src/lib.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.2 +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.11 pub mod prelude; diff --git a/eth-bytecode-db/eth-bytecode-db/verifier-alliance-entity/src/prelude.rs b/eth-bytecode-db/eth-bytecode-db/verifier-alliance-entity/src/prelude.rs index 239d8e180..c56d98afc 100644 --- a/eth-bytecode-db/eth-bytecode-db/verifier-alliance-entity/src/prelude.rs +++ b/eth-bytecode-db/eth-bytecode-db/verifier-alliance-entity/src/prelude.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.2 +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.11 pub use super::{ code::Entity as Code, compiled_contracts::Entity as CompiledContracts, diff --git a/eth-bytecode-db/eth-bytecode-db/verifier-alliance-entity/src/verified_contracts.rs b/eth-bytecode-db/eth-bytecode-db/verifier-alliance-entity/src/verified_contracts.rs index 12ed4078f..63aca8bd7 100644 --- a/eth-bytecode-db/eth-bytecode-db/verifier-alliance-entity/src/verified_contracts.rs +++ b/eth-bytecode-db/eth-bytecode-db/verifier-alliance-entity/src/verified_contracts.rs @@ -1,14 +1,16 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.2 +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.11 use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] #[sea_orm(table_name = "verified_contracts")] pub struct Model { - #[sea_orm(primary_key, auto_increment = false)] - pub id: Uuid, + #[sea_orm(primary_key)] + pub id: i64, + pub created_at: DateTimeWithTimeZone, + pub updated_at: DateTimeWithTimeZone, + pub deployment_id: Uuid, pub compilation_id: Uuid, - pub contract_id: Uuid, pub creation_match: bool, #[sea_orm(column_type = "JsonBinary", nullable)] pub creation_values: Option, @@ -32,13 +34,13 @@ pub enum Relation { )] CompiledContracts, #[sea_orm( - belongs_to = "super::contracts::Entity", - from = "Column::ContractId", - to = "super::contracts::Column::Id", + belongs_to = "super::contract_deployments::Entity", + from = "Column::DeploymentId", + to = "super::contract_deployments::Column::Id", on_update = "NoAction", on_delete = "NoAction" )] - Contracts, + ContractDeployments, } impl Related for Entity { @@ -47,9 +49,9 @@ impl Related for Entity { } } -impl Related for Entity { +impl Related for Entity { fn to() -> RelationDef { - Relation::Contracts.def() + Relation::ContractDeployments.def() } } diff --git a/eth-bytecode-db/eth-bytecode-db/verifier-alliance-migration/src/m20220101_000001_initial_migration.rs b/eth-bytecode-db/eth-bytecode-db/verifier-alliance-migration/src/m20220101_000001_initial_migration.rs index a0734b930..4f0973d9e 100644 --- a/eth-bytecode-db/eth-bytecode-db/verifier-alliance-migration/src/m20220101_000001_initial_migration.rs +++ b/eth-bytecode-db/eth-bytecode-db/verifier-alliance-migration/src/m20220101_000001_initial_migration.rs @@ -7,37 +7,52 @@ pub struct Migration; impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { let sql = r#" - -- Needed for gen_random_uuid() + /* Needed for gen_random_uuid() */ CREATE EXTENSION pgcrypto; - -- The `code` table stores a mapping from code hash to bytecode - -- This table may store both normalized and unnormalized code. Code is normalized when all - -- libraries/immutable variables that are not constants are replaced with zeroes. In other words - -- the variable `address private immutable FACTORY = 0xAABB...EEFF` would not be replaced with - -- zeroes, but the variable `address private immutable OWNER = msg.sender` would. + /* + The `code` table stores a mapping from code hash to bytecode. This table may store + both normalized and unnormalized code. + + Code is normalized when all libraries/immutable variables that are not constants are + replaced with zeroes. In other words the variable `address private immutable FACTORY = 0xAABB...EEFF` + would not be replaced with zeroes, but the variable `address private immutable OWNER = msg.sender` would. + + The `code` column is not marked NOT NULL because we need to distinguish between + empty code, and no code. Empty code occurs when a contract is deployed with no runtime code. + No code occurs when a contract's code is written directly to the chain in a hard fork + */ CREATE TABLE code ( - code_hash bytea PRIMARY KEY, -- the keccak256 hash of the bytecode - code bytea -- the raw bytecode itself + /* the keccak256 hash of the `code` column */ + code_hash bytea NOT NULL PRIMARY KEY, + + /* the bytecode */ + code bytea ); - -- Used to designate non-existant code, which is different from empty code i.e. keccak256('') + /* ensure the sentinel value exists */ INSERT INTO code (code_hash, code) VALUES ('\x', NULL); - -- The `contracts` table stores information which can be used to identify a unique contract in a - -- chain-agnostic manner. In other words, suppose you deploy the same contract on two chains, all - -- properties that would be shared across the two chains should go in this table because they uniquely - -- identify the contract. + /* + The `contracts` table stores information which can be used to identify a unique contract in a + chain-agnostic manner. In other words, suppose you deploy the same contract on two chains, all + properties that would be shared across the two chains should go in this table because they uniquely + identify the contract. + */ CREATE TABLE contracts ( - id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + /* an opaque id */ + id uuid NOT NULL PRIMARY KEY DEFAULT gen_random_uuid(), - -- the creation code is the bytecode from the calldata (for eoa deployments) or given to create/create2 - -- the runtime code is the bytecode that was returned from the constructor - -- both fields are not normalized + /* + the creation code is the calldata (for eoa creations) or the instruction input (for create/create2) + the runtime code is the bytecode that's returned by the creation code and stored on-chain - creation_code_hash bytea NOT NULL REFERENCES code (code_hash), - runtime_code_hash bytea NOT NULL REFERENCES code (code_hash), + neither fields are normalized + */ + creation_code_hash bytea NOT NULL REFERENCES code (code_hash), + runtime_code_hash bytea NOT NULL REFERENCES code (code_hash), CONSTRAINT contracts_pseudo_pkey UNIQUE (creation_code_hash, runtime_code_hash) ); @@ -46,33 +61,49 @@ impl MigrationTrait for Migration { CREATE INDEX contracts_runtime_code_hash ON contracts USING btree(runtime_code_hash); CREATE INDEX contracts_creation_code_hash_runtime_code_hash ON contracts USING btree(creation_code_hash, runtime_code_hash); - -- The `contract_deployments` table stores information about a specific deployment unique to a chain. - -- One contract address may have multiple deployments on a single chain if SELFDESTRUCT/CREATE2 is used - -- The info stored in this table should be retrievable from an archive node. In other words, it should - -- not be augmented with any inferred data + /* + The `contract_deployments` table stores information about a specific deployment unique to a chain. + One contract address may have multiple deployments on a single chain if SELFDESTRUCT/CREATE2 is used + The info stored in this table should be retrievable from an archive node. In other words, it should + not be augmented with any inferred data + */ CREATE TABLE contract_deployments ( - -- an opaque id assigned to this specific deployment, since it's easier to reference than the three fields below - id uuid PRIMARY KEY DEFAULT gen_random_uuid(), - - -- these three fields uniquely identify a specific deployment, assuming that it is never possible - -- to deploy to successfully an address twice in the same transaction - -- (create2 -> selfdestruct -> create2 should revert on the second create2) - -- in the case of a "genesis" contract, the transaction_hash should be set - -- to keccak256(creation_code_hash || runtime_code_hash) - chain_id numeric NOT NULL, - address bytea NOT NULL, - transaction_hash bytea NOT NULL, - - -- geth full nodes have the option to prune the transaction index, so this is another way - -- to find the transaction. be sure to check that the hash is correct! - block_number numeric, - txindex numeric, - - -- this is the address which actually deployed the contract (i.e. called the create/create2 opcode) - deployer bytea, - - -- the contract itself + /* an opaque id*/ + id uuid NOT NULL PRIMARY KEY DEFAULT gen_random_uuid(), + + /* + these three fields uniquely identify a specific deployment of a contract, assuming + that it is impossible to deploy to successfully an address twice in the same transaction + (create2 -> selfdestruct -> create2 should revert on the second create2) + + in the case of a "genesis" contract, the transaction_hash should be set + to keccak256(creation_code_hash || runtime_code_hash). this is because the transaction_hash + needs to differ to distinguish between two versions of the same genesis contract, and so + it needs to embed inside it the only feature that changes. + + also note that for genesis contracts, creation_code_hash may be '\x' (i.e. there is no creation code) + */ + chain_id numeric NOT NULL, + address bytea NOT NULL, + transaction_hash bytea NOT NULL, + + /* + geth full nodes have the ability to prune the transaction index, so if the transaction_hash + can't be found directly, use the block_number and txindex. make sure to compare the transaction_hash to + make sure it matches! + + for genesis contracts, both values should be set to -1 + */ + block_number numeric NOT NULL, + txindex numeric NOT NULL, + + /* + this is the address which actually deployed the contract (i.e. called the create/create2 opcode) + */ + deployer bytea NOT NULL, + + /* the contract itself */ contract_id uuid NOT NULL REFERENCES contracts(id), CONSTRAINT contract_deployments_pseudo_pkey UNIQUE (chain_id, address, transaction_hash) @@ -80,93 +111,119 @@ impl MigrationTrait for Migration { CREATE INDEX contract_deployments_contract_id ON contract_deployments USING btree(contract_id); - -- The compiled_contracts table stores information about a specific compilation. A compilation is - -- defined as a set of inputs (compiler settings, source code, etc) which uniquely correspond to a - -- set of outputs (bytecode, documentation, ast, etc) + /* + The `compiled_contracts` table stores information about a specific compilation. A compilation is + defined as a set of inputs (compiler settings, source code, etc) which uniquely correspond to a + set of outputs (bytecode, documentation, ast, etc) + */ CREATE TABLE compiled_contracts ( - -- an opaque id - id uuid PRIMARY KEY DEFAULT gen_random_uuid(), - - -- these three fields uniquely identify the high-level compiler mode to use - -- note that the compiler is the software ('solc', 'vyper', 'huff') while language is - -- the syntax ('solidity', 'vyper', 'yul'). there may be future compilers which aren't solc - -- but can still compile solidity, which is why we need to differentiate the two. - -- the version should uniquely identify the compiler - compiler VARCHAR NOT NULL, - version VARCHAR NOT NULL, - language VARCHAR NOT NULL, - - -- the name is arbitrary and often not a factor in verifying contracts (solidity encodes it in - -- the auxdata which we ignore, and vyper doesn't even have the concept of sourceunit-level - -- names) - -- because of this we don't include it in the unique constraint - name VARCHAR NOT NULL, - - -- the fully qualified name is compiler-specific and indicates exactly which contract to look for + /* an opaque id */ + id uuid NOT NULL PRIMARY KEY DEFAULT gen_random_uuid(), + + /* timestamps */ + created_at timestamptz NOT NULL DEFAULT NOW(), + updated_at timestamptz NOT NULL DEFAULT NOW(), + + /* + these three fields uniquely identify the high-level compiler mode to use + + note that the compiler is the software ('solc', 'vyper', 'huff') while language is + the syntax ('solidity', 'vyper', 'yul'). there may be future compilers which aren't solc + but can still compile solidity, which is why we need to differentiate the two + + the version should uniquely identify the compiler + */ + compiler VARCHAR NOT NULL, + version VARCHAR NOT NULL, + language VARCHAR NOT NULL, + + /* + the name is arbitrary and often not a factor in verifying contracts (solidity encodes it in + the auxdata which we ignore, and vyper doesn't even have the concept of sourceunit-level names) + because of this we don't include it in the unique constraint. it is stored purely for informational + purposes + */ + name VARCHAR NOT NULL, + + /* the fully qualified name is compiler-specific and indicates exactly which contract to look for */ fully_qualified_name VARCHAR NOT NULL, - -- map of path to source code (string => string) + /* map of path to source code (string => string) */ sources jsonb NOT NULL, - -- compiler-specific settings such as optimization, linking, etc (string => any) + /* compiler-specific settings such as optimization, linking, etc (string => any) */ compiler_settings jsonb NOT NULL, - -- general and compiler-specific artifacts (abi, userdoc, devdoc, licenses, etc) + /* general and compiler-specific artifacts (abi, userdoc, devdoc, licenses, etc) */ compilation_artifacts jsonb NOT NULL, - -- note that we can't pull out creation/runtime code into its own table - -- imagine that a future compiler and language combo result in the same bytecode - -- this is something that we would want a record of, because the two sources are semantically - -- unique - -- in other words, the hypothetical table would need to be keyed on everything that this table already is + /* + note that we can't pull out creation/runtime code into its own table + imagine that a future compiler and language combo result in the same bytecode + this is something that we would want a record of, because the two sources are semantically + unique + in other words, the hypothetical table would need to be keyed on everything that this table already is + */ - -- these fields store info about the creation code (sourcemaps, linkreferences) + /* these fields store info about the creation code (sourcemaps, linkreferences) */ creation_code_hash bytea NOT NULL REFERENCES code (code_hash), creation_code_artifacts jsonb NOT NULL, - -- these fields store info about the runtime code (sourcemaps, linkreferences, immutables) - -- the runtime code should be normalized (i.e. immutables set to zero) + /* + these fields store info about the runtime code (sourcemaps, linkreferences, immutables) + the runtime code should be normalized (i.e. immutables set to zero) + */ runtime_code_hash bytea NOT NULL REFERENCES code (code_hash), runtime_code_artifacts jsonb NOT NULL, - -- two different compilers producing the same bytecode is unique enough that we want to preserve it - -- the same compiler with two different versions producing the same bytecode is not unique (f.ex nightlies) + /* + two different compilers producing the same bytecode is unique enough that we want to preserve it + the same compiler with two different versions producing the same bytecode is not unique (f.ex nightlies) + */ CONSTRAINT compiled_contracts_pseudo_pkey UNIQUE (compiler, language, creation_code_hash, runtime_code_hash) ); CREATE INDEX compiled_contracts_creation_code_hash ON compiled_contracts USING btree (creation_code_hash); CREATE INDEX compiled_contracts_runtime_code_hash ON compiled_contracts USING btree (runtime_code_hash); - -- The verified_contracts table links an on-chain contract with a compiled_contract - -- Note that only one of creation or runtime bytecode must match, because: - -- We could get a creation match but runtime mismatch if the contract is a proxy that uses assembly to return custom runtime bytecode - -- We could get a runtime match but creation mismatch if the contract is deployed via a create2 factory + /* + The verified_contracts table links an on-chain contract with a compiled_contract + Note that only one of creation or runtime bytecode must match, because: + We could get a creation match but runtime mismatch if the contract is a proxy that uses assembly to return custom runtime bytecode + We could get a runtime match but creation mismatch if the contract is deployed via a create2 factory + */ CREATE TABLE verified_contracts ( - -- an opaque id - id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + /* an opaque id, but sequentially ordered */ + id BIGSERIAL NOT NULL PRIMARY KEY, + + /* timestamps */ + created_at timestamptz NOT NULL DEFAULT NOW(), + updated_at timestamptz NOT NULL DEFAULT NOW(), - -- the foreign references - compilation_id uuid NOT NULL REFERENCES compiled_contracts (id), - contract_id uuid NOT NULL REFERENCES contracts(id), + /* the specific deployment and the specific compilation */ + deployment_id uuid NOT NULL REFERENCES contract_deployments (id), + compilation_id uuid NOT NULL REFERENCES compiled_contracts (id), - -- if the code matches, then the values and transformation fields contain - -- all the information required to transform the compiled bytecode to the deployed bytecode - -- see the json schemas provided for more information + /* + if the code matches, then the values and transformation fields contain + all the information required to transform the compiled bytecode to the deployed bytecode + see the json schemas provided for more information + */ - creation_match bool NOT NULL, - creation_values jsonb, - creation_transformations jsonb, + creation_match bool NOT NULL, + creation_values jsonb, + creation_transformations jsonb, - runtime_match bool NOT NULL, - runtime_values jsonb, - runtime_transformations jsonb, + runtime_match bool NOT NULL, + runtime_values jsonb, + runtime_transformations jsonb, - CONSTRAINT verified_contracts_pseudo_pkey UNIQUE (compilation_id, contract_id) + CONSTRAINT verified_contracts_pseudo_pkey UNIQUE (compilation_id, deployment_id) ); - CREATE INDEX verified_contracts_contract_id ON verified_contracts USING btree (contract_id); + CREATE INDEX verified_contracts_deployment_id ON verified_contracts USING btree (deployment_id); CREATE INDEX verified_contracts_compilation_id ON verified_contracts USING btree (compilation_id); "#;