【区块链安全 | 第二十五篇】表达式与控制结构(一)

在这里插入图片描述

表达式与控制结构

控制结构

Solidity 提供了大多数常见的控制结构,这些结构在使用大括号的语言中十分常见,包括:if、else、while、do、for、break、continue 和 return,其语义与 C 或 JavaScript 中基本一致。

Solidity 还支持 try/catch 异常处理语句,但仅适用于外部函数调用和合约创建调用。此外,可以使用 revert 语句来主动抛出错误。

条件语句中的括号是必须的,但单条语句体可以省略大括号。

注意:Solidity 不支持从非布尔类型隐式转换为布尔类型,因此诸如 if (1) { … } 这样的写法在 Solidity 中是无效的。

函数调用

内部函数调用

当前合约中的函数可以直接(即“内部”)调用,也可以实现递归调用,例如下方这个无意义但有效的例子:

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

// 这将触发一个警告
contract C {
    function g(uint a) public pure returns (uint ret) { return a + f(); }
    function f() internal pure returns (uint ret) { return g(7) + f(); }
}

这些函数调用在 EVM 中会被编译为简单的跳转(jump),意味着内存不会被清空,因此向内部函数传递内存引用是非常高效的。

注意:仅同一个合约实例中的函数才能通过内部调用方式访问。应避免深度递归调用,因为每次内部调用至少占用一个栈槽,而 EVM 中栈槽总数仅为 1024 个。

外部函数调用

函数也可以通过 this.g(8); 或 c.g(2); 的方式调用,其中 c 是某个合约实例,g 是该合约中的函数。这类调用属于外部调用,是通过**消息调用(message call)**实现的,而非简单跳转。

注意:在构造函数中不能调用 this 的函数,因为此时合约尚未部署完成。

对其他合约的函数调用必须使用外部调用方式,并且所有参数都需要复制到内存中。

注意:从一个合约调用另一个合约的函数不会生成新的交易,该调用作为原始交易的一部分通过消息传递机制进行。

调用外部合约时,可以使用 {value: 10, gas: 10000} 这样的选项来显式指定调用时发送的 Wei 金额或 Gas 数量。不过不推荐手动指定 Gas 数值,因为未来操作码的 Gas 成本可能发生变化。任何通过 value 发送的 Wei 都会增加接收合约的总余额。

示例如下:

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

contract InfoFeed {
    function info() public payable returns (uint ret) { return 42; }
}

contract Consumer {
    InfoFeed feed;
    function setFeed(InfoFeed addr) public { feed = addr; }
    function callFeed() public { feed.info{value: 10, gas: 800}(); }
}

我们必须在 info 函数上添加 payable 修饰符,否则不能通过 value 向其转账。

注意:表达式 feed.info{value: 10, gas: 800} 只是设置调用参数,并不会执行函数调用。必须在末尾添加 () 才会触发实际调用,即 feed.info{value: 10, gas: 800}() 才是完整的函数调用表达式。

由于 EVM 对不存在的合约地址调用默认视为成功,Solidity 会使用 extcodesize 操作码检查目标地址是否有代码(即是否为有效合约)。若地址无代码,将抛出异常。但若调用后还需解码返回值,该检查将被跳过,因为 ABI 解码器能捕获无代码地址的异常情况。

警告:
与其他合约的交互始终存在潜在风险,特别是当被调用合约的源码未知时。当前合约在执行过程中会将控制权转交给被调用合约,而后者可能会执行任何逻辑。即使被调用合约继承自已知合约,只要接口一致,其具体实现也可以截然不同,因此仍存在风险。

此外,还需防范被调用合约在第一次返回前就回调我们系统中的其他合约,甚至是当前合约本身。这类“重入攻击”可能被用来修改调用合约的状态变量。

因此,强烈建议:将对外部合约的调用放在状态变量更改之后,以防止重入攻击。

使用具名参数的函数调用

函数调用时可以使用具名参数(Named Parameters),即将参数包裹在 {} 中,并通过名称传入。此时参数顺序可以任意,但名称必须与函数声明中的一致:

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

contract C {
    mapping(uint => uint) data;

    function f() public {
        set({value: 2, key: 3});
    }

    function set(uint key, uint value) public {
        data[key] = value;
    }
}

函数定义中省略的名称

函数声明中的参数名和返回值名可以省略。被省略的参数仍存在于栈中,但无法通过名称访问;而被省略名称的返回值仍然可以通过 return 语句返回给调用者:

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

contract C {
    // 参数的名称被省略
    function func(uint k, uint) public pure returns(uint) {
        return k;
    }
}

通过 new 创建合约

合约可以使用 new 关键字来部署其他合约。被创建合约的完整代码必须在编译时已知,因此不支持创建依赖尚未定义的合约(即递归依赖):

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract D {
    uint public x;
    constructor(uint a) payable {
        x = a;
    }
}

contract C {
    D d = new D(4); // 这将在 C 的构造函数中执行

    function createD(uint arg) public {
        D newD = new D(arg);
        newD.x();
    }

    function createAndEndowD(uint arg, uint amount) public payable {
        // 创建时发送以太币
        D newD = new D{value: amount}(arg);
        newD.x();
    }
}

如上所示,可以通过 value 选项在创建 D 实例时发送以太币。然而,不能显式指定 gas 使用量。

如果合约创建失败(例如因栈溢出、余额不足或其他问题),将抛出异常。

带有 Salt 的合约创建 / create2

通常情况下,合约地址是根据部署者地址和一个递增的计数器(nonce)计算出来的。但如果使用 salt(一个 bytes32 类型的值),则会使用 CREATE2 指令生成合约地址,其地址计算方式如下:

合约地址由以下字段确定:

  • 创建者地址(address(this))

  • 给定的 salt 值

  • 被创建合约的创建字节码(creationCode)和构造函数参数的编码值

这种方式不依赖 nonce,因此允许在链下预计算新合约的部署地址。即使创建者合约中间又部署了其他合约,也不会影响预计算的地址。

这一机制常用于只有在链上出现争议时才需要部署的仲裁型合约:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract D {
    uint public x;
    constructor(uint a) {
        x = a;
    }
}

contract C {
    function createDSalted(bytes32 salt, uint arg) public {
        // 这个复杂的表达式仅用于说明地址是如何被预计算出来的。
        // 实际上我们只需要写 ``new D{salt: salt}(arg)``。
        address predictedAddress = address(uint160(uint(keccak256(abi.encodePacked(
            bytes1(0xff),
            address(this),
            salt,
            keccak256(abi.encodePacked(
                type(D).creationCode,
                abi.encode(arg)
            ))
        )))));

        D d = new D{salt: salt}(arg);
        require(address(d) == predictedAddress);
    }
}

注意:通过 CREATE2 创建的合约,即使在销毁后,也可以使用相同地址重新创建。但需谨慎:

  • 地址是否复用,取决于新合约的创建字节码与原合约是否完全一致。

  • 构造函数若依赖外部状态,不同部署时可能生成不同的运行时代码(runtime bytecode),导致行为不一致。

表达式的求值顺序

Solidity 中表达式的求值顺序是未定义的(更准确地说,表达式树中某个节点的子表达式的求值顺序不确定,但会在父节点求值前完成)。

唯一被明确保证的是:

  • 语句按顺序执行

  • 布尔表达式采用短路求值(即 && 和 || 表达式遵循从左到右,必要时跳过右侧分支的执行)

# 题目重述 您提出的要求是:**重新生成份完整、正确、可步步操作的建模步骤**,用于在 AnyLogic 中文版中构建“基于区块链的沃尔玛猪肉溯源系统采纳价值评估模型”。该问题属于“普通文本型”问题,但实质是对前期所有内容的整合升华。 以下是为您精心设计的 **终极版、手把手级、零基础可执行的操作指南**,已融合: - 智能体建模(ABM) - 系统动力学(SD) - 流程流转 - 扫码交互 - 宏观-微观联动机制 全适配 AnyLogic 中文界面,每步均可直接点击完成。 --- # 详解 ## ✅ 第步:新建模型项目 1. 打开 AnyLogic 软件; 2. 点击菜单栏 `文件 > 新建 > 模型`; 3. 在弹出窗口中填写: - **模型名称**:`区块链猪肉溯源_采纳评估` - **作者**:输入您的姓名 4. 建模方法选择: - ✔️ 勾选 **基于智能体建模(Agent-Based Modeling)** 5. 时间单位选择:`天` 6. 单位系统:`自定义` 7. 点击 `完成` > 📌 进入主程序编辑界面。 --- ## ✅ 第二步:创建五类供应链主体(智能体类型) ### (1)创建【农户】智能体 1. 在左侧“项目”面板右键 `智能体` → `新建 > 智能体`; 2. 名称输入:`农户`,点击确定; 3. 切换到 `参数` 选项卡,添加: | 名称 | 类型 | 默认值 | 说明 | |------|------|--------|------| | 是否使用区块链 | boolean | false | 决策开关 | | 区块链年成本 | double | 500 | 上链支出 | 4. 切换到 `变量` 选项卡,添加: | 名称 | 类型 | 初始值 | 说明 | |------|------|--------|------| | 当前信任度影响因子 | double | 0.5 | 来自市场反馈 | | 最新生猪批次编号 | int | 0 | 标识生产批次 | 5. 保存并关闭。 --- ### (2)创建【加工商】智能体 1. 同样方式新建智能体:`加工商` 2. 添加参数: | 名称 | 类型 | 默认值 | |------|------|--------| | 是否使用区块链 | boolean | false | | 加工每公斤成本 | double | 8 | | 区块链运营成本 | double | 800 | 3. 添加变量: | 名称 | 类型 | 说明 | |------|------|------| | 质检报告哈希值 | String | 存储质检信息的数字指纹 | --- ### (3)创建【物流商】智能体 1. 新建智能体:`物流商` 2. 参数: | 名称 | 类型 | 默认值 | |------|------|--------| | 是否使用区块链 | boolean | false | | 温控监测年成本 | double | 300 | 3. 变量: | 名称 | 类型 | 说明 | |------|------|------| | 当前温度 | double | 实时冷链温度 | | 运输路线 | List<Location> | GPS轨迹记录 | --- ### (4)创建【零售商】智能体(代表沃尔玛) 1. 新建智能体:`零售商` 2. 参数: | 名称 | 类型 | 默认值 | 说明 | |------|------|--------|------| | 是否要求供应商上链 | boolean | true | 强制标准 | | 可追溯产品溢价系数 | double | 1.2 | 安全肉加价比例 | | 消费者平均信任指数 | double | 0.7 | 动态更新 | 3. 变量: | 名称 | 类型 | 说明 | |------|------|------| | 已销售可追溯猪肉总量 | int | 统计销量 | | 总收入 | double | 分类统计收益 | --- ### (5)创建【消费者】智能体 1. 新建智能体:`消费者` 2. 参数: | 名称 | 类型 | 默认值 | 说明 | |------|------|--------|------| | 基础支付溢价意愿 | double | 0.1 | 愿多付10% | | 对溯源敏感度 | double | 0.6 | 关注二维码信息 | --- ## ✅ 第三步:在主程序中建立供应链结构 1. 打开 `主程序(Main)` 编辑界面; 2. 在“智能体群体”区域依次拖入以下四个群体: | 智能体类型 | 群体名称 | 数量 | |-----------|----------|------| | 农户 | 农户群体 | 10 | | 加工商 | 加工商群体 | 3 | | 物流商 | 物流群体 | 2 | | 零售商 | 零售商实例 | 1(不设数量>1) | 3. 使用工具栏中的 `网络` 工具绘制连接线: - 从 `农户群体` → `加工商群体` - 从 `加工商群体` → `物流群体` - 从 `物流群体` → `零售商实例` > 💡 表示产品流动方向。 --- ## ✅ 第四步:定义猪肉批次类(自定义Java类) 1. 菜单栏 → `工具 > 新建Java类` 2. 名称输入:`猪肉批次` 3. 替换默认代码为: ```java public class 猪肉批次 { public int 批次编号; public 农户 生产者; public double 重量公斤; public String 养殖记录哈希; public String 加工记录哈希; public String 冷链日志哈希; public boolean 是否全程可追溯; public 猪肉批次(int id, 农户 f) { this.批次编号 = id; this.生产者 = f; this.是否全程可追溯 = false; } } ``` 4. 保存。 --- ## ✅ 第五步:模拟“区块链上链”行为(哈希函数) 1. 菜单栏 → `工具 > 函数` 2. 点击 `新建函数` 3. 设置如下: - **名称**:`模拟数据上链` - **返回类型**:`String` - **参数**:`Object 数据对象` 4. 在函数体中输入: ```java String dataStr = 数据对象.toString(); String hashValue = md5(dataStr); trace("【区块链】数据已登记,哈希值:" + hashValue); return hashValue; ``` > ✅ 此函数将在各环节调用,模拟“上传数据到链上”。 --- ## ✅ 第六步:为主体制定“是否采用区块链”的决策逻辑 以【农户】为例: 1. 打开 `农户` 智能体; 2. 插入 `状态图` 元素; 3. 绘制两个状态: - `未使用区块链` - `正在使用区块链` 4. 添加转移箭头,设置条件表达式: ```java double T = 主程序.消费者平均信任水平; double Rp = 主程序.支付溢价意愿; double Cb = 区块链年成本; double exponent = 0.8*T + 0.6*Rp - 0.4*(Cb/100); double probability = 1 / (1 + Math.exp(-exponent)); return randomTrue(probability); ``` 5. 添加个【事件】: - 名称:`检查采纳决策` - 类型:`超时` - 触发时间:`30` - 重复间隔:`30` - 动作:无(由状态图自动处理) > 👉 每30天检查次是否切换状态。 --- ## ✅ 第七步:搭建产品流动流程(使用流程图) ### 启用流程建模库 - 点击顶部 `开发工具箱` → 勾选 `流程建模库` ### 创建流程图节点序列 在 `主程序` 中插入 `流程图`,绘制: ``` [源] --> [农户节点] --> [加工商节点] --> [物流运输] --> [零售商展示] --> [扫码查询] --> [是否购买] ``` #### 各节点配置说明: | 节点 | 类型 | 关键动作 | |------|------|---------| || Source | 生成 `猪肉批次` 实例 | | 农户节点 | Service | 调用 `模拟数据上链(养殖信息)` 并记录哈希 | | 加工商节点 | Service | 更新加工信息并再次上链 | | 物流运输 | Delay | 模拟运输时间,周期记录温度 | | 零售商展示 | Queue | 显示二维码供扫描 | | 扫码查询 | Service | 验证哈希致性,更新信任 | | 是否购买 | SelectOutput | 根据信任决定是否购买 | #### 【扫码查询】进入时动作代码: ```java 猪肉批次 batch = (猪肉批次) unit; String chainHash = 主程序.getBlockchainHash(batch.批次编号); String localHash = md5(batch.toString()); if (equals(chainHash, localHash)) { batch.是否全程可追溯 = true; trace("✅ 数据致,信息真实!"); } else { batch.是否全程可追溯 = false; trace("❌ 数据被篡改!"); } // 更新当前消费者信任 消费者.thisAgent().当前信任水平 = batch.是否全程可追溯 ? 0.8 : 0.3; ``` --- ## ✅ 第八步:嵌入系统动力学模块(核心增强!) ### 目标:实现“信任—采纳—收益”闭环反馈 1. 在 `主程序` 中插入 `系统动力学图`; 2. 绘制以下元素: ### (1)存量(Stock) | 名称 | 初始值 | 属性 | |------|--------|------| | 已接入区块链的企业数量 | 2 | 非负 | | 消费者平均信任水平 | 0.5 | 非负 | | 累计扫码次数 | 0 | 非负 | ### (2)流量(Flow) | 名称 | 连接存量 | 表达式 | |------|----------|--------| | 新增接入速度 | → 已接入企业数量 | `(15 - 已接入区块链的企业数量) * max(0, 0.8*消费者平均信任水平 + 0.6*支付溢价意愿 - 0.4*(500/100))` | | 信任变化速率 | → 消费者平均信任水平 | `0.05 * (实际溯源质量感知 - 消费者平均信任水平)` | | 扫码增长速率 | → 累计扫码次数 | `0.3 * (已接入区块链的企业数量/15) * 100` | ### (3)辅助变量 | 名称 | 表达式 | 说明 | |------|--------|------| | 可追溯产品市场占比 | `已接入区块链的企业数量 / 15` | 透明度指标 | | 支付溢价意愿 | `0.8 * 消费者平均信任水平` | 影响价格 | | 实际溯源质量感知 | `可追溯产品市场占比 > 0.6 ? 0.9 : 0.4` | 用户体验判断 | | 总企业数量 | `15` | 固定值 | --- ## ✅ 第九步:实现“扫码”交互功能(鼠标点击模拟) 1. 在零售商界面上插入张二维码图片(PNG格式); 2. 右键图片 → 属性 → “单击时”事件; 3. 输入代码: ```java message( "【猪肉溯源详情】\n" + "批次编号:" + 当前显示批次.批次编号 + "\n" + "养殖地:" + 当前显示批次.生产者 + "\n" + "加工时间:" + 加工完成时间 + "\n" + "冷链记录完整:" + (冷链正常 ? "是" : "否") + "\n" + "是否全程可追溯:" + (当前显示批次.是否全程可追溯 ? "是" : "否") ); ``` > ✅ 模拟手机扫码后跳转页面效果。 --- ## ✅ 第十步:连接 SD ABM(双向数据同步) ### (1)ABM → SD:定期更新接入数量 在 `主程序` 中添加个【事件】: - 名称:`更新SD数据` - 类型:`超时` - 触发时间:`0` - 重复间隔:`30` - 动作代码: ```java int total = 0; total += 农户群体.count(f -> f.是否使用区块链); total += 加工商群体.count(p -> p.是否使用区块链); total += 物流群体.count(l -> l.是否使用区块链); 主程序.已接入区块链的企业数量 = total; ``` ### (2)SD → ABM:驱动决策函数 在【农户】状态转移条件中引用 SD 输出(已完成第六步) --- ## ✅ 第十步:设置可视化实验 ### 添加动态图表 1. 折线图:显示 - `消费者平均信任水平` - `已接入企业数量` 2. 柱状图:对比不同成本下的最终采纳率 ### 添加控制按钮 | 按钮名 | 动作 | |-------|------| | 开始仿真 | `run()` | | 暂停 | `pause()` | | 导出日志 | `exportToCSV(...)` | --- # 知识点 1. **系统动力学刻画宏观演化规律** 通过存量流量方程描述信任积累技术扩散过程。 2. **混合建模实现微观-宏观联动** ABM 提供个体行为,SD 提供环境反馈,形成闭环系统。 3. **扫码交互作为信任传递接口** 消费者通过主动获取信息建立真实感知,驱动正向激励循环。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

秋说

感谢打赏

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

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

打赏作者

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

抵扣说明:

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

余额充值