Skip to content

Conversation

tdelabro
Copy link
Collaborator

@tdelabro tdelabro commented Sep 16, 2025

Add a ContractAddress wrapper type that guarantee the felt it contains is a valid starknet contract address.
Contain some quality of like function for string conversion

Not a breaking change

Discussion:
@xJonathanLEI suggested we add ClassHash too
I chose to exclude addresses 0x0 and 0x1 from the normal ContractAddress flow. You can still build them with the unchecked one. Should we add constant ContractAddress::ZERO and ContractAddress::ONE to make those special case more explicit?
Do we want to allow them at all? Is there a risk to make some API unusable downstream if those value are entierly excluded?

@tdelabro tdelabro force-pushed the add-contract-address-type branch 2 times, most recently from bea90b3 to 02d6946 Compare September 16, 2025 15:15
Copy link
Collaborator

@gabrielbosio gabrielbosio left a comment

Choose a reason for hiding this comment

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

Some small comments

@gabrielbosio gabrielbosio self-requested a review September 23, 2025 21:50
@tdelabro
Copy link
Collaborator Author

@gabrielbosio do you have an opinion about the points I highlighted in the issue description?

@gabrielbosio
Copy link
Collaborator

@gabrielbosio do you have an opinion about the points I highlighted in the issue description?

It depends if we want to migrate the ContractAddress struct from the sequencer repo. If that's the case then the try_from method should only check for the upper bound like it's done here.

@tdelabro
Copy link
Collaborator Author

@dorimedini-starkware you are maintaining the sequencer repo?
What do you think?
Is there a reason not to have a common ContractAddress shared between the whole ecosystem (sequencer, starknet-rs, ...)

@dorimedini-starkware
Copy link
Collaborator

@dorimedini-starkware you are maintaining the sequencer repo? What do you think? Is there a reason not to have a common ContractAddress shared between the whole ecosystem (sequencer, starknet-rs, ...)

no reason I can think of, but this suggested implementation differs from ours in several ways, like:

  1. we have another indirection (ContractAddress(pub PatriciaKey(Felt)) vs. ContractAddress(Felt))
  2. we allow zero and one as valid addresses
  3. we use derive_more::Display and derive_more::Deref
  4. serde derives are not feature-gated

@tdelabro
Copy link
Collaborator Author

@dorimedini-starkware

  1. If I remember correctly the intermediate indirection to PatriciaKey is made useless by the fact that the internal value is pub. Which mean you can freely overstep the types restriction ContractAddress(PatriciaKey(<Some totally non valid Felt value>)). So by making the internal hidden, and using From/TryFrom rather than value.0 we will increase the type safety, without need for internal nesting of types
  2. This make sense. I believe we can ma them valid values, and just add a checking method is_regular_contract_address(&self) -> bool that allow for users to check if they are not receiving special address values. An alternative is to have a second type, in the same way we have NonZeroFelt, RegularContractAddress (or something). Maybe the best solution, as it is still optional, but provide the best protection for users willing to use it
  3. Sure I can add those quality of life impl
  4. You can just enable the feature and it will be the same. But it is a good practice to have non-core features requiring the import of depencies (like serde , num-traits, and so on) feature gated. It doesn't cost much and is a good practice.

@dorimedini-starkware
Copy link
Collaborator

@dorimedini-starkware

1. If I remember correctly the intermediate indirection to `PatriciaKey` is made useless by the fact that the internal value is `pub`. Which mean you can freely overstep the types restriction `ContractAddress(PatriciaKey(<Some totally non valid Felt value>))`. So by making the internal hidden, and using `From`/`TryFrom` rather than `value.0` we will increase the type safety, without need for internal nesting of types

2. This make sense. I believe we can ma them valid values, and just add a checking method `is_regular_contract_address(&self) -> bool` that allow for users to check if they are not receiving special address values. An alternative is to have a second type, in the same way we have `NonZeroFelt`, `RegularContractAddress` (or something). Maybe the best solution, as it is still optional, but provide the best protection for users willing to use it

3. Sure I can add those quality of life `impl`

4. You can just enable the feature and it will be the same. But it is a good practice to have non-core features requiring the import of depencies (like `serde` , `num-traits`, and so on) feature gated. It doesn't cost much and is a good practice.
  1. PatriciaKey is pub but it's internal Felt is not, and creating a PatriciaKey with an out of range Felt should panic. I'm not saying the safety changes, I'm saying changing this indirection will require wide refactors if and when we migrate our ContractAddress type
  2. .
  3. please do :)
  4. makes sense.

all in all I have no issue with this addition; it may take time for us to migrate to this new type though

@tdelabro
Copy link
Collaborator Author

@dorimedini-starkware so do you want this crate to also add a PatriciaKey type. Would it be helpful for you?

@dorimedini-starkware
Copy link
Collaborator

@dorimedini-starkware so do you want this crate to also add a PatriciaKey type. Would it be helpful for you?

I guess it would make the transition less painful, so sure.
We use PatriciaKey to describe an index in any of our state trees (only one of which is actually indexed by contract addresses) - the class tree and each storage tree also need to be bound by the patricia key limit

@tdelabro
Copy link
Collaborator Author

tdelabro commented Sep 30, 2025

@dorimedini-starkware going back to point 2.

using derive_more::Display, or manual impl end up being the same. So no worries here

Now regarding derive_more::Deref, we won't implement it and will ask you to use as_ref or into instead.
Indeed the Deref trait purpose is to create smart-pointers, to add on the native ownership semantic, not to perform implicit type conversion. I understand the attraction of having the compiler quietly do those for you, but this is not what this trait is designed for and can be surprising for the user.
It is a bit more verbose, but for the best.

feature = "parity-scale-codec",
derive(parity_scale_codec::Encode, parity_scale_codec::Decode)
)]
pub struct RegularContractAddress(ContractAddress);
Copy link
Collaborator

Choose a reason for hiding this comment

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

what is this ?

Copy link
Collaborator Author

@tdelabro tdelabro Oct 6, 2025

Choose a reason for hiding this comment

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

Addresses that are not 0x0 and 0x1. Most users facing application code should never interact with the protocol special addresses. If they do, it is because of a bug.
It can be enforced at the type level this way.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@tdelabro note that not just 0x1 is "reserved", see here

Copy link
Collaborator

@0xLucqs 0xLucqs Oct 8, 2025

Choose a reason for hiding this comment

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

sounds super confusing, why not add just a regular ContractAddress and have specific functions to create 0x1 etc etc ?

Copy link
Collaborator

Choose a reason for hiding this comment

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

or go the other way around and create a type ReservedContractAddresses

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@dorimedini-starkware thanks for the head ups. Those are not mention on the doc https://docs.starknet.io/learn/protocol/state#special-addresses

Copy link
Collaborator Author

@tdelabro tdelabro Oct 8, 2025

Choose a reason for hiding this comment

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

@0xLucqs the use case I envision is more the opposite of what you are describing.
I'm writing rust code that allows users to interact with some contracts deployed on chain.
By using RegularContractAddress instead of ContractAddress, I can guarantee that a bug where I send money to 0x0 or 0x1 due to some badly initialized value, or corrupted json deserialization, will never happen.
A good use case would be: any code that references a user account should use the most restrictive type of the two.

why not add just a regular ContractAddress and have specific functions to create 0x1

This is a good quality of like improvment (hardcoded const for reserved addresses). I will add it

Copy link
Collaborator

Choose a reason for hiding this comment

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

@dorimedini-starkware thanks for the head ups. Those are not mention on the doc https://docs.starknet.io/learn/protocol/state#special-addresses

yeah, we should probably link to blockifier versioned constants for this. 0x3 is reserved without use ATM, but it is possible we will also use 0x3 and even 0x4 in the future. versioned constants should be the point of truth

@dorimedini-starkware
Copy link
Collaborator

@dorimedini-starkware going back to point 2.

using derive_more::Display, or manual impl end up being the same. So no worries here

Now regarding derive_more::Deref, we won't implement it and will ask you to use as_ref or into instead. Indeed the Deref trait purpose is to create smart-pointers, to add on the native ownership semantic, not to perform implicit type conversion. I understand the attraction of having the compiler quietly do those for you, but this is not what this trait is designed for and can be surprising for the user. It is a bit more verbose, but for the best.

the point was to get a Felt by doing **address, but we can refactor usages, not an issue

/// https://docs.starknet.io/learn/protocol/state#special-addresses
/// https://github.com/starkware-libs/sequencer/blob/ecd4779abef7bf345938a69f18ef70b6239d3a50/crates/blockifier/resources/blockifier_versioned_constants_0_15_0.json#L92-L97
pub fn is_regular_contract_address(&self) -> bool {
self > &Felt::THREE && self < &PATRICIA_KEY_UPPER_BOUND
Copy link
Collaborator

Choose a reason for hiding this comment

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

maybe hard-coding Felt::THREE as the lower bound isn't ideal, consider defining RegularContractAddress::LOWER_BOUND or something

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@dorimedini-starkware
Can you explain this to me too:
https://github.com/starkware-libs/starknet-api/blob/91fcc564b125c16ada9d956dcbfa2628415ca011/src/core.rs#L120C1-L121C101

It makes it look like the max contract address is not PATRICIA_KEY_UPPER_BOUND, but PATRICIA_KEY_UPPER_BOUND - 256.
Can you confirm this? And give me an explanation on why so that I can add it to the impl and doc in this PR

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@dorimedini-starkware Do you have infos on this?

@tdelabro tdelabro force-pushed the add-contract-address-type branch from 794750f to 0ec371c Compare October 15, 2025 17:14
@tdelabro tdelabro force-pushed the add-contract-address-type branch from 0ec371c to 6143570 Compare October 15, 2025 17:23
@tdelabro tdelabro force-pushed the add-contract-address-type branch from 6143570 to 1a3ea40 Compare October 15, 2025 17:24
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.

4 participants