# 开发手册

# 1. 简介

NULS智能合约使用Java进行开发,合约运行在NULS虚拟机中。合约开发不能使用所有的Java特性,在第3节列出具体限制。

# 2. 开发环境

# 2.1 安装JDK 8

# 2.2 安装IntelliJ IDEA

Nuls智能合约使用的开发工具为IntelliJ IDEA。

# 2.3 NULS智能合约开发工具

NULS智能合约开发工具提供的主要功能:

  • 新建NULS智能合约Maven工程
  • 提供可视化页面来编译、打包、部署合约、调用合约、查询合约相关数据

构建NULS智能合约开发工具的说明文档

# 3. NULS智能合约规范与语法

Nuls智能合约语法是Java语法的一个子集,在Java语法上做了一些限制。

# 3.1 NULS智能合约规范

合约主类必须实现Contract接口,一个智能合约只能有一个类实现Contract接口,其他类和接口都是为这个合约提供功能的。

# 3.2 关键字

下面列出Java关键字,其中将标注NULS智能合约不支持的关键字

访问控制

  • public
  • protected
  • private

定义类、接口、抽象类和实现接口、继承类的关键字、实例化对象

  • class
  • interface
  • abstract
  • implements
  • extends
  • new

包的关键字

  • import
  • package

数据类型的关键字

  • byte
  • char
  • boolean
  • short
  • int
  • float
  • long
  • double
  • void
  • null
  • true
  • false

条件循环(流程控制)

  • if
  • else
  • while
  • for
  • switch
  • case
  • default
  • do
  • break
  • continue
  • return
  • instanceof

错误处理

  • catch
  • try
  • finally
  • throw
  • throws

修饰方法、类、属性和变量

  • static
  • final
  • super
  • this
  • native(不支持)
  • strictfp(不支持)
  • synchronized(不支持)
  • transient(不支持)
  • volatile(不支持)

其他

  • enum(不支持)
  • assert(不支持)

# 3.3 基本语法

下面的语法与Java相同,只是简单列出,具体可参考Java相关文档

  • 标识符:由字符、下划线、美元符或数字组成,以字符、下划线、美元符开头
  • 基本数据类型:byte short int long float double char boolean
  • 引用数据类型:类、接口、数组
  • 算术运算符:+ - * / % ++ --
  • 关系运算符:> < >= <= == !=
  • 逻辑运算符:! & | ^ && ||
  • 位运算符:& | ^ ~ >> << >>>
  • 赋值运算符:=
  • 拓展赋值运算符:+ = -= *= /=
  • 字符串链接运算符:+
  • 三目条件运算符 ? :
  • 流程控制语句(if,switch,for,while,do...while)

# 3.4 支持的类

Nuls智能合约只能使用下面的类进行开发

  • io.nuls.contract.sdk.Address
  • io.nuls.contract.sdk.Block
  • io.nuls.contract.sdk.BlockHeader
  • io.nuls.contract.sdk.Contract
  • io.nuls.contract.sdk.Event
  • io.nuls.contract.sdk.Msg
  • io.nuls.contract.sdk.Utils
  • io.nuls.contract.sdk.MultyAssetValue
  • io.nuls.contract.sdk.annotation.View
  • io.nuls.contract.sdk.annotation.Required
  • io.nuls.contract.sdk.annotation.Payable
  • io.nuls.contract.sdk.annotation.JSONSerializable
  • io.nuls.contract.sdk.annotation.PayableMultyAsset
  • java.lang.Boolean
  • java.lang.Byte
  • java.lang.Short
  • java.lang.Character
  • java.lang.Integer
  • java.lang.Long
  • java.lang.Float
  • java.lang.Double
  • java.lang.String
  • java.lang.StringBuilder
  • java.math.BigInteger
  • java.math.BigDecimal
  • java.util.Collection
  • java.util.List
  • java.util.ArrayList
  • java.util.LinkedList
  • java.util.Map
  • java.util.HashMap
  • java.util.LinkedHashMap
  • java.util.Set
  • java.util.HashSet

# 3.5 其他限制

  • 合约类只能有一个构造方法,其他类不限制
  • 执行一次合约方法最大的Gas消耗是1000万,请保证尽可能的优化合约代码
  • 执行一次@View类型的方法调用,最大的Gas消耗是1亿,请保证尽可能的优化合约代码

# 4. NULS智能合约简单示例

一个简单的合约

合约主类必须实现Contract接口,其他类和接口都是为这个合约提供功能的。

package contracts.examples;

import io.nuls.contract.sdk.Contract;
import io.nuls.contract.sdk.annotation.Payable;
import io.nuls.contract.sdk.annotation.Required;
import io.nuls.contract.sdk.annotation.View;

public class SimpleStorage implements Contract {

    private String storedData;

    /**
     * 调用后合约状态不会改变,可以通过这种方法查询合约状态
     */
    @View
    public String getStoredData() {
        return storedData;
    }

    /**
     * 标记@Payable的方法,才能在调用时候传入NULS金额
     */
    @Payable
    public void setStoredData(@Required String storedData) {
        this.storedData = storedData;
    }
    
    /**
     * 返回值会被VM自动JSON序列化,以JSON字符串的形式返回
     * 注意:对象层级不得超过3层,超过3层的部分会调用对象的toString方法,不会再继续序列化
     */
    @JSONSerializable
    public Map vJsonSerializableMap() {
        Map map = new HashMap();
        map.put("name", "nuls");
        map.put("url", "https://nuls.io");
        return map;
    }

}

合约写好后,编译打包,部署到NULS链上时候,虚拟机会执行合约的构造方法初始化这个合约,并把这个合约状态保存在链上,合约状态是合约类的所有成员变量。 合约部署好以后,合约类的所有public方法都是能调用的,通过调用这些方法读取或修改合约状态。

注解说明

@JSONSerializable 标记@JSONSerializable的方法,返回值会被VM自动JSON序列化,以JSON字符串的形式返回。

注意:对象层级不得超过3层,超过3层的部分会调用对象的toString方法,不会再继续序列化。

@View 标记@View的方法,调用后合约状态不会改变,可以通过这种方法查询合约状态。

@Payable 标记@Payable的方法,才能在调用时候传入NULS金额

@PayableMultyAsset 标记@PayableMultyAsset的方法,才能在调用时候传入其他资产金额,支持同时转入多个其他资产

@Required 标记@Required的参数,调用时候必须传入值,若不想传递未标记此注解的参数,需要填入0或者null占位

# Github上里面有一些合约示例。

NULS合约示例收集

NULS合约示例 - NRC20

NULS合约示例 - NRC721

NULS合约示例 - POCM

# 5. NULS Contract SDK

合约SDK提供了几个类,方便合约开发:

# io.nuls.contract.sdk.Address

public class Address {

    private final String address;

    public Address(String address) {
        valid(address);
        this.address = address;
    }

    /**
     * 获取该地址的可用余额
     *
     * @return BigInteger
     */
    public native BigInteger balance();

    /**
     * 获取该地址的总余额
     *
     * @return BigInteger
     */
    public native BigInteger totalBalance();

    /**
     * 合约向该地址转账(NULS, 可锁定)
     *
     * @param value         转账金额(多少Na)
     * @param lockedTime    锁定时间(单位秒,若锁定1分钟,则填入60)
     */
    public native void transferLocked(BigInteger value, long lockedTime);

    /**
     * 合约向该地址转账(NULS)
     *
     * @param value
     */
    public void transfer(BigInteger value) {
        this.transferLocked(value, 0);
    }


    /**
     * 合约向该地址转账指定的资产(可锁定)
     *
     * @param value          转账金额
     * @param assetChainId   资产链ID
     * @param assetId        资产ID
     * @param lockedTime     锁定时间(单位秒,若锁定1分钟,则填入60)
     */
    public native void transferLocked(BigInteger value, int assetChainId, int assetId, long lockedTime);

    /**
     * 合约向该地址转账指定的资产
     *
     * @param value          转账金额
     * @param assetChainId   资产链ID
     * @param assetId        资产ID
     */
    public void transfer(BigInteger value, int assetChainId, int assetId) {
        this.transferLocked(value, assetChainId, assetId, 0);
    }

    /**
     * 获取该地址指定资产的可用余额
     *
     * @param assetChainId   资产链ID
     * @param assetId        资产ID
     * @return
     */
    public native BigInteger balance(int assetChainId, int assetId);

    /**
     * 获取该地址指定资产的总余额
     *
     * @param assetChainId   资产链ID
     * @param assetId        资产ID
     * @return
     */
    public native BigInteger totalBalance(int assetChainId, int assetId);

    /**
     * 调用该地址的合约方法
     *
     * @param methodName 方法名
     * @param methodDesc 方法签名
     * @param args       参数
     * @param value      附带的货币量(多少Na)
     */
    public native void call(String methodName, String methodDesc, String[][] args, BigInteger value);

    /**
     * 调用该地址的合约方法并带有返回值(String)
     *
     * @param methodName 方法名
     * @param methodDesc 方法签名
     * @param args       参数
     * @param value      附带的货币量(多少Na)
     * @return 调用合约后的返回值
     */
    public native String callWithReturnValue(String methodName, String methodDesc, String[][] args, BigInteger value);

    /**
     * 调用该地址的合约方法并带有返回值(String)
     *
     * @param methodName        方法名
     * @param methodDesc        方法签名
     * @param args              参数
     * @param value             转入资产数量
     * @param multyAssetValues  转入的其他资产
     * @return 调用合约后的返回值
     */
    public native String callWithReturnValue(String methodName, String methodDesc, String[][] args, BigInteger value, MultyAssetValue[] multyAssetValues);

    /**
     * 验证地址
     *
     * @param address 地址
     */
    private native void valid(String address);

    /**
     * 验证地址是否是合约地址
     *
     */
    public native boolean isContract();

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        Address address1 = (Address) o;
        return address != null ? address.equals(address1.address) : address1.address == null;
    }

    @Override
    public int hashCode() {
        return address != null ? address.hashCode() : 0;
    }

    @Override
    public String toString() {
        return address;
    }

}

# io.nuls.contract.sdk.Block

public class Block {

    /**
     * 给定块的区块头
     *
     * @param blockNumber 区块高度
     * @return 给定块的区块头
     */
    public static native BlockHeader getBlockHeader(long blockNumber);

    /**
     * 当前块的区块头
     *
     * @return 当前块的区块头
     */
    public static native BlockHeader currentBlockHeader();

    /**
     * 最新块的区块头
     *
     * @return 最新块的区块头
     */
    public static native BlockHeader newestBlockHeader();

    /**
     * 给定块的哈希值
     * hash of the given block
     *
     * @param blockNumber
     * @return 给定块的哈希值
     */
    public static String blockhash(long blockNumber) {
        return getBlockHeader(blockNumber).getHash();
    }

    /**
     * 当前块矿工地址
     * current block miner’s address
     *
     * @return 地址
     */
    public static Address coinbase() {
        return currentBlockHeader().getPackingAddress();
    }

    /**
     * 当前块编号
     * current block number
     *
     * @return number
     */
    public static long number() {
        return currentBlockHeader().getHeight();
    }

    /**
     * 当前块时间戳
     * current block timestamp
     *
     * @return timestamp
     */
    public static long timestamp() {
        return currentBlockHeader().getTime();
    }
    
}

# io.nuls.contract.sdk.BlockHeader

public class BlockHeader {

    private String hash;
    private long time;
    private long height;
    private long txCount;
    private Address packingAddress;
    private String stateRoot;

    public String getHash() {
        return hash;
    }

    public long getTime() {
        return time;
    }

    public long getHeight() {
        return height;
    }

    public long getTxCount() {
        return txCount;
    }

    public Address getPackingAddress() {
        return packingAddress;
    }

    public String getStateRoot() {
        return stateRoot;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        BlockHeader that = (BlockHeader) o;

        if (time != that.time) return false;
        if (height != that.height) return false;
        if (txCount != that.txCount) return false;
        if (hash != null ? !hash.equals(that.hash) : that.hash != null) return false;
        if (packingAddress != null ? !packingAddress.equals(that.packingAddress) : that.packingAddress != null)
            return false;
        return stateRoot != null ? stateRoot.equals(that.stateRoot) : that.stateRoot == null;
    }

    @Override
    public int hashCode() {
        int result = hash != null ? hash.hashCode() : 0;
        result = 31 * result + (int) (time ^ (time >>> 32));
        result = 31 * result + (int) (height ^ (height >>> 32));
        result = 31 * result + (int) (txCount ^ (txCount >>> 32));
        result = 31 * result + (packingAddress != null ? packingAddress.hashCode() : 0);
        result = 31 * result + (stateRoot != null ? stateRoot.hashCode() : 0);
        return result;
    }

    @Override
    public String toString() {
        return "BlockHeader{" +
                "hash='" + hash + '\'' +
                ", time=" + time +
                ", height=" + height +
                ", txCount=" + txCount +
                ", packingAddress=" + packingAddress +
                ", stateRoot='" + stateRoot + '\'' +
                '}';
    }

}

# io.nuls.contract.sdk.Contract

/**
 * 合约接口,合约类实现这个接口
 */
public interface Contract {

    /**
     * 直接向合约转账,会触发这个方法,默认不做任何操作,可以重载这个方法。
     * 前提: 需重载这个方法,并且标记`@Payable`注解
     */
    default void _payable() {
    }

    /**
     * 直接向合约转账其他资产,会触发这个方法,默认不做任何操作
     * 前提: 若合约地址支持直接转账其他资产,需重载这个方法,并且标记`@PayableMultyAsset`注解
     */
    default void _payableMultyAsset() {
    }
    
    /**
     * 1. 当共识节点奖励地址是合约地址时,会触发这个方法,参数是区块奖励地址明细二维数组数据 eg. [[address, amount], [address, amount], ...]
     * 2. 当委托节点地址是合约地址时,会触发这个方法,参数是合约地址和奖励金额二维数组数据 eg. [[address, amount]]
     * 前提: 需重载这个方法,并且标记`@Payable`注解
     */
    default void _payable(String[][] args) {
    }

}

# io.nuls.contract.sdk.Event

/**
* 事件接口,事件类实现这个接口
*/
public interface Event {
}

# io.nuls.contract.sdk.Msg

public class Msg {

    /**
     * 剩余Gas
     * remaining gas
     *
     * @return 剩余gas
     */
    public static native long gasleft();

    /**
     * 合约发送者地址
     * sender of the contract
     *
     * @return 消息发送者地址
     */
    public static native Address sender();

    /**
     * 合约发送者地址公钥
     * sender public key of the contract
     *
     * @return 消息发送者地址公钥
     */
    public static native String senderPublicKey();

    /**
     * 合约发送者转入合约地址的Nuls数量,单位是Na,1Nuls=1亿Na
     * The number of Nuls transferred by the contract sender to the contract address, the unit is Na, 1Nuls = 1 billion Na
     *
     * @return
     */
    public static native BigInteger value();
    
    /**
     * 合约发送者转入合约地址的其他资产列表
     *
     */
    public static native MultyAssetValue[] multyAssetValues();

    /**
     * Gas价格
     * gas price
     *
     * @return Gas价格
     */
    public static native long gasprice();

    /**
     * 合约地址
     * contract address
     *
     * @return 合约地址
     */
    public static native Address address();

}

# io.nuls.contract.sdk.MultyAssetValue

public class MultyAssetValue {

    private BigInteger value;
    private int assetChainId;
    private int assetId;

    public MultyAssetValue() {
    }

    public MultyAssetValue(BigInteger value, int assetChainId, int assetId) {
        this.value = value;
        this.assetChainId = assetChainId;
        this.assetId = assetId;
    }

    public BigInteger getValue() {
        return value;
    }

    public void setValue(BigInteger value) {
        this.value = value;
    }

    public int getAssetChainId() {
        return assetChainId;
    }

    public void setAssetChainId(int assetChainId) {
        this.assetChainId = assetChainId;
    }

    public int getAssetId() {
        return assetId;
    }

    public void setAssetId(int assetId) {
        this.assetId = assetId;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        MultyAssetValue that = (MultyAssetValue) o;

        if (assetChainId != that.assetChainId) return false;
        if (assetId != that.assetId) return false;
        if (!value.equals(that.value)) return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result = value.hashCode();
        result = 31 * result + assetChainId;
        result = 31 * result + assetId;
        return result;
    }
}

# io.nuls.contract.sdk.Utils

public class Utils {

    private Utils() {
    }

    /**
     * 检查条件,如果条件不满足则回滚
     *
     * @param expression
     */
    public static void require(boolean expression) {
        if (!expression) {
            revert();
        }
    }

    /**
     * 检查条件,如果条件不满足则回滚
     *
     * @param expression
     * @param errorMessage
     */
    public static void require(boolean expression, String errorMessage) {
        if (!expression) {
            revert(errorMessage);
        }
    }

    /**
     * 终止执行并还原改变的状态
     */
    public static void revert() {
        revert(null);
    }

    /**
     * 终止执行并还原改变的状态
     *
     * @param errorMessage
     */
    public static native void revert(String errorMessage);

    /**
     * 发送事件
     *
     * @param event
     */
    public static native void emit(Event event);

	/**
     * @param seed a private seed
     * @return pseudo random number (0 ~ 1)
     */
    public static float pseudoRandom(long seed) {
        int hash1 = Block.currentBlockHeader().getPackingAddress().toString().substring(2).hashCode();
        int hash2 = Msg.address().toString().substring(2).hashCode();
        int hash3 = Msg.sender() != null ? Msg.sender().toString().substring(2).hashCode() : 0;
        int hash4 = Long.valueOf(Block.timestamp()).toString().hashCode();

        long hash = seed ^ hash1 ^ hash2 ^ hash3 ^ hash4;

        seed = (hash * 0x5DEECE66DL + 0xBL) & ((1L << 48) - 1);
        return ((int) (seed >>> 24) / (float) (1 << 24));
    }

    /**
     * @return pseudo random number (0 ~ 1)
     */
    public static float pseudoRandom() {
        return pseudoRandom(0x5DEECE66DL);
    }

    /**
     *
     * Please note that this is the SHA-3 FIPS 202 standard, not Keccak-256.
     *
     * @param src source string (hex encoding string)
     * @return sha3-256 hash (hex encoding string)
     */
    public static native String sha3(String hexString);

    /**
     *
     * Please note that this is the SHA-3 FIPS 202 standard, not Keccak-256.
     *
     * @param bytes source byte array
     * @return sha3-256 hash (hex encoding string)
     */
    public static native String sha3(byte[] bytes);
    
    /**
     * [Testnet]verify signature data(ECDSA)
     *
     * @param data(hex encoding string)
     * @param signature(hex encoding string)
     * @param pubkey(hex encoding string)
     * @return verify result
     */
    public static native boolean verifySignatureData(String data, String signature, String pubkey);

    /**
     * [Testnet]根据截止高度和原始种子数量,用特定的算法生成一个随机种子
     *
     * @param endHeight 截止高度
     * @param seedCount 原始种子数量
     * @param algorithm hash算法标识
     * @return 原始种子字节数组合并后, 使用hash算法得到32位hash字节数组, 再转化为BigInteger(new BigInteger(byte[] bytes))
     */
    public static native BigInteger getRandomSeed(long endHeight, int seedCount, String algorithm);

    /**
     * [Testnet]根据截止高度和原始种子数量,用`SHA3-256`hash算法生成一个随机种子
     *
     * @param endHeight 截止高度
     * @param seedCount 原始种子数量
     * @return 原始种子字节数组合并后, 使用`SHA3-256`hash算法得到32位hash字节数组, 再转化为BigInteger(new BigInteger(byte[] bytes))
     */
    public static BigInteger getRandomSeed(long endHeight, int seedCount) {
        return getRandomSeed(endHeight, seedCount, "SHA3");
    }

    /**
     * [Testnet]根据高度范围,用特定的算法生成一个随机种子
     *
     * @param startHeight 起始高度
     * @param endHeight   截止高度
     * @param algorithm   hash算法标识
     * @return 原始种子字节数组合并后, 使用hash算法得到32位hash字节数组, 再转化为BigInteger(new BigInteger(byte[] bytes))
     */
    public static native BigInteger getRandomSeed(long startHeight, long endHeight, String algorithm);

    /**
     * [Testnet]根据高度范围,用`SHA3-256`hash算法生成一个随机种子
     *
     * @param startHeight 起始高度
     * @param endHeight   截止高度
     * @return 原始种子字节数组合并后, 使用`SHA3-256`hash算法得到32位hash字节数组, 再转化为BigInteger(new BigInteger(byte[] bytes))
     */
    public static BigInteger getRandomSeed(long startHeight, long endHeight){
        return getRandomSeed(startHeight, endHeight, "SHA3");
    }

    /**
     * [Testnet]根据截止高度和原始种子数量,获取原始种子的集合
     *
     * @param endHeight 截止高度
     * @param seedCount 原始种子数量
     * @return 返回原始种子的集合,元素是字节数组转化的BigInteger(new BigInteger(byte[] bytes))
     */
    public static native List<BigInteger> getRandomSeedList(long endHeight, int seedCount);

    /**
     * [Testnet]根据高度范围,获取原始种子的集合
     *
     * @param startHeight 起始高度
     * @param endHeight   截止高度
     * @return 返回原始种子的集合,元素是字节数组转化的BigInteger(new BigInteger(byte[] bytes))
     */
    public static native List<BigInteger> getRandomSeedList(long startHeight, long endHeight);
    
    /**
     * 调用链上其他模块的命令
     *
     * @see <a href="https://docs.nuls.io/zh/NULS2.0/vm-sdk.html">调用命令详细说明</a>
     * @param cmdName 命令名称
     * @param args 命令参数
     * @return 命令返回值(根据注册命令的返回类型可返回字符串,字符串数组,字符串二维数组)
     */
    public static native Object invokeExternalCmd(String cmdName, String[] args);
    
    /**
     * 把对象转换成json字符串
     * 注意:对象内如果包含复杂对象,序列化深度不得超过3级
     *
     * @param obj
     * @return json字符串
     */
    public static native String obj2Json(Object obj);
    
    /**
     * 内部创建合约
     * @param salt 参与计算生成新的合约地址的因素
     * @param codeCopy 指定合约模板
     * @param args 部署的参数
     * @return 部署的合约地址
     */
    public static String deploy(String[] salt, Address codeCopy, String[] args) {
        require(codeCopy.isContract(), "not contract address");
        String finalSalt = encodePacked(salt);
        int argsLength = 0;
        if (args != null) {
            argsLength = args.length;
        }
        String[] arr = new String[args.length + 2];
        arr[0] = codeCopy.toString();
        arr[1] = finalSalt;
        for (int i = 0; i < argsLength; i++) {
            arr[2 + i] = args[i];
        }
        return (String) Utils.invokeExternalCmd("createContract", arr);
    }

    /**
     * 字符串数组编码
     */
    public static String encodePacked(String[] args) {
        return (String) Utils.invokeExternalCmd("encodePacked", args);
    }

    /**
     * 计算合约地址
     * @param salt 参与计算生成新的合约地址的因素
     * @param codeHash 合约代码的hash值
     * @param sender 合约部署者
     * @return 合约地址
     */
    public static String computeAddress(String[] salt, String codeHash, Address sender) {
        String finalSalt = encodePacked(salt);
        return (String) Utils.invokeExternalCmd("computeAddress", new String[]{finalSalt, codeHash, sender.toString()});
    }

    /**
     * 获取合约代码的hash值
     * @param codeAddress 合约地址
     * @return 合约代码的hash值
     */
    public static String getCodeHash(Address codeAddress) {
        require(codeAddress.isContract(), "not contract address");
        return (String) Utils.invokeExternalCmd("getCodeHash", new String[]{codeAddress.toString()});
    }
}

# io.nuls.contract.sdk.annotation.Payable

@Payable 标记@Payable的方法,才能在调用时候转入NULS金额

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Payable {
}

# io.nuls.contract.sdk.annotation.PayableMultyAsset

@PayableMultyAsset 标记@PayableMultyAsset的方法,才能在调用时候转入其他资产,支持同时转入多个其他资产

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PayableMultyAsset {
}

# io.nuls.contract.sdk.annotation.Required

@Required 标记@Required的参数,调用时候必须传入值, 未标记此注解的参数,若不想传递参数,需要填入0或者null占位

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Required {
}

# io.nuls.contract.sdk.annotation.View

@View 标记@View的方法,调用后合约状态不会改变,可以通过这种方法查询合约状态

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface View {
}

# io.nuls.contract.sdk.annotation.JSONSerializable

@JSONSerializable 标记@JSONSerializable的方法,返回值会被VM自动JSON序列化,以JSON字符串的形式返回。

注意:对象层级不得超过3层,超过3层的部分会调用对象的toString方法,不会再继续序列化。

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface JSONSerializable {
}

# 6. 智能合约主要的API

在NULS2.0模块NULS-API中,我们提供了大部分常用的API,请参考该文档中智能合约部分。

NULS-API_JSONRPC

NULS-API_RESTFUL

# 7. 智能合约方法参数传递的一些说明

智能合约的方法中如果有数组类型的参数,请使用如下方式传递参数

参考投票合约代码中的create方法

{
  "sender": "NsdtydTVWskMc7GkZzbsq2FoChqKFwMf",
  "password": "",
  "contractAddress": "NseLt14NacjTDhXaTXUdrk6VF7aEwtW4",
  "gasLimit": 200000,
  "price": 1,
  "value": 10000000000,
  "methodName": "create",
  "methodDesc": "",
  "remark": "",
  "args": [
    "测试投票1",
    "第一个投票合约",
    [
      "第一个选项",
      "第二个选项",
      "第三个选项"
    ],
    1536044066056, 1536184066056, false, 300, false
  ]
}
Last Updated: 2022/6/28 上午11:58:11