About the events, is there a limit about the amount of members that can be a key?
Also, does it cost more to emit an event as key rather than data?
You can define them inside the contract module, but not under the impl marked as external
. Note that if they touch the state, then you need to pass a ContractState
argument. There is an advantage of defining such functions under an impl:
#[generate_trait]
impl PrivateFunctons of PrivateFunctionsTrait {
fn is_even(self: ContractState) {
self.counter.read() % 2 == 0
}
}
If you defined the above impl, then calling private functions would look like self.is_even()
.
Components are not yet finalized, but since the component’s storage will be explicit in your storage it can be based on name in the same way.
Typo, should be external(v0)
just like in the counter contract. However, please note that the components section is still not finalized; the goal here is to demonstrate what is planned and give an example of how we can achieve it.

About the events, is there a limit about the amount of members that can be a key?
Also, does it cost more to emit an event as key rather than data?
Good question! Currently, these are not priced. In the future, adding more keys/data will probably cost similarly to tx calldata (paying per byte), which today is not yet part of the fee mechanism (once it’s added, we can consider keys being slightly more expensive). Re the maximum number of keys, it is defined at the node level. Different nodes may work with different limits, but I don’t think you need a very large number here.
For those who don’t really care about the motivation and just want to get their code to compile, the docs offer a more concise guide for migrating.
@FeedTheFed sometimes we need a @view function to “mutate the state” (without committing it obviously) for the purpose of its calculations.
For example, is_valid_signature
needs to consider whether a time-locked remove_signer_with_etd
has expired - in which case is_valid_signature
will fail if it was called with the removed signer.
Similarly, another use-case is our “sign-later” multisig impl which needs to know whether a pending multi-sig request has expired
We implement this by running the time-locked request handler at the beginning of the @view
function - this brings the contract into the expected state, and then subsequent logic is run on that state.
I want to make sure this is still supported in the suggested changes
GM,
At the moment if I have a pure function, I’ll have to make such an interface:
#[starknet::interface]
trait SomeTrait<TContractState> {
fn some_fn(self: @TContractState, some_param: felt252) -> bool;
}
But if this function is pure (it doesn’t even require to read the contract state) it’ll just cluter the code by adding some overhead and could lead to some questions like
“Why do I have an argument that isn’t even used/useful?”
Is this intended?
Thx in advance
ATM, this can be generalized in the future. We simplified the emit syntax a bit by auto-implementing into
for the event’s structs, so emit can take the struct directly and it will be converted to the corresponding variant (see here).
I really appreciate the simplification about events!
Makes the code a lot more readable with complex events
And the functions implementation and type name could be anything, like PrivateFunctions/PrivateMethods/PrivateTrait/etc.? As in, does the trait that’s mentioned – PrivateFunctionsTrait – have to be explicitly defined anywhere or anything goes and these are just arbitrary chosen names?
When will components be released?
What is the recommended way to implement extensibility before that happens? It’s not clear how ContractState
from external libraries is explicitly passed in under the new syntax.
Anything goes, for private functions you can use the #[generate_trait]
attribute in order to avoid having to choose a name for the trait (note that you don’t HAVE to define a separate impl for them, it’s convenient though as it will allow using the dot operator with self.private_function
)
Aiming at <1 month from today. In the meantime, you can initialize ContractState unsafely as follows:
let state: OtherContractModule::ContractState = OtherContractModule::unsafe_new_contract_state();
let res = OtherContractModule::ExternalImplName::external_function_name(@state);
For functions that take state by ref define let mut state = ...
and pass it by ref. Note that it’s only a hackish way to do things before we have components.
Apologies for the late reply, really appreciate your feedback!
-
The events attribute are untouched for now, in the future, we can consider not having to explicitly deriving (99% of events will probably derive the Event trait rather than define a custom implementation).
-
We simplified the events emission syntax (there’s a generated
Into
impl from the structs to the Event variants).
Re components
-
Contract vs component - If you only want to use this logic as part of a larger context, then components are the thing for you. I think OZ standards are the classical example for components (I want my contract to be “ownable” or “upgradable”, I don’t want a new deployed contract, I just want the functionality)
-
We’re trying to keep the fancy generics visible to the developer to a minimum. In 1-2 weeks I’ll publish another post focused on components, and we can discuss the more mature design there. Would love to get your input then.
We use a quite chunky monolithic contract that calls out to other modules for logic whenever possible live on mainnet right now (Carmine governance).
Until components arrive, what’s the way to access the state of the contract from other modules? I can’t call .read / .write on the state obtained through unsafe_new_contract_state
since that uses InternalContractStateTrait which has the same name but is defined differently for every single type that resides in storage.
Is this really the best that can be done?
let state : ActualContract::ContractState = ActualContract::unsafe_new_contract_state();
let yay = ActualContract::storage_member_name::InternalContractStateTrait::read(@state.storage_member_name, param);
just a small clarification regarding internal functions.
Do you mean
impl PrivateFunctons of PrivateFunctionsTrait {
...
}
in the example you have ... PrivateFunctionsTrait` {
is
`
a typo ?
I have a couple of questions that involve the strategy through which the component implementation is able to retrieve a reference to it’s state.
- What happens when two components use the same name for their storage? (the name being the argument to
contract_state(name)
) - What if they use the same name for their storage but only have one entry in the contracts’ storage struct?
Example scenarios:
- I’m using two components which both inherit / depend on another base (stateful) component, and I’d like to keep their state separate.
- I might want to have the same component twice.
- I’m using two libraries with components and they use the same storage names.
- In the post you mention:
When we write
self.component_snap()
, the compiler looks for impls that have acomponent_snap
method whose first argument is of the same type asself
, in this caseTContractState
. Luckily, it finds it in theGetComponent<TContractState, OwnableStorage>
impl,I
, on whichOwnableImpl
depends. Again we were able to utilize standard Cairo 1.0 behavior in our smart contract syntax.
However the dependency is on GetComponent<TContractState, OwnableState>>
OwnableStorage vs Ownable State. Is this a typo, or is there something else that’s implicitly ensuring this depencency is present?
Good points!
- Each component will have a different address space (up to hash collisions); it will define an offset that will be used to hash addresses used from the component.
- A component instance must appear in your Storage struct. It affects your storage/events, so the Storage and Event types have to be changed accordingly. For example, I can have two instances of the same component, e.g. with
ownable1: OwnableStorage
andownable2: OwnableStorage
, where both are annotated with some attributed (this attribute will tell the Starknet plugin in the compiler to generate aHasComponent
Impl). - It’s a typo, should be OwnableState (from there you can access storage variables).
Hey, apologies for the delay, if you want to access storage directly it’s indeed a bit more cumbersome with unsafe contract state, but there’s a better option if you import the right trait.
Take this example, then you can replace:
let mut state = Governance::unsafe_new_contract_state();
let applied: felt252 = proposal_applied::InternalContractStateTrait::read(
@state.proposal_applied, prop_id
);
with
use governance::contract::Governance::proposal_appliedContractStateTrait;
...
let mut state = Governance::unsafe_new_contract_state();
let applied = state.proposal_applied.read(prop_id);
By importing the generated trait per storage variable, yo can access the unsafe state directly.