-
Notifications
You must be signed in to change notification settings - Fork 120
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
Enhancing Access to Extrinsic Operations through an API #1213
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To make this fully usable, we'd later also need to a way to get back the result (instead of just having it printed out). Since many of the run
function already seem to return a result, currently the Ok variant is just a unit, this could be done in a follow up, right? @ascjones WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As @xermicus alludes to, the issue is that the top level clap commands are tightly coupled to the invocation via a CLI (e.g. with user prompts). So I'm not sure whether it makes sense to make these commands the public API of this crate.
Another way to approach it might be to move the top level commands back to the cargo-contract
crate, and make a finer grained API that does the transcoding and node interactions, and any CLI specific stuff (any stdin
/stdout
) IO be done
What I did?
Why?
|
That looks better now, but I'm still missing a few things. Let's take the This method creates the Also, I do think the printing logic should be factored out further to go back into cargo contract. If I have my own tool like this:
This prints: But I might not have such a flag. So this is specific to Maybe we can even make the the Small nitpicks on the builder I noticed:
|
Having I propose having a method
|
For the This makes me think we can do the same for other commands for consistency and since this is a cleaner way of handling preprocessing. Taking
pub struct UploadExec {
upload_params: UploadCommand,
code: WasmCode,
signer: PairSigner,
}
This is another approach to the api to what I implement now in this PR. Which do you think makes a better API? |
This sounds good to me. So the general idea is that the Additionally, regarding the printing. As we just discussed, it's probably better to move the Also the |
eb15bab
to
ac6ef09
Compare
use contract_extrinsics::{ExtrinsicOpts, UploadCommand};
#[tokio::main]
async fn main() {
let opts = ExtrinsicOpts::new()
.file("flipper.contract")
.suri("//Alice")
.done();
let upload = UploadCommand::new().extrinsic_opts(opts).done().await;
let result = upload.upload_code_rpc().await.unwrap().unwrap();
println!(
"Code uploaded successfully.\nCode hash: {:?}",
result.code_hash
);
} or use contract_extrinsics::{ExtrinsicOpts, UploadCommand};
#[tokio::main]
async fn main() {
let opts = ExtrinsicOpts::new()
.file("flipper.contract")
.suri("//Alice")
.done();
let upload = UploadCommand::new().extrinsic_opts(opts).done().await;
let upload_result = upload.upload_code().await.unwrap();
if let Some(code_stored) = upload_result.code_stored {
println!(
"Contract uploaded successfully.\nCode hash: {:?}",
code_stored.code_hash
);
} else {
println!("This contract has already been uploaded");
}
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That looks much better now, thanks! Already makes re-using it much easier.
I gave a first round of review on the updated version. @ascjones please have a look too once you are back and have the time for it.
I've written two new tests for the updated API, which mirror existing tests focused on the entire lifecycle of cargo contract extrinsics. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is still some CLI specific code in extrinsics
. Is the end purpose of the libraray to be a pure "invoke contract extrinsics" crate? Or "invoke contract extrinsics" plus convenience CLI I/O methods for building your own CLI?
If we went full pure extrinsics invocation there could be even more separation, even moving the clap
commands back to cargo-contract
, which would involve conversion to extrinsics commands but would make clear the separation of concerns.
Regarding |
The tradeoff there is that now those helpers become part of the API to the crate, so people will start depending on them and it is less easy to do refactoring or change the way they behave without the possibility of breaking somebody's downstream code. So possibly introducing a higher maintenance cost. |
@tareknaser if you move the CLI structs parsed with |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the integration tests module, there is #[cfg(any(feature = "integration-tests", feature = "test-ci-only"))]
, so you need to add these features to the Cargo.toml
of the extrinsics library.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks much better now.
@ascjones I'll wait for your review before merging
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, nearly there. I would like all those unwrap
s gone from the done
methods then we can merge this 👍
Signed-off-by: Tarek <[email protected]>
Signed-off-by: Tarek <[email protected]>
Signed-off-by: Tarek <[email protected]>
Signed-off-by: Tarek <[email protected]>
Signed-off-by: Tarek <[email protected]>
Signed-off-by: Tarek <[email protected]>
Signed-off-by: Tarek <[email protected]>
Signed-off-by: Tarek <[email protected]>
Signed-off-by: Tarek <[email protected]>
Signed-off-by: Tarek <[email protected]>
Signed-off-by: Tarek <[email protected]>
Signed-off-by: Tarek <[email protected]>
Signed-off-by: Tarek <[email protected]>
Signed-off-by: Tarek <[email protected]>
Signed-off-by: Tarek <[email protected]>
Signed-off-by: Tarek <[email protected]>
Signed-off-by: Tarek <[email protected]>
pub async fn estimate_gas(&self) -> Result<Weight> { | ||
match (self.gas_limit, self.proof_size) { | ||
(Some(ref_time), Some(proof_size)) => { | ||
Ok(Weight::from_parts(ref_time, proof_size)) | ||
} | ||
Err(ref err) => { | ||
let object = ErrorVariant::from_dispatch_error(err, &client.metadata())?; | ||
if self.output_json { | ||
Err(anyhow!("{}", serde_json::to_string_pretty(&object)?)) | ||
} else { | ||
name_value_println!("Result", object, MAX_KEY_COL_WIDTH); | ||
display_contract_exec_result::<_, MAX_KEY_COL_WIDTH>(&call_result)?; | ||
Err(anyhow!("Pre-submission dry-run failed. Use --skip-dry-run to skip this step.")) | ||
_ => { | ||
let call_result = self.call_dry_run().await?; | ||
match call_result.result { | ||
Ok(_) => { | ||
// use user specified values where provided, otherwise use the | ||
// estimates | ||
let ref_time = self | ||
.gas_limit | ||
.unwrap_or_else(|| call_result.gas_required.ref_time()); | ||
let proof_size = self | ||
.proof_size | ||
.unwrap_or_else(|| call_result.gas_required.proof_size()); | ||
Ok(Weight::from_parts(ref_time, proof_size)) | ||
} | ||
Err(ref err) => { | ||
let object = ErrorVariant::from_dispatch_error( | ||
err, | ||
&self.client.metadata(), | ||
)?; | ||
Err(anyhow!("Pre-submission dry-run failed. Error: {}", object)) | ||
} | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function is duplicate code in call and instantiate
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Although these functions do have similarities, they use different dry-run methods and object-specific information. Trying to combine them with a macro or common function might make the code more complex.
Do you have any suggestions or ideas for a simpler solution?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, the matching on the result could go into a helper just like that. Everything could go into a helper if Exec
was a trait instead. But this might require a bit more careful design. Also it's not the end of the world for me . Let's not bike shed the PR forever. The PR is already a good move into the right direction, we can still improve from here.
@tareknaser CI failed |
Signed-off-by: Tarek <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
Just missing some file headers on the new files.
Signed-off-by: Tarek <[email protected]>
Thank you @tareknaser, good stuff! |
Description
This pull request addresses limitations that hindered integration of the extrinsics into other projects. The existing commands were primarily designed for
ink!
contracts and lacked the necessary flexibility for customization in different project contexts.To overcome these challenges, this pull request introduces changes that enhance the crate's usability, and integration capabilities.
Related issue #1195
Changes:
Default
forExtrinsicOpts
to simplify their usage throughout the codebase.