Skip to content

Commit 4584cdb

Browse files
authored
Merge pull request #14 from paritytech/sm/fix
Add support for args in constructors
2 parents 7d358cd + 7e560f9 commit 4584cdb

File tree

21 files changed

+777
-132
lines changed

21 files changed

+777
-132
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/pvm-contract-e2e-tests/src/cast.rs

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,36 @@ impl CastClient {
1616
}
1717

1818
/// Deploy a contract and return the contract address.
19-
pub fn deploy(&self, bytecode_hex: &str, private_key: &str) -> String {
19+
/// If `constructor_sig` is non-empty, ABI-encodes `args` and appends them to the bytecode.
20+
pub fn deploy(
21+
&self,
22+
bytecode_hex: &str,
23+
constructor_sig: &str,
24+
args: &[&str],
25+
private_key: &str,
26+
) -> String {
27+
let bytecode = if !constructor_sig.is_empty() {
28+
let mut cmd = Command::new("cast");
29+
cmd.args(["abi-encode", constructor_sig]);
30+
for arg in args {
31+
cmd.arg(arg);
32+
}
33+
let output = cmd.output().expect("cast abi-encode failed to execute");
34+
assert!(
35+
output.status.success(),
36+
"cast abi-encode failed: {}",
37+
String::from_utf8_lossy(&output.stderr)
38+
);
39+
let encoded_args = String::from_utf8(output.stdout)
40+
.unwrap()
41+
.trim()
42+
.trim_start_matches("0x")
43+
.to_string();
44+
format!("{bytecode_hex}{encoded_args}")
45+
} else {
46+
bytecode_hex.to_string()
47+
};
48+
2049
let output = Command::new("cast")
2150
.args([
2251
"send",
@@ -28,7 +57,7 @@ impl CastClient {
2857
"9999999999999",
2958
"--json",
3059
"--create",
31-
bytecode_hex,
60+
&bytecode,
3261
])
3362
.output()
3463
.expect("cast send --create failed to execute");

crates/pvm-contract-e2e-tests/tests/e2e_tests.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ fn deploy_variant(variant: &str) -> (AnvilPolkadot, CastClient, String) {
3030
let anvil = AnvilPolkadot::start();
3131
let cast = CastClient::new(&anvil.rpc_url);
3232
let hex = c.bytecode_hex(variant, "release");
33-
let address = cast.deploy(&hex, DEFAULT_PRIVATE_KEY);
33+
let address = cast.deploy(&hex, "", &[], DEFAULT_PRIVATE_KEY);
3434
(anvil, cast, address)
3535
}
3636

@@ -88,7 +88,7 @@ fn dispatch_fallback_handles_unknown_selector() {
8888
let anvil = AnvilPolkadot::start();
8989
let cast = CastClient::new(&anvil.rpc_url);
9090
let hex = c.bytecode_hex(DEFAULT_VARIANT, "release");
91-
let address = cast.deploy(&hex, DEFAULT_PRIVATE_KEY);
91+
let address = cast.deploy(&hex, "", &[], DEFAULT_PRIVATE_KEY);
9292

9393
// 0xdeadbeef is not a known selector — fallback should handle it
9494
let mut cmd = std::process::Command::new("cast");

crates/pvm-contract-e2e-tests/tests/integration_tests.rs

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ fn deploy(binary_name: &str) -> (AnvilPolkadot, CastClient, String) {
88
let anvil = AnvilPolkadot::start();
99
let cast = CastClient::new(&anvil.rpc_url);
1010
let hex = c.bytecode_hex(binary_name, "release");
11-
let address = cast.deploy(&hex, DEFAULT_PRIVATE_KEY);
11+
let address = cast.deploy(&hex, "", &[], DEFAULT_PRIVATE_KEY);
1212
(anvil, cast, address)
1313
}
1414

@@ -384,3 +384,65 @@ fn composite_tuple_false() {
384384
);
385385
assert_eq!(val, "0");
386386
}
387+
388+
// --- Constructor Arguments ---
389+
390+
fn deploy_constructor_args(owner: &str, supply: &str) -> (AnvilPolkadot, CastClient, String) {
391+
let c = contract("test-contracts");
392+
c.build();
393+
let anvil = AnvilPolkadot::start();
394+
let cast = CastClient::new(&anvil.rpc_url);
395+
let hex = c.bytecode_hex("constructor-args", "release");
396+
let address = cast.deploy(
397+
&hex,
398+
"constructor(address,uint256)",
399+
&[owner, supply],
400+
DEFAULT_PRIVATE_KEY,
401+
);
402+
(anvil, cast, address)
403+
}
404+
405+
#[test]
406+
fn constructor_args_sets_owner_and_supply() {
407+
let owner = DEFAULT_ADDRESS;
408+
let supply = "1000000";
409+
let (_anvil, cast, addr) = deploy_constructor_args(owner, supply);
410+
411+
let got_owner = cast.call(&addr, "getOwner()(address)", &[]);
412+
assert_eq!(
413+
got_owner.to_lowercase(),
414+
owner.to_lowercase(),
415+
"Constructor should set owner"
416+
);
417+
418+
let got_supply = cast.call(&addr, "getInitialSupply()(uint256)", &[]);
419+
assert_eq!(got_supply, supply, "Constructor should set initial supply");
420+
}
421+
422+
#[test]
423+
fn constructor_args_different_values() {
424+
let owner = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8";
425+
let supply = "999";
426+
let (_anvil, cast, addr) = deploy_constructor_args(owner, supply);
427+
428+
let got_owner = cast.call(&addr, "getOwner()(address)", &[]);
429+
assert_eq!(
430+
got_owner.to_lowercase(),
431+
owner.to_lowercase(),
432+
"Constructor should set different owner"
433+
);
434+
435+
let got_supply = cast.call(&addr, "getInitialSupply()(uint256)", &[]);
436+
assert_eq!(
437+
got_supply, supply,
438+
"Constructor should set different supply"
439+
);
440+
}
441+
442+
#[test]
443+
fn constructor_args_zero_supply() {
444+
let (_anvil, cast, addr) = deploy_constructor_args(DEFAULT_ADDRESS, "0");
445+
446+
let got_supply = cast.call(&addr, "getInitialSupply()(uint256)", &[]);
447+
assert_eq!(got_supply, "0", "Constructor should handle zero supply");
448+
}

crates/pvm-contract-macros/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,5 @@ pvm-contract-types = { path = "../pvm-contract-types" }
2222

2323
[dev-dependencies]
2424
ruint = { version = "1.17", default-features = false }
25+
serde_json = { workspace = true }
2526
pvm-contract-types = { path = "../pvm-contract-types", features = ["alloc"] }

crates/pvm-contract-macros/src/codegen/abi_gen.rs

Lines changed: 31 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,37 @@ fn generate_abi_gen_main_impl(parsed: &ParsedContract) -> syn::Result<TokenStrea
3838
};
3939

4040
let constructor_entry = if parsed.has_constructor {
41+
let constructor_input_entries: Vec<TokenStream> = parsed
42+
.constructor_inputs
43+
.iter()
44+
.map(|(name, sol_type)| {
45+
let name_str = name.to_string();
46+
let type_name_expr = generate_sol_type_name_expr(sol_type)?;
47+
Ok(quote! {
48+
if !__first_ctor_input {
49+
__abi.push(',');
50+
} else {
51+
__first_ctor_input = false;
52+
}
53+
__abi.push_str("{\"name\":\"");
54+
__abi.push_str(#name_str);
55+
__abi.push_str("\",\"type\":\"");
56+
__abi.push_str(&#type_name_expr);
57+
__abi.push_str("\"}");
58+
})
59+
})
60+
.collect::<syn::Result<Vec<_>>>()?;
61+
4162
quote! {
4263
if !__first_item {
4364
__abi.push(',');
4465
} else {
4566
__first_item = false;
4667
}
47-
__abi.push_str("{\"type\":\"constructor\",\"inputs\":[]}");
68+
__abi.push_str("{\"type\":\"constructor\",\"inputs\":[");
69+
let mut __first_ctor_input = true;
70+
#(#constructor_input_entries)*
71+
__abi.push_str("]}");
4872
}
4973
} else {
5074
quote! {}
@@ -237,95 +261,20 @@ fn generate_sol_type_name_expr(sol_type: &SolType) -> syn::Result<TokenStream> {
237261
#[cfg(test)]
238262
mod tests {
239263
use super::*;
240-
use crate::signature::FunctionSignature;
241264

242265
#[test]
243266
fn returns_empty_stream_for_sol_path_contract() {
244-
let parsed = parsed_contract(vec![], false);
245-
246-
assert!(generate_abi_gen_main(&parsed, true).is_empty());
247-
}
248-
249-
#[test]
250-
fn generates_constructor_and_known_types_without_sol_encode_import() {
251-
let parsed = parsed_contract(
252-
vec![method(
253-
"balance_of",
254-
"balanceOf",
255-
vec![SolType::Address],
256-
vec![SolType::Uint(256)],
257-
&["account"],
258-
)],
259-
true,
260-
);
261-
262-
let output = generate_abi_gen_main(&parsed, false).to_string();
263-
264-
assert!(output.contains("cfg (feature = \"abi-gen\")"));
265-
assert!(output.contains("{\\\"type\\\":\\\"constructor\\\",\\\"inputs\\\":[]}"));
266-
assert!(output.contains("{\\\"type\\\":\\\"function\\\",\\\"name\\\":\\\""));
267-
assert!(output.contains("__abi . push_str (\"balanceOf\")"));
268-
assert!(output.contains("__abi . push_str (\"account\")"));
269-
assert!(output.contains("String :: from (\"address\")"));
270-
assert!(output.contains("String :: from (\"uint256\")"));
271-
assert!(!output.contains("SolEncode"));
272-
}
273-
274-
#[test]
275-
fn generates_sol_encode_calls_for_custom_types() {
276-
let parsed = parsed_contract(
277-
vec![method(
278-
"touch",
279-
"touch",
280-
vec![SolType::Custom("my_crate::MyType".to_string())],
281-
vec![SolType::Array(Box::new(SolType::Custom(
282-
"my_crate::MyType".to_string(),
283-
)))],
284-
&["value"],
285-
)],
286-
false,
287-
);
288-
289-
let output = generate_abi_gen_main(&parsed, false).to_string();
290-
291-
assert!(output.contains("use :: pvm_contract_types :: SolEncode ;"));
292-
assert!(output.contains(
293-
"< my_crate :: MyType as :: pvm_contract_types :: SolEncode > :: sol_name ()"
294-
));
295-
assert!(output.contains("push_str (\"[]\")"));
296-
}
297-
298-
fn parsed_contract(methods: Vec<MethodInfo>, has_constructor: bool) -> ParsedContract {
299-
ParsedContract {
267+
let parsed = ParsedContract {
300268
mod_name: syn::parse_str("contract").unwrap(),
301-
methods,
302-
has_constructor,
269+
methods: vec![],
270+
has_constructor: false,
303271
has_fallback: false,
304272
constructor_name: None,
305273
constructor_returns_result: false,
274+
constructor_inputs: vec![],
306275
fallback_name: None,
307-
}
308-
}
276+
};
309277

310-
fn method(
311-
fn_name: &str,
312-
signature_name: &str,
313-
inputs: Vec<SolType>,
314-
outputs: Vec<SolType>,
315-
param_names: &[&str],
316-
) -> MethodInfo {
317-
MethodInfo {
318-
fn_name: syn::parse_str(fn_name).unwrap(),
319-
signature: FunctionSignature {
320-
name: signature_name.to_string(),
321-
inputs,
322-
outputs,
323-
},
324-
param_names: param_names
325-
.iter()
326-
.map(|name| syn::parse_str(name).unwrap())
327-
.collect(),
328-
returns_result: false,
329-
}
278+
assert!(generate_abi_gen_main(&parsed, true).is_empty());
330279
}
331280
}

0 commit comments

Comments
 (0)