【区块链安全 | 第十八篇】类型之引用类型(二)

在这里插入图片描述

引用类型

数组切片

数组切片是对数组中连续部分的一种视图。它的语法为 x[start:end],其中 start 和 end 是表达式,结果类型为 uint256(或者可以隐式转换为 uint256)。切片的第一个元素是 x[start],最后一个元素是 x[end - 1]。

  • 如果 start 大于 end,或者 end 超过数组的长度,会抛出异常。

  • start 和 end 都是可选的:start 默认为 0,end 默认为数组的长度。

数组切片本身没有成员。它们可以隐式地转换为其底层类型的数组,并支持索引访问。索引访问相对于切片的起始位置,而不是底层数组的绝对位置。

注意: 目前,数组切片仅在 calldata 类型的数组上可用。数组切片在 ABI 解码函数参数时非常有用,特别是在处理来自外部调用的数据时:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.5 <0.9.0;

contract Proxy {
    /// @dev 代理管理的客户端合约地址
    address client;

    constructor(address client_) {
        client = client_;
    }

    /// 在对地址参数进行基本验证后,转发调用到客户端实现的 "setOwner(address)"。
    function forward(bytes calldata payload) external {
        bytes4 sig = bytes4(payload[:4]);
        // 由于截断行为,bytes4(payload) 的效果是一样的。
        // bytes4 sig = bytes4(payload);
        if (sig == bytes4(keccak256("setOwner(address)"))) {
            address owner = abi.decode(payload[4:], (address));
            require(owner != address(0), "Address of owner cannot be zero.");
        }
        (bool status,) = client.delegatecall(payload);
        require(status, "Forwarded call failed.");
    }
}

结构体

Solidity 允许开发者定义结构体(struct),这种自定义数据类型可以包含多个字段,例如地址、数值等,以下是一个例子:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.9.0;

// 定义一个具有两个字段的新类型。
// 在合约外部声明结构体允许它被多个合约共享。
// 这里其实不需要这样做。
struct Funder {
    address addr;
    uint amount;
}

contract CrowdFunding {
    // 结构体也可以在合约内部定义,这样它只在合约内部以及派生合约中可见。
    struct Campaign {
        address payable beneficiary;
        uint fundingGoal;
        uint numFunders;
        uint amount;
        mapping(uint => Funder) funders;
    }

    uint numCampaigns;
    mapping(uint => Campaign) campaigns;

    function newCampaign(address payable beneficiary, uint goal) public returns (uint campaignID) {
        campaignID = numCampaigns++; // campaignID 是返回的变量
        // 我们不能使用 "campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0)"
        // 因为右侧会创建一个内存结构体 "Campaign",其中包含一个映射。
        Campaign storage c = campaigns[campaignID];
        c.beneficiary = beneficiary;
        c.fundingGoal = goal;
    }

    function contribute(uint campaignID) public payable {
        Campaign storage c = campaigns[campaignID];
        // 创建一个新的临时内存结构体,并用给定的值初始化,
        // 然后将其复制到存储中。
        // 注意,你也可以使用 Funder(msg.sender, msg.value) 来初始化。
        c.funders[c.numFunders++] = Funder({addr: msg.sender, amount: msg.value});
        c.amount += msg.value;
    }

    function checkGoalReached(uint campaignID) public returns (bool reached) {
        Campaign storage c = campaigns[campaignID];
        if (c.amount < c.fundingGoal)
            return false;
        uint amount = c.amount;
        c.amount = 0;
        c.beneficiary.transfer(amount);
        return true;
    }
}

该合约虽然未实现完整的众筹功能,但它涵盖了理解结构体所需的核心概念。结构体(struct)类型可以被用作映射(mapping)和数组(array)中的元素,而结构体自身也可以包含映射或数组成员。

需要注意的是,结构体不能直接包含自身类型的成员,尽管它们可以作为映射的值类型,或者包含自身类型的动态数组。这一限制是为了确保结构体的整体大小是有限的,从而便于在存储中分配和管理。

在所有函数中,结构体类型通常被赋值给一个 存储(storage)位置 的局部变量。此操作不会复制整个结构体,而是创建一个引用,使得对该局部变量成员的修改会直接作用于合约的状态变量。

当然,也可以不通过局部变量,直接访问结构体的成员,例如:

campaigns[campaignID].amount = 0;

这同样会修改合约中的存储数据。

在 Solidity 0.7.0 及更早版本中,允许内存结构体中包含存储类型的成员(例如映射)。因此,像以下这样的结构体初始化虽然可以编译并执行,但会悄无声息地跳过映射等不支持的成员:

campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0);

为了避免出现意料之外的行为,推荐使用 storage 引用方式手动赋值各成员字段,尤其是在结构体中包含复杂类型的情况下。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秋说

感谢打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值