Skip to content

Commit 2e4e41c

Browse files
committed
Use the wait-for-deployment function
This patch adds test vectors for the create call and uses the wait for deployment function rather than duplicating it's implemetnation.
1 parent 9bb1ef9 commit 2e4e41c

2 files changed

Lines changed: 166 additions & 110 deletions

File tree

clarity/src/contract.rs

Lines changed: 143 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -204,106 +204,156 @@ mod tests {
204204
use super::*;
205205
use crate::utils::bytes_to_hex_str;
206206

207-
#[test]
208-
fn test_calculate_contract_address_nonce_0() {
209-
let deployer: Address = "0x6ac7ea33f8831ea9dcc53393aaa88b25a785dbf0"
210-
.parse()
211-
.unwrap();
212-
let nonce = Uint256::from(0u8);
213-
let expected: Address = "0xcd234a471b72ba2f1ccf0a70fcaba648a5eecd8d"
214-
.parse()
215-
.unwrap();
216-
217-
let result = calculate_contract_address(deployer, nonce);
218-
assert_eq!(result, expected);
207+
/// Variant of contract address derivation.
208+
enum AddressVariant {
209+
/// CREATE: address = keccak256(rlp([deployer, nonce]))[12:]
210+
Create { nonce: u64 },
211+
/// CREATE2: address = keccak256(0xff ++ deployer ++ salt ++ keccak256(init_code))[12:]
212+
Create2 {
213+
salt: [u8; 32],
214+
init_code_hash: [u8; 32],
215+
},
219216
}
220217

221-
#[test]
222-
fn test_calculate_contract_address_nonce_1() {
223-
let deployer: Address = "0x6ac7ea33f8831ea9dcc53393aaa88b25a785dbf0"
224-
.parse()
225-
.unwrap();
226-
let nonce = Uint256::from(1u8);
227-
let expected: Address = "0x343c43a37d37dff08ae8c4a11544c718abb4fcf8"
228-
.parse()
229-
.unwrap();
230-
231-
let result = calculate_contract_address(deployer, nonce);
232-
assert_eq!(result, expected);
218+
/// Minimum data needed to verify contract address prediction.
219+
///
220+
/// Supports both CREATE and CREATE2 opcode variants.
221+
///
222+
/// For CREATE:
223+
/// address = keccak256(rlp([deployer, nonce]))[12:]
224+
///
225+
/// For CREATE2:
226+
/// address = keccak256(0xff ++ deployer ++ salt ++ keccak256(init_code))[12:]
227+
///
228+
/// To add test vectors, find a real Ethereum contract deployment and record:
229+
/// - The deployer (transaction `from` field)
230+
/// - For CREATE: the deployer's nonce at that block
231+
/// - For CREATE2: the salt and init code hash used
232+
/// - The resulting contract address (transaction receipt `contractAddress` field)
233+
struct ContractAddressTest {
234+
deployer: &'static str,
235+
variant: AddressVariant,
236+
expected: &'static str,
233237
}
234238

235-
#[test]
236-
fn test_calculate_contract_address_nonce_2() {
237-
let deployer: Address = "0x6ac7ea33f8831ea9dcc53393aaa88b25a785dbf0"
238-
.parse()
239-
.unwrap();
240-
let nonce = Uint256::from(2u8);
241-
let expected: Address = "0xf778b86fa74e846c4f0a1fbd1335fe81c00a0c91"
242-
.parse()
243-
.unwrap();
244-
245-
let result = calculate_contract_address(deployer, nonce);
246-
assert_eq!(result, expected);
247-
}
248-
249-
#[test]
250-
fn test_calculate_contract_address_high_nonce() {
251-
// Test with a larger nonce value
252-
let deployer: Address = "0x6ac7ea33f8831ea9dcc53393aaa88b25a785dbf0"
253-
.parse()
254-
.unwrap();
255-
let nonce = Uint256::from(1000u32);
256-
257-
// Should not panic and should return a valid address
258-
let result = calculate_contract_address(deployer, nonce);
259-
assert_eq!(result.as_bytes().len(), 20);
260-
}
261-
262-
#[test]
263-
fn test_calculate_contract_address_create2_zero() {
239+
/// Known contract address derivation vectors.
240+
///
241+
/// Includes both CREATE and CREATE2 test cases.
242+
///
243+
/// Sources / how to verify:
244+
/// - Etherscan: look up a contract deployment tx, the receipt `contractAddress`
245+
/// is the expected address, and the tx `nonce` is the deployer nonce.
246+
/// - Python: import rlp; from eth_utils import keccak; keccak(rlp.encode([bytes.fromhex(addr[2:]), nonce]))[12:].hex()
247+
/// - Cast: cast compute-address --nonce <nonce> <deployer> (for CREATE)
248+
/// - Cast: cast compute-address --code-hash <hash> --salt <salt> <deployer> (for CREATE2)
249+
const VECTORS: &[ContractAddressTest] = &[
250+
// CREATE tests
251+
// From EIP-55 reference implementation and clarity doctest.
252+
ContractAddressTest {
253+
deployer: "0x6ac7ea33f8831ea9dcc53393aaa88b25a785dbf0",
254+
variant: AddressVariant::Create { nonce: 0 },
255+
expected: "0xcd234a471b72ba2f1ccf0a70fcaba648a5eecd8d",
256+
},
257+
// Same deployer with nonce 1
258+
ContractAddressTest {
259+
deployer: "0x6ac7ea33f8831ea9dcc53393aaa88b25a785dbf0",
260+
variant: AddressVariant::Create { nonce: 1 },
261+
expected: "0x343c43a37d37dff08ae8c4a11544c718abb4fcf8",
262+
},
263+
// Same deployer with nonce 2
264+
ContractAddressTest {
265+
deployer: "0x6ac7ea33f8831ea9dcc53393aaa88b25a785dbf0",
266+
variant: AddressVariant::Create { nonce: 2 },
267+
expected: "0xf778b86fa74e846c4f0a1fbd1335fe81c00a0c91",
268+
},
269+
// Test with a larger nonce value (> 128 for multi-byte RLP encoding)
270+
ContractAddressTest {
271+
deployer: "0x6ac7ea33f8831ea9dcc53393aaa88b25a785dbf0",
272+
variant: AddressVariant::Create { nonce: 1000 },
273+
expected: "0xB9cDb7F5e62043c1e4EB7a6d76eF8Ee246D364Ec",
274+
},
275+
ContractAddressTest {
276+
deployer: "0xaf69eBeF35607d6834Dac02294792F7d21B95AF9",
277+
variant: AddressVariant::Create { nonce: 344 },
278+
expected: "0x67DB0a68230BA4104DD121Bd451Bd066e5c5fB74",
279+
},
280+
// CREATE2 tests
264281
// All-zero deployer, salt, and init_code_hash
265-
let deployer: Address = "0x0000000000000000000000000000000000000000"
266-
.parse()
267-
.unwrap();
268-
let salt = [0u8; 32];
269-
let init_code_hash = [0u8; 32];
270-
let expected: Address = "0xffc4f52f884a02bcd5716744cd622127366f2edf"
271-
.parse()
272-
.unwrap();
273-
274-
let result = calculate_contract_address_create2(deployer, salt, init_code_hash);
275-
assert_eq!(result, expected);
276-
}
277-
278-
#[test]
279-
fn test_calculate_contract_address_create2_custom() {
280-
let deployer: Address = "0xdeadbeef00000000000000000000000000000000"
281-
.parse()
282-
.unwrap();
283-
let salt = [0u8; 32];
284-
let init_code_hash = [0u8; 32];
285-
let expected: Address = "0x85f15e045e1244ac03289b48448249dc0a34aa30"
286-
.parse()
287-
.unwrap();
288-
289-
let result = calculate_contract_address_create2(deployer, salt, init_code_hash);
290-
assert_eq!(result, expected);
291-
}
282+
ContractAddressTest {
283+
deployer: "0x0000000000000000000000000000000000000000",
284+
variant: AddressVariant::Create2 {
285+
salt: [0u8; 32],
286+
init_code_hash: [0u8; 32],
287+
},
288+
expected: "0xffc4f52f884a02bcd5716744cd622127366f2edf",
289+
},
290+
// Custom deployer with CREATE2
291+
ContractAddressTest {
292+
deployer: "0xdeadbeef00000000000000000000000000000000",
293+
variant: AddressVariant::Create2 {
294+
salt: [0u8; 32],
295+
init_code_hash: [0u8; 32],
296+
},
297+
expected: "0x85f15e045e1244ac03289b48448249dc0a34aa30",
298+
},
299+
// CREATE2 with different salt
300+
ContractAddressTest {
301+
deployer: "0x0000000000000000000000000000000000000000",
302+
variant: AddressVariant::Create2 {
303+
salt: {
304+
let mut s = [0u8; 32];
305+
s[31] = 1;
306+
s
307+
},
308+
init_code_hash: [0u8; 32],
309+
},
310+
expected: "0x12741fEC8148E76ad3a51Cdd5fD73061C6a39148",
311+
},
312+
// TODO: add deployer + nonce + expected from real mainnet deployments
313+
// Example: record the `from`, `nonce`, and receipt `contractAddress` from
314+
// any contract-creation transaction on Etherscan.
315+
//
316+
// ContractAddressTest {
317+
// deployer: "0x...",
318+
// variant: AddressVariant::Create { nonce: 0 },
319+
// expected: "0x...",
320+
// },
321+
];
292322

293323
#[test]
294-
fn test_calculate_contract_address_create2_different_salt() {
295-
let deployer: Address = "0x0000000000000000000000000000000000000000"
296-
.parse()
297-
.unwrap();
298-
let mut salt = [0u8; 32];
299-
salt[31] = 1; // Different salt
300-
let init_code_hash = [0u8; 32];
301-
302-
let result1 = calculate_contract_address_create2(deployer, [0u8; 32], init_code_hash);
303-
let result2 = calculate_contract_address_create2(deployer, salt, init_code_hash);
304-
305-
// Different salts should produce different addresses
306-
assert_ne!(result1, result2);
324+
fn contract_address_prediction() {
325+
for (i, v) in VECTORS.iter().enumerate() {
326+
let deployer: Address = v
327+
.deployer
328+
.parse()
329+
.unwrap_or_else(|e| panic!("vector {i}: bad deployer address: {e}"));
330+
let expected: Address = v
331+
.expected
332+
.parse()
333+
.unwrap_or_else(|e| panic!("vector {i}: bad expected address: {e}"));
334+
335+
match v.variant {
336+
AddressVariant::Create { nonce } => {
337+
let got = calculate_contract_address(deployer, Uint256::from(nonce));
338+
assert_eq!(
339+
got, expected,
340+
"vector {i} (CREATE): deployer={} nonce={} expected={} got={}",
341+
v.deployer, nonce, expected, got
342+
);
343+
}
344+
AddressVariant::Create2 {
345+
salt,
346+
init_code_hash,
347+
} => {
348+
let got = calculate_contract_address_create2(deployer, salt, init_code_hash);
349+
assert_eq!(
350+
got, expected,
351+
"vector {i} (CREATE2): deployer={} expected={} got={}",
352+
v.deployer, expected, got
353+
);
354+
}
355+
}
356+
}
307357
}
308358

309359
#[test]

web30/src/client/transactions.rs

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -519,7 +519,7 @@ impl Web3 {
519519
/// * `value` - Amount of ETH to send with deployment (in wei)
520520
/// * `options` - Vector of SendTxOption for configuration (gas, nonce, etc.)
521521
/// * `wait_timeout` - Optional timeout for waiting for the deployment transaction to be mined, if none
522-
/// we will not wait for deployment and return immediately with the predicted address.
522+
/// we will not wait for deployment and return immediately with the predicted address.
523523
///
524524
/// # Returns
525525
/// The address where the contract was deployed
@@ -530,7 +530,7 @@ impl Web3 {
530530
/// # use clarity::{PrivateKey, Uint256};
531531
/// # use std::time::Duration;
532532
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
533-
/// let web3 = Web3::new("https://eth.llamarpc.com", Duration::from_secs(30));
533+
/// let web3 = Web3::new("https://eth.althea.net", Duration::from_secs(30));
534534
/// let private_key: PrivateKey = "0x...".parse()?;
535535
/// let init_code = vec![0x60, 0x80, 0x60, 0x40]; // Your contract bytecode
536536
/// let constructor_args = vec![]; // ABI-encoded constructor args
@@ -599,18 +599,8 @@ impl Web3 {
599599

600600
match wait_timeout {
601601
Some(timeout) => {
602-
// Wait for the transaction to be mined
603-
self.wait_for_transaction(tx_hash, timeout, None).await?;
604-
605-
// Verify contract was deployed
606-
let code = self.eth_get_code(predicted_address, None).await?;
607-
if code.is_empty() {
608-
return Err(Web3Error::ContractCallError(
609-
"Contract deployment failed: no code at predicted address".to_string(),
610-
));
611-
}
612-
613-
Ok(predicted_address)
602+
self.wait_for_deployment(tx_hash, timeout, predicted_address)
603+
.await
614604
}
615605
None => Ok(predicted_address),
616606
}
@@ -666,9 +656,11 @@ impl Web3 {
666656
&self,
667657
tx_hash: Uint256,
668658
timeout: Duration,
659+
expected_address: Address,
669660
) -> Result<Address, Web3Error> {
670-
// Wait for transaction to be mined
671-
self.wait_for_transaction(tx_hash, timeout, None).await?;
661+
// Wait for transaction to be mined, wait at least one block after as well
662+
self.wait_for_transaction(tx_hash, timeout, Some(1u8.into()))
663+
.await?;
672664

673665
// Get the transaction receipt
674666
let receipt = self
@@ -678,16 +670,30 @@ impl Web3 {
678670
Web3Error::BadResponse("No receipt found for deployment transaction".to_string())
679671
})?;
680672

673+
if !receipt.get_success() {
674+
return Err(Web3Error::ContractCallError(format!(
675+
"Contract deployment failed, transaction reverted. Receipt: {:#?}",
676+
receipt
677+
)));
678+
}
679+
681680
// Extract contract address from receipt
682681
let contract_address = receipt.get_contract_address().ok_or_else(|| {
683682
Web3Error::BadResponse("No contract address in deployment receipt".to_string())
684683
})?;
685684

685+
if contract_address != expected_address {
686+
return Err(Web3Error::BadResponse(format!(
687+
"Deployed contract address does not match expected address. Got: {:?}, Expected: {:?}",
688+
contract_address, expected_address
689+
)));
690+
}
691+
686692
// Verify contract code exists
687693
let code = self.eth_get_code(contract_address, None).await?;
688694
if code.is_empty() {
689695
return Err(Web3Error::ContractCallError(
690-
"Contract deployment reverted: no code at address".to_string(),
696+
"Contract deployment: no code at address".to_string(),
691697
));
692698
}
693699

0 commit comments

Comments
 (0)