PS. 才疏学浅,欢迎交流指正。
Remix介绍
Remix:remix.ethereum.org/
Remix是一个可以编写、调试、部署智能合约的在线工具。下面我们将使用Remix编写Solidity智能合约。
第一个智能合约
进入Remix,删除自带的所有文件和文件夹。创建一个contracts
文件夹,在其中创建文件SimpleStorage.sol
。
sol文件首先需要指定代码使用协议,这里我们使用限制较少的MIT协议。
// SPDX-License-Identifier: MIT
复制代码
接着需要指定Solidity版本,这里我们使用此时较为稳定的0.8.7版本。
// SPDX-License-Identifier: MIT
pragma solidity 0.8.7;
复制代码
Solidity版本有多种指定方式,例如可以使用pragma solidity ^0.8.7;
指定0.8.7或以上的版本。
可以使用pragma solidity >=0.8.7 <0.9.0;
指定0.8.7或以上、0.9.0以下的版本。
此时可以点击Compile按钮编译合约,编译前编译器版本会自动更改为符合代码中指定的版本。
因为此时文件中没有实际的合约代码,因此会提示No Contract Compiled Yet
,但只要侧边栏上的SOLIDITY COMPILER按钮上有勾,那么编译就是成功的。
下面编写合约代码。
// SPDX-License-Identifier: MIT
pragma solidity 0.8.7;
contract SimpleStorage {
}
复制代码
其中contract
关键字表示后面的代码是合约代码。
此时的合约文件中就已经包含一个有效的合约了,编译成功后侧边栏中也会出现新的合约相关的选项。
基础数据类型
bool
即boolean类型,值为true或false
uint
无符号整数。无符号的意思是只会是可正可负的,只会是正数。
可以通过使用不同的uint关键字给uint类型的值分配空间,如uint8
表示给该uint值分配8个bit。使用unit
关键字不指明分配的空间时,效果等同于使用uint256
,分配256bit。
给uint类型指定空间需要按照8的倍数,即以bytes(8个bits)为单位,如:uint8
,uint16
,uint32
等,最多为uint256
。
如果声明uint类型的变量,但是不赋值,如uint256 defaultUint;
则该变量会被赋值为0。
int
(带符号)整数,可正可负。
int类型和uint类型一样可以指定分配的空间。
address
地址类型,可以用来表示钱包地址。
bytes
bytes最多分配为bytes32
。
bytes
等同于bytes32
。
string
字符串类型。本质上是bytes类型,可以自动转化为bytes类型,比如可以给bytes类型的变量赋值字符串。
关于类型的更多的知识可以去看solidity的官方文档。
下面尝试定义上述类型的变量
// SPDX-License-Identifier: MIT
pragma solidity 0.8.7;
contract SimpleStorage {
bool hasFavoriteNumber = true;
uint256 defaultUint;
uint256 favoriteNumber = 5;
string favoriteNumberInText = "Five";
int256 favoriteInt = -5;
bytes32 favoriteBytes = "cat";
}
复制代码
尝试编译,成功则表明代码中定义的变量都符合要求。
函数
Remix VM是本地测试用的区块链,可以快速模拟交易,不需要等待。
Remix还提供了大量本地测试用的账户,初始时里面都有100Ether的测试币。
将SimpleStorage.sol
中的合约代码修改如下后,我们将部署这个合约。
// SPDX-License-Identifier: MIT
pragma solidity 0.8.7;
contract SimpleStorage {
uint256 favoriteNumber;
function store (uint256 _favoriteNumber) public {
favoriteNumber = _favoriteNumber;
}
}
复制代码
这里可选择要部署的合约文件。
点击Deploy按钮后,如果部署成功,可以在下图所示位置看到部署好的合约,其中红框部分是合约的地址。
部署成功后,我们还可以在底栏看到部署相关的信息。
点击可以查看更多部署详情。
在部署详情中我们可以看到有交易哈希(transaction hash)、发送方(from)、燃料费(gas)等,这是因为部署一个合约实际上就是发送一个交易,在区块链上修改任何数据都是在发送交易。
下图中的红框部分为部署好的合约中的方法,可以在这里调用合约方法。
输入任意一个数字并点击按钮后,就调用了store
合约方法,这次调用改变了合约中的favoriteNumber变量的值,前面说过只要修改区块链上的数据就是在发送交易,因此发起调用后我们可以在侧栏看到交易的详情。
为了查看favoriteNumber
变量的值,我们需要给favoriteNumber
加上public
关键字。
// SPDX-License-Identifier: MIT
pragma solidity 0.8.7;
contract SimpleStorage {
uint256 public favoriteNumber;
function store (uint256 _favoriteNumber) public {
favoriteNumber = _favoriteNumber;
}
}
复制代码
我们可以重新部署合约来查看上面改动的效果。
在部署之前,为了方便观察,我们可以点击Deployed Contract
旁边的删除按钮删除前面部署好的合约。
只是界面上删除。区块链的数据不能被删除。但这只是在本地测试区块链上,不可删除只是一定程度上。
再次部署成功后侧边栏多了变量名按钮。
点击后会出现该变量当前值,此时值为默认值0。
调用store
方法后,再次点击按钮查看,可以看到变量的值已被修改。
可见性
变量的可见性
变量的可见性默认为internal
可见性为public
的变量和internal
变量的区别在于编译器会为前者自动创建getter方法,其它合约可以通过getter方法读取这些public
变量的值。这个我们在前面已经看到了效果。
函数的可见性
现在我们只需要知道view
函数只能读取数据就好了。
下面我们尝试添加view
函数retrieve
,代码如下:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.7;
contract SimpleStorage {
uint256 public favoriteNumber;
function store (uint256 _favoriteNumber) public {
favoriteNumber = _favoriteNumber;
}
function retrieve() public view returns(uint256){
return favoriteNumber;
}
}
复制代码
调用改变状态的函数和读取状态的函数的日志有不同。改变状态的方法的日志会有勾图标和交易哈希,如下图:
2-5 数组和结构体
定义结构体
使用Struct
关键字定义结构体。如下:
struct People {
uint256 favoriteNumber;
string name;
}
复制代码
使用结构体
struct People {
uint256 favoriteNumber;
string name;
}
People public person = People({favoriteNumber: 2, name: "Patrik"});
复制代码
效果
部署成功之后可以看到person
的getter方法,点击Person按钮后可以看到结构体中的数据,如下图:
声明数组
People[] public people;
复制代码
部署成功后出现people
的getter按钮,按钮旁边有一个输入框。如下图:
按钮旁边的输入框是用来输入数组索引的,由于目前People
数组为空数组,因此输入索引也看不到效果。
数组大小
这个数组的大小是动态的,可以容纳动态数量的元素,我们也可以在中括号中指明数组的大小,如下:
People[3] public people;
复制代码
这个数组就只能容纳3个元素。不过下面我们依然使用动态大小的People
数组。
在进入下一环节前,我们先把favoriteNumber
变量的public
关键字去掉,因为我们已经有了retrive
方法可以获取favoriteNumber
的值了。
此时的完整代码如下:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.7;
contract SimpleStorage {
uint256 favoriteNumber;
function store(uint256 _favoriteNumber) public {
favoriteNumber = _favoriteNumber;
}
function retrieve() public view returns (uint256) {
return favoriteNumber;
}
struct People {
uint256 favoriteNumber;
string name;
}
People[] public people;
}
复制代码
向数组添加元素
定义addPerson
方法,如下:
function addPerson(string memory _name, uint256 _favoriteNumber) public{
People memory newPerson = People({
favoriteNumber: _favoriteNumber,
name: _name
});
people.push(newPerson);
}
复制代码
部署成功后侧边栏出现addPerson
方法的按钮,如下图:
在addPerson
按钮右边的输入框中输入数据,并点击按钮调用addPerson
方法向people中添加元素,如下图。
如果调用成功,则此时我们已经在people
数组中添加了第一个元素。在People
按钮右边的输入框中输入0,并点击按钮,我们就可以看到数组已经有了刚刚添加的数据,如下图:
定义结构体变量的简单写法
可以不用花括号,直接按照键在结构体中的顺序传入参数定义结构体变量,效果是一样的,代码如下。
People memory newPerson = People(_favoriteNumber, _name);
复制代码
在此之上,我们可以更进一步地简化代码,将构造结构体变量的代码直接作为下一步的参数。代码如下:
function addPerson(string memory _name, uint256 _favoriteNumber) public {
people.push(People(_favoriteNumber, _name));
}
复制代码
错误和警告
错误
尝试把下图所指位置的分号删掉,左侧行号位置会出现红色感叹号。
鼠标放到红色感叹号上可以看到如下图的错误信息,告诉我们缺了分号。
把分号加回去后重新编译,错误就消失了。
警告
尝试把第一行的代码使用协议的声明去掉,左侧行号位置会出现黄色感叹号,侧边栏的编译器按钮也会出现黄色标签,如下图。
鼠标放到黄色感叹号上可以看到警告信息,如下图。
上面的警告信息告诉我们需要添加SPDX许可证,把删掉的代码恢复,再重新编译,警告就消失了。
警告不会阻止代码的编译,但最好还是尽可能的消除警告。
映射类型
下面使用mapping
类型构造一个将name
映射到favoriteNumber
的变量。
mapping(string => uint256) public nameToFavoriteNumber;
复制代码
在addPerson
方法中添加给nameToFavoriteNumber
赋值的功能,代码如下:
function addPerson(string memory _name, uint256 _favoriteNumber) public {
people.push(People(_favoriteNumber, _name));
nameToFavoriteNumber[_name] = _favoriteNumber;
}
复制代码
部署成功后左边会出现nameToFavoriteNumber
按钮,如图:
mapping
的value默认值是0,在对mapping
变量赋值前从中取值都会得到0,如图:
调用addPerson
方法时,会对nameToFavoriteNumber
变量进行赋值。调用后就可以从nameToFavorite
变量中取出值了,如图:
部署合约到链上
切换环境,如图:
随后会弹出MetaMask弹出,选择想要用来进行部署到钱包账户,并将MetaMask连接的链切换为Goerli,效果如图:
点击左侧的Deploy按钮,在MetaMask中确认交易,然后就会开始部署合约到链上。
等到底栏出现带绿色勾的记录,就说明合约部署成功,可以点击view on etherscan查看部署合约这笔交易的信息。
这是左边的Deployed Contracts里的就是刚刚部署到链上的合约了,下面我们尝试在这里与部署好的合约进行交互。
在store按钮右边的输入框输入要保存的数字,和前面与本地环境中的合约交互不同,这时会唤起钱包,因为这是个改变链上数据的方法,需要支付gas才能调用。
调用成功后,点击retrieve按钮,就能看到刚刚保存到合约里的数字了。
评论