Solidity / ETH илгээх (send, transfer, call)

ETH илгээх (send, transfer, call)

Contract-аас ETH илгээх гурван арга байдаг: transfer, send, call. Тус бүр өөрийн давуу болон сул талтай. Орчин үеийн Solidity-д call ашиглахыг зөвлөдөг.

transfer — хялбар боловч хязгаартай

solidity
payable(recipient).transfer(amount);
  • Gas хязгаар: 2300 gas (зөвхөн үйл явдал бичихэд хүрэлцэнэ)
  • Амжилтгүй бол автоматаар revert хийнэ
  • Return утга байхгүй
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract TransferExample {
    function sendWithTransfer(address payable recipient) public payable {
        require(msg.value > 0, "ETH илгээх шаардлагатай");
        // Амжилтгүй бол автоматаар буцаагдана
        recipient.transfer(msg.value);
    }
}

Сул тал: Хүлээн авагч contract нь receive() функцдоо нарийн логик байвал 2300 gas хүрэлцэхгүй бөгөөд гүйлгээ бүтэлгүйтэнэ.

send — гар аргаар шалгах шаардлагатай

solidity
bool success = payable(recipient).send(amount);
  • Gas хязгаар: 2300 gas (transfer-тэй адил)
  • Амжилтгүй бол false буцаана — revert хийхгүй
  • Return утгыг заавал шалгах ёстой
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SendExample {
    function sendWithSend(address payable recipient) public payable {
        require(msg.value > 0, "ETH илгээх шаардлагатай");

        bool success = recipient.send(msg.value);
        require(success, "ETH илгээхэд алдаа гарлаа"); // Заавал шалгана
    }
}

Сул тал: transfer-тэй ижил 2300 gas хязгаартай, гэхдээ алдааг гар аргаар шалгах шаардлагатай тул алдаа гаргахад хялбар.

call — зөвлөмж болгох арга

solidity
(bool success, bytes memory data) = payable(recipient).call{value: amount}("");
  • Gas хязгаар: бүх боломжит gas дамжуулна
  • Return утга: (bool success, bytes memory data)
  • Хамгийн уян хатан, орчин үеийн арга
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract CallExample {
    function sendWithCall(address payable recipient) public payable {
        require(msg.value > 0, "ETH илгээх шаардлагатай");

        (bool success, ) = recipient.call{value: msg.value}("");
        require(success, "ETH илгээхэд алдаа гарлаа");
    }
}

Яагаад call ашиглах нь дээр вэ?

2019 оны Istanbul hard fork-оор gas зардал өөрчлөгдсөний дараа transfer болон send-ийн 2300 gas хязгаар хүрэлцэхгүй тохиолдол гарч эхэлсэн. Тиймээс Ethereum Foundation call ашиглахыг зөвлөж байна.

solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SafeWallet {
    mapping(address => uint256) public balances;

    function deposit() public payable {
        balances[msg.sender] += msg.value;
    }

    function withdraw(uint256 amount) public {
        require(balances[msg.sender] >= amount, "Үлдэгдэл хүрэлцэхгүй");

        // ✅ Checks-Effects-Interactions загвар
        // 1. Checks — нөхцөл шалгах (дээр хийсэн)
        // 2. Effects — төлөв өөрчлөх
        balances[msg.sender] -= amount;

        // 3. Interactions — гадаад дуудлага
        (bool success, ) = payable(msg.sender).call{value: amount}("");
        require(success, "ETH илгээхэд алдаа гарлаа");
    }
}

Гурван аргын харьцуулалт

| | transfer | send | call | | ----------------- | ---------- | ------------- | ------------- | | Gas хязгаар | 2300 | 2300 | Бүгд | | Амжилтгүй бол | Revert | false буцаана | false буцаана | | Зөвлөмж | ❌ | ❌ | ✅ | | Reentrancy эрсдэл | Бага | Бага | Өндөр* |

*call ашиглахдаа Checks-Effects-Interactions загварыг заавал дагах ёстой.

Reentrancy — анхаарах эрсдэл

call ашиглахад хүлээн авагч contract нь дуудлагын дундуур таны contract руу дахин дуудлага хийж болно. Үүнийг reentrancy attack гэнэ.

solidity
// ⚠️ АЮУЛТАЙ — reentrancy-д өртөмтгий жишээ
contract VulnerableWallet {
    mapping(address => uint256) public balances;

    function withdraw() public {
        uint256 amount = balances[msg.sender];
        require(amount > 0, "Үлдэгдэл байхгүй");

        // ❌ Муу дараалал: эхлээд ETH илгээж, дараа нь тооцоо шинэчилнэ
        (bool success, ) = payable(msg.sender).call{value: amount}("");
        require(success, "Илгээхэд алдаа");

        balances[msg.sender] = 0; // Хэт оройтсон!
    }
}

Засах арга: төлөв өөрчлөлтийг гадаад дуудлагаас өмнө хийх. Reentrancy attack-ийг дэлгэрэнгүй 26-р хичээлд судална.

Дараагийн хичээлд:

ERC-20 token стандартыг танилцуулна.