Giriş
2024 yılında Solana, Toplam Kilitli Değer (TVL) açısından dördüncü sıradaki halka açık blok zinciri konumuna yükselerek yatırımcıların ve geliştiricilerin ilgisini çekti.
BlockSec, Solana'nın temel kavramlarına ilişkin makaleler, Solana akıllı sözleşmeleri yazma eğitimleri ve Solana işlemlerini analiz etmeye yönelik rehberler içeren "Solana Simplified" serisini derledi. Bu serinin amacı, okuyucuların Solana ekosistemine hakim olmasına ve Solana üzerinde proje geliştirme ile işlem gerçekleştirme konusunda temel becerileri edinmesine yardımcı olmaktır.
Bu serinin ilk makalesinde, Solana ağının işleyiş mekanizması, hesap modeli ve işlemler dahil olmak üzere temel kavramlarını ele alarak doğru ve yüksek performanslı Solana sözleşmeleri yazmak için sağlam bir temel oluşturduk.
Bu makalede, makale yayınlama ve görüntüleme işlevi sunan bir Solana programı (yani bir Solana akıllı sözleşmesi) yazma sürecinde size rehberlik edeceğiz. Bu, ilk makaledeki kavramların pekiştirilmesine yardımcı olacak ve henüz ele almadığımız bazı Solana özelliklerini tanıtacaktır. İlgili program ve test kodu GitHub üzerinde yayımlanmıştır.
Ortamı Kurma
Not: Aşağıdaki komutlar yalnızca Ubuntu sistemini kapsamaktadır. Bazı komutlar Windows ve macOS sistemlerinde çalışmayabilir.
👉 Alternatif komutlar kullanabilir veya bunu çözmek için yerel geliştirme kurulumuna ve Solana CLI kurulumuna başvurabilirsiniz.
Solana programlarını yerel ortamda derleyip dağıtacağız. Solana program geliştirmeye başlamadan önce bazı komutlar ve bağımlılıklar yüklememiz gerekmektedir.
Rust
Solana programları büyük ölçüde Rust programlama diliyle yazılmaktadır; bu nedenle Rust'ı kurmak için aşağıdaki komutu çalıştırmamız gerekmektedir:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
node.js & TypeScript
Test betiği TypeScript ile yazılmıştır; bu nedenle Node.js ve TypeScript araç zincirini kurmamız gerekmektedir:
curl -fsSL https://deb.nodesource.com/setup_15.x | sudo -E bash -
sudo apt-get install -y nodejs npm
npm install -g typescript
npm install -g ts-node
Solana CLI
Ardından, cüzdan oluşturma ve sözleşme dağıtma gibi görevler için komutlar sağlayan Solana CLI aracını kurmamız gerekmektedir:
sh -c "$(curl -sSfL https://release.solana.com/stable/install)"
Değişiklikleri mevcut terminale uygulamak için aşağıdaki komutu çalıştırın:
source ~/.profile
Solana CLI'nin başarıyla kurulduğunu doğrulamak için solana --version komutunu çalıştırabilirsiniz:
$ solana --version
solana-cli 1.17.25 (src:d0ed878d; feat:3580551090, client:SolanaLabs)
Yerel Cüzdan
Ardından, yerel bir dosya sistemi cüzdanı oluşturmak için aşağıdaki komutu çalıştırın:
solana-keygen new
Bu komut varsayılan olarak ~/.config/solana/id.json konumunda bir özel anahtar dosyası oluşturur; bu dosyayı daha sonra test betiğinde kullanacağız. Oluşturulan adresi görmek için solana address komutunu kullanabilirsiniz:
$ solana address
9FcGDHvzs8Czo4B1pQq8GPdXfdGHqNqayvh6bu4hVGfy
Devnet
Program devnet üzerinde dağıtılacağından, Solana CLI'yi devnet'e geçirmek için aşağıdaki komutu çalıştırmamız gerekmektedir:
solana config set --url https://api.devnet.solana.com
SOL Token'ları
Sözleşme dağıtma ve test işlemleri gönderme ücretsiz olmadığından, biraz SOL token'ı talep etmemiz gerekmektedir. solana airdrop 2 komutunu çalıştırabilir veya doğrudan herkese açık web musluğundan token talep edebilirsiniz.
Programı Yazma
Tanıtacağımız program, kullanıcıların makale yayınlamasına ve yayınlanan tüm makaleleri listelemesine olanak tanır. Program üç tür talimatı işler:
- init talimatı: Makalelerin mevcut maksimum dizinini depolamak için bir veri hesabı oluşturarak programı başlatır.
- post talimatı: Yayınlanan bir makaleyi yeni bir veri hesabında depolar.
- list talimatı: Yayınlanan tüm makaleleri günlüğe yazdırır.
Programın yapısı aşağıdaki gibidir:
$ tree program/
program/
├── Cargo.lock
├── Cargo.toml
└── src
├── instructions
│ ├── init.rs
│ ├── list.rs
│ ├── mod.rs
│ └── post.rs
├── lib.rs
└── processor.rs
Cargo.toml, proje için harici kütüphaneleri belirtir:
[dependencies]
borsh = "0.10.0"
borsh-derive = "0.10.0"
solana-program = "=1.17.25"
Not: solana-program sürümü, yüklü Solana CLI sürümüyle eşleşmelidir (
solana --versionkullanarak bunu kontrol edebilirsiniz). Aksi takdirde derleme sırasında hatalarla karşılaşabilirsiniz.
Sözleşmenin giriş fonksiyonu processor.rs dosyasında tanımlanmıştır. Dosya, gerekli bağımlılıkları içe aktararak başlar (diğer Rust dosyalarındaki benzer kısımları atlayacağız):

Ardından sözleşmenin giriş fonksiyonunun tanımı gelir:

Programın 10. satırında, Solana kütüphanesinden entrypoint! makrosu, giriş fonksiyonunun process_instruction olduğunu belirtir. Bu fonksiyon üç parametre alır:
- program_id: Mevcut programın dağıtılmış adresi.
- accounts: Talimata dahil olan tüm hesaplar.
- instruction_data: Talimatları işlemek için kullanılan bayt dizisi.
process_instruction, talimat türünü belirlemek için instruction_data'nın ilk baytını çıkarır ve bunu işlemek için karşılık gelen fonksiyonu çağırır. Şimdi bu üç fonksiyonun nasıl uygulandığına bakacağız.
init
Üç fonksiyonun her biri, program/src/instructions dizininde aynı adı taşıyan bir dosyada tanımlanmıştır. init.rs ile başlayalım.

init talimatı ek bir bayt dizisi gerektirmediğinden, yalnızca iki parametre kabul eder: program_id ve accounts.
12 ile 15. satırlar arasında program, gerekli hesap bilgilerini sırayla çıkarır, ardından mevcut maksimum makale dizinini depolamak için kullanılan veri hesabının adresini hesaplamak üzere find_program_address fonksiyonunu çağırır: index_pda_key. Ardından program, index_pda_key'in index_pda adresiyle eşit olduğunu doğrular.
Burada index_pda'nın adresi, PDA'lar (Program Türetilmiş Adresler) olarak adlandırılan özel bir adrestir. Genel anahtarlardan üretilen "cüzdan" hesap adreslerinden farklı olarak, PDA'lar isteğe bağlı bir bayt dizisinden, "bump" adı verilen bir bayttan ve bir programın adresinden türetilir. Bayt dizisi ve program adresi çağıran tarafından açıkça sağlanırken, bump find_program_address fonksiyonu tarafından otomatik olarak üretilip döndürülür. Bump, bu şekilde oluşturulan adreslerin genel anahtarla oluşturulan adreslerle çakışmamasını sağlar.

index_pda'nın adresinin beklendiği onaylandıktan sonra, index_pda'yı oluşturmak için SystemProgram tarafından sağlanan create_account talimatını kullanırız. 22. satırda program, makalelerin mevcut maksimum dizinini kaydeden (0 olarak başlatılan) IndexAccountData türünde bir değişken oluşturur. Aşağıdaki şekilde gösterildiği gibi, bu tür BorshSerialize ve BorshDeserialize özelliklerini uygular; bu sayede Borsh formatında serileştirilebilir ve seriden çıkarılabilir:

23 ile 24. satırlar, hesap verilerini depolamak için gereken alanı ve hesabı oluşturmak için gereken minimum kira miktarını hesaplar.
27 ile 37. satırlar, SystemProgram'dan bir create_account talimatı oluşturur ve bunu invoke_signed yöntemi kullanarak çağırır. Bir program içinde başka bir programa talimat çağırma işlemi CPI (Çapraz Program Çağrısı) olarak adlandırılır. Solana'da CPI gerçekleştirmek için iki fonksiyon kullanabilirsiniz: invoke ve invoke_signed.
invoke fonksiyonu verilen talimatı doğrudan işler ve fonksiyon imzası şu şekildedir:
pub fn invoke(
instruction: &Instruction,
account_infos: &[AccountInfo<'_>]
) -> Result<(), ProgramError>
invoke_signed ile invoke arasındaki tek fark, invoke_signed'ın PDA hesapları için imzalama imkânı sunmasıdır. PDA hesaplarının genel-özel anahtar çifti bulunmadığından doğrudan imza sağlayamazlar. invoke_signed bu sorunu çözer. Bu fonksiyon, signers_seeds adlı ek bir parametre kabul eder. Fonksiyon imzası aşağıdaki gibidir.
find_program_address fonksiyonunun çalışma şekline benzer şekilde, invoke_signed, sağlanan signers_seeds ve çağıranın program_id'si temelinde bir dizi PDA adresi hesaplar. account_infos bu PDA adreslerini içeriyorsa, imza sağlamışlar gibi imzalayan olarak işaretlenirler.
pub fn invoke_signed(
instruction: &Instruction,
account_infos: &[AccountInfo<'_>],
signers_seeds: &[&[&[u8]]]
) -> Result<(), ProgramError>
Burada, create_account talimatı oluşturulan hesabın imzalayan olmasını gerektirdiğinden, index_pda için imza sağlamak amacıyla invoke_signed kullanmamız gerekmektedir.

Programın sonunda, sözleşme index_data üzerinde serialize yöntemini çağırarak veriyi serileştirir ve index_pda'nın data alanına yazar.
post_article

Şimdi post talimatının uygulamasına bakalım. init talimatından farklı olarak, post talimatı yayınlanan bir makaleyi kabul eder; bu nedenle makalenin serileştirilmiş verisini depolayan instruction_data_inner adlı ek bir parametreye sahiptir.
Her zamanki gibi program, talimat için gereken hesap bilgilerini çıkararak başlar. Her makale için ayrı bir PDA hesabı oluşturduğumuzdan, program 23. satırda new_article_pda adlı ek bir hesabı kabul eder.
init talimatına benzer şekilde, 27 ile 30. satırlar iletilen index_pda'nın adresinin doğru olduğunu doğrulamak için find_program_address kullanır. Ardından mevcut maksimum tahsis edilmiş dizini okumak için data alanındaki veriyi seriden çıkarırız.
33 ile 40. satırlar aynı zamanda new_article_pda'nın adresini de doğrular. Program, yeni bir adres oluşturmak için "ARTICLE_PDA" dizesini ve mevcut dizini kullanır. Dizin, her makale yüklenmesiyle birlikte artarak oluşturulan adresin benzersiz olmasını sağlar.

Ardından program, instruction_data_inner'ı PostArticleData türündeki makale verisine dönüştürmek için seriden çıkarır:

Bu yapının yalnızca iki alanı vardır: title ve content. 45 ile 49. satırlarda program, makalenin tek bir hesaba sığamayacak kadar büyük olmadığını doğrulamak için bir kontrol gerçekleştirir.

Sonraki adımlar da init talimatına benzerdir: Program önce hesap için gereken alanı ve kira miktarını hesaplar, ardından makaleyi depolamak üzere PDA hesabını oluşturmak için SystemProgram'ın create_account talimatını çağırır. Son olarak, yayınlanan makale new_article_pda'nın data alanına serileştirilir ve mevcut maksimum dizin bir artırılarak index_pda'nın data alanına serileştirilir.
list_articles

Son olarak, list talimatının uygulamasına bakalım. Bu talimat tüm makaleleri listelemesi gerektiğinden, 15. satırda makaleleri depolayan tüm PDA hesaplarını temsil etmek için bir vektör kullanılır ve index_pda hesabının adresi de doğrulanır.

Ardından program, vektörün boyutunun mevcut maksimum dizinle aynı olduğunu doğrular ve her hesabın adresinin doğruluğunu kontrol eder.

Programın sonunda, her hesap üzerinde yineleme yapılır ve seriden çıkarılan makale içeriği solana-program'dan msg! makrosu kullanılarak tek tek çıktılanır.
İşlem Testi
Sözleşmemizi test etmek için, depodaki client/main.ts konumunda bulunan bir TypeScript betiği kullanırız.

Betiğin başında tüm gerekli kütüphaneleri içe aktarır ve üç global sabit tanımlarız. KEYPAIR_PATH, solana-keygen new kullanılarak oluşturulan özel anahtar dosyasının yolunu belirtir; PROGRAM_ID, son bölümde yazdığımız dağıtılmış programın adresidir ve POST_ARTICLE_SCHEMA, makale serileştirmesi için kullanılan nesnedir.


Betiğin ana gövdesinde, önce özel anahtar dosyasını ayrıştırmak ve işlem ücretleri için ödeyici olarak kullanılacak bir Keypair nesnesi elde etmek üzere loadKeyFromFile fonksiyonu çağrılır. Ardından indexPda'nın adresini hesaplamak için @solana/web3.js tarafından sağlanan findProgramAddressSync yöntemi kullanılır. Bu yöntem, Rust sözleşmesiyle aynı algoritmayı kullanır ve aynı parametrelerle aynı adresi hesapladığını garanti eder. connection nesnesi, test için devnet kullanacağımızı belirtir.

Ardından ilk başlatma işlemini göndeririz. Tek bir talimat içeren bir işlem oluştururuz. Bu talimatın data alanı yalnızca bir "0" baytı içerir; bu, bunun init talimatına bir çağrı olduğunu gösterir. İşlem daha sonra sendAndConfirmTransaction kullanılarak gönderilir ve onaylanır.

İkinci işlem, bazı makaleler yayınlamak için post talimatını çağırır. 56 ile 63. satırlar arasında betik iki makaleyi serileştirir ve karşılık gelen PDA adreslerini hesaplar. Ardından bu makaleleri oluşturmak için tek bir işleme iki post talimatı eklenir.

Son işlem list talimatını çağırır ve işlem onaylandıktan sonra günlükler yazdırılır.
Programı betikle test etmek için, KEYPAIR_PATH'i özel anahtarınızın yoluyla değiştirin (varsayılan yol değilse). Ardından aşağıdaki komutları çalıştırarak Rust akıllı sözleşmesini derleyip dağıtın:
cd program
cargo build-bpf
solana program deploy ./target/deploy/hello_solana.so
Ardından dağıtılan adresi kopyalayıp PROGRAM_ID alanına yapıştırın. Projenin kök dizininde bağımlılıkları yüklemek için npm install, ardından betiği çalıştırmak için npm start komutunu çalıştırın.
Bundan sonra, test işlemlerinin yürütme ayrıntılarını görüntülemek için Solscan'ı kullanabiliriz.

Doğrudan makale yayınlamaya yönelik ikinci işleme bakalım. İki talimat içermekte olup her birinin Instruction Data Raw alanının ilk baytı 0x01'dir. Ayrıca her talimat, System Program'ın CreateAccount talimatına yapılan bir çağrı olan bir iç talimat içerir.

Benzer şekilde, tüm makaleleri listeleyen üçüncü işlemi inceleyebiliriz. Program Logs bölümünde, iki makalenin dizinlerinin, başlıklarının ve içeriklerinin yazdırıldığını görebiliriz.
Sonuç
Bu makalede, önce Solana programlama ve çalışma zamanı ortamının yerel olarak nasıl kurulacağını açıkladık. Ardından bir Solana sözleşmesinin uygulama mantığını ayrıntılı biçimde ele aldık. Son olarak bir TypeScript betiği kullanarak sözleşme işlevlerini test ettik. Bu adım adım eğitimle, basit bir Solana akıllı sözleşmesinin nasıl yazılacağını öğrendiğinize inanıyoruz 🥳.
Bir sonraki makalede, Phalcon Explorer kullanarak Solana işlemlerinin nasıl görüntüleneceği ve analiz edileceği konusunda ayrıntılı bir rehber sunacağız (Phalcon Explorer artık Solana'yı desteklemektedir). Bizi takip etmeye devam edin!



