university.alchemy7
Your Goal: Is Member 你的目标:是否为成员
Create an external, view function called isMember which takes an address and returns a bool indicating whether or not the address is a member.
创建一个名为isMember的外部视图函数,该函数接受一个地址</b1并返回一个布尔值</b2,用于指示该地址</b3是否为成员。
1 | |
Your Goal: Remove Member 你的目标:移除成员
Create an external function called removeMember that will take an address and set its corresponding value in the members mapping to false.
创建一个名为removeMember的外部函数,该函数将接收一个地址</b1,并将members映射中与其对应的 value 设置为false。
1 | |
什么时候用view,什么时候函数不用view?
在 Solidity 中,view 是函数修饰符,核心规则是:不修改合约状态的函数用 view,修改合约状态的函数不能用 view。其本质是告诉编译器和链上节点:该函数仅读取数据、无副作用,可避免不必要的Gas消耗(纯本地调用时),同时明确函数的行为边界。
一、先明确:view 函数的核心限制(必须满足以下所有条件)
view 函数承诺不修改任何合约状态变量,一旦函数做了以下操作,就不能加 view:
- 不修改状态变量(包括合约自身的状态变量、映射
mapping、数组array的元素); - 不触发状态变更(如转账
transfer/send、调用非view/pure的外部合约函数); - 不发送交易(如
call{value: ...}带ETH转账,或调用会修改状态的外部函数); - 不使用
selfdestruct、delegatecall等会改变合约状态的操作。
简单说:view 函数只能“读”,不能“写”。
二、什么时候必须用 view?
只要函数满足「仅读取数据、不修改任何状态」,就应该加 view(语法允许省略,但不推荐,会降低代码可读性且可能误导调用者)。常见场景:
1. 查询合约状态变量(纯读取)
函数仅返回合约中定义的状态变量(如用户余额、配置参数、列表数据),无任何修改操作:
1 | |
2. 基于状态变量的计算(无副作用)
函数接收参数,结合合约状态做计算后返回结果,但不修改任何状态:
1 | |
3. 读取链上全局变量(无修改)
仅读取 block.number、block.timestamp、msg.sender 等全局变量(不结合状态修改):
1 | |
4. 调用其他 view/pure 函数(无状态变更)
函数内部仅调用其他 view 或 pure 函数(不触发状态修改):
1 | |
三、什么时候不能用 view?
只要函数会修改合约状态或触发状态变更,就绝对不能加 view(加了会编译报错,或运行时出现不可预期行为)。常见场景:
1. 修改合约状态变量
函数直接修改合约的状态变量(包括赋值、数组/映射的增删改):
1 | |
2. 发送/接收ETH(触发转账状态)
函数中包含 transfer、send、call{value: ...} 等转账操作(会修改合约和接收者的ETH余额状态):
1 | |
3. 调用非 view/pure 的外部函数
函数调用了外部合约中不带 view/pure 的函数(即使自身不直接修改状态,也可能通过外部调用触发状态变更):
1 | |
4. 执行状态变更操作
函数包含 selfdestruct(销毁合约)、delegatecall(委托调用,可能修改状态)、create2(创建合约)等操作:
1 | |
四、关键补充:view 的Gas消耗规则(避免踩坑)
- 本地调用(off-chain):如果通过Web3.js/Ethers.js等库直接调用
view函数(不发送交易),节点仅本地执行,不消耗任何Gas(因为无需上链); - 链上调用(on-chain):如果在另一个非
view函数中调用view函数(作为交易的一部分),view函数的执行会被计入交易Gas(因为整个交易需要上链打包); - 语法允许省略
view:如果函数本应是view但省略了修饰符,编译器不会报错,但调用者无法识别其“只读”特性,且本地调用仍会被当作“可能修改状态”处理(无Gas优化),强烈不推荐省略。
总结:核心判断标准
| 函数行为 | 是否用 view |
| 仅读取状态/计算,不修改任何数据 | 必须用(推荐显式添加) |
| 修改状态变量、转账、调用非 view 函数 | 绝对不能用 |
简单记:“读”用 view,“写”不用 view,显式添加 view 能提升代码可读性、帮助调用者优化Gas,还能让编译器提前拦截非法状态修改。
Your Goal: User Mapping Tokens 你的目标:用户映射代币
Let’s create a new token where every new user will receive 100 tokens!
让我们创建一种新代币,每个新用户都将获得100个代币!
Create a public mapping called users that will map an address to a User struct.
创建一个名为users的公共映射,该映射将address映射到User结构体。
Create an external function called createUser. This function should create a new user and associate it to the msg.sender address in the users mapping.
创建一个名为createUser的外部函数。该函数应创建一个新用户,并将其关联到users映射中的msg.sender地址。
The balance of the new user should be set to 100 and the isActive boolean should be set to true.
新用户的balance应设置为100,且isActive布尔值应设置为true。
1 | |
Your Goal: Transfer Amount 你的目标:用户映射代币
Create an external function called transfer which takes two parameters: an address for the recipient and a uint for the amount.
创建一个名为transfer的外部函数,该函数接受两个参数:一个用于接收者的address和一个用于金额的uint。
In this function, transfer the amount specified from the balance of the msg.sender to the balance of the recipient address.
在此函数中,将指定的amount从msg.sender的余额转移到接收者address的余额。
1 | |
Your Goal: Make Connections 你的目标:建立连接
Create a public mapping called connections which will map an address to a mapping of an address to a ConnectionTypes enum value.
创建一个名为connections的公共映射,该映射会将一个address映射到另一个从address到ConnectionTypes枚举值的映射。
In theconnectWith function, create a connection from the msg.sender to the other address.
在connectWith函数中,创建一个从msg.sender到other地址的连接。
1 | |
Voting Contract 投票合约
In this tutorial we’re going to build a voting contract! We’ll use the lessons learned here to understand how the Governor standard emerged
在本教程中,我们将构建一个投票合约!我们会运用在这里学到的知识,来理解Governor标准是如何形成的。
Proposals 提案
We’re going to focus on creating a voting contract that will allow members to create new proposals. This contract can contain many proposals which will be voted on by a group of members. Each proposal will keep track of its votes to decide when its time to execute.
我们将专注于创建一个投票合约,该合约将允许成员创建新提案。这个合约可以包含许多提案,供一群成员进行投票。每个提案都会记录其票数,以决定何时执行。
At execution time, these proposals will send calldata to a smart contract. The calldata could be anything! We could have a Voting system that allows 100 members to decide when to upgrade a protocol. The calldata might target a function with the signature upgrade(address) and send over the new protocol implementation. That would be a very cool use of your Voting contract!
在执行时,这些提案会将调用数据发送到智能合约。调用数据可以是任何内容!我们可以有一个投票系统,允许100名成员决定何时升级协议。调用数据可能会以签名upgrade(address)为目标函数,并发送新的协议实现。这将是对你的投票合约非常酷的一种使用方式!
Your Goal: Proposals 你的目标:提案
Create a public array of type Proposal and call it proposals.
创建一个类型为Proposal的公共数组,并将其命名为proposals。
Create an external function newProposal which takes two arguments:
创建一个外部函数newProposal,它接受两个参数:
An address argument which will be the target address of the proposal. We’ll send some calldata to this address.
一个address参数,它将是提案的目标地址。我们会向这个地址发送一些调用数据。
A bytes argument which will be the calldata to eventually send to the smart contract when the proposal is executed.
一个bytes参数,它将是提案执行时最终发送给智能合约的调用数据。
In the newProposal function create a new Proposal with the arguments passed in and the yes/no vote counts are initialized at 0.
在newProposal函数中,使用传入的参数创建一个新的Proposal,赞成/反对票数初始化为0。
Add the new Proposal to the proposals array.
将新的Proposal添加到proposals数组中。
1 | |
calldata的问题
function newProposal(address addr, bytes calldata data) external {
在 Solidity 中,calldata 是一个特殊的数据存储位置,专门用于函数参数,尤其是外部函数(external)的参数。它是只读的,具有一定的效率和燃气优化优势。
为什么需要使用 calldata?
函数签名和燃气效率
- 当一个函数被标记为
external时,函数的参数是从外部传入的。参数通常存储在memory或storage中,取决于如何处理。而calldata是一个为外部函数调用优化的数据位置,它直接从交易输入数据中读取,这样可以减少燃气消耗。 - 在你提供的例子中,
data参数是通过外部函数调用传递的。使用calldata可以确保该参数直接从交易输入数据中获取,而不需要复制到memory中,这样节省了燃气。
- 当一个函数被标记为
calldata是只读的calldata是一个只读数据存储位置,意味着它不能在函数中被修改。这对于只读取传入数据的函数来说非常合适。在你这个例子中,data参数看起来只是用来读取的,所以使用calldata确保了数据的不可变性,并且 Solidity 编译器会相应地进行优化。- 相比之下,
memory是一个临时数据存储位置,允许修改,但使用memory比使用calldata更加消耗燃气。
外部函数调用的优化
- 对于
external函数,Solidity 会优化它们与其他合约的交互,而calldata是这种优化的核心部分。通过使用calldata,数据不需要被复制到其他地方,因此可以减少内存和存储开销。
- 对于
总结
使用 calldata 可以提高函数参数的读取效率,减少燃气费用,因为它是专为外部调用设计的,只读且不需要复制到 memory 中。
Your Goal: Cast Vote 你的目标:投票
Create an external function castVote which takes a uint proposalId and a bool which indicates whether the vote supports the proposal (true for yes, false for no).
创建一个外部函数castVote,它接收一个uint类型的proposalId和一个bool类型的参数,该参数用于表示投票是否支持该提案(true表示赞成,false表示反对)。
For each vote cast, update the yesCount and noCount in the referenced proposal accordingly.
对于每一张所投的票,相应地更新所引用提案中的yesCount和noCount。
1 | |