Skip to content

Commit 13bb837

Browse files
authored
feat: use a proxy for the registration (#427)
* test: detects_project_type_correctly * feat: parachain deployment in pop up * fix: parachain feature * docs: improve comments * feat: deploy a parachain commands * feat: build specs in pop up * feat: logic to interact with a chain * feat: register parachain * refactor: clean code and improve docs * test: unit tests for pop up methods * refactor: small fixes with visibility and removing logs * feat: return events in submit_signed_extrinsic * feat: get para_id from event * test: fix detects_parachain_correctly * refactor: improve docs and code * test: fix change_working_directory_works * fix: clippy warnings * refactor: move submit_extrinsic_with_wallet in a common file * refactor: remove unnecesary code * refactor: UpChainCommand structure * test: adjust tests to refactored struct * refactor: renaming prepare_register_parachain_call_data and prepare_rerve_parachain_call_data * refactor: move events module * fix: submit_extrinsic_with_wallet under parachain feature * refactor: remove unnecesary code * test: increase coverage with reserve_parachain_id_fails_wrong_chain and resolve_genesis_files_fails_wrong_path * refactor: remove unnecesary clones * refactor: minor improvements * test: refactor tests and include comments * refactor: map errors in submit_extrinsic_with_wallet * test: fix prepare_register_parachain_call_data_works * refactor: move configure_chain into a common folder * refactor: function visibility * fix: error message and include test for it * feat: construct_proxy_extrinsic * feat: prompt for proxy address in the UI * docs: improve flag description and display command to be executed * docs: improve prompt to the user message for proxy * feat: replace index.html with costs for calls with proxy in ui * refactor: remove display command information (separate PR) * test: fix resolve_proxy_address_works * refactor: rename and more clear messages for the user to prompt proxied account * chore: update index.html with latest changes * test: fix format * test: detects_project_type_correctly * feat: parachain deployment in pop up * fix: parachain feature * docs: improve comments * feat: replace index.html with summary costs in ui (#430) * refactor: rename parachain to rollup * docs: improve deprecation message * refactor: rename parachain to rollup in up help * feat: deploy a parachain commands * feat: build specs in pop up * feat: logic to interact with a chain * feat: register parachain * refactor: clean code and improve docs * test: unit tests for pop up methods * refactor: small fixes with visibility and removing logs * feat: return events in submit_signed_extrinsic * feat: get para_id from event * test: fix detects_parachain_correctly * refactor: improve docs and code * test: fix change_working_directory_works * fix: clippy warnings * refactor: move submit_extrinsic_with_wallet in a common file * refactor: remove unnecesary code * refactor: UpChainCommand structure * test: adjust tests to refactored struct * refactor: renaming prepare_register_parachain_call_data and prepare_rerve_parachain_call_data * refactor: move events module * fix: submit_extrinsic_with_wallet under parachain feature * refactor: remove unnecesary code * test: increase coverage with reserve_parachain_id_fails_wrong_chain and resolve_genesis_files_fails_wrong_path * refactor: remove unnecesary clones * refactor: minor improvements * test: refactor tests and include comments * refactor: map errors in submit_extrinsic_with_wallet * test: fix prepare_register_parachain_call_data_works * refactor: move configure_chain into a common folder * refactor: function visibility * fix: error message and include test for it * refactor: build specs removing repetitive code * refactor: use prepare_extrinsic from Call module to prepare a call * docs: improve comments and messages * refactor: rename variables and structs * refactor: relay_chain_url * refactor: rename prepare_reserve_call_data and prepare_register_call_data * test: remove unnecesary test * refactor: remove events module * refactor: rename parachain to rollup * chore: improve succesful message * chore: change intro title to use rollup * docs: comments for Reserved event * refactor: clean code to avoid unnecesary clone * feat: wrap Id by default in the proxy address and improve comment * refactor: remove ProxyConfig and use Proxy * refactor: resolve_proxied_address * fix: change visibility for prompt_for_param * refactor: more clarity in code variables and comments * refactor: custom message to prompt for proxy address * refactor: support only Account Id type for proxied_address * test: remove unnecesary async in resolve_proxied_address_works
1 parent b413add commit 13bb837

File tree

13 files changed

+210
-51
lines changed

13 files changed

+210
-51
lines changed

crates/pop-cli/src/assets/index.html

+2-2
Large diffs are not rendered by default.

crates/pop-cli/src/commands/call/contract.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ use crate::{
1111
use anyhow::{anyhow, Result};
1212
use clap::Args;
1313
use cliclack::spinner;
14-
use pop_common::{DefaultConfig, Keypair};
14+
use pop_common::{parse_account, DefaultConfig, Keypair};
1515
use pop_contracts::{
1616
build_smart_contract, call_smart_contract, call_smart_contract_from_signed_payload,
1717
dry_run_call, dry_run_gas_estimate_call, get_call_payload, get_message, get_messages,
18-
parse_account, set_up_call, CallExec, CallOpts, DefaultEnvironment, Verbosity,
18+
set_up_call, CallExec, CallOpts, DefaultEnvironment, Verbosity,
1919
};
2020
use sp_weights::Weight;
2121
use std::path::PathBuf;

crates/pop-cli/src/commands/up/rollup.rs

+109-13
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,18 @@ use crate::{
1212
};
1313
use anyhow::{anyhow, Result};
1414
use clap::Args;
15-
use pop_parachains::{find_dispatchable_by_name, Action, Payload, Reserved};
15+
use pop_common::parse_account;
16+
use pop_parachains::{
17+
construct_proxy_extrinsic, find_dispatchable_by_name, Action, Payload, Reserved,
18+
};
1619
use std::path::{Path, PathBuf};
1720
use url::Url;
1821

22+
type Proxy = Option<String>;
23+
1924
const DEFAULT_URL: &str = "wss://paseo.rpc.amforc.com/";
2025
const HELP_HEADER: &str = "Chain deployment options";
26+
const PLACEHOLDER_ADDRESS: &str = "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty";
2127

2228
#[derive(Args, Clone, Default)]
2329
#[clap(next_help_heading = HELP_HEADER)]
@@ -37,6 +43,10 @@ pub struct UpCommand {
3743
/// Websocket endpoint of the relay chain.
3844
#[arg(long)]
3945
pub(crate) relay_chain_url: Option<Url>,
46+
/// Proxied address. Your account must be registered as a proxy which can act on behalf of this
47+
/// account.
48+
#[arg(long = "proxy")]
49+
pub(crate) proxied_address: Option<String>,
4050
}
4151

4252
impl UpCommand {
@@ -69,18 +79,30 @@ impl UpCommand {
6979
let chain =
7080
configure("Enter the relay chain node URL", DEFAULT_URL, &self.relay_chain_url, cli)
7181
.await?;
72-
let id = self.resolve_id(&chain, cli).await?;
82+
let proxy = self.resolve_proxied_address(cli)?;
83+
let id = self.resolve_id(&chain, &proxy, cli).await?;
7384
let (genesis_code, genesis_state) = self.resolve_genesis_files(id, cli).await?;
74-
Ok(Registration { id, genesis_state, genesis_code, chain })
85+
Ok(Registration { id, genesis_state, genesis_code, chain, proxy })
86+
}
87+
88+
// Retrieves the proxied address, prompting the user if none is specified.
89+
fn resolve_proxied_address(&self, cli: &mut impl Cli) -> Result<Proxy> {
90+
if let Some(addr) = &self.proxied_address {
91+
return Ok(parse_account(addr).map(|valid_addr| Some(format!("Id({valid_addr})")))?);
92+
}
93+
if cli.confirm("Would you like to use a proxy for registration? This is considered a best practice.").interact()? {
94+
return Ok(Some(prompt_for_proxy_address(cli)?));
95+
}
96+
Ok(None)
7597
}
7698

7799
// Resolves the ID, reserving a new one if necessary.
78-
async fn resolve_id(&self, chain: &Chain, cli: &mut impl Cli) -> Result<u32> {
100+
async fn resolve_id(&self, chain: &Chain, proxy: &Proxy, cli: &mut impl Cli) -> Result<u32> {
79101
match self.id {
80102
Some(id) => Ok(id),
81103
None => {
82104
cli.info(format!("You will need to sign a transaction to reserve an ID on {} using the `Registrar::reserve` function.", chain.url))?;
83-
reserve(chain, cli).await
105+
reserve(chain, proxy, cli).await
84106
},
85107
}
86108
}
@@ -106,6 +128,7 @@ pub(crate) struct Registration {
106128
genesis_state: StatePathBuf,
107129
genesis_code: CodePathBuf,
108130
chain: Chain,
131+
proxy: Proxy,
109132
}
110133
impl Registration {
111134
// Registers by submitting an extrinsic.
@@ -120,7 +143,7 @@ impl Registration {
120143

121144
// Prepares and returns the encoded call data for registering a chain.
122145
fn prepare_register_call_data(&self, cli: &mut impl Cli) -> Result<Vec<u8>> {
123-
let Registration { id, genesis_code, genesis_state, chain } = self;
146+
let Registration { id, genesis_code, genesis_state, chain, proxy } = self;
124147
let dispatchable = find_dispatchable_by_name(
125148
&chain.pallets,
126149
Action::Register.pallet_name(),
@@ -130,20 +153,23 @@ impl Registration {
130153
.map_err(|err| anyhow!("Failed to read genesis state file: {}", err.to_string()))?;
131154
let code = std::fs::read_to_string(genesis_code)
132155
.map_err(|err| anyhow!("Failed to read genesis code file: {}", err.to_string()))?;
133-
let xt = Call {
156+
let mut xt = Call {
134157
function: dispatchable.clone(),
135158
args: vec![id.to_string(), state, code],
136159
use_wallet: true,
137160
..Default::default()
138161
}
139162
.prepare_extrinsic(&chain.client, cli)?;
163+
if let Some(addr) = proxy {
164+
xt = construct_proxy_extrinsic(&chain.pallets, addr.to_string(), xt)?;
165+
}
140166
Ok(xt.encode_call_data(&chain.client.metadata())?)
141167
}
142168
}
143169

144170
// Reserves an ID by submitting an extrinsic.
145-
async fn reserve(chain: &Chain, cli: &mut impl Cli) -> Result<u32> {
146-
let call_data = prepare_reserve_call_data(chain, cli)?;
171+
async fn reserve(chain: &Chain, proxy: &Proxy, cli: &mut impl Cli) -> Result<u32> {
172+
let call_data = prepare_reserve_call_data(chain, proxy, cli)?;
147173
let events = submit_extrinsic(&chain.client, &chain.url, call_data, cli)
148174
.await
149175
.map_err(|e| anyhow::anyhow!("ID reservation failed: {}", e))?;
@@ -156,14 +182,17 @@ async fn reserve(chain: &Chain, cli: &mut impl Cli) -> Result<u32> {
156182
}
157183

158184
// Prepares and returns the encoded call data for reserving an ID.
159-
fn prepare_reserve_call_data(chain: &Chain, cli: &mut impl Cli) -> Result<Vec<u8>> {
185+
fn prepare_reserve_call_data(chain: &Chain, proxy: &Proxy, cli: &mut impl Cli) -> Result<Vec<u8>> {
160186
let dispatchable = find_dispatchable_by_name(
161187
&chain.pallets,
162188
Action::Reserve.pallet_name(),
163189
Action::Reserve.function_name(),
164190
)?;
165-
let xt = Call { function: dispatchable.clone(), use_wallet: true, ..Default::default() }
191+
let mut xt = Call { function: dispatchable.clone(), use_wallet: true, ..Default::default() }
166192
.prepare_extrinsic(&chain.client, cli)?;
193+
if let Some(addr) = proxy {
194+
xt = construct_proxy_extrinsic(&chain.pallets, addr.to_string(), xt)?;
195+
}
167196
Ok(xt.encode_call_data(&chain.client.metadata())?)
168197
}
169198

@@ -195,6 +224,19 @@ async fn generate_spec_files(
195224
))
196225
}
197226

227+
// Prompt the user to input an address and return it formatted as `Id(address)`
228+
fn prompt_for_proxy_address(cli: &mut impl Cli) -> Result<String> {
229+
let address = cli
230+
.input("Enter the account that the proxy will make a call on behalf of")
231+
.placeholder(&format!("e.g {}", PLACEHOLDER_ADDRESS))
232+
.validate(|input: &String| match parse_account(input) {
233+
Ok(_) => Ok(()),
234+
Err(_) => Err("Invalid address."),
235+
})
236+
.interact()?;
237+
Ok(format!("Id({address})"))
238+
}
239+
198240
#[cfg(test)]
199241
mod tests {
200242
use super::*;
@@ -204,6 +246,8 @@ mod tests {
204246
use tempfile::tempdir;
205247
use url::Url;
206248

249+
const MOCK_PROXIED_ADDRESS: &str = "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty";
250+
const MOCK_PROXY_ADDRESS_ID: &str = "Id(13czcAAt6xgLwZ8k6ZpkrRL5V2pjKEui3v9gHAN9PoxYZDbf)";
207251
const POLKADOT_NETWORK_URL: &str = "wss://polkadot-rpc.publicnode.com";
208252
const POP_NETWORK_TESTNET_URL: &str = "wss://rpc1.paseo.popnetwork.xyz";
209253

@@ -216,6 +260,7 @@ mod tests {
216260
id: Some(2000),
217261
genesis_state: Some(genesis_state.clone()),
218262
genesis_code: Some(genesis_code.clone()),
263+
proxied_address: Some(MOCK_PROXIED_ADDRESS.to_string()),
219264
..Default::default()
220265
}
221266
.prepare_for_registration(&mut cli)
@@ -225,6 +270,37 @@ mod tests {
225270
assert_eq!(chain_config.genesis_code, genesis_code);
226271
assert_eq!(chain_config.genesis_state, genesis_state);
227272
assert_eq!(chain_config.chain.url, Url::parse(POLKADOT_NETWORK_URL)?);
273+
assert_eq!(chain_config.proxy, Some(format!("Id({})", MOCK_PROXIED_ADDRESS.to_string())));
274+
cli.verify()
275+
}
276+
277+
#[test]
278+
fn resolve_proxied_address_works() -> Result<()> {
279+
let mut cli = MockCli::new()
280+
.expect_confirm("Would you like to use a proxy for registration? This is considered a best practice.", true)
281+
.expect_input(
282+
"Enter the account that the proxy will make a call on behalf of",
283+
MOCK_PROXIED_ADDRESS.into(),
284+
);
285+
let proxied_address = UpCommand::default().resolve_proxied_address(&mut cli)?;
286+
assert_eq!(proxied_address, Some(format!("Id({})", MOCK_PROXIED_ADDRESS)));
287+
cli.verify()?;
288+
289+
cli = MockCli::new().expect_confirm(
290+
"Would you like to use a proxy for registration? This is considered a best practice.",
291+
false,
292+
);
293+
let proxied_address = UpCommand::default().resolve_proxied_address(&mut cli)?;
294+
assert_eq!(proxied_address, None);
295+
cli.verify()?;
296+
297+
cli = MockCli::new();
298+
let proxied_address = UpCommand {
299+
proxied_address: Some(MOCK_PROXIED_ADDRESS.to_string()),
300+
..Default::default()
301+
}
302+
.resolve_proxied_address(&mut cli)?;
303+
assert_eq!(proxied_address, Some(format!("Id({})", MOCK_PROXIED_ADDRESS)));
228304
cli.verify()
229305
}
230306

@@ -238,11 +314,20 @@ mod tests {
238314
&mut cli,
239315
)
240316
.await?;
241-
let call_data = prepare_reserve_call_data(&chain, &mut cli)?;
317+
let call_data = prepare_reserve_call_data(&chain, &None, &mut cli)?;
242318
// Encoded call data for a reserve extrinsic.
243319
// Reference: https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Fpolkadot.public.curie.radiumblock.co%2Fws#/extrinsics/decode/0x4605
244320
let encoded_reserve_extrinsic: &str = "0x4605";
245321
assert_eq!(call_data, decode_call_data(encoded_reserve_extrinsic)?);
322+
323+
// Ensure `prepare_reserve_call_data` works with a proxy.
324+
let call_data =
325+
prepare_reserve_call_data(&chain, &Some(MOCK_PROXY_ADDRESS_ID.to_string()), &mut cli)?;
326+
// Encoded call data for a proxy extrinsic with reserve as the call.
327+
// Reference: https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Fpolkadot.public.curie.radiumblock.co%2Fws#/extrinsics/decode/0x1d000073ebf9c947490b9170ea4fd3031ae039452e428531317f76bf0a02124f8166de004605
328+
let encoded_reserve_extrinsic: &str =
329+
"0x1d000073ebf9c947490b9170ea4fd3031ae039452e428531317f76bf0a02124f8166de004605";
330+
assert_eq!(call_data, decode_call_data(encoded_reserve_extrinsic)?);
246331
Ok(())
247332
}
248333

@@ -259,6 +344,7 @@ mod tests {
259344
genesis_code: Some(genesis_code.clone()),
260345
relay_chain_url: Some(Url::parse(POP_NETWORK_TESTNET_URL)?),
261346
path: None,
347+
proxied_address: None,
262348
}
263349
.execute(&mut cli)
264350
.await?;
@@ -279,6 +365,7 @@ mod tests {
279365
genesis_code: Some(genesis_code.clone()),
280366
relay_chain_url: Some(Url::parse(POP_NETWORK_TESTNET_URL)?),
281367
path: None,
368+
proxied_address: None,
282369
}
283370
.execute(&mut cli)
284371
.await?;
@@ -300,11 +387,12 @@ mod tests {
300387
let temp_dir = tempdir()?;
301388
let genesis_state_path = temp_dir.path().join("genesis_state");
302389
let genesis_code_path = temp_dir.path().join("genesis_code.wasm");
303-
let up_chain = Registration {
390+
let mut up_chain = Registration {
304391
id: 2000,
305392
genesis_state: genesis_state_path.clone(),
306393
genesis_code: genesis_code_path.clone(),
307394
chain,
395+
proxy: None,
308396
};
309397

310398
// Expect failure when the genesis state file cannot be read.
@@ -328,6 +416,14 @@ mod tests {
328416
up_chain.prepare_register_call_data(&mut cli)?,
329417
decode_call_data(encoded_register_extrinsic)?
330418
);
419+
420+
// Ensure `prepare_register_call_data` works with a proxy.
421+
up_chain.proxy = Some(MOCK_PROXY_ADDRESS_ID.to_string());
422+
let call_data = up_chain.prepare_register_call_data(&mut cli)?;
423+
// Encoded call data for a proxy extrinsic with register as the call.
424+
// Reference: https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Fpolkadot.public.curie.radiumblock.co%2Fws#/extrinsics/decode/0x1d000073ebf9c947490b9170ea4fd3031ae039452e428531317f76bf0a02124f8166de004600d0070000081234081234
425+
let encoded_reserve_extrinsic: &str = "0x1d000073ebf9c947490b9170ea4fd3031ae039452e428531317f76bf0a02124f8166de004600d0070000081234081234";
426+
assert_eq!(call_data, decode_call_data(encoded_reserve_extrinsic)?);
331427
Ok(())
332428
}
333429

crates/pop-common/src/account_id.rs

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// SPDX-License-Identifier: GPL-3.0
2+
3+
use crate::{Config, DefaultConfig, Error};
4+
use std::str::FromStr;
5+
6+
/// Parses an account ID from its string representation.
7+
///
8+
/// # Arguments
9+
/// * `account` - A string representing the account ID to parse.
10+
pub fn parse_account(account: &str) -> Result<<DefaultConfig as Config>::AccountId, Error> {
11+
<DefaultConfig as Config>::AccountId::from_str(account)
12+
.map_err(|e| Error::AccountAddressParsing(format!("{}", e)))
13+
}
14+
15+
#[cfg(test)]
16+
mod tests {
17+
use super::*;
18+
use anyhow::Result;
19+
20+
#[test]
21+
fn parse_account_works() -> Result<(), Error> {
22+
let account = parse_account("5CLPm1CeUvJhZ8GCDZCR7nWZ2m3XXe4X5MtAQK69zEjut36A")?;
23+
assert_eq!(account.to_string(), "5CLPm1CeUvJhZ8GCDZCR7nWZ2m3XXe4X5MtAQK69zEjut36A");
24+
Ok(())
25+
}
26+
27+
#[test]
28+
fn parse_account_fails_wrong_value() -> Result<(), Error> {
29+
assert!(matches!(
30+
parse_account("5CLPm1CeUvJhZ8GCDZCR7"),
31+
Err(super::Error::AccountAddressParsing(..))
32+
));
33+
assert!(matches!(
34+
parse_account("wrongaccount"),
35+
Err(super::Error::AccountAddressParsing(..))
36+
));
37+
Ok(())
38+
}
39+
}

crates/pop-common/src/errors.rs

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ use thiserror::Error;
66
/// Represents the various errors that can occur in the crate.
77
#[derive(Error, Debug)]
88
pub enum Error {
9+
#[error("Failed to parse account address: {0}")]
10+
AccountAddressParsing(String),
911
#[error("Anyhow error: {0}")]
1012
AnyhowError(#[from] anyhow::Error),
1113
#[error("Configuration error: {0}")]

crates/pop-common/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
use std::net::TcpListener;
44

5+
pub use account_id::parse_account;
56
pub use build::Profile;
67
pub use errors::Error;
78
pub use git::{Git, GitHub, Release};
@@ -14,6 +15,8 @@ pub use subxt::{Config, PolkadotConfig as DefaultConfig};
1415
pub use subxt_signer::sr25519::Keypair;
1516
pub use templates::extractor::extract_template_files;
1617

18+
/// Module for parsing and handling account IDs.
19+
pub mod account_id;
1720
pub mod build;
1821
pub mod errors;
1922
pub mod git;

crates/pop-contracts/src/call.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::{
66
utils::{
77
get_manifest_path,
88
metadata::{process_function_args, FunctionType},
9-
parse_account, parse_balance,
9+
parse_balance,
1010
},
1111
};
1212
use anyhow::Context;
@@ -16,7 +16,7 @@ use contract_extrinsics::{
1616
DisplayEvents, ErrorVariant, ExtrinsicOptsBuilder, TokenMetadata,
1717
};
1818
use ink_env::{DefaultEnvironment, Environment};
19-
use pop_common::{create_signer, Config, DefaultConfig, Keypair};
19+
use pop_common::{create_signer, parse_account, Config, DefaultConfig, Keypair};
2020
use sp_weights::Weight;
2121
use std::path::PathBuf;
2222
use subxt::{tx::Payload, SubstrateConfig};

crates/pop-contracts/src/errors.rs

-2
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ use thiserror::Error;
77
#[derive(Error, Debug)]
88
#[allow(clippy::enum_variant_names)]
99
pub enum Error {
10-
#[error("Failed to parse account address: {0}")]
11-
AccountAddressParsing(String),
1210
#[error("Anyhow error: {0}")]
1311
AnyhowError(#[from] anyhow::Error),
1412
#[error("Failed to parse balance: {0}")]

crates/pop-contracts/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ pub use up::{
3030
};
3131
pub use utils::{
3232
metadata::{get_message, get_messages, ContractFunction},
33-
parse_account, parse_hex_bytes,
33+
parse_hex_bytes,
3434
};
3535
// External exports
3636
pub use contract_extrinsics::CallExec;

0 commit comments

Comments
 (0)