-
Notifications
You must be signed in to change notification settings - Fork 6
Support for Factory Contract Dependency in forge create #162
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
base: master
Are you sure you want to change the base?
Changes from 13 commits
f96402d
c01f6b2
b6eb1e3
c3be5c8
f25cf41
869de53
56bd04b
850206f
0efe3a4
4543076
b6328da
8b65934
cccbe25
bb249c9
0552ce5
64fa04d
3411a24
af8036c
053f1e4
145f6da
ff4e4d1
90d9554
1c8fe0a
4c53953
f06ccf9
05fbbcc
2dc0615
c36e11c
c58e525
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,11 +10,13 @@ use alloy_serde::WithOtherFields; | |
| use alloy_signer::Signer; | ||
| use alloy_transport::TransportError; | ||
| use clap::{Parser, ValueHint}; | ||
| use eyre::{Context, Result}; | ||
|
|
||
| use codec::{Compact, Encode}; | ||
| use eyre::{Context, OptionExt, Result}; | ||
| use forge_verify::{RetryArgs, VerifierArgs, VerifyArgs}; | ||
| use foundry_cli::{ | ||
| opts::{BuildOpts, EthereumOpts, EtherscanOpts, TransactionOpts}, | ||
| utils::{self, read_constructor_args_file, remove_contract, LoadConfig}, | ||
| utils::{self, get_child_contracts, read_constructor_args_file, remove_contract, LoadConfig}, | ||
| }; | ||
| use foundry_common::{ | ||
| compile::{self}, | ||
|
|
@@ -34,7 +36,7 @@ use foundry_config::{ | |
| }; | ||
| use serde_json::json; | ||
| use std::{borrow::Borrow, marker::PhantomData, path::PathBuf, sync::Arc, time::Duration}; | ||
|
|
||
| use tracing::debug; | ||
| merge_impl_figment_convert!(CreateArgs, build, eth); | ||
|
|
||
| /// CLI arguments for `forge create`. | ||
|
|
@@ -100,11 +102,54 @@ pub struct CreateArgs { | |
| retry: RetryArgs, | ||
| } | ||
|
|
||
| /// Uploads a child contract to a blockchain network using the Alloy framework. | ||
| async fn upload_child_contract_alloy( | ||
| rpc_url: &str, | ||
| private_key: String, | ||
| encoded_bytes: String, | ||
| ) -> Result<String> { | ||
| use alloy_primitives::{Address, U256}; | ||
| use alloy_provider::Provider; | ||
| use alloy_rpc_types::TransactionRequest; | ||
| use alloy_serde::WithOtherFields; | ||
| use alloy_signer_local::PrivateKeySigner; | ||
| use foundry_common::provider::ProviderBuilder; | ||
| use std::str::FromStr; | ||
|
|
||
| // This wallet will be used to sign the deployment transaction | ||
| let wallet = PrivateKeySigner::from_str(&private_key)?; | ||
|
|
||
| // This establishes the connection to the target network and prepares for transaction signing | ||
| let provider = ProviderBuilder::new(rpc_url).build_with_wallet(EthereumWallet::new(wallet))?; | ||
|
|
||
| // Use the special "magic address" for child contract deployment | ||
| let magic_address: Address = "0x6d6f646c70792f70616464720000000000000000".parse()?; | ||
|
|
||
| // Convert the hex-encoded bytecode string to actual bytes for the transaction input | ||
| // Remove "0x" prefix if present before decoding | ||
| let input_bytes = hex::decode(encoded_bytes.trim_start_matches("0x"))?; | ||
|
|
||
| // Construct the transaction request | ||
| let tx = TransactionRequest::default() | ||
| .to(magic_address) | ||
| .input(input_bytes.into()) | ||
| .value(U256::from(0u64)); | ||
|
|
||
| // Wrap the transaction in WithOtherFields for proper serialization | ||
| let wrapped_tx = WithOtherFields::new(tx); | ||
|
|
||
| // Send the transaction to the network and wait for it to be included in a block | ||
| let pending_tx = provider.send_transaction(wrapped_tx).await?; | ||
| let receipt = pending_tx.get_receipt().await?; | ||
|
|
||
| // Return the transaction hash as a string for tracking and verification | ||
| Ok(receipt.transaction_hash.to_string()) | ||
| } | ||
|
|
||
| impl CreateArgs { | ||
| /// Executes the command to create a contract | ||
| pub async fn run(mut self) -> Result<()> { | ||
| let mut config = self.load_config()?; | ||
|
|
||
| // Install missing dependencies. | ||
| if install::install_missing_dependencies(&mut config) && config.auto_detect_remappings { | ||
| // need to re-configure here to also catch additional remappings | ||
|
|
@@ -120,7 +165,64 @@ impl CreateArgs { | |
| project.find_contract_path(&self.contract.name)? | ||
| }; | ||
|
|
||
| let output = compile::compile_target(&target_path, &project, shell::is_json())?; | ||
| let output: foundry_compilers::ProjectCompileOutput = | ||
| compile::compile_target(&target_path, &project, shell::is_json())?; | ||
|
|
||
| match get_child_contracts(output.clone(), &self.contract.name) { | ||
|
||
| Ok(child_contracts) => { | ||
| if child_contracts.is_empty() { | ||
| debug!("No child contracts found for '{}'", self.contract.name); | ||
| } else { | ||
| debug!( | ||
| "Found {} child contract(s) for '{}':", | ||
| child_contracts.len(), | ||
| self.contract.name | ||
| ); | ||
| for child in &child_contracts { | ||
| match &child.bytecode { | ||
| BytecodeObject::Bytecode(bytes) => { | ||
| let scaled_encoded_bytes = bytes.encode(); | ||
| let storage_deposit_limit = Compact(10000000000u128); | ||
| let encoded_storage_deposit_limit = storage_deposit_limit.encode(); | ||
| let combined_hex = "0x3c04".to_string() + | ||
| &hex::encode(&scaled_encoded_bytes) + | ||
| &hex::encode(&encoded_storage_deposit_limit); | ||
|
|
||
| // Pass RPC URL and private key to upload_child_contract | ||
| let rpc_url = config.get_rpc_url_or_localhost_http()?; | ||
| let private_key = self | ||
| .eth | ||
| .wallet | ||
| .raw | ||
| .private_key | ||
| .clone() | ||
| .ok_or_eyre("Private key not provided")?; | ||
|
|
||
| let tx_hash = upload_child_contract_alloy( | ||
|
||
| rpc_url.as_ref(), | ||
| private_key, | ||
| combined_hex, | ||
| ) | ||
| .await?; | ||
| debug!( | ||
| "Transaction sent! Hash: {:?} for child contract {:?}", | ||
| tx_hash, self.contract.name | ||
| ); | ||
| } | ||
| BytecodeObject::Unlinked(_) => { | ||
| debug!( | ||
| "Bytecode: Available (unlinked) for child contract) {:?}", | ||
| self.contract.name | ||
| ); | ||
| } | ||
| }; | ||
| } | ||
| } | ||
| } | ||
| Err(e) => { | ||
| debug!("Error getting child contracts: {}", e); | ||
| } | ||
| } | ||
|
|
||
| let (abi, bin, id) = remove_contract(output, &target_path, &self.contract.name)?; | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In
ResolcContractwe havepub factory_dependencies: Option<BTreeMap<String, String>>,Is there any reason why you do not use it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no ResolcContract in workspace.
Also, Since I am already getting child contracts in pre-existing output object from compiler in
"let output: foundry_compilers::ProjectCompileOutput =
compile::compile_target(&target_path, &project, shell::is_json())?;"
I used it in our usecase
I have just looping through the artifacts from the compiled output to get the child contracts. Attaching below sample artifact array that we get from output, it has both child and parent contract details, get_child_contracts just extracts the child contracts from existing output -
https://gist.github.com/soul022/8f8dc0ef4c0811b05d382d7668f07e40
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How will foundry behave when you compile the following code:
Will foundry upload the Assert library as a child contract?
What about the case where deploy time linking is being used - will your solution still work?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this is internal library, the compiler simply embeds its code directly into your contract. There’s no separate library contract on-chain and nothing extra to deploy or link at runtime.
My code to get child contracts was treating it as a contract and uploading it, I have modified code in get_child_contracts and added a check to filter the same, so it does not gets uploaded
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for deploy time linking i.e external library, it should work as is, As per foundry book -we need to deploy external library first and use linker options (https://paritytech.github.io/foundry-book-polkadot/reference/forge/forge-create.html#linker-options) to create the contract using that external library.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried testing linker option for forge create for below contract, but it failed. seems like linker option is not working in foundry-polkadot.
contract - https://gist.github.com/soul022/9ef77e6ac0cf10365b8949fd9bc310fa
logs - https://gist.github.com/soul022/eccb3fc929953e207adafcfd62cba1c9
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My point is that the logic you're implementing already exists in the
Revivecompiler. I think we should find a way to makefactory_dependenciesavailable.In the near future, we’ll probably also need to expose
unlinked_dependenciesfor deploy time linking. WDYT?@pkhry do you have any opinion on this? You have been looking into the linking already.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just add a field to
solccontract artifact infoundry-compilers.factory-depsare already present, you just need to forward them.unlinked depsshould probably forwarded the same way as a field onsolc::Contract.something like this below a la how metadata is currently forwarded:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also yeah, not a fan of doing something manually if it's already done by the compiler.