From 884279eddbc1a06c4f47a411779d2d55a521424f Mon Sep 17 00:00:00 2001 From: Jonathan LEI Date: Sun, 15 Oct 2023 08:33:59 +0000 Subject: [PATCH] feat: apply argument resolution to selectors Adds a new scheme `selector:` that can be used anywhere as felt. Also allows using raw selector values for `invoke` and `call` when the original entrypoint name is unknown. --- book/src/argument-resolution.md | 17 +++++++--- src/decode.rs | 55 ++++++++++++++++++++++++--------- src/subcommands/call.rs | 9 +++--- src/subcommands/invoke.rs | 6 ++-- 4 files changed, 61 insertions(+), 26 deletions(-) diff --git a/book/src/argument-resolution.md b/book/src/argument-resolution.md index a4fb76d..40a3f32 100644 --- a/book/src/argument-resolution.md +++ b/book/src/argument-resolution.md @@ -29,18 +29,25 @@ The `const` scheme uses `content` as the key to look up a hard-coded table to co | `u256_max` | `0xffffffffffffffffffffffffffffffff, 0xffffffffffffffffffffffffffffffff` | | `felt_max` | `0x0800000000000011000000000000000000000000000000000000000000000000` | +### `selector` + +The `selector` calculates the _Starknet Keccak_ hash for the content to derive the function entryponit. + ## Scheme omission -Normally, the `scheme:` prefix is required for opting in to argument resolution. However, there's one exception: the `addr:` prefix can be omitted when an address is expected. +Normally, the `scheme:` prefix is required for opting in to argument resolution. However, there are a few exceptions: + +- the `addr:` prefix can be omitted when an address is expected; +- the `selector:` prefix can be omitted when a selector is expected. -As an example, consider the `starkli invoke` command. To use the `addr` scheme, one would run: +As an example, consider the `starkli invoke` command. To use the `addr` and `selector` schemes, one would run: ```console -starkli invoke addr:eth ... +starkli invoke addr:eth selector:transfer ... ``` -However, since the first positional argument for the `starkli invoke` is always expected to be an address, this command can be simplified into: +However, since the first positional argument for the `starkli invoke` is always expected to be an address, and the second one a selector, this command can be simplified into: ```console -starkli invoke eth ... +starkli invoke eth transfer ... ``` diff --git a/src/decode.rs b/src/decode.rs index 0589668..3875df9 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -1,6 +1,9 @@ use anyhow::Result; use num_bigint::BigUint; -use starknet::core::{types::FieldElement, utils::cairo_short_string_to_felt}; +use starknet::core::{ + types::FieldElement, + utils::{cairo_short_string_to_felt, get_selector_from_name}, +}; use crate::{address_book::AddressBookResolver, chain_id::ChainIdSource}; @@ -8,6 +11,13 @@ pub struct FeltDecoder { address_book_resolver: AddressBookResolver, } +#[derive(Clone, Copy)] +enum FallbackOption { + Address, + Selector, + None, +} + impl FeltDecoder { pub fn new(address_book_resolver: AddressBookResolver) -> Self { Self { @@ -21,7 +31,20 @@ where S: ChainIdSource, { pub async fn decode_single_with_addr_fallback(&self, raw: &str) -> Result { - let decoded = self.decode_inner(raw, true).await?; + let decoded = self.decode_inner(raw, FallbackOption::Address).await?; + + if decoded.len() == 1 { + Ok(decoded[0]) + } else { + Err(anyhow::anyhow!( + "expected 1 element but found {}", + decoded.len() + )) + } + } + + pub async fn decode_single_with_selector_fallback(&self, raw: &str) -> Result { + let decoded = self.decode_inner(raw, FallbackOption::Selector).await?; if decoded.len() == 1 { Ok(decoded[0]) @@ -34,10 +57,14 @@ where } pub async fn decode(&self, raw: &str) -> Result> { - self.decode_inner(raw, false).await + self.decode_inner(raw, FallbackOption::None).await } - async fn decode_inner(&self, raw: &str, addr_fallback: bool) -> Result> { + async fn decode_inner( + &self, + raw: &str, + fallback_option: FallbackOption, + ) -> Result> { if let Some(addr_name) = raw.strip_prefix("addr:") { Ok(vec![self.resolve_addr(addr_name).await?]) } else if let Some(u256_str) = raw.strip_prefix("u256:") { @@ -99,19 +126,19 @@ where } } else if let Some(short_string) = raw.strip_prefix("str:") { Ok(vec![cairo_short_string_to_felt(short_string)?]) + } else if let Some(selector) = raw.strip_prefix("selector:") { + Ok(vec![get_selector_from_name(selector)?]) } else { match raw.parse::() { Ok(value) => Ok(vec![value]), - Err(err) => { - if addr_fallback { - match self.resolve_addr(raw).await { - Ok(value) => Ok(vec![value]), - Err(_) => Err(err.into()), - } - } else { - Err(err.into()) - } - } + Err(err) => match fallback_option { + FallbackOption::Address => match self.resolve_addr(raw).await { + Ok(value) => Ok(vec![value]), + Err(_) => Err(err.into()), + }, + FallbackOption::Selector => Ok(vec![get_selector_from_name(raw)?]), + FallbackOption::None => Err(err.into()), + }, } } } diff --git a/src/subcommands/call.rs b/src/subcommands/call.rs index 6028d73..f1bb8b3 100644 --- a/src/subcommands/call.rs +++ b/src/subcommands/call.rs @@ -3,10 +3,7 @@ use std::sync::Arc; use anyhow::Result; use clap::Parser; use starknet::{ - core::{ - types::{BlockId, BlockTag, FunctionCall}, - utils::get_selector_from_name, - }, + core::types::{BlockId, BlockTag, FunctionCall}, providers::Provider, }; @@ -38,7 +35,9 @@ impl Call { let contract_address = felt_decoder .decode_single_with_addr_fallback(&self.contract_address) .await?; - let selector = get_selector_from_name(&self.selector)?; + let selector = felt_decoder + .decode_single_with_selector_fallback(&self.selector) + .await?; let mut calldata = vec![]; for element in self.calldata.iter() { diff --git a/src/subcommands/invoke.rs b/src/subcommands/invoke.rs index 831f248..3cca7ce 100644 --- a/src/subcommands/invoke.rs +++ b/src/subcommands/invoke.rs @@ -5,7 +5,7 @@ use clap::Parser; use colored::Colorize; use starknet::{ accounts::{Account, Call}, - core::{types::FieldElement, utils::get_selector_from_name}, + core::types::FieldElement, }; use crate::{ @@ -65,7 +65,9 @@ impl Invoke { .await?; let next_arg = arg_iter.next().ok_or_else(unexpected_end_of_args)?; - let selector = get_selector_from_name(&next_arg)?; + let selector = felt_decoder + .decode_single_with_selector_fallback(&next_arg) + .await?; let mut calldata = vec![]; for arg in &mut arg_iter {