Skip to content

Commit

Permalink
Add basic Yul verification capability (#136)
Browse files Browse the repository at this point in the history
smart-contract-verifier: add basic Yul verification capability(without constructor arguments). 

Breaking: make ABI field in verification response optional, and return 'null' if Yul contract was verified
  • Loading branch information
rimrakhimov authored Oct 11, 2022
1 parent 972e8e1 commit d733312
Show file tree
Hide file tree
Showing 15 changed files with 321 additions and 61 deletions.
3 changes: 2 additions & 1 deletion smart-contract-verifier-http/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,8 @@ If verification succeeds, the service returns 200 with a success status:
// (optional) automatically extracted from creation transaction input
// constructor arguments used for deploying verified contract
"constructor_arguments": "0xcafecafecafe",
// (https://docs.soliditylang.org/en/latest/abi-spec.html?highlight=abi#json)
// (optional) contract abi (https://docs.soliditylang.org/en/latest/abi-spec.html?highlight=abi#json);
// is `null` for Yul contracts
"abi": "[ { ... } ]"
},
// Status of 0 indicates successful verification
Expand Down
12 changes: 7 additions & 5 deletions smart-contract-verifier-http/src/verification_response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub struct VerificationResult {
pub optimization: Option<bool>,
pub optimization_runs: Option<usize>,
pub contract_libraries: BTreeMap<String, String>,
pub abi: String,
pub abi: Option<String>,
pub sources: BTreeMap<String, String>,
}

Expand All @@ -46,8 +46,10 @@ impl From<VerificationSuccess> for VerificationResult {
.into_iter()
.flat_map(|(_path, libs)| libs)
.collect(),
abi: serde_json::to_string(&verification_success.abi)
.expect("Is result of local compilation and, thus, should be always valid"),
abi: verification_success.abi.as_ref().map(|abi| {
serde_json::to_string(abi)
.expect("Is result of local compilation and, thus, should be always valid")
}),
sources: compiler_input
.sources
.into_iter()
Expand All @@ -70,7 +72,7 @@ impl From<SourcifySuccess> for VerificationResult {
optimization: sourcify_success.optimization,
optimization_runs: sourcify_success.optimization_runs,
contract_libraries: sourcify_success.contract_libraries,
abi: sourcify_success.abi,
abi: Some(sourcify_success.abi),
sources: sourcify_success.sources,
}
}
Expand Down Expand Up @@ -124,7 +126,7 @@ mod tests {
"some_library".into(),
"some_address".into(),
)]),
abi: "abi".to_string(),
abi: Some("abi".to_string()),
sources: serde_json::from_str(
r#"{
"source.sol": "content"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"language": "Yul",
"sources": {
"MainCombined.sol": {
"content": "object \"Proxy\" {\n\t// deployment code\n\tcode {\n\t\tlet size := datasize(\"runtime\")\n\t\tdatacopy(0, dataoffset(\"runtime\"), size)\n\t\treturn(0, size)\n\t}\n\tobject \"runtime\" {\n\t\t// deployed code\n\t\tcode {\n\t\t\tcalldatacopy(0, 32, sub(calldatasize(), 32))\n\t\t\tlet result := create2(callvalue(), 0, sub(calldatasize(), 32), calldataload(0))\n\t\t\tif iszero(result) { revert(0, 0) }\n\t\t\tmstore(0, result)\n\t\t\treturn(12, 20)\n\t\t}\n\t}\n}"
}
},
"settings": {
"optimizer": {
"enabled": false
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0x336000556104a36100146000396104a36000f3fe6100093415610495565b61001161021d565b6370a082318114610063576318160ddd81146100825763a9059cbb8114610097576323b872dd81146100c05763095ea7b381146100f35763dd62ed3e811461011c576340c10f19811461014557600080fd5b61007d6100786100736000610246565b6103a6565b61029d565b61016a565b61009261008d610379565b61029d565b61016a565b6100b36100a4600161027a565b6100ae6000610246565b6101a3565b6100bb6102a7565b61016a565b6100e66100cd600261027a565b6100d76001610246565b6100e16000610246565b6101d5565b6100ee6102a7565b61016a565b61010f610100600161027a565b61010a6000610246565b6101b2565b6101176102a7565b61016a565b61014061013b61012c6001610246565b6101366000610246565b6103fc565b61029d565b61016a565b610161610152600161027a565b61015c6000610246565b610170565b6101696102a7565b5b506104a2565b61018061017b610478565b610495565b61018982610389565b61019382826103b9565b61019f828260006102b3565b5050565b6101ae8282336101f0565b5050565b6101bb81610489565b6101c6828233610411565b6101d18282336102e6565b5050565b6101e0833383610422565b6101eb8383836101f0565b505050565b6101f982610489565b61020383826103d4565b61020d83836103b9565b6102188383836102b3565b505050565b60007c010000000000000000000000000000000000000000000000000000000060003504905090565b60006102518261027a565b905073ffffffffffffffffffffffffffffffffffffffff1981161561027557600080fd5b919050565b6000602082026004016020810136101561029357600080fd5b8035915050919050565b8060005260206000f35b6102b1600161029d565b565b7fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef6102e084848484610319565b50505050565b7f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92561031384848484610319565b50505050565b8360005282828260206000a350505050565b600090565b60006001905090565b600081611000019050919050565b600061035282610339565b905080600052826020526040600020905092915050565b600061037361032b565b54905090565b6000610383610330565b54905090565b61039a81610395610379565b61045a565b6103a2610330565b5550565b60006103b182610339565b549050919050565b6103c281610339565b6103cd83825461045a565b8155505050565b6103dd81610339565b80546103f16103ec828661044c565b610495565b838103825550505050565b60006104088383610347565b54905092915050565b8261041c8383610347565b55505050565b61042c8282610347565b805461044061043b828761044c565b610495565b84810382555050505050565b600082821115905092915050565b60008282019050828110828210171561047257600080fd5b92915050565b600033610483610369565b14905090565b61049281610495565b50565b8061049f57600080fd5b50565b
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0x6100093415610495565b61001161021d565b6370a082318114610063576318160ddd81146100825763a9059cbb8114610097576323b872dd81146100c05763095ea7b381146100f35763dd62ed3e811461011c576340c10f19811461014557600080fd5b61007d6100786100736000610246565b6103a6565b61029d565b61016a565b61009261008d610379565b61029d565b61016a565b6100b36100a4600161027a565b6100ae6000610246565b6101a3565b6100bb6102a7565b61016a565b6100e66100cd600261027a565b6100d76001610246565b6100e16000610246565b6101d5565b6100ee6102a7565b61016a565b61010f610100600161027a565b61010a6000610246565b6101b2565b6101176102a7565b61016a565b61014061013b61012c6001610246565b6101366000610246565b6103fc565b61029d565b61016a565b610161610152600161027a565b61015c6000610246565b610170565b6101696102a7565b5b506104a2565b61018061017b610478565b610495565b61018982610389565b61019382826103b9565b61019f828260006102b3565b5050565b6101ae8282336101f0565b5050565b6101bb81610489565b6101c6828233610411565b6101d18282336102e6565b5050565b6101e0833383610422565b6101eb8383836101f0565b505050565b6101f982610489565b61020383826103d4565b61020d83836103b9565b6102188383836102b3565b505050565b60007c010000000000000000000000000000000000000000000000000000000060003504905090565b60006102518261027a565b905073ffffffffffffffffffffffffffffffffffffffff1981161561027557600080fd5b919050565b6000602082026004016020810136101561029357600080fd5b8035915050919050565b8060005260206000f35b6102b1600161029d565b565b7fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef6102e084848484610319565b50505050565b7f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92561031384848484610319565b50505050565b8360005282828260206000a350505050565b600090565b60006001905090565b600081611000019050919050565b600061035282610339565b905080600052826020526040600020905092915050565b600061037361032b565b54905090565b6000610383610330565b54905090565b61039a81610395610379565b61045a565b6103a2610330565b5550565b60006103b182610339565b549050919050565b6103c281610339565b6103cd83825461045a565b8155505050565b6103dd81610339565b80546103f16103ec828661044c565b610495565b838103825550505050565b60006104088383610347565b54905092915050565b8261041c8383610347565b55505050565b61042c8282610347565b805461044061043b828761044c565b610495565b84810382555050505050565b600082821115905092915050565b60008282019050828110828210171561047257600080fd5b92915050565b600033610483610369565b14905090565b61049281610495565b50565b8061049f57600080fd5b50565b
186 changes: 186 additions & 0 deletions smart-contract-verifier-http/tests/contracts/yul_erc20/source.yul
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
object "Token" {
code {
// Store the creator in slot zero.
sstore(0, caller())

// Deploy the contract
datacopy(0, dataoffset("runtime"), datasize("runtime"))
return(0, datasize("runtime"))
}
object "runtime" {
code {
// Protection against sending Ether
require(iszero(callvalue()))

// Dispatcher
switch selector()
case 0x70a08231 /* "balanceOf(address)" */ {
returnUint(balanceOf(decodeAsAddress(0)))
}
case 0x18160ddd /* "totalSupply()" */ {
returnUint(totalSupply())
}
case 0xa9059cbb /* "transfer(address,uint256)" */ {
transfer(decodeAsAddress(0), decodeAsUint(1))
returnTrue()
}
case 0x23b872dd /* "transferFrom(address,address,uint256)" */ {
transferFrom(decodeAsAddress(0), decodeAsAddress(1), decodeAsUint(2))
returnTrue()
}
case 0x095ea7b3 /* "approve(address,uint256)" */ {
approve(decodeAsAddress(0), decodeAsUint(1))
returnTrue()
}
case 0xdd62ed3e /* "allowance(address,address)" */ {
returnUint(allowance(decodeAsAddress(0), decodeAsAddress(1)))
}
case 0x40c10f19 /* "mint(address,uint256)" */ {
mint(decodeAsAddress(0), decodeAsUint(1))
returnTrue()
}
default {
revert(0, 0)
}

function mint(account, amount) {
require(calledByOwner())

mintTokens(amount)
addToBalance(account, amount)
emitTransfer(0, account, amount)
}
function transfer(to, amount) {
executeTransfer(caller(), to, amount)
}
function approve(spender, amount) {
revertIfZeroAddress(spender)
setAllowance(caller(), spender, amount)
emitApproval(caller(), spender, amount)
}
function transferFrom(from, to, amount) {
decreaseAllowanceBy(from, caller(), amount)
executeTransfer(from, to, amount)
}

function executeTransfer(from, to, amount) {
revertIfZeroAddress(to)
deductFromBalance(from, amount)
addToBalance(to, amount)
emitTransfer(from, to, amount)
}


/* ---------- calldata decoding functions ----------- */
function selector() -> s {
s := div(calldataload(0), 0x100000000000000000000000000000000000000000000000000000000)
}

function decodeAsAddress(offset) -> v {
v := decodeAsUint(offset)
if iszero(iszero(and(v, not(0xffffffffffffffffffffffffffffffffffffffff)))) {
revert(0, 0)
}
}
function decodeAsUint(offset) -> v {
let pos := add(4, mul(offset, 0x20))
if lt(calldatasize(), add(pos, 0x20)) {
revert(0, 0)
}
v := calldataload(pos)
}
/* ---------- calldata encoding functions ---------- */
function returnUint(v) {
mstore(0, v)
return(0, 0x20)
}
function returnTrue() {
returnUint(1)
}

/* -------- events ---------- */
function emitTransfer(from, to, amount) {
let signatureHash := 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
emitEvent(signatureHash, from, to, amount)
}
function emitApproval(from, spender, amount) {
let signatureHash := 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925
emitEvent(signatureHash, from, spender, amount)
}
function emitEvent(signatureHash, indexed1, indexed2, nonIndexed) {
mstore(0, nonIndexed)
log3(0, 0x20, signatureHash, indexed1, indexed2)
}

/* -------- storage layout ---------- */
function ownerPos() -> p { p := 0 }
function totalSupplyPos() -> p { p := 1 }
function accountToStorageOffset(account) -> offset {
offset := add(0x1000, account)
}
function allowanceStorageOffset(account, spender) -> offset {
offset := accountToStorageOffset(account)
mstore(0, offset)
mstore(0x20, spender)
offset := keccak256(0, 0x40)
}

/* -------- storage access ---------- */
function owner() -> o {
o := sload(ownerPos())
}
function totalSupply() -> supply {
supply := sload(totalSupplyPos())
}
function mintTokens(amount) {
sstore(totalSupplyPos(), safeAdd(totalSupply(), amount))
}
function balanceOf(account) -> bal {
bal := sload(accountToStorageOffset(account))
}
function addToBalance(account, amount) {
let offset := accountToStorageOffset(account)
sstore(offset, safeAdd(sload(offset), amount))
}
function deductFromBalance(account, amount) {
let offset := accountToStorageOffset(account)
let bal := sload(offset)
require(lte(amount, bal))
sstore(offset, sub(bal, amount))
}
function allowance(account, spender) -> amount {
amount := sload(allowanceStorageOffset(account, spender))
}
function setAllowance(account, spender, amount) {
sstore(allowanceStorageOffset(account, spender), amount)
}
function decreaseAllowanceBy(account, spender, amount) {
let offset := allowanceStorageOffset(account, spender)
let currentAllowance := sload(offset)
require(lte(amount, currentAllowance))
sstore(offset, sub(currentAllowance, amount))
}

/* ---------- utility functions ---------- */
function lte(a, b) -> r {
r := iszero(gt(a, b))
}
function gte(a, b) -> r {
r := iszero(lt(a, b))
}
function safeAdd(a, b) -> r {
r := add(a, b)
if or(lt(r, a), lt(r, b)) { revert(0, 0) }
}
function calledByOwner() -> cbo {
cbo := eq(owner(), caller())
}
function revertIfZeroAddress(addr) {
require(addr)
}
function require(condition) {
if iszero(condition) { revert(0, 0) }
}
}
}
}
38 changes: 25 additions & 13 deletions smart-contract-verifier-http/tests/solidity_multiple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,17 +143,22 @@ async fn test_success(dir: &'static str, mut input: TestInput) {
);

let verification_result = verification_response.result.expect("Checked above");

let abi: Result<ethabi::Contract, _> = serde_json::from_str(&verification_result.abi);
let abi: Option<Result<ethabi::Contract, _>> = verification_result
.abi
.as_ref()
.map(|abi| serde_json::from_str(abi));
assert_eq!(
verification_result.contract_name, input.contract_name,
"Invalid contract name"
);
assert!(
abi.is_ok(),
"Abi deserialization failed: {}",
abi.unwrap_err()
);
if !input.is_yul {
assert!(abi.is_some(), "Solidity contracts must have abi");
assert!(
abi.as_ref().unwrap().is_ok(),
"Abi deserialization failed: {}",
abi.unwrap().as_ref().unwrap_err()
);
}
assert_eq!(
verification_result.constructor_arguments, expected_constructor_argument,
"Invalid constructor args"
Expand Down Expand Up @@ -321,12 +326,19 @@ mod success_tests {
test_success(contract_dir, test_input).await;
}

// #[actix_rt::test]
// async fn yul_contract() {
// let contract_dir = "yul";
// let test_input = TestInput::new("ContractFromFactory", "v0.8.7+commit.e28d00a7").is_yul();
// test_success(contract_dir, test_input).await;
// }
#[actix_rt::test]
async fn yul_contract() {
let contract_dir = "yul";
let test_input = TestInput::new("Proxy", "v0.8.7+commit.e28d00a7").set_is_yul();
test_success(contract_dir, test_input).await;
}

#[actix_rt::test]
async fn yul_erc20() {
let contract_dir = "yul_erc20";
let test_input = TestInput::new("Token", "v0.8.7+commit.e28d00a7").set_is_yul();
test_success(contract_dir, test_input).await;
}

#[actix_rt::test]
async fn solidity_0_4_10() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ impl TestInput {
self
}

pub fn is_yul(mut self) -> Self {
pub fn set_is_yul(mut self) -> Self {
self.is_yul = true;
self
}
Expand Down
25 changes: 19 additions & 6 deletions smart-contract-verifier-http/tests/standard_json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,16 +103,22 @@ async fn test_success(dir: &'static str, mut input: TestInput) {
.result
.expect("Verification result is not Some");

let abi: Result<ethabi::Contract, _> = serde_json::from_str(&verification_result.abi);
let abi: Option<Result<ethabi::Contract, _>> = verification_result
.abi
.as_ref()
.map(|abi| serde_json::from_str(abi));
assert_eq!(
verification_result.contract_name, input.contract_name,
"Invalid contract name"
);
assert!(
abi.is_ok(),
"Abi deserialization failed: {}",
abi.unwrap_err()
);
if !input.is_yul {
assert!(abi.is_some(), "Solidity contracts must have abi");
assert!(
abi.as_ref().unwrap().is_ok(),
"Abi deserialization failed: {}",
abi.unwrap().as_ref().unwrap_err()
);
}
assert_eq!(
verification_result.constructor_arguments, expected_constructor_argument,
"Invalid constructor args"
Expand Down Expand Up @@ -186,6 +192,13 @@ mod success_tests {
let test_input = TestInput::new("Main", "v0.4.18+commit.9cf6e910");
test_success(contract_dir, test_input).await;
}

#[actix_rt::test]
async fn yul() {
let contract_dir = "yul";
let test_input = TestInput::new("Proxy", "v0.8.7+commit.e28d00a7").set_is_yul();
test_success(contract_dir, test_input).await
}
}

mod regression_tests {
Expand Down
7 changes: 7 additions & 0 deletions smart-contract-verifier-http/tests/standard_json_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub struct TestInput {
pub contract_name: &'static str,
pub compiler_version: &'static str,
pub has_constructor_args: bool,
pub is_yul: bool,

/// If None, the input would be read from the corresponding file
pub standard_input: Option<String>,
Expand All @@ -19,6 +20,7 @@ impl TestInput {
contract_name,
compiler_version,
has_constructor_args: false,
is_yul: false,

standard_input: None,
creation_tx_input: None,
Expand All @@ -31,6 +33,11 @@ impl TestInput {
self
}

pub fn set_is_yul(mut self) -> Self {
self.is_yul = true;
self
}

pub fn with_standard_json_input(mut self, standard_json_input: String) -> Self {
self.standard_input = Some(standard_json_input);
self
Expand Down
Loading

0 comments on commit d733312

Please sign in to comment.