1. Anasayfa
  2. Solidity

Solidity Eğitimi: ABI hakkında her şey

Solidity Eğitimi: ABI hakkında her şey
ABI hakkında her şey
0

Bu makalede, Solidity’de ABI’nin ne olduğunu, akıllı sözleşmeleri tanımlamaya nasıl yardımcı olduğunu ve EVM bayt koduna nasıl çevrildiğini açıklıyoruz. ABI spesifikasyonuna göre kodlama + kod çözme için birkaç yerleşik Solidity yöntemine de bakacağız.

Solidity Eğitimi: ABI hakkında her şey

  • Akıllı Sözleşmeler ABI
    • ABI vs Bayt Kodu
    • ABI = Kodlama ve kod çözme belirtimi
  • Bir sözleşmenin JSON ABI’sini anlama
    • işlevler için JSON ABI belirtimi
    • Olaylar için JSON ABI belirtimi
  • ABI + Solidity — kodlama verileri
  • ABI + Solidity — Standart Olmayan / Paketlenmiş kodlama
  • ABI + Solidity — fonksiyon çağrılarını kodlama
    • abi.encodeWithSelector(…)
    • abi.encodeWithSignature(…)
  • Solidity’yi ABI türleriyle eşleştirme
  • Referanslar

Akıllı Sözleşme ABI

ABI ve Bayt Kodu

Akıllı sözleşmeler çok rahat küçük varlıklardır. Hala ağda, blok zincirinin güneşi altında, çağrılmayı bekliyorlar.

Kendi başlarına anlamsızdırlar ve pek bir işe yaramazlar. Ama onların “zekalarını” göz ardı etmemek gerekir! Aslında, onlara “akıllı sözleşme” unvanını kazandıran şey, ilişkili bayt kodlarıdır.

Bir ağda akıllı bir sözleşme kurulduğunda, bayt kodu adresiyle ilişkilendirilir ve blok zincirinde depolanır. Daha kesin olmak gerekirse, akıllı bir sözleşmenin bayt kodu, akıllı sözleşme adresinin “kod” alanı altında dünya durumunda saklanır.

Anlamak için burada pratik yapalım ve Ethereum Ana Ağında UniswapV3Factory akıllı sözleşme adresinin arkasındaki bayt kodunu görelim! Aşağıdaki Javascript ve web3.js kod parçacığı bunu yapmanızı sağlayacaktır:

const Web3 = require("web3");
const provider = "YOUR_INFURA_OR_QUICKNODE_HTTP_ENDPOINT";
const web3 = new Web3(provider);
const UniswapV3Factory = "0x1F98431c8aD98523631AE4a59f267346ea31F984";
web3.eth.getCode(UniswapV3Factory).then(console.log);
> 0x608060405234801561001057600080fd5b506004361061007d5760003560e01c8063890357301161005b578063890357301461013b5780638a7c195f146101855780638da5cb5b146101b0578063a1671295146101b85761007d565b806313af4035146100825780631698ee82146100aa57806322afcccb14610102575b600080fd5b6100a86004803603602081101561009857600080fd5b50356001600160a01b03166101f4565b005b6100e6600480360360608110156100c057600080fd5b5080356001600160a01b03908116916020810135909116906040013562ffffff16610267565b604080516001600160a01b039092168252519081900360200190f35b6101246004803603602081101561011857600080fd5b503562ffffff16610293565b6040805160029290920b8252519081900360200190f35b6101436102a8565b604080516001600160a01b0396871681529486166020860152929094168383015262ffffff16606083015260029290920b608082015290519081900360a00190f35b6100a86004803603604081101561019b57600080fd5b5062ffffff813516906020013560020b6102de565b6100e66103a1565b6100e6600480360360608110156101ce57600080fd5b5080356001600160a01b03908116916020810135909116906040013562ffffff166103b0565b6003546001600160a01b0316331461020b57600080fd5b6003546040516001600160a01b038084169216907fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c90600090a3600380546001600160a01b0319166001600160a01b0392909216919091179055565b60056020908152600093845260408085208252928452828420905282529020546001600160a01b031681565b60046020526000908152604090205460020b81565b600054600154600280546001600160a01b03938416939283169281169162ffffff600160a01b83041691600160b81b9004900b85565b6003546001600160a01b031633146102f557600080fd5b620...

Akıllı bir sözleşmenin bayt kodunu onun “beyni” olarak düşünün. Sözleşme mantığını makine kodu biçiminde açıklar. Bu makine kodu, Solidity gibi üst düzey bir akıllı sözleşme programlama dilinin yürütülebilir bir makine diline derlenmesiyle elde edilir: EVM bayt kodu.

Yukarıdaki EVM bayt kodu, onaltılık olarak yazılmış bir dizi EVM işlem kodundan başka bir şey değildir.

Bu bizi 1. sorunla baş başa bırakır: yürütülebilir kod makine kodudur. İnsan tarafından okunabilir değil, yalnızca makine tarafından okunabilir. Ve sadece özel bir makine (EVM, Ethereum Sanal Makinesi) onu anlayabilir ve nasıl çalıştırılacağını bilir.

Bunların hepsi EVM için iyi. Peki ya insanlar için? İnsanlar için, sözleşme bayt kodu herhangi bir bağlam sağlamaz (bir C++ dosyasının ikili yürütülebilir dosyası gibi).

Sahip olduğumuz 2. sorun, akıllı sözleşmelerin, mantıklarını tetikleyip yürütebilmeleri için onlarla etkileşime girmediğiniz sürece pek bir faydası olmamasıdır. Ancak akıllı sözleşmeleri blok zincirinde yürütülebilir yapan şey, onların gerçek bayt kodlarıdır.

Burada, yukarıdaki 2 problemin birbirini beslediği çelişkili bir mantıkla sonuçlandığımızı görebiliriz.

Bu kendi kendine beslenme sorununun çözümü, Solidity belgelerinde şu şekilde açıklanan ABI’de yatmaktadır:

“ABI, Ethereum ekosistemindeki sözleşmelerle etkileşim kurmanın standart yoludur. Hem blok zincirinin dışından hem de sözleşmeden sözleşmeye etkileşim için”.

ABI’deki “I”, Arayüz anlamına gelir. Bu, verilen girdileri EVM için “makine tarafından okunabilir” bir biçime çevirerek bir sözleşmenin EVM bayt koduyla iletişimi sağlayan şeydir.

ABI’yi bir insan için akıllı sözleşmeyi tanımlamanın daha basit ve daha anlaşılır bir yolu olarak düşünün. Akıllı bir sözleşmenin ABI’si, genel arayüzünü ve onunla nasıl etkileşime girileceğini açıklayacaktır.

Hangi işlevleri çağırabileceğinizi tanımlar ve işlevin beklediğiniz biçimde veri döndürmesini garanti eder.

ABI = kodlama + kod çözme özelliği

Daha önce ABI’nin bir istemci (doğrudan bir EOA’dan veya bir arayüzden) ile bir akıllı sözleşme bayt kodu (EVM işlem kodlarındaki sözleşme mantığı) arasındaki etkileşim bağlantısını oluşturan şey olduğunu görmüştük.

Ancak ABI sadece bu iki katman (insan ve EVM) arasındaki bağlantı değildir. En önemlisi, ABI, verilerin ve sözleşme çağrılarının nasıl kodlanacağına ve kodunun çözüleceğine ilişkin net spesifikasyonları tanımlar.

Bu nedenle, Ethereum ve herhangi bir EVM tabanlı zincirde, ABI temelde EVM için sözleşme çağrılarının nasıl kodlandığıdır (böylece EVM hangi talimatların çalıştırılacağını anlar).

Aynı şey geriye doğru gider. ABI, işlemlerde belirtilen tüm veriler ham onaltılık olarak kodlandığından, işlemlerden verilerin nasıl okunacağını ve kodunun çözüleceğini belirtir.

Bu nedenle, ABI, makine kodunun içine/dışarı veri kodlama ve kod çözme yöntemidir.

Bunu, farklı veri türlerinin ABI tarafından nasıl kodlandığını ve kodunun çözüldüğünü farklı bir bölümde göreceğiz.

Bir sözleşmenin JSON ABI’sini anlama

Dapp arayüzleri gibi blok zincirinin dışından etkileşime giren aracılar için, bir sözleşmenin ABI’si JSON formatında temsil edilir.

Bir Dapp arabirimi için, bir Solidity akıllı sözleşmesinin ABI’si bir nesne dizisi olarak temsil edilir.

Her nesne şunlara karşılık gelebilir:

  • Sözleşme yönteminde genel olarak çağrılabilir olan bir yöntem (işlev) (= kısıtlayıcı değiştiriciler eklenmiş olmadıkça herkes tarafından çağrılabilir).
  • bir olay tanımı
  • bir geri dönüş() veya alma() işlevi.

Truffle veya Hardhat içinde bir Solidity sözleşmesi derlerken, oluşturulan .json eserinin içinde ABI’yi bulabilirsiniz. Bu JSON dosyası “abi” adında bir alan içerecektir.

İşte UniswapV3Factory’den temel bir örnek:

{
"abi": [
    {
      "inputs": [],
      "stateMutability": "nonpayable",
      "type": "constructor"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "uint24",
          "name": "fee",
          "type": "uint24"
        },
        {
          "indexed": true,
          "internalType": "int24",
          "name": "tickSpacing",
          "type": "int24"
        }
      ],
      "name": "FeeAmountEnabled",
      "type": "event"
    },
    // events definition of `OwnerChanged`, `PoolCreated`
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "tokenA",
          "type": "address"
        },
        {
          "internalType": "address",
          "name": "tokenB",
          "type": "address"
        },
        {
          "internalType": "uint24",
          "name": "fee",
          "type": "uint24"
        }
      ],
      "name": "createPool",
      "outputs": [
        {
          "internalType": "address",
          "name": "pool",
          "type": "address"
        }
      ],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "uint24",
          "name": "fee",
          "type": "uint24"
        },
        {
          "internalType": "int24",
          "name": "tickSpacing",
          "type": "int24"
        }
      ],
      "name": "enableFeeAmount",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    // function definitions of:
    //     - `feeAmountTickSpacing(...)`
    //     - `getPool(...)`
    //     - `owner()` 
    //     - `parameters(...)`
    //     - `setOwner(...)`
  ]
}

Gördüğünüz gibi, ABI sözleşmeyi biraz daha kavramsallaştırmaya yardımcı oluyor. Türleriyle birlikte parametre listesi üzerinden hangi fonksiyonların nasıl çağrılabileceğini gösterir.

ABI, yalnızca işlevler ve olaylar hakkında bilgi içerir. Durum değişkenleri veya değiştiriciler hakkında bilgi içermez (durum değişkenlerinin public olarak tanımlandığı durumlar hariç. Bu durumda, bir genel alıcı oluşturulur).

JSON ABI’da hem function hem de event özelliklerine ve özelliklerine ayrıntılı olarak bakalım.

Bir işlevin JSON ABI belirtimi

  • type: function, constructor, event , receive (alma işlevi için) veya fallback (varsayılan geri dönüş işlevi için)
  • name : işlevin adı.
  • inputs : fonksiyon parametreleri, her parametrenin adı ve türü ile bir dizi olarak.
  • outputs : işlev tarafından bir dizi dönüş değeri/değerleri
  • stateMutability : view, pure , payable ya da nonpayable (= bir işlevin varsayılan değişebilirliği). Bu ayrıca işlevin salt okunur olup olmadığını veya sözleşme durumuna yazabileceğini belirtir.

Not: constructor ve fallback işlevleri için JSON ABI‘deki ad ve çıktı alanları boştur.

Not2: fallback işlevi için, geri dönüş işlevi bağımsız değişkenleri kabul edemediğinden giriş her zaman boştur.

fonksiyon girişleri (detaylar)

inputs” alanı, her nesne işlev parametresini tanımlayan bir dizi nesne içerir.

type: parametrenin temel türü (örneğin: uint256, address, vb.).
outputs: Girdilere benzer bir dizi çıktı nesnesidir.

Parametre bir tuple ise (kullanıcı tanımlı struct gibi), “input” alanı “components” adında bir alan içerecektir. Bu alanda başka bir tuple varsa, “type” olarak tanımlanacaktır: “tuple” (“type”: “tuple”).

stateMutability (durum değişebilirliği) Detaylar

Bir fonksiyonun “stateMutability” alanı, fonksiyonun Solidity‘de nasıl yazıldığına bağlı olarak aşağıdaki dört değerden birine sahip olabilir:

pure: fonksiyon blok zinciri durumunu okumaz veya yazmaz.

view: işlev yalnızca blok zinciri durumundan okuyabilir, ancak ona yazamaz.

payable: işlev, çağrılırken blok zinciri durumunu okuyabilir ve yazabilir + etherleri ✅ 💸 (veya EVM zincirinden yerel para birimini) kabul edebilir.

nonpayable (varsayılan): işlev blok zinciri durumunu okuyabilir ve bu duruma yazabilir, ancak çağrılırken etherleri ❌ 💸 (veya EVM zincirinden yerel para birimi) kabul edemez.

Bahs ettiğimiz üzere bir fonksiyonun durum değişkenliği 4 farklı şekilde belirtilebilir.

Olayların JSON ABI belirtimi

type: her zaman olay. na0me: olayın adı.
inputs: olaya iletilebilecek parametre dizisi.

anonymous: Olay Solidity kodunda anonim olarak bildirilmişse true, aksi halde false.

event inputs (detaylar)

İşlevler gibi, JSON ABI‘deki bir olayın “in*0put” alanı, olay parametrelerini temsil eden bir dizi nesne içerir. Her nesne aşağıdaki özelliklerden oluşur:

  • name: parametrenin adı.
  • type: parametrenin temel türü (örneğin: uint256, address, vb.).
  • indexed: alan günlüğün konularının bir parçasıysa true, günlüğün veri bölümlerinden biriyse false.

Solidity ABI — kodlama verileri

ABI‘nin Solidity veri türlerinin EVM tarafından yorumlanabilmesi için nasıl kodlanması gerektiğini tanımladığını daha önce tartıştık ve gördük.

Solidity‘deki address , uint256 veya bytes32 gibi statik türlerin çoğu 32 byte olarak kodlanmıştır.

Bayt dolgusu, temeldeki Solidity türlerine göre değişir. Örneğin, adres sol tarafta sıfır dolguluyken, 32 bayttan küçük sabit boyutlu bayt değerleri (bytes4 , bytes8 , bytes20 , vb…) sağ tarafta sıfır dolguludur.

En ilginç olanı, string veya diziler gibi temel olmayan türlerle birlikte gelir. Yani bu türler, Solidity ABI tarafından belirtilen belirli bir şekilde kodlanmıştır.

Aşağıda bir string ve bir sabit boyutlu dizi ile iki örnek göstereceğiz.

abi.encode(…)

Solidity yerleşik işlevi abi.encode, herhangi bir Solidity türünün doğrudan EVM tarafından yorumlanabilen ham baytlara kodlanmasını sağlar.

Bu fonksiyona birden fazla argüman verilebileceğini unutmayın.

İlk örnekle başlayalım:

string

abi.encode(“Solidity”)

Yukarıdaki işlev, aşağıdaki ham bayt değerini döndürür.

0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000004536f6c6964697479000000000000000000000000000000000000000000000000
// Split in words 32 bytes long
0x0000000000000000000000000000000000000000000000000000000000000020
  0000000000000000000000000000000000000000000000000000000000000004
  536f6c6964697479000000000000000000000000000000000000000000000000

Dizeler her zaman aşağıdaki biçim kullanılarak kodlanır:

  1. (32 bayt) kelime = offset → dizenin hangi bayt indeksinde başladığını gösterir. Burada 0x20 (onaltılık olarak) = 32 (ondalık olarak). Baştan 32 sayarsanız (= dizin 32), asıl kodlanmış dizenin başladığı başlangıç noktasına ulaşırsınız.
  2. (32 bayt) kelime = dize uzunluğu → dize durumunda, bu, dizeye kaç karakterin (boşluklar dahil) dahil edildiğini gösterir. Yani basitçe “string.length
  3. (32 bayt) kelime = gerçek utf8 kodlu dize → her bir bayt, utf8’de kodlanmış bir harf / karakterin onaltılı gösterimine karşılık gelir. Bir utf8 tablosunda 536f6c6964697479’dan her bir baytı tek tek ararsanız, dizenin kodunu çözebilirsiniz. Örneğin, 53 büyük S harfine, 6f küçük o harfine, 6c küçük l harfine karşılık gelir, vb…

Standart olmayan kodlama = abi.encodePacked(…)

Solidity, yerleşik işlev abi.encodePacked(…) aracılığıyla veriler için standart olmayan bir kodlama modu sunar. Bu, ABI tarafından belirtilen kurallara uyulmadan verilerin ham bayt cinsinden kodlanmasını sağlar.

Solidity‘de paketlenmiş kodlama yaparken aşağıdaki ABI kuralları bırakılır:

  • dinamik türler (örneğin: diziler, diziler, …) ofset veya uzunluk olmadan oldukları gibi kodlanır.
  • 32 bayttan kısa statik türler (örneğin: uint8, bytes4, vb…) sıfır dolgulu değildir.

Solidity” dizesini kodlamaya ilişkin önceki örneğimizi kullanalım ve abi.encode(…) aracılığıyla standart kodlama ile abi.encodePacked(…) aracılığıyla standart olmayan/paketlenmiş kodlama arasındaki farkı karşılaştıralım.

abi.encode("Solidity")
> 0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000004536f6c6964697479000000000000000000000000000000000000000000000000
abi.encodePacked("Solidity")
> 0x536f6c6964697479

Yukarıdan da görebileceğiniz gibi, abi.encodePacked(…) sadece 16 tabanındaki dizgenin utf8 temsilini döndürmek için dizge ofsetini ve uzunluğunu düşürür.

show difference between these outputs:
abi.encode(0xaaaaaaaa, 0xbbbbbbbb, 0xcccccccc)
abi.encodePacked(0xaaaaaaaa, 0xbbbbbbbb, 0xcccccccc)

Paketlenmiş kodlama ve etkileri hakkında daha fazla ayrıntı için Solidity belgelerindeki “ABI > Standart Olmayan Paket Modu” bölümüne bakın.

Solidity ABI — sözleşme çağrılarını kodlama

abi.encodeWithSignature(…) ve abi.encodeWithSelector(…) işlevleri, Solidity‘de harici sözleşme çağrıları için ham baytlarda veri yükü hazırlamak için kullanılabilir. Bu tür yükler daha sonra düşük seviyeli Solidity .call(…) , .delegatecall(…) ve .staticcall(…) işlevlerine parametre olarak geçirilebilir.

Bu iki işlev, ikinci parametreden başlayarak iletilen argümanları kodlayacaktır.

abi.encodeWithSelector(…)

abi.encodeWithSelector(bytes4 selector, ...) returns (bytes memory)

Bu fonksiyon ile çağırmak istediğiniz fonksiyonun bytes4 fonksiyon seçicisini sağlamalısınız. Seçici, işlev imzasının Keccak256-hash‘sinin ilk dört baytından oluşur.

Bir fonksiyonun seçicisini elde etmenin birkaç yolu vardır:

  • sözleşme türünde bir değişkene atıfta bulunuyorsanız, someContract.someFunction.selector sözdizimini kullanın.
  • interface işlev seçicisine doğrudan başvurabilirsiniz.

bytes4 karmasını imza dizesinden oluşturabilirsiniz:

bytes4 selector = 
bytes4(keccak256("transfer(address,address,uint256)"));

… veya aşağıdaki abi.encodeWithSignature(…) fonksiyonunu kullanabilirsiniz.

Uniswap’tan Örnek

Uniswap’ın Solidity kodu, bu yerleşik abi işlevlerinin Solidity’de nasıl kullanıldığına gerçekten iyi bir örnek sağlar. UniswapV2Pair sözleşmesine bir göz atın.

_safeTransfer(…) dahili işlevi abi.encodeWithSelector kullanarak herhangi bir ERC20 belirteç sözleşmesine genel bir düşük seviyeli çağrı gerçekleştirerek protokolün fonları Uniswap havuzu ile ERC20 belirteç sözleşmeleri arasında transfer etmesini ve ardından taşımasını sağlar.

function _safeTransfer(
    address token, 
    address to, 
    uint value
) private {        
    
    (bool success, bytes memory data) = token.call(abi.encodeWithSelector(SELECTOR, to, value));
    
    require(
        success && (data.length == 0 || abi.decode(data, (bool))),
        'UniswapV2: TRANSFER_FAILED');    
}

Yukarıdaki örneğe benzer şekilde, UniswapV2Pair’deki genel ERC20 transfer(…) işlevinin bytes4 seçicisi, tanımladığımız gibi oluşturulur:

bytes4 private constant SELECTOR = 
bytes4(keccak256(bytes('transfer(address,uint256)')));

abi.encodeWithSignature(…)

abi.encodeWithSignature(string memory signature, ...) returns (bytes memory)

Bu işlev şuna eşdeğerdir:

abi.encodeWithSelector(bytes4(keccak256(bytes(signature))), ...)

Solidity’yi ABI türleriyle eşleştirme

Solidity’de bulunan temel tiplerin çoğu, ABI spesifikasyonlarının bir parçasıdır. Buna address , bytes32 vb. dahildir.

Ancak bazı belirli Solidity türlerinin ABI’de doğrudan bir karşılığı yoktur. Bunlar daha sonra ABI’de tanımlanan türlere çevrilir.

Mapping Solidity to ABI types
Mapping Solidity to ABI types

Özetle, adres payable veya sözleşme türünde bir değişken, kabuğun altında ABI tarafından standart bir adres olarak kodlanacak/kodu çözülecektir.

Aynı şey struct için de geçerlidir. ABI, bir yapıyı temel türlerin bir demeti olarak kodlayacaktır.

Enums, enum‘da tanımlanan tüm değerleri tutacak kadar büyük en küçük uint türü tarafından ABI’de tanımlanırdı.

Örneğin, 255 veya daha az değerden oluşan bir enum ABI tarafından bir uint8 ile eşlenirken, 256 veya daha fazla değerden oluşan bir numaralandırma (enum) bir uint16 ile eşlenirdi.

Bu, Solidity 0.8.0 sürümünden beri değişti, çünkü enum artık 256’dan fazla üyeye sahip olamaz.

Blockchain ​​Developer Olmak
Blockchain ​​Developer Olmak

Bu makale “ABI Hakkında” oluşturulan içeriklerden derlenmiştir.

Akıllı sözleşme geliştirme yolculuğunuz hakkında daha iyi rehberlik almak için Solidity nedir? Ethereum Akıllı Sözleşmelerinin Dili Rehberi içeriğimize göz atın. Dilerseniz Yeni Başlayanlar için Solidity – Akıllı Sözleşme Geliştirme Hızlandırılmış Kursuna katılın.

Çalışmaya nereden başlayacağım diyenler için Blockchain ​​Developer Olmak İçin Yol Haritası içeriğine de muhakkak bakın.

Bu makaleyi okuduğunuz için teşekkürler! Bana destek olmak isterseniz;

Beni TwitterLinkedin ve YouTube‘da takip edin.

Kısa bir yorum bırakmayı UNUTMAYIN!

Hasan YILDIZ, Girişimci. Doktora Öğrencisi. Yazmayan YAZILIMCI. Veri Şeysi. Eğitmen...

Yazarın Profili
İlginizi Çekebilir

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir