Web3-代币ERC20/ERC721以及合约安全溢出和下溢的研究

Web3-代币ERC20/ERC721以及合约安全溢出和下溢的研究

以太坊上的代币

如果你对以太坊的世界有一些了解,你很可能听人们聊过代币— ERC20代币

一个 代币 在以太坊基本上就是一个遵循一些共同规则的智能合约——即它实现了所有其他代币合约共享的一组标准函数,例如 transfer(address _to, uint256 _value)balanceOf(address _owner)

在智能合约内部,通常有一个映射, mapping(address => uint256) balances,用于追踪每个地址还有多少余额。

所以基本上一个代币只是一个追踪谁拥有多少该代币的合约,和一些可以让那些用户将他们的代币转移到其他地址的函数。

ERC20的重要性

由于所有 ERC20 代币共享具有相同名称的同一组函数,它们都可以以相同的方式进行交互。

这意味着如果你构建的应用程序能够与一个 ERC20 代币进行交互,那么它就也能够与任何 ERC20 代币进行交互。 这样一来,将来你就可以轻松地将更多的代币添加到你的应用中,而无需进行自定义编码。 你可以简单地插入新的代币合约地址,然后哗啦,你的应用程序有另一个它可以使用的代币了。

其中一个例子就是交易所。 当交易所添加一个新的 ERC20 代币时,实际上它只需要添加与之对话的另一个智能合约。 用户可以让那个合约将代币发送到交易所的钱包地址,然后交易所可以让合约在用户要求取款时将代币发送回给他们。

交易所只需要实现这种转移逻辑一次,然后当它想要添加一个新的 ERC20 代币时,只需将新的合约地址添加到它的数据库即可。

其他代币标准

对于像货币一样的代币来说,ERC20 代币非常酷。 但是要在我们僵尸游戏中代表僵尸就并不是特别有用。

首先,僵尸不像货币可以分割 —— 我可以发给你 0.237 以太,但是转移给你 0.237 的僵尸听起来就有些搞笑。

其次,并不是所有僵尸都是平等的。 你的2级僵尸"Steve"完全不能等同于我732级的僵尸"H4XF13LD MORRIS 💯💯😎💯💯"。(你差得远呢,Steve)。

有另一个代币标准更适合如 CryptoZombies 这样的加密收藏品——它们被称为*ERC721 代币.*

*ERC721 代币*能互换的,因为每个代币都被认为是唯一且不可分割的。 你只能以整个单位交易它们,并且每个单位都有唯一的 ID。 这些特性正好让我们的僵尸可以用来交易。

请注意,使用像 ERC721 这样的标准的优势就是,我们不必在我们的合约中实现拍卖或托管逻辑,这决定了玩家能够如何交易/出售我们的僵尸。 如果我们符合规范,其他人可以为加密可交易的 ERC721 资产搭建一个交易所平台,我们的 ERC721 僵尸将可以在该平台上使用。 所以使用代币标准相较于使用你自己的交易逻辑有明显的好处。

ERC721标准 多重继承

我们先看看ERC721标准

contract ERC721 {event Transfer(address indexed _from, address indexed _to, uint256 _tokenId);event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId);function balanceOf(address _owner) public view returns (uint256 _balance);function ownerOf(uint256 _tokenId) public view returns (address _owner);function transfer(address _to, uint256 _tokenId) public;function approve(address _to, uint256 _tokenId) public;function takeOwnership(uint256 _tokenId) public;
}

我们在代码中应该如何使用ERC721,我们这边首先应用erc721,然后再继承他

pragma solidity ^0.4.19;import "./zombieattack.sol";
// 在这里引入文件
import "./erc721.sol";
// 在这里声明 ERC721 的继承
contract ZombieOwnership is ZombieAttack, ERC721 {
}
balanceOf和ownerOf

我们将实现两个方法balanceOf和ownerOf

balanceOf:这个函数只需要一个传入 address 参数,然后返回这个 address 拥有多少代币。

  function balanceOf(address _owner) public view returns (uint256 _balance);

ownerOf:这个函数需要传入一个代币 ID 作为参数 (我们的情况就是一个僵尸 ID),然后返回该代币拥有者的 address

  function ownerOf(uint256 _tokenId) public view returns (address _owner);
ERC721转移标准

把所有权从一个人转移给另一个人来继续我们的 ERC721 规范的实现。

注意 ERC721 规范有两种不同的方法来转移代币:

function transfer(address _to, uint256 _tokenId) public;function approve(address _to, uint256 _tokenId) public;
function takeOwnership(uint256 _tokenId) public;
  1. 第一种方法是代币的拥有者调用transfer 方法,传入他想转移到的 address 和他想转移的代币的 _tokenId
  2. 第二种方法是代币拥有者首先调用 approve,然后传入与以上相同的参数。接着,该合约会存储谁被允许提取代币,通常存储到一个 mapping (uint256 => address) 里。然后,当有人调用 takeOwnership 时,合约会检查 msg.sender 是否得到拥有者的批准来提取代币,如果是,则将代币转移给他。

你注意到了吗,transfertakeOwnership 都将包含相同的转移逻辑,只是以相反的顺序。 (一种情况是代币的发送者调用函数;另一种情况是代币的接收者调用它)。

所以我们把这个逻辑抽象成它自己的私有函数 _transfer,然后由这两个函数来调用它。 这样我们就不用写重复的代码了。

ERC721 批准 approve

现在,让我们来实现 approve

记住,使用 approve 或者 takeOwnership 的时候,转移有2个步骤:

  1. 你,作为所有者,用新主人的 address 和你希望他获取的 _tokenId 来调用 approve
  2. 新主人用 _tokenId 来调用 takeOwnership,合约会检查确保他获得了批准,然后把代币转移给他。

因为这发生在2个函数的调用中,所以在函数调用之间,我们需要一个数据结构来存储什么人被批准获取什么。

合约安全增强:溢出和下溢

在编写智能合约的时候需要注意的一个主要的安全特性:防止溢出和下溢。

什么是 溢出 (*overflow*)?

假设我们有一个 uint8, 只能存储8 bit数据。这意味着我们能存储的最大数字就是二进制 11111111 (或者说十进制的 2^8 - 1 = 255).

来看看下面的代码。最后 number 将会是什么值?

uint8 number = 255;
number++;

在这个例子中,我们导致了溢出 — 虽然我们加了1, 但是 number 出乎意料地等于 0了。 (如果你给二进制 11111111 加1, 它将被重置为 00000000,就像钟表从 23:59 走向 00:00)。

下溢(underflow)也类似,如果你从一个等于 0uint8 减去 1, 它将变成 255 (因为 uint 是无符号的,其不能等于负数)。

虽然我们在这里不使用 uint8,而且每次给一个 uint2561 也不太可能溢出 (2^256 真的是一个很大的数了),在我们的合约中添加一些保护机制依然是非常有必要的,以防我们的 DApp 以后出现什么异常情况。

使用SafeMath

为了防止这些情况,OpenZeppelin 建立了一个叫做 SafeMath 的 (*library*),默认情况下可以防止这些问题。

不过在我们使用之前…… 什么叫做库?

一个**** 是 Solidity 中一种特殊的合约。其中一个有用的功能是给原始数据类型增加一些方法。

比如,使用 SafeMath 库的时候,我们将使用 using SafeMath for uint256 这样的语法。 SafeMath 库有四个方法 — addsubmul, 以及 div。现在我们可以这样来让 uint256 调用这些方法:

using SafeMath for uint256;uint256 a = 5;
uint256 b = a.add(3); // 5 + 3 = 8
uint256 c = a.mul(2); // 5 * 2 = 10

SafeMath的部分核心代码

library SafeMath {function mul(uint256 a, uint256 b) internal pure returns (uint256) {if (a == 0) {return 0;}uint256 c = a * b;assert(c / a == b);return c;}function div(uint256 a, uint256 b) internal pure returns (uint256) {// assert(b > 0); // Solidity automatically throws when dividing by 0uint256 c = a / b;// assert(a == b * c + a % b); // There is no case in which this doesn't holdreturn c;}function sub(uint256 a, uint256 b) internal pure returns (uint256) {assert(b <= a);return a - b;}function add(uint256 a, uint256 b) internal pure returns (uint256) {uint256 c = a + b;assert(c >= a);return c;}
}

首先我们有了 library 关键字 — 库和 合约很相似,但是又有一些不同。 就我们的目的而言,库允许我们使用 using 关键字,它可以自动把库的所有方法添加给一个数据类型:

using SafeMath for uint;
// 这下我们可以为任何 uint 调用这些方法了
uint test = 2;
test = test.mul(3); // test 等于 6 了
test = test.add(5); // test 等于 11 了

注意 muladd 其实都需要两个参数。 在我们声明了 using SafeMath for uint 后,我们用来调用这些方法的 uint 就自动被作为第一个参数传递进去了(在此例中就是 test)

我们来看看 add 的源代码看 SafeMath 做了什么:

function add(uint256 a, uint256 b) internal pure returns (uint256) {uint256 c = a + b;assert(c >= a);return c;
}

基本上 add 只是像 + 一样对两个 uint 相加, 但是它用一个 assert 语句来确保结果大于 a。这样就防止了溢出。

assertrequire 相似,若结果为否它就会抛出错误。 assertrequire 区别在于,require 若失败则会返还给用户剩下的 gas, assert 则不会。所以大部分情况下,你写代码的时候会比较喜欢 requireassert 只在代码可能出现严重错误的时候使用,比如 uint 溢出。

所以简而言之, SafeMath 的 addsubmul, 和 div 方法只做简单的四则运算,然后在发生溢出或下溢的时候抛出错误。

通常情况下,总是使用 SafeMath 而不是普通数学运算是个好主意,也许在以后 Solidity 的新版本里这点会被默认实现,但是现在我们得自己在代码里实现这些额外的安全措施。

不过我们遇到个小问题 — winCountlossCountuint16, 而 leveluint32。 所以如果我们用这些作为参数传入 SafeMath 的 add 方法。 它实际上并不会防止溢出,因为它会把这些变量都转换成 uint256:

function add(uint256 a, uint256 b) internal pure returns (uint256) {uint256 c = a + b;assert(c >= a);return c;
}// 如果我们在`uint8` 上调用 `.add`。它将会被转换成 `uint256`.
// 所以它不会在 2^8 时溢出,因为 256 是一个有效的 `uint256`.

这就意味着,我们需要再实现两个库来防止 uint16uint32 溢出或下溢。我们可以将其命名为 SafeMath16SafeMath32

代码将和 SafeMath 完全相同,除了所有的 uint256 实例都将被替换成 uint32uint16

我们已经将这些代码帮你写好了,打开 safemath.sol 合约看看代码吧。

总结

本文研究了以太坊智能合约中代币标准ERC20/ERC721的实现及其安全问题。首先介绍了ERC20代币作为可互换资产的合约标准,分析了其balanceOf和transfer等核心功能。其次探讨了ERC721代币作为不可互换资产的特性,详细说明了其多重继承的实现方式。文章重点分析了ERC721的两种所有权转移机制:直接transfer和approve/takeOwnership组合。最后强调了智能合约安全中防范数据溢出和下溢的重要性,建议使用SafeMath库来确保数值运算的安全性。通过标准代币接口和安全数学运算,开发者可以构建更可靠、更安全的去中心化应用。

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

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

相关文章

论文笔记 <交通灯><多智能体>MetaLight:基于价值的元强化学习用于交通信号控制

今天看的论文是这篇MetaLight:基于价值的元强化学习用于交通信号控制 里面提到的创新点就是MetaLight框架&#xff1a;他目标是让交通信号控制智能体&#xff08;Agent&#xff09;在新路口&#xff08;即使结构或流量模式不同&#xff09;上能​​快速学习​​&#xff08;Few…

华为OD-2024年E卷-寻找符合要求的最长子串[200分] -- python

问题描述&#xff1a; 给定一个字符串s&#xff0c;找出这样一个子串: 1)该子串中的任意一个字符最多出现2次; 2)该子串不包含指定某个字符; 请你找出满足该条件的最长子串的长度。 输入描述 第一行为要求不包含的指定字符&#xff0c;为单个字符&#xff0c;取值范围[0-9a-zA…

CppCon 2016 学习:What C++ Programmers Need to Know about Header <random>

随机数生成的历史背景 Middle-Square 方法&#xff08;中位平方法&#xff09;&#xff1a; 已知最早的随机算法之一或由修道士 Brother Edvin 在 1245 年发明由 John von Neumann 在 1949 年重新发现缺点明显&#xff0c;但执行速度快 Monte Carlo 方法&#xff1a; 起初是…

Origin:误差棒点线图绘制

1.首先将你的数据复制到表格 2.选中B(y)列数据&#xff0c;依次点击图示选项 3.选中图中红框数据&#xff0c;点击绘制点线图即可 4.结果展示

Spring 源码学习 1:ApplicationContext

Spring 源码学习 1&#xff1a;ApplicationContext Bean 定义和 Bean 实例 AnnotationConfigApplicationContext 首先&#xff0c;创建一个最简单的 Spring Boot 应用。 在入口类中接收SpringApplication.run的返回值&#xff1a; SpringBootApplication public class Dem…

CppCon 2017 学习:Design Patterns for Low-Level Real-Time Rendering

这段内容讲的是离散显卡&#xff08;Discrete GPU&#xff09;中的内存管理模型&#xff0c;重点是CPU和GPU各自独立管理自己的物理内存&#xff0c;以及它们如何通过虚拟内存和DMA引擎实现高效通信。以下是详细的理解和梳理&#xff1a; 1. 基本概念 CPU 和 GPU 是两个独立的…

【单调队列】-----【原理+模版】

单调队列 一、什么是单调队列&#xff1f; 单调队列是一种在滑动窗口或区间查询中维护候选元素单调性的数据结构&#xff0c;通常用于解决“滑动窗口最大值/最小值”等问题。 核心思想是&#xff1a;利用双端队列&#xff08;deque&#xff09;维护当前窗口内或候选范围内元素…

CSS语法中的选择器与属性详解

CSS:层叠样式表&#xff0c;Cascading Style Sheets 层叠样式表 内容和样式分离解耦&#xff0c;便于修改样式。 特殊说明&#xff1a; 最后一条声明可以没有分号&#xff0c;但是为了以后修改方便&#xff0c;一般也加上分号为了使用样式更加容易阅读&#xff0c;可以将每条代…

模拟设计的软件工程项目

考核题目 论文论述题&#xff1a;结合你 参与开发、调研或模拟设计的软件工程项目 &#xff0c;撰写一篇论文 完成以下任务&#xff0c;论文题目为《面向微服务架构的软件系统设计与建模分析》&#xff0c;总分&#xff1a; 100 分。 1. 考核内容&#xff1a; 一、系统论述…

个人理解redis中IO多路复用整个网络处理流

文章目录 1.redis网络处理流2.理解通知机制 1.redis网络处理流 10个客户端通过TCP与Redis建立socket连接&#xff0c;发送GET name指令到服务器端。服务器端的网卡接收数据&#xff0c;数据进入内核态的网络协议栈。Redis通过IO多路复用机制中的epoll向内核注册监听这些socket的…

【郑州轻工业大学|数据库】数据库课设-酒店管理系统

该数据课设是一个基于酒店管理系统的数据库设计 建库语句 create database hotel_room default charset utf8 collate utf8_general_ci;建表语句 use hotel_room;-- 房型表 create table room_type( id bigint primary key auto_increment comment 房型id, name varchar(50)…

TCP 三次握手与四次挥手详解

前言 在当今互联网时代&#xff0c;前端开发的工作范畴早已超越了简单的页面布局和交互设计。随着前端应用复杂度的不断提高&#xff0c;对网络性能的优化已成为前端工程师不可忽视的重要职责。而要真正理解并优化网络性能&#xff0c;就需要探究支撑整个互联网的基础协议——…

RTD2735TD/RTD2738 (HDMI,DP转EDP 高分辨率高刷新率显示器驱动芯片)

一、芯片概述 RTD2738是瑞昱半导体&#xff08;Realtek&#xff09;推出的一款高性能显示驱动芯片&#xff0c;专为高端显示器、便携屏、专业显示设备及多屏拼接系统设计。其核心优势在于支持4K分辨率下240Hz高刷新率及8K30Hz显示&#xff0c;通过集成DisplayPort 1.4a与HDMI …

C++实现手写strlen函数

要实现求字符串长度的函数&#xff0c;核心思路是通过指针或索引遍历字符串&#xff0c;直到遇到字符串结束标志 \0 。以下是两种常见的实现方式&#xff1a; 指针遍历版本 #include <iostream> using namespace std; // 指针方式实现strlen size_t myStrlen(const cha…

NVPL 函数库介绍和使用

文章目录 NVPL 函数库介绍和使用什么是 NVPLNVPL 的主要组件NVPL 的优势安装 NVPL基本使用示例示例1&#xff1a;使用 NVPL RAND 生成随机数示例2&#xff1a;使用 NVPL FFT 进行快速傅里叶变换 编译 NVPL 程序性能优化建议总结 NVPL 函数库介绍和使用 什么是 NVPL NVPL (NVI…

HTTP相关内容补充

目录 一、URI 和 URL 二、使用 Cookie 的状态管理 三、返回结果的 HTTP状态码 一、URI 和 URL URI &#xff1a;统一资源标识符 URL&#xff1a;统一资源定位符 URI 格式 登录信息&#xff08;认证&#xff09;指定用户名和密码作为从服务器端获取资源时必要的登录信息&a…

MySQL: Invalid use of group function

https://stackoverflow.com/questions/2330840/mysql-invalid-use-of-group-function 出错SQL: 错误原因&#xff1a; 1. 不能在 WHERE 子句中使用聚合&#xff08;或分组&#xff09;函数 2. HAVING 只能筛选分组后的聚合结果或分组字段 # Write your MySQL query statem…

C#财政票查验接口集成-医疗发票查验-非税收入票据查验接口

财政票据是企事业单位、医疗机构、金融机构等组织的重要报销凭证&#xff0c;其真实性、完整性和合规性日益受到重视。现如今&#xff0c;为有效防范虚假票据报销、入账、资金流失等问题的发生&#xff0c;财政票据查验接口&#xff0c;结合财政票据识别接口&#xff0c;旨在为…

浏览器基础及缓存

目录 浏览器概述 主流浏览器&#xff1a;IE、Chrome、Firefox、Safari Chrome Firefox IE Safari 浏览器内核 核心职责 主流浏览器内核 JavaScript引擎 主流的JavaScript引擎 浏览器兼容性 浏览器渲染 渲染引擎的基本流程 DOM和render树构建 html解析 DOM 渲染…

Ubuntu 安装Telnet服务

1. 安装Telnet 客户端 sudo apt-get install telnet 2. 安装Telnet 服务器 &#xff08;这样才能用A电脑的客户端连接B电脑的Telnet服务&#xff09; sudo apt-get install telnetd 3. 这时候Telnet服务器是无法自我启动的&#xff0c;需要网络守护进程服务程序来管理…