Komponen di dalam mesin
Komponen memberikan modularitas yang kuat pada kontrak Starknet. Tetapi bagaimana sebenarnya sihir ini terjadi di balik layar?
Bab ini akan menjelajahi secara mendalam tentang internal kompilator untuk menjelaskan mekanisme yang memungkinkan komposabilitas komponen.
Pengantar tentang Implementasi yang Dapat Ditanam
Sebelum mempelajari lebih dalam tentang komponen, kita perlu memahami implementasi yang dapat ditanam.
Sebuah implementasi dari trait antarmuka Starknet (ditandai dengan #[starknet::interface]
) dapat dibuat dapat ditanam. Implementasi yang dapat ditanam dapat disisipkan ke dalam kontrak apa pun, menambahkan titik masuk baru dan memodifikasi ABI dari kontrak tersebut.
Mari kita lihat contoh untuk melihat hal ini dalam aksi:
{{#include ../listings/ch99-starknet-smart-contracts/components/no_listing_01_embeddable/src/lib.cairo}}
Dengan menyisipkan SimpleImpl
, kita secara eksternal mengekspos ret4
dalam ABI kontrak.
Sekarang setelah kita lebih familiar dengan mekanisme penyisipan, kita dapat melihat bagaimana komponen membangun dari hal ini.
Di Dalam Komponen: Implementasi Generik
Ingatlah sintaks blok impl yang digunakan dalam komponen:
{{#include ../listings/ch99-starknet-smart-contracts/components/listing_01_ownable/src/component.cairo:impl_signature}}
Poin-poin kunci:
-
OwnableImpl
membutuhkan implementasi dari traitHasComponent<TContractState>
oleh kontrak yang mendasarinya, yang secara otomatis dihasilkan dengan macrocomponent!()
saat menggunakan sebuah komponen di dalam sebuah kontrak.Kompilator akan menghasilkan sebuah impl yang membungkus setiap fungsi di dalam
OwnableImpl
, mengganti argumenself: ComponentState<TContractState>
denganself: TContractState
, di mana akses ke status komponen dilakukan melalui fungsiget_component
dalam traitHasComponent<TContractState>
.Untuk setiap komponen, kompilator akan menghasilkan trait
HasComponent
. Trait ini mendefinisikan antarmuka untuk menjembatani antaraTContractState
sebenarnya dari kontrak generik, danComponentState<TContractState>
.// dihasilkan per komponen trait HasComponent<TContractState> { fn get_component(self: @TContractState) -> @ComponentState<TContractState>; fn get_component_mut(ref self: TContractState) -> ComponentState<TContractState>; fn get_contract(self: @ComponentState<TContractState>) -> @TContractState; fn get_contract_mut(ref self: ComponentState<TContractState>) -> TContractState; fn emit<S, impl IntoImp: traits::Into<S, Event>>(ref self: ComponentState<TContractState>, event: S); }
Dalam konteks kita,
ComponentState<TContractState>
adalah tipe yang khusus untuk komponen ownable, yaitu memiliki anggota berdasarkan variabel penyimpanan yang didefinisikan dalamownable_component::Storage
. Berpindah dariTContractState
generik keComponentState<TContractState>
akan memungkinkan kita untuk menyisipkanOwnable
ke dalam kontrak apa pun yang ingin menggunakannya. Arah sebaliknya (ComponentState<TContractState>
keContractState
) berguna untuk dependensi (lihat contoh implementasiUpgradeable
komponen yang bergantung pada implementasiIOwnable
di bagian Ketergantungan Komponen).Singkatnya, seseorang harus memikirkan implementasi dari
HasComponent<T>
di atas sebagai mengatakan: "Kontrak yang memiliki status T memiliki komponen yang dapat diupgrade". -
Ownable
dianotasi dengan atributembeddable_as(<nama>)
:embeddable_as
mirip denganembeddable
; ia hanya berlaku untukimpls
dari traitstarknet::interface
dan memungkinkan penyisipan implementasi ini di dalam modul kontrak. Dengan demikian,embeddable_as(<nama>)
memiliki peran lain dalam konteks komponen. Pada akhirnya, ketika menyisipkanOwnableImpl
ke dalam suatu kontrak, kita berharap mendapatkan sebuah impl dengan fungsi-fungsi berikut:{{#include ../listings/ch99-starknet-smart-contracts/components/listing_01_ownable/src/component.cairo:trait_def}}
Perhatikan bahwa meskipun dimulai dengan sebuah fungsi yang menerima tipe generik
ComponentState<TContractState>
, kita ingin berakhir dengan sebuah fungsi yang menerimaContractState
. Di sinilahembeddable_as(<nama>)
berperan. Untuk melihat gambaran keseluruhan, kita perlu melihat impl yang dihasilkan oleh kompilator karena adanya anotasiembeddable_as(Ownable)
:#[starknet::embeddable] impl Ownable< TContractState, +HasComponent<TContractState> , impl TContractStateDrop: Drop<TContractState> > of super::IOwnable<TContractState> { fn owner(self: @TContractState) -> ContractAddress { let component = HasComponent::get_component(self); OwnableImpl::owner(component, ) } fn transfer_ownership(ref self: TContractState, new_owner: ContractAddress ) { let mut component = HasComponent::get_component_mut(ref self); OwnableImpl::transfer_ownership(ref component, new_owner, ) } fn renounce_ownership(ref self: TContractState) { let mut component = HasComponent::get_component_mut(ref self); OwnableImpl::renounce_ownership(ref component, ) } }
Perhatikan bahwa berkat adanya impl dari
HasComponent<TContractState>
, kompilator dapat membungkus fungsi-fungsi kita dalam sebuah impl baru yang tidak secara langsung mengetahui tentang tipeComponentState
.Ownable
, yang nama kita pilih saat menulisembeddable_as(Ownable)
, adalah impl yang akan kita sisipkan di dalam sebuah kontrak yang menginginkan kepemilikan.
Integrasi Kontrak
Kita telah melihat bagaimana implementasi generik memungkinkan kegunaan ulang komponen. Selanjutnya, mari kita lihat bagaimana sebuah kontrak mengintegrasikan sebuah komponen.
Kontrak menggunakan alias impl untuk menginisialisasi implementasi generik komponen dengan tipe konkret ContractState
dari kontrak.
{{#include ../listings/ch99-starknet-smart-contracts/components/listing_01_ownable/src/contract.cairo:embedded_impl}}
Baris di atas menggunakan mekanisme penyisipan impl Cairo bersamaan dengan sintaks alias impl. Kita menginisialisasi OwnableImpl<TContractState>
yang generik dengan tipe konkret ContractState
. Ingatlah bahwa OwnableImpl<TContractState>
memiliki parameter impl generik HasComponent<TContractState>
. Implementasi dari trait ini dihasilkan oleh macro component!
.
Perhatikan bahwa hanya kontrak yang menggunakan yang dapat mengimplementasikan trait ini karena hanya kontrak yang mengetahui tentang kedua status kontrak dan status komponen.
Ini menyatukan semua hal untuk menyisipkan logika komponen ke dalam kontrak.
Simpulan Penting
- Impl yang dapat disisipkan memungkinkan penyisipan logika komponen ke dalam kontrak dengan menambahkan titik masuk dan memodifikasi ABI kontrak.
- Kompilator secara otomatis menghasilkan implementasi trait
HasComponent
saat sebuah komponen digunakan dalam sebuah kontrak. Ini menciptakan jembatan antara status kontrak dan status komponen, memungkinkan interaksi di antara keduanya. - Komponen mengemas logika yang dapat digunakan kembali secara generik, tanpa tergantung pada kontrak tertentu.
Kontrak mengintegrasikan komponen melalui alias impl dan mengaksesnya melalui trait
HasComponent
yang dihasilkan. - Komponen membangun dari impl yang dapat disisipkan dengan mendefinisikan logika komponen yang generik yang dapat diintegrasikan ke dalam setiap kontrak yang ingin menggunakan komponen tersebut. Alias impl menginisialisasi impl generik ini dengan tipe penyimpanan konkret kontrak.