# 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>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.hg.network/dapp-kai-fa-jiao-cheng/web3-yu-solidity-ji-chu.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
