Published: 2020-01-24

浅尝NEAR智能合约开发

1 参与NEAR黑客松以及我犯的错误

前段时间参见了NEAR举办的黑客松活动,由于还有其他事情,仅花了4个工作日做了一个基于near的reddit原型,项目链接参见这里

由于时间比较赶,开发后匆忙测试了一遍没有问题就提交了,到最后线上评审到时候demo没有成功跑起来,自然也没拿到什么名次。

后来我看了下是什么原因没成功运行,发现自己在合约方法里犯了一个常规错误,导致“用户提交帖子”的时候默认的gas不够,而我开发的时候测试是够的。

错误原因是我用了一个类似array的形式保存了订阅同一个“栏目”下的所有“用户”,当用户“用户提交帖子”的时候,会遍历这个array里的用户做相应事情。 随着用户增多,会需要更多的gas。这也是问什么测试的时候只有少数几个还可以最后演示的demo就不行了。

本质上,是我思虑不周,实现逻辑的时候没有考虑到遍历数组当元素多了后gas怎么处理,而这正是智能合约与普通编程的一个差异点:“操作需要代价”。

接下来总结下其它编写合约过程中我遇到的一些难点以及怎么解决的。

2 编写智能合约踩的坑

下面这些我遇到的问题有可能是由于我不知道正确的解决办法,也有可能是合约相关的实现还有缺陷,我并没有求证,这里只是记录我遇到的问题和解决的办法。也有可能后续这些问题会得到解决。

2.1 map需要先判断值存不存在

刚开始的时候,直接从一个 PersistentMap 获取值返回,如果key不存在,那么合约执行(VM端)会报错,返回的报错信息无法判断是什么原因,花了我一些功夫。

而可能是 near-runtime-ts 实现或者 nearcore 实现原因, PersistentMap.get(k, defaultValue) 并不能按预期工作,最后我总是先判断key是否存在再获取值,如下:

// 获取订阅的subreddit
export function getsubscribeSubreddits(user:string): Subreddits | null {
    if (userSubscribedSubreddits.contains(user)) {  // get前总是先判断是否contains
        return userSubscribedSubreddits.get(user)
    } else {
        let result = new Subreddits();
        result.subreddit_ids = new Array<u64>(0);
        return result
    }
}

2.2 存储数组

使用 PersistentMap 来存储 key->array ,然后在接口里根据key获取这个array的时候会在编译的时候提示Array没有实现serialize方法。 由于我不知道如何给Array添加serialize方法,所以我每当需要返回一个Array的时候,总是新建了一个对象,来包裹这个Array,即使造成了啰嗦。 这样合约方法返回的时候,直接返回 Subreddits 对象,而不是 Array<Subreddit>

export class Subreddit {
    id:u64;
    name:string;
    description:string;
    submit_ids:Array<u64>;
    creator:string;
}

export class Subreddits {  // 对Subreddit数组的包装
    subreddit_ids:u64[]
}

2.3 返回单个对象时需要用数组封装

当需要根据id来返回一个object的时候,我遇到这样的问题:

  1. 合约方法不能返回null,否则虚拟机执行的时候报错
  2. map.get(key) 这样的方法由于类型推断一定需要我在方法的返回类型上加上 null
  3. 我不知道如何一个方法返回三个类型

最后折衷起来用数组包裹单个object,当空数组的时候,表示不存在,而不是用 null 表示。

// 根据subreddit id获取subreddit详细内容
// note: 我被迫用数组来返回单个对象, 因为我无法用null来表示不存在,返回wasm 执行错误, 也不知怎么返回3种类型
export function getSubredditDetail(id:u64):Subreddit[] | null {

    // 判断是否存在id的内容
    if (!AllSubreddits.contains(id)) {
        return new Array<Subreddit>(0)         // 空数组表示不存在
    } else {
        let result = new Array<Subreddit>(0);  // 新建一个数组来包裹单个对象
        result.push(AllSubreddits.getSome(id));
        return result
    }
}


该问题应该有其他更合理的解决方法。

3 End

Author: Nisen

Email: imnisen@163.com