Çağrı Verisi Optimizasyonu için Kısa ABI'ler
Giriş
Bu makalede iyimser toplamalar, onların işlem ücretleri ve bu farklı maliyet yapısının Ethereum Ana Ağı'ndakilere göre farklı şeyler için optimizasyon yapmamızı nasıl şart koştuğu hakkında bilgi edineceksiniz. Aynı zamanda bu optimizasyon işlemini nasıl uygulayacağınızı da göreceksiniz.
Bilgilendirme
Ben tam zamanlı bir "Optimism"(opens in a new tab) çalışanıyım, bu yüzden bu makaledeki örnekler Optimism üzerinde çalışabilecek örnekler olacaktır. Ancak, burada anlatacağım teknik diğer toplamalarda da işe yarayacaktır.
Terminoloji
Toplamalar üzerinde konuşurken üretim Ethereum Ağı olan Ana Ağ için "katman 1 (L1)" terimi kullanılacaktır. "Katman 2 (L2)" terimi ise toplama veya güvenliği L1'e dayanan fakat işlemlerinin çoğunu zincir dışında yapan her türlü sistem için kullanılacaktır.
L2 işlemlerinin maliyetlerini nasıl daha da azaltabiliriz?
İyimser toplamalar, insanların sonradan gözden geçirip durumun doğru olup olmadığını kontrol edebilmesi için tüm geçmiş işlemlerin kayıtlarını tutmalıdır. Verileri Ethereum Ana Ağı'na sokabilmenin en uygun yolu, onları çağrı verisi olarak yazmaktır. Bu çözüm, hem Optimism(opens in a new tab) hem de Arbitrum(opens in a new tab) tarafından tercih edilmiştir.
L2 işlemlerinin maliyeti
L2 işlemlerinin maliyetleri iki bileşenden oluşur:
- L2 işlemi, genelde çok ucuzdur
- L1 depolaması, Ana Ağ'ın gaz ücretlerine bağlıdır
Bunu yazarken, Optimism'de L2 gazının maliyeti 0,001 Gwei idi. L1 gazının maliyeti ise şu an yaklaşık 40 Gwei'dir. Güncel fiyatları buradan inceleyebilirsiniz(opens in a new tab).
Çağrı verisinin bir baytı 4 gaz (eğer sıfırsa) veya 16 gazdır (eğer farklı bir değerse). EVM'deki en pahalı işlemlerden biri, depolamaya yazmaktır. 32 baytlık bir kelimeyi L2'deki bir depoya yazmanın maksimum maliyeti 22100 gazdır. Şu anda bu 22,1 Gwei'ye tekabül ediyor. Yani eğer sıfır baytlık bir çağrı verisi tasarruf etmemiz, depolamaya 200 bayt bile yazsak hala kârda olabileceğimizi gösteriyor.
ABI
İşlemlerin büyük bir çoğunluğu, bir sözleşmeye dıştan sahiplenilmiş bir hesaptan erişir. Çoğu sözleşme Solidity ile yazılmıştır ve veri alanlarını uygulama ikili arayüzü (ABI)(opens in a new tab) ile uyumlu olacak şekilde yorumlar.
Bununla birlikte ABI, bir çağrı verisi baytının maliyetinin yaklaşık olarak dört aritmetik işlemle aynı olduğu L1 için tasarlanmıştır; bir çağrı verisi baytının bin aritmetik işlemden daha pahalı olduğu L2 için değil. Örneğin, bir ERC-20 transfer işlemini burada bulabilirsiniz(opens in a new tab). Çağrı verisi şu şekilde bölünür:
Bölüm | Uzunluk | Baytlar | Harcanan bayt | Harcanan gaz | Gereken bayt | Gereken gaz |
---|---|---|---|---|---|---|
Fonksiyon seçici | 4 | 0-3 | 3 | 48 | 1 | 16 |
Sıfırlar | 12 | 4-15 | 12 | 48 | 0 | 0 |
Varış adresi | 20 | 16-35 | 0 | 0 | 20 | 320 |
Miktar | 32 | 36-67 | 17 | 64 | 15 | 240 |
Toplam | 68 | 160 | 576 |
Açıklama:
- İşlem Seçici: Sözleşmenin 256'den az fonksiyonu var, yani bunları tek bir baytla ayrıştırabiliriz. Bu baytların değeri genelde sıfırdan farklıdır ve bu sebeple maliyetleri 16 gazdır(opens in a new tab).
- Sıfırlar: Bu baytlar her zaman 0'dır çünkü 20 baytlık bir adres onu tutabilmek için 32 baytlık bir kelimeye ihtiyaç duymaz. 4 gaz tutan 0 maliyetli baytlar (sarı kağıdı inceleyin(opens in a new tab), Ek G, sayfa 27,
G
txdatazero
değeri). - Miktar: Bu sözleşmede
decimals
değerinin on sekiz (normal değer) ve transfer edilecek maksimum jeton sayısının da 1018 olduğunu varsayarsak, maksimum 1036 gibi bir miktar elde ederiz. 25615 > 1036, yani 15 bayt yeterlidir.
L1 üzerinde harcanan 160 gaz normalde göz ardı edilebilir bir değerdir. Bir işlemin maliyeti en az 21.000 gazdır(opens in a new tab), yani ekstra %0,8'in bir önemi yoktur. Fakat L2'de işler biraz daha farklıdır. Buradaki işlem maliyetinin neredeyse tamamı işlemi L1'e yazmaktır. İşlem çağrı verisine ek olarak, 109 baytlık bir işlem başlığı vardır (varış adresi, imza vs.). Toplam maliyet 109*16+576+160=2480
kadardır ve bunun %65'ini boşa harcıyoruz.
Hedefi kontrol etmediğimiz durumlarda maliyetleri azaltma
Hedef sözleşme üzerinde kontrolünüz olmadığını varsayarsak, yine de buna(opens in a new tab) benzer bir çözüm yolu kullanabilirsiniz. Hadi ilgili dosyalara bir göz atalım.
Token.sol
Bu, hedef sözleşmedir(opens in a new tab). Bu, bir ek özellikle gelen standart bir ERC-20 sözleşmesidir. Bu faucet
, her kullanıcının kullanabilmek için biraz jeton almasını sağlar. Bu, üretim ERC-20 sözleşmesini gereksiz kılabilecek olsa da, ERC-20 sadece test yapmayı kolaylaştırmak amaçlı var olduğunda işleri gerçekten kolaylaştırıyor.
1 /**2 * @dev Gives the caller 1000 tokens to play with3 */4 function faucet() external {5 _mint(msg.sender, 1000);6 } // function faucetKopyala
Burada bu sözleşmenin dağıtılmış olduğu bir örneği görebilirsiniz(opens in a new tab).
CalldataInterpreter.sol
Bu, işlemlerin daha küçük çağrı verileriyle çağırması gereken sözleşmedir(opens in a new tab). Hadi satır satır inceleyelim.
1//SPDX-License-Identifier: Unlicense2pragma solidity ^0.8.0;345import { OrisUselessToken } from "./Token.sol";Kopyala
Nasıl çağırabileceğimizi bilmek için jeton işlevine ihtiyacımız var.
1contract CalldataInterpreter {23 OrisUselessToken public immutable token;Kopyala
Bizim vekil olduğumuz jetonun adresi.
12 /**3 * @dev Specify the token address4 * @param tokenAddr_ ERC-20 contract address5 */6 constructor(7 address tokenAddr_8 ) {9 token = OrisUselessToken(tokenAddr_);10 } // constructorTümünü gösterKopyala
Jetonun adresi belirtmemiz gereken tek parametredir.
1 function calldataVal(uint startByte, uint length)2 private pure returns (uint) {Kopyala
Çağrı verisinden bir değer okuyalım.
1 uint _retVal;23 require(length < 0x21,4 "calldataVal length limit is 32 bytes");56 require(length + startByte <= msg.data.length,7 "calldataVal trying to read beyond calldatasize");Kopyala
32 baytlık (256 bit) tek bir kelimeyi belleğe yükleyecek ve istediğimiz alanın bir parçası olmayan baytlardan kurtulacağız. Bu algoritma, 32 bayttan daha büyük değerler için işe yaramaz ve tabi ki çağrı verisini okuyup geçemeyiz. L1'de gaz tasarrufu için bu testleri atlamak gerekli olabilir fakat L2'de gaz oldukça ucuzdur ve düşünebileceğimiz her mantık kontrolünü yapabilmemize olanak sağlar.
1 assembly {2 _retVal := calldataload(startByte)3 }Kopyala
fallback()
(aşağıya bakın) çağrısından verileri kopyalayabilirdik, fakat EVM'nin derleme dili olan Yul(opens in a new tab)'u kullanmak daha kolaydır.
Burada, baytları okuyup yığına yerleştirmek için (startByte
ve startByte+31
) CALLDATALOAD işlem kodunu(opens in a new tab) kullanıyoruz. Genelde, Yul'daki bir işlem kodunun söz dizimi şu şekildedir: <opcode name>(<first stack value, if any>,<second stack value, if any>...)
.
12 _retVal = _retVal >> (256-length*8);Kopyala
Sadece en önemli length
baytları alanın bir parçasıdır, bu yüzden diğer verilerden kurtulmak için sağa kaydırma(opens in a new tab) kullanıyoruz. Bu işlem, değeri alanın sağına taşıma avantajını sağlıyor; yani değer çarpı 256something yerine değerin kendisini kullanmış oluyoruz.
12 return _retVal;3 }456 fallback() external {Kopyala
Bir Solidity sözleşmesine yapılan çağrı hiçbir işlev imzasıyla eşleşmezse, fallback()
fonksiyonunu(opens in a new tab) çağırır (bir tane olduğunu varsayarak). CalldataInterpreter
söz konusu olduğunda, başka bir external
veya public
işlev olmadığından her çağrı buraya ulaşır.
1 uint _func;23 _func = calldataVal(0, 1);Kopyala
Çağrı verisinin ilk baytını okuyun, bu bize fonksiyonu anlatır. Burada bir fonksiyonun ulaşılabilir olmamasının iki sebebi vardır:
pure
veyaview
olan fonksiyonlar, durumu değiştirmezler ve gaz maliyetleri yoktur (zincir dışı olarak çağrıldıklarında). O yüzden gaz maliyetini düşürmeye çalışmanın da bir anlamı yoktur.msg.sender
(opens in a new tab)'a bağımlı olan fonksiyonlar.msg.sender
'ın değeri, çağıranın değilCalldataInterpreter
'ın adresi olacaktır.
Malesef, ERC-20'nin özelliklerine bakıldığında(opens in a new tab) bu bize sadece bir fonksiyon bırakıyor: transfer
. Bu da bize 2 fonsiyon bırakıyor: transfer
(çünkü transferFrom
çağrısı yapabiliyoruz) ve faucet
(çünkü jetonları bizi kim çağırdıysa ona transfer edebiliyoruz).
12 // Call the state changing methods of token using3 // information from the calldata45 // faucet6 if (_func == 1) {Kopyala
faucet()
'a yapılan parametresiz bir çağrı.
1 token.faucet();2 token.transfer(msg.sender,3 token.balanceOf(address(this)));4 }Kopyala
token.faucet()
'i çağırdıktan sonra jetonlara sahip oluyoruz. Fakat vekil sözleşmesi olarak, jetonlara ihtiyaç duymuyoruz. Ama EOA (dışarıdan sahip olunan hesap) ya da bizi çağıran sözleşme duyuyor. Yani biz bizi kim çağırırsa ona tüm jetonlarımızı transfer ediyoruz.
1 // transfer (assume we have an allowance for it)2 if (_func == 2) {Kopyala
Jeton transferi iki parametreye ihtiyaç duyuyor: hedef adres ve miktar.
1 token.transferFrom(2 msg.sender,Kopyala
Kullanıcıların sadece kendi sahip oldukları jetonları transfer etmesine izin veriyoruz
1 address(uint160(calldataVal(1, 20))),Kopyala
Hedef adres, 1 numaralı baytta başlıyor (0 numaralı bayt fonksiyonun kendisi). Bir adres olarak uzunluğu 20 bayttır.
1 calldataVal(21, 2)Kopyala
Bu spesifik sözleşme için birinin isteyebileceği maksimum jeton sayısının 2 bayta sığacağını varsayıyoruz (65536'dan daha az).
1 );2 }Kopyala
Ortalama olarak bir transfer 35 bayt kadar çağrı verisi kaplar:
Bölüm | Uzunluk | Bayt |
---|---|---|
Fonksiyon seçici | 1 | 0 |
Varış adresi | 32 | 1-32 |
Miktar | 2 | 33-34 |
1 } // fallback23} // contract CalldataInterpreterKopyala
test.js
Bu Javascript birim testi(opens in a new tab) bize bu mekanizmayı nasıl kullanacağımızı (ve nasıl doğru çalışacağını onaylayacağımızı) gösteriyor. chai(opens in a new tab) and ethers(opens in a new tab) kısımlarını anladığınızı varsayıp sadece sözleşme için geçerli olan kısımları anlatacağım.
1const { expect } = require("chai");23describe("CalldataInterpreter", function () {4 it("Should let us use tokens", async function () {5 const Token = await ethers.getContractFactory("OrisUselessToken")6 const token = await Token.deploy()7 await token.deployed()8 console.log("Token addr:", token.address)910 const Cdi = await ethers.getContractFactory("CalldataInterpreter")11 const cdi = await Cdi.deploy(token.address)12 await cdi.deployed()13 console.log("CalldataInterpreter addr:", cdi.address)1415 const signer = await ethers.getSigner()Tümünü gösterKopyala
Her iki sözleşmeyi dağıtarak başlıyoruz.
1 // Get tokens to play with2 const faucetTx = {
Normalde işlem oluşturmak için kullandığımız yüksek seviyeli fonksiyonları (token.faucet()
gibi) kullanamıyoruz, çünkü biz ABI'yi uygulamıyoruz. Bunun yerine, işlemi kendimiz oluşturmalı ve sonrasında göndermeliyiz.
1 to: cdi.address,2 data: "0x01"
İşlem için temin etmemiz gereken 2 parametre var:
to
, hedef adres. Bu, çağrı verisi yorumlama sözleşmesidir.data
, gönderilecek çağrı verisi. Bir musluk çağrısı durumunda veri tek bayttır,0x01
.
12 }3 await (await signer.sendTransaction(faucetTx)).wait()
İmza sahibinin sendTransaction
yöntemini(opens in a new tab) çağırıyoruz. Çünkü hedefi çoktan belirledik (faucetTx.to
) ve artık imzalanacak olan işleme ihtiyacımız var.
1// Check the faucet provides the tokens correctly2expect(await token.balanceOf(signer.address)).to.equal(1000)
Burada bakiyeyi onaylıyoruz. view
fonsiyonlarında gaz tasarrufuna gerek yoktur, bu yüzden bunları sadece normal şekilde çalıştırıyoruz.
1// CDI'ye bir izin verin (onaylar vekalet edilemez)2const approveTX = await token.approve(cdi.address, 10000)3await approveTX.wait()4expect(await token.allowance(signer.address, cdi.address)).to.equal(10000)
Çağrı verisi yorumlayıcısına transferleri yapabilmesi için bir ödenek verin.
1// Transfer tokens2const destAddr = "0xf5a6ead936fb47f342bb63e676479bddf26ebe1d"3const transferTx = {4 to: cdi.address,5 data: "0x02" + destAddr.slice(2, 42) + "0100",6}
Bir transfer işlemi oluşturun. İlk bayt "0x02"dir ve ardından hedef adres gelir; son olarak da miktar bulunur (0x0100, ondalık olarak 256).
1 await (await signer.sendTransaction(transferTx)).wait()23 // Check that we have 256 tokens less4 expect (await token.balanceOf(signer.address)).to.equal(1000-256)56 // And that our destination got them7 expect (await token.balanceOf(destAddr)).to.equal(256)8 }) // it9}) // describeTümünü göster
Örnek
Bu dosyaları kendiniz çalıştırmadan çalışırken görmek istiyorsanız, şu bağlantıları izleyin:
-
OrisUselessToken
(opens in a new tab)'ın0x950c753c0edbde44a74d3793db738a318e9c8ce8
(opens in a new tab) adresine dağıtılması. CalldataInterpreter
(opens in a new tab)'ın0x16617fea670aefe3b9051096c0eb4aeb4b3a5f55
(opens in a new tab) adresine dağıtılması.faucet()
(opens in a new tab) çağrısı.OrisUselessToken.approve()
(opens in a new tab) çağrısı. Bu çağrı doğrudan jeton sözleşmesine gider, çünkü işlememsg.sender
'a dayanır.transfer()
(opens in a new tab) çağrısı.
Hedef sözleşmeyi kontrol ederken maliyeti azaltma
Eğer hedef sözleşme üzerinde gerçekten kontrolünüz varsa msg.sender
'i atlatabilen fonksiyonlar oluşturabilirsiniz. Çünkü bunlar çağrı verisi yorumlayıcısına güvenir. Burada bunun, control-contract
bölümü(opens in a new tab) içerisinde nasıl çalıştığına dair bir örnek görebilirsiniz.
Eğer sözleşme sadece harici sözleşmelere cevap veriyorsa, bunu sadece tek bir sözleşmeye sahip olarak halledebiliriz. Fakat bu birleştirilebilirliiği bozardı. Normal ERC-20 çağrılarına yanıt veren bir sözleşmeye ve küçük çağrı verilerine cevap verebilen başka bir sözleşmeye sahip olmak çok daha iyidir.
Token.sol
Bu örnekte, Token.sol
'u modifiye ediyoruz. Bu, bizim sadece vekilin çağırabileceği bir çok fonksiyona sahip olmamızı sağlıyor. İşte yeni bölümler:
1 // The only address allowed to specify the CalldataInterpreter address2 address owner;34 // The CalldataInterpreter address5 address proxy = address(0);Kopyala
ERC-2O sözleşmesi yetkili vekilin kimliğini bilmelidir. Fakat, oluşturucu içindeki bu değişkeni biz ayarlayamayız, çünkü değeri henüz bilmiyoruz. Bu sözleşme, vekil jetonun adresinin oluşturucusunda olmasını beklediğinden ilk somutlaştırılan sözleşmedir.
1 /**2 * @dev Calls the ERC20 constructor.3 */4 constructor(5 ) ERC20("Oris useless token-2", "OUT-2") {6 owner = msg.sender;7 }Kopyala
Vekili belirlemesine izin verilen tek adres olduğundan yaratıcının adresi (owner
) de burada depolanır.
1 /**2 * @dev set the address for the proxy (the CalldataInterpreter).3 * Can only be called once by the owner4 */5 function setProxy(address _proxy) external {6 require(msg.sender == owner, "Can only be called by owner");7 require(proxy == address(0), "Proxy is already set");89 proxy = _proxy;10 } // function setProxyTümünü gösterKopyala
Güvenlik kontrollerini atlayabildiği için vekilin ayrıcalıklı erişimi vardır. Vekile güvenebileceğimizden emin olmak için bu fonksiyonu sadece 1 kereliğine owner
'ın çağırmasına izin veriyoruz. proxy
'nin gerçek bir değeri olduğunda (sıfır dışında), o değer değişemez; sözleşme sahibi kötü niyetli olarak bunu değiştirmeye kalksa veya anımsatıcısı açığa çıksa bile hala güvendeyiz demektir.
1 /**2 * @dev Some functions may only be called by the proxy.3 */4 modifier onlyProxy {Kopyala
Bu modifier
fonksiyonudur(opens in a new tab) ve diğer fonksiyonların çalışma şeklini değiştirebilir.
1 require(msg.sender == proxy);Kopyala
İlk olarak, başkası tarafından değil, vekil tarafından çağrıldığımızı doğrulayalım. Eğer değilse, revert
kullanın.
1 _;2 }Kopyala
Doğrulayabiliyorsa, değiştirdiğimiz fonksiyonu çalıştıralım.
1 /* Proxy'nin hesaplar için gerçekten proxy yapmasına izin veren işlevler */23 function transferProxy(address from, address to, uint256 amount)4 public virtual onlyProxy() returns (bool)5 {6 _transfer(from, to, amount);7 return true;8 }910 function approveProxy(address from, address spender, uint256 amount)11 public virtual onlyProxy() returns (bool)12 {13 _approve(from, spender, amount);14 return true;15 }1617 function transferFromProxy(18 address spender,19 address from,20 address to,21 uint256 amount22 ) public virtual onlyProxy() returns (bool)23 {24 _spendAllowance(from, spender, amount);25 _transfer(from, to, amount);26 return true;27 }Tümünü gösterKopyala
Bunlar normalde mesajın doğrudan jeton aktaran veya bir ödeneği onaylayan kuruluştan gelmesini gerektiren üç işlemdir. Burada bu işlemlerin şu nitelikleri taşıyan vekil versiyonları mevcuttur:
- Başka hiç kimse kontrol sahibi olamasın diye
onlyProxy()
tarafından değiştirilmiş. - Normalde
msg.sender
olan adresi ekstra parametre olarak alan.
CalldataInterpreter.sol
Çağrı verisi yorumlayıcısı neredeyse yukardakiyle aynı olmasına rağmen şu noktada ayrışır: vekil fonksiyonlar msg.sender
parametresi alır ve transfer
için herhangi bir ödeneğe ihtiyaç yoktur.
1 // transfer (no need for allowance)2 if (_func == 2) {3 token.transferProxy(4 msg.sender,5 address(uint160(calldataVal(1, 20))),6 calldataVal(21, 2)7 );8 }910 // approve11 if (_func == 3) {12 token.approveProxy(13 msg.sender,14 address(uint160(calldataVal(1, 20))),15 calldataVal(21, 2)16 );17 }1819 // transferFrom20 if (_func == 4) {21 token.transferFromProxy(22 msg.sender,23 address(uint160(calldataVal( 1, 20))),24 address(uint160(calldataVal(21, 20))),25 calldataVal(41, 2)26 );27 }Tümünü gösterKopyala
Test.js
Az önceki test kodları ve aşağıdakinin arasında birkaç değişiklik vardır.
1const Cdi = await ethers.getContractFactory("CalldataInterpreter")2const cdi = await Cdi.deploy(token.address)3await cdi.deployed()4await token.setProxy(cdi.address)Kopyala
ERC-20 sözleşmesine hangi vekil sunucuya güveneceğini aktarmamız gerekir
1console.log("CalldataInterpreter addr:", cdi.address)23// Need two signers to verify allowances4const signers = await ethers.getSigners()5const signer = signers[0]6const poorSigner = signers[1]Kopyala
approve()
ve transferFrom()
'u kontrol edebilmek için ikinci bir imza sahibine ihtiyacımız var. Buna poorSigner
adını veriyoruz çünkü bizim jetonlarımızın hiçbirini almıyor (elbette ETH sahibi olmasına gerek yok).
1// Transfer tokens2const destAddr = "0xf5a6ead936fb47f342bb63e676479bddf26ebe1d"3const transferTx = {4 to: cdi.address,5 data: "0x02" + destAddr.slice(2, 42) + "0100",6}7await (await signer.sendTransaction(transferTx)).wait()Kopyala
ERC-20 sözleşmesi (cdi
) vekile güvendiğinden transferleri aktarmak için ödeneğe ihtiyaç duymayız.
1// approval and transferFrom2const approveTx = {3 to: cdi.address,4 data: "0x03" + poorSigner.address.slice(2, 42) + "00FF",5}6await (await signer.sendTransaction(approveTx)).wait()78const destAddr2 = "0xE1165C689C0c3e9642cA7606F5287e708d846206"910const transferFromTx = {11 to: cdi.address,12 data: "0x04" + signer.address.slice(2, 42) + destAddr2.slice(2, 42) + "00FF",13}14await (await poorSigner.sendTransaction(transferFromTx)).wait()1516// Check the approve / transferFrom combo was done correctly17expect(await token.balanceOf(destAddr2)).to.equal(255)Tümünü gösterKopyala
İki yeni fonksiyonu test edin. transferFromTx
öğesinin iki adres parametresi gerektirdiğini unutmayın: ödeneği veren ve alıcı.
Örnek
Bu dosyaları kendiniz çalıştırmadan çalışırken görmek istiyorsanız, şu bağlantıları izleyin:
OrisUselessToken-2
(opens in a new tab)'ın0xb47c1f550d8af70b339970c673bbdb2594011696
(opens in a new tab) adresine dağıtılması.CalldataInterpreter
'ın(opens in a new tab)0x0dccfd03e3aaba2f8c4ea4008487fd0380815892
(opens in a new tab) adresine dağıtılması.setProxy()
(opens in a new tab) çağrısı.faucet()
(opens in a new tab) çağrısı.transferProxy()
(opens in a new tab) çağrısı.approveProxy()
(opens in a new tab) çağrısı.transferFromProxy()
(opens in a new tab) çağrısı. Bu çağrının diğerlerinden farklı bir adresten geldiğini de unutmayın;poorSigner
yerinesigner
.
Sonuç
Hem Optimism(opens in a new tab) hem de Arbitrum(opens in a new tab), L1'e yazılan çağrı verilerinin boyutunu ve dolayısıyla işlem maliyetlerini azaltmanın yollarını aramaktadır. Fakat altyapı sağlayıcıları genel çözümler arıyorken, bizim yapabileceklerimiz sınırlıdır. Merkeziyetsiz uygulama geliştiricisi olarak uygulamaya özel bilgilere sahipsiniz. Bu da sizin çağrı verilerinizi bizim genel bir çözümle yapabileceğimize göre çok daha iyi optimize edebilmenizi mümkün kılar. Umarım bu makale, ihtiyaçlarınız için ideal çözümler bulmanıza yardımcı olur.
Son düzenleme: @Shiva-Sai-ssb(opens in a new tab), 30 Haziran 2024