区块链智能合约( solidity) 安全编程

news2024/12/14 5:31:51

引言:本文由天玄链开源开发者提供,欢迎报名公益天玄链训练营

https://blockchain.163.com/trainingCamp

一、重入和竞态

重入和竞态在solidity 编程安全中会多次提及,历史上也造成了重大的损失。

1.1 问题分析

竞态的描述不严格,竞态是在并发编程中的概念。而以太坊智能合约是单线程运行的。不存在并发。它的问题来自于重入。

我们在实现智能合约无法做到可重入的,所以就要添加重入的保护。一般情况下,重入问题来自于fallback 或者 recevie 方法,因为以太坊转账是最常用的功能。

当然如果调用了其他不安全的合约一样有这个问题。

1.2 可重入保护

一般的解决方案,“检查-生效-交互”,这个方法是现在常用的,也是推荐使用的。

function untrustedWithdraw(address recipient) public {

    uint amountToWithdraw = userBalances[recipient];

    rewardsForA[recipient] = 0;

    if (!(recipient.call.value(amountToWithdraw)())) { throw; }

}

  

function untrustedGetFirstWithdrawalBonus(address recipient) public {

    if (claimedBonus[recipient]) { throw; } // 检查

    claimedBonus[recipient] = true;//生效

    rewardsForA[recipient] += 100;

    untrustedWithdraw(recipient); // 交互

}

另外,既然它表现的像多线程并发。还有一种解决方案,就是加锁。openzeppelin 中 ReentrancyGuard合约中的nonReentrant modifier也是阻止重入的一种方法。

但是需要注意的是,这个modifier  只能对单个方法起作用,需要在所有状态相关方法上添加才能防止交叉重入。

综上,“检查-生效-交互” 推荐使用来防止重入。

 1.3 显示标示untrust

在调用不安全外部合约的方法时,需要将该方法标示为untrust。同时,调用该untrust 的方法也应该是untrust。

对待untrust 方法规范是,首先处理完内部状态,最后调用untrust 方法。向外部地址转账,显然也是调用了外部合约方法,属于untrust 调用

二、安全转账

在uniswap中有很多转账的范例,比如下面的代码,将常用的ERC20 转账,注意,在safeTransferETH 中,它使用了to.call 而不是 to.transfer,因为而.send 或者 .transfer 是发送固定gas,

 solidity 升级后,一些操作码的gas 可能有变化。这就导致之前的fallback 方法可能会失败。对建立在uniswap上的生态造成影响。

但是直接使用.call 有很多风险,比如要校验返回值,再比如重入攻击问题。所以尽量使用成熟的范式,是增强代码安全的有效手段。这里可能引入的重入风险,我们后面再单独分析。

function safeTransfer(

    address token,

    address to,

    uint256 value

) internal {

    // bytes4(keccak256(bytes('transfer(address,uint256)')));

    (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value));

    require(

        success && (data.length == 0 || abi.decode(data, (bool))),

        'TransferHelper::safeTransfer: transfer failed'

    );

}

function safeTransferFrom(

    address token,

    address from,

    address to,

    uint256 value

) internal {

    // bytes4(keccak256(bytes('transferFrom(address,address,uint256)')));

    (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value));

    require(

        success && (data.length == 0 || abi.decode(data, (bool))),

        'TransferHelper::transferFrom: transferFrom failed'

    );

}

function safeTransferETH(address to, uint256 value) internal {

    (bool success, ) = to.call{value: value}(new bytes(0));

    require(success, 'TransferHelper::safeTransferETH: ETH transfer failed');

}

三、以太发送方法

3.1 常用方法

常用的两种方法,send, transfer

send与transfer对比简析

相同之处

(1)均是向address发送ETH(以Wei做单位)

(2)发送的同时传输2300gas(gas数量很少,只允许接收方合约执行最简单的操作)

不同之处

(1)send执行失败返回false,transfer执行失败则会throw。这也就意味着使用send时一定要判断是否执行成功。

推荐

(1)默认情况下最好使用transfer(因为内置了执行失败的处理)

安全推荐是transfer,相信大家都知道。那么为什么transfer 更好,在于它是更高级的方法,封装了校验返回值,并抛出异常。

3.2 引申一下

我想引申一点是,在我们写方法的时候,当校验时,应该使用require 而不是 if,使用if 使得方法没有完成必要逻辑,而交易成功。这其实有隐患的。

除非你非常清楚这个差别,并认为只有if 才能满足函数需求。

比如有一个编程模式做频率检查,模式设计推荐的是

modifier enabledEvery(uint t) {   

  if (now >= enabledAt) {

    enabledAt = now + t;

    _;

  }

}

其实这个设计就有我上面说的问题,虽然达到了限制频率的目的,但是整个交易时成功的。正常使用就会有很多困惑,一旦被集成到其他合约,就会造成攻击的隐患。

建议是使用require,一旦失败,交易会revert,而且原因清晰。

modifier enabledEvery(uint t) {

  require(now >= enabledAt,"too freq, wait a minute");

  enabledAt = now + t;

  _;

3.3 转账成功的条件

如果对方是合约地址,那么它需要具备receive 方法,或者payable 的fallback 方法。如果两者都没有则转账失败。但是从另一面说,没有这两个方法不代表不能收取以太。

这还是要从概念上区分一下。

这就引申出两个问题,

3.3.1 合约为用户转账

要考虑到失败的可能。尽量使用pull 而不是push 方法。也就是让用用户发起withdraw收取以太,而不是合约dispatch。 

3.3.2 合约需要收帐的。

即便没有设置payable 的fallback,和 receive 方法,你依然不能阻止绕过收款方法,直接转账给合约,使得合约balance 跟合约内账本对不上。

所以,业务逻辑要使用自己的账本。

3.3.3 非标准ERC20 的影响

业务逻辑建立在自己的账本上就够了吗?对于有些token 虽然符合ERC 标准,但是具体实现却又做了一些改动,比如有个项目做了转账收手续费的操作,

在转账过程中收取。也就是实际到账比交易执行的要少,如果只关注交易是否成功,然后记账是造成漏洞,所以这种情况,还需要去查balance 的变化。

以上是在转账过程需要注意的,不仅要合约记录账本,还需要核对balance,以保证业务逻辑没有漏洞。

当然如果业务确信没有此类问题。可以简化,比如只涉及USDT 的入账,那么可以不考虑3.3.3。

四、慎用tx.origin

永远不要 tx.origin 用于授权,另一个合约可以有一个方法来调用你的合约(例如,用户有一些资金)并且你的合约将授权该交易,因为你的地址位于tx.origin.

contract MyContract {

    address owner;

    function MyContract() public {
        owner = msg.sender;
    }

    function sendTo(address receiver, uint amount) public {
        require(tx.origin == owner);
        (bool success, ) = receiver.call.value(amount)("");
        require(success);
    }

}

contract AttackingContract {

    MyContract myContract;
    address attacker;

    function AttackingContract(address myContractAddress) public {
        myContract = MyContract(myContractAddress);
        attacker = msg.sender;
    }

    function() public {
        myContract.sendTo(attacker, msg.sender.balance);
    }

}

这是最常见的错误。

如果我们用tx.origin 能用来干嘛?我们先引申一下。EOA 的概念。

4.1 如何判断一个账户是EOA 账户而不是contract。

一种做法是如下,很多业务是这么写的。但其实是有问题。某些情况会失效,比如constructor中。

/** @dev Modifier requiring sender to be EOA.  This check could be bypassed by a malicious

 *  contract via initcode, but it takes care of the user error we want to avoid.

 */

modifier onlyEOA() {

    // Used to stop deposits from contracts (avoid accidentally lost tokens)

    require(!Address.isContract(msg.sender), "Account not EOA");

    _;

}

那么如何判断EOA 呢,这里就用到了tx.origin. 

    modifier onlyEOA() {

        // Used to stop deposits from contracts (avoid accidentally lost tokens)

        require(tx.origin == msg.sender, "Account not EOA");

        _;

    }

  

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2259133.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【深度学习量化交易7】miniQMT快速上手教程案例集——使用xtQuant进行历史数据下载篇

我是Mr.看海,我在尝试用信号处理的知识积累和思考方式做量化交易,应用深度学习和AI实现股票自动交易,目的是实现财务自由~ 目前我正在开发基于miniQMT的量化交易系统。 在前几篇的文章中讲到,我正在开发的看海量化交易系统&#x…

相差不超过k的最多数,最长公共子序列(一),排序子序列,体操队形,青蛙过河

相差不超过k的最多数 链接:相差不超过k的最多数 来源:牛客网 题目描述: 给定一个数组,选择一些数,要求选择的数中任意两数差的绝对值不超过 𝑘 。问最多能选择多少个数? 输入描述: 第一行输入两个正整…

解决navicat 导出excel数字为科学计数法问题

一、原因分析 用程序导出的csv文件,当字段中有比较长的数字字段存在时,在用excel软件查看csv文件时就会变成科学技术法的表现形式。 其实这个问题跟用什么语言导出csv文件没有关系。Excel显示数字时,如果数字大于12位,它会自动转化…

C++3--内联函数、auto

1.内联函数 1.1概念 以inline修饰的函数叫做内联函数,编译时C编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序的效率 如果在上述函数前增加inline关键字将其改成内联函数,在编译期间编译器会用函…

AES 与 SM4 加密算法:深度解析与对比

🧑 博主简介:CSDN博客专家,历代文学网(PC端可以访问:https://literature.sinhy.com/#/literature?__c1000,移动端可微信小程序搜索“历代文学”)总架构师,15年工作经验,…

视频怎么转音频mp3?5种视频转音频的方法

在视频剪辑时,将视频中的音频提取出来并转换为MP3格式已成为许多人的需求。无论是为了制作音乐播放列表、剪辑音频片段,还是为了在其他设备上更方便地播放,将视频转换为音频MP3都显得尤为重要。下面将介绍五种实用的方法,帮助你轻…

Maven学习(传统Jar包管理、Maven依赖管理(导入坐标)、快速下载指定jar包)

目录 一、传统Jar包管理。 (1)基本介绍。 (2)传统的Jar包导入方法。 1、手动寻找Jar包。并放置到指定目录下。 2、使用IDEA的库管理功能。 3、配置环境变量。 (3)传统的Jar包管理缺点。 二、Maven。 &#…

【机器学习】分类器

在机器学习(Machine Learning,ML)中,分类器泛指算法或模型,用于将输入数据分为不同的类别或标签。分类器是监督学习的一部分,它依据已知的数据集中的特征和标签进行训练,并根据这些学习到的知识对新的未标记数据进行分…

uni-app在image上绘制点位并回显

在 Uni-app 中绘制多边形可以通过使用 Canvas API 来实现。Uni-app 是一个使用 Vue.js 开发所有前端应用的框架,同时支持编译为 H5、小程序等多个平台。由于 Canvas 是 H5 和小程序中都支持的 API,所以通过 Canvas 绘制多边形是一个比较通用的方法。 1.…

【机器学习与数据挖掘实战】案例01:基于支持向量回归的市财政收入分析

【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈机器学习与数据挖掘实战 ⌋ ⌋ ⌋ 机器学习是人工智能的一个分支,专注于让计算机系统通过数据学习和改进。它利用统计和计算方法,使模型能够从数据中自动提取特征并做出预测或决策。数据挖掘则是从大型数…

Spire.PDF for .NET【页面设置】演示:向 PDF 文档添加页码

在 PDF 文档中添加页码不仅实用,而且美观,因为它提供了类似于专业出版材料的精美外观。无论您处理的是小说、报告还是任何其他类型的长文档的数字副本,添加页码都可以显著提高其可读性和实用性。在本文中,您将学习如何使用Spire.P…

【iOS】OC高级编程 iOS多线程与内存管理阅读笔记——自动引用计数(三)

目录 ARC规则 概要 所有权修饰符 __strong修饰符 __weak修饰符 __unsafe_unretained修饰符 __autoreleasing修饰符 ARC规则 概要 “引用计数式内存管理”的本质部分在ARC中并没有改变,ARC只是自动地帮助我们处理“引用计数”的相关部分。 在编译单位上可以…

An error happened while trying to locate the file on the Hub and we cannot f

An error happened while trying to locate the file on the Hub and we cannot find the requested files in the local cache. Please check your connection and try again or make sure your Internet connection is on. 关于上述comfy ui使用control net预处理器的报错问…

angular19-官方教程学习

周日了解到angular已经更新到19了,想按官方教程学习一遍,工欲善其事必先利其器,先更新工具: 安装新版版本 卸载老的nodejs 20.10.0,安装最新的LTS版本 https://nodejs.org 最新LTS版本已经是22.12.0 C:\Program File…

计算机毕业设计Python+Vue.js游戏推荐系统 Steam游戏推荐系统 Django Flask 游 戏可视化 游戏数据分析 游戏大数据 爬虫 机

温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 作者简介:Java领…

上海亚商投顾:创业板指震荡调整 机器人概念股再度爆发

上海亚商投顾前言:无惧大盘涨跌,解密龙虎榜资金,跟踪一线游资和机构资金动向,识别短期热点和强势个股。 一.市场情绪 沪指昨日冲高回落,深成指、创业板指盘中跌超1%,尾盘跌幅有所收窄。机器人概念股逆势爆…

粘贴可运行:Java调用大模型(LLM) 流式Flux stream 输出;基于spring ai alibaba

在Java中,使用Spring AI Alibaba框架调用国产大模型通义千问,实现流式输出,是一种高效的方式。通过Spring AI Alibaba,开发者可以轻松地集成通义千问模型,并利用其流式处理能力,实时获取模型生成的文本。这…

【CSS in Depth 2 精译_070】11.3 利用 OKLCH 颜色值来处理 CSS 中的颜色问题(下):从页面其他颜色衍生出新颜色

当前内容所在位置(可进入专栏查看其他译好的章节内容) 第四部分 视觉增强技术 ✔️【第 11 章 颜色与对比】 ✔️ 11.1 通过对比进行交流 11.1.1 模式的建立11.1.2 还原设计稿 11.2 颜色的定义 11.2.1 色域与色彩空间11.2.2 CSS 颜色表示法 11.2.2.1 RGB…

Ajax--实现检测用户名是否存在功能

目录 (一)什么是Ajax (二)同步交互与异步交互 (三)AJAX常见应用情景 (四)AJAX的优缺点 (五)使用jQuery实现AJAX 1.使用JQuery中的ajax方法实现步骤&#xf…

【PSINS】以速度和位置作为观测量(即6维观测量)的组合导航滤波,EKF实现,提供可直接运行的MATLAB代码

原有的代码是以位置作为观测量的,这里提供位置+速度,共6维的观测量,状态量还是15维不变 文章目录 源代码运行结果PS源代码 源代码如下: % 【PSINS】位置与速度为观测的153,EKF。从速度观测的EKF153改进而来 % 2024-12-11/Ver1:位置与速度为观测量% 清空工作空间,清除命…