WETH(Wrapped ETH)是 ETH 的包裝版本。我們常見的 WETH、WBTC、WBNB,都是帶包裝的原生代幣。那我們為什麼要包裝它們?
2015 年,ERC20 標準出現,此代幣標準旨在為以太坊上的代幣制定一套標準化的規則,從而簡化了新代幣的發布,並使區塊鏈上的所有代幣相互可比。不幸的是,以太幣本身並不符合 ERC20 標準。 WETH 的開發是為了提高區塊鏈之間的互通性,並使 ETH 可用於去中心化應用程式(dApps)。它就像是給原生代幣穿了一件智能合約做的衣服:穿上衣服的時候,就變成了 WETH,符合 ERC20 同質化代幣標準,可以跨鏈,可以用於 dApp;脫下衣服,它可 1:1 兌換ETH。
目前在用的主網 WETH 合約寫於 2015 年,非常老,那時 solidity 是 0.4 版本。我們用 0.8 版本重寫一個 WETH。
WETH 符合 ERC20 標準,它比普通的 ERC20 多了兩個功能:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract WETH is ERC20{
// 事件:存款和提款
event Deposit(address indexed dst, uint wad);
event Withdrawal(address indexed src, uint wad);
// 建構子,初始化ERC20的名字和代號
constructor() ERC20("WETH", "WETH"){
}
// 回傳函數,當使用者往WETH合約轉ETH時,會觸發deposit()函數
fallback() external payable {
deposit();
}
// 回傳函數,當使用者往WETH合約轉ETH時,會觸發deposit()函數
receive() external payable {
deposit();
}
// 存款函數,當使用者存入ETH時,給他鑄造等量的WETH
function deposit() public payable {
_mint(msg.sender, msg.value);
emit Deposit(msg.sender, msg.value);
}
// 提款函數,用戶銷毀WETH,取回等量的ETH
function withdraw(uint amount) public {
require(balanceOf(msg.sender) >= amount);
_burn(msg.sender, amount);
payable(msg.sender).transfer(amount);
emit Withdrawal(msg.sender, amount);
}
}
WETH 符合 ERC20 代幣標準,因此 WETH 合約繼承了 ERC20 合約。
WETH 合約共有 2 個事件:
除了 ERC20 標準的函數外,WETH 合約有 5 個函數:
deposit()
:存款函數,當使用者存入 ETH 時,給他鑄造等量的 WETH。withdraw()
:提款函數,讓使用者銷毀 WETH,並歸還等量的 ETH。分帳合約允許將 ETH 按權重轉給一組帳戶後,進行分帳。程式碼部分由 OpenZeppelin 函式庫的 PaymentSplitter 合約簡化而來。
分帳就是依照一定比例分錢財。在現實中,經常會有「分贓不均」的事情發生;而在區塊鏈的世界裡 Code is Law,我們可以事先把每個人應分的比例寫在智能合約中,獲得收入後,再由智能合約來進行分帳。
分帳合約(PaymentSplit)具有以下幾個特點:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;
/**
* 分帳合約
* @dev 這個合約會把收到的ETH按事先定好的份額分給幾個帳戶。收到ETH會存在分帳合約中,需要每個受益人呼叫release()函數來領取。
*/
contract PaymentSplit{
// 事件
event PayeeAdded(address account, uint256 shares); // 增加受益人事件
event PaymentReleased(address to, uint256 amount); // 受益人提款事件
event PaymentReceived(address from, uint256 amount); // 合約收款事件
// 狀態變數
uint256 public totalShares; // 總份額,為shares的和
uint256 public totalReleased; // 總支付,從分帳合約向受益人支付出去的ETH,為 released 的和
mapping(address => uint256) public shares; // address到uint256的映射,記錄每個受益人的份額
mapping(address => uint256) public released; // address到uint256的映射,記錄分帳合約支付給每個受益人的金額
address[] public payees; // address陣列,記錄受益人地址
// 函數
/**
* @dev 建構子:始化受益人陣列_payees和分帳份額陣列_shares
* 陣列長度不能為0,兩個陣列長度要相等。 _shares中元素要大於0,_payees中位址不能為0位址且不能有重複位址。
*/
constructor(address[] memory _payees, uint256[] memory _shares) payable {
// 檢查_payees和_shares儲存容量相同,且不為0
require(_payees.length == _shares.length, "PaymentSplitter: payees and shares length mismatch");
require(_payees.length > 0, "PaymentSplitter: no payees");
// 呼叫_addPayee,更新受益人地址payees、受益人份額shares和總份額totalShares
for (uint256 i = 0; i < _payees.length; i++) {
_addPayee(_payees[i], _shares[i]);
}
}
/**
* @dev 回傳函數,在分帳合約收到ETH時釋放PaymentReceived事件
*/
receive() external payable virtual {
emit PaymentReceived(msg.sender, msg.value);
}
/**
* @dev 分帳函數,為有效受益人地址_account分配對應的ETH。任何人都可以觸發這個函數,但ETH會轉給受益人地址account。
* 呼叫了releasable()函數。
*/
function release(address payable _account) public virtual {
// account必須是有效受益人
require(shares[_account] > 0, "PaymentSplitter: account has no shares");
// 計算account應得的eth
uint256 payment = releasable(_account);
// 應得的eth不能為0
require(payment != 0, "PaymentSplitter: account is not due payment");
// 更新總支付totalReleased和支付給每個受益人的金額released
totalReleased += payment;
released[_account] += payment;
// 轉帳
_account.transfer(payment);
emit PaymentReleased(_account, payment);
}
/**
* @dev 計算一個受益人地址應領取的ETH。
* 呼叫了pendingPayment()函數。
*/
function releasable(address _account) public view returns (uint256) {
// 計算分帳合約總收入totalReceived
uint256 totalReceived = address(this).balance + totalReleased;
// 呼叫_pendingPayment計算帳戶應得的ETH
return pendingPayment(_account, totalReceived, released[_account]);
}
/**
* @dev 根據受益人地址_account, 分帳合約總收入_totalReceived和該地址已領取的錢_alreadyReleased,計算該受益人現在應分的ETH。
*/
function pendingPayment(
address _account,
uint256 _totalReceived,
uint256 _alreadyReleased
) public view returns (uint256) {
// account應得的ETH = 總應得ETH - 已領到的ETH
return (_totalReceived * shares[_account]) / totalShares - _alreadyReleased;
}
/**
* @dev 新增受益人函數及其份額函數。在合約初始化的時候被呼叫,之後不能修改。
*/
function _addPayee(address _account, uint256 _accountShares) private {
// 檢查_account不為0地址
require(_account != address(0), "PaymentSplitter: account is the zero address");
// 檢查_accountShares不為0
require(_accountShares > 0, "PaymentSplitter: shares are 0");
// 檢查帳戶不重複
require(shares[_account] == 0, "PaymentSplitter: account already has shares");
// 更新payees,shares和totalShares
payees.push(_account);
shares[_account] = _accountShares;
totalShares += _accountShares;
// 釋放增加受益人事件
emit PayeeAdded(_account, _accountShares);
}