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: create verifiable Standard JSON for projects with external files #36

Merged
merged 5 commits into from
Dec 28, 2023

Conversation

tash-2s
Copy link
Contributor

@tash-2s tash-2s commented Dec 26, 2023

Currently, Foundry projects containing Solidity files outside the project root directory face contract verification failures on block explorers. This issue occurs from the Standard JSON including unusable source paths for external files, represented as full absolute paths in their host file systems.

This PR addresses the issue by improving the path conversion process. For files not located under the project root directory, relative parent directory paths (..) are used, enabling the compiler to find the files within the json.

Steps to reproduce the issue are detailed in the linked issue above. Additionally, a test case representing that scenario has been added.

With this change, the json created in the reproduction scenario will appear as follows:

{
  "language": "Solidity",
  "sources": {
    "src/Counter.sol": {
      "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.13;\n\nimport \"@remapped/Parent.sol\";\n\ncontract Counter {\n    uint256 public number;\n\n    function setNumber(uint256 newNumber) public {\n        number = newNumber;\n    }\n\n    function increment() public {\n        number++;\n    }\n}\n"
    },
    "../remapped/Parent.sol": {
      "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.13;\nimport \"./Child.sol\";\n"
    },
    "../remapped/Child.sol": {
      "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.13;\n"
    }
  },
  "settings": {
    "remappings": [
      "@remapped/=../remapped/",
      "ds-test/=lib/forge-std/lib/ds-test/src/",
      "forge-std/=lib/forge-std/src/"
    ],
    "optimizer": {
      "enabled": true,
      "runs": 200
    },
    "metadata": {
      "useLiteralContent": false,
      "bytecodeHash": "ipfs",
      "appendCBOR": true
    },
    "outputSelection": {
      "*": {
        "": [
          "ast"
        ],
        "*": [
          "abi",
          "evm.bytecode",
          "evm.deployedBytecode",
          "evm.methodIdentifiers",
          "metadata"
        ]
      }
    },
    "evmVersion": "paris",
    "libraries": {}
  }
}

The source path is now aligned with the project root.

I have successfully deployed and verified the contract on Etherscan using this change.

forge create --rpc-url "wss://ethereum-holesky.publicnode.com" --verify --verifier-url "https://api-holesky.etherscan.io/api" --etherscan-api-key "..." --private-key "..." src/Counter.sol:Counter

https://holesky.etherscan.io/address/0xe08c332706185521fc8bc2b224f67adf814b1880#code

```
$ cargo test --test project --all-features can_create_standard_json_input_with_external_file
    Finished test [unoptimized + debuginfo] target(s) in 0.10s
     Running tests/project.rs (target/debug/deps/project-bc070fde513cf057)

running 1 test
test can_create_standard_json_input_with_external_file ... FAILED

failures:

---- can_create_standard_json_input_with_external_file stdout ----
thread 'can_create_standard_json_input_with_external_file' panicked at tests/project.rs:1649:5:
assertion failed: `(left == right)`

Diff < left / right > :
 [
     "src/Counter.sol",
     "../remapped/Parent.sol",
<    "/private/var/folders/1m/9vppwqks3pz4btz9gnczfmgh0000gn/T/.tmpSWn2KX/remapped/Child.sol",
>    "../remapped/Child.sol",
 ]

note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

failures:
    can_create_standard_json_input_with_external_file

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 52 filtered out; finished in 1.38s

error: test failed, to rerun pass `--test project`
```
Comment on lines +1616 to +1617
let verif_dir = utils::canonicalize(dir.path()).unwrap().join("verif");
let remapped_dir = utils::canonicalize(dir.path()).unwrap().join("remapped");
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After my previous PR is merged, this canonicalization can be removed. It is necessary on this branch because the symlink-resolving canonicalization is still in effect, and the temporary directory on macOS is a symlink.

Copy link
Member

@mattsse mattsse left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

amazing, tysm!

as mentioned in the other PR, I'd like to see a foundry companion PR with patched crate dep as a sanity check before we merge this

@tash-2s
Copy link
Contributor Author

tash-2s commented Dec 27, 2023

Thank you for your review!

I've created a companion PR in foundry with this patch. Also, I have confirmed that the tests in foundry passed successfully.

@tash-2s
Copy link
Contributor Author

tash-2s commented Dec 27, 2023

The workflow test for this PR failed due to a failure in nextest installation. As this issue is unrelated to the changes made, re-running the test should resolve it. https://github.com/foundry-rs/compilers/actions/runs/7331166894/job/19979490471

@tash-2s tash-2s requested a review from mattsse December 27, 2023 19:39
src/lib.rs Outdated
Comment on lines 967 to 980
for (i, (base_component, path_component)) in
base.components().zip(path.components()).enumerate()
{
if base_component == path_component {
new_path.pop_front();
} else {
let mut parent_dirs =
vec![std::path::Component::ParentDir; base.components().count() - i];
parent_dirs.extend(new_path);
new_path = parent_dirs.into();

break;
}
}
Copy link
Member

@mattsse mattsse Dec 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can solve this a bit differently
don't zip the two iterators but create the two iters separately and then iterate over the path
and continue as long as base and path components match, once they diverge collect the remaining into a PathBuf

this way we don't need the vecdeque

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I'm not sure I understand your suggestion.

If we compare the components of base and path at the same positions, it seems similar to using zip.

Could you please elaborate on the type of implementation you have in mind?

Copy link
Member

@mattsse mattsse Dec 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

something like

let mut components = Vec::new(); // perhaps we can even directly push components to a PathBuf?
let mut base_iter = base.components();
let mut path_iter = path.components();

while let Some(path) = path_iter.next() {
  if Some(path) != base_iter.next() {
     components.push(path);
     components.extend(path_iter);
  }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your explanation. I now understand your point. I've made the change and pushed it.

@tash-2s tash-2s requested a review from mattsse December 28, 2023 17:39
Copy link
Member

@mattsse mattsse left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

last pedantic doc nit

src/lib.rs Outdated
Comment on lines 950 to 958
// The returned path from this function usually starts either with a normal component (e.g., `src`)
// or a parent directory component (i.e., `..`), which is based on the base directory. Additionally,
// this function converts the path into a UTF-8 string and replaces all separators with forward
// slashes (`/`).
//
// The rebasing process is as follows:
//
// 1. Remove the leading components from the path that match the base components.
// 2. Prepend `..` components to the path, equal in number to the remaining base components.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we add an example here that illustrates this, otherwise this is a bit hard to understand.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated the comment, adding examples to make it easier to understand.

@tash-2s tash-2s requested a review from mattsse December 28, 2023 19:46
Copy link
Member

@mattsse mattsse left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

awesome job!

I'll merge both and upstream to foundry after a new release

@mattsse mattsse merged commit 247161c into foundry-rs:main Dec 28, 2023
20 checks passed
@tash-2s tash-2s deleted the fix-standard-json-input-external branch December 28, 2023 21:17
@PaulRBerg
Copy link

Amazing work.

Doesn't this PR also address issue foundry-rs/foundry#3507?

@tash-2s
Copy link
Contributor Author

tash-2s commented Dec 29, 2023

@PaulRBerg I haven't looked into it deeply, but it seems like a different matter from this PR and #35, as they don't involve --via-ir.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

etherscan verification failure (when remapping points to external file)
3 participants