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
英文地址,請注明出處:http://en.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;需要網絡守護進程服務程序來管理…