> For the complete documentation index, see [llms.txt](https://docs.hg.network/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.hg.network/dapp-kai-fa-jiao-cheng/web3-yu-solidity-ji-chu.md).

# Web3与Solidity基础

***本教程是技术大牛 luofei614 的DApp开发学习笔记，编成教程分享给开发者。***

### 1、web3的基础使用

web3.js 是以太坊官方发布的与以太坊交互的js库。

上面示例中已经有初步接触， 我们可以在VUE中使用web3 也可以在truffle控制台使用web3进行代码调试。

下面说下web3的常用方法：

* 获得账户

&#x20;web3.eth.getAccounts()

* 查看余额

web3.eth.getBalance("accountAddress")

* 账户转账

web3.eth.sendTransaction({from:"fromaddress",to:"toaddress",value:1000})

还有跟交易相关的gas , nonce等参数， 都会自动生成或者也可以指定覆盖。

* 单位转换

web3.utils.toWei("1","ether"); //将1ETH转换为wei单位

web3.utils.fromWei("20000000000","ether");//将wei单位转换为ETH

web3.utils.toBN("123");

* 获得协议版本

web3.eth.getProtocolVersion()

* 获得当前gas价格

web3.eth.getGasPrice()

* 获得交易数量

web3.eth.getTransactionCount("address")

* 预估执行合约要的gas数量

instance.functionName.estimateGas(args)

这里instance是truffle的合约对象。传参args 是functionName的传参。

### 2、solidity的基础使用

solidity是编写智能合约的编程语言，solidity代码通过编译器编译为EVM字节码， 以太坊区块上有个EVM虚拟机机制可以执行EVM的字节码。

上面在部署水龙头合约时已经初步接触了solidity，这一小节详细讲解一下solidity。

#### 2.1合约结构

定义一个简单的合约

// SPDX-License-Identifier: GPL-3.0

pragma solidity >0.7.0 <0.8.0;

contract Mytest {

&#x20;   uint storednumber=0;&#x20;

&#x20;   address payable owner;

&#x20;   event LogNumber(uint storednumber);

&#x20;   constructor(){

&#x20;       owner=msg.sender;

&#x20;   }

&#x20;   function test(uint number) public payable {

&#x20;       storednumber=helper(number);

&#x20;       emit LogNumber(storednumber);

&#x20;   }

&#x20;   function destory() public {

&#x20;       require(msg.sender == owner);

&#x20;       selfdestruct(owner);

&#x20;   }

}

function helper(uint x) pure returns (uint) {

&#x20;   return x \* 2;

}

第一行是声明开源协议

第二行声明代码适合的编译器版本

第三行contract关键词定义合约

合约中可以包括：状态变量， 函数， 事件。

#### 2.2状态变量

状态变量是永久地存储在合约存储中的值， 我们可以把合约理解成为一个单例的程序，constructor 构造函数只会在第一次合约创建时调用，所以合约中定义的变量就像单例对象的属于一样，值会全局一直保存。一般在构造函数中存储合约的创建者地址，方便合约后期做一些只能创建者操作的权限判断。

变量的数据类型有：

* 布尔值bool，值为true或false
* 整数型int， uint（无符合），声明长度，以8比特为单位， int8到uint256 ,如果没有定义长度，默认是256
* 浮点数fixed ，ufixed , 定义方式(ufixedMxN), M是整数的比特位数，N是小数位数
* 地址address， address有成员函数balance，transfer等 ， 地址类型要能支付需要在定义时用payable修饰或者在使用时用payable函数转换
* 字节数组（固定）， bytes1 到 bytes32， 一个英文字母会占一个byte
* 字节数组（动态） bytes， string , 字符串没有直接支持中文。 中文要utf8编码后才能赋值给变量。
  * 字符串拼接， 没有直接拼接的方法， 需要封装一个函数：

function  strConcat(string memory \_a, string memory \_b) internal pure returns (string memory){

&#x20;       bytes memory \_ba = bytes(\_a);

&#x20;       bytes memory \_bb = bytes(\_b);

&#x20;       string memory ret = new string(\_ba.length + \_bb.length);

&#x20;       bytes memory bret = bytes(ret);

&#x20;       uint k = 0;

&#x20;       for (uint i = 0; i < \_ba.length; i++) bret\[k++] = \_ba\[i];

&#x20;       for (uint i = 0; i < \_bb.length; i++) bret\[k++] = \_bb\[i];

&#x20;       return string(ret);

}

* 枚举类型 enum Name {Labe1,labe2}
* 数组 ，如unit32\[]\[5]
  * 数组的添加
    * arrayName.push() ; 添加数组
  * 数组的删除
    * delete arrayName\[1]; ,这个是清空值: 数字为变为0，地址会变为0地址，位置还存在，需要手动移动位置，如：

uint\[] array = \[1,2,3,4,5];

uint index=3;

delete array\[index];

for (uint i = index; i\<array.length-1; i++){

&#x20;array\[i] = array\[i+1];

}

array.pop();//删除最后一个函数

网上有文章讲array.length--改变长度的，0.6后不支持array.length-- 改变长度，需要array.pop 删除最后一个元素。

注意和js语言的pop函数不同的是，这里不会返回最后一个元素，也不能传参制定索引。

* * 数组的遍历

for(uint i=0;i\<arr.length;i++){

&#x20;arr\[i]; //获得数组的值

}

arr.length是属于uint类型的。

数组中的值只能是标量，不能为复杂的struct结构， 可以结合映射实现复杂的例子，这个例子可以学习一下： <https://ethereum.stackexchange.com/questions/12611/solidity-filling-a-struct-array-containing-itself-an-array/12614>

* 结构：struct NAME{TYPE1 NAME1; TYPE2 NAME2}
* 映射： mapping(KEYTYPE => VALUETYPE) NAME;
* 变量修饰符 memory、storage、calldata。

&#x20;状态变量会强制为storage类型， 外部传参的参数默认会为calldata类型。 calldata类型只能只读。 memory类型是内存临时变量，执行完后就会被释放， 一般函数类定义的变量会为memory类型的。

#### 2.3函数

形式： function FunctionName(\[parameters]) {public|external|private|internal} \[pure|constant|view|playable] \[modifiers] \[returns (return types)]

下面对各部分说明一下。

* 可见性说明

solidity函数的可见性定义在函数名之后，且是必须定义的。

* * public 内部外部都可以调用
  * external 外部可调用，内部要用this调用 , external比public更省gas
  * internal 只能合约内部调用，子类也可以调用
  * private 只能合约内部调用，子类不能调用
* 函数行为声明
  * view , 声明不会改变区块链状态（修改状态变量），只会读取状态变量，可以return 返回数据
  * prue， 声明不会改变区块链状态，也不读取状态变量，只处理计算就返回结果。
  * payable， 接受支付以太坊。
  * 不声明，表示non-payable 类型，表示会改变区块链状态但不支持支付。

注：view 和 prue 是不会产生gas费用的，因为他不用旷工确认，多声明view或prue，这是在省钱。

* 函数修饰符modifier

contract Mytest {

&#x20;event printString(string s);

&#x20;modifier onlyOwner{

&#x20;        require(msg.sender == owner);

&#x20;        \_;

&#x20;        emit printString("runing");

&#x20;    }

&#x20; function testmodifer() public onlyOwner{

&#x20;       emit printString("body");

&#x20; }

}

函数修饰符常用于验证条件的封装，下划线"\_" 表示要替换代码的body部分。

#### 2.4事件

用event声明，用emit触发，会记录到区块链的交易日志中，参数可以添加indexed关键词作为索引查询web3可以查询

event Withdrawal(adress indexed to,uint amount);

....

emit Withdrawal(msg.sender,123);

我们经常也会用事件来调试， 事件日志在合约调用也是会显示在remix控制台的。

#### 2.5错误处理

* require , 不符合条件就会抛出异常， 第二个参数可以写错误原因

require(msg.sender==owner,"Only the contract owner can call this function");

* revert ,支持抛出异常和输出错误信息

if(msg.sender!=owner){

&#x20;revert("Only the contract owner can call this function");

}

#### 2.6全局变量

* msg对象
  * msg.sender 调用者地址，可以是外部账户也可以是合约账户（合约之间可以互相调用）
  * msg.value 以太坊数量，单位wei
  * msg.gas 新版是用gasleft()函数获得要剩余的gas。
  * msg.data 传入的数据 bytes类型
  * msg.sig 签名, 类型是bytes4，其实是函数选择器的字符，EVM通过这个知道执行那个函数。
* tx对象， 交易相关信息
  * tx.gasprice 交易价格。
  * tx.origin 交易发起的外部账户。
* block区块对象
  * blockhash(blockNumber)函数，指定区块ID返回区块hash，类型是bytes32;
  * block.coinbase 矿工地址
  * block.difficulty 当前区块正明难度
  * block.gaslimit ， 当前区块花费的最大gas
  * block.number , 当前区块编号
  * block.timestamp , 当前区块写入的时间戳
* address，地址对象
  * address.balance 余额
  * address.transfter(number) 向这个地址转账
  * address.send(number) 与transfter 类似， 出错是不会抛出异常，会返回false
  * call/callcode/delegatecall 调用合约

&#x20;这几个方法都是比较旧的方式， 新方式可以把合约对象作为传参，然后显性调用， 如：

contract Mybase{

&#x20;   function run() public pure returns(string memory){

&#x20;       return "test";

&#x20;   }

}

contract Mytest {

&#x20;event printString(string  s);

&#x20;function testcall(Mybase my) public {

&#x20;       emit printString("run start");

&#x20;       string memory s = my.run();

&#x20;       emit printString(s);

&#x20;       emit printString("run end");

&#x20;   }

}

这my这个参数在调用的时候传递合约地址。 其实这个合约地址下只要有run方法就行。EVM是无法识别合约是不是属于Mybase的。

#### 2.7接口和库

接口

interface InterfaceBase {

&#x20;   //抽象方法

&#x20;   function brand() external  returns (bytes32);

}

contract MyContract is InterfaceBase{

&#x20;   function brand()  external override pure  returns (bytes32){

&#x20;       return '123';

&#x20;   }

}

接口定义的方法要用external不能用public

实现接口的方法要用override修饰

solidity 是用is实现继承的， is可以跟多个类来继承

库：

library  Lib {

&#x20;   function test(string memory s) external pure returns(string memory){

&#x20;       return s;

&#x20;   }

}

contract MyContract {

&#x20;   function run()  external pure{

&#x20;       Lib.test("xxx"); //调用库的方法

&#x20;   }

}

#### 2.8导入文件

父类或库独立成单独的文件方便代码管理，然后可以用import导入。

比如独立 lib.sol 文件

导入：

import "./lib.sol";

这样导入文件中所有类会被导入， 为了防止有名称冲突可以：

import \* as lib from "./lib.sol"

也可以自定导入文件中的几个对象

import {Lib,InterfaceBase} from "./lib.sol"

solidity的导入语法是遵循ES6规范的。

#### 2.9让合约可以接受支付

合约中加入如下代码：

event Received(address, uint);

receive() external payable {

&#x20;emit Received(msg.sender, msg.value);

}

#### 2.10合约被调用到不存在的方法的处理

fallback() external {

&#x20;//处理代码

}

可以接受calldata直接调用：

#### 2.11合约摧毁

调用函数 selfdestrunct 可以摧毁合约， 摧毁时会把合约的余额打入到函数指定的地址，并奖励gas， 这个机制是激励开发回收不用的合约，虽然合约摧毁了，但是交易记录依然永久存在区块链上。

如：

// SPDX-License-Identifier: GPL-3.0

pragma solidity >0.7.0 <0.8.0;

contract Mytest {

&#x20;address payable owner;

&#x20;constructor(){

&#x20;owner=msg.sender;

&#x20;}

&#x20;function destory() public {

&#x20;require(msg.sender == owner);

&#x20;selfdestruct(owner);

&#x20;}

}

调用合约的destory方法就可以摧毁合约。 摧毁的是合约的代码的并不会摧毁合约地址。 所以看见的现象会是，合约还能调用，而且还能往合约地址打以太坊， 合约地址成为了黑户，如果往里面打以太坊将取不回。 所以摧毁合同是危险的，还是不要轻易摧毁。

更多学习，看文档： <https://learnblockchain.cn/docs/solidity/index.html>
