part 1

四个步骤确定是否真正了解一个事情

  1. 学习
  2. 向不懂的人解释
  3. 如果你卡住了, 重新学习
  4. 直达你能够使用类比和简化来解释他

资源

  1. Become Ethereum Blockchain Developer : https://ethereum-blockchain-developer.com/000-introduction/01-your-instructor/ 2.学习手册: https://docs.google.com/spreadsheets/d/1OO06RZ7vw8-Hij8ZxB68FaRYRtQEz3GifnLDNwW8sTs/edit?pli=1##gid=1051902784
  2. mirror.zxy : Mirror.xyz项目是什么? 简单理解,Mirror 是一个采用区块链技术的内容创作平台,普遍认为它是去中心化版本的medium。
  3. gitcoin : Gitcoin 是一个由社区管理的开源项目资助平台,其使命是构建和资助数字公共产品,并于2021 年5 月发布了治理代币GTC,用于治理Gitcoin Grants 和GitcoinDAO。 本文将主要介绍Gitcoin 最有影响力的产品Gitcoin Grants,也是Web3 最大的公共物品募捐池。

1. Rmix IDE

set up Remix

链接是: https://remix.ethereum.org/

需要注意的是,Remix 网站的连接是否是https , 如果是使用http 切换成为https 的时候, 代码文件可能全部消失。

// SPDX-License-Identifier: GPL-3.0  -> 许可证

pragma solidity 0.8.14 : 编译器版本
  1. 选择工作空间 通常使用默认工作空间
  2. 创建文件
  3. 合约contract
  4. 编译器版本 pragma
  5. 自动编译

memory 关键字是干嘛的?在 Solidity 中,memory 关键字用于声明临时内存变量。与存储变量不同,内存变量在函数执行期间存在,但在函数执行完毕后会被清除。

简单的hello world 代码

// SPDX-License-Identifier: MIT
pragma solidity >=0.6.12 <0.9.0;

contract HelloWorld {
  /**
   * @dev Prints Hello World string
   */

  uint public myuint = 10000;
  bool public  is_dely = true;
  function print() public pure returns (string memory) {
    return "Hello World!";
  }
}

代码的基本结构就是

  1. 版本与协议
  2. contract
  3. 属性和方法

2. The Blockchain Messenger

数据结构

  1. 首先介绍了Booleans
  2. 整数和小数 uint,int
  3. 整数翻转。 unchecked 是一个关键字,用于在某些情况下关闭整数运算的溢出和下溢检查。通常情况下,Solidity 会在进行整数运算时检查是否发生溢出或下溢,并在发生溢出或下溢时抛出异常。但是,有时候开发人员可能希望在某些情况下禁用这些检查,以提高性能或处理特殊情况。

unchecked 关键字可以用来标记一个代码块,指示编译器在该代码块内不进行整数溢出和下溢检查。这样,如果在 unchecked 块内发生溢出或下溢,Solidity 不会抛出异常,而是继续执行代码。开发人员应该非常谨慎地使用 unchecked 关键字,并确保在其内部的代码不会导致不良后果。

字符串 bytes 和string

view : view 是一个函数修饰符,用于声明函数不会修改合约的状态。也就是说,被标记为 view 的函数只能读取合约的数据,而不能修改它们。这使得 Solidity 编译器能够进行优化,并允许在不向区块链写入数据的情况下调用这些函数。

//SPDX-License-Identifier: MIT

pragma solidity 0.8.15;

contract ExampleStrings {

    string public myString = "Hello World";

    function setMyString(string memory _myString) public {
        myString = _myString;
    }

    function compareTwoStrings(string memory _myString) public view returns(bool) {
        return keccak256(abi.encodePacked(myString)) == keccak256(abi.encodePacked(_myString));
    }

}
地址类型 adress 类型

adress 存在一些属性, 例如balance,其他包括: - transfer:address 类型的变量可以通过 .transfer() 函数向另一个地址发送以太币 - send:与 transfer 类似,address 类型的变量也可以通过 .send() 函数向另一个地址发送以太币。不过,.send() 函数会返回一个布尔值,表示转账是否成功。 - call:address 类型的变量也可以通过 .call() 函数向另一个地址发送消息(调用函数)。 - type conversion:address 类型可以进行类型转换,将其转换为 payable address 类型,以便于进行转账操作。

//SPDX-License-Identifier: MIT

pragma solidity 0.8.15;

contract ExampleAddress {

    address public someAddress;

    function setSomeAddress(address _someAddress) public {
        someAddress = _someAddress;
    }

    function getAddressBalance() public view returns(uint) {
        return someAddress.balance;
    }

}

msg

在 Solidity 中,msg 是一个全局变量,用于访问有关当前交易或消息的信息。msg 变量具有多个属性,常用的属性包括:

  • msg.sender:表示当前交易或消息的发送者地址(即调用合约或函数的地址)。
  • msg.value:表示当前交易或消息所附带的以太币数量(单位为 wei)。
  • msg.data:表示当前交易或消息的数据字节码。在函数调用时,该字段包含传递给函数的参数。
  • msg.sig:表示当前函数调用的函数选择器(function selector),用于识别要调用的函数。
  • msg.gas:表示当前交易或消息剩余的 gas 量。在智能合约中,gas 用于执行计算和状态更改操作。
view 和 pure
  • view:用于声明函数不会修改合约的状态。这意味着在 view 函数内部,不能修改合约的状态变量,也不能调用修改状态的其他函数
  • pure:用于声明函数不会读取或修改合约的状态,也不会与外部合约或以太坊网络进行交互。pure 函数通常用于执行纯粹的计算操作,例如数学计算或数据转换。调用 pure 函数不会消耗 gas,因为它不涉及合约状态或外部交互。
构造函数 constructor

在 Solidity 中,constructor 是一种特殊的函数,用于在合约部署时执行初始化操作。constructor 函数有以下特点:

  • 名称与合约名相同:constructor 函数的名称必须与合约的名称完全相同。 -0 仅执行一次:constructor 函数只会在合约被部署时执行一次。当使用 Ethereum 区块链上的一个智能合约创建新的合约实例时,会自动执行 constructor 函数。
  • 不可继承:constructor 函数不能被继承,也不能在子合约中被重写。
  • 无返回值:constructor 函数不能有返回值,因为它们在部署合约时执行,而不是在调用合约方法时执行。
  • 用于初始化状态:constructor 函数通常用于初始化合约的状态变量、执行一些必要的设置或验证操作。这样可以确保在合约部署后,合约的状态处于正确的初始状态。
pragma solidity ^0.8.0;

contract MyContract {
    uint256 public myNumber;

    constructor(uint256 _initialNumber) {
        myNumber = _initialNumber;
    }
}
一个小例子

修改message , 当地址是本人的时候

//SPDX-License-Identifier: MIT

pragma solidity 0.8.15;

contract TheBlockchainMessenger {

    uint public changeCounter;

    address public owner;

    string public theMessage;

    constructor() {
        owner = msg.sender;
    }

    function updateTheMessage(string memory _newMessage) public {
        if(msg.sender == owner) {
            theMessage = _newMessage;
            changeCounter++;
        }
    }
}

3. Smart Money Deposit and WithDrawals

MetaMask

  1. 获取测试币;
  2. 跟踪交易

MetaMask 如何工作


EtherScan  
↓↑
↓↑
Blockchain
↓↑
↓↑
BloackChain     ->    Bloackchain Node   -> Infura  -> MetaMask
↓↑
↓↑

Bacckend 

↓↑
↓↑

Browser
  • Infura 是一个由 ConsenSys 开发的以太坊基础设施服务提供商。
  • EtherScan 是一个以太坊区块链浏览器和分析平台,它提供了丰富的以太坊区块链数据和统计信息 : https://etherscan.io/

以太坊交易

sendTransaction

以太坊交易(Ethereum transactions)是在以太坊区块链上发生的操作,可以涉及发送以太币(Ether)或调用智能合约等操作。每笔交易都会被打包到区块中,并且需要经过网络中的矿工进行验证和确认。以下是以太坊交易的一些重要特点和组成部分:

  1. 交易发送者(Sender):交易发送者是发起交易的地址,也称为交易的发件人或发送方。交易发送者需要有足够的以太币来支付交易费用和执行智能合约所需的 Gas。
  2. 交易接收者(Recipient):交易接收者是交易的目标地址,可以是另一个以太坊地址或者智能合约地址。交易可以是向地址发送以太币,也可以是调用合约函数执行特定操作。
  3. 交易金额(Value):交易金额指定了发送者向接收者发送的以太币数量。对于普通的以太币转账交易,该值为正数;对于调用智能合约的交易,该值通常为零。
  4. Gas 限额和 Gas 价格:Gas 限额(Gas Limit)指定了交易允许消耗的最大 Gas 数量,Gas 价格(Gas Price)指定了每单位 Gas 的价格。交易的 Gas 费用等于 Gas 限额乘以 Gas 价格。Gas 价格越高,矿工越愿意打包这笔交易,但交易的成本也会相应增加。
  5. 交易数据(Data):交易数据是一个可选字段,用于向智能合约发送额外的数据或参数。当调用智能合约时,交易数据通常包含要执行的合约函数名称和参数。
  6. 交易哈希(Transaction Hash):交易哈希是交易的唯一标识符,由交易的内容经过哈希算法生成。交易哈希用于在区块链上唯一标识交易,并且可以用来查询交易的状态和详细信息。
  7. 交易状态:交易状态表示交易的执行结果,可以是成功、失败或者挂起等状态。如果交易成功执行,相应的状态改变将被记录到区块链上。

前三是必须的, 后面不是必须得

以太坊交易是如何进行的
  1. 创建交易: 在以太坊上,交易可以通过以太坊钱包软件、开发工具或者编程接口创建。 创建交易时,需要提供发送者地址、接收者地址、发送的金额、Gas 限额和 Gas 价格等信息。Gas 限额表示愿意支付的最大 Gas 数量,Gas 价格表示愿意支付的 Gas 单价。 如果是调用智能合约,还需要提供合约地址和调用数据。
  2. 交易签名: 在创建交易后,使用发送者的私钥对交易进行签名。 交易签名是对交易数据进行加密的过程,以确保交易的完整性和身份验证。只有拥有发送者私钥的用户才能够正确地对交易进行签名。
  3. 交易广播: 签名完成后的交易被广播到整个以太坊网络中的节点。 交易通过网络进行传播,并且每个节点都可以接收到这些交易。
  4. 验证交易: 当节点接收到交易后,首先会对交易的签名进行验证,以确保交易确实是由发送者发起的。 节点还会验证交易的格式是否正确,包括交易数据、发送者地址和接收者地址等信息。 节点还会检查发送者账户是否有足够的余额来支付 Gas 费用。
  5. 交易打包: 验证通过的交易将被打包到一个区块中。一个区块可以包含多个交易。 区块是以太坊网络中的数据单元,它们包含了一系列的交易以及其他重要的区块头信息。
  6. 矿工确认: 矿工节点在进行挖矿的过程中会选择未确认的交易,并将其包含在新的区块中。 矿工通过解决难题(例如工作量证明)来确认区块,一旦确认完成,新的区块就会被添加到区块链上。
  7. 区块链更新: 当新的区块被确认并添加到区块链上时,其中包含的交易就被认为是已经完成的。 交易的状态会相应更新,包括发送者和接收者的余额变化等。
签名函数 signTransaction

Transaction + Privete Key =                                 Signed Transacction

                                                                      ↓ ECRECOVER

Private Key --------> ECDSA  --------> Publick Key -------> Ethereum Account
 

ECDSA

ECDSA 是椭圆曲线数字签名算法(Elliptic Curve Digital Signature Algorithm)的缩写。它是一种基于椭圆曲线数学原理的数字签名算法,用于在公开密钥密码系统中对数据进行签名和验证。

ECDSA 的主要原理涉及到椭圆曲线上的点的加法和乘法运算。它利用了椭圆曲线上的离散对数难题,使得在已知公钥的情况下,无法有效地获取私钥。这种数学性质使得 ECDSA 在数字签名中具有很高的安全性。

ECDSA 算法包括以下几个关键步骤:

密钥生成:生成一对公钥和私钥,其中公钥用于验证签名,私钥用于生成签名。 签名生成:使用私钥对消息进行哈希,然后对哈希值进行数字签名生成。 签名验证:使用公钥对签名进行验证,确保签名的正确性和消息的完整性。 ECDSA 签名算法在加密货币领域被广泛使用,例如在比特币和以太坊等区块链网络中用于对交易进行签名和验证,确保交易的安全和不可篡改性。

ECRECOVER

ECRECOVER 是以太坊智能合约中的一个函数,用于从数字签名中恢复签名者的公钥。它通常用于验证以太坊交易中的数字签名,并从签名中提取签名者的地址。ECRECOVER 函数的主要作用是验证签名的有效性,并提取签名者的地址以进行后续的逻辑处理。

在以太坊智能合约中,ECRECOVER 函数接收四个参数:

  1. bytes32 类型的 _hash:要验证的消息的哈希值。
  2. uint8 类型的 v:签名中的恢复标志,用于确定签名是使用哪个公钥进行的。
  3. bytes32 类型的 r:签名的前半部分,ECDSA 签名中的 r 值。
  4. bytes32 类型的 s:签名的后半部分,ECDSA 签名中的 s 值。
  5. ECRECOVER 函数通过这些参数来计算签名者的公钥,并从公钥中提取地址。然后,可以将提取的地址与预期的地址进行比较,以验证签名的有效性,并执行相应的逻辑处理。

ECRECOVER 函数在以太坊智能合约中经常用于验证交易的签名,确保交易的发送者是有效的,并且交易数据没有被篡改。

Cryptographic Hashing

library(digest)

## 原始数据
data <- "Hello, World!"

## 使用MD5哈希算法对数据进行哈希
md5_hash <- digest(data, algo = "md5")

## 使用SHA-256哈希算法对数据进行哈希
sha256_hash <- digest(data, algo = "sha256")

## 输出MD5哈希值
print(md5_hash)
## [1] "90db1978c9472c71314717beeb40ea8d"
## 输出SHA-256哈希值
print(sha256_hash)
## [1] "540daddd0ea64da95b229e7fc26e1499147002d863227db4b3ec8ae34f4bc30d"

哈希函数(Hash Function)是一种将任意长度的输入数据映射为固定长度的输出数据的函数。哈希函数通常被用于密码学、数据完整性验证、数据检索等领域。

哈希函数具有以下特性:

固定长度输出:哈希函数的输出具有固定的长度,不受输入数据长度的影响。 易计算性:对于给定的输入数据,哈希函数应该能够高效地计算出对应的哈希值。 不可逆性:理论上,不同的输入数据应该产生不同的哈希值;同时,从哈希值推导出原始输入数据应该是非常困难的,即使是微小的输入数据变化也应该导致输出哈希值的巨大变化。 雪崩效应:即使输入数据的微小变化也应该导致输出哈希值的巨大变化,使得哈希值的修改难以被察觉。 哈希函数在密码学中被广泛应用于密码哈希函数、数字签名、消息认证码、随机数生成等方面。

不同hash 函数的区别

MD5(Message Digest Algorithm 5)、SHA-1(Secure Hash Algorithm 1)和SHA-256(Secure Hash Algorithm 256)都是常见的哈希函数算法,它们之间有以下区别:

  1. 输出长度:
  • MD5:输出长度为128位(16字节)。
  • SHA-1:输出长度为160位(20字节)。
  • SHA-256:输出长度为256位(32字节)。
  1. 安全性:
  • MD5:由于其较短的输出长度和已知的碰撞攻击,MD5 已经被认为不安全,不适用于对安全性要求较高的场景。
  • SHA-1:虽然 SHA-1 比 MD5 更安全,但是随着时间的推移,由于存在碰撞攻击,SHA-1 也逐渐被视为不安全,不适用于对安全性要求较高的场景。
  • SHA-256:SHA-256 是 SHA-2 系列中的一种算法,目前仍被认为是安全的,并且适用于对安全性要求较高的场景。
  1. 速度:
  • MD5 和 SHA-1 通常比 SHA-256 更快,因为它们的输出长度较短,计算过程相对简单。 输出哈希值的位数:
  • MD5 和 SHA-1 输出的哈希值通常以十六进制表示,每个十六进制数字对应4位二进制数(4位 = 1个十六进制数)。
  • SHA-256 输出的哈希值通常以64位十六进制表示,每个十六进制数字对应4位二进制数(64位 = 256位)。

Hashing of Hashes , 对hash 的结果进行hashing

hashing of Blockchain , 都对区块链结果进行hashing。

总结

  1. 区块链中每一个区块都会有前一个区块的哈希值
  2. 改变前一个区块的哈希值会导致之后的区块哈希值发生改变。
Update、Delete Tansactions

在以太坊中,Gas 是一种计算单位,用于衡量执行智能合约或交易所需的计算资源。Gas 被用来支付以太坊网络中的计算费用,以确保网络的安全性和可靠性。Gas 的作用如下:

  1. 执行智能合约和交易:Gas 用于支付执行智能合约和交易时消耗的计算资源,包括计算机处理时间、内存使用和存储空间等。
  2. 防止滥用和拒绝服务攻击:通过向执行者(矿工)支付 Gas 费用,以太坊网络可以防止滥用和拒绝服务攻击,因为执行者需要消耗实际成本来执行交易或智能合约。
  3. 确定交易的优先级:交易的 Gas Price 决定了执行者愿意为执行该交易所支付的 Gas 费用。交易的优先级与 Gas Price 成正比,因此高 Gas Price 的交易更有可能被矿工包含在下一个区块中,从而更快地得到确认。
  4. 限制资源使用:Gas 限制了智能合约的资源使用,防止合约无限循环、内存泄漏等问题,确保合约执行的安全性和可靠性。

Remix 链接 metamask

Patavle 修饰词 和msg.value
//SPDX-License-Identifier: MIT

pragma solidity 0.8.15;

contract SampleContract {
    string public myString = "Hello World";

    function updateString(string memory _newString) public payable {
        if(msg.value == 1 ether) {
            myString = _newString;
        } else {
            payable(msg.sender).transfer(msg.value);
        }
    }
}

//payable(msg.sender).transfer(msg.value);:如果传入的以太币量不等于 1 ether,则将传入的以太币退还给发送交易的地址 msg.sender。

这个代码表示如果有一个以太币, 则修改字符串

Fallback 和 Recive 函数

Fallback 和 receive 函数

  1. Fallback 函数
    • Fallback 函数是在合约接收到以太币但没有匹配任何其他函数时触发的函数。
    • 在 Solidity 0.6.0 版本之前,Fallback 函数没有显式的声明方式,它会在合约中未匹配到任何其他函数时被调用。
    • 从 Solidity 0.6.0 版本开始,Fallback 函数必须显式声明,命名为 fallback,并且不带任何参数或函数修饰符。
    • Fallback 函数通常用于处理未知或不符合预期的情况,例如接收以太币但没有指定其他操作时。
  2. receive 函数
    • receive 函数是 Solidity 0.6.0 版本引入的新特性,用于处理合约接收到以太币的情况。
    • receive 函数没有参数,也没有返回值,命名为 receive
    • 当合约接收到以太币时,如果没有与之匹配的 receive 函数,则会调用 Fallback 函数(如果存在),否则会触发回滚。
    • receive 函数通常用于处理合约接收到以太币后的具体逻辑,例如更新合约的状态、记录日志等操作。

总之,Fallback 函数和 receive 函数都用于处理合约接收到以太币的情况,但它们的使用方式和声明方式略有不同。Fallback 函数在 Solidity 0.6.0 版本之前存在,而 receive 函数是在 Solidity 0.6.0 版本中引入的新特性。

Receive()是一个在calldata为空时优先于fallback()的函数。但是,当calldata不符合有效的函数签名时,回退优先于接收。 ######## receive()

//SPDX-License-Identifier: MIT

pragma solidity 0.8.15;

contract SampleFallback {
    uint public lastValueSent;
    string public lastFunctionCalled;

    receive() external payable {
        lastValueSent = msg.value;
        lastFunctionCalled = "receive";
    }
}

receive() external payable 是 Solidity 中用于接收以太币的特殊函数。它具有以下含义:

  • receive 函数是 Solidity 0.6.0 版本引入的新特性。
  • external 关键字表示该函数只能通过外部调用触发,即只能由其他合约或外部账户向合约发送以太币来触发。
  • payable 关键字表示该函数可以接收以太币。
  • receive 函数没有参数和返回值。
  • 当合约接收到以太币时,如果没有与之匹配的 receive 函数,则会触发回滚。
  • receive 函数通常用于处理合约接收到以太币后的具体逻辑,例如更新合约的状态、记录日志等操作。

总之,receive() external payable 函数声明告诉编译器,当合约接收到以太币时,可以调用这个函数来处理接收到的以太币。

Fallback函数

//SPDX-License-Identifier: MIT

pragma solidity 0.8.15;

contract SampleFallback {
    uint public lastValueSent;
    string public lastFunctionCalled;

    receive() external payable {
        lastValueSent = msg.value;
        lastFunctionCalled = "receive";
    }

    fallback() external payable {
        lastValueSent = msg.value;
        lastFunctionCalled = "fallback";
    }
}

总结

  1. View/Pure 函数 : 标记为 view 的函数表示该函数不会修改合约的状态。标记为 pure 的函数表示该函数不会读取或修改合约的状态,也不会读取区块链的数据。
  2. Function Visibility
  • public
  • private
  • external
  • internal
  1. constructor 构造函数
  2. receive 和 fallback 函数
  3. msg.value 和 msg.address

Wallet smart contract

写一个存款取款的智能合约

// SPDX-License-Identifier: MIT
pragma solidity >0.8.16;

contract SendWithdrawMoney {
    uint public balanceReceived; // 用于记录合约收到的以太币数量

    // 存款函数,接收以太币
    function deposit() public payable {
        // 将接收到的以太币数量累加到合约的余额中
        balanceReceived += msg.value;
    }

    // 查看合约当前余额的函数
    function getContractBalance() public view returns(uint) {
        return address(this).balance; // 返回合约的当前余额
    }

    // 提取全部余额到调用者的函数
    function withdrawAll() public {
        // 将调用者的地址转换为可支付地址
        address payable to = payable(msg.sender);
        // 将合约的全部余额转账给调用者
        to.transfer(getContractBalance());
    }

    // 将全部余额提取到指定地址的函数
    function withdrawToAddress(address payable to) public {
        // 将合约的全部余额转账给指定的地址
        to.transfer(getContractBalance());
    }
}

4. Smart wallet

Mapping

mapping 是 Solidity 中用于创建键值对映射的一种数据结构。它类似于其他编程语言中的字典或关联数组。

在 Solidity 中,mapping 通常用于将一个值与另一个值关联起来,类似于键值对的概念,其中一个值称为键,另一个值称为对应的值。mapping 的语法如下:

mapping(KeyType => ValueType) public myMapping;
  • KeyType 是键的数据类型,可以是任何合法的 Solidity 数据类型,如 uintaddressstring 等。
  • ValueType 是值的数据类型,也可以是任何合法的 Solidity 数据类型。
  • public 是修饰符,指定了映射的可见性,表示可以通过合约的外部调用访问该映射。

mapping 在区块链智能合约开发中经常用于以下情况:

  1. 管理账户余额: 在代币合约中,可以使用 mapping 来存储每个账户的余额。例如,mapping(address => uint256) public balances; 可以用来记录每个账户的余额。

  2. 存储状态信息: 在分布式应用程序中,可以使用 mapping 存储各种状态信息,如用户信息、商品信息、交易信息等。例如,mapping(address => User) public users; 可以用来存储用户信息。

  3. 权限管理: 在多方参与的合约中,可以使用 mapping 存储各种权限信息,如管理员权限、投票权限等。例如,mapping(address => bool) public isAdmin; 可以用来记录管理员权限。

  4. 索引和查询: mapping 可以用于构建索引,以便快速查询数据。例如,可以使用 mapping(uint256 => address) public index; 来记录地址的索引,以便快速查找地址对应的数据。

  5. 事件日志: 在合约中触发事件时,可以使用 mapping 将事件关联到相应的处理函数。例如,mapping(address => function) public eventHandlers; 可以用来存储不同事件对应的处理函数。

总的来说,mapping 是一种非常灵活和强大的数据结构,在区块链智能合约开发中具有广泛的应用场景,可以用于存储各种类型的数据和管理各种类型的状态信息。

高级mapping

使用mapping 追踪地址和余额

// SPDX-License-Identifier: MIT

// 声明合约的 Solidity 版本
pragma solidity ^0.8.14;

// 声明一个名为 MappingsStructExample 的智能合约
contract MappingsStructExample {

    // 声明了一个名为 balanceReceived 的公共 mapping,用于存储地址对应的收到的金额
    mapping(address => uint) public balanceReceived;

    // 定义了一个公共视图函数 getBalance(),用于查看合约的余额
    function getBalance() public view returns(uint) {
        return address(this).balance;
    }

    // 定义了一个公共可支付函数 sendMoney(),用于向合约发送以太币,并将发送者的地址与收到的金额关联起来
    function sendMoney() public payable {
        balanceReceived[msg.sender] += msg.value;
    }

    // 定义了一个公共提款函数 withdrawMoney(),用于从合约中提取指定数量的以太币并转账给指定地址。如果发送者的余额不足,则会触发 require 断言。
    function withdrawMoney(address payable _to, uint _amount) public {
        require(_amount <= balanceReceived[msg.sender], "not enough funds");
        balanceReceived[msg.sender] -= _amount;
        _to.transfer(_amount);

    }

    // 定义了一个公共提款函数 withdrawAllMoney(),用于将发送者的全部余额提取并转账给指定地址
    function withdrawAllMoney(address payable _to) public {
        uint balanceToSend = balanceReceived[msg.sender];
        balanceReceived[msg.sender] = 0;
        _to.transfer(balanceToSend);
    }
}

require 语句,用于在 Solidity合约中实现断言。它的作用是确保某个条件成立,否则将终止函数执行并回滚交易。

结构体 struct - 复杂数据类型

Solidity 中的 structs 是一种自定义的复合数据类型,它允许你定义包含不同数据类型的数据结构。structs 允许你将多个相关的变量组合在一起,形成一个逻辑单元,方便在合约中使用和管理。

// 定义一个名为 Person 的 struct,包含两个属性:name 和 age
struct Person {
    string name;
    uint age;
}

// 声明一个存储 Person 结构的数组
Person[] public people;

// 创建一个新的 Person 实例并添加到数组中
function addPerson(string memory _name, uint _age) public {
    people.push(Person(_name, _age));
}

另外一个实际例子

// SPDX-License-Identifier: MIT

// Solidity版本声明,指定代码遵循MIT许可证
pragma solidity 0.8.15;

// PaymentReceived合约定义
contract PaymentReceived {
    // 存储发送者地址和发送的金额
    address public from;
    uint public amount;

    // 构造函数,接受发送者地址和金额作为参数
    constructor(address _from, uint _amount) {
        // 初始化from和amount变量
        from = _from;
        amount = _amount;
    }
}

// Wallet合约定义
contract Wallet {
    // PaymentReceived类型的公共状态变量payment
    PaymentReceived public payment;

    // payContract函数,用于向合约付款
    function payContract() public payable {
        // 创建新的PaymentReceived实例,传入发送者地址和发送的以太币数量
        payment = new PaymentReceived(msg.sender, msg.value);
    }
}


// 等价于

//SPDX-License-Identifier: MIT

pragma solidity 0.8.15;

contract Wallet2 {
    struct PaymentReceivedStruct {
        address from;
        uint amount;
    }

    PaymentReceivedStruct public payment;

    function payContract() public payable {
        payment = PaymentReceivedStruct(msg.sender, msg.value);
    }
}

使用struct 比较节约gas,相比于合约嵌套

Mapping 和 structs

//SPDX-License-Identifier: MIT

pragma solidity ^0.8.14;

contract MappingsStructExample {

    struct Transaction {
        uint amount;
        uint timestamp;
    }

    struct Balance {
        uint totalBalance;
        uint numDeposits;
        mapping(uint => Transaction) deposits;
        uint numWithdrawals;
        mapping(uint => Transaction) withdrawals;
    }

    mapping(address => Balance) public balanceReceived;


    function getBalance(address _addr) public view returns(uint) {
        return balanceReceived[_addr].totalBalance;
    }

    function depositMoney() public payable {
        balanceReceived[msg.sender].totalBalance += msg.value;

        Transaction memory deposit = Transaction(msg.value, block.timestamp);
        balanceReceived[msg.sender].deposits[balanceReceived[msg.sender].numDeposits] = deposit;
        balanceReceived[msg.sender].numDeposits++;
    }

    function withdrawMoney(address payable _to, uint _amount) public {
        balanceReceived[msg.sender].totalBalance -= _amount; //reduce the balance by the amount ot withdraw

        //record a new withdrawal
        Transaction memory withdrawal = Transaction(msg.value, block.timestamp);
        balanceReceived[msg.sender].withdrawals[balanceReceived[msg.sender].numWithdrawals] = withdrawals;
        balanceReceived[msg.sender].numWithdrawals++;

        //send the amount out.
        _to.transfer(_amount);
    }
}

require

require 是 Solidity 中的一个关键字,用于在执行智能合约函数时检查条件是否满足,如果不满足,则终止函数执行并回滚状态。通常情况下,require 语句用于验证函数参数、状态变量的值或者合约当前状态是否符合预期,如果不符合预期,则会抛出异常并终止函数执行。

require(condition, errorMessage);

其中,condition 是需要验证的条件,如果为 false,则会触发异常,终止函数执行;errorMessage 是可选的错误信息,用于指示发生了什么错误。

Assert

assert 是 Solidity 中的一个关键字,用于在智能合约中执行断言(assertion)。它与 require 关键字类似,但在语义上略有不同。

assert 语句用于在代码执行过程中验证一些不可变的条件,如果这些条件不满足,则会触发异常并导致交易失败。与 require 不同的是,assert 通常用于检查不可变的内部错误,如编程错误或者异常情况,而不是用于验证外部输入或者合约状态。

assert 语句的一般格式如下:

assert(condition);

其中,condition 是需要验证的条件,如果为 false,则会触发异常,终止函数执行并回滚状态。

try/catch

try/catch 是 Solidity 0.8.0 版本引入的异常处理机制。它允许在智能合约中进行异常处理,提供了一种处理异常情况的方法,以便在出现错误时进行适当的处理而不导致交易失败。

try {
    // 可能会引发异常的代码块
} catch Error(string memory reason) {
    // 处理异常的代码块
} catch (bytes memory lowLevelData) {
    // 处理底层异常的代码块
}

External Function Calls and Low-Level Calls In-Depth

External Function Calls 和 Low-Level Calls 是 Solidity 中用于与其他智能合约或以太坊地址进行交互的两种主要方法。它们允许智能合约调用其他合约的函数或执行底层的 EVM 指令。下面是它们的详细解释:

External Function Calls(外部函数调用)
  1. 定义

    • External function calls 是使用合约 ABI(应用二进制接口)来调用其他智能合约中的函数的一种方式。ABI 定义了函数签名、参数和返回值等信息。
    • 这种调用方式是 Solidity 中最常见和最推荐的方式,因为它提供了类型安全性和易用性。
  2. 语法

    • 使用合约地址和函数名称来调用其他合约中的函数。
    • 在函数调用前需要声明函数的可见性,如 externalpublic
  3. 特点

    • 使用合约 ABI,提供了类型安全性。
    • Solidity 编译器会进行一些静态检查,如参数数量和类型是否匹配。
    • 支持异常处理,可以使用 try/catch 语句来捕获异常。
  4. 示例

    // 合约 A
    contract ContractA {
        function foo(uint256 x) external returns (uint256) {
            return x * 2;
        }
    }
    
    // 合约 B
    contract ContractB {
        ContractA public contractA;
    
        constructor(address _contractA) {
            contractA = ContractA(_contractA);
        }
    
        function bar(uint256 y) external returns (uint256) {
            return contractA.foo(y);
        }
    }
Low-Level Calls(低级调用)
  1. 定义

    • Low-level calls 提供了一种更底层的方法来与合约进行交互,允许直接执行 EVM 指令。
    • 它们比外部函数调用更灵活,但也更容易出错,因为没有 ABI 的类型检查。
  2. 语法

    • 使用 address.calladdress.delegatecall 方法来执行低级调用。
    • 必须以字节数组的形式传递参数和数据。
  3. 特点

    • 不提供类型安全性,需要手动编码参数和数据,并确保正确性。
    • 不会触发 Solidity 的异常,需要通过返回值来判断调用是否成功。
    • 可以用于与任何合约进行交互,包括合约自身的函数调用、外部地址、预编译的合约等。
  4. 示例

    // 合约 A
    contract ContractA {
        function foo(uint256 x) external returns (uint256) {
            return x * 2;
        }
    }
    
    // 合约 B
    contract ContractB {
        address public contractAAddr;
    
        constructor(address _contractA) {
            contractAAddr = _contractA;
        }
    
        function bar(uint256 y) external returns (uint256) {
            // 使用低级调用执行合约 A 的 foo 函数
            (bool success, bytes memory data) = contractAAddr.call(abi.encodeWithSignature("foo(uint256)", y));
            require(success, "Call failed");
    
            // 解析返回值
            uint256 result;
            assembly {
                result := mload(add(data, 0x20))
            }
            return result;
        }
    }

比较和选择

  • External function calls 更推荐使用,因为它们提供了更高的安全性和易用性。
  • Low-level calls 用于需要更灵活的情况,如需要直接与 EVM 交互,或者调用合约中未知函数的情况下。但要注意它们的风险和复杂性。

总结

智能合同钱包 : The Smart Contract Wallet

一些要求:

  1. 钱包只有一个钱包
  2. 钱包能够收到资金
  3. 所有者可以在任何类型的地址上花费资金,不管是所谓的外部拥有帐户(EOA-with a private key) ,还是合同地址。
  4. 所有者可以在任何类型的地址上花费资金,不管是所谓的外部拥有帐户(EOA-with a private key) ,还是合同地址。
  5. 所有者可以在任何类型的地址上花费资金,不管是所谓的外部拥有帐户(EOA-with a private key) ,还是合同地址。
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

contract SampleWallet {
    // 合约拥有者
    address payable owner;
    
    // 记录每个地址的转账额度
    mapping(address => uint) public allowance;
    // 记录哪些地址被允许发送交易
    mapping(address => bool) public isAllowedToSend;

    // 记录谁是合约的守护者
    mapping(address => bool) public guardian;
    // 下一个拥有者
    address payable nextOwner;
    // 守护者重置次数
    uint guardiansResetCount;
    // 守护者重置所需的确认次数
    uint public constant confirmationsFromGuardiansForReset = 3;

    // 构造函数,设置合约的初始拥有者为部署合约的账户
    constructor() {
        owner = payable(msg.sender);
    }

    // 提议设定新的合约拥有者
    function proposeNewOwner(address payable newOwner) public {
        // 需要调用者是守护者
        require(guardian[msg.sender], "You are no guardian, aborting");
        // 如果新的拥有者和当前拥有者不同,重置守护者确认次数
        if(nextOwner != newOwner) {
            nextOwner = newOwner;
            guardiansResetCount = 0;
        }

        // 增加守护者重置次数
        guardiansResetCount++;

        // 如果守护者确认次数达到要求,更新合约拥有者
        if(guardiansResetCount >= confirmationsFromGuardiansForReset) {
            owner = nextOwner;
            nextOwner = payable(address(0));
        }
    }

    // 设置地址的转账额度
    function setAllowance(address _from, uint _amount) public {
        // 需要调用者是合约拥有者
        require(msg.sender == owner, "You are not the owner, aborting!");
        // 设置地址的转账额度并标记为允许发送交易
        allowance[_from] = _amount;
        isAllowedToSend[_from] = true;
    }

    // 拒绝某个地址发送交易
    function denySending(address _from) public {
        // 需要调用者是合约拥有者
        require(msg.sender == owner, "You are not the owner, aborting!");
        // 标记地址为不允许发送交易
        isAllowedToSend[_from] = false;
    }

    // 发起转账交易
    function transfer(address payable _to, uint _amount, bytes memory payload) public returns (bytes memory) {
        // 检查转账金额不能大于合约的余额
        require(_amount <= address(this).balance, "Can't send more than the contract owns, aborting.");
        // 如果调用者不是合约拥有者,检查是否被允许发送交易以及转账额度是否足够
        if(msg.sender != owner) {
            require(isAllowedToSend[msg.sender], "You are not allowed to send any transactions, aborting");
            require(allowance[msg.sender] >= _amount, "You are trying to send more than you are allowed to, aborting");
            allowance[msg.sender] -= _amount;
        }

        // 调用外部合约的payable函数进行转账
        (bool success, bytes memory returnData) = _to.call{value: _amount}(payload);
        // 检查转账是否成功
        require(success, "Transaction failed, aborting");
        return returnData;
    }

    // 接收以太币的fallback函数
    receive() external payable {}
}

5. ERC20 token Sale

Web3.js 介绍

学习目标:

  1. 了解Web3.js 部署智能合约所涉及的组件
  2. 如何使用Web3.js 与只能合约进行交互

Web3.js是一个JavaScript库,用于与以太坊区块链进行交互。它允许开发人员通过JavaScript代码与以太坊智能合约和节点进行通信,从而创建基于以太坊的去中心化应用(DApp)。

Web3.js提供了一系列API,可以用于执行以下操作:

  1. 连接到以太坊节点:通过Web3.js,可以连接到本地或远程的以太坊节点,以便与区块链网络进行通信。

  2. 与智能合约交互:可以使用Web3.js部署、调用和与以太坊智能合约进行交互,包括读取合约状态和调用合约方法。

  3. 发送交易:可以使用Web3.js创建和发送以太币交易或调用智能合约方法的交易。

  4. 监听区块链事件:Web3.js允许开发人员监听以太坊区块链上的事件,例如新块的生成、交易的发送和智能合约的状态变化。

  5. 管理以太坊钱包:可以使用Web3.js生成新的以太坊地址、签名交易、导入钱包等操作。

通过这些功能,Web3.js为开发人员提供了构建以太坊DApp所需的一切工具和功能。

// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"eth_getBalance","params":["0x407d73d8a49eeb85d32cf465507dd71d507100c1", "latest"],"id":1}'
// Result
{
  "id":1,
  "jsonrpc": "2.0",
  "result": "0x0234c8a3397aab58" // 158972490234375000
}

Web3 Providers: HTTP vs. WebSocket vs. IPC

Web3 Providers是Web3.js用来与以太坊节点进行通信的接口。在Web3.js中,可以使用不同类型的提供者(Providers)来连接到以太坊节点。常见的提供者类型包括HTTP、WebSocket和IPC(Inter-Process Communication)。下面是它们的主要特点和用途:

  1. HTTP Provider:
    • 通过HTTP协议连接到以太坊节点。
    • 使用HTTP Provider时,Web3.js会向节点发送HTTP请求,并等待节点的响应。
    • 通常用于与远程以太坊节点进行通信,例如使用Infura提供的托管节点。
  2. WebSocket Provider:
    • 通过WebSocket协议连接到以太坊节点。
    • WebSocket Provider与HTTP Provider相比具有更好的实时性和效率,因为它可以实现双向通信,而不是简单的请求-响应模式。
    • WebSocket Provider通常用于需要实时数据更新或需要持续连接的应用,例如实时交易监控或即时通讯应用。
  3. IPC Provider:
    • 通过本地进程间通信(Inter-Process Communication)连接到以太坊节点。
    • IPC Provider通常用于与本地运行的以太坊客户端(例如Geth或Parity)进行通信。
    • IPC Provider提供了更高的安全性,因为它只允许本地进程之间的通信,而不通过网络。

选择合适的提供者取决于应用的具体需求。对于需要实时数据更新或双向通信的应用,WebSocket Provider可能是更好的选择。而对于简单的读取数据或发送交易的应用,HTTP Provider通常足够了。 IPC Provider则适用于与本地节点进行通信的场景。

uunderstanding the ABI array

ABI 代表应用二进制接口。在 Remix 中,转到 Plugins 并启用 Debugger 插件。添加如下代码:

//SPDX-License-Identifier: MIT

pragma solidity 0.8.14;

contract MyContract {

    uint public myUint = 123;

    function setMyUint(uint newUint) public {
        myUint = newUint;
    }

}

打开调试,

Web3js 试图提供与智能合同交互的良好功能

ABI Array 包含所有函数、输入和输出,以及智能契约中的所有变量及其类型。如果在 artifacts/MyContract.json 文件,则可以一直滚动到底部。在这里你可以找到 ABI 数组:

https://ethereum-blockchain-developer.com/2022-05-erc20-token/images/20220813133213.webp

  1. the ABI array
  2. the ABI array

添加以下脚本并从 Deploy & SendTransactions 插件添加的合同地址

(async() => {

    const address = "ENTER_ADDRESS_HERE_FROM_RUN_TX_PLUGIN";
    const abi = [
    {
        "inputs": [],
        "name": "myUint",
        "outputs": [
            {
                "internalType": "uint256",
                "name": "",
                "type": "uint256"
            }
        ],
        "stateMutability": "view",
        "type": "function"
    },
    {
        "inputs": [
            {
                "internalType": "uint256",
                "name": "newUint",
                "type": "uint256"
            }
        ],
        "name": "setMyUint",
        "outputs": [],
        "stateMutability": "nonpayable",
        "type": "function"
    }
];


    let contractInstance = new web3.eth.Contract(abi, address);

    console.log(await contractInstance.methods.myUint().call());
    let accounts = await web3.eth.getAccounts();
    await contractInstance.methods.setMyUint(345).send({from: accounts[0]});

    console.log(await contractInstance.methods.myUint().call());
})()

然后右键单击该文件并运行该脚本。

现在已经准备好了一些更高级的契约交互!

Event as return variable

让我们从一个返回值的简单智能合同开始:

//SPDX-License-Identifier: MIT
pragma solidity 0.8.16;

contract EventExample {

    mapping(address => uint) public tokenBalance;

    constructor() {
        tokenBalance[msg.sender] = 100;
    }

    function sendToken(address _to, uint _amount) public returns(bool) {
        require(tokenBalance[msg.sender] >= _amount, "Not enough tokens");
        tokenBalance[msg.sender] -= _amount;
        tokenBalance[_to] += _amount;
        return true;
    }
}

使用虚拟机部署,让我们看看如果使用 JavaScriptVM 部署智能契约会发生什么。

现在让我们实际使用“ sendToken”函数。将 Account # 2的地址复制到“ _ to”字段中,并在 _ amount 字段中输入“1”,然后启动事务:

观察“事务”窗口中发生的情况!

  1. 观察“事务”窗口中发生的情况!
  2. 看看解码后的输出字段!

通常,没有解码输出。发送事务是通常没有返回值的并发操作。有一些讨论是为了实际返回一些东西,但是在编写这些行时,事件在这里是为了从编写事务中发出值。让我们用一个真正的区块链测试这一点!

但是, 使用MetaMask 部署是没有输出结果的。

使用Event

修改合同代码如下:

//SPDX-License-Identifier: MIT
pragma solidity 0.8.16;

contract EventExample {

    mapping(address => uint) public tokenBalance;

    event TokensSent(address _from, address _to, uint _amount);

    constructor() {
        tokenBalance[msg.sender] = 100;
    }

    function sendToken(address _to, uint _amount) public returns(bool) {
        require(tokenBalance[msg.sender] >= _amount, "Not enough tokens");
        tokenBalance[msg.sender] -= _amount;
        tokenBalance[_to] += _amount;

        emit TokensSent(msg.sender, _to, _amount);

        return true;
    }
}

再使用MetaMask 部署, 则能够看到交易结果。

6. Non Fungible Token

ERC 721

opensea : https://opensea.io/

OpenZeppelin

OpenZeppelin 是一个广泛使用的开源框架,专门为以太坊和其他区块链平台上的智能合约开发提供安全、可靠的库和工具。它的目标是帮助开发者编写更安全的智能合约,并加速开发过程。以下是 OpenZeppelin 的主要特性和组件:

主要特性

  1. 智能合约库
    • 标准实现:提供了 ERC20、ERC721、ERC1155 等标准代币的实现,确保符合以太坊标准并经过广泛审计。
    • 安全性:包含许多安全功能和防范措施,帮助开发者避免常见的智能合约漏洞。
    • 模块化设计:合约库是模块化的,开发者可以根据需求组合使用不同的合约模块。
  2. 合约向导(Contracts Wizard)
    • 一个交互式工具,允许开发者通过图形界面定制和生成合约代码。用户可以选择所需的特性和功能,并生成符合标准的合约代码。
  3. Upgrades
    • 提供了安全的智能合约升级模式。智能合约一旦部署到区块链上通常是不可修改的,但 OpenZeppelin Upgrades 提供了一种方式,允许开发者通过代理模式安全地升级合约的逻辑。
  4. 安全审计
    • OpenZeppelin 提供专业的智能合约安全审计服务,帮助项目识别并修复安全漏洞。它的代码库和工具都经过严格的审计和测试,确保其安全性。
  5. 开发者工具
    • 集成了 Truffle 和 Hardhat 等开发工具,提供了全面的开发环境,支持编写、测试、部署和管理智能合约。

主要组件

  1. Contracts
    • 这是 OpenZeppelin 提供的智能合约库,包含了各种标准和常用合约实现,例如代币合约、访问控制合约、治理合约等。
  2. Upgrades Plugins
    • 提供用于 Truffle 和 Hardhat 的插件,帮助开发者实现和管理可升级合约。
  3. OpenZeppelin CLI
    • 命令行工具,简化了智能合约的开发、部署和管理过程。
  4. Defender
    • OpenZeppelin Defender 是一个安全运营平台,帮助开发者自动化和保护他们的智能合约。它包括自动化任务、监控和警报功能,确保合约的持续安全。

应用场景

  • 代币发行:通过 OpenZeppelin 的标准 ERC20、ERC721 等实现,开发者可以快速、安全地发行新代币。
  • 去中心化应用(dApps):提供的合约库和开发工具帮助加速 dApp 的开发过程,确保代码的安全性和可靠性。
  • DAO 和治理:提供治理合约和工具,帮助创建和管理去中心化自治组织(DAO)。
  • 合约升级:通过代理合约模式,实现合约的可升级性,确保项目能够随着需求变化进行迭代和更新。

Truffle、Hardhat 和 Foundry

是以太坊智能合约开发中的三个主要框架和工具集,它们为开发、测试、部署和管理智能合约提供了强大的支持。以下是对每个工具的详细介绍:

1. Truffle

Truffle 是一个开发框架,专为以太坊智能合约和去中心化应用(dApps)设计。它提供了全面的开发环境和工具集,使得智能合约的开发和管理更加容易。

  • 主要特性
    • 开发环境:提供了一个丰富的开发环境,包括智能合约编译、部署和管理工具。
    • 脚手架:可以快速生成新的项目结构和智能合约模板。
    • 测试框架:集成了 Mocha 测试框架,支持 JavaScript 和 Solidity 测试。
    • 调试器:提供了强大的调试工具,可以逐步执行合约代码,帮助开发者排查和修复问题。
    • 网络管理:支持多个网络配置,可以轻松部署智能合约到不同的以太坊网络(如主网、测试网和本地开发网络)。
  • 使用场景:适用于需要全面开发环境和工具集的智能合约项目,尤其是那些需要复杂测试和调试功能的项目。

2. Hardhat

Hardhat 是一个灵活的以太坊开发环境,专注于智能合约的编译、部署和测试。它提供了一个可扩展的插件系统,允许开发者根据自己的需求定制开发环境。

  • 主要特性
    • 任务运行器:可以定义和运行自定义任务和脚本,简化开发流程。
    • 插件系统:拥有丰富的插件生态系统,支持各种开发工具和功能扩展(如 Ethers.js、Waffle、Solhint)。
    • 本地开发网络:内置的 Hardhat Network 提供了快速、灵活的本地以太坊网络,支持即时编译和部署。
    • 调试支持:提供了强大的 Solidity 调试工具,包括堆栈跟踪和控制流分析。
    • 兼容性:与 Truffle 和其他工具兼容,可以平滑迁移和集成现有项目。
  • 使用场景:适用于需要高度定制化和灵活性的开发者,尤其是那些希望利用插件系统扩展功能的项目。

3. Foundry

Foundry 是一个快速、可扩展的智能合约开发工具集,专注于高性能和易用性。它采用了 Rust 语言开发,并且针对速度和效率进行了优化。

  • 主要特性
    • 高性能:由于采用了 Rust 语言,Foundry 在编译和测试速度上具有显著优势。
    • 简洁的 CLI:提供了简洁直观的命令行界面,支持快速编译、部署和测试。
    • Forge:Foundry 的主要工具,集成了编译、部署、测试和交互功能。
    • 脚本支持:支持编写和运行自定义脚本,以实现复杂的部署和交互任务。
    • 模块化设计:可以与其他工具和框架无缝集成,支持灵活的开发工作流。
  • 使用场景:适用于追求高性能和高效率的开发者,特别是那些对编译和测试速度有较高要求的项目。

ERC20 和 ERC777 是两种不同的以太坊代币标准。它们都有自己的特性和应用场景,下面是它们之间的主要区别:

ERC20 VS ERC777

ERC20 - 主要特性

  1. 基本接口:ERC20 定义了一组基本接口,包括 totalSupply(), balanceOf(address), transfer(address, uint256), transferFrom(address, address, uint256), approve(address, uint256)allowance(address, address),这些接口为代币的转账和授权操作提供了标准化的方法。
  2. 简单性:ERC20 的设计非常简单,适合绝大多数的代币发行和基本交易需求。
  3. 广泛使用:由于其简单性和早期发布,ERC20 是最广泛使用的代币标准,几乎所有的以太坊钱包和交易所都支持 ERC20 代币。

局限性

  1. 安全问题:ERC20 存在一些安全问题,例如代币直接发送到合约地址时可能导致丢失,因为合约地址可能不会处理收到的代币。
  2. 事件通知:ERC20 的事件通知机制有限,无法灵活地通知代币接收者或实现更复杂的逻辑。

ERC777 主要特性

  1. 改进的接口:ERC777 包含了 ERC20 的所有功能,同时提供了更加灵活和安全的接口。
  2. 发送钩子:ERC777 引入了 sendreceive 钩子,允许代币在转移时触发合约内的逻辑。通过这些钩子,开发者可以在代币转账时执行额外的逻辑。
  3. 操作员机制:ERC777 支持操作员(operator),即用户可以授权第三方代表自己发送和接收代币。这为更加复杂的应用场景提供了灵活性。
  4. 向后兼容:ERC777 保持与 ERC20 的向后兼容性,意味着大多数 ERC20 工具和合约可以直接使用 ERC777 代币。

优势

  1. 安全性:ERC777 解决了 ERC20 的一些安全问题,例如防止代币发送到合约地址时的丢失。
  2. 灵活性:通过发送和接收钩子,ERC777 允许在代币转移过程中执行复杂的业务逻辑,增强了智能合约的互动性。
  3. 可操作性:操作员机制使得代币管理和使用更加灵活,适合复杂的去中心化应用。

局限性

  1. 复杂性:由于增加了更多的功能和灵活性,ERC777 的实现比 ERC20 更加复杂。
  2. 支持度:虽然 ERC777 提供了很多改进,但其普及度和工具支持度仍然不如 ERC20 广泛。

总结

  • ERC20:简单、广泛使用,适合基本的代币发行和交易场景,但存在一些安全和灵活性方面的局限性。
  • ERC777:提供了更多的功能和安全性,适合需要复杂业务逻辑和灵活代币管理的应用,但实现起来更加复杂,普及度还不如 ERC20。

ERC721和ERC1155是以太坊上用于创建和管理非同质化代币(NFT)的两种主要标准。它们各自有不同的特性和应用场景。以下是它们之间的主要区别和特性:

ERC721 VS ERC 1155

ERC721 主要特性

  1. 非同质化:每个ERC721代币都是独一无二的,具有独特的ID,可以表示独特的资产(如艺术品、收藏品、游戏物品等)。
  2. 单一代币类型:每个ERC721合约只能管理一种类型的代币,每个代币都有一个唯一的标识符(token ID)。
  3. 接口方法:ERC721定义了一些基本的接口方法,如balanceOf, ownerOf, transferFrom, approve, setApprovalForAllsafeTransferFrom,用于管理代币的所有权和转移。
  4. 事件:包括TransferApproval事件,用于通知代币转移和授权操作。

应用场景

  • 数字艺术和收藏品:由于每个代币都是唯一的,ERC721非常适合表示数字艺术品、收藏品和其他具有独特价值的资产。
  • 虚拟地产:用于表示虚拟世界中的土地或其他独特的虚拟物品。
  • 游戏物品:可以表示游戏中的独特角色、装备或其他虚拟资产。

ERC1155 主要特性

  1. 多代币标准:ERC1155允许在同一个合约中管理多种类型的代币,这些代币可以是同质化的(如ERC20)或非同质化的(如ERC721)。
  2. 批量操作:支持批量转移、批量铸造和批量销毁操作,极大地提高了效率,特别适合游戏和其他需要大量操作的应用。
  3. 节省Gas费:由于支持批量操作,ERC1155在进行大量代币操作时可以显著节省Gas费。
  4. 接口方法:ERC1155定义了一些接口方法,如balanceOf, balanceOfBatch, safeTransferFrom, safeBatchTransferFrom, setApprovalForAllisApprovedForAll,用于管理和转移代币。
  5. 事件:包括TransferSingleTransferBatch事件,用于通知单个和批量代币转移操作。

应用场景

  • 游戏物品:由于支持多种类型的代币和批量操作,ERC1155非常适合表示游戏中的物品(如武器、道具、角色等),这些物品可以是同质化的(如弹药)或非同质化的(如独特装备)。
  • 数字收藏品:可以表示一系列的收藏品,既包括同质化的普通收藏品,也包括独特的稀有品。
  • 市场和交易平台:支持多种代币类型和批量操作,适合构建高效的市场和交易平台。

理解 ERC 721

  1. token id : 令牌在合约中是一个唯一的unit256
  2. MetaData : 元数据

OpenZeppelin Wizard : https://wizard.openzeppelin.com/

7. introduction to the legacy content