diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index 4c5acd1774d60..8b6eeb049c9fd 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -4067,7 +4067,7 @@ { "func": { "id": "deployCode_0", - "description": "Deploys a contract from an artifact file. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.", + "description": "Deploys a contract from an artifact file. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.\nReverts if the target artifact contains unlinked library placeholders.", "declaration": "function deployCode(string calldata artifactPath) external returns (address deployedAddress);", "visibility": "external", "mutability": "", @@ -4087,7 +4087,7 @@ { "func": { "id": "deployCode_1", - "description": "Deploys a contract from an artifact file. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.\nAdditionally accepts abi-encoded constructor arguments.", + "description": "Deploys a contract from an artifact file. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.\nReverts if the target artifact contains unlinked library placeholders.\nAdditionally accepts abi-encoded constructor arguments.", "declaration": "function deployCode(string calldata artifactPath, bytes calldata constructorArgs) external returns (address deployedAddress);", "visibility": "external", "mutability": "", @@ -4107,7 +4107,7 @@ { "func": { "id": "deployCode_2", - "description": "Deploys a contract from an artifact file. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.\nAdditionally accepts `msg.value`.", + "description": "Deploys a contract from an artifact file. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.\nReverts if the target artifact contains unlinked library placeholders.\nAdditionally accepts `msg.value`.", "declaration": "function deployCode(string calldata artifactPath, uint256 value) external returns (address deployedAddress);", "visibility": "external", "mutability": "", @@ -4127,7 +4127,7 @@ { "func": { "id": "deployCode_3", - "description": "Deploys a contract from an artifact file. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.\nAdditionally accepts abi-encoded constructor arguments and `msg.value`.", + "description": "Deploys a contract from an artifact file. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.\nReverts if the target artifact contains unlinked library placeholders.\nAdditionally accepts abi-encoded constructor arguments and `msg.value`.", "declaration": "function deployCode(string calldata artifactPath, bytes calldata constructorArgs, uint256 value) external returns (address deployedAddress);", "visibility": "external", "mutability": "", @@ -4147,7 +4147,7 @@ { "func": { "id": "deployCode_4", - "description": "Deploys a contract from an artifact file, using the CREATE2 salt. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.", + "description": "Deploys a contract from an artifact file, using the CREATE2 salt. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.\nReverts if the target artifact contains unlinked library placeholders.", "declaration": "function deployCode(string calldata artifactPath, bytes32 salt) external returns (address deployedAddress);", "visibility": "external", "mutability": "", @@ -4167,7 +4167,7 @@ { "func": { "id": "deployCode_5", - "description": "Deploys a contract from an artifact file, using the CREATE2 salt. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.\nAdditionally accepts abi-encoded constructor arguments.", + "description": "Deploys a contract from an artifact file, using the CREATE2 salt. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.\nReverts if the target artifact contains unlinked library placeholders.\nAdditionally accepts abi-encoded constructor arguments.", "declaration": "function deployCode(string calldata artifactPath, bytes calldata constructorArgs, bytes32 salt) external returns (address deployedAddress);", "visibility": "external", "mutability": "", @@ -4187,7 +4187,7 @@ { "func": { "id": "deployCode_6", - "description": "Deploys a contract from an artifact file, using the CREATE2 salt. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.\nAdditionally accepts `msg.value`.", + "description": "Deploys a contract from an artifact file, using the CREATE2 salt. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.\nReverts if the target artifact contains unlinked library placeholders.\nAdditionally accepts `msg.value`.", "declaration": "function deployCode(string calldata artifactPath, uint256 value, bytes32 salt) external returns (address deployedAddress);", "visibility": "external", "mutability": "", @@ -4207,7 +4207,7 @@ { "func": { "id": "deployCode_7", - "description": "Deploys a contract from an artifact file, using the CREATE2 salt. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.\nAdditionally accepts abi-encoded constructor arguments and `msg.value`.", + "description": "Deploys a contract from an artifact file, using the CREATE2 salt. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.\nReverts if the target artifact contains unlinked library placeholders.\nAdditionally accepts abi-encoded constructor arguments and `msg.value`.", "declaration": "function deployCode(string calldata artifactPath, bytes calldata constructorArgs, uint256 value, bytes32 salt) external returns (address deployedAddress);", "visibility": "external", "mutability": "", diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index bc049a1fef5d8..87d938db5691e 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -1925,11 +1925,13 @@ interface Vm { /// Deploys a contract from an artifact file. Takes in the relative path to the json file or the path to the /// artifact in the form of :: where and parts are optional. + /// Reverts if the target artifact contains unlinked library placeholders. #[cheatcode(group = Filesystem)] function deployCode(string calldata artifactPath) external returns (address deployedAddress); /// Deploys a contract from an artifact file. Takes in the relative path to the json file or the path to the /// artifact in the form of :: where and parts are optional. + /// Reverts if the target artifact contains unlinked library placeholders. /// /// Additionally accepts abi-encoded constructor arguments. #[cheatcode(group = Filesystem)] @@ -1937,6 +1939,7 @@ interface Vm { /// Deploys a contract from an artifact file. Takes in the relative path to the json file or the path to the /// artifact in the form of :: where and parts are optional. + /// Reverts if the target artifact contains unlinked library placeholders. /// /// Additionally accepts `msg.value`. #[cheatcode(group = Filesystem)] @@ -1944,6 +1947,7 @@ interface Vm { /// Deploys a contract from an artifact file. Takes in the relative path to the json file or the path to the /// artifact in the form of :: where and parts are optional. + /// Reverts if the target artifact contains unlinked library placeholders. /// /// Additionally accepts abi-encoded constructor arguments and `msg.value`. #[cheatcode(group = Filesystem)] @@ -1951,11 +1955,13 @@ interface Vm { /// Deploys a contract from an artifact file, using the CREATE2 salt. Takes in the relative path to the json file or the path to the /// artifact in the form of :: where and parts are optional. + /// Reverts if the target artifact contains unlinked library placeholders. #[cheatcode(group = Filesystem)] function deployCode(string calldata artifactPath, bytes32 salt) external returns (address deployedAddress); /// Deploys a contract from an artifact file, using the CREATE2 salt. Takes in the relative path to the json file or the path to the /// artifact in the form of :: where and parts are optional. + /// Reverts if the target artifact contains unlinked library placeholders. /// /// Additionally accepts abi-encoded constructor arguments. #[cheatcode(group = Filesystem)] @@ -1963,6 +1969,7 @@ interface Vm { /// Deploys a contract from an artifact file, using the CREATE2 salt. Takes in the relative path to the json file or the path to the /// artifact in the form of :: where and parts are optional. + /// Reverts if the target artifact contains unlinked library placeholders. /// /// Additionally accepts `msg.value`. #[cheatcode(group = Filesystem)] @@ -1970,6 +1977,7 @@ interface Vm { /// Deploys a contract from an artifact file, using the CREATE2 salt. Takes in the relative path to the json file or the path to the /// artifact in the form of :: where and parts are optional. + /// Reverts if the target artifact contains unlinked library placeholders. /// /// Additionally accepts abi-encoded constructor arguments and `msg.value`. #[cheatcode(group = Filesystem)] diff --git a/crates/cheatcodes/src/fs.rs b/crates/cheatcodes/src/fs.rs index d316354e6954d..193215e1861f7 100644 --- a/crates/cheatcodes/src/fs.rs +++ b/crates/cheatcodes/src/fs.rs @@ -401,7 +401,7 @@ fn deploy_code( Ok(address.abi_encode()) } -/// Returns the path to the json artifact depending on the input +/// Returns the bytecode from a JSON artifact file. /// /// Can parse following input formats: /// - `path/to/artifact.json` @@ -411,6 +411,10 @@ fn deploy_code( /// - `path/to/contract.sol:0.8.23` /// - `ContractName` /// - `ContractName:0.8.23` +/// +/// This function is safe to use with contracts that have library dependencies. +/// `alloy_json_abi::ContractObject` validates bytecode during JSON parsing and will +/// reject artifacts with unlinked library placeholders. fn get_artifact_code(state: &Cheatcodes, path: &str, deployed: bool) -> Result { let path = if path.ends_with(".json") { PathBuf::from(path) @@ -942,4 +946,17 @@ mod tests { let artifact: ContractObject = serde_json::from_str(s).unwrap(); assert!(artifact.deployed_bytecode.is_some()); } + + #[test] + fn test_alloy_json_abi_rejects_unlinked_bytecode() { + let artifact_json = r#"{ + "abi": [], + "bytecode": "0x73__$987e73aeca5e61ce83e4cb0814d87beda9$__63baf2f868" + }"#; + + let result: Result = serde_json::from_str(artifact_json); + assert!(result.is_err(), "should reject unlinked bytecode with placeholders"); + let err = result.unwrap_err().to_string(); + assert!(err.contains("expected bytecode, found unlinked bytecode with placeholder")); + } }