university.alchemy5
Your Goal: Security 你的目标:安全性
If anyone tries to call approve other than the arbiter address, revert the transaction.
除仲裁者地址外,若有任何人尝试调用approve,则撤销该交易。
错误
我的错误代码:require(arbiter = msg.owner);
错误原因分析
报错的核心是 msg 全局变量中没有 owner 属性!Solidity 中全局变量 msg 仅包含以下关键成员(你混淆了 msg.sender 和自定义的 owner/arbiter 变量):
msg.sender:当前调用者的地址(触发交易的账户);
msg.value:随交易发送的原生代币数量(以 wei 为单位);
不存在 msg.owner!你想表达的是 “判断调用者是否为仲裁者地址”,而非 “msg 的 owner 属性”。
另外,你的代码还有两个潜在问题:
比较运算符错误:= 是赋值运算符,判断相等需用 ==;
未定义 arbiter 变量(需先声明仲裁者地址,通常在构造函数中初始化)。
正确答案
1 | |
Your Goal: Approved
Create an event called Approved which takes a single uint parameter: the balance that is sent to the beneficiary.
Emit this event from within the approve function.
我的错误代码
1 | |
根据题目要求,你的代码需要修正以下几个问题,核心是创建正确名称的事件、传递正确的参数、移除无效逻辑:
问题分析
- 事件名称错误:题目要求事件名为
Approved,但你定义的是Mybalance; - 参数逻辑错误:
_balance无需手动赋值msg.value(合约余额由存款时的payable构造函数确定),应直接使用合约实际转给受益人的金额作为事件参数; - 冗余代码:
_balance = msg.value是无效逻辑(approve调用时无需额外转账,合约余额在构造函数存款时已确定)。
正确答案
1 | |
关键修正说明
- 事件定义:严格按照题目要求命名为
Approved,参数为uint类型(表示转给受益人的余额); - 函数参数移除:
approve无需_balance参数(合约余额由构造函数存款时的msg.value确定,转账时直接取合约全部余额即可,更符合托管合约逻辑); - 事件参数传递:触发事件时,传递的是实际转给受益人的
amountToSend(即合约余额),完全匹配题目“发送给受益人的余额”要求; - 移除无效逻辑:删除
_balance = msg.value(approve调用时无需额外转账,msg.value在此处无意义); - 错误提示优化:给
require添加明确错误提示,便于调试。
验证逻辑
- 存款人调用构造函数(带
msg.value存款),初始化仲裁者、受益人; - 只有仲裁者能调用
approve函数; - 调用后合约余额全部转给受益人,同时触发
Approved事件,事件参数为实际转账金额; - 可通过区块链浏览器查询事件日志,验证是否正确传递了受益人收到的余额。
数组
数组的行为和storage/memory/calldata非常不同
当你需要iteration循环的时候,可以使用数组
allNumbers.push(1); 的push是什么意思?
在 Solidity 语法 中,push() 是 数组(Array)的内置操作,核心作用和 JavaScript 类似,但有 Solidity 特有的规则 ——向动态数组的末尾追加元素,且仅支持对「动态长度数组」使用。
结合你的代码 allNumbers.push(1),先明确前提:allNumbers 必须是 动态数组(即声明时未指定固定长度的数组,比如 uint[] allNumbers;),不能是固定长度数组(比如 uint[3] allNumbers; 不支持 push)。
calldata和memory有什么区别
在 Solidity 中,calldata 和 memory 都是 数据存储位置(Data Location),用于指定变量在 EVM(以太坊虚拟机)中的存储方式,核心区别体现在 用途、生命周期、可修改性、成本 上——简单说:calldata 是只读的外部输入数据区,memory 是临时可写的内存区。
下面从多个维度详细对比,结合实际场景帮你理解:
一、核心区别对比表
| 特性 | calldata | memory |
|---|---|---|
| 核心用途 | 存储外部输入数据(函数参数、msg.data) |
存储函数执行时的临时数据(临时变量、中间结果) |
| 可修改性 | 只读(不可修改,修改会编译报错) | 可读写(函数内可修改值) |
| 生命周期 | 仅函数调用期间有效(调用结束后数据销毁) | 仅函数调用期间有效(调用结束后数据销毁) |
| 适用变量类型 | 仅引用类型(数组、结构体、字符串 string) |
仅引用类型(数组、结构体、字符串 string) |
| 数据来源 | 外部传入(如用户调用函数时传的参数) | 函数内创建(如 memory uint[] arr = new uint[](3);)或从其他位置拷贝 |
| gas 成本 | 极低(仅读取外部数据,不占用EVM内存空间) | 中等(需要占用EVM内存,扩容/拷贝会增加成本) |
| 特殊限制 | 不支持动态数组 push(Solidity 0.8+ 部分支持,但不推荐) |
支持动态数组 push,但内存数组扩容效率低 |
| 典型场景 | 函数参数(尤其是外部函数 external 的数组/结构体参数) |
函数内临时处理数据(如数组排序、结构体组装) |
二、逐个特性拆解(结合代码示例)
1. 用途与数据来源:外部输入 vs 内部临时
calldata:专门用来接收「外部传入」的数据,比如用户调用函数时传的数组、结构体,本质是直接引用
msg.data(交易的原始数据),不会在EVM内存中拷贝,所以成本极低。- 注意:只有
external函数(外部调用)的参数默认是calldata,public函数参数默认是memory(但也可以显式指定为calldata优化成本)。
- 注意:只有
memory:用来存储函数执行过程中需要「临时处理」的数据,数据是在函数内创建或从其他位置(如
storage、calldata)拷贝过来的,占用EVM的内存空间。
示例:
1 | |
2. 可修改性:只读 vs 可读写
这是最直观的区别:
calldata是「只读视图」,只能读取数据,不能修改(比如给calldata数组的元素赋值会直接编译报错);memory是「可读写临时区」,函数内可以自由修改其值(如上面示例中tempArr[i] = _input[i] * 2)。
3. gas 成本:calldata 更省钱
EVM 中,calldata 不需要占用EVM的内存空间,只是引用交易的原始数据,所以读取成本极低;而 memory 需要占用EVM的内存(内存是按「槽位」分配的,扩容时成本会递增)。
优化技巧:如果函数是 external 类型(仅外部调用),且参数是数组/结构体/字符串,优先用 calldata 而不是 memory,能大幅节省 gas。
示例(优化前后对比):
1 | |
4. 适用场景与限制
(1)calldata 的适用场景
- 外部函数(
external)的数组/结构体/字符串参数(优先选择,省 gas); - 不需要修改的外部输入数据(如只读查询、数据验证)。
(2)memory 的适用场景
- 函数内需要临时创建/修改的引用类型(如组装结构体、处理数组);
public函数的参数(public函数既可以外部调用也可以内部调用,内部调用无法使用calldata,所以默认是memory);- 函数返回引用类型(Solidity 中返回数组/结构体时,必须是
memory类型,不能是calldata)。
(3)特殊限制
calldata不支持动态数组的push操作(即使是 Solidity 0.8+ 部分支持,也不推荐,因为calldata是外部数据,扩容会破坏原始数据引用);memory数组如果是动态长度(如uint[] memory arr;),支持push,但扩容时需要重新分配内存,效率较低——如果能提前确定长度,建议用new uint[](length)初始化(更省 gas)。
三、常见误区提醒
- 值类型不需要指定存储位置:
calldata和memory只用于「引用类型」(数组、结构体、string),值类型(uint、bool、address等)不需要指定,默认存储在「栈(stack)」中; public函数参数不能默认 calldata:public函数可以被内部调用(如合约内其他函数调用),而内部调用没有calldata区域,所以public函数参数默认是memory;- 返回引用类型必须是 memory:比如函数返回
uint[]时,必须声明为returns (uint[] memory),不能是calldata(calldata是外部输入,无法作为返回值传递); string是特殊的引用类型:string本质是动态字节数组,所以calldata string用于外部传入的字符串(省 gas),memory string用于函数内临时处理的字符串。
四、总结:如何选择?
- 当你写 external 函数,且参数是数组/结构体/字符串,且不需要修改参数 → 用
calldata(省 gas,推荐); - 当你需要在 函数内创建/修改引用类型数据(如临时数组、组装结构体) → 用
memory; - 当你写 public 函数,参数是引用类型 → 用
memory(默认就是,无需显式指定); - 当你需要 返回引用类型数据 → 用
memory。
一句话记忆:calldata 是「只读外部输入区」,memory 是「可写临时工作区」。
Your Goal: Find the Sum 你的目标:求出总和
Create a pure, external function sum which takes a fixed size array of five unsigned integers.
创建一个纯粹的外部函数sum,该函数接受一个包含五个无符号整数的固定大小数组。
Find the sum of the unsigned integers and return it as a uint.
求无符号整数的和,并将其作为uint返回。
我的错误代码
1 | |
编译通过的代码
1 | |
学习代码
根据你的需求,我来修正并完善代码——Solidity 的语法和 Python 差异较大,尤其是函数定义、数组传递、循环语法和 pure 函数的约束,以下是完整的正确实现:
1 | |
关键修正说明(对比你的代码)
1. 函数定义修正
原代码
function public sum()错误:- 缺少参数(需要接收固定大小为5的数组);
- 修饰符错误:需求是
external(外部函数,适合外部调用)+pure(无存储读写、无状态依赖),而非public(public包含内部调用,没必要); - 缺少返回值声明
returns (uint)。
正确定义:
function sum(uint[5] calldata arr) external pure returns (uint)uint[5]:固定大小为5的无符号整数数组(满足需求);calldata:外部函数接收数组时的推荐位置(只读、省gas,pure函数不能用storage);external:外部可见,只能被外部账户或其他合约调用;pure:函数不读写存储、不依赖链上状态,仅做数学计算(必须满足,否则编译报错)。
2. 变量初始化
- Solidity 中变量必须显式初始化,不能像 Python 那样直接赋值(原代码
mysum未初始化); - 正确:
uint mysum = 0;(从0开始累加)。
3. 循环语法修正(核心差异)
- Python 的
for i in myArrays:在 Solidity 中不适用,Solidity 有两种循环写法:- 按索引循环(兼容所有版本):
for (uint i = 0; i < arr.length; i++) { ... }- 循环变量
i必须声明类型(uint); - 循环条件用分号分隔(初始化; 条件; 自增);
- 代码块用
{}包裹(不可省略)。
- 循环变量
- for-in 循环(Solidity 0.8.17+ 支持):
for (uint num : arr) { ... }(直接遍历元素,更简洁)。
- 按索引循环(兼容所有版本):
4. 避免修改数组(pure 函数约束)
- 原代码
myArrays[i]++会修改数组元素,而pure函数不允许修改任何数据(包括参数数组); - 正确做法:仅读取元素累加
mysum += arr[i](不修改原数组)。
5. 数组传递方式
- 原代码
uint[5] storage myArrays = [1,2,3,4,5];错误:storage关键字用于引用合约存储中的数组,而pure函数不能访问存储;- 需求是“接收外部传入的数组”,而非在函数内硬编码数组(硬编码会导致函数只能计算固定数组的和,不符合需求)。
测试方式
你可以在 Remix 编辑器中测试该函数,传入固定大小为5的数组(如 [1,2,3,4,5]),会返回正确总和 15:
- 部署合约;
- 调用
sum函数,输入参数[1,2,3,4,5]; - 返回结果为
15(正确)。
核心语法对比(Python vs Solidity)
| 特性 | Python | Solidity |
|---|---|---|
| 函数声明 | def sum(arr): |
function sum(uint[5] calldata arr) external pure returns (uint) { ... } |
| 循环 | for i in arr: |
for (uint i=0; i<arr.length; i++) 或 for (uint num : arr) |
| 变量初始化 | 可自动初始化(mysum = 0 可选) |
必须显式初始化(uint mysum = 0;) |
| 数组传递 | 直接传递列表 | 需指定大小+位置(uint[5] calldata) |
| 纯函数约束 | 无显式约束 | 用 pure 修饰,禁止存储操作/数据修改 |
这样修改后,代码完全满足需求:接收固定大小为5的无符号整数数组,计算总和并返回,且符合 external 和 pure 的约束。
Your Goal: Sum Dynamic Array 你的目标:求动态数组的和
Create a pure, external function sum which takes a dynamic size array of unsigned integers.
创建一个纯外部函数sum,该函数接收一个动态大小的无符号整数数组。
Find the sum of the unsigned integers and return it as a uint.
求无符号整数的和,并将其作为uint返回。
答案:
1 | |
Your Goal: Filter Even Numbers 你的目标:筛选偶数
Create a public, dynamic sized array of unsigned integers as a state variable called evenNumbers.
创建一个名为evenNumbers的公共、动态大小的无符号整数数组作为状态变量。
Create an external function called filterEven which takes an dynamic size array of unsigned integers as its only argument. Find all of the even numbers in this array and push them into the evenNumbers storage array.
创建一个名为filterEven的外部函数,该函数仅接受一个动态大小的无符号整数数组作为参数。找出该数组中的所有偶数,并将它们放入evenNumbers存储数组中。
我的错误答案
1 | |
你的代码存在多个关键错误,我来逐一修复并解释问题所在,最终提供可正确运行的代码:
主要错误分析
函数修饰符错误:filterEven 函数需要修改状态变量 evenNumbers,但你使用了 pure 修饰符(pure 函数不允许读写状态变量),应改为 external(仅外部调用,符合题目要求)。
偶数判断逻辑错误:if i/2 == 0 完全错误!
你判断的是索引 i 而非数组元素 arr[i]
偶数判断的正确逻辑是 取余运算:arr[i] % 2 == 0(能被 2 整除的数是偶数)
语法错误:if 条件表达式必须加括号 (),即 if (条件)
返回值错误:题目要求函数将偶数存入 evenNumbers 状态变量,不需要返回值(你的 returns (uint) 多余且无意义)
正确答案
1 | |
Your Goal: Filter Even Numbers 你的目标:筛选偶数
Create a pure, external function called filterEven which takes an dynamic size array of unsigned integers as its only argument.
创建一个名为filterEven的纯外部函数,该函数仅接受一个动态大小的无符号整数数组作为参数。
Find all the even numbers and add them to a new array in memory. This array should contain only the even numbers.
找出所有偶数并将它们添加到内存中的新数组中。这个数组应该只包含偶数。
Return this new array. 返回这个新数组。
初始化后,内存数组无法调整大小。这意味着即使在上面的示例中,一旦addresses数组初始化为大小5,它在整个交易过程中都将保持该长度。
1 | |
Your Goal: Add Members 你的目标:添加成员
Create a dynamic sized array of addresses called members
创建一个名为members的动态大小地址数组
Create an external function addMember which has a single parameter: an address for a new member. Add this address to the members array.
创建一个外部函数addMember,它有一个参数:新成员的address。将此地址添加到members数组中。
Create a public view function isMember that takes an address and returns a bool indicating whether the address is a member or not.
创建一个公共视图函数isMember,该函数接收一个address并返回一个bool,以指示该address是否为成员。
答案:
1 | |