掘金 后端 ( ) • 2024-04-19 10:07

大家好,我是Frank。

在上篇中,我们用JavaScript开发了一个简单的区块链,本篇文章是上一篇的姐妹篇,如果上篇的内容还没有完全消化的话,没关系,多看几遍,自然就对其运行原理有个大概的了解了。 本篇文章主要介绍如何通过Solidity语言来开发智能合约,并在Remix线上平台对我们的程序进行编译、测试和发布。

什么是智能合约?

智能合约可以理解为类似于生活中的合同,不过也有点不同,生活中的合同需要手工去签字,而智能合约则是触发某个前置条件后,便自动执行。这么说比较抽象,可能还是不太好理解,没关系,我们继续往下看。

什么是Solidity?

Solidity是一种专门为开发智能合约而设计的编程语言,它吸收了typescript和OOP中的思想,因此,如果你对二者都熟悉的话,尤其对于后端开发的朋友们来说,学习起来应该没那么吃力。

用Solidity编写的程序和普通程序一样,需要进行编译、测试和部署,不过,我们不需要额外的再搭建平台,本教程会直接使用Remix线上平台走完这些流程。

编写智能合约

打开Remix网页我们可以看到如下界面: image.png 删除红框内的所有文件,然后新建一个新文件Blockchain.sol

image.png

我们先看第一行代码:

// SPDX-License-Identifier: MIT

这行代码是每份合约代码前面必须有的,用于指定代码的许可证类型,这里我们选用MIT协议

pragma solidity ^0.8.20;

第二行代码用于指定Solidity的版本,这里"^"符号指的是版本大于等于0.8.20

contract Blockchain {
    
}

创建一份名称为Blockchain的合约,这里的contract就可以理解成JavaScript里的class关键字

在Solidity中,我们可以使用struct关键字创建任何抽象的数据结构,因此我们可以利用它来创建一个表示区块的数据结构:

 struct Block {
    uint256 index;
    uint256 timestamp;
    uint256 amount;
    address sender;
    address recipient; 
}

uint,Solidity语言的一种数据类型,代表无符号整型,后面的数字256限定了数值的最大范围,如果没有数字后缀,如uint,则默认为uint256

address,表示以太坊地址,包含一个20字节的字符串

是不是有点像JavaScript中的对象?

const Block = {
    index: 0,
    timestamp: 0,
    amount: 0,
    sender: '',
    recipient: ''
}

接着定义两个变量,分别用于存储区块链和区块的总数量:

Block[] chain;
uint256 chainCount;

有了区块数据结构,我们还需要定义一个与前端环境通信的事件,我们可以在添加完区块后触发它:

event BlockEvent(uint256 amount, address  sender, address recipient);

最后,我们定义三个函数addBlockToChain、getChain、getChainCount,分别用于添加区块、获取区块链和区块总数:

addBlockToChain函数

function addBlockToChain(uint256 amount, address payable recipient) public {
        chainCount += 1;
        chain.push(Block(
            chainCount,
            block.timestamp,
            amount,
            msg.sender,
            recipient
        ));
        emit BlockEvent(amount, msg.sender, recipient);
    }

address payable中的payable关键字说明这个钱包地址是可以被以太坊接受的,之所以设计这个关键字,目的就是区分以太坊地址和非以太坊地址,因为智能合约不一定会运行在以太坊上。

函数定义的最后的public,说明这个函数可以被其他合约访问。

接着看看函数内部的代码,我们首先把区块数量+1,接着,把新的区块添加到链上,最后,触发一个事件到前端。

getChain函数:

function getChain() public view returns (Block[] memory) {
    return chain;
}

public 后面的view关键字表明函数内部不会改变合约的状态,目前合约的状态包含两个变量,分别是chain和chainCount。与之相对应的,还有另外个关键字pure,它对函数内部的约束更加严格,不仅不可修改合约的状态,连读也不可以。

retuns 表示函数有返回值

Block[] 表示函数的返回值类型

memory说明了函数的返回结果存储在内存上,Solitidy中有两种存储类型,一种Storage永久存储,另一种Memory表示暂时存储

getChainCount函数:

function getChainCount() public view returns (uint256) {
    return chainCount;
}

完整代码如下:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract Blockchain {
    struct Block {
        uint256 index;
        uint256 timestamp;
        uint256 amount;
        address sender;
        address recipient; 
    }

    Block[] chain;
    uint256 chainCount = 0;

    event BlockEvent(uint256 amount, address  sender, address recipient);

    function addBlockToChain(uint256 amount, address payable recipient) public {
        chainCount += 1;
        chain.push(Block(
            chainCount,
            block.timestamp,
            amount,
            msg.sender,
            recipient
        ));
        emit BlockEvent(amount, msg.sender, recipient);
    }

    function getChain() public view returns (Block[] memory) {
        return chain;
    }

    function getChainCount() public view returns (uint256) {
        return chainCount;
    }
}

编译智能合约

我们从最左边的菜单栏中选中第三个图标将会看到下面的界面,在点击蓝色的编辑按钮:

image.png

需要注意的是,最上面红框中的版本号,需跟合约中指定的版本号对应。

部署和测试

接下来我们开始部署智能合约,点击下图中的橘色按钮

image.png 看到下图中红框中的内容,说明已经部署成功

image.png

接下来,我们测试一次转账,首先,复制一个收款方钱包地址,下拉框选中第三个后,点击复制:

image.png

由于每个账户中都预先存入了100以太币,所以,我们可以开始测试转账了:

image.png

可以看到,这里有三个可供测试的函数,我们点击第一个addBlock函数后面的下拉按钮:

image.png 填入金额,把前面复制好的收款地址粘贴到recipient输入框,最后点击transact按钮,

image.png

同样的操作,我们通过测试getChain和getChainCount函数,得到结果:

image.png getChain函数返回了一个元祖数组,大家可以验证下收款地址和金额是否正确,getChainCount返回了数字1,正是我们期望的。

最后

恭喜你!坚持到了最后,现在你已经可以了解了如何开发一个简单的智能合约,并且,还成功完成了一笔转账,这为后面的深入开发奠定了坚实的基础!如果学习过程中你有任何疑问,记得在留言区讨论哦!