按营销型网站要求重做网站 费用,网站合同需要注意什么呢,天津建设网首页,烟台网站建设公司报价Solidity 开发环境
Solidity编辑器#xff1a;Solidity编辑器是⼀种专⻔⽤于编写和编辑Solidity代码的编辑器。常⽤的Solidity编辑器包括 Visual Studio Code、Atom和Sublime Text。以太坊开发环境#xff1a;以太坊开发环境#xff08;Ethereum Development EnvironmentSolidity编辑器是⼀种专⻔⽤于编写和编辑Solidity代码的编辑器。常⽤的Solidity编辑器包括 Visual Studio Code、Atom和Sublime Text。以太坊开发环境以太坊开发环境Ethereum Development Environment是⼀种专⻔⽤于以太坊开发的⼯ 具可以⽤于部署、测试和调试智能合约。常⽤的以太坊开发环境包括Truffle、Embark和Buidler Hardhat。以太坊虚拟机以太坊虚拟机Ethereum Virtual MachineEVM是以太坊区块链的运⾏环境⽤于在以太 坊区块链上运⾏智能合约。常⽤的以太坊虚拟机包括ganache-cli和geth。以太坊浏览器以太坊浏览器是⼀种⽤于浏览、搜索和交互以太坊区块链数据的⼯具。常⽤的以太坊浏览器 包括Etherscan、Etherchain和Blockchain Explorer。 Hardhat https://hardhat.org/ Hardhat是⽬前最好的框架之⼀⽀持快速测试同时提供了最好的教程和最简单的集成。 ⽼实说每个喜欢JS框 架的⼈都应该在某个时候试⽤Hardhat。它真的很容易上⼿具有快速的测试 ⽽且⼊⻔⾮常简单。Hardhat的 Discord也总是⾮常迅速地回答问题因此如果遇到问题你 总是可以寻求帮助。Hathat使⽤Waffle和Ethers.js进 ⾏测试 —— 可以说是更好的JavaScript 智能合约框架 —— 开发⼈员的⽣活质量确实能得到⼀些改善。 Hardhat还可以与OpenZeppelin的可升级智能合约插件直接集成这是⼀个巨⼤的胜利。 这个项⽬给⼈⼀种很棒的 感觉很⼲净。它会执⾏你想要的操作。真的很快。该项⽬正在不断改进 Hardhat显然致⼒于使智能合约开发⼈ 员的⽣活更轻松。 Hardhat概要 ETH基⾦会资助的项⽬以前的名字是Builder 技术JavascriptWeb3.js和Ethers.js插件OpenZeppelin可升级合同插件 Etherscan插件区块链分叉 区块链Hardhat运⾏时环境/本地、测试⽹、主⽹ 测试⽀持Waffle 维护⾮常活跃 ⽀持活跃 开源 Truffle https://trufflesuite.com/ ⼏年来Truffle⼀直是以太坊智能合约的默认开发框架这是有充分理由的。 Truffle是⼀个强⼤的框架为其他许多 ⼈树⽴了标准。你很容易找到使⽤此平台的项⽬ 因此查找示例很容易。Truffle也可以很容易地与它的姊妹⼯具 Drizzle和Ganache集成在⼀起。 特别是Ganache它是⼯程师运⾏本地区块链的最流⾏⽅法之⼀。对于那些正在寻 找更多⼯具的⼈ 你可以为升级的Truffle团队帐户付费并可以访问智能合约的持续集成可视化部署和监视。 Truffle还可以与OpenZeppelin的可升级智能合约插件直接集成这是⼀个巨⼤的胜利。 Truffle的开发团队显然是⼀ 群有才华的⼯程师他们想要使世界成为⼀个更好的智能合约场所。 Truffle测试的运⾏速度不如hardhat那样快并且由于⽤户数量众多获得⽀持可能很困难。 我很期待看到被 ConsenSys收购后他们将如何改善这个项⽬。Truffle的⽂档质量似乎开始下降 并且很难遵循但是如果你⽤Google 搜索遇到的错误则很可能会遇到遇到该错误并已解决的⼈。 我发现改善项⽬的⼀些最佳⽅法是在GitHub上发布 问题。⽆论如何保持⽣态系统不断壮⼤是我们 的开源职责 由于⼏乎每个⼈都熟悉它因此获得同⾏的⽀持通常很容易。我真的希望看到团队在这个项⽬ 上获得更多⽀持 因为他们有这么多⽤户。我希望他们能看到本⽂并致⼒于改善其⽂档以使 其能够继续作为测试和部署智能合约 的⾸选平台之⼀。 Truffle概要 使⽤最⼴泛的平台最近被ConsenSys收购2020年11⽉ 技术JavascriptWeb3.jsOpenZeppelin可升级合同插件Etherscan插件区块链分叉 区块链Ganache /本地测试⽹主⽹ 有测试 维护⾮常活跃 ⽀持活跃 开源可以付费升级 Embark Embark是整个DAPP框架。这是⼀个全栈的区块链平台。在Gitter的 ⼀些帮助下我能够将Chainlink合约部署到 Kovan⽹络。它带有⼀个UI允许你在GUI中与区块链 和合约进⾏交互。Embark有⼀段学习曲线我没有花⾜够的 时间来克服但它展示了其潜⼒。这就是 为什么我想将其包括在这⾥的原因因为我觉得我没有完全消化很多东 ⻄。 我希望看到⼈们更多地尝试该框架并看到其功能。由于在项⽬中花费的时间有限我觉得我可能 ⽆法在这⾥做到 公正。我确实认为将前端与后端解耦仍然是最佳做法但是如果你需要启动⼀个 具有良好前端的项⽬并且不关⼼ 解耦那么您应该100尝试这个项⽬。 这是⼀个很酷的项⽬如果有⼈喜欢Hardhat和Truffle并且⼜想与全栈解决⽅案集成那么我愿意 推荐Embark给 他。 Embark概要 具有⼤量功能的JavaScript框架⽤于前端开发 技术JavaScriptWeb3.js代理合约⽀持 区块链Ganache /本地测试⽹主⽹ ⽀持测试 维护轻度活跃 ⽀持活跃 开源 Remix基本使⽤ Remix 是以太坊智能合约编程语⾔Solidity IDE其实基于浏览器的IDE有⼀个很⼤的好处就是不⽤安装打开即 ⽤。 官⽹ https://remix.ethereum.org/。 Remix基本功能 合约创建如上图创建⼀个空的⼯作空间在⼯作空间下创建⼀个智能合约⽂件,ex: HelloWord.sol 智能合约⽂件以 .sol 结尾 ⽂件名采⽤⼤驼峰命名法 ⽂件名和合约名保持⼀致编写合约代码 合约编译 / SPDX-License-Identifier: MIT; / 智能合约的许可协议 pragma solidity ^0.8.7; / 智能合约的适⽤版本 contract HelloWord { string name; function get() public view returns (string memory){ return name; } function set (string memory _name) public { name _name; } }
编译结果 ⽬录产⽣⼀个 artifacts ⽂件夹 合约部署
通过第四个菜单进⼊部署界⾯选择部署环境选择部署合约的账户地址设置gas限制选择要部署的合约deploy 按钮进⾏部署 部署成功效果 合约调试通过函数的返回值查看变量event Log solidity默认没有consol.log 或者 print 类似的事件系统 但是我们可以通过注册事件查看对应的log⽇志 / SPDX-License-Identifier: MIT pragma solidity ^0.8.13; contract Variables { event Log(address); event Log(uint); function doSomething() public { uint timestamp block.timestamp; / Current block timestamp address sender msg.sender; / address of the caller emit Log(timestamp); emit Log(sender); } }
本地部署ReMix IDE 在线Remix访问缓慢如果你有很好的⽹络环境也可以本地部署⼀套。 在本地部署Remix需要准备两个东⻄⼀个是Remix-project可以理解为Remix的前端另⼀个是Remixd可以理 解为Remix的后端它们的Github仓库地址分别是 https://github.com/ethereum/remix-project https://github.com/ethereum/remix-project/tree/master/libs/remixd Docker⽅式安装 如果要运⾏合并到主分⽀中的最新更改请运⾏ 如果你想运⾏最新的 remix-live 版本运⾏。 docker-compose⽅式安装 docker pull remixproject/remix-ide:latest docker run -p 8080:80 remixproject/remix-ide:latest
docker pull remixproject/remix-ide:remix_live docker run -p 8080:80 remixproject/remix-ide:remix_live
部署 Remixd Remixd 的安装使⽤步骤如下 1.通过 npm 或者 yarn 安装 Remixd建议⽤yarn npm 命令 2.启动 Remix-IDE 3.在 Remix-IDE 上点两下 ⼀个点 Solidity选择相应环境另⼀个点 Connect to Localhost连接本地环境 docker-compose pull docker-compose up -d
npm install -g remix-project/remixd yarn add global remix-project/remixd
remixd -s ./shared_project -u http:/“localhost:8080 1 Solidity基础介绍 认识⼀个最简单的存储合约 /” SPDX-License-Identifier: MIT; / 智能合约的许可协议 pragma solidity ^0.8.7; / 智能合约的适⽤版本 import “”; / 导⼊ contract HelloWord { string name; / 状态变量 / 函数 function get() public view returns (string memory){ return name; } function set (string memory _name) public { name _name;
授权协议 默认情况下在发布源代码时加⼊机器可读许可证说明是很重要的。由于提供源代码总是涉及版权⽅⾯的法 律问题Solidity 编译器⿎励使⽤机器可读的 SPDX 许可证标识符,⽐如 / SPDX-License-Identifier: MIT ⾄于什么是SPDX ⼤家可以参考如下资料 ⾸先是SPDX这是个组织名其⽹站为 SPDX 许可证列表 |软件包数据交换 SPDX SPDX-License-Identifier 组合起来就是在指SPDX的许可证列表 后⾯的格式为 SPDX的许可证列表中的某个许可证 ⽐如上⾯例⼦中的MIT 许可 MIT 基本信息 全名MIT License 麻省理⼯学院许可证 标志符 MIT MIT的具体内容 特此免费授予获得“软件”副本的任何⼈不受限制地处理本软件的许可包括但不限于使⽤、复制、修改、合 并、发布、分发、再许可和/或出售本软件副本的权利 Solidity版本限制 第⼆⾏是告诉编译器源代码所适⽤的Solidity版本为0.7.0 及 0.9.0 。这样的说明是为了确保合约不会在新的 编译器版本中发⽣异常的⾏为。关键字 pragma 是告知编译器如何处理源代码的通⽤指令 执⾏原理 在⼀个去中⼼化的世界我们的程序并不紧紧活⼀台机器的CPU 上, 在⼀个去中⼼化的世界是由很多节点组成的 P2P ⽹络。合约代码会在各节点上[ Full Node ]单独运⾏⽽事实上P2P的各节点相互之间都不信任的所以每个节 点都会存⼀份⾃⼰的状态Distributed Ledger,分布式账本在该示例就是name当调⽤set()的时候⼤家都改 变了name此时需要⼀种共识机制PoS如果PoS认为name合法此次调⽤完成。 否则回滚上⼀个name的 值因此每⼀次改变状态变量的调⽤都是以⼀个事务Transcation来执⾏。 Api⽂档 https://solidity-by-example.org/ 变量 } }
名称 返回 blockhash(uint blockNumber) returns (bytes32) 给定区块的哈希值 – 只适⽤于256最近区块, 不包含当前区 块。 block.coinbase (address payable) 当前区块矿⼯的地址 block.difficulty (uint) 当前区块的难度 block.gaslimit (uint) 当前区块的gaslimit block.number (uint) 当前区块的number block.timestamp (uint) 当前区块的时间戳为unix纪元以来的秒 gasleft() returns (uint256) 剩余 gas msg.data (bytes calldata) 完成 calldata msg.sender (address payable) 消息发送者 (当前 caller) msg.sig (bytes4) calldata的前四个字节 (function identifier) msg.value (uint) 当前消息的wei值 now (uint) 当前块的时间戳 tx.gasprice (uint) 交易的gas价格 tx.origin (address payable) 交易的发送⽅ 局部变量 在函数内部声明 不存储到链上 状态变量 在函数外部声明 状态变量是永久地存储在链上的值。 全局变量 内置提供有关区块链的信息⽐如 block 、 msg 等 全局变量 这些是全局⼯作区中存在的特殊变量提供有关区块链和交易属性的信息。 / SPDX-License-Identifier: MIT pragma solidity ^0.8.13; contract Variables {
状态变量 function doSomething() public view returns(uint, address) { / 内置全局变量 uint timestamp block.timestamp; / 获取区块时间戳 address sender msg.sender; / 获取区块地址 return (timestamp,sender); } }
局部变量 在为变量命名时请记住以下规则。 / SPDX-License-Identifier: MIT pragma solidity ^0.8.13; contract Variables { uint public nun 123; function doSomething() public{ nun ; } }
/ SPDX-License-Identifier: MIT pragma solidity ^0.8.13; contract Variables { function doSomething() public pure returns(uint){ uint num 123; num ; / 局部变量不保存每次使⽤都重置 return num; } }
不应使⽤ Solidity 保留关键字作为变量名。例如 break 或 boolean 变量名⽆效。 不应以数字(0-9)开头必须以字⺟或下划线开头。例如 123test 是⼀个⽆效的变量名但是 _123test 是 ⼀个有效的变量名。 变量名区分⼤⼩写。例如 Name 和 name 是两个不同的变量。 Solidity 可⻅性修饰符 1.public – 所有合约与账号都可以调⽤ 2.private -只有在定义该函数的合约可以调⽤ 3.internal- 当前合约或者继承该合约的类似java ⾥⾯的protected 关键字。 4.external – 只有其他合约或者账号可以调⽤,定义该函数的合约不能调⽤,除⾮使⽤ this 关键字 函数
函数是代码的可执⾏单元。函数通常在合约内部定义但也可以在合约外定义。Solidity有两个关键字与函数输出相关return和returns他们的区别在于 returns加在函数名后⾯⽤于声明返回的变量类型及变量名。 return⽤于函数主体中返回指定的变量。view和pure的⽤法 getter 类型的函数可以被view 或者 pure 修饰。 view 修饰的函数不能改变状态变量。pure 则既不能改 变状态变量也不取读取状态变量。 在我们⽆法确定该⽤view还是pure时remix会给我们完善的提示信息 错误Errors / SPDX-License-Identifier: GPL-3.0 pragma solidity (0.8.0 0.9.0; contract Storage { function set() public { / 定义函数 / …* } } function OutsideFunc(uint x) pure returns (uint) { return x * 2; }
/ SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract ViewPureTest { uint public x 1; / 不能改变状态变量. function addToX(uint y) public view returns (uint) { return x y; } /函数中没有任何状态变量出现。 function add(uint i, uint j) public pure returns (uint) { return i j; } }
assert(bool condition) 1 − 如果不满⾜条件此⽅法调⽤将导致⼀个⽆效的操作码对状态所做的任何更改将被还原。这个⽅法是⽤ 来处理内部错误的。 require(bool condition) − 如果不满⾜条件此⽅法调⽤将恢复到原始状态。此⽅法⽤于检查输 ⼊或外部组件的错误。 require(bool condition, string memory message) − 如果不满⾜条件此⽅法调⽤将恢复到原 始状态。此⽅法⽤于检查输⼊或外部组件的错误。它提供了⼀个提供⾃定义消息的选项。 revert() − 此⽅法将中⽌执⾏并将所做的更改还原为执⾏前状态。 revert(string memory reason) − 此⽅法将中⽌执⾏并将所做的更改还原为执⾏前状态。它提供了 ⼀个提供⾃定义消息的选项。 回退状态但是gas费⽤是需要消耗 assert 合约内部错误 require 外部参数错误 assert 内部错误 Solidity 为应对失败允许⽤户定义 error 来描述错误的名称和数据。 跟⽤错误字符串相⽐ error 更便宜并 且允许你编码额外的数据还可以⽤ NatSpec 为⽤户去描述错误。 事件Event 事件是能⽅便地调⽤以太坊虚拟机⽇志功能的接⼝。 参数被2整除 1 / SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.4; error NotFoundUser(address account, uint256 uid); contract Token { mapping(address , uint256) users; function getUser(address _account, uint256 _uid) public view { uint256 uid users[msg.sender]; if (uid _uid) { revert NotFoundUser(_account, _uid); } / …*
solidity默认没有consol.log 或者 print 类似的事件系统 但是我们可以通过注册事件查看对应的log⽇志 变量的数据位置 数据位置 在合约中声明和使⽤的变量都有⼀个数据位置指明变量值应该存储在哪⾥。合约变量的数据位置将会影响Gas消 耗量。 / SPDX-License-Identifier: MIT pragma solidity ^0.8.13; contract Variables { event Log(address); event Log(uint); function doSomething() public { uint timestamp block.timestamp; / Current block timestamp address sender msg.sender; / address of the caller emit Log(timestamp); emit Log(sender); } }
Solidity 提供4种类型的数据位置。 Storage Memory Calldata Stack Storage 该存储位置存储永久数据这意味着该数据可以被合约中的所有函数访问。可以把它视为计算机的硬盘数据所有 数据都永久存储。 保存在存储区(Storage)中的变量以智能合约的状态存储并且在函数调⽤之间保持持久性。与其他数据位置相 ⽐存储区数据位置的成本较⾼。 存储中的数据是永久存在的。存储是⼀个key/value库 存储中的数据写⼊区块链因此会修改状态这也是存储使⽤成本⾼的原因。 占⽤⼀个256位的槽需要消耗20000 gas 修改⼀个已经使⽤的存储槽的值需要消耗5000 gas 当清零⼀个存储槽时会返还⼀定数量的gas 存储按256位的槽位分配即使没有完全使⽤⼀个槽位也需要⽀付其开销 Memory 内存位置是临时数据⽐存储位置便宜。它只能在函数中访问。 通常内存数据⽤于保存临时变量以便在函数执⾏期间进⾏计算。⼀旦函数执⾏完毕它的内容就会被丢弃。你 可以把它想象成每个单独函数的内存(RAM)。 内存是⼀个字节数组槽⼤⼩位256位32字节 数据仅在函数执⾏期间存在执⾏完毕后就被销毁 读或写⼀个内存槽都会消耗3gas 为了避免矿⼯的⼯作量过⼤22个操作之后的单操作成本会上涨 / SPDX-License-Identifier: MIT pragma solidity ^0.8.13; contract DataLocations { struct MyStruct { uint256 foo; } mapping(uint256 , MyStruct) public myStructs; function funcStorage(uint8 _idx, uint _val) public { / 从映射中获取结构体,storage 变量改变会影响状态变量的值 MyStruct storage myStruct myStructs[_idx]; myStruct.foo _val; } }
Calldata Calldata是不可修改的⾮持久性数据位置所有传递给函数的值都存储在这⾥。此外Calldata是外部函数的参数 (⽽不是返回参数)的默认位置。 Stack 堆栈是由EVM (Ethereum虚拟机)维护的⾮持久性数据。EVM使⽤堆栈数据位置在执⾏期间加载变量。堆栈位置最多 有1024个级别的限制。 可以看到要永久性存储可以保存在存储区(Storage)。 数据位置规则 规则1 – 状态变量 状态变量总是存储在存储区中。 此外不能显式地标记状态变量的位置。 / SPDX-License-Identifier: MIT pragma solidity ^0.8.13; contract DataLocations { struct MyStruct { uint256 foo; } mapping(uint256 , MyStruct) public myStructs; function funcStorage(uint8 _idx, uint _val) public view returns (MyStruct memory) { / 从映射中获取结构体,storage 变量改变会影响状态变量的值 MyStruct memory myStruct myStructs[_idx]; myStruct.foo _val; return myStruct; } }
pragma solidity ^0.5.0; contract DataLocation { / storage uint stateVariable; uint[] stateArray; }
规则2 – 函数参数与返回值 函数参数包括返回参数都存储在内存中。 此处函数参数 uint num1 与 uint num2 返回值 uint result 都存储在内存中。 规则3 – 局部变量 值类型的局部变量存储在内存中。但是对于引⽤类型需要显式地指定数据位置。 pragma solidity ^0.5.0; contract DataLocation { uint storage stateVariable; / 错误 uint[] memory stateArray; / 错误 }
pragma solidity ^0.5.0; contract DataLocation { / storage uint stateVariable; uint[] stateArray; function calculate(uint num1, uint num2) public pure returns (uint result) { return num1 num2 } }
pragma solidity ^0.5.0; contract Locations { /- 此处都是状态变量 */ / 存储在storage中 bool flag; uint number; address account; function doSomething() public { /- 此处都是局部变量 */ / 值类型 / 所以它们被存储在内存中 bool flag2; uint number2; address account2;
不能显式覆盖具有值类型的局部变量。 规则4 – 外部函数的参数 外部函数的参数(不包括返回参数)存储在Calldata中。 赋值的数据位置规则 Solidity数据类型 Solidity 是⼀种静态类型语⾔这意味着每个变量状态变量和局部变量都需要在编译时指定变量的类型。 Solidity中变量类型有以下⼏⼤类 值类型 地址类型 引⽤类型 / 引⽤类型需要显示指定数据位置此处指定为内存 uint[] memory localArray; } }
function doSomething() public { /- 此处都是局部变量 */ / 值类型 bool memory flag2; / 错误 uint Storage number2; / 错误 address account2; }
值类型 类型 保留字 取值 布尔 型 bool true/false 整型 int/uint 有符号整数/⽆符号整数。 整型 int8 to int256 8位到256位的带符号整型数。int256与int相同。 整型 uint8 to uint256 8位到256位的⽆符号整型。uint256和uint是⼀样的。 定⻓ 浮点 型 fixed/unfixed 有符号和⽆符号的定⻓浮点型 定⻓ 浮点 型 fixedMxN 带符号的定⻓浮点型其中M表示按类型取的位数N表示⼩数点。M应该能被8整 除从8到256。N可以是0到80。fixed与fixed128x18相同。 定⻓ 浮点 型 ufixedMxN ⽆符号的定⻓浮点型其中M表示按类型取的位数N表示⼩数点。M应该能被8整 除从8到256。N可以是0到80。fixed与fixed128x18相同。 地址类型 地址类型表示以太坊地址⻓度为20字节。地址可以使⽤ .balance ⽅法获得余额也可以使⽤ .transfer ⽅法 将余额转到另⼀个地址。 复制 引⽤类型/复合数据类型 Solidity中有⼀些数据类型由值类型组合⽽成相⽐于简单的值类型这些类型通常通过名称引⽤被称为引⽤ 类型。 引⽤类型包括 数组 (字符串与bytes是特殊的数组所以也是引⽤类型) struct (结构体) address x 0x212; address myAddress this; if (x.balance 10 2 myAddress.balance ( 10) x.transfer(10);
map (映射) “ undefined ”或“ null ”值的概念在Solidity中不存在但是新声明的变量总是有⼀个 默认值 具体的默认值跟类 型相关。 要处理任何意外的值应该使⽤错误处理来恢复整个交易或者返回⼀个带有第⼆个 bool 值的元组表 示成功。 bool/布尔类型 布尔值的取值范围为 true 和 false 。 默认值 false 运算符 ●!逻辑⾮ ● 逻辑与 “and” ●|| 逻辑或 “or” ● 等于 ●! 不等于 int、uint/整数类型 int/uint 变⻓的有符号或⽆符号整型。变量⽀持的步⻓以 8 递增⽀持从 uint8 到 uint256 以及 int8 到 int256 。需要注意的是 uint 和 int 默认代表的是 uint256 和 int256 。 int 有符号整型(包含负数) 默认为 int256 不同位⻓的整形范围如下 int8 取值范围-(2 ** 7)到 2 ** 7 -1 int16 取值范围-(2 ** 15)到 2 ** 15 -1 … intX 取值范围-(2 X -1)到 2( X -1) -1 pragma solidity ^0.8.0; contract TestBool { error NotEqual(bool A,bool B); bool public A; / false bool public B true; /“true /” require(A4B,“A not equal B”); if (A !6 B) { error NotEqual(A,B); } }
int256 取值范围-(2 ** 255)到 2 ** 255 -1 uint ⽆符号整型 默认为 uint256 不同位⻓的整形范围如下 uint8 取值范围0 到 2 ** 8 - 1 uint16 取值范围0 到 2 ** 16 - 1 … uintX 取值范围0 到 2 ** X - 1 uint256 取值范围0 到 2 ** 256 - 1 对于整形 X可以使⽤ type(X).min 和 type(X).max 去获取这个类型的最⼩值与最⼤值。 address/地址 默认值: 0x0000000000000000000000000000000000000000 20字节的16进制地址⽤来表示⼀个账户 或者合约地址 / SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract TestIntval { int8 public i8 -1; int public i256 456; int public i -123; / int 等同于 int256 / int 的最⼤最⼩值 int public minInt type(int).min; int public maxInt type(int).max; uint8 public u8 1; uint256 public u256 456; uint public u 123; / uint 等同于 uint256 / uint 的最⼤最⼩值 uint public minUInt type(uint).min; uint public maxUInt type(uint).max;
function mini() public pure returns(uint8){ return type(uint8).max; } }
/ SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract TestAddress { /与其他机器语⾔相区别的类型就是这个address 类型160-bit/20byte
bytes/字节数组 在计算机中的最⼩存储单位是 bit(位) 1byte等于8位 Solidity中byte可以赋值为 16进制数字 单引号的单个或多个字符 定⻓字节数组 bytes1 后⾯数字1是表示1字节 bytes默认等于bytes1 Bytes2 后⾯数字2是表示2字节 Bytes3 后⾯数字3是表示3字节 bytes4 后⾯数字4是表示4字节 … bytes32 后⾯数字32是表示32字节 bytes32 等价于 int256或uint256 的位数 成员变量 .length 表示这个字节数组的⻓度只读 string/字符串 中⽂特殊字符需要⽤ unicode 编码 通过concat ⽅法进⾏拼接 bytes 和 string之间转化 address public myAddr 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4; /合约⾃⼰的地址 address contractAddress address(this); /“跟普通的地址类型⼀样但多了两个⽅法 transfer/send 这两个⽅法后⾯章节会讲到 /” address sender payable(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4); /可以使⽤ balance 属性来查询⼀个地址的余额 function getBalance() public view returns (uint256, uint256) { require(myAddr.balance contractAddress.balance, “1 must lg 2”); return (myAddr.balance, contractAddress.balance); } } string 字符串不能通过 length ⽅法获取其⻓度。 keccak256(abi.encodePacked(s1)) 4 keccak256(abi.encodePacked(s2)) 可以通过这个⽅法⽐较 两个字符串是否相等。 abi.encodePacked(s1, s2) :通过这个⽅法进⾏字符串合并拼接。 / SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract TestAddress { string public str1 “123”; / 中⽂不适⽤unicode编码报错 / string public str2 “你好”; string public str2 unicodeabc; function concat() public view returns(string memory) { string memory result string.concat(str1,str2); return result; } function caoncat2(string memory _a, string memory _b) public pure returns(string memory) { return string.concat(_a,_b); } function caoncat3(string memory _a, string memory _b) public pure returns(bytes memory) { bytes memory _ba bytes(_a); bytes memory _bb bytes(_b); return bytes.concat(_ba,_bb); } function caoncat4(string memory _a, string memory _b) public pure returns(string memory) { bytes memory _ba bytes(_a); bytes memory _bb bytes(_b); return string(bytes.concat(_ba,_bb)); } / ⽐较s1和s2是否相等相等返回true不相等返回false function compareEqual(string memory s1, string memory s2) public pure returns (bool) { / 不⽀持字符直接⽐较 return s1 4 s2; / return keccak256(abi.encodePacked(s1)) 4 keccak256(abi.encodePacked(s2)); } / 将s1和s2合并为⼀个字节数组 function mergeS1AndS2ReturnBytes(string memory s1, string memory s2) public pure returns (bytes memory) {
Enum(枚举) 枚举将⼀个变量的取值限制为⼏个预定义值中的⼀个。精确使⽤枚举类型有助于减少代码中的bug。 array/数组 T[k]: 元素类型为T固定⻓度为K的数组 uint[5] return abi.encodePacked(s1, s2); } / 将s1和s2合并为⼀个字节数组转换为string function mergeS1AndS2ReturnString(string memory s1, string memory s2) public pure returns (string memory) { return string(abi.encodePacked(s1, s2)); } }
ontract UserState { / 枚举 /“默认值是列表中的第⼀个元素 enum State { Online, /” 0 Offline, / 1 Unknown / 2 } State public status; function get() public view returns (State) { return status; } / 通过将uint传递到输⼊来更新状态 function set(State _status) public { status _status; } / 也可以是这样确定属性的更新 function off() public { status State.Offline; } / delete 将枚举重置为其第⼀个值 0 function reset() public { delete status; } }
T[]: 元素类型为T, ⻓度可以动态调整 ⼀、固定⻓度的数组Arrays / SPDX-License-Identifier: GPL-3.0 pragma solidity (0.7.0 0.9.0; contract MappingTest { / 创建定⻓数组 uint256[5] public arr [1, 2, 3, 4, 5]; / 定⻓数组求和 function getAll() public view returns (uint256) { uint256 num 0; for (uint256 i 0; i arr.length; i) { num arr[i]; } return num; } function get() public view returns (uint256[5] memory) { return arr; } / 获取定⻓数组⻓度 function getLenth() public view returns (uint256) { return arr.length; } / 修改⻓度失败 function changeLenth() public { / arr.length 7; } / 修改内部数据 function change(uint256 _idx, uint256 _val) public { arr[_idx] _val; } / push 修改 / function change(uint256 _val) public { / arr.push(_val); / } }
⼆、可变⻓度的Arrays uint [] T [1,2,3,4,5] 这句代码表示声明了⼀个可变⻓度的 T 数组因为我们给它初始化了 5 个⽆符号 整数所以它的⻓度默认为 5 。 (0.5.0 的版本中 length ⽅法只读不可修改。 5版本之前⽀持length属性修改缺失的以0补位 三、⼆维数组 - 数组⾥⾯放数组 uint [] T [1,2,3,4,5]; 1 / SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract C { uint256[2][3] T [[1, 2], [3, 4], [5, 6]]; function T_len() public view returns (uint256) { return T.length; / uint256: 3 } function getT() public view returns (uint256[2][3] memory) { return T; } function change() public { T[1][0] 55; } } uint [2][3] T [[1,2],[3,4],[5,6]] 这是⼀个三⾏两列的数组你会发现和Java、C语⾔等的其它语⾔中 ⼆位数组⾥⾯的列和⾏之间的顺序刚好相反。在其它语⾔中上⾯的内容应该是这么存储 uint [2][3] T [[1,2,3],[4,5,6]] 。 上⾯的 数组T 是 storage 类型的数组对于 storage 类型的数组数组⾥⾯可以存放任意类型的值⽐如其它 数组结构体字典映射等等。对于 memory 类型的数组如果它是⼀个 public 类型的函数的参数那么它 ⾥⾯的内容不能是⼀个 mapping(映射字典) 并且它必须是⼀个 ABI 类型。 四、数组字⾯量 Array Literals / 内联数组 Inline Arrays 结构体 struct 通过基本数据类型来组合成⾃定义复杂的数据类型 语法结构: 声明结构体 创建结构变量 / SPDX-License-Identifier: GPL-3.0 pragma solidity (0.7.0 0.9.0; contract C {
function f() pure public { g([1, 2, 3]); g([uint(1),2,3]) }
function g(uint[3] memory _data) pure public { / …* } }
struct 关键字 结构体名称 { 类型1 属性名1; 类型2 属性名2 … }
struct Person { uint8 age; string id; string name; }
修改结构体变量 函数中返回结构体 字典映射Mappings) 结构体 变量名 结构体(属性1属性2…*) 结构体 变量名 结构体({属性1value1属性2value2Ï}) Person student1 Person(18,1,“柯南”) Person student2 Person({age:17, id: 2, name: “迪迦Ï”})
function setStudent(uint _age, string _name) public { student1.age _age student1.name _name }
function getStudent() public view returns(Person) { return student1 }
contract Structs { struct Todo { string text; bool completed; } / 结构体数组
Todo[] public todos; / 初始化结构的3种⽅法 function create(string calldata _text) public { / 1.像函数⼀样调⽤它 todos.push(Todo(_text, false)); / 2. 键值对 todos.push(Todo({text: _text, completed: false})); / 3.初始化⼀个空结构然后更新它 Todo memory todo; todo.text _text; todos.push(todo);/ completed 没有定义,默认为 false } /通过索引获取结构体数组中⼀个元素,并更新内部的属性 function update(uint _index) public { Todo storage todo todos[_index]; todo.completed !todo.completed; } }
与数组和结构体⼀样映射也是引⽤类型。 是⼀个⼀对⼀键值存储关系。 可以理解成js中的对象 _KeyType – 可以是任何内置类型或者bytes和字符串。不允许使⽤引⽤类型或复杂对象。 _ValueType – 可以是任何类型。 注意 映射的数据位置(data location)只能是storage通常⽤于状态变量。 映射可以标记为publicSolidity ⾃动为它创建getter。 mapping 不能直接在函数返回 创建mapping mapping(_KeyType , _ValueType) 1 contract MappingTest { mapping(address , uint256) public balances; function update(uint256 _amount) public { balances[msg.sender] _amount; } function getAmount() public view returns(uint){ return balances[msg.sender]; } }
contract LedgerBalance { mapping(address , uint) public balances; function updateBalance(uint newBalance) public { balances[msg.sender] newBalance; } } contract Updater { function updateBalance() public returns (uint) { LedgerBalance ledgerBalance new LedgerBalance(); ledgerBalance.updateBalance(10); return ledgerBalance.balances(address(this)); } }
contract Mapping { Solidity运算符 /从地址到uint的映射 mapping(address , uint) public myMap; function get(address _addr) public view returns (uint) { /映射始终返回⼀个值。 /“如果从未设置该值它将返回默认值。 return myMap[_addr]; } /” 更新此地址的值 function set(address _addr, uint _i) public { myMap[_addr] _i; } function remove(address _addr) public { /将值重置为默认值 delete myMap[_addr]; } } /“嵌套 mapping contract NestedMapping { /“嵌套映射从地址映射到另⼀个映射 mapping(address , mapping(uint , bool)) public nested; function get(address _addr1, uint _i) public view returns (bool) { /” 可以从嵌套映射中获取值 return nested[_addr1][_i]; } function set( address _addr1, uint _i, bool _boo ) public { nested[_addr1][_i] _boo; } /” 删除 mapping 的⼀个元素 function remove(address _addr1, uint _i) public { delete nested[_addr1][_i]; } }
序号 运算符与描述 1 (加) 求和 例: A B 30 2 – (减) 相减 例: A – B -10 3 * (乘) 相乘 例: A * B 200 4 / (除) 相除 例: B / A 2 5 % (取模) 取模运算 例: B % A 0 6 (递增) 递增 例: A 11 7 — (递减) 递减 例: A– 9 序号 运算符与描述 1 (等于) 2 ! (不等于) 3 (⼤于) 4 (⼩于) 5 (⼤于等于) 6 (⼩于等于) 序号 运算符与描述 1 (逻辑与) 如果两个操作数都⾮零则条件为真。 例: (A B) 为真 2 || (逻辑或) 如果这两个操作数中有⼀个⾮零则条件为真。 例: (A || B) 为真 3 ! (逻辑⾮) 反转操作数的逻辑状态。如果条件为真则逻辑⾮操作将使其为假。 例: ! (A B) 为假 算术 ⽐较 逻辑 位运算符 序 号 运算符与描述 1 (位与) 对其整数参数的每个位执⾏位与操作。 例: (A B) 为 2. 2 | (位或) 对其整数参数的每个位执⾏位或操作。 例: (A | B) 为 3. 3 ^ (位异或) 对其整数参数的每个位执⾏位异或操作。 例: (A ^ B) 为 1. 4 ~ (位⾮) ⼀元操作符反转操作数中的所有位。 例: (~B) 为 -4. 5 (左移位)) 将第⼀个操作数中的所有位向左移动移动的位置数由第⼆个操作数指定新的位由0填 充。将⼀个值向左移动⼀个位置相当于乘以2移动两个位置相当于乘以4以此类推。 例: (A 1) 为 4. 6 (右移位) 左操作数的值向右移动移动位置数量由右操作数指定 例: (A 1) 为 1. 序号 运算符与描述 1 (简单赋值) 将右侧操作数的值赋给左侧操作数 例: C A B 表示 A B 赋给 C 2 (相加赋值) 将右操作数添加到左操作数并将结果赋给左操作数。 例: C A 等价于 C C A 3 − (相减赋值) 从左操作数减去右操作数并将结果赋给左操作数。 例: C - A 等价于 C C – A 4 * (相乘赋值) 将右操作数与左操作数相乘并将结果赋给左操作数。 例: C * A 等价于 C C * A 5 / (相除赋值) 将左操作数与右操作数分开并将结果分配给左操作数。 例: C / A 等价于 C C / A 6 % (取模赋值) 使⽤两个操作数取模并将结果赋给左边的操作数。 例: C % A 等价于 C C % A 序号 运算符与描述 1 ? : (条件运算符 ) 如果条件为真 ? 则取值X : 否则值Y 赋值 solidity ⽀持的赋值运算符如下表所示 注意 – 同样的逻辑也适⽤于位运算符因此它们将变成 9 、 ; 、 ; 、 、 和 ^ 。 条件运算符 Solidity循环语句 while do…while Solidity 中 do…while循环的语法如下 while (表达式) { 被执⾏语句(如果表示为真) }
/ SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.0; contract TestBool { uint256 public num 10; function test(int max) public { int256 start 1; while (start ? max) { start; num; } } }
do { 被执⾏语句(如果表示为真) } while (表达式); 注意: 不要漏掉do后⾯的分号。
/ SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.0; contract TestBool { uint public num 10; function test () public returns(uint) { int start 1; int max 10; do{ start ; num ; }while(start ?max); return num; } }
for break 与 continue Solidity条件语句 Solidity⽀持条件语句让程序可以根据条件执⾏不同的操作。条件语句包括 if for (初始化; 测试条件; 迭代语句) { 被执⾏语句(如果表示为真) }
contract TestBool { uint256 public num 10; function test(int max) public { for(int256 start 1; startmax; start ) { num ; } } }
/ SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.0; contract TestBool { uint256 public num 10; function test(int max) public { for(int256 start 1; startmax; start ) { if(start 4 1) { / break; / 跳出循环 num 10 continue; / 跳出本次循环 } num ; } } }
if…*else if…*else if Solidity中的函数 函数修饰符 函数修饰符⽤于修改函数的⾏为。例如向函数添加条件限制。 修饰符定义中出现特殊符号 _ 的地⽅⽤于插⼊函数体。如果在调⽤此函数时满⾜了修饰符的条件则执 ⾏该函数否则将抛出异常。 / SPDX-License-Identifier: GPL-3.0 pragma solidity (0.7.0 0.9.0; contract Owner { address owner; uint256 price 10; constructor() { owner msg.sender; } / 定义修饰符 onlyOwner 不带参数
视图函数view View(视图)函数 使⽤状态变量但是不修改状态 如果函数中存在以下语句则被视为修改状态编译器将抛出警告。 修改状态变量。 触发事件。 创建合约。 使⽤ selfdestruct 。 发送以太。 调⽤任何不是视图函数或纯函数的函数 使⽤底层调⽤ 使⽤包含某些操作码的内联程序集。 Getter⽅法是默认的视图函数。声明视图函数可以在函数声明⾥添加 view 关键字。 纯函数Pure modifier onlyOwner() { require(msg.sender 4 owner); _; } / 使⽤修饰符 onlyOwner 限制只有发布者才能调⽤ function changePrice(uint256 _price) public view onlyOwner returns (address, uint256) { return (owner, _price); } }
/ SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.0; contract Test { function getResult() public view returns (uint256, uint256) { uint256 a 1; / 局部变量 uint256 b 2; uint256 product a * b; uint256 sum a b; return (product, sum); } }
纯函数Pure Pure(纯)函数不读取或修改状态。 声明纯函数可以在函数声明⾥添加 pure 关键字。 如果函数中存在以下语句则被视为读取状态编译器将抛出警告 读取状态变量。 访问 address(this).balance 或
.balance 访问任何区块、交易、msg等特殊变量(msg.sig 与 msg.data 允许读取)。 调⽤任何不是纯函数的函数。 使⽤包含特定操作码的内联程序集。 如果发⽣错误纯函数可以使⽤ revert() 和 require() 函数来还原潜在的状态更改。 函数重载 同⼀个作⽤域内相同函数名可以定义多个函数。这些函数的参数(参数类型或参数数量)必须不⼀样。仅仅 是返回值不⼀样不被允许。 加密函数 Solidity 提供了常⽤的加密函数。以下是⼀些重要函数 keccak256(bytes memory) returns (bytes32) 计算输⼊的Keccak-256散列。 sha256(bytes memory) returns (bytes32) 计算输⼊的SHA-256散列。 / SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.0; contract Test { function getSum(uint256 a, uint256 b) public pure returns (uint256) { return a b; } function getSum( uint256 a, uint256 b, uint256 c ) public pure returns (uint256) { return a b c; } } ripemd160(bytes memory) returns (bytes20) 计算输⼊的RIPEMD-160散列。 ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address) 从椭圆曲 线签名中恢复与公钥相关的地址或在出错时返回零。函数参数对应于签名的ECDSA值: r – 签名的前32 字节; s: 签名的第⼆个32字节; v: 签名的最后⼀个字节。这个⽅法返回⼀个地址。 智能合约 合约继承 就像Java、C中类的继承⼀样Solidity中合约继承是扩展合约功能的⼀种⽅式。Solidity⽀持单继承和 多继承。 Solidity中合约继承的重要特点 派⽣合约可以访问⽗合约的所有⾮私有成员包括内部⽅法和状态变量。但是不允许使⽤ this 。 如果函数签名保持不变则允许函数重写。如果输出参数不同编译将失败。 可以使⽤ super 关键字或⽗合同名称调⽤⽗合同的函数。 在多重继承的情况下使⽤ super 的⽗合约函数调⽤优先选择被最多继承的合约。 contract Test { function callKeccak256() public pure returns(bytes32 result){ return keccak256(“ABC”); } }
/ SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.0; contract Base { uint256 private data; uint256 public info; constructor() { info 10; } function increment(uint256 a) private pure returns (uint256) { return a 1; } function updateData(uint256 a) public { data a; } function getData() public view returns (uint256) { return data; }
构造函数 构造函数是使⽤ construct 关键字声明的特殊函数⽤于初始化合约的状态变量。合约中构造函数是可选 的可以省略。 构造函数有以下重要特性 ⼀个合约只能有⼀个构造函数。 构造函数在创建合约时执⾏⼀次⽤于初始化合约状态。 在执⾏构造函数之后合约最终代码被部署到区块链。合约最终代码包括公共函数和可通过公共函数访问的 代码。构造函数代码或仅由构造函数使⽤的任何内部⽅法不包括在最终代码中。 构造函数可以是公共的也可以是内部的。 内部构造函数将合约标记为抽象合约。 如果没有定义构造函数则使⽤默认构造函数。 function compute(uint256 a, uint256 b) internal pure returns (uint256) { return a b; } } contract Test is Base { uint256 private result; Base private base; constructor() { base new Base(); } function getComputedResult() public { result compute(3, 5); } function getResult() public view returns (uint256) { return result; } }