欢迎查看whitecoin智能合约文档
gjavac 用Java语言实现的可以在uvm上执行的智能合约
备注
This project is under active development.
目录
智能合约简介
首先,我们所说的智能合约是在区块链(blockchain)和加密货币(cryptocurrencies)的上下文中.
智能合约(smart contract)是:
预先写好的代码逻辑(我们使用glua进行编写,也支持其他语言的语法)
在分布式的存储平台上进行存储和调用(blockchain)
可以被运行在同一区块链上的节点执行
运行的结果会形成交易进行存储并记录到区块链上
简单点说,智能合约就是一段可执行的代码(它可以被合约编写者赋予各种各样的功能),它经过编译然后被存储在区块链上;然后根据合约的地址,区块链上的节点可以调用它实现相关的功能.
智能合约有以下几个特点:
智能合约是一段可执行的代码,被记录在区块链上,无法被修改
当指定的调用代码的交易被发出,区块链上的每个节点(也可能是部分节点)都会运行这段代码并进行必要的校验
当前智能合约不能自动执行,但可以由程序外部触发执行
智能合约执行所需的输入数据应该依赖区块链的数据,这样才能保证验证结果的一致性
XWC 链支持的智能合约底层采用lua的虚拟机,执行效率较高。同时也是图灵完备的语言,可以支持任意的业务逻辑,极大的扩展了区块链的功能。同时我们还提供了其他语言的翻译工具,开发者能够以 Java/Kotlin 和 C# 来进行智能合约的开发。
gjavac介绍
gjavac是一个将Java字节码文件转换成hvm字节码文件的转换器, 结合Java, Kotlin等JVM平台上语言的编译器将Java, Kotlin等源码编译成Java字节码,就可以实现将Java, Kotlin等JVM平台上的编程语言编译到hvm字节码,从而可以用来写智能合约和链上脚本了
入门教程
开发流程

通常我们在测试链上进行开发调试,当开发测试完成后,再部署到正式链使用。
你需要什么:
用于开发Java的IDE开发环境
依赖的jar包 gjavac.jar
编译工具 uvm_ass.exe go_package_gpc.exe
正式链账户及一定数量的代币
1.开发环境
安装Java开发IDE
新建Java项目
导入依赖jar包
以idea为例
在项目中新建一个文件夹来存放jar包,一般习惯文件夹名字为libs
右键你需要添加的jar包,选择Add as Library
在弹出的Create Library 窗口上点击OK就可以了
2.第一个合约
3.编译
- 1.Java源码编译成字节码
javac -Djava.ext.dirs={依赖的gjavac.jar} {源码文件夹} -d {输出目录}
- 2.Java字节码编译成.ass和.json文件
进入字节码根目录,执行一下命令 java -classpath "{依赖的gjavac.jar};" gjavac.MainKt {字节码文件列表} "-o" "输出目录"
- 3.生成.out文件
uvm_ass.exe .ass文件
- 4.生成.gpc文件
go_package_gpc.exe -package -binary-code=.out文件 -meta=.json文件
- 5.通过rpc形式注册合约上链
curl -X POST -d "{"id": 1,"method": "register_contract", "params": ["kevin", 0.00000001, 50000,"{gpc文件地址}"]}"
- 6.合约部署成功,可以调用合约中方法
1.调用非上链方法 invoke_contract_offline {调用者名称} {合约地址} {调用的合约方法} "{合约方法的参数列表}" 2.调用上链方法 invoke_contract {调用者名称} {gas价格} {gas最大步数} {调用的合约地址} {调用的合约方法} "{调用的合约方法的参数列表}" .. note:
gas 价格,即单步执行花费的 XWC 金额,最少为 0.00001 gas 最大步数,如果实际执行步数小于该限制,按照实际收取费用,如果大于该限制,调用失败
4.开始用Java编写智能合约
通过以上简单的例子我们有了一个直观的感受。智能合约的开发过程非常简单。而开发实际的应用则需要复杂的业务逻辑,也可能要用到更为复杂的数据结构和控制流程。这里可以参考具体的语法参考文档。
gjavac 使用指南
因为gjavac 不是直接将Java/Kotlin源代码编译到hvm字节码,所以需要先安装Jetbrains IDEA(有免费的社区版)或者Java SDK来将Java/Kotlin源码编译到Java字节码文件, 然后再使用gjavac编译Java字节码文件.class文件转换到hvm字节码,推荐安装Jetbrains IDEA
可以按照以下步骤配置开发环境:
请按照Java SDK 8+ 并正确配置环境变量
请安装Jetbrains IDEA Community
新建Java或Kotlin项目,在项目的“引用”中加入gjavac的几个.jar文件。这个项目就是用来写智能合约或者链上脚本的项目,最终我们就是要把这个项目编译到hvm字节码
参照demo中例子,DemoContract1.java和DemoContract.kt是智能合约的例子。修改新建项目的源代码
在同一个Java/Kotlin项目下或者新建一个引用刚才项目的新项目。 这个项目是用来直接在Jetbrains IDEA中调试调用智能合约Java/Kotlin代码用的,将此项目设置为解决方案的启动项目
在第4步创建的项目中,可以运行项目对智能合约进行模拟调试运行
编译整个项目,在第2步创建的项目的target/classes或者classes文件夹下(根据IDE和项目管理方式可能不同)找到此项目产生的各.class文件
执行gjavac 第6步产生的合约相关的各class文件 -o result.ass产生hvm汇编文件result.ass和合约元信息文件result.meta.json,然后使用hvm_assembler -g result.ass result.meta.json得到result.gpc文件,这是目标项目的合约.gpc文件
使用产生的.gpc文件来做注册合约,调用合约,注册脚本等后续行为
如果是不是要写合约上链,只是要执行代码,需要在第7步中,改用hvm_assembler -c result.ass result_meta.json 来产生 "result.out"文件,这是hvm字节码文件,然后可以用hvm 文件路径.out来直接执行这个字节码文件
数据类型
String: 同Java的String类型,String是否相等只能用==,暂不支持equals()方法,UvmCoreLibs.tostring(Object obj)转换为String.
Long: 同Java的Long类型,使用UvmCoreLibs.tointeger(Object obj)转换为Long.
Boolean: 布尔类型,同Java
UvmMap: UvmMap.create()创建,操作方法同Java的HashMap.
UvmArray: UvmArray.create()创建,操作方法同Java的ArrayList,元素下标从1开始.
示例
Main
import gjavac.lib.UvmContract;
import static gjavac.lib.UvmCoreLibs.print;
public class DemoContractEntrypoint {
public UvmContract main() {
print("hello java");
StableTokenContractDemo contract = new StableTokenContractDemo();
contract.setStorage(new Storage());
print(contract);
// contract.init();
return contract;
}
}
ContractInterface
public interface MultiOwnedContractSimpleInterface {
void on_deposit_contract_token( String arg);
Object getOn_deposit_contract_token();
}
Contract
import gjavac.lib.*;
import static gjavac.lib.UvmCoreLibs.*;
@Contract(storage = Storage.class)
public class StableTokenContractDemo extends UvmContract<Storage> {
@Override
public void init() {
print("token contract creating");
this.getStorage().name = "";
this.getStorage().symbol = "";
this.getStorage().supply = 0L;
this.getStorage().precision = 0L;
this.getStorage().state = "NOT_INITED";
this.getStorage().admin = caller_address();
this.getStorage().minter = "";
this.getStorage().allowLock = false;
this.getStorage().fee = 0L;
this.getStorage().minTransferAmount = 0L;
this.getStorage().feeReceiveAddress = caller_address();
print("token contract created");
}
@Offline
public String state(String arg) {
return this.getStorage().state;
}
@Offline
public String tokenName(String arg) {
new Utils().checkStateInited(this);
return this.getStorage().name;
}
@Offline
public Long precision(String arg) {
new Utils().checkStateInited(this);
return this.getStorage().precision;
}
@Offline
public String admin(String arg) {
new Utils().checkStateInited(this);
return this.getStorage().admin;
}
public long totalSupply(String arg) {
new Utils().checkStateInited(this);
return this.getStorage().supply;
}
@Offline
public String isAllowLock(String arg) {
return tostring(this.getStorage().allowLock);
}
@Offline
public long supply(String arg) {
return this.getStorage().supply;
}
@Offline
public String tokenSymbol(String arg) {
return this.getStorage().symbol;
}
@Offline
public String fee(String arg) {
return tostring(this.getStorage().fee);
}
@Offline
public String minTransferAmount(String arg) {
return tostring(this.getStorage().minTransferAmount);
}
@Offline
public String feeReceiveAddress(String arg) {
return this.getStorage().feeReceiveAddress;
}
@Offline
public String minter(String arg) {
return this.getStorage().minter;
}
private void onDeposit(int amount) {
error("not support deposit to token");
}
public void onDestroy() {
error("can't destroy token contract");
}
public void initToken(String arg) {
Utils utils = new Utils();
Storage storage = this.getStorage();
UvmJsonModule json = (UvmJsonModule) UvmCoreLibs.importModule(UvmJsonModule.class, "json");
utils.checkAdmin(this);
pprint("arg:" + arg);
if (state(arg)!= utils.NOT_INITED()) {
error("this token contract inited before");
return;
}
UvmArray<String> parsed = utils.parseArgs(arg, 4, "argument format error, need format: name,symbol,minter_contract,precision");
UvmMap<Object> info = UvmMap.create();
String name = parsed.get(1);
String symbol = parsed.get(2);
String minter = parsed.get(3);
long precision = tointeger(parsed.get(4));
info.set("name", name);
info.set("symbol", symbol);
info.set("minter", minter);
info.set("precision", precision);
if (utils.isBlank(name)) {
error("name needed");
return;
}
if (utils.isBlank(symbol)) {
error("symbol needed");
return;
}
if (utils.isBlank(minter)) {
error("minter needed");
return;
}
if (!is_valid_contract_address(minter)) {
error("minter must be contract");
return;
}
if (precision <= 0) {
error("precision must be positive integer");
return;
}
UvmArray<Long> allowedPrecisions = UvmArray.create();
allowedPrecisions.add(1L);
allowedPrecisions.add(10L);
allowedPrecisions.add(100L);
allowedPrecisions.add(1000L);
allowedPrecisions.add(10000L);
allowedPrecisions.add(100000L);
allowedPrecisions.add(1000000L);
allowedPrecisions.add(10000000L);
allowedPrecisions.add(100000000L);
if (!utils.arrayContains(allowedPrecisions, precision)) {
error("precision can only be positive integer in " + json.dumps(allowedPrecisions));
return;
}
storage.setMinter(minter);
storage.setPrecision(precision);
storage.setState(utils.COMMON());
emit("Inited", json.dumps(info));
}
public void openAllowLock(String arg) {
Utils utils = new Utils();
utils.checkAdmin(this);
utils.checkState(this);
if (this.getStorage().getAllowLock()) {
error("this contract had been opened allowLock before");
return;
}
this.getStorage().setAllowLock(true);
emit("AllowedLock", "");
}
public void setFee(String feeStr) {
Utils utils = new Utils();
utils.checkAdmin(this);
utils.checkState(this);
if (tointeger(feeStr) < 0) {
error("error fee format");
return;
}
this.getStorage().setFee(tointeger(feeStr));
emit("FeeChanged", feeStr);
}
public void setMinTransferAmount(String minTransferAmountStr) {
Utils utils = new Utils();
utils.checkAdmin(this);
utils.checkState(this);
if (tointeger(minTransferAmountStr) < 0) {
error("error minTransferAmount format");
return;
}
this.getStorage().setMinTransferAmount(tointeger(minTransferAmountStr));
emit("MinTransferAmountChanged", minTransferAmountStr);
}
public void setFeeReceiveAddress(String feeReceiveAddress) {
Utils utils = new Utils();
utils.checkAdmin(this);
utils.checkState(this);
if (!is_valid_address(feeReceiveAddress)) {
error("invalid address");
return;
}
if (is_valid_contract_address(feeReceiveAddress)) {
error("can't use contract address");
return;
}
this.getStorage().setFeeReceiveAddress(feeReceiveAddress);
emit("FeeReceiveAddressChanged", feeReceiveAddress);
}
public void transfer(String arg) {
Utils utils = new Utils();
utils.checkState(this);
if ((Storage) this.getStorage() != null) {
UvmArray parsed = utils.parseAtLeastArgs(arg, 2, "argument format error, need format is to_address,integer_amount[,memo]");
String to = UvmCoreLibs.tostring(parsed.get(1));
String amountStr = (String) parsed.get(2);
utils.checkAddress(to);
UvmSafeMathModule safemathModule = (UvmSafeMathModule) UvmCoreLibs.importModule(UvmSafeMathModule.class, "safemath");
UvmBigInt bigintAmount = safemathModule.bigint(amountStr);
UvmBigInt bigint0 = safemathModule.bigint(0);
if (amountStr == null || safemathModule.le(bigintAmount, bigint0)) {
UvmCoreLibs.error("invalid amount:" + amountStr);
return;
}
String fromAddress = utils.getFromAddress();
if (fromAddress == to) {
UvmCoreLibs.error("fromAddress and toAddress is same:" + fromAddress);
return;
}
Object temp = UvmCoreLibs.fast_map_get("users", fromAddress);
if (temp == null) {
temp = "0";
}
UvmBigInt fromBalance = safemathModule.bigint(temp);
temp = UvmCoreLibs.fast_map_get("users", to);
if (temp == null) {
temp = "0";
}
UvmBigInt toBalance = safemathModule.bigint(temp);
if (safemathModule.lt(fromBalance, bigintAmount)) {
UvmCoreLibs.error("insufficient balance:" + safemathModule.tostring(fromBalance));
}
fromBalance = safemathModule.sub(fromBalance, bigintAmount);
toBalance = safemathModule.add(toBalance, bigintAmount);
String frombalanceStr = safemathModule.tostring(fromBalance);
if (frombalanceStr == "0") {
UvmCoreLibs.fast_map_set("users", fromAddress, (Object) null);
} else {
UvmCoreLibs.fast_map_set("users", fromAddress, frombalanceStr);
}
UvmCoreLibs.fast_map_set("users", to, safemathModule.tostring(toBalance));
if (UvmCoreLibs.is_valid_contract_address(to)) {
MultiOwnedContractSimpleInterface multiOwnedContract = (MultiOwnedContractSimpleInterface) UvmCoreLibs.importContractFromAddress(MultiOwnedContractSimpleInterface.class, to);
if (multiOwnedContract != null && multiOwnedContract.getOn_deposit_contract_token() != null) {
multiOwnedContract.on_deposit_contract_token(amountStr);
}
}
UvmMap eventArg = UvmMap.create();
eventArg.set("from", fromAddress);
eventArg.set("to", to);
eventArg.set("amount", amountStr);
String eventArgStr = UvmCoreLibs.tojsonstring(eventArg);
UvmCoreLibs.emit("Transfer", eventArgStr);
}
}
public void transferFrom(String arg) {
Utils utils = new Utils();
utils.checkState(this);
if ((Storage) this.getStorage() != null) {
UvmArray parsed = utils.parseAtLeastArgs(arg, 3, "argument format error, need format is fromAddress,toAddress,amount(with precision)");
String fromAddress = UvmCoreLibs.tostring(parsed.get(1));
String toAddress = UvmCoreLibs.tostring(parsed.get(2));
String amountStr = UvmCoreLibs.tostring(parsed.get(3));
utils.checkAddress(fromAddress);
utils.checkAddress(toAddress);
if (fromAddress == toAddress) {
UvmCoreLibs.error("fromAddress and toAddress is same:" + fromAddress);
return;
}
UvmSafeMathModule safemathModule = (UvmSafeMathModule) UvmCoreLibs.importModule(UvmSafeMathModule.class, "safemath");
UvmBigInt bigintAmount = safemathModule.bigint(amountStr);
UvmBigInt bigint0 = safemathModule.bigint(0);
if (amountStr == null || safemathModule.le(bigintAmount, bigint0)) {
UvmCoreLibs.error("invalid amount:" + amountStr);
}
Object temp = UvmCoreLibs.fast_map_get("users", fromAddress);
if (temp == null) {
temp = "0";
}
UvmBigInt bigintFromBalance = safemathModule.bigint(temp);
Object temp2 = UvmCoreLibs.fast_map_get("users", toAddress);
if (temp2 == null) {
temp2 = "0";
}
UvmBigInt bigintToBalance = safemathModule.bigint(temp2);
if (safemathModule.lt(bigintFromBalance, bigintAmount)) {
UvmCoreLibs.error("insufficient balance :" + safemathModule.tostring(bigintFromBalance));
}
Object allowedDataStr = UvmCoreLibs.fast_map_get("allowed", fromAddress);
if (allowedDataStr == null) {
UvmCoreLibs.error("not enough approved amount to withdraw");
} else {
UvmJsonModule jsonModule = (UvmJsonModule) UvmCoreLibs.importModule(UvmJsonModule.class, "json");
UvmMap allowedData = (UvmMap) UvmCoreLibs.totable(jsonModule.loads(UvmCoreLibs.tostring(allowedDataStr)));
String contractCaller = utils.getFromAddress();
if (allowedData == null) {
UvmCoreLibs.error("not enough approved amount to withdraw");
} else {
String approvedAmountStr = (String) allowedData.get(contractCaller);
if (approvedAmountStr == null) {
UvmCoreLibs.error("no approved amount to withdraw");
}
UvmBigInt bigintApprovedAmount = safemathModule.bigint(approvedAmountStr);
if (bigintApprovedAmount != null && !safemathModule.gt(bigintAmount, bigintApprovedAmount)) {
bigintFromBalance = safemathModule.sub(bigintFromBalance, bigintAmount);
String bigintFromBalanceStr = safemathModule.tostring(bigintFromBalance);
if (bigintFromBalanceStr == "0") {
bigintFromBalance = null;
}
bigintToBalance = safemathModule.add(bigintToBalance, bigintAmount);
String bigintToBalanceStr = safemathModule.tostring(bigintToBalance);
if (bigintToBalanceStr == "0") {
bigintToBalanceStr = null;
}
bigintApprovedAmount = safemathModule.sub(bigintApprovedAmount, bigintAmount);
UvmCoreLibs.fast_map_set("users", fromAddress, bigintFromBalanceStr);
UvmCoreLibs.fast_map_set("users", toAddress, bigintToBalanceStr);
if (safemathModule.tostring(bigintApprovedAmount) == "0") {
allowedData.set(contractCaller, null);
} else {
allowedData.set(contractCaller, safemathModule.tostring(bigintApprovedAmount));
}
allowedDataStr = UvmCoreLibs.tojsonstring(allowedData);
UvmCoreLibs.fast_map_set("allowed", fromAddress, allowedDataStr);
if (UvmCoreLibs.is_valid_contract_address(toAddress)) {
MultiOwnedContractSimpleInterface multiOwnedContract = (MultiOwnedContractSimpleInterface) UvmCoreLibs.importContractFromAddress(MultiOwnedContractSimpleInterface.class, toAddress);
if (multiOwnedContract != null && multiOwnedContract.getOn_deposit_contract_token() != null) {
multiOwnedContract.on_deposit_contract_token(amountStr);
}
}
UvmMap eventArg = UvmMap.create();
eventArg.set("from", fromAddress);
eventArg.set("to", toAddress);
eventArg.set("amount", amountStr);
String eventArgStr = UvmCoreLibs.tojsonstring(eventArg);
UvmCoreLibs.emit("Transfer", eventArgStr);
} else {
UvmCoreLibs.error("not enough approved amount to withdraw");
}
}
}
}
}
public void approve(String arg) {
Utils utils = new Utils();
utils.checkState(this);
if ((Storage) this.getStorage() != null) {
UvmArray parsed = utils.parseAtLeastArgs(arg, 2, "argument format error, need format is spenderAddress,amount(with precision)");
String spender = UvmCoreLibs.tostring(parsed.get(1));
utils.checkAddress(spender);
String amountStr = UvmCoreLibs.tostring(parsed.get(2));
UvmSafeMathModule safemathModule = (UvmSafeMathModule) UvmCoreLibs.importModule(UvmSafeMathModule.class, "safemath");
UvmBigInt bigintAmount = safemathModule.bigint(amountStr);
UvmBigInt bigint0 = safemathModule.bigint(0);
if (amountStr == null || safemathModule.lt(bigintAmount, bigint0)) {
UvmCoreLibs.error("amount must be non-negative integer");
}
String contractCaller = utils.getFromAddress();
UvmJsonModule jsonModule = (UvmJsonModule) UvmCoreLibs.importModule(UvmJsonModule.class, "json");
UvmMap allowedDataTable = (UvmMap) null;
Object allowedDataStr = UvmCoreLibs.fast_map_get("allowed", contractCaller);
if (allowedDataStr == null) {
allowedDataTable = UvmMap.create();
} else {
allowedDataTable = (UvmMap) UvmCoreLibs.totable(jsonModule.loads(UvmCoreLibs.tostring(allowedDataStr)));
if (allowedDataTable == null) {
UvmCoreLibs.error("allowed storage data error");
return;
}
}
if (safemathModule.eq(bigintAmount, bigint0)) {
allowedDataTable.set(spender, null);
} else {
allowedDataTable.set(spender, amountStr);
}
UvmCoreLibs.fast_map_set("allowed", contractCaller, UvmCoreLibs.tojsonstring(allowedDataTable));
UvmMap eventArg = UvmMap.create();
eventArg.set("from", contractCaller);
eventArg.set("spender", spender);
eventArg.set("amount", amountStr);
String eventArgStr = UvmCoreLibs.tojsonstring(eventArg);
UvmCoreLibs.emit("Approved", eventArgStr);
}
}
public void pause(String arg) {
Utils utils = new Utils();
Storage var10000 = (Storage) this.getStorage();
if (var10000 != null) {
Storage storage = var10000;
String state = storage.getState();
if (state == utils.STOPPED()) {
UvmCoreLibs.error("this contract stopped now, can't pause");
} else if (state == utils.PAUSED()) {
UvmCoreLibs.error("this contract paused now, can't pause");
} else {
utils.checkAdmin(this);
storage.setState(utils.PAUSED());
UvmCoreLibs.emit("Paused", "");
}
}
}
public void resume(String arg) {
Utils utils = new Utils();
Storage var10000 = (Storage) this.getStorage();
if (var10000 != null) {
Storage storage = var10000;
String state = storage.getState();
if (state != utils.PAUSED()) {
UvmCoreLibs.error("this contract not paused now, can't resume");
} else {
utils.checkAdmin(this);
storage.setState(utils.COMMON());
UvmCoreLibs.emit("Resumed", "");
}
}
}
public void stop(String arg) {
Utils utils = new Utils();
Storage var10000 = (Storage) this.getStorage();
if (var10000 != null) {
Storage storage = var10000;
String state = storage.getState();
if (state == utils.STOPPED()) {
UvmCoreLibs.error("this contract stopped now, can't stop");
} else if (state == utils.PAUSED()) {
UvmCoreLibs.error("this contract paused now, can't stop");
} else {
utils.checkAdmin(this);
storage.setState(utils.STOPPED());
UvmCoreLibs.emit("Stopped", "");
}
}
}
public void lock(String arg) {
Utils utils = new Utils();
utils.checkState(this);
Storage var10000 = (Storage) this.getStorage();
if (var10000 != null) {
Storage storage = var10000;
if (!storage.getAllowLock()) {
UvmCoreLibs.error("this token contract not allow lock balance");
} else {
UvmArray parsed = utils.parseAtLeastArgs(arg, 2, "arg format error, need format is integer_amount,unlockBlockNumber");
String toLockAmount = (String) parsed.get(1);
long unlockBlockNumber = UvmCoreLibs.tointeger(parsed.get(2));
UvmSafeMathModule safemathModule = (UvmSafeMathModule) UvmCoreLibs.importModule(UvmSafeMathModule.class, "safemath");
UvmBigInt bigintToLockAmount = safemathModule.bigint(toLockAmount);
UvmBigInt bigint0 = safemathModule.bigint(0L);
if (toLockAmount != null && !safemathModule.le(bigintToLockAmount, bigint0)) {
if (unlockBlockNumber < UvmCoreLibs.get_header_block_num()) {
UvmCoreLibs.error("to unlock block number can't be earlier than current block number " + UvmCoreLibs.tostring(UvmCoreLibs.get_header_block_num()));
} else {
String fromAddress = utils.getFromAddress();
if (fromAddress != UvmCoreLibs.caller_address()) {
UvmCoreLibs.error("only common user account can lock balance");
} else {
Object temp = UvmCoreLibs.fast_map_get("users", fromAddress);
if (temp == null) {
UvmCoreLibs.error("your balance is 0");
} else {
UvmBigInt bigintFromBalance = safemathModule.bigint(temp);
if (safemathModule.gt(bigintToLockAmount, bigintFromBalance)) {
UvmCoreLibs.error("you have not enough balance to lock");
} else {
Object lockedAmount = UvmCoreLibs.fast_map_get("lockedAmounts", fromAddress);
if (lockedAmount == null) {
UvmCoreLibs.fast_map_set("lockedAmounts", fromAddress, UvmCoreLibs.tostring(toLockAmount) + "," + UvmCoreLibs.tostring(unlockBlockNumber));
bigintFromBalance = safemathModule.sub(bigintFromBalance, bigintToLockAmount);
UvmCoreLibs.fast_map_set("users", fromAddress, safemathModule.tostring(bigintFromBalance));
UvmCoreLibs.emit("Locked", UvmCoreLibs.tostring(toLockAmount));
} else {
UvmCoreLibs.error("you have locked balance now, before lock again, you need unlock them or use other address to lock");
}
}
}
}
}
} else {
UvmCoreLibs.error("to unlock amount must be positive integer");
}
}
}
}
public void unlock(String arg) {
Utils utils = new Utils();
String fromAddress = utils.getFromAddress();
forceUnlock(fromAddress);
}
public void forceUnlock(String unlockAddress) {
Utils utils = new Utils();
utils.checkState(this);
if (this.getStorage().getAllowLock() == false) {
UvmCoreLibs.error("this token contract not allow lock balance");
} else {
Object lockedStr = UvmCoreLibs.fast_map_get("lockedAmounts", unlockAddress);
if (lockedStr == null) {
UvmCoreLibs.error("you have not locked balance");
} else {
UvmArray lockedInfoParsed = utils.parseAtLeastArgs(UvmCoreLibs.tostring(lockedStr), 2, "locked amount info format error");
String lockedAmountStr = UvmCoreLibs.tostring(lockedInfoParsed.get(1));
long canUnlockBlockNumber = UvmCoreLibs.tointeger(lockedInfoParsed.get(2));
if (UvmCoreLibs.get_header_block_num() < canUnlockBlockNumber) {
UvmCoreLibs.error("your locked balance only can be unlock after block #" + UvmCoreLibs.tostring(canUnlockBlockNumber));
return;
}
UvmCoreLibs.fast_map_set("lockedAmounts", unlockAddress, (Object) null);
Object temp = UvmCoreLibs.fast_map_get("users", unlockAddress);
if (temp == null) {
temp = "0";
}
UvmSafeMathModule safemathModule = (UvmSafeMathModule) UvmCoreLibs.importModule(UvmSafeMathModule.class, "safemath");
UvmBigInt bigintFromBalance = safemathModule.bigint(temp);
UvmBigInt bigintLockedAmount = safemathModule.bigint(UvmCoreLibs.tostring(lockedAmountStr));
bigintFromBalance = safemathModule.add(bigintFromBalance, bigintLockedAmount);
UvmCoreLibs.fast_map_set("users", unlockAddress, safemathModule.tostring(bigintFromBalance));
String tempevent = unlockAddress + "," + UvmCoreLibs.tostring(lockedStr);
UvmCoreLibs.emit("Unlocked", tempevent);
}
}
}
public void mint(String arg) {
Utils utils = new Utils();
UvmJsonModule json = (UvmJsonModule) UvmCoreLibs.importModule(UvmJsonModule.class, "json");
utils.checkState(this);
utils.checkMinter(this);
UvmArray<String> parsed = utils.parseArgs(arg, 2, "argument format error, need format: to_address,token_amount");
String toAddress = parsed.get(1);
String amountStr = parsed.get(2);
long amount = utils.checkInteger(amountStr);
if (!is_valid_address(toAddress)) {
error("to_address is not valid address");
return;
}
if (amount <= 0) {
error("arg token_amount must > 0");
return;
}
long originSupply = this.getStorage().getSupply();
long newSupply = originSupply + amount;
if (newSupply <= originSupply) {
error("supply over flow");
return;
}
long userOldBalance = tointeger(fast_map_get("users", toAddress));
fast_map_set("users", toAddress, userOldBalance + amount);
UvmMap eventArg = UvmMap.create();
eventArg.set("address", toAddress);
eventArg.set("amount", amount);
String eventArgStr = UvmCoreLibs.tojsonstring(eventArg);
emit("Mint", json.dumps(eventArgStr));
}
public void destoryAndTrans(String arg) {
Utils utils = new Utils();
utils.checkState(this);
utils.checkMinter(this);
UvmArray<String> parsed = utils.parseArgs(arg, 4, "argument format error, need format: from_address,destory_amount,trans_to_address,trans_amount");
String fromAddress = parsed.get(1);
long destoryAmount = utils.checkInteger(parsed.get(2));
if (destoryAmount < 0) {
error("arg destory_amount must >= 0");
return;
}
String transToAddress = parsed.get(3);
long transAmount = utils.checkInteger(parsed.get(4));
if (transAmount < 0) {
error("arg trans_amount must >= 0");
return;
}
if (destoryAmount == 0 && transAmount == 0) {
error("destory_amount and trans_amount is 0");
return;
}
long originSupple = this.getStorage().getSupply();
if (originSupple < destoryAmount) {
error("supply minus error");
return;
}
this.getStorage().setSupply(originSupple - destoryAmount);
long fromOldBalance = tointeger(fast_map_get("users", fromAddress));
long subFromAmount = destoryAmount + transAmount;
if (fromOldBalance < subFromAmount) {
error("not enough balance to destory and trans , now balance:" + tostring(fromOldBalance) + " need amount:" + tostring(subFromAmount));
return;
}
if (fromOldBalance == subFromAmount) {
fast_map_set("users", fromAddress, null);
} else {
fast_map_set("users", fromAddress, fromOldBalance - subFromAmount);
}
if (transAmount > 0) {
if (!is_valid_address(transToAddress)) {
error("trans_to_address is not valid address");
return;
}
long toOldBalance = tointeger(fast_map_get("users", transToAddress));
fast_map_set("users", transToAddress, toOldBalance + transAmount);
}
UvmJsonModule json = (UvmJsonModule) UvmCoreLibs.importModule(UvmJsonModule.class, "json");
UvmMap eventArg = UvmMap.create();
eventArg.set("from_address", fromAddress);
eventArg.set("destory_amount", destoryAmount);
eventArg.set("trans_to_address", transToAddress);
eventArg.set("trans_amount", transAmount);
String eventArgStr = UvmCoreLibs.tojsonstring(eventArg);
emit("DestoryAndTrans", json.dumps(eventArgStr));
}
@Offline
public String lockedBalanceOf(String owner) {
Object resultStr = fast_map_get("lockedAmounts", owner);
if (resultStr == null) {
return "0,0";
}
return String.valueOf(resultStr);
}
@Offline
public String balanceOf(String owner) {
Utils utils = new Utils();
utils.checkStateInited(this);
utils.checkAddress(owner);
String amountStr = utils.getBalanceOfUser(this, owner);
return amountStr;
}
@Offline
public String approvedBalanceFrom(String arg) {
Utils utils = new Utils();
if ((Storage) this.getStorage() != null) {
UvmArray parsed = utils.parseAtLeastArgs(arg, 2, "argument format error, need format is spenderAddress,authorizerAddress");
String spender = UvmCoreLibs.tostring(parsed.get(1));
String authorizer = UvmCoreLibs.tostring(parsed.get(2));
utils.checkAddress(spender);
utils.checkAddress(authorizer);
Object allowedDataStr = UvmCoreLibs.fast_map_get("allowed", authorizer);
if (allowedDataStr == null) {
return "0";
} else {
UvmJsonModule jsonModule = (UvmJsonModule) UvmCoreLibs.importModule(UvmJsonModule.class, "json");
UvmMap allowedDataTable = (UvmMap) UvmCoreLibs.totable(jsonModule.loads(UvmCoreLibs.tostring(allowedDataStr)));
if (allowedDataTable == null) {
return "0";
} else {
String allowedAmount = (String) allowedDataTable.get(spender);
return allowedAmount == null ? "0" : allowedAmount;
}
}
} else {
return "";
}
}
@Offline
public String allApprovedFromUser(String arg) {
Utils utils = new Utils();
if ((Storage) this.getStorage() != null) {
utils.checkAddress(arg);
Object allowedDataStr = UvmCoreLibs.fast_map_get("allowed", "authorizer");
if (allowedDataStr == null) {
return "{}";
} else {
return UvmCoreLibs.tostring(allowedDataStr);
}
} else {
return "";
}
}
}
Storage
public class Storage {
public String name;
public String symbol;
public Long supply;
public Long precision;
public String state;
public boolean allowLock;
public Long fee;
/* 每次最低转账金额 */
public Long minTransferAmount;
/* 手续费接收地址 */
public String feeReceiveAddress;
/* admin user address */
public String admin;
public String minter;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSymbol() {
return symbol;
}
public void setSymbol(String symbol) {
this.symbol = symbol;
}
public Long getSupply() {
return supply;
}
public void setSupply(Long supply) {
this.supply = supply;
}
public Long getPrecision() {
return precision;
}
public void setPrecision(Long precision) {
this.precision = precision;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public boolean getAllowLock() {
return allowLock;
}
public void setAllowLock(boolean allowLock) {
this.allowLock = allowLock;
}
public Long getFee() {
return fee;
}
public void setFee(Long fee) {
this.fee = fee;
}
public Long getMinTransferAmount() {
return minTransferAmount;
}
public void setMinTransferAmount(Long minTransferAmount) {
this.minTransferAmount = minTransferAmount;
}
public String getFeeReceiveAddress() {
return feeReceiveAddress;
}
public void setFeeReceiveAddress(String feeReceiveAddress) {
this.feeReceiveAddress = feeReceiveAddress;
}
public String getAdmin() {
return admin;
}
public void setAdmin(String admin) {
this.admin = admin;
}
public String getMinter() {
return minter;
}
public void setMinter(String minter) {
this.minter = minter;
}
}
Utils
import gjavac.lib.*;
import kotlin.Pair;
import static gjavac.lib.UvmCoreLibs.*;
@Component
public class Utils {
public String NOT_INITED() {
return "NOT_INITED";
}
public String COMMON() {
return "COMMON";
}
public String PAUSED() {
return "PAUSED";
}
public String STOPPED() {
return "STOPPED";
}
public final long checkInteger(String numstr) {
if (isBlank(numstr)) {
error("integer format error of " + numstr);
return 0;
}
return tointeger(numstr);
}
public final String getFromAddress() {
String fromAddress;
final String prevContractId = get_prev_call_frame_contract_address();
if (isNotBlank(prevContractId) && is_valid_address(prevContractId)) {
fromAddress = prevContractId;
} else {
fromAddress = caller_address();
}
return fromAddress;
}
public final void checkAdmin(StableTokenContractDemo self) {
String fromAddress = getFromAddress();
if (self.getStorage().admin != fromAddress) {
error("you are not admin, can't call this function");
}
}
public final void checkMinter(StableTokenContractDemo self) {
String fromAddress = getFromAddress();
if (self.getStorage().minter != fromAddress) {
error("you are not minter, can't call this function");
}
}
public final UvmArray<String> parseArgs(String arg, int count, String errorMsg) {
if (isBlank(arg)) {
error(errorMsg);
return UvmArray.create();
}
UvmStringModule stringModule = importModule(UvmStringModule.class, "string");
UvmArray<String> parsed = stringModule.split(arg, ",");
if (parsed != null && parsed.size() == count) {
return parsed;
} else {
error(errorMsg);
return UvmArray.create();
}
}
public final UvmArray<String> parseAtLeastArgs(String arg, int count, String errorMsg) {
if (isBlank(arg)) {
error(errorMsg);
return UvmArray.create();
}
UvmStringModule stringModule = importModule(UvmStringModule.class, "string");
UvmArray<String> parsed = stringModule.split(arg, ",");
if (parsed != null && parsed.size() >= count) {
return parsed;
} else {
error(errorMsg);
return UvmArray.create();
}
}
public final boolean arrayContains(UvmArray col, Object item) {
if (col != null && item != null) {
ArrayIterator colTter = col.ipairs();
for (Pair colKeyValuePari = (Pair) colTter.invoke(col, 0);
colKeyValuePari.getFirst() != null;
colKeyValuePari = (Pair) colTter.invoke(col, colKeyValuePari.getFirst())) {
if (colKeyValuePari != null && colKeyValuePari.getSecond() == item) {
return true;
}
}
return false;
} else {
return false;
}
}
public final void checkState(StableTokenContractDemo self) {
String state = self.getStorage().state;
if (state == NOT_INITED())
error("contract token not inited");
if (state == PAUSED())
error("contract paused");
if (state == STOPPED())
error("contract stopped");
}
public final void checkStateInited(StableTokenContractDemo self) {
if (self.getStorage().state == NOT_INITED())
error("contract token not inited");
}
public final boolean checkAddress(String addr) {
boolean result = is_valid_address(addr);
if (!result) {
error("address format error");
}
return result;
}
public final String getBalanceOfUser(StableTokenContractDemo self, String addr) {
Object balance = fast_map_get("users", addr);
if (balance == null) {
return "0";
}
return tostring(balance);
}
public final boolean isBlank(String str) {
return str == null || str.length() == 0;
}
public final boolean isNotBlank(String str) {
return !isBlank(str);
}
}
合约API函数
合约API是在jar的UvmCoreLibs包中的静态方法,主要的API:
使用全局函数transfer_from_contract_to_address可以从当前合约(这个函数调用代码所在的合约)转账一定数额的某种资产给某个地址,第一个参数是目标地址(字符串),第二个参数是资产名称(比如HSR)第三个参数是转账数量的10万倍(int64类型),要求是正数
返回值 0 转账成功 -1 未知系统异常 -2 Asset_symbol异常 -3 合约地址非法 -4 目标地址非法 -5 账户余额不足支付转账金额 -6 转账金额为负数
使用全局函数get_contract_balance_amount可以获取某个合约带精度的余额(精度为100000),第一个参数是合约地址(支持查询其他合约的余额),第二个参数是资产名称(比如HSR),返回带精度的合约余额(int64类型),如果出现错误或者合约不存在返回负数
返回值 非负数 合约账户余额 -1 资产id异常 -2 合约地址异常
使用全局函数get_chain_now可以获取链上的当前时间,没有参数.
返回值 正数 时间戳整数 0 系统异常
使用全局函数get_chain_random可以获取链上的一个伪随机数字,但是同一个此链上的operation操作,不同节点不同时间执行返回结果都一样(实际是取操作发生的块上prev_secret_hash和本交易结合后的哈希)
返回值 随机结果
使用全局函数get_header_block_num,可以获取上一个块的块号
返回值 当前链最新块的序号
使用全局函数get_current_contract_address可以获取这个函数调用出现位置的合约地址,没有参数
全局变量caller存储着调用合约的用户的公钥,全局变量caller_address存储着调用合约的用户的账户地址
在转账到合约发生的时候,如果合约中定义了on_deposit_asset(参数是"资产标识,转账金额")这个API,那么在转账发生后会调用这个API,并且保证转账和触发此API是原子性的,如果中途出现错误,整体回滚,转账失败。
使用语句emit EventName(arg: string)可以抛出事件,这里emit是关键字,EventName根据需要写入事件名称,由区块链记录下来,其他节点同步到emit触发的event时可以调用本地设置的回调
使用全局函数 is_valid_address(arg: string)可以检查一个地址字符串是否是合法的本区块链地址
使用全局函数 is_valid_contract_address(arg: string)可以检查一个地址字符串是否是合法的合约地址
使用全局函数get_transaction_fee() 可以获取一笔交易的手续费
返回值 正整数 结果值 -1 手续费资产id异常 -2 系统异常
使用全局函数get_transaction_id(): string 可以获取本次交易的交易id
使用全局函数transfer_from_contract_to_public_account(to_account_name: string, asset_type: string, amount: int)可以从当前合约中转账到链上的账户名称,返回是否转账的状态
返回值 0 转账成功 -1 未知系统异常 -2 Asset_symbol异常 -3 合约地址非法 -4 目标地址非法 -5 账户余额不足支付转账金额 -6 转账金额为负数 -7 不存在指定账户名
import_contract: (string) => table 引用合约,参数是合约的名称字符串,返回合约对应的table
import_contract_from_address: (string) => table 根据合约地址引用合约,返回合约对应的table
get_prev_call_frame_contract_address: () => string 获取合约调用栈的上一级合约地址(如果上一级合约调用栈不是合约,则返回null)
get_prev_call_frame_api_name: () => string 获取合约调用栈的上一级合约API名称(如果上一级合约调用栈不是合约,则返回null)
get_contract_call_frame_stack_size: () => int 获取合约调用栈深度
wait_for_future_random: (int) => int 根据参数的块高度获取根据这个块数据得到的伪随机数,如果这个块高度还没有达到,则返回0
get_system_asset_symbol: () => string 获取系统基础资产的资产符号
get_system_asset_precision: () => int 获取系统基础资产的精度,这个值一般是10的若干次方