使用Remix入门Solidity智能合约(一)

3,386 阅读2分钟

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)为单位,如:uint8uint16uint32等,最多为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中添加元素,如下图。

image.png

如果调用成功,则此时我们已经在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按钮,就能看到刚刚保存到合约里的数字了。