← Öğren'e geri dön

Güvenlik ve Zincirler Arası Geliştirme

Akıllı sözleşme güvenlik açıkları milyarlarca dolarlık kayba yol açtı. Bu rehber en yaygın saldırı vektörlerini, oracle güvenlik desenlerini, test stratejilerini ve zincirler arası DeFi uygulamaları geliştirmenin temellerini ele alır.

Reentrancy Saldırıları

Reentrancy, en kötü şöhretli akıllı sözleşme güvenlik açığı olmaya devam ediyor. Bu, harici bir çağrının durum güncellemeleri tamamlanmadan sözleşmeye yeniden girmesiyle ortaya çıkar; böylece saldırgan, bakiye sıfırlanmadan önce withdraw çağrısını tekrar tekrar yaparak fonları boşaltabilir.

Çözümün iki katmanı vardır: harici çağrılar yapılmadan önce durumu güncellemek (checks-effects-interactions) ve ikinci savunma hattı olarak OpenZeppelin'in ReentrancyGuard bileşenini kullanmak. Modern Solidity 0.8+ integer overflow'u katkıda bulunan bir faktör olarak ortadan kaldırır, ancak temel reentrancy deseni hâlâ geçerlidir.

Savunmasız vs. güvenli para çekme
// VULNERABLE — external call before state update
function withdraw() external {
    uint256 amount = balances[msg.sender];
    (bool success, ) = msg.sender.call{value: amount}("");
    require(success);
    balances[msg.sender] = 0; // Too late!
}

// SECURE — state update before external call + guard
function withdraw() external nonReentrant {
    uint256 amount = balances[msg.sender];
    balances[msg.sender] = 0; // State updated first
    (bool success, ) = msg.sender.call{value: amount}("");
    require(success, "Transfer failed");
}

Erişim Kontrolü

Rol tabanlı erişim kontrolü; admin, operatör ve pauser yetkilerini birbirinden ayırır. Bu, tek bir ele geçirilmiş anahtarın tüm protokolü kontrol etmesini engeller. OpenZeppelin'in AccessControl bileşeni, rol hiyerarşileriyle savaşta sınanmış bir uygulama sunar.

Kritik operasyonlar — ödül oranlarını değiştirmek, sözleşmeyi duraklatmak, implementasyonları yükseltmek — farklı roller gerektirmelidir. DEFAULT_ADMIN_ROLE diğer rolleri verebilir ve geri alabilir, bu nedenle asla tek bir EOA tarafından değil, bir multi-sig veya timelock sözleşmesi tarafından tutulmalıdır.

Rol tabanlı erişim kontrolü
import "@openzeppelin/contracts/access/AccessControl.sol";

contract SecureProtocol is AccessControl {
    bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
    bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
    bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");

    constructor() {
        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
        _grantRole(ADMIN_ROLE, msg.sender);
    }

    function setRewardRate(uint256 newRate) external onlyRole(ADMIN_ROLE) {
        require(newRate <= 10000, "Rate too high");
        rewardRate = newRate;
    }

    function pause() external onlyRole(PAUSER_ROLE) { paused = true; }
    function unpause() external onlyRole(ADMIN_ROLE) { paused = false; }
}

Oracle Güvenliği

Fiyat oracle manipülasyonu en yaygın DeFi saldırı vektörlerinden biridir. Tek bir fiyat beslemesine doğrulama yapmadan güvenen bir protokol; flash-loan destekli fiyat manipülasyonuna, ağ tıkanıklığı sırasında bayat verilere ve oracle kesintilerine karşı savunmasızdır.

Derinlemesine savunma; bayatlığı kontrol etmeyi (bir eşikten daha eski fiyatları reddetmeyi), makullüğü doğrulamayı (negatif fiyatları veya son bilinen iyi fiyattan aşırı sapmaları reddetmeyi) ve ağırlıklı oylamayla çok-oracle toplaması uygulamayı içerir. Tüm oracle'lar başarısız olursa, protokol kötü verilerle çalışmak yerine duraklamalıdır.

Ağırlıklı toplamalı çoklu oracle
function getWeightedPrice() public view returns (uint256) {
    uint256 totalWeight = 0;
    uint256 weightedSum = 0;

    for (uint256 i = 0; i < oracleKeys.length; i++) {
        OracleData memory oracle = oracles[oracleKeys[i]];
        if (!oracle.isActive) continue;

        try oracle.feed.latestRoundData() returns (
            uint80, int256 price, uint256, uint256 updatedAt, uint80
        ) {
            if (price > 0 && block.timestamp - updatedAt <= 3600) {
                weightedSum += uint256(price) * oracle.weight;
                totalWeight += oracle.weight;
            }
        } catch {
            continue; // Skip failed oracle
        }
    }

    require(totalWeight > 0, "No valid oracles");
    return weightedSum / totalWeight;
}

Test ve Doğrulama

Üretime hazır bir DeFi sözleşmesi; her fonksiyon için birim testlerine, karmaşık iş akışları için entegrasyon testlerine, rastgele girdi desenleri için fuzz testlerine ve ideal olarak kritik değişmezler için biçimsel doğrulamaya ihtiyaç duyar. Herhangi bir denetimden önce en az %95 test kapsamını hedefleyin.

Fuzz testi, manuel testlerin kaçırdığı uç durumları yakalar. Yüzlerce rastgele yatırma/çekme dizisi çalıştırın ve toplam mevduatların her zaman bireysel bakiyelerin toplamına eşit olduğunu doğrulayın. Certora gibi biçimsel doğrulama araçları, değişmezlerin yalnızca test etmeyi düşündüğünüz girdiler için değil, tüm olası girdiler için geçerli olduğunu matematiksel olarak kanıtlayabilir.

Fuzz testi: durum tutarlılığı
describe("Fuzz Testing", function () {
  it("Should maintain state consistency across random operations", async function () {
    for (let i = 0; i < 100; i++) {
      const user = Math.random() > 0.5 ? user1 : user2;
      const action = Math.random() > 0.5 ? "deposit" : "withdraw";
      const amount = ethers.parseEther((Math.random() * 100).toFixed(2));

      try {
        if (action === "deposit") {
          await token.connect(user).approve(await protocol.getAddress(), amount);
          await protocol.connect(user).deposit(amount);
        } else {
          const balance = await protocol.userDeposits(user.address);
          if (balance > 0n) {
            const w = amount > balance ? balance : amount;
            await protocol.connect(user).withdraw(w);
          }
        }
      } catch (error) {
        expect(error.message).to.match(/(InsufficientBalance|InvalidAmount)/);
      }
    }

    // Invariant: totals must match
    const total = await protocol.totalDeposits();
    const b1 = await protocol.userDeposits(user1.address);
    const b2 = await protocol.userDeposits(user2.address);
    expect(total).to.equal(b1 + b2);
  });
});

Denetime Hazırlık

Bir denetçiyle çalışmaya başlamadan önce, kapsamlı bir denetim öncesi kontrol listesinden geçin. Kod kalitesi maddeleri: kapsamlı NatSpec dokümantasyonu, tutarlı stil, kullanılmayan import yok, tüm TODO'lar çözülmüş, gaz optimizasyonları uygulanmış. Güvenlik maddeleri: reentrancy koruması, erişim kontrolleri, girdi doğrulaması, oracle manipülasyonu koruması, acil durum duraklatma mekanizması.

Test gereksinimleri: tüm fonksiyonlar için birim testleri, karmaşık iş akışları için entegrasyon testleri, uç durum testleri, fuzz testleri ve %95 üzerinde kapsam. Dokümantasyon: teknik şartname, mimari diyagramlar, bilinen kısıtlamalar, dağıtım talimatları ve bir acil durum prosedürleri kılavuzu (runbook).

Zincirler Arası Mimari

Çok zincirli DeFi; zincir kimliklerini RPC URL'lerine, sözleşme adreslerine, blok gezginlerine ve yerel para birimlerine eşleyen bir zincir soyutlama katmanı gerektirir. Bu, frontend'inizin kod tabanı boyunca sabit kodlanmış mantık olmadan Ethereum, Polygon, Arbitrum ve Base arasında dinamik olarak geçiş yapmasını sağlar.

Zincirler arası yatırımlar bir köprü (bridge) sözleşme deseni kullanır: token'ları kaynak zincirde kilitle, köprü aracılığıyla bir mesaj gönder ve hedef zincirde token'ları basar (mint) veya serbest bırak. Alıcı sözleşme, mesajın köprüden ve doğru kaynak zincir protokol adresinden geldiğini doğrulamalıdır. Her zaman replay koruması uygulayın ve her zincirin kesinlik (finality) garantilerini göz önünde bulundurun.

Zincirler arası yatırım akışı
function depositCrossChain(uint256 amount, uint256 targetChain)
    external nonReentrant
{
    require(chainProtocols[targetChain] != address(0), "Unsupported chain");
    IERC20(depositToken).transferFrom(msg.sender, address(this), amount);

    bytes memory message = abi.encode(msg.sender, amount);
    bytes32 messageId = bridge.sendMessage(
        targetChain,
        chainProtocols[targetChain],
        message
    );

    emit CrossChainDeposit(msg.sender, amount, targetChain, messageId);
}

function receiveMessage(
    uint256 sourceChain, address sourceAddress, bytes calldata message
) external {
    require(msg.sender == address(bridge), "Only bridge");
    require(sourceAddress == chainProtocols[sourceChain], "Invalid source");
    (address user, uint256 amount) = abi.decode(message, (address, uint256));
    userDeposits[user] += amount;
    totalDeposits += amount;
}
WarningBir DeFi akıllı sözleşmesini asla en az bir bağımsız güvenlik denetimi olmadan mainnet'e dağıtmayın. Savaşta sınanmış desenler bile yanlış uygulanabilir ve bir denetimin maliyeti, bir istismarın (exploit) maliyetiyle kıyaslandığında önemsizdir.