Skip to content
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

feat: guide user to call a contract #306

Open
wants to merge 36 commits into
base: main
Choose a base branch
from
Open

Conversation

AlexD10S
Copy link
Collaborator

@AlexD10S AlexD10S commented Sep 9, 2024

PR that parses the contract metadata and guides the user in interacting with the smart contract

┌   Pop CLI : Call a contract
│
◇  Where is your project located?
│  ./
│
◇  Paste the on-chain contract address:
│  15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm
│
◇  Select the message to call:
│  get 
│
◇  Where is your contract deployed?
│  wss://rpc1.paseo.popnetwork.xyz
│
◇  Signer calling the contract:
│  //Alice
│
◓  Calling the contract...                                                                                                                                            ⚙  Result: Ok(false)
│  
▲  Your call has not been executed.
│  
◆  Do you want to do another call?
│  ○ Yes  / ● No 
└  

Apologies for the large PR; it grew bigger as we needed to test user interactions in the pop-cli crate.
This required adding more code for testing purposes, including changes to the CLI module and the introduction of a new init_tests file which contains reusable methods for various tests.

Ideally, these changes should have been introduced in #315, but they were included here due to the necessity of testing the current implementation.

Another note, for testing purposes it has introduced a testing.json file. This file is the metadata of a modified flipper contract, which includes an extra message, to effectively test argument parsing and payable functions.

/// A message for testing, flips the value of the stored `bool` with `new_value`
/// and is payable
#[ink(message)]
#[ink(payable)]
pub fn specific_flip(&mut self, new_value: bool) {
    self.value = new_value;
}

Copy link

codecov bot commented Sep 10, 2024

Codecov Report

Attention: Patch coverage is 79.18552% with 322 lines in your changes missing coverage. Please review.

Project coverage is 72.54%. Comparing base (258a4b2) to head (b0f3e8b).

Files with missing lines Patch % Lines
crates/pop-cli/src/commands/call/contract.rs 84.98% 44 Missing and 85 partials ⚠️
crates/pop-contracts/src/utils/metadata.rs 75.06% 71 Missing and 26 partials ⚠️
crates/pop-cli/src/cli.rs 56.58% 50 Missing and 6 partials ⚠️
crates/pop-contracts/src/up.rs 71.42% 0 Missing and 14 partials ⚠️
crates/pop-contracts/src/call.rs 82.43% 0 Missing and 13 partials ⚠️
crates/pop-cli/src/common/build.rs 73.07% 1 Missing and 6 partials ⚠️
crates/pop-contracts/src/testing.rs 68.42% 0 Missing and 6 partials ⚠️
@@            Coverage Diff             @@
##             main     #306      +/-   ##
==========================================
+ Coverage   70.47%   72.54%   +2.07%     
==========================================
  Files          54       57       +3     
  Lines        9250    10665    +1415     
  Branches     9250    10665    +1415     
==========================================
+ Hits         6519     7737    +1218     
- Misses       1737     1839     +102     
- Partials      994     1089      +95     
Files with missing lines Coverage Δ
crates/pop-cli/src/commands/up/contract.rs 18.46% <100.00%> (-5.42%) ⬇️
crates/pop-contracts/src/errors.rs 100.00% <ø> (+100.00%) ⬆️
crates/pop-contracts/src/testing.rs 68.42% <68.42%> (ø)
crates/pop-cli/src/common/build.rs 73.07% <73.07%> (ø)
crates/pop-contracts/src/call.rs 81.22% <82.43%> (+2.60%) ⬆️
crates/pop-contracts/src/up.rs 80.33% <71.42%> (+2.44%) ⬆️
crates/pop-cli/src/cli.rs 67.36% <56.58%> (-4.89%) ⬇️
crates/pop-contracts/src/utils/metadata.rs 75.06% <75.06%> (ø)
crates/pop-cli/src/commands/call/contract.rs 83.33% <84.98%> (+83.33%) ⬆️

... and 7 files with indirect coverage changes

@AlexD10S AlexD10S marked this pull request as ready for review September 10, 2024 09:50
@AlexD10S AlexD10S added ready-for-final-review The PR is ready for final review ready-for-high-level-review The PR needs a high level review and removed ready-for-final-review The PR is ready for final review labels Sep 10, 2024
Copy link
Collaborator

@brunopgalvao brunopgalvao left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall it LGTM. Some feedback:

┌   Pop CLI : Call a contract
│
◇  Where is your project located?
│  ./
│
◇  Paste the on-chain contract address:
│  5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt
│
└  Unable to fetch contract metadata.
Error: Anyhow error: Cannot infer the root project id

It would be nice if “error” was not repeated. Something like this:

│
◇  Where is your project located?
│  ./
│
◇  Paste the on-chain contract address:
│  5DYs7UGBm2LuX4ryvyqfksozNAW5V47tPbGiVgnjYWCZ29bt
│
└  Unable to fetch contract metadata.
Error: Cannot infer the root project id

Same with this:

◇  Do you want to execute the call? (Selecting 'No' will perform a dry run)
│  Yes 
│
Error: Rpc error: RPC error: Error when opening the TCP socket: Connection refused (os error 61)

Caused by:
    RPC error: Error when opening the TCP socket: Connection refused (os error 61)

Change to this:

◇  Do you want to execute the call? (Selecting 'No' will perform a dry run)
│  Yes 
│
Error when opening the TCP socket: Connection refused (os error 61)

Caused by:
    RPC error: Error when opening the TCP socket: Connection refused (os error 61)

◇  Where is your project located?
│  ./flipper

Autocomplete when typing the contract's location would be useful.

Looking up RPC URL's can be cumbersome. It would be nice to be able to select commonly used RPC URL's such as Pop Network or localhost.

So this:

◇  Where is your contract deployed?
│  wss://rpc1.paseo.popnetwork.xyz

Could be something like this:

◆  Where is your contract deployed?
│  ● Pop Network TestNet (wss://rpc1.paseo.popnetwork.xyz)
│  ○ Local on Port 9944 (ws://localhost:9944) ○ Custom (Specify)

Change this:

◆  Do you want to do another call?
│  ○ Yes  / ● No 

To something like this:

◆  Do you want to do another call?
│  ● No
│  ○ Yes, with the same contract
│  ○ Yes, with another contract on the same chain

If this change will not be incorporated then at least don’t clear the screen when I say “yes” to call another contract so that I can copy the address and chain URL. Regardless, the default for pop call contract should be to never clear the screen when it is ran because I find it useful to have a “log” of calls that I have done either for copying info to use again or simply to remember the order of calls that I have done and with which parameters.

Change this:

◆  Select the message to call:
│  ● flip ( A message that can be called on instantiated contracts.  This one flips the value of the stored `bool` from `true`  to `false` and vice versa.)
│  ○ get 
└  

I would imagine these Rust Doc comments can easily be 4+ lines for a more sophisticated contract.
For example:

/// Use the new `call_v2` host function via the call builder to forward calls to
/// the other contract, initially calling `flip` and then `get` to return the
/// result.
///
/// This demonstrates how to set the new weight and storage limit parameters via
/// the call builder API.

Therefore I would have it be displayed underneath the function call making sure to preserve the same formatting that was presented in the Rust Doc comment e.g.:

◆  Select the message to call:
│  ● flip_and_get_invoke_v2_with_limits
|  
│   Use the new `call_v2` host function via the call builder to forward calls to
│   the other contract, initially calling `flip` and then `get` to return the
│   result.
│   
│   This demonstrates how to set the new weight and storage limit parameters via
│   the call builder API.
|  
│  ○ get 
└  

◇  Signer calling the contract:
│  //Alice

For a custom account, I was not sure what to input so I pasted my account’s address:

◇  Signer calling the contract:
│  16hkVKMehgVjTP7AsUvhU8rsYQhK9JDirPWWhpAwHaFTHRfc
Error: Failed to create keypair from URI: Cannot parse phrase: mnemonic has an invalid word count: 1. Word count must be 12, 15, 18, 21, or 24

It was not intuitive to me that it was expecting a mnemonic phrase like so:

│  car pocket team home penalty leg forget attend draft width blush lettuce

@AlexD10S AlexD10S marked this pull request as draft September 11, 2024 15:55
@AlexD10S
Copy link
Collaborator Author

AlexD10S commented Sep 13, 2024

Thanks for the feedback @brunopgalvao
Nice catch on the error messages.
I’ve improved the error message you mentioned, but error handling needs a proper refactor in all the project. Since it could become a larger task for this PR, I’ve opened a new issue to address it separately: #309

Great point about placing the message docs below the message name I’ve fixed that:

◆  Select the message to call:
│  ○ flip
 
│  ● get
 ( Simply returns the current value of our `bool`.)
│  ○ specific_flip
 
└ 

Same for clearing the screen when prompting for another call. I liked your idea of reusing the contract information, but I reduced it to two options instead of the three you suggested. If want to call another contract, the user should start again:

◆  Do you want to do another call using the existing smart contract?
│  ○ Yes  / ● No 

Autocomplete for the path and a list of addresses for me are nice-to-have features we can add in the future, feel free to create issues if you think they are needed.

As for the signer, I’m not sure how to improve that right now, but we’ll be working on a better process for signing transactions soon, so hopefully that will make things clearer.

Another piece of feedback from @peterwht that has been implemented is showing the user the call that will be executed after all the values are prompted:

⚙  pop call contract --path ./ --contract 5GumB5XDPMT7i1JAFTApxhEneWqRyLaBGdJb8CVXngb4ngTX --message get --url ws://localhost:9944/ --suri //Alice
│  
◐  Calling the contract...                                                                                                                                            ⚙  Result: Ok(false)

The option to skip the dry-run and submit the call directly is a new feature. I’d prefer to address this in a separate PR. I’ve opened an issue for it: #310.

@AlexD10S AlexD10S marked this pull request as ready for review September 13, 2024 09:45
@Daanvdplas
Copy link
Collaborator

Some thoughts;

  • Improving the amount of clicks required to call a contract for testing:
  1. when I was deploying my contract and testing it I don't want to think about the gas limit or proof size limit. So if we can call contracts with a flag that prevents always having to specify that it would be really nice and decreases the amount of clicks.
  2. The last question; whether or not to call the contract again, is unnecessary maybe, just immediately ask for which message to call again and if the user wants to stop it can simply do ctrl c. Again, the fewer clicks the better the devex tmo.
  3. The question whether or not to dry-run the call also seems unnecessary for testing. So perhaps a flag dev will prevent the weight limit question described in 1. and also this dry run question
  • Improving the functionality and usefulness of the pop cli:
    It would be amazing if we could return the value of the message, this is decoding it and printing it out in the console. Then the dev really doesn't have to use the contracts ui anymore. This is the successful return value, error type, even returning the event would be a requirement here I think.
  • Potential bugs:
    I couldn't deploy a new contract while using --salt.

@AlexD10S
Copy link
Collaborator Author

AlexD10S commented Nov 5, 2024

Some thoughts;

  • Improving the amount of clicks required to call a contract for testing:
  1. when I was deploying my contract and testing it I don't want to think about the gas limit or proof size limit. So if we can call contracts with a flag that prevents always having to specify that it would be really nice and decreases the amount of clicks.
  2. The last question; whether or not to call the contract again, is unnecessary maybe, just immediately ask for which message to call again and if the user wants to stop it can simply do ctrl c. Again, the fewer clicks the better the devex tmo.
  3. The question whether or not to dry-run the call also seems unnecessary for testing. So perhaps a flag dev will prevent the weight limit question described in 1. and also this dry run question
  • Improving the functionality and usefulness of the pop cli:
    It would be amazing if we could return the value of the message, this is decoding it and printing it out in the console. Then the dev really doesn't have to use the contracts ui anymore. This is the successful return value, error type, even returning the event would be a requirement here I think.
  • Potential bugs:
    I couldn't deploy a new contract while using --salt.

I like your idea to improving DevEx with less clicks. I’ve added the dev flag which for now skips the point 1 and 3. This provides a good starting point, and we can continue iterating from here @brunopgalvao thoughts?.

Regarding the --salt flag it works for me. Not it has to be an hex encoded value. I tried: --salt 0x6644a2ae6564ea712b23389008cf9e4fe635cd7d4e9b04dd4733eb614c4112ce (generated in https://ui.use.ink/instantiate) and --salt $(date +%s) (example form ink! docs https://use.ink/4.x/basics/cross-contract-calling#basiccontractref-walkthrough)

Copy link
Contributor

@evilrobot-01 evilrobot-01 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

High level review only. I think addressing the suggested changes around the call contract command can greatly improve the UX and the simplify the implementation.

A more flexible experience provides a greater experience. With very little refactoring, I can partially specify args on the command line e.g. 'pop call contract --contract xx' and the call UI can be used if message is ommitted.

That means I can enter stuff into the command which I dont want to repeat over and over, and then allow the terminal history to bring it up and to get me back to the bits I want to specify.

I feel this should be addressed prior to this being merged as it improves the UX significantly.

crates/pop-cli/src/commands/call/contract.rs Outdated Show resolved Hide resolved
crates/pop-cli/src/commands/call/contract.rs Outdated Show resolved Hide resolved
crates/pop-cli/src/commands/call/contract.rs Outdated Show resolved Hide resolved
crates/pop-cli/src/commands/call/contract.rs Outdated Show resolved Hide resolved
crates/pop-contracts/tests/files/testing.contract Outdated Show resolved Hide resolved
crates/pop-contracts/src/init_tests.rs Outdated Show resolved Hide resolved
crates/pop-contracts/src/init_tests.rs Outdated Show resolved Hide resolved
crates/pop-contracts/src/init_tests.rs Outdated Show resolved Hide resolved
crates/pop-contracts/src/lib.rs Outdated Show resolved Hide resolved
crates/pop-contracts/src/init_tests.rs Outdated Show resolved Hide resolved
@evilrobot-01
Copy link
Contributor

Running on the command on a fresh project, no build, results in the following:
image

@AlexD10S
Copy link
Collaborator Author

AlexD10S commented Nov 6, 2024

Running on the command on a fresh project, no build, results in the following: image

Good catch! We can apply the same logic as the pop up contract building it if it hasn't been built already.

AlexD10S and others added 2 commits November 6, 2024 19:44
Merged set_up_call_config and guide_user_to_call_contract into a single function. Also adds short symbols for arguments.
Copy link
Contributor

@al3mart al3mart left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now we assume that we are receiving a path to a contract project to load the metadata, and if not present build the contract to obtain the metadata.

We only need the metadata in order to call a contract, so it would be handy having both, an option for any user to point to their project and build the contract if necessary, or passing the location of the metadata directly to pop. Maybe the user only has access to the metadata.

crates/pop-contracts/src/call/metadata.rs Outdated Show resolved Hide resolved
AlexD10S and others added 2 commits November 22, 2024 14:12
* fix: automatically add some or none to Option argument

* test: refactor and tests

* refactor: improve code and comments

* fix: renaming and clean code

* chore: option params not mandatory

* fix: parse user inputs for Option arguments in constructor (#335)

* fix: automatically add some or none to Option argument

* fix: tests

* refactor: process_function_args

* test: update tests accordingly last changes

* fix: issue with delimiter

* test: fix unit test

* refactor: renaming and fix comments

* refactor: format types (#339)

Shows the full type representation, making it easier to see the entry format of parameter values.

* fix: logo doesn't show in README

---------

Co-authored-by: Frank Bell <[email protected]>
Co-authored-by: Alejandro Martinez Andres <[email protected]>
@AlexD10S AlexD10S removed the ready-for-final-review The PR is ready for final review label Nov 25, 2024
* chore: allow the user specify the metadata file to call a contract

* test: unit test to parse metadata from a file

* docs: fix docs

* refactor: ensure_contract_built after user input path

* fix: call contract when metadata file

* fix: remove default_input in contract address

* docs: rename metadata with artifact

* fix: panic at has_contract_been_built

* fix: clippy

* refactor: keep ensure_contract_built as a CallContractCommand function

* fix: ensure_contract_built

* docs: improve comments

* fix: feedback and include wasm file for testing
@al3mart
Copy link
Contributor

al3mart commented Nov 26, 2024

Good catch! We can apply the same logic as the pop up contract building it if it hasn't been built already.

A note on this one, @AlexD10S . Right now if we execute pop call contract on a contract that hasn't being built, and no metadata is provided either, the build starts right away which is great, but right after users are prompted for an address, as if the contract had been deployed already. At that point of the flow there's not much more users can do than killing the process.

@AlexD10S
Copy link
Collaborator Author

AlexD10S commented Nov 27, 2024

A note on this one, @AlexD10S . Right now if we execute pop call contract on a contract that hasn't being built, and no metadata is provided either, the build starts right away which is great, but right after users are prompted for an address, as if the contract had been deployed already. At that point of the flow there's not much more users can do than killing the process.

Interesting. After some discussions the best approach seems to be prompting the user after build the contract if the contract has already been deployed. There's a valid case where the contract hasn't been built locally, but the user still wants to interact with an already deployed contract, and the local build is only a way to get the artifacts.
If the user confirms, we continue; if they decline, we display a message prompting them to use pop up contract to deploy the contract.
Implementation in the last commit d71f35e

Copy link
Contributor

@al3mart al3mart left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After all the improvements and addressing all the feedback the call contract flow looks pretty nice!

I believe there's one new function that could be divided in two. But aside from that all is looking great. Thanks!

crates/pop-cli/src/commands/call/contract.rs Outdated Show resolved Hide resolved
Copy link
Contributor

@al3mart al3mart left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All is looking great! ❤️

Copy link
Contributor

@evilrobot-01 evilrobot-01 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whilst there are a lot of comments, most of them are minor. Once addressed I think we should be ready to merge.

@@ -1,5 +1,7 @@
// SPDX-License-Identifier: GPL-3.0

#[cfg(feature = "contract")]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the need for this module, which has a single function, when there is already a contracts module?

/// # Arguments
/// * `path` - An optional path to the project directory. If no path is provided, the current
/// directory is used.
pub fn has_contract_been_built(path: Option<&Path>) -> bool {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move to existing common/contracts module in the same crate. Can then remove this file completely.

/// directory is used.
pub fn has_contract_been_built(path: Option<&Path>) -> bool {
let project_path = path.unwrap_or_else(|| Path::new("./"));
let manifest = match from_path(Some(project_path)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be simplified with let Ok(manifest) = from_path(Some(project_path)) else { return false; };

/// The name of the contract message to call.
#[clap(long, short)]
message: String,
message: Option<String>,
/// The constructor arguments, encoded as strings.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// The constructor arguments, encoded as strings.
/// The message arguments, encoded as strings.

/// The constructor arguments, encoded as strings.
#[clap(long, num_args = 0..)]
#[clap(long, num_args = 0..,)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#[clap(long, num_args = 0..,)]
#[clap(short, long, num_args = 0..,)]

.collect())
}

/// Extracts the information of a smart contract constructor parsing the metadata file.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Extracts the information of a smart contract constructor parsing the metadata file.
/// Extracts the information of a smart contract constructor.

metadata -> contract artifacts

/// Extracts the information of a smart contract constructor parsing the metadata file.
///
/// # Arguments
/// * `path` - Location path of the project.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// * `path` - Location path of the project.
/// * `path` - Location path of the project or contract artifact.

}

/// Processes a list of argument values for a specified contract function,
/// wrapping each value in `Some(...)` or replacing it with `None` if the argument is optional
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// wrapping each value in `Some(...)` or replacing it with `None` if the argument is optional
/// wrapping each value in `Some(...)` or replacing it with `None` if the argument is optional.

/// wrapping each value in `Some(...)` or replacing it with `None` if the argument is optional
///
/// # Arguments
/// * `path` - Location path of the project.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// * `path` - Location path of the project.
/// * `path` - Location path of the project or contract artifact.


let value: BalanceVariant<<DefaultEnvironment as Environment>::Balance> =
parse_balance(&call_opts.value)?;

let contract: <DefaultConfig as Config>::AccountId = parse_account(&call_opts.contract)?;
// Process the argument values input by the user.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Process the argument values input by the user.
// Process the provided argument values.

From the perspective of a library crate, there is no guarantee that it has been input by a user. It could be read from a file as an example.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants