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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;

contract Escrow {
address public depositor;
address public beneficiary;
address public arbiter;
constructor(address _arbiter, address _beneficiary) payable {
arbiter = _arbiter;
beneficiary = _beneficiary;
depositor = msg.sender;
}
function approve() external {
require(arbiter == msg.sender);
//transfer(msg.value,beneficiary);
//payable(msg.value,beneficiary);
uint256 amount = address(this).balance;
(bool success, ) = payable(beneficiary).call{value: amount}("");
require(success);
}
}

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;

contract Escrow {
address public depositor;
address public beneficiary;
address public arbiter;

event Mybalance(uint); //我增加的第一行
constructor(address _arbiter, address _beneficiary) payable {
arbiter = _arbiter;
beneficiary = _beneficiary;
depositor = msg.sender;
}
function approve(uint _balance) external {
//msg.value = _balance; //第二行
_balance = msg.value; //第二行
require(arbiter == msg.sender);
uint256 amount = address(this).balance;
(bool success, ) = payable(beneficiary).call{value: amount}("");
require(success);
emit Mybalance(_balance); //第3行
}
}

根据题目要求,你的代码需要修正以下几个问题,核心是创建正确名称的事件、传递正确的参数、移除无效逻辑

问题分析

  1. 事件名称错误:题目要求事件名为 Approved,但你定义的是 Mybalance
  2. 参数逻辑错误:_balance 无需手动赋值 msg.value(合约余额由存款时的 payable 构造函数确定),应直接使用合约实际转给受益人的金额作为事件参数;
  3. 冗余代码:_balance = msg.value 是无效逻辑(approve 调用时无需额外转账,合约余额在构造函数存款时已确定)。

正确答案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;

contract Escrow {
address public depositor;
address public beneficiary;
address public arbiter;

event Approved(uint); //我增加的第一行
constructor(address _arbiter, address _beneficiary) payable {
arbiter = _arbiter;
beneficiary = _beneficiary;
depositor = msg.sender;
}
function approve() external {
require(arbiter == msg.sender);
uint256 amount = address(this).balance;
(bool success, ) = payable(beneficiary).call{value: amount}("");
require(success);
emit Approved(amount); //第2行
}
}

关键修正说明

  1. 事件定义:严格按照题目要求命名为 Approved,参数为 uint 类型(表示转给受益人的余额);
  2. 函数参数移除approve 无需 _balance 参数(合约余额由构造函数存款时的 msg.value 确定,转账时直接取合约全部余额即可,更符合托管合约逻辑);
  3. 事件参数传递:触发事件时,传递的是实际转给受益人的 amountToSend(即合约余额),完全匹配题目“发送给受益人的余额”要求;
  4. 移除无效逻辑:删除 _balance = msg.valueapprove 调用时无需额外转账,msg.value 在此处无意义);
  5. 错误提示优化:给 require 添加明确错误提示,便于调试。

验证逻辑

  1. 存款人调用构造函数(带 msg.value 存款),初始化仲裁者、受益人;
  2. 只有仲裁者能调用 approve 函数;
  3. 调用后合约余额全部转给受益人,同时触发 Approved 事件,事件参数为实际转账金额;
  4. 可通过区块链浏览器查询事件日志,验证是否正确传递了受益人收到的余额。

数组

数组的行为和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 中,calldatamemory 都是 数据存储位置(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 函数(外部调用)的参数默认是 calldatapublic 函数参数默认是 memory(但也可以显式指定为 calldata 优化成本)。
  • memory:用来存储函数执行过程中需要「临时处理」的数据,数据是在函数内创建或从其他位置(如 storagecalldata)拷贝过来的,占用EVM的内存空间。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
pragma solidity ^0.8.0;

contract DataLocationDemo {
// 1. calldata:外部函数参数(接收外部输入)
function processExternalData(uint[] calldata _input) external pure returns (uint sum) {
// _input 是 calldata 类型,只读,直接引用外部传入的数据
for (uint i = 0; i < _input.length; i++) {
sum += _input[i];
}
// _input[0] = 10; // 编译报错:calldata 不可修改
}

// 2. memory:函数内临时数据(内部创建/拷贝)
function processTempData(uint[] calldata _input) external pure returns (uint[] memory) {
// 创建一个 memory 数组,存储临时结果
uint[] memory tempArr = new uint[](_input.length);
for (uint i = 0; i < _input.length; i++) {
tempArr[i] = _input[i] * 2; // memory 可修改
}
return tempArr; // 返回 memory 数组(会拷贝到调用者)
}
}

2. 可修改性:只读 vs 可读写

这是最直观的区别:

  • calldata 是「只读视图」,只能读取数据,不能修改(比如给 calldata 数组的元素赋值会直接编译报错);
  • memory 是「可读写临时区」,函数内可以自由修改其值(如上面示例中 tempArr[i] = _input[i] * 2)。

3. gas 成本:calldata 更省钱

EVM 中,calldata 不需要占用EVM的内存空间,只是引用交易的原始数据,所以读取成本极低;而 memory 需要占用EVM的内存(内存是按「槽位」分配的,扩容时成本会递增)。

优化技巧:如果函数是 external 类型(仅外部调用),且参数是数组/结构体/字符串,优先用 calldata 而不是 memory,能大幅节省 gas。

示例(优化前后对比):

1
2
3
4
5
// 优化前:public 函数参数默认 memory,成本高
function badExample(uint[] memory _data) public pure returns (uint) { ... }

// 优化后:external 函数参数用 calldata,成本低(推荐)
function goodExample(uint[] calldata _data) external pure returns (uint) { ... }

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)。

三、常见误区提醒

  1. 值类型不需要指定存储位置calldatamemory 只用于「引用类型」(数组、结构体、string),值类型(uintbooladdress 等)不需要指定,默认存储在「栈(stack)」中;
  2. public 函数参数不能默认 calldatapublic 函数可以被内部调用(如合约内其他函数调用),而内部调用没有 calldata 区域,所以 public 函数参数默认是 memory
  3. 返回引用类型必须是 memory:比如函数返回 uint[] 时,必须声明为 returns (uint[] memory),不能是 calldatacalldata 是外部输入,无法作为返回值传递);
  4. 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
2
3
4
5
6
7
8
9
10
11
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;

contract Contract {
function public sum() {
uint[5] storage myArrays = [1,2,3,4,5];
for i in myArrays: //我只知道python语法,没用过solidity的for循环
mysum += myArrays[i];
return mysum;
}
}

编译通过的代码

1
2
3
4
5
6
7
8
9
10
11
12
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;

contract Contract {
function sum(uint[5] calldata arr) external pure returns (uint) {
uint mysum = 0;
for (uint i = 0; i < arr.length; i++) {
mysum += arr[i];
}
return mysum;
}
}

学习代码

根据你的需求,我来修正并完善代码——Solidity 的语法和 Python 差异较大,尤其是函数定义、数组传递、循环语法和 pure 函数的约束,以下是完整的正确实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;

contract Contract {
// 1. 函数修饰符:external(外部调用)+ pure(无状态/无存储操作)
// 2. 参数:固定大小为5的无符号整数数组(uint[5])
// 3. 返回值:uint 类型的总和
function sum(uint[5] calldata arr) external pure returns (uint) {
uint mysum = 0; // 初始化总和(Solidity 变量必须显式初始化)

// Solidity 数组循环的两种常用写法(选一种即可)
// 写法1:按索引循环(推荐,兼容所有场景)
for (uint i = 0; i < arr.length; i++) {
mysum += arr[i]; // 累加数组元素(注意:不要用 arr[i]++,会修改数组,pure函数不允许)
}

// 写法2:for-in 循环(Solidity 0.8.17+ 支持,更简洁)
// for (uint num : arr) {
// mysum += num;
// }

return mysum; // 返回总和
}
}

关键修正说明(对比你的代码)

1. 函数定义修正

  • 原代码 function public sum() 错误:

    • 缺少参数(需要接收固定大小为5的数组);
    • 修饰符错误:需求是 external(外部函数,适合外部调用)+ pure(无存储读写、无状态依赖),而非 publicpublic 包含内部调用,没必要);
    • 缺少返回值声明 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

  1. 部署合约;
  2. 调用 sum 函数,输入参数 [1,2,3,4,5]
  3. 返回结果为 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的无符号整数数组,计算总和并返回,且符合 externalpure 的约束。

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
2
3
4
5
6
7
8
9
10
11
12
13
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;

contract Contract {
function sum(uint[] calldata arr) external pure returns (uint) {
//上面一行,删掉数字5就可以了。
uint mysum = 0;
for (uint i = 0; i < arr.length; i++) {
mysum += arr[i];
}
return mysum;
}
}

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
2
3
4
5
6
7
8
9
10
11
12
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;

contract Contract {
uint[] public evenNumbers;
function filterEven(uint[] calldata arr) external pure returns (uint) {
for (uint i = 0; i < arr.length; i++) {
if i/2 == 0 {
evenNumbers.push(i);
}
}
}

你的代码存在多个关键错误,我来逐一修复并解释问题所在,最终提供可正确运行的代码:
主要错误分析
函数修饰符错误:filterEven 函数需要修改状态变量 evenNumbers,但你使用了 pure 修饰符(pure 函数不允许读写状态变量),应改为 external(仅外部调用,符合题目要求)。
偶数判断逻辑错误:if i/2 == 0 完全错误!
你判断的是索引 i 而非数组元素 arr[i]
偶数判断的正确逻辑是 取余运算:arr[i] % 2 == 0(能被 2 整除的数是偶数)
语法错误:if 条件表达式必须加括号 (),即 if (条件)
返回值错误:题目要求函数将偶数存入 evenNumbers 状态变量,不需要返回值(你的 returns (uint) 多余且无意义)

正确答案

1
2
3
4
5
6
7
8
9
10
11
12
13
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;

contract Contract {
uint[] public evenNumbers;
function filterEven(uint[] calldata arr) external {
for (uint i = 0; i < arr.length; i++) {
if (arr[i] % 2 == 0) {
evenNumbers.push(arr[i]);
}
}
}
}

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;

contract Contract {
uint[] public evenNumbers;
function filterEven(uint[] calldata old_array) external pure returns(uint[] memory) {
//创建一个名为filterEven的纯外部函数,该函数仅接受一个动态大小的无符号整数数组作为参数。

uint evenCount = 0;
for (uint i = 0; i < old_array.length; i++) {
//找出所有偶数并将它们添加到内存中的新数组中。这个数组应该只包含偶数。
if (old_array[i] % 2 == 0) {
//evenNumbers.push(old_array[i]); 内存数组没有push方法
evenCount++;
}
}
// 第二步:初始化内存数组(长度为偶数个数,避免空间浪费)
uint[] memory evenArray = new uint[](evenCount);
uint index = 0; // 新数组的索引指针
// 第三步:遍历原数组,将偶数存入新数组
for (uint i = 0; i < old_array.length; i++) {
if (old_array[i] % 2 == 0) {
evenArray[index] = old_array[i];
index++; // 仅在存入偶数时移动索引
}
}

// 返回筛选后的偶数数组
return evenArray;
}
}

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;

contract StackClub {
address[] public members;
function addMember(address addr) external returns(address[] memory) {
members.push(addr);
return members;
}
function isMember(address addr) external view returns(bool) {
for (uint i = 0; i < members.length; i++) {
if (addr == members[i]) {
return true;
}
}
}

}