Skip to content

Commit

Permalink
feat: apply argument resolution to selectors
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
xJonathanLEI committed Oct 15, 2023
1 parent f379378 commit 884279e
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 26 deletions.
17 changes: 12 additions & 5 deletions book/src/argument-resolution.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 ...
```
55 changes: 41 additions & 14 deletions src/decode.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
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};

pub struct FeltDecoder<S> {
address_book_resolver: AddressBookResolver<S>,
}

#[derive(Clone, Copy)]
enum FallbackOption {
Address,
Selector,
None,
}

impl<S> FeltDecoder<S> {
pub fn new(address_book_resolver: AddressBookResolver<S>) -> Self {
Self {
Expand All @@ -21,7 +31,20 @@ where
S: ChainIdSource,
{
pub async fn decode_single_with_addr_fallback(&self, raw: &str) -> Result<FieldElement> {
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<FieldElement> {
let decoded = self.decode_inner(raw, FallbackOption::Selector).await?;

if decoded.len() == 1 {
Ok(decoded[0])
Expand All @@ -34,10 +57,14 @@ where
}

pub async fn decode(&self, raw: &str) -> Result<Vec<FieldElement>> {
self.decode_inner(raw, false).await
self.decode_inner(raw, FallbackOption::None).await
}

async fn decode_inner(&self, raw: &str, addr_fallback: bool) -> Result<Vec<FieldElement>> {
async fn decode_inner(
&self,
raw: &str,
fallback_option: FallbackOption,
) -> Result<Vec<FieldElement>> {
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:") {
Expand Down Expand Up @@ -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::<FieldElement>() {
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()),
},
}
}
}
Expand Down
9 changes: 4 additions & 5 deletions src/subcommands/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand Down Expand Up @@ -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() {
Expand Down
6 changes: 4 additions & 2 deletions src/subcommands/invoke.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -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 {
Expand Down

0 comments on commit 884279e

Please sign in to comment.