写给Java程序员的Solidity合约快速入门

Posted 百家饭OpenAPI

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了写给Java程序员的Solidity合约快速入门相关的知识,希望对你有一定的参考价值。

前段时间有个项目要用到智能合约,合约的载体是类以太的某链,采用Solidity作为语言,因为疫情原因拉了公司一个Java来写,于是有了这篇Solidity快速入门。

我首先要说的是Java开发写solidity合约是基本可行的,大体思路和解决方案都类似,甚至我认为比通常的Java开发还要简单一些。当然公链开发要再学习一些优化技巧,这些我们就不在这里聊了。

准备工作

要做Solidity开发,首先要有一个趁手的工具,REMIX 是Ethernum官方一直推荐的IDE,用起来很方便,建议通过这个入手。

我让我的JAVA开发首先做的事情是通过这个IDE编写一个简单的加法合约,也就是实现输入a,b,输出出a+b的简单合约。

通过这个合约,我们要达到了解以下语法结构的目的:

  1. 基本的语法逻辑
  2. 基础的工具用法

那通过这个过程,JAVA开发会很容易发现,整体Solidity和Java的语法很类似:

  1. 一个合约就是一个类,只是类的关键字改成了contract
  2. 合约中可以定义类变量和函数。函数采用function开头,returns放到最后,可以返回多个变量,是否是public函数,声明放到returns之前

类似产出这样:

pragma solidity ^0.4.13;

contract XxxContract 
    
    function func1(int256 a, int256 b) public returns (int256) 
        return a+b;
    


其中第一行是编译版本声明,而语法部分就很类似了。

而关于工具使用,据反馈,主要参考了以下的两篇文章,这里就不多讲了。

Remix的使用
Solidity教程一

其中第二篇文章是solidity的语法说明。

开始正式的合约编写

我们的Java开发做完这步就被我拉去上场了……,在这段时间里,他达到了以下的目标:

  • 完成了简单a+b合约的书写
  • 知道了Remix里如何编译,如何测试
  • 熟悉了基础语法结构

那上面的简易合约要变成一个正式的可以使用的合约,我们还需要三个步骤:

学会存储变量

首先我们开始将a+b合约逻辑升级成为:

  • 提供一个setA函数存储a
  • 提供一个setB函数存储b
  • 提供一个无入参的getAplusB函数用于获得a+b的结果

这里面,重要的事情是,a和b要以变量的形式存储起来。

其实改起来很简单了,大概是这个样子:

pragma solidity ^0.8.7;

contract Storage 
    int256 a;
    int256 b;

    function setA(int256 num) public 
        a = num;
    

    function setB(int256 num) public 
        b = num;
    

    function getAplusB() public returns (int256) 
        return a + b;
    


那这个可能你会说和前面的也类似啊,没什么区别。

那这里面我们要给Java开发两个概念:

  1. 没有其他的存储设备需要使用。
  2. 合约的数据存储基本就是依靠变量了。

可能一个开发后台的小伙伴就会很疑惑这个事情,因为通常我们的数据的持久化会依靠数据库等持久化存储设备,为什么这个地方仅仅依靠变量,那这些变量又是怎么持久化的呢?

这个问题的回答说起来就不是入门的问题了,我们可以简单的理解,区块链把程序每一次执行都进行了快照,下一次执行就是通过在上一次快照的基础上进行进一步的执行,来达到存储数据的目的。

所以我们可以简单的理解

合约就是程序,合约就是存储

因此,我们可以这么理解:写合约就是类似写service层或者mapper层,通过setter和getter来达到存储数据的目的。

学会存储Map

那接下来,我们基本就理解了合约怎么存储数据,那大家可能要问,那合约是怎么做成货币的呢,里面又是怎么存储账户余额的呢?

其实就很简答了,就是通过我们经常见到的map结果,solidity里面是mapping,写法如下:

mapping(int256 => Account) accounts;

这里面还用到了一个自定义结构体Account,加在一起就是

    struct Account 
        bool existed;
        int256 a;
        int256 b;
    
    //存储用户id和具体分数的关系
    mapping(int256 => Account) accounts;

这样一个结构就可以达到输入为int256类型用户id的所有用户存储数据的目的,那我们可以把上面的a+b合约再次升级成:支持多个用户存储独立a,b值,按用户返回a+b数据的目的,类似

pragma solidity ^0.8.7;

contract Storage 
    struct Account 
        int256 a;
        int256 b;
    

    mapping(int256 => Account) accounts;

    function setA(int256 id, int256 num) public 
        accounts[id].a = num;
    

    function setB(int256 id, int256 num) public 
        accounts[id].b = num;
    

    function getAplusB(int256 id) public view returns (int256) 
        return  accounts[id].a +  accounts[id].b;
    


这样,我们就基本完成了存储多个id的不同数据的目的了。

搞定业务逻辑

有了上面的基础,其实合约的书写就变得很简答,无非是根据不同的要求,修改Account的内容,比如string用于存证类应用,int用于各种的账户类应用,基本的逻辑都是通过存储和调用map中的数值达到存储和使用数据的目的。

那这个基础上,我们就可以去完成一些所谓”合约”的业务了,这些其实就是加强函数功能而已,比如把上述的简单a+b改成一些带判断逻辑的结构,比如增加一个函数,返回a+b最大的用户id等,这些就不多讲了,就是函数中的if,else,循环等逻辑的添加了。

常见的一些入门级坑

那我们的java工程师在项目中也顺利的完成了多个存证合约的书写,逻辑很清楚了,修改Account的结构达到存储和使用的目的, 那对于同样想做这件事情的Java工程师来讲,我们还要讲一些常见的坑,以避免在初步接触这个阶段遇到一些莫名奇妙的问题。

Stack too deep

这个限制经常出现在复杂函数中,比如我们希望通过一个setInfo函数将Account需要的多个变量都传进去,实际来讲,通常会遇到错误(Stack too deep, try using fewer variables)。这个坑是个跟内部编译有关系的坑,和过程中用到的变量值,出参计算涉及的变量值都有关系。

比如下面这个会报错的函数

    function setInfo(int256 id, int256 numA, int256 numB, int256 numC, int256 numD, int256 numE, int256 numF, int256 numG, string memory numH,string memory numI,string memory numJ,string memory info) public returns (int256) 
        var account = accounts[id];
        account.a = numA;
        account.b = numB;
        account.c = numC;
        account.d = numD;
        account.e = numE;
        account.f = numF;
        account.g = numG;
        account.h = numH;
        account.i = numI;
        account.info = info;
        return numA+numB+numC+numD+numE;
    

可以通过减少return中涉及的变量,或者减少入参来达到目的,总数上限应该是16个。如果超出了,只能通过修改数据结构来达到目的了。(如果是存证,可以考虑干脆就要求应用上传json字符串)

map没有不包含一说

这个什么意思呢,就是map拿任意key去取值,都会取出来一个对应的值,只是如果是没有存储的,会返回默认值,没有通常语言常用的IsExists等判断是否有对应值的操作。

所以我们常常把struct中默认带一个existed变量

struct Account 
        bool existed;
        int256 a;
        int256 b;
    
mapping(int256 => Account) accounts;

function setAccount(int256 user_id,int256 numa, int256 numb) public 
    Account memory account = accounts[user_id];
    //如果该用户并没有被创建
    if (!account .existed) 
      account = Account (true, numa,num b);
    

更多语法说明

那这个时候我们还会遇到一些新的语法问题,比如storage和memory的声明,pure、views等的函数限制说明,这个根据ide提示来添加就好了。

进一步深入

当然,如果要写更好的solidity区块链应用,我们可能会需要了解更多的东西,比如日志的写法,多合约的调用,有了这些的帮助,我们才能够在合约的基础上去附加更多的比如统计操作等。

另外,针对应用开发,合理的上层架构架构开发也是很重要的部分,比如合理利用交易和查询来进行提速等,这些都是后话了。

个人觉得,做到除这一章之外的其他部分,我们就可以去写一些存证应用等区块链应用了,是不是很简单?

以太坊智能合约开发:Solidity 语言快速入门

在本文中,我们从一个简单的智能合约样例出发,通过对智能合约源文件结构的剖析与介绍,使大家对Solidity语言有一个初步的认识。最后,我们将该智能合约样例在 Remix 合约编译器中编译、部署,观察其执行结果。

开始之前

在开始之前,我们先对Solidity有个初步的了解,即Solidity是什么?

让我们看一下官方的描述:

  1. Solidity是一种面向对象(合约)的,为实现智能合约而创建的高级编程语言;
  2. Solidity是一种针对以太坊虚拟机(EVM)设计的语言,它受到了C++、Python和JavaScript的影响;
  3. Solidity是一种静态类型语言,支持复杂的用户定义编程,支持库和继承。

合约样例

下面是一个简单的合约例子,我们用来演示如何用Solidity编写一个简单的智能合约。

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

// 第一个合约
contract HelloWorld 
    // 状态变量
    string public str = "Hello World!";

    // set 函数
    function set(string memory s) public 
        str = s;
    

    // get 函数
    function get() public view returns(string memory) 
        return str;
    

合约结构

版权许可

// SPDX-License-Identifier: MIT

版本许可位于源文件中的第1行,用于定义合约的版权许可标识。虽然不是强制的,但我们建议在每个源文件中都应该以这样的代码开始,来说明合约的版权许可证。如果你不想指定一个许可证,或者如果源代码不开源,请使用特殊值 UNLICENSED

版本标识

pragma solidity ^0.8.13;

版本标识位于源文件中的第2行,用于定义Solidity的版本,其中 pragma 是定义版本标识的关键字。

这行代码表示不允许低于 0.8.7 版本的编译器编译,也不允许高于 0.9.0 的编译器编译,即使用的编译器版本介于 0.8.7 与 0.9.0之间。

Solidity编译器版本规范如下:

序号版本规范说明
1^0.5.1指定的主版本号下所有更新的版本。即匹配 0.5.1 ~ 0.6.0 之间的版本
2~0.5.1指定的主版本号与次版本号下所有更新的版本。即匹配 0.5.1 ~ 0.5.9 之间的版本
3>=0.5.1版本号大于等于0.5.1,匹配 >=0.5.1 的所有版本
4<=0.5.1版本号小于等于0.5.1,匹配 <=0.5.1 的所有版本
5x匹配所有版本
60.5或0.5.x匹配指定主版本号与次版本号下的所有版本

截止到目前,Solidity的编译器版本已更新到 0.8.15。我们建议在编译部署合约时,应该尽量使用最新版本,因为新版本会有一些新特性以及bug修复。

合约类(对象)

contract HelloWorld 
    // 函数和数据

在Solidity语言中,合约类似于其他面向对象编程语言中的类。contract 是定义合约类的关键字,HelloWorld 是合约名称。我们建议合约名称和本地文件名用同一个名称,且第一个字母大写。

合约还可以从其他合约继承,可以是一些特殊的合约,比如库(library)和接口(interface)。这些知识点我们会在后续的课程中讲解。

状态变量

string public str = "Hello World!";

这段代码中的 str 就是一个状态变量,它是一个永久存储在合约存储中的值。

函数

function set(string memory s) public
function get() public view returns(string memory)

函数是合约代码的可执行单元,函数通常在合约内部定义。函数一般有以下几部分组成:

  • 函数名
  • 参数
  • 返回值

在样例代码中,我们定义了2个函数:

  • set():用于设置状态变量的值;
  • get():用于返回状态变量的值。

注释

合约中的注释有单行注释(//)和多行注释(/*...*/)两种,和C++的注释类似。

// 这是一个单行注释

/*
	这是一个
	多行注释
*/

合约部署

我们推荐使用Remix来开发简单合约。

Remix 是一个合约开发和编译器,可以在线使用,而无需安装任何东西。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hv3XOyUM-1669021104665)(D:\\资料\\我的\\项目\\IT培训项目\\区块链\\课程\\Solidity语言基础教程\\images\\remix.png)]

我们在Remix中编译、部署和运行这个样例合约。执行结果如下图:

以上是关于写给Java程序员的Solidity合约快速入门的主要内容,如果未能解决你的问题,请参考以下文章

solidity入门1. HelloWeb3

Solidity 智能合约入门

创建自己的区块链合约java版web3接口——以太坊代币

Solidity零基础入门Solidity编写智能合约代码

Solidity零基础入门Solidity编写智能合约代码

Solidity零基础入门Solidity编写智能合约代码