Skip to content

Latest commit

 

History

History
465 lines (403 loc) · 13.5 KB

tutorial.md

File metadata and controls

465 lines (403 loc) · 13.5 KB

VNT Dapp开发流程

开发工具

  • Bottle,vnt合约工具,用于合约编写的代码提示,代码编译,abi生成,压缩和解压。
  • VNT Contract for vscode,合约代码提示的vscode插件。
  • vnt.js,JavaScript接口,用于提供常用的账户查询,转账,合约等相关操作。
  • vnt-kit.js,JavaScript接口,用于提供钱包相关的基本操作。

合约语法

合约编写

VNT合约使用c语言进行编写,同时吸纳了以太坊合约语言solidity的一些语法,使得拥有c语言基础和solidity基础的开发者能够很容易的开发出一个智能合约

#include "vntlib.h"

typedef struct
{
  uint256 balance;     //存款
  string nickName;     //昵称
  bool freeAddress;    //是否已经领取过赠送的筹码
  uint64 winCount;     //赢的局数
  uint64 loseCount;    //输的局数
  uint64 chickenCount; //猜中50的局数
  uint256 winReward;   //赢的收益
  uint256 loseAmount;  //输的总额
} Account;
//账号余额
KEY mapping(address, Account) accounts;

//总局数
KEY uint64 totalGameCount;

//存款总额
KEY uint256 deposit;

// 10%
KEY uint256 fee = U256(10);

KEY address owner;

KEY uint256 freeAmount = U256(100000000000000000000); // 100*10**18;

EVENT EVENT_BET(indexed address from, string nickname, uint256 amount,
                int32 bigger, uint64 lottery, uint256 reward);
EVENT EVENT_WITHDRAW(indexed address from, string nickname, uint256 amount);
EVENT EVENT_DEPOSIT(indexed address from, string nickname, uint256 amount);
EVENT EVENT_NICKNAME(indexed address from, string nickName);
EVENT EVENT_GETFREEVNT(indexed address from, bool got);

constructor $Dice()
{
  owner = GetSender();
  totalGameCount = 0;
}

// getFee
uint256 getReward(uint256 amount)
{
  PrintUint256T("get amount in getreward:", amount);
  PrintUint256T("get fee1:", fee);
  uint256 res = U256SafeDiv(amount, fee);
  PrintUint256T("get fee2:", res);
  uint256 reward = U256SafeSub(amount, res);
  PrintUint256T("get reward:", reward);
  return reward;
}

//是否有足够的赌注
void checkAmount(uint256 amount)
{
  Require(U256_Cmp(amount, U256(0) == 1), "amount must > 0");
  address from = GetSender();
  accounts.key = from;
  uint256 balance = accounts.value.balance;
  PrintAddress("get sender:", from);
  PrintUint256T("get balance:", balance);
  Require(U256_Cmp(U256SafeSub(balance, amount), 0) != -1,
          "No enough money to bet");
}

//奖池是否足够
void checkPool(uint256 amount)
{
  uint256 contractBalance = GetBalanceFromAddress(GetContractAddress());
  PrintAddress("get contract address:", GetContractAddress());
  PrintUint256T("get contract balance:", contractBalance);
  PrintUint256T("get deposit balance:", deposit);
  uint256 reward = getReward(amount);
  Require(
      U256_Cmp(U256SafeSub(contractBalance,
                           U256SafeAdd(deposit, U256SafeMul(reward, U256(10)))),
               0) != -1,
      "No enough money in prize pool");
}

void checkOwner()
{
  address sender = GetSender();
  Require(Equal(sender, owner) == true, "Only the owner can operate");
}

uint64 random()
{
  uint64 time = GetTimestamp();
  PrintUint64T("get time", time);
  string time_sha3 = SHA3(SHA3(SHA3(FromU64(time))));
  PrintStr("get time sha3", time_sha3);
  uint64 index = time % 63 + 2;
  PrintStr("get index", index);
  uint64 gas = GetGas() % 64 + 2;
  PrintStr("get gas", gas);
  uint64 random_a = (uint64)time_sha3[index];
  PrintUint64T("get random_a", random_a);
  uint64 random_b = (uint64)time_sha3[index + 1];
  PrintUint64T("get random_b", random_b);
  uint64 random_c = random_a * random_b * gas % 101;
  PrintUint64T("get result", random_c);
  return random_c;
}

UNMUTABLE
uint64 testRandom() { return random(); }

//-1:<50,0:=50,1:>50
MUTABLE
void Bet(uint256 amount, int32 bigger)
{
  PrintUint256T("get amount:", amount);
  checkAmount(amount);
  checkPool(amount);
  address sender = GetSender();
  uint64 res = random();
  totalGameCount += 1;
  if (res > 50 && bigger == 1)
  {
    // you win
    accounts.key = sender;
    uint256 reward = getReward(amount);
    accounts.value.balance = U256SafeAdd(accounts.value.balance, reward);
    accounts.value.winReward = U256SafeAdd(accounts.value.winReward, reward);
    deposit = U256SafeAdd(deposit, reward);
    accounts.value.winCount += 1;
    EVENT_BET(sender, accounts.value.nickName, amount, bigger, res, reward);
  }
  else if (res < 50 && bigger == -1)
  {
    // you win
    accounts.key = sender;
    uint256 reward = getReward(amount);
    accounts.value.balance = U256SafeAdd(accounts.value.balance, reward);
    accounts.value.winReward = U256SafeAdd(accounts.value.winReward, reward);
    deposit = U256SafeAdd(deposit, reward);
    accounts.value.winCount += 1;
    EVENT_BET(sender, accounts.value.nickName, amount, bigger, res, reward);
  }
  else if (res == 50 && bigger == 0)
  {
    // you are the luckist man
    accounts.key = sender;
    uint256 reward = getReward(amount);
    reward = U256SafeMul(reward, U256(100));
    accounts.value.balance = U256SafeAdd(accounts.value.balance, reward);
    accounts.value.winReward = U256SafeAdd(accounts.value.winReward, reward);
    deposit = U256SafeAdd(deposit, reward);
    accounts.value.chickenCount += 1;
    EVENT_BET(sender, accounts.value.nickName, amount, bigger, res, reward);
  }
  else
  {
    // you lose
    accounts.key = sender;
    accounts.value.balance = U256SafeSub(accounts.value.balance, amount);
    accounts.value.loseAmount = U256SafeAdd(accounts.value.loseAmount, amount);
    deposit = U256SafeSub(deposit, amount);
    accounts.value.loseCount += 1;
    EVENT_BET(sender, accounts.value.nickName, amount, bigger, res, U256(0));
  }
}

//提款
MUTABLE
void Withdraw(uint256 amount)
{
  checkAmount(amount);
  address from = GetSender();
  if (TransferFromContract(from, amount) == true)
  {

    accounts.key = from;
    accounts.value.balance = U256SafeSub(accounts.value.balance, amount);
    deposit = U256SafeSub(deposit, amount);
    EVENT_WITHDRAW(from, accounts.value.nickName, amount);
  }
}

//提取全部
MUTABLE
void WithdrawAll()
{
  accounts.key = GetSender();
  uint256 amount = accounts.value.balance;
  Withdraw(amount);
}

//提取奖池,only owner
MUTABLE
void WithdrawPool(uint256 amount)
{
  checkOwner();
  checkPool(amount);
  TransferFromContract(GetSender(), amount);
}

//提取奖池
MUTABLE
void WithdrawPoolAll()
{
  uint256 amount = GetBalanceFromAddress(GetContractAddress());
  WithdrawPool(amount);
}

//扩充奖池
MUTABLE
void $DepositPool() {}

//存款
MUTABLE
void $Deposit()
{
  uint256 amount = GetValue();
  address from = GetSender();
  accounts.key = from;
  accounts.value.balance = U256SafeAdd(accounts.value.balance, amount);
  deposit = U256SafeAdd(deposit, amount);
  EVENT_DEPOSIT(from, accounts.value.balance, amount);
}

//免费筹获取100VNT的筹码,每个账号可以获取一次
MUTABLE
void GetFreeChips()
{
  address from = GetSender();
  accounts.key = from;
  bool flag = accounts.value.freeAddress;
  Require(flag == false, "you have got before");
  accounts.value.balance = U256SafeAdd(accounts.value.balance, freeAmount);
  deposit = U256SafeAdd(deposit, freeAmount);
  accounts.value.freeAddress = true;
  EVENT_GETFREEVNT(from, true);
}

MUTABLE
void SetNickName(string name)
{
  address from = GetSender();
  accounts.key = from;
  accounts.value.nickName = name;
  EVENT_NICKNAME(from, name);
}

UNMUTABLE
string GetNickNameFromAddress(address addr)
{
  accounts.key = addr;
  return accounts.value.nickName;
}

UNMUTABLE
string GetNickName() { return GetNickNameFromAddress(GetSender()); }

UNMUTABLE
address GetOwner() { return owner; }

UNMUTABLE
uint256 GetAmountFromAddress(address addr)
{
  accounts.key = addr;
  return accounts.value.balance;
}

UNMUTABLE
uint256 GetAmount() { return GetAmountFromAddress(GetSender()); }

UNMUTABLE
string GetWinAndLose()
{
  accounts.key = GetSender();
  uint64 win = accounts.value.winCount;
  uint64 lose = accounts.value.loseCount;
  uint64 chicken = accounts.value.chickenCount;
  return Concat(
      Concat(Concat(Concat(FromU64(win), "-"), FromU64(chicken)), "-"),
      FromU64(lose));
}

UNMUTABLE
uint256 GetPool()
{
  uint256 amount = GetBalanceFromAddress(GetContractAddress());
  return U256SafeSub(amount, deposit);
}

UNMUTABLE
uint64 GetTotalGameCount() { return totalGameCount; }

$_() { $Deposit(); }

上面的代码是一个猜数字的智能合约例子,里面包含了随机数的生成,链上数据的存储和读取,event事件,合约转账等关键操作,更详细的语法参考文档

合约编译

合约编译需要使用bottle,编译完后会生成abi文件和xxx.compress文件,abi文件用于和合约的交互,compress文件是压缩后的合约字节码,用于合约的部署

$ bottle compile -code ./contract/dice.c

>>>Compile finished. 633.684578ms
>>>Input file
   Contract path :./contract/dice.c
>>>Output file
   Abi path: contract/output/$Dice.abi
   Precompile code path: contract/output/$Dice_precompile.c
   Wasm path: contract/output/$Dice.wasm
   Compress Data path: contract/output/$Dice.compress
   Compress Hex Data path: contract/output/$Dice.hex
   Deploy JS path: contract/output/$Dice.js
>>>Please use $Dice.compress when you want to create a contract

合约部署

合约部署之前需要先对账号进行解锁,解锁需要使用keystore文件和解锁密码,解锁后得到账号的私钥,通过私钥可以对交易进行签名,签名后的交易才能够发送到链上

import vntkit from 'vnt-kit';
var unlockAccount = (keystore, passwd) => {
    return vntkit.account.decrypt(keystore, passwd, false)
};

部署合约需要用到compress文件和abi文件

import Account from 'ethereumjs-account';
import TX from 'ethereumjs-tx';
import Vnt from 'vnt';
import vntkit from 'vnt-kit';

var vnt = new Vnt();
vnt.setProvider(new vnt.providers.HttpProvider(Config.providerUrl));

var codeFile =
    '../output/$Dice.compress';
var abiFile =
    '../output/abi.json';
var wasmabi = fs.readFileSync(abiFile);
var abi = JSON.parse(wasmabi.toString('utf-8'));


function deployWasmContract() {
  var contract = vnt.core.contract(abi).codeFile(codeFile);
  var deployContract = contract.packContructorData(
      {
        data: contract.code,  
        gas: 4000000,
        value: vnt.toWei(100000000, 'vnt'),
      });
  var nonce = vnt.core.getTransactionCount(from);
  var options = {
    nonce: nonce,
    gasPrice: vnt.toHex(30000000000000),
    gasLimit: vnt.toHex(4000000),
    data: deployContract,
    value: vnt.toHex(vnt.toWei(value)),
    chainId: CHAINID
  };
  var tx = new TX(options);
  tx.sign(new Buffer(
        prikey.substring(
            2,
            ),
        'hex'));
  var serializedTx = tx.serialize();
  vnt.core.sendRawTransaction(
        '0x' + serializedTx.toString('hex'), function(err, txHash) {
          if (err) {
            console.log('err happened: ', err)
            console.log('transaction hash: ', txHash);
          } else {
            console.log('transaction hash: ', txHash);
            var receipt = vnt.core.getTransactionReceipt("txHash")
            var contractAddress = receipt.contractAddress
          }
  });
}

合约部署成功后会获得合约的address,address代表合约的唯一入口,记下这个address,之后所有和合约相关的操作都会用到它

执行合约

执行合约需要使用abi文件和合约的address,以上面例子中调用bet方法和GetNickName为例 执行合约之前需要先对账号进行解锁,解锁参考部署合约里的代码

Bet

var requestBet = (amount, bigger, from, prikey, cb) => {
    var funcName = 'Bet';
    var data = contract.packFunctionData(funcName, [vnt.toWei(amount), bigger]);
    sendTransaction(data, 0, from, prikey, cb);
  };
var sendTransaction = (data, value, from, prikey, cb) => {
    var nonce = vnt.core.getTransactionCount(from);
    var options = {
      nonce: nonce,
      to: contractAddress,
      gasPrice: vnt.toHex(30000000000000),
      gasLimit: vnt.toHex(4000000),
      data: data,
      value: vnt.toHex(vnt.toWei(value)),
      chainId: CHAINID
    };
    var tx = new TX(options);
    tx.sign(new Buffer(
        prikey.substring(
            2,
            ),
        'hex'));
    var serializedTx = tx.serialize();
    vnt.core.sendRawTransaction(
        '0x' + serializedTx.toString('hex'), function(err, txHash) {
          cb(err, txHash);
          if (err) {
            console.log('err happened: ', err)
            console.log('transaction hash: ', txHash);
          } else {
            console.log('transaction hash: ', txHash);
          }
        });
  };

GetNickName

var requestNickName = (from, prikey, cb) => {
    var funcName = 'GetNickName';
    var data = contract.packFunctionData(funcName);
    var options =
        {from: from, to: contractAddress, data: data, chainId: CHAINID};
    vnt.core.call(options, function(err, res) {
      cb(err, contract.unPackOutput(funcName, res));
    });
  };

最后

以上这些,是完成一个vnt智能合约必要的步骤,对于Dapp开发,除了写出一个有创意,实用,有趣的智能合约外,还会涉及到dapp前端的展示,数据的交互,对于一些更复杂的Dapp,还需要在服务器端记录一些有用的信息。