【区块链安全 | 第十篇】智能合约概述

部分内容与前文互补。

在这里插入图片描述

文章目录

    • 一个简单的智能合约
    • 子货币(Subcurrency)示例
    • 区块链基础
      • 交易
      • 区块
      • 预编译合约

一个简单的智能合约

我们从一个基础示例开始,该示例用于设置变量的值,并允许其他合约访问它。

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;contract SimpleStorage {uint storedData;function set(uint x) public {storedData = x;}function get() public view returns (uint) {return storedData;}
}

代码的第一行表明,该源代码采用 GPL 3.0 许可证进行授权。在以源代码公开为默认规则的环境中,使用机器可读的许可证标识符是非常重要的。

接下来一行 pragma solidity >=0.4.16 <0.9.0; 指定了该合约适用于 Solidity 0.4.16 及以上版本,但不包括 0.9.0。这是为了确保合约不会在未来的破坏性更新(Breaking Changes)中出现兼容性问题。Pragma 语句是编译器的指令,类似于 C/C++ 语言中的 pragma once,用于指定源代码的编译方式。

在 Solidity 语言中,合约(contract) 本质上是一个代码(函数)和数据(状态)的集合,它们驻留在以太坊区块链上的特定地址处。

contract SimpleStorage {uint storedData;function set(uint x) public {storedData = x;}function get() public view returns (uint) {return storedData;}
}

在合约 SimpleStorage 中,uint storedData; 声明了一个状态变量 storedData,其类型为 uint(无符号整数,默认为 256 位)。你可以把它看作数据库中的一个单一存储槽位,可以通过调用合约中的函数来查询和修改它。在这个示例中,合约提供了 set 和 get 两个函数,分别用于修改和获取 storedData 的值。

在 Solidity 中,访问当前合约的成员变量(如 storedData),通常无需使用 this. 前缀,直接使用变量名即可。这不仅仅是代码风格的问题,而是影响访问方式的关键区别(后续会详细讲解)。

这个合约本身功能还比较简单,但得益于以太坊的基础架构,它允许任何人存储一个数值,并让全球范围内的任何人访问。理论上,没有任何方法可以阻止你发布这个数值。但需要注意,任何人都可以再次调用 set 方法,修改存储的值,并覆盖之前的数据。不过,之前存储的数据仍然会保留在区块链的历史记录中。

后续会介绍如何实现访问权限控制,以便只有你自己才能修改这个值。

警告:使用 Unicode 文本时需要小心,因为一些看起来相似甚至完全相同的字符,可能具有不同的代码点(Code Point),因此它们的字节编码可能不同,从而引发安全或兼容性问题。
注意:所有标识符(包括合约名、函数名和变量名)都必须使用 ASCII 字符集。不过,你仍然可以在 string 类型的变量中存储 UTF-8 编码的数据。

子货币(Subcurrency)示例

以下合约实现了最简单形式的加密货币。该合约仅允许其创建者铸造新币。任何人都可以在没有用户名和密码的情况下相互转账,所需的只是一个以太坊密钥对。

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.26;// 该合约只能通过 IR 编译
contract Coin {// 关键字 "public" 使变量可被其他合约访问// 相当于所有人可见创建者的合约地址address public minter;mapping(address => uint) public balances;// 事件允许客户端对你声明的特定合约更改做出反应event Sent(address from, address to, uint amount);// 构造函数代码仅在合约创建时运行constructor() {minter = msg.sender;}// 向指定地址铸造一定数量的新币// 仅合约创建者可以调用// 相当于只有合约创建者可以向别人发送新币function mint(address receiver, uint amount) public {require(msg.sender == minter);balances[receiver] += amount;}// 错误(Errors)允许提供有关操作失败原因的信息// 这些信息会返回给调用该函数的用户error InsufficientBalance(uint requested, uint available);// 发送一定数量的现有币// 任何人都可以调用,将代币发送至指定地址function send(address receiver, uint amount) public {// 发送的数量必须小于等于自己拥有的数量require(amount <= balances[msg.sender], InsufficientBalance(amount, balances[msg.sender]));// 发送者减少balances[msg.sender] -= amount;// 接收者增加balances[receiver] += amount;emit Sent(msg.sender, receiver, amount);}
}

代码 address public minter; 声明了一个 address 类型的状态变量。

address 类型是一个 160 位的值,不允许执行任何算术运算。它适用于存储合约地址,或者存储外部账户(EOA)公钥哈希的一部分。

关键字 public 会自动生成一个函数,使外部可以访问当前合约的状态变量。如果没有 public,其他合约将无法访问该变量。

编译器生成的代码等效于以下函数(暂时忽略 external 和 view 关键字):

function minter() external view returns (address) { return minter; 
}

下一行代码:

mapping(address => uint) public balances;

这行代码同样定义了一个 public 状态变量,但它的类型比 address 更复杂。mapping 是 Solidity 提供的一种映射类型,它将地址映射到 uint(无符号整数),即每个地址对应一个余额。

mapping 的特性:

  • mapping 类似于哈希表,所有可能的键在初始化时就已经存在,并默认映射到 0(即字节表示全为零)。

  • 无法获取 mapping 的所有键或所有值,因此如果你需要跟踪存储在 mapping 中的数据,最好自己维护一个列表,或者使用更合适的数据结构。

使用 mapping 是因为它提供了一种高效且简洁的方式来关联每个地址与其余额,且适应了区块链中分布式账本的特点。

由于 balances 变量是 public,编译器会自动生成以下 getter 函数:

function balances(address account) external view returns (uint) {return balances[account];
}

这个函数可以用于查询某个账户的余额,例如:

uint myBalance = contract.balances(myAddress);

这样,你就可以直接在外部访问某个地址的 balance,而无需手动编写 getter 方法。

这一行代码:

event Sent(address from, address to, uint amount);

声明了一个 事件(event),它在 send 函数的最后一行被触发(emit)。像 Web 应用程序这样的以太坊客户端可以监听这些事件,而不会产生太多成本。

当事件被触发后,监听器会立即收到 from、to 和 amount 这三个参数,从而能够跟踪交易。

刚才提到的以太坊客户端使用以下 JavaScript 代码(web3.js)监听 Sent 事件,并调用 balances 函数来更新用户界面:

Coin.Sent().watch({}, '', function(error, result) {if (!error) {console.log("Coin transfer: " + result.args.amount +" coins were sent from " + result.args.from +" to " + result.args.to + ".");console.log("Balances now:\n" +"Sender: " + Coin.balances.call(result.args.from) +"Receiver: " + Coin.balances.call(result.args.to));}
});

构造函数是一种特殊的函数,在合约创建时执行,且无法在之后被调用。

在这个合约中,构造函数会永久存储创建合约的人的地址:

constructor() {minter = msg.sender;
}

其中,msg 是 Solidity 提供的全局变量,它包含了一些区块链相关的属性,比如msg.sender为当前调用该函数的外部账户(EOA)或合约地址。

这个合约有两个主要的用户调用函数:

  • mint —— 铸造新币

  • send —— 发送已存在的币

mint(铸造新币)

function mint(address receiver, uint amount) public {require(msg.sender == minter);balances[receiver] += amount;
}

只有合约的创建者(minter)可以调用 mint,因为:

require(msg.sender == minter);

如果 msg.sender 不是 minter,则交易会被回滚(revert)。

balances[receiver] += amount; 为接收者账户增加一定数量的新币。

注意: 虽然 minter 可以无限制铸造代币,但如果 balances[receiver] + amount 超过 uint 类型的最大值 2的256次方 - 1,就会导致溢出(overflow)。然而,Solidity 默认启用了 Checked arithmetic(溢出检查),所以如果溢出发生,交易会自动回滚。

send(发送币)

function send(address receiver, uint amount) public {require(amount <= balances[msg.sender], InsufficientBalance(amount, balances[msg.sender]));balances[msg.sender] -= amount;balances[receiver] += amount;emit Sent(msg.sender, receiver, amount);
}

任何人(已经拥有币的人)都可以调用 send,将币发送给其他人。

如果 msg.sender 的余额不足:

require(amount <= balances[msg.sender], InsufficientBalance(amount, balances[msg.sender]));

交易会回滚(revert),并返回 InsufficientBalance 错误,错误信息会提供给调用者,以便前端应用或区块浏览器能够显示失败的具体原因。

Solidity 允许在交易失败时提供更多的错误信息,以便前端应用可以更容易地调试或做出反应。

错误信息通过 revert 语句触发:

error InsufficientBalance(uint requested, uint available);

当 require 失败时,它会返回 InsufficientBalance,并提供请求的金额 requested 和可用余额 available。

注意,在这个例子中,所有的代币操作(如铸造、转账)都在合约内部完成,余额和交易信息是局部的,仅存储在合约的 balances 映射中。

普通区块链浏览器(如 Etherscan)只能显示以太坊全局账户余额,你不会在普通的区块浏览器中看到余额变化。

解决方案:监听 Sent 事件,并创建自己的区块链浏览器来跟踪交易记录和余额变化,但你查询合约地址(通过合约内部的查询函数),而不是代币持有人的地址。

区块链基础

区块链作为一个概念对于程序员来说并不难理解。大多数复杂性(如哈希、椭圆曲线加密、对等网络等)只是为了为平台提供一组特定的功能和承诺。一旦你接受了这些特性作为前提,你就不必担心底层技术——就像你不需要知道亚马逊的 AWS 是如何在内部工作的。

交易

区块链是一个全球共享的事务性数据库。这意味着每个人都可以通过参与网络来读取数据库中的条目。如果你想更改数据库中的内容,你必须创建一个所谓的“交易”,并且这个交易必须被所有其他参与者接受。

“交易”一词意味着你想要进行的更改(假设你同时想更改两个值)要么完全不做,要么完全应用。此外,在你的交易被应用到数据库时,其他交易不能修改它。

例如,假设有一个表格列出了所有账户的余额。如果请求从一个账户转账到另一个账户,数据库的事务性特征确保如果从一个账户扣除金额,这个金额始终会被加到另一个账户上。如果由于某种原因无法将金额添加到目标账户,源账户也不会被修改。

此外,交易总是由发送者(创建者)进行加密签名。这使得保护对数据库特定修改的访问变得简单。举个例子,只有持有账户密钥的人可以从中转移一定的货币。

区块

需要克服的一个主要问题是双重支付攻击:“如果在网络中有两个交易都想清空一个账户,该怎么办?”

解决方案是:只有其中一个交易可以是有效的,通常是先被接受的那个。

问题在于,“先”在对等网络中并不是一个客观的术语。

对此的抽象回答是:你不需要担心。一个全球公认的交易顺序会为你选定,从而解决冲突。这些交易会被打包成一个叫做“区块”的内容,然后被执行并在所有参与节点之间分发。如果两个交易互相矛盾,第二个交易会被拒绝,并不会成为区块的一部分。

这些区块形成了一个线性时间序列,这也是“区块链”这一术语的来源。区块会在定期的间隔时间内添加到链中,尽管这些间隔时间将来可能会发生变化。为了获取最新的信息,建议监控网络,例如通过 Etherscan。

可能会发生区块偶尔被回滚的情况,但仅限于“链顶”部分。这是因为越多的区块添加到某个区块上时,这个区块被回滚的可能性就越小。所以,可能会出现你的交易被回滚甚至从区块链中移除的情况,但等待的时间越长,这种情况发生的可能性就越小。

注意
交易并不能保证会包含在下一个区块或任何特定的未来区块中,因为是否将交易包含在区块中并不是由交易提交者决定的,而是由矿工决定交易被包含在哪个区块中。

如果我们想安排未来的智能合约调用,可以使用智能合约自动化工具(比如定时触发某个操作,或者基于某个事件触发合约的函数调用)或预言机服务。

预编译合约

在以太坊中,智能合约通常用 Solidity 编写,并转换为 EVM 字节码执行。但一些计算(例如椭圆曲线加密、哈希计算)如果用 Solidity 实现,会消耗大量 Gas,甚至无法在区块 Gas 限制内完成。因此,以太坊提供了一组内置的预编译合约。

地址范围 0x01 到 0x0a(包含 0x0a) 属于预编译合约(Precompiled Contracts)。这些合约可以像普通合约一样被调用,但它们的行为(包括 Gas 消耗)并不是由存储在这些地址上的 EVM 代码决定的。这些合约直接在 EVM 层面执行,比普通智能合约运行更高效,并且Gas 消耗更少。

在这里插入图片描述

这些合约特别适用于密码学、哈希计算、零知识证明等高计算量的任务。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/73912.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

XML标签格式转换为YOLO TXT格式

针对的是多边形&#xff08;<polygon>&#xff09;来描述对象的边界&#xff0c;而不是传统的矩形框&#xff08;<bndbox>&#xff09; import xml.etree.ElementTree as ET import os from pathlib import Path# 解析VOC格式的XML文件&#xff0c;提取目标框的标…

大唐杯02 DTM.PX4.016

01 5G关键技术概述 回传压力大&#xff1a;核心网向基站回传压力大 02 5G关键技术介绍01

CSS3学习教程,从入门到精通, CSS3 盒子模型的详细语法知识点及案例代码(23)

CSS3 盒子模型的详细语法知识点及案例代码 CSS3 盒子模型完整指南 一、盒子模型基础 每个 HTML 元素都被视为一个矩形盒子&#xff0c;由以下部分组成&#xff1a; 内容区 (Content)内边距 (Padding)边框 (Border)外边距 (Margin) 二、语法知识点详解 1. 盒子的宽和高 sel…

《Linux运维实战:Ubuntu 22.04修改root用户默认名并禁止登录》

总结&#xff1a;整理不易&#xff0c;如果对你有帮助&#xff0c;可否点赞关注一下&#xff1f; 更多详细内容请参考&#xff1a;Linux运维实战总结 一、背景信息 由于安全方面的考虑&#xff0c;先要求Ubuntu 22.04系统重的root用户禁止登录&#xff0c;并修改用户名root为ad…

docker-compose自定义网络,解决docker-compose网段路由冲突

问题排查 先route一波查看一下路由表 容器路由19和堡垒机路由冲突 解决方案 更改docker网段更改docker生成容器的网段 > 基本操作 docker network ls &#xff1a;查看docker网络列表 docker network inspect <network id/name>&#xff1a;查看某个docker网络详情…

前端 - ts - - declare声明类型

在使用typeScript的项目中 需要声明属性类型 单独的局部属性 可以直接在当前文件中声明 全局属性需要在项目根目录下新建.d.ts文件 vite会自动识别.d.ts类型文件 在该文件中使用declare声明类型有三种写法 1、在某种类型的文件中声明 2、声明window上的属性类型 3、全局声明…

[Mac]利用Hexo+Github Pages搭建个人博客

由于我这台Mac基本没啥环境&#xff0c;因此需要从零开始配置&#xff0c;供各位参考。 注意⚠️&#xff1a;MacBook (M4)使用/bin/zsh作为默认Shell&#xff0c;其对应的配置文件为~/.zshrc 参考文档&#xff1a; HEXO系列教程 | 使用GitHub部署静态博客HEXO | 小白向教程 文…

运维面试题(十一)

1.如果一个硬盘 IO 时阻塞了&#xff0c;会发生什么情况&#xff1f; 进程/线程挂起&#xff1a;发起I/O操作的进程或线程会被操作系统置为阻塞状态&#xff08;等待状态&#xff09;&#xff0c;直到I/O完成。CPU资源释放&#xff1a;阻塞的线程会让出CPU&#xff0c;操作系统…

sql2022 复制 事务级别发布后无法删除

Cannot execute as the database principal because the principal "dbo" does not exist, this type of principal cannot be impersonated, or you do not have permission. 用SA用户登录执行下列语句 USE [xxxxx] GO EXEC dbo.sp_changedbowner loginame Nsa, …

合规+增效 正也科技携智能营销产品出席中睿论坛

正也科技作为医药数字化领域的标杆企业&#xff0c;受邀参展第二届中睿医健产业企业家年会暨第十三届中睿医药新春论坛&#xff0c;本次论坛以“合力启新程”为主题&#xff0c;吸引了800多位医药健康企业的董事长、总经理参与&#xff0c;并通过主论坛、分论坛、路演等形式探讨…

ubuntu 安装 postgresql

在 Ubuntu 系统中安装 PostgreSQL 的步骤如下&#xff1a; 步骤 1&#xff1a;更新软件包列表 sudo apt update步骤 2&#xff1a;安装 PostgreSQL Ubuntu 默认仓库包含 PostgreSQL&#xff0c;直接安装&#xff1a; sudo apt install postgresql postgresql-contrib -ypost…

智能巡检机器人:2025年企业安全运维的“数字哨兵“

文章目录 一、2025年&#xff0c;为什么企业需要智能巡检机器人&#xff1f;二、2025年智能巡检机器人的六大核心价值三、2025行业落地实景1. 电网系统——"巡线鹰"集群作战2. 化工园区——"防爆卫士"全天候守护3. 数据中心——"冷血侦探"精准运…

K8S学习之基础五十一:k8s部署jenkins

k8s部署jenkins 创建nfs共享目录&#xff0c; mkdir -p /data/v2 echo /data/v2 *(rw,no_root_squash) > /etc/exports exportfs -arv创建pv、pvc vi pv.yaml apiVersion: v1 kind: PersistentVolume metadata:name: jenkins-k8s-pv spec:capacity:storage: 1GiaccessMod…

Vue实现的表格多选方案支持跨页选择和回显功能

以下是纯Vue实现的表格多选方案&#xff08;不依赖UI库&#xff09;&#xff0c;支持跨页选择和回显功能&#xff1a; <template><div class"custom-table"><!-- 表格主体 --><table><thead><tr><th><input type"…

Oracle 19C 备份

在 Oracle 19c 中&#xff0c;备份数据库通常使用 RMAN&#xff08;Recovery Manager&#xff09; 工具&#xff0c;它是 Oracle 提供的官方备份和恢复工具。以下是通过 RMAN 备份 Oracle 19c 数据库的详细步骤和命令。 一、RMAN 基本概念 RMAN 是 Oracle 的备份和恢复工具&am…

Elasticsearch:人工智能时代的公共部门数据治理

作者&#xff1a;来自 Elastic Darren Meiss 人工智能&#xff08;AI&#xff09;和生成式人工智能&#xff08;GenAI&#xff09;正在迅速改变公共部门&#xff0c;从理论探讨走向实际应用。正确的数据准备、管理和治理将在 GenAI 的成功实施中发挥关键作用。 我们最近举办了…

AT24Cxx移植第三方库到裸机中使用

简介 MCU : STM32F103C8T6 库: HAL库裸机开发 EEPROM : AT24C02, 256Byte容量&#xff0c;I2C接口 电路图 AT24C02 电路图 电路图引用 裸机直接读写 // 写入数据到 EEPROM HAL_StatusTypeDef EEPROM_WriteByte(uint16_t MemAddress, uint8_t Data) {// 发送数据uint8_t …

算法刷题记录——LeetCode篇(1.3) [第21~30题](持续更新)

更新时间&#xff1a;2025-03-29 LeetCode题解专栏&#xff1a;实战算法解题 (专栏)技术博客总目录&#xff1a;计算机技术系列目录页 优先整理热门100及面试150&#xff0c;不定期持续更新&#xff0c;欢迎关注&#xff01; 21. 合并两个有序链表 将两个升序链表合并为一个…

常用数据库

模式的定义于删除 1.定义模式 CREATE SCHEMA [ <模式名> ] AUTHORIZATION < 用户名 >;要创建模式&#xff0c;调用该命令的用户必须拥有数据库管理员权限&#xff0c;或者获得了DBA授权 eg:为用户WANG定义一个模式S-C-SC CREATE SCHEMA "S-C-SC" AUT…

Processor System Reset IP 核 v5.0(vivado)

这个IP的作用&#xff0c;我的理解是&#xff0c;比普通按键复位更加高效灵活&#xff0c;可以配置多个复位输出&#xff0c;可以配置复位周期。 1、输入信号&#xff1a; 重要的信号有时钟clk信号&#xff0c;一般连接到系统时钟&#xff1b;输入复位信号&#xff0c;一般是外…