Trang chủHướng dẫnPayable Solidity: Gửi ETH từ Smart Contract và Quản lý dòng tiền On-chain (P2)
Blockchain

Payable Solidity: Gửi ETH từ Smart Contract và Quản lý dòng tiền On-chain (P2)

CyStack blog 4 phút để đọc
CyStack blog09/05/2025
Locker Avatar

Đức Hacker

My passion is hunting down the latest attack trends—ransomware, APTs, you name it—while passing on knowledge to help businesses forge ironclad defenses. I’ve left my mark on data encryption projects and intrusion detection tools now widely used across Vietnam. I’m the shadow that strikes before the enemy does.

Locker logo social
Reading Time: 4 minutes

Trong phần trước, mình đã chia sẻ chi tiết về payable function trong Solidity — cách cho phép smart contract nhận ETH, và cách EVM xử lý transaction gửi kèm value. Mình sẽ để link bài viết trước ở đây cho bạn nào chưa đọc: Payable Function Solidity là gì? Cơ chế xử lý ETH tới smart contract (P1). Còn trong phần này, chúng ta sẽ đi sâu vào mặt còn lại của payable trong Solidity: làm thế nào để gửi ETH từ smart contract tới địa chỉ khác một cách an toàn, tối ưu và không dính lỗi thường gặp.

Nếu bạn đang viết các contract có xử lý dòng tiền — như treasury, refund, tipping, profit-sharing, NFT claim hoặc reward payout — thì đây chính là phần bạn cần.

payable solidity

Gửi ETH từ Smart Contract không đơn giản như bạn nghĩ

Được rồi, bạn đã nhận ETH vào contract bằng payable function. Nhưng giờ làm sao để gửi ETH từ smart contract đó ra ví người dùng, dev, treasury hay DAO multisig?

Trong Solidity, bạn có thể send ether from smart contract to address bằng 3 cách chính:

Cách 1: .transfer()

Ví dụ:

payable(recipient).transfer(1 ether);

  • Cấp mặc định 2,300 gas cho bên nhận
  • Tự động revert nếu gửi thất bại
  • An toàn với ví cá nhân, nhưng có thể fail nếu recipient là smart contract có fallback function phức tạp

Cách 2: .send()

bool success = payable(recipient).send(1 ether); require(success, “Transfer failed”);

  • Cũng cấp 2,300 gas như transfer()
  • Trả về true/false thay vì tự revert → dễ bị bỏ sót nếu dev quên check

Cách 3: .call()

(bool sent, ) = payable(recipient).call{value: 1 ether}(“”); require(sent, “Transfer failed”);

  • Không giới hạn gas
  • Tương thích tốt với contract có fallback logic
  • Được khuyến nghị sử dụng trong Solidity hiện đại

Tóm lại: nếu bạn chỉ gửi ETH đến ví thường → dùng transfer() là đủ. Còn nếu người nhận có thể là smart contract, tốt nhất nên dùng call() để tránh các lỗi “gas trap”.

So Sánh Cụ Thể: transfer() vs send() vs call()

Phương pháp Tự revert Giới hạn gas Khả năng tương thích Được khuyến nghị
transfer() 2,300 Chỉ ví thường Trung bình
send() Không 2,300 Chỉ ví thường Không
call() Không Không Smart contract & ví

Mình từng deploy một NFT claim contract, dùng transfer() để gửi ETH refund cho người không mint được. Trên testnet thì ổn, nhưng lên mainnet gặp một user dùng ví smart contract có fallback → refund fail. Cuối cùng phải refactor lại dùng call().

Xem thêm: Cách airdrop NFT

Rút toàn bộ số dư ETH về ví chủ

Một pattern phổ biến là contract nhận ETH và chủ sở hữu có thể rút tiền về ví cá nhân hoặc treasury.

contract Vault { address public owner;

constructor() {
    owner = msg.sender;
}

receive() external payable {}

function withdrawAll() external {
    require(msg.sender == owner, "Not owner");
    (bool sent, ) = payable(owner).call{value: address(this).balance}("");
    require(sent, "Withdraw failed");
}

}

  • receive() giúp contract nhận ETH từ bất kỳ ai
  • withdrawAll() gửi toàn bộ ETH đến ví chủ bằng call()
  • Có check đầy đủ quyền và kết quả

Bạn cũng có thể tách withdraw từng phần, hoặc cho phép nhiều người cùng withdraw theo balance nội bộ.

Hãy cẩn thận với Gas Trap và Fallback Function

Như đã nói, transfer() và send() chỉ cấp 2,300 gas. Nếu ví người nhận là một smart contract có logic trong fallback (ví dụ emit event, ghi log…), nó có thể không đủ gas để chạy → transfer thất bại.

contract Receiver { fallback() external payable { // logic dùng nhiều gas hơn 2,300 doSomething(); } }

→ Gửi ETH bằng transfer đến contract này sẽ luôn fail. Đây gọi là gas trap.

Cách tránh:

  • Dùng call() thay cho transfer()/send()
  • Nếu cần tính toán chi tiết hơn, bạn có thể truyền gas thủ công: call{value: x, gas: y}()

Reentrancy Attack – Khi gửi ETH bị gọi ngược

Một trong những lỗ hổng kinh điển khi gửi ETH từ contract là reentrancy.

Ví dụ:

function withdraw() public { uint amount = balances[msg.sender]; require(amount > 0, “No balance”);

(bool sent, ) = msg.sender.call{value: amount}("");
require(sent, "Withdraw failed");

balances[msg.sender] = 0; // cập nhật sau — DỄ DÍNH REENTRANCY!

}

Kẻ tấn công có thể dùng fallback() trong contract của họ để gọi lại withdraw() nhiều lần trước khi balance được reset.

Cách phòng chống:

  • Dùng Checks-Effects-Interactions pattern (cập nhật balance trước khi gửi ETH)
  • Hoặc dùng modifier nonReentrant (OpenZeppelin ReentrancyGuard)

Có thể bạn quan tâm: Cách deploy contract trên polygon

Logging & Event khi gửi ETH

Luôn emit event khi bạn gửi ETH:

event Payout(address indexed to, uint amount);

function payout(address to, uint amount) external onlyOwner { (bool sent, ) = payable(to).call{value: amount}(“”); require(sent, “Transfer failed”); emit Payout(to, amount); }

Việc này giúp:

  • Frontend dễ tracking giao dịch
  • Indexer (như The Graph) có thể lấy dữ liệu payout
  • Audit log rõ ràng hơn

Những lỗi thường gặp khi gửi ETH trong Solidity

Không nên Nên
cast địa chỉ sang payable:
address user = msg.sender;
user.transfer(1 ether); // lỗi compile dùng: payable(user).transfer(1 ether);
Không check kết quả khi dùng call():
(bool sent, ) = to.call{value: 1 ether}(“”);
// Không có require(sent) → không biết gửi có thành công hay không
Dùng transfer cho ví smart contract có fallback → fail do gas limit dùng call
Không bảo vệ withdraw() bằng onlyOwner hoặc permission control Luôn xác thực quyền người gọi

Hy vọng những nội dung này hữu ích với các bạn!

Bài viết liên quan:

0 Bình luận

Đăng nhập để thảo luận

CyStack blog

Mẹo, tin tức, hướng dẫn và các best practice độc quyền của CyStack

Đăng ký nhận bản tin của chúng tôi

Hãy trở thành người nhận được các nội dung hữu ích của CyStack sớm nhất

Xem chính sách của chúng tôi Chính sách bảo mật.