
WTF Solidity 合约安全: S08. 绕过合约检查
我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 推特:@0xAA_Science|@WTFAcademy_ 社区:Discord|微信群|官网 wtf.academy 所有代码和教程开源在github: github.com/AmazingAng/WTFSolidity这一讲,我们将介绍绕过合约长度检查,并介绍预防的方法。绕过合约检查很多 freemint 的项目为了限制科学家(程序员)会用到 isContract() 方法,希望将调用者 msg.sender 限制为外部账户(EOA),而非合约。这个函数利用 extcodesize 获取该地址所存储的 bytecode 长度(runtime),若大于0,则判断为合约,否则就是EOA(用户)。 // 利用 extcodesize 检查是否为合约 function isContract(address account) public view returns (bool) { // extcodesize > 0 的地址一定是合约...

WTF Solidity 合约安全: S09. 拒绝服务
我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 推特:@0xAA_Science|@WTFAcademy_ 社区:Discord|微信群|官网 wtf.academy 所有代码和教程开源在github: github.com/AmazingAng/WTFSolidity这一讲,我们将介绍智能合约的拒绝服务(Denial of Service, DoS)漏洞,并介绍预防的方法。NFT项目 Akutar 曾因为 DoS 漏洞损失 11,539 ETH,当时价值 3400 万美元。DoS在 Web2 中,拒绝服务攻击(DoS)是指通过向服务器发送大量垃圾信息或干扰信息的方式,导致服务器无法向正常用户提供服务的现象。而在 Web3,它指的是利用漏洞使得智能合约无法正常提供服务。 在2022年4月,一个很火的 NFT 项目名为 Akutar,他们使用荷兰拍卖进行公开发行,筹集了 11,539.5 ETH,非常成功。之前持有他们社区Pass的参与者会得到 0.5 ETH的退款,但是他们处理...

WTF Solidity 合约安全 S06. 签名重放
我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 推特:@0xAA_Science|@WTFAcademy_ 社区:Discord|微信群|官网 wtf.academy 所有代码和教程开源在github: github.com/AmazingAng/WTFSolidity这一讲,我们将介绍智能合约的签名重放(Signature Replay)攻击和预防方法,它曾间接导致了著名做市商 Wintermute 被盗2000万枚 $OP。签名重放上学的时候,老师经常会让家长签字,有时候家长很忙,我就会很“贴心”照着以前的签字抄一遍。某种意义上来说,这就是签名重放。 在区块链中,数字签名可以用于识别数据签名者和验证数据完整性。发送交易时,用户使用私钥签名交易,使得其他人可以验证交易是由相应账户发出的。智能合约也能利用 ECDSA 算法验证用户将在链下创建的签名,然后执行铸造或转账等逻辑。更多关于数字签名的介绍请见WTF Solidity第37讲:数字签名。 数字签名一般有两种常见的重放攻击...
WTF Academy: wtf.academy



WTF Solidity 合约安全: S08. 绕过合约检查
我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 推特:@0xAA_Science|@WTFAcademy_ 社区:Discord|微信群|官网 wtf.academy 所有代码和教程开源在github: github.com/AmazingAng/WTFSolidity这一讲,我们将介绍绕过合约长度检查,并介绍预防的方法。绕过合约检查很多 freemint 的项目为了限制科学家(程序员)会用到 isContract() 方法,希望将调用者 msg.sender 限制为外部账户(EOA),而非合约。这个函数利用 extcodesize 获取该地址所存储的 bytecode 长度(runtime),若大于0,则判断为合约,否则就是EOA(用户)。 // 利用 extcodesize 检查是否为合约 function isContract(address account) public view returns (bool) { // extcodesize > 0 的地址一定是合约...

WTF Solidity 合约安全: S09. 拒绝服务
我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 推特:@0xAA_Science|@WTFAcademy_ 社区:Discord|微信群|官网 wtf.academy 所有代码和教程开源在github: github.com/AmazingAng/WTFSolidity这一讲,我们将介绍智能合约的拒绝服务(Denial of Service, DoS)漏洞,并介绍预防的方法。NFT项目 Akutar 曾因为 DoS 漏洞损失 11,539 ETH,当时价值 3400 万美元。DoS在 Web2 中,拒绝服务攻击(DoS)是指通过向服务器发送大量垃圾信息或干扰信息的方式,导致服务器无法向正常用户提供服务的现象。而在 Web3,它指的是利用漏洞使得智能合约无法正常提供服务。 在2022年4月,一个很火的 NFT 项目名为 Akutar,他们使用荷兰拍卖进行公开发行,筹集了 11,539.5 ETH,非常成功。之前持有他们社区Pass的参与者会得到 0.5 ETH的退款,但是他们处理...

WTF Solidity 合约安全 S06. 签名重放
我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 推特:@0xAA_Science|@WTFAcademy_ 社区:Discord|微信群|官网 wtf.academy 所有代码和教程开源在github: github.com/AmazingAng/WTFSolidity这一讲,我们将介绍智能合约的签名重放(Signature Replay)攻击和预防方法,它曾间接导致了著名做市商 Wintermute 被盗2000万枚 $OP。签名重放上学的时候,老师经常会让家长签字,有时候家长很忙,我就会很“贴心”照着以前的签字抄一遍。某种意义上来说,这就是签名重放。 在区块链中,数字签名可以用于识别数据签名者和验证数据完整性。发送交易时,用户使用私钥签名交易,使得其他人可以验证交易是由相应账户发出的。智能合约也能利用 ECDSA 算法验证用户将在链下创建的签名,然后执行铸造或转账等逻辑。更多关于数字签名的介绍请见WTF Solidity第37讲:数字签名。 数字签名一般有两种常见的重放攻击...
Share Dialog
Share Dialog
WTF Academy: wtf.academy

Subscribe to 0xAA

Subscribe to 0xAA
我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。
WTF Academy社群: 官网 wtf.academy | discord | 微信群申请
所有代码和教程开源在github: github.com/AmazingAng/WTFSolidity
这一讲,我们介绍分账合约,该合约允许将ETH按权重转给一组账户中,进行分账。代码部分由oppenzepplin库的PaymentSplitter合约简化而来。
分账就是按照一定比例分钱财。在现实中,经常会有“分赃不均”的事情发生;而在区块链的世界里,Code is Law,我们可以事先把每个人应分的比例写在智能合约中,获得收入后,再由智能合约来进行分账。

分账合约(PaymentSplit)具有以下几个特点:
在创建合约时定好分账受益人payees和每人的份额shares。
份额可以是相等,也可以是其他任意比例。
在该合约收到的所有ETH中,每个受益人将能够提取与其分配的份额成比例的金额。
分账合约遵循Pull Payment模式,付款不会自动转入账户,而是保存在此合约中。受益通过调用release()函数触发实际转账。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/**
* 分账合约
* @dev 这个合约会把收到的ETH按事先定好的份额分给几个账户。收到ETH会存在分账合约中,需要每个受益人调用release()函数来领取。
*/
contract PaymentSplit{
分账合约中共有3个事件:
PayeeAdded:增加受益人事件。
PaymentReleased:受益人提款事件。
PaymentReceived:分账合约收款事件。
// 事件
event PayeeAdded(address account, uint256 shares); // 增加受益人事件
event PaymentReleased(address to, uint256 amount); // 受益人提款事件
event PaymentReceived(address from, uint256 amount); // 合约收款事件
分账合约中共有5个状态变量,用来记录受益地址、份额、支付出去的ETH等变量:
totalShares:总份额,为shares的和。
totalReleased:从分账合约向受益人支付出去的ETH,为released的和。
payees:address数组,记录受益人地址
shares:address到uint256的映射,记录每个受益人的份额。
released:address到uint256的映射,记录分账合约支付给每个受益人的金额。
uint256 public totalShares; // 总份额
uint256 public totalReleased; // 总支付
mapping(address => uint256) public shares; // 每个受益人的份额
mapping(address => uint256) public released; // 支付给每个受益人的金额
address[] public payees; // 受益人数组
分账合约中共有6个函数:
构造函数:始化受益人数组_payees和分账份额数组_shares,其中数组长度不能为0,两个数组长度要相等。_shares中元素要大于0,_payees中地址不能为0地址且不能有重复地址。
receive():回调函数,在分账合约收到ETH时释放PaymentReceived事件。
release():分账函数,为有效受益人地址_account分配相应的ETH。任何人都可以触发这个函数,但ETH会转给受益人地址account。调用了releasable()函数。
releasable():计算一个受益人地址应领取的ETH。调用了pendingPayment()函数。
pendingPayment():根据受益人地址_account, 分账合约总收入_totalReceived和该地址已领取的钱_alreadyReleased,计算该受益人现在应分的ETH。
_addPayee():新增受益人函数及其份额函数。在合约初始化的时候被调用,之后不能修改。
/**
* @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直接发送到受益人地址。任何人都可以触发这个函数,但钱会打给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计算account应得的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 新增受益人_acount以及对应的份额_acountShares。只能在构造器中被调用,不能修改。
*/
function _addPayee(address _account, uint256 _acountShares) private {
// 检查_account不为0地址
require(_account != address(0), "PaymentSplitter: account is the zero address");
// 检查_acountShares不为0
require(_acountShares > 0, "PaymentSplitter: shares are 0");
// 检查_account不重复
require(shares[_account] == 0, "PaymentSplitter: account already has shares");
// 更新payees,shares和totalShares
payees.push(_account);
shares[_account] = _acountShares;
totalShares += _acountShares;
// 释放增加受益人事件
emit PayeeAdded(_account, _acountShares);
}
在构造函数中,输入两个受益人地址,份额为1和3。
这一讲,我们介绍了分账合约。在区块链的世界里,Code is Law,我们可以事先把每个人应分的比例写在智能合约中,获得收入后,由智能合约来进行分账,避免事后“分赃不均”。
我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。
WTF Academy社群: 官网 wtf.academy | discord | 微信群申请
所有代码和教程开源在github: github.com/AmazingAng/WTFSolidity
这一讲,我们介绍分账合约,该合约允许将ETH按权重转给一组账户中,进行分账。代码部分由oppenzepplin库的PaymentSplitter合约简化而来。
分账就是按照一定比例分钱财。在现实中,经常会有“分赃不均”的事情发生;而在区块链的世界里,Code is Law,我们可以事先把每个人应分的比例写在智能合约中,获得收入后,再由智能合约来进行分账。

分账合约(PaymentSplit)具有以下几个特点:
在创建合约时定好分账受益人payees和每人的份额shares。
份额可以是相等,也可以是其他任意比例。
在该合约收到的所有ETH中,每个受益人将能够提取与其分配的份额成比例的金额。
分账合约遵循Pull Payment模式,付款不会自动转入账户,而是保存在此合约中。受益通过调用release()函数触发实际转账。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/**
* 分账合约
* @dev 这个合约会把收到的ETH按事先定好的份额分给几个账户。收到ETH会存在分账合约中,需要每个受益人调用release()函数来领取。
*/
contract PaymentSplit{
分账合约中共有3个事件:
PayeeAdded:增加受益人事件。
PaymentReleased:受益人提款事件。
PaymentReceived:分账合约收款事件。
// 事件
event PayeeAdded(address account, uint256 shares); // 增加受益人事件
event PaymentReleased(address to, uint256 amount); // 受益人提款事件
event PaymentReceived(address from, uint256 amount); // 合约收款事件
分账合约中共有5个状态变量,用来记录受益地址、份额、支付出去的ETH等变量:
totalShares:总份额,为shares的和。
totalReleased:从分账合约向受益人支付出去的ETH,为released的和。
payees:address数组,记录受益人地址
shares:address到uint256的映射,记录每个受益人的份额。
released:address到uint256的映射,记录分账合约支付给每个受益人的金额。
uint256 public totalShares; // 总份额
uint256 public totalReleased; // 总支付
mapping(address => uint256) public shares; // 每个受益人的份额
mapping(address => uint256) public released; // 支付给每个受益人的金额
address[] public payees; // 受益人数组
分账合约中共有6个函数:
构造函数:始化受益人数组_payees和分账份额数组_shares,其中数组长度不能为0,两个数组长度要相等。_shares中元素要大于0,_payees中地址不能为0地址且不能有重复地址。
receive():回调函数,在分账合约收到ETH时释放PaymentReceived事件。
release():分账函数,为有效受益人地址_account分配相应的ETH。任何人都可以触发这个函数,但ETH会转给受益人地址account。调用了releasable()函数。
releasable():计算一个受益人地址应领取的ETH。调用了pendingPayment()函数。
pendingPayment():根据受益人地址_account, 分账合约总收入_totalReceived和该地址已领取的钱_alreadyReleased,计算该受益人现在应分的ETH。
_addPayee():新增受益人函数及其份额函数。在合约初始化的时候被调用,之后不能修改。
/**
* @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直接发送到受益人地址。任何人都可以触发这个函数,但钱会打给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计算account应得的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 新增受益人_acount以及对应的份额_acountShares。只能在构造器中被调用,不能修改。
*/
function _addPayee(address _account, uint256 _acountShares) private {
// 检查_account不为0地址
require(_account != address(0), "PaymentSplitter: account is the zero address");
// 检查_acountShares不为0
require(_acountShares > 0, "PaymentSplitter: shares are 0");
// 检查_account不重复
require(shares[_account] == 0, "PaymentSplitter: account already has shares");
// 更新payees,shares和totalShares
payees.push(_account);
shares[_account] = _acountShares;
totalShares += _acountShares;
// 释放增加受益人事件
emit PayeeAdded(_account, _acountShares);
}
在构造函数中,输入两个受益人地址,份额为1和3。
这一讲,我们介绍了分账合约。在区块链的世界里,Code is Law,我们可以事先把每个人应分的比例写在智能合约中,获得收入后,由智能合约来进行分账,避免事后“分赃不均”。
>100 subscribers
>100 subscribers
No activity yet