Skip to content

Commit

Permalink
Verifier: add support for deployed code with immutable variables (#687)
Browse files Browse the repository at this point in the history
  • Loading branch information
rimrakhimov authored Nov 27, 2023
1 parent 4f2d394 commit 5c8b63c
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use smart_contract_verifier_proto::blockscout::smart_contract_verifier::v2::{
VerifyResponse,
};
use smart_contract_verifier_server::{Settings, SolidityVerifierService};
use solidity_types::TestCase;
use solidity_types::{BytecodeType, TestCase};
use std::{
collections::BTreeMap,
str::{from_utf8, FromStr},
Expand All @@ -40,14 +40,14 @@ async fn global_service() -> &'static Arc<SolidityVerifierService> {
.await
}

async fn test_setup<T: TestCase>(test_case: &T) -> ServiceResponse {
async fn test_setup<T: TestCase>(test_case: &T, bytecode_type: BytecodeType) -> ServiceResponse {
let service = global_service().await;
let app = test::init_service(
App::new().configure(|config| route_solidity_verifier(config, service.clone())),
)
.await;

let request = test_case.to_request();
let request = test_case.to_request(bytecode_type);

TestRequest::post()
.uri(T::route())
Expand All @@ -56,8 +56,8 @@ async fn test_setup<T: TestCase>(test_case: &T) -> ServiceResponse {
.await
}

async fn test_success(test_case: impl TestCase) {
let response = test_setup(&test_case).await;
async fn test_success<T: TestCase>(test_case: &T, bytecode_type: BytecodeType) {
let response = test_setup(test_case, bytecode_type).await;
if !response.status().is_success() {
let status = response.status();
let body = read_body(response).await;
Expand Down Expand Up @@ -260,8 +260,12 @@ async fn test_success(test_case: impl TestCase) {
}
}

async fn _test_failure(test_case: impl TestCase, expected_message: &str) {
let response = test_setup(&test_case).await;
async fn _test_failure<T: TestCase>(
test_case: &T,
bytecode_type: BytecodeType,
expected_message: &str,
) {
let response = test_setup(test_case, bytecode_type).await;

assert!(
response.status().is_success(),
Expand Down Expand Up @@ -293,12 +297,13 @@ async fn _test_failure(test_case: impl TestCase, expected_message: &str) {
);
}

async fn _test_error(
test_case: impl TestCase,
async fn _test_error<T: TestCase>(
test_case: &T,
bytecode_type: BytecodeType,
expected_status: StatusCode,
expected_message: &str,
) {
let response = test_setup(&test_case).await;
let response = test_setup(test_case, bytecode_type).await;
let status = response.status();
let body = read_body(response).await;
let message = from_utf8(&body).expect("Read body as UTF-8");
Expand All @@ -318,13 +323,15 @@ mod success_tests {
#[tokio::test]
async fn returns_compilation_related_artifacts() {
let test_case = solidity_types::from_file::<Flattened>("simple_storage");
test_success(test_case).await;
test_success(&test_case, BytecodeType::CreationInput).await;
test_success(&test_case, BytecodeType::DeployedBytecode).await;
}

#[tokio::test]
async fn returns_compilation_related_artifacts_with_two_cbor_auxdata() {
let test_case = solidity_types::from_file::<Flattened>("two_cbor_auxdata");
test_success(test_case).await;
test_success(&test_case, BytecodeType::CreationInput).await;
test_success(&test_case, BytecodeType::DeployedBytecode).await;
}

// TODO: is not working right now, as auxdata is not retrieved for contracts compiled without metadata hash.
Expand All @@ -333,4 +340,11 @@ mod success_tests {
// let test_case = solidity_types::from_file::<StandardJson>("no_metadata_hash");
// test_success(test_case).await;
// }

#[tokio::test]
async fn verifies_runtime_code_with_immutables() {
let test_case = solidity_types::from_file::<Flattened>("immutables");
test_success(&test_case, BytecodeType::CreationInput).await;
test_success(&test_case, BytecodeType::DeployedBytecode).await;
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
use blockscout_display_bytes::Bytes as DisplayBytes;
use serde::{de::DeserializeOwned, Deserialize};
use smart_contract_verifier_proto::blockscout::smart_contract_verifier::v2::{
source::MatchType, BytecodeType,
};
use smart_contract_verifier_proto::blockscout::smart_contract_verifier::v2::source::MatchType;
use std::{borrow::Cow, collections::BTreeMap};

pub use smart_contract_verifier_proto::blockscout::smart_contract_verifier::v2::BytecodeType;

const TEST_CASES_DIR: &str = "tests/test_cases_solidity";

pub trait TestCase {
fn route() -> &'static str;

fn to_request(&self) -> serde_json::Value;
fn to_request(&self, bytecode_type: BytecodeType) -> serde_json::Value;

fn is_yul(&self) -> bool;

Expand Down Expand Up @@ -90,11 +90,17 @@ impl TestCase for Flattened {
"/api/v2/verifier/solidity/sources:verify-multi-part"
}

fn to_request(&self) -> serde_json::Value {
fn to_request(&self, bytecode_type: BytecodeType) -> serde_json::Value {
let extension = if self.is_yul() { "yul" } else { "sol" };
let bytecode = match bytecode_type {
BytecodeType::Unspecified | BytecodeType::CreationInput => {
self.creation_bytecode.as_str()
}
BytecodeType::DeployedBytecode => self.deployed_bytecode.as_str(),
};
serde_json::json!({
"bytecode": self.creation_bytecode,
"bytecodeType": BytecodeType::CreationInput.as_str_name(),
"bytecode": bytecode,
"bytecodeType": bytecode_type.as_str_name(),
"compilerVersion": self.compiler_version,
"evmVersion": self.evm_version,
"optimization_runs": self.optimization_runs,
Expand Down Expand Up @@ -223,10 +229,16 @@ impl TestCase for StandardJson {
"/api/v2/verifier/solidity/sources:verify-standard-json"
}

fn to_request(&self) -> serde_json::Value {
fn to_request(&self, bytecode_type: BytecodeType) -> serde_json::Value {
let bytecode = match bytecode_type {
BytecodeType::Unspecified | BytecodeType::CreationInput => {
self.creation_bytecode.as_str()
}
BytecodeType::DeployedBytecode => self.deployed_bytecode.as_str(),
};
serde_json::json!({
"bytecode": self.creation_bytecode,
"bytecodeType": BytecodeType::CreationInput.as_str_name(),
"bytecode": bytecode,
"bytecodeType": bytecode_type.as_str_name(),
"compilerVersion": self.compiler_version,
"input": self.input,
"metadata": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"_comment": "Code with immutable references",
"deployed_bytecode": "0x608060405234801561001057600080fd5b50600436106100415760003560e01c80636057361d146100465780638381f58a14610062578063d555654414610080575b600080fd5b610060600480360381019061005b9190610138565b61009e565b005b61006a6100d3565b6040516100779190610174565b60405180910390f35b6100886100d9565b6040516100959190610174565b60405180910390f35b7f0000000000000000000000000000000000000000000000000000000000000001816100ca91906101be565b60008190555050565b60005481565b7f000000000000000000000000000000000000000000000000000000000000000181565b600080fd5b6000819050919050565b61011581610102565b811461012057600080fd5b50565b6000813590506101328161010c565b92915050565b60006020828403121561014e5761014d6100fd565b5b600061015c84828501610123565b91505092915050565b61016e81610102565b82525050565b60006020820190506101896000830184610165565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60006101c982610102565b91506101d483610102565b92508282019050808211156101ec576101eb61018f565b5b9291505056fea26469706673582212207215155b6a310856ceddcbd9ba15e34e5672fef1e055905f305ad37d295d755c64736f6c63430008120033",
"creation_bytecode": "0x60a0604052600160809081525034801561001857600080fd5b506080516102286100396000396000818160a0015260db01526102286000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c80636057361d146100465780638381f58a14610062578063d555654414610080575b600080fd5b610060600480360381019061005b9190610138565b61009e565b005b61006a6100d3565b6040516100779190610174565b60405180910390f35b6100886100d9565b6040516100959190610174565b60405180910390f35b7f0000000000000000000000000000000000000000000000000000000000000000816100ca91906101be565b60008190555050565b60005481565b7f000000000000000000000000000000000000000000000000000000000000000081565b600080fd5b6000819050919050565b61011581610102565b811461012057600080fd5b50565b6000813590506101328161010c565b92915050565b60006020828403121561014e5761014d6100fd565b5b600061015c84828501610123565b91505092915050565b61016e81610102565b82525050565b60006020820190506101896000830184610165565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60006101c982610102565b91506101d483610102565b92508282019050808211156101ec576101eb61018f565b5b9291505056fea26469706673582212207215155b6a310856ceddcbd9ba15e34e5672fef1e055905f305ad37d295d755c64736f6c63430008120033",
"compiler_version": "v0.8.18+commit.87f61d96",
"contract_name": "Storage",
"source_code": "// SPDX-License-Identifier: GPL-3.0\n\npragma solidity >=0.7.0 <0.9.0;\n\n/**\n * @title Storage\n * @dev Store & retrieve value in a variable\n */\ncontract Storage {\n uint256 public number;\n\n uint256 public immutable offset = 1;\n\n /**\n * @dev Store value in variable\n * @param num value to store\n */\n function store(uint256 num) public {\n number = num + offset;\n }\n}",
"expected_compiler_artifacts": {
"abi": [{"inputs":[],"name":"number","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"offset","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"num","type":"uint256"}],"name":"store","outputs":[],"stateMutability":"nonpayable","type":"function"}],
"userdoc": {"kind":"user","methods":{},"version":1},
"devdoc": {"details":"Store & retrieve value in a variable","kind":"dev","methods":{"store(uint256)":{"details":"Store value in variable","params":{"num":"value to store"}}},"title":"Storage","version":1},
"storageLayout": {"storage":[{"astId":4,"contract":"source.sol:Storage","label":"number","offset":0,"slot":"0","type":"t_uint256"}],"types":{"t_uint256":{"encoding":"inplace","label":"uint256","numberOfBytes":"32"}}},
"sources": {"source.sol": {"id": 0} }
},
"expected_creation_input_artifacts": {
"sourceMap": "141:253:0:-:0;;;226:1;192:35;;;;;141:253;;;;;;;;;;;;;;;;;;;;;;;;;;;",
"linkReferences": {},
"cborAuxdata": {
"0": {
"offset": 556,
"value": "0xa2646970667358221220513ec1504761ca45b2bc19e320ccc677d01d7b728a1c1422e9c2155f2e8128ad64736f6c63430008120033"
}
}
},
"expected_deployed_bytecode_artifacts": {
"sourceMap": "141:253:0:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;319:73;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;164:21;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;192:35;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;319:73;379:6;373:3;:12;;;;:::i;:::-;364:6;:21;;;;319:73;:::o;164:21::-;;;;:::o;192:35::-;;;:::o;88:117:1:-;197:1;194;187:12;334:77;371:7;400:5;389:16;;334:77;;;:::o;417:122::-;490:24;508:5;490:24;:::i;:::-;483:5;480:35;470:63;;529:1;526;519:12;470:63;417:122;:::o;545:139::-;591:5;629:6;616:20;607:29;;645:33;672:5;645:33;:::i;:::-;545:139;;;;:::o;690:329::-;749:6;798:2;786:9;777:7;773:23;769:32;766:119;;;804:79;;:::i;:::-;766:119;924:1;949:53;994:7;985:6;974:9;970:22;949:53;:::i;:::-;939:63;;895:117;690:329;;;;:::o;1025:118::-;1112:24;1130:5;1112:24;:::i;:::-;1107:3;1100:37;1025:118;;:::o;1149:222::-;1242:4;1280:2;1269:9;1265:18;1257:26;;1293:71;1361:1;1350:9;1346:17;1337:6;1293:71;:::i;:::-;1149:222;;;;:::o;1377:180::-;1425:77;1422:1;1415:88;1522:4;1519:1;1512:15;1546:4;1543:1;1536:15;1563:191;1603:3;1622:20;1640:1;1622:20;:::i;:::-;1617:25;;1656:20;1674:1;1656:20;:::i;:::-;1651:25;;1699:1;1696;1692:9;1685:16;;1720:3;1717:1;1714:10;1711:36;;;1727:18;;:::i;:::-;1711:36;1563:191;;;;:::o",
"linkReferences": {},
"immutableReferences": {"7":[{"length":32,"start":160},{"length":32,"start":219}]},
"cborAuxdata": {
"0": {
"offset": 499,
"value": "0xa2646970667358221220513ec1504761ca45b2bc19e320ccc677d01d7b728a1c1422e9c2155f2e8128ad64736f6c63430008120033"
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::{
use bytes::Bytes;
use ethabi::{Constructor, Token};
use ethers_solc::{
artifacts::{self, Contract},
artifacts::{self, Contract, Offsets},
Artifact, CompilerOutput,
};
use mismatch::Mismatch;
Expand Down Expand Up @@ -208,12 +208,18 @@ impl<T: Source> Verifier<T> {
VerificationErrorKind::InternalError(format!("modified contract: {err}"))
})?;

let immutable_references = contract
.get_deployed_bytecode()
.expect("deployed bytecode object exists as 'deployed_bytecode' has been retrieved successfully before")
.immutable_references
.clone();
let local_bytecode = LocalBytecode::new(
(creation_tx_input, deployed_bytecode),
(creation_tx_input_modified, deployed_bytecode_modified),
immutable_references,
)?;

let match_type = Self::compare_creation_tx_inputs(&self.remote_bytecode, &local_bytecode)?;
let match_type = Self::compare_bytecodes(&self.remote_bytecode, &local_bytecode)?;

let abi = contract.get_abi().map(|abi| abi.into_owned());

Expand All @@ -231,41 +237,56 @@ impl<T: Source> Verifier<T> {
})
}

fn compare_creation_tx_inputs(
fn compare_bytecodes(
remote_bytecode: &Bytecode<T>,
local_bytecode: &LocalBytecode<T>,
) -> Result<MatchType, VerificationErrorKind> {
let remote_creation_tx_input = remote_bytecode.bytecode();
let local_creation_tx_input = local_bytecode.bytecode();
let remote_code = remote_bytecode.bytecode();
let local_code = local_bytecode.bytecode();

if remote_creation_tx_input.starts_with(local_creation_tx_input) {
if remote_code.len() < local_code.len() {
return Err(VerificationErrorKind::BytecodeLengthMismatch {
part: Mismatch::new(local_code.len(), remote_code.len()),
raw: Mismatch::new(local_code.clone().into(), remote_code.clone().into()),
});
}

let processed_remote_code = if T::has_immutable_references() {
Self::nullify_immutable_references(remote_code, &local_bytecode.immutable_references)
} else {
remote_code.clone()
};

if processed_remote_code.starts_with(local_code) {
// If local compilation bytecode is prefix of remote one,
// metadata parts are the same and we do not need to compare bytecode parts.
return Ok(MatchType::Full);
}

if remote_creation_tx_input.len() < local_creation_tx_input.len() {
return Err(VerificationErrorKind::BytecodeLengthMismatch {
part: Mismatch::new(
local_creation_tx_input.len(),
remote_creation_tx_input.len(),
),
raw: Mismatch::new(
local_creation_tx_input.clone().into(),
remote_creation_tx_input.clone().into(),
),
});
}

Self::compare_bytecode_parts(
remote_creation_tx_input,
local_creation_tx_input,
&processed_remote_code,
local_code,
local_bytecode.bytecode_parts(),
)?;

Ok(MatchType::Partial)
}

fn nullify_immutable_references(
deployed_code: &Bytes,
immutable_references: &BTreeMap<String, Vec<Offsets>>,
) -> Bytes {
let mut updated_deployed_code = deployed_code.to_vec();
for offsets in immutable_references.values() {
for offset in offsets {
let range = offset.start as usize..offset.start as usize + offset.length as usize;
updated_deployed_code[range].fill(0);
}
}

Bytes::from(updated_deployed_code)
}

/// Performs an actual comparison of locally compiled bytecode
/// with remote bytecode provided for verification.
///
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
use super::errors::{BytecodeInitError, VerificationErrorKind};
use bytes::{Buf, Bytes};
use ethers_solc::{artifacts::Contract, Artifact};
use ethers_solc::{
artifacts::{Contract, Offsets},
Artifact,
};
use mismatch::Mismatch;
use solidity_metadata::MetadataHash;
use std::marker::PhantomData;
use std::{collections::BTreeMap, marker::PhantomData};

/// Types that can be used as Bytecode source indicator
pub trait Source {
Expand All @@ -14,6 +17,8 @@ pub trait Source {
/// (used when comparing unused bytes with constructor ABI)
fn has_constructor_args() -> bool;

fn has_immutable_references() -> bool;

fn source_kind() -> SourceKind;
}

Expand Down Expand Up @@ -51,6 +56,10 @@ impl Source for DeployedBytecode {
false
}

fn has_immutable_references() -> bool {
true
}

fn source_kind() -> SourceKind {
SourceKind::DeployedBytecode
}
Expand Down Expand Up @@ -84,6 +93,10 @@ impl Source for CreationTxInput {
true
}

fn has_immutable_references() -> bool {
false
}

fn source_kind() -> SourceKind {
SourceKind::CreationTxInput
}
Expand Down Expand Up @@ -149,6 +162,8 @@ pub struct LocalBytecode<T> {
pub creation_tx_input_parts: Vec<BytecodePart>,
pub deployed_bytecode_parts: Vec<BytecodePart>,

pub immutable_references: BTreeMap<String, Vec<Offsets>>,

source: PhantomData<T>,
}

Expand All @@ -168,6 +183,7 @@ impl<T> LocalBytecode<T> {
Bytecode<CreationTxInput>,
Bytecode<DeployedBytecode>,
),
immutable_references: BTreeMap<String, Vec<Offsets>>,
) -> Result<Self, VerificationErrorKind> {
let creation_tx_input_parts = Self::split(
creation_tx_input.bytecode(),
Expand All @@ -183,6 +199,7 @@ impl<T> LocalBytecode<T> {
deployed_bytecode,
creation_tx_input_parts,
deployed_bytecode_parts,
immutable_references,

source: Default::default(),
})
Expand Down Expand Up @@ -379,6 +396,7 @@ mod local_bytecode_initialization_tests {
LocalBytecode::new(
(creation_tx_input.clone(), deployed_bytecode.clone()),
(creation_tx_input_modified, deployed_bytecode_modified),
Default::default(),
)
.map(|local_bytecode| Bytecodes {
local_bytecode,
Expand Down

0 comments on commit 5c8b63c

Please sign in to comment.