Smart Contract¶
Linguagens¶
Na Nebulas suportamos duas linguagens de smart contract:
São suportadas pela integração do Chrome V8, um motor de JavaScript desenvolvido pelo The Chromium Project para o Google Chrome e navegadores baseados no Chromium.
Modelo de Execução¶
O diagrama abaixo é o Modelo de Execução do Smart Contract:
Modelo de Execução do Smart Contract
- O código fonte do Smart Contract e argumentos serão guardados na transacção e implementados na Nebulas.
- The execution of Smart Contract are divided into two phases:
- Preprocesso: injecção de instrução tracing, etcetera.
- Executa: gera src executável e executa-o.
Contractos¶
Contractos são semelhantes a classes, em linguagens orientadas a objectos. Contêm dados persistentes em variáveis de estado e funções que podem modificar essas variáveis.
Escrever um Contracto¶
Um contracto tem de ser um Objecto Protótipo ou Classe em JavaScript ou TypeScript.
Um contracto tem de incluir uma função init
, que apenas é executada uma vez após a execução. Functions, named starting with _
are private
, can‘t be executed in Transaction. The others are all public
and can be executed in Transaction.
Visto que o Contracto é executado no Chrome V8, todas as instâncias das variáveis estão na memória. Não é aconselhável guardá-las todas no state trie da Nebulas. Na Nebulas, fornecemos os objectos LocalContractStorage
e GlobalContractStorage
de modo a permitir que desenvolvedores definam os campos que necessitam de ser guardados no state trie. Estes campos devem ser definidos no constructor
do Contracto, antes de qualquer outra função.
O seguinte é um exemplo de um contracto:
class Rectangle {
constructor() {
// define campos guardados no state trie.
LocalContractStorage.defineProperties(this, {
height: null,
width: null,
});
}
// init function.
init(height, width) {
this.height = height;
this.width = width;
}
// calcula área
calcArea() {
return this.height * this.width;
}
// verify function.
verify(expected) {
let area = this.calcArea();
if (expected != area) {
throw new Error("Error: expected " + expected + ", actual is " + area + ".");
}
}
}
Visibilidade¶
Em JavaScript, não há visibilidade de funções. Todas as funções definidas no objecto protótipo são públicas.
Na Nebulas, definimos dois tipos de visibilidade, public
e private
:
public
Todas as funções cujo regexp seja^[a-zA-Z$][A-Za-z0-9_$]*$
são públicas, excepto oinit
. Funções públicas podem ser chamadas através de Transaction.private
Todas as funções cujo nome comece por_
são privadas. Uma função privada apenas pode ser invocada por um função pública.
Objectos Globais¶
linha de comandos¶
O módulo console
fornece uma consola de depuração que é semelhante à do JavaScript fornecida por navegadores web.
A consola global pode ser usada sem usar require('console')
.
console.info([...args])¶
...args <any>
A função console.info() é um alias para
console.log()
.
console.log([...args])¶
...args <any>
Print
args
para o Logger da Nebulas, com o nívelinfo
.
console.debug([...args])¶
...args <any>
Print
args
para o Logger da Nebulas, com o níveldebug
.
console.warn([...args])¶
...args <any>
Print
args
para o Logger da Nebulas, com o nívelwarn
.
console.error([...args])¶
...args <any>
Print
args
para o Logger da Nebulas, com o nívelerror
.
LocalContractStorage¶
O módulo LocalContractStorage
fornece capacidade de armazenamento baseada no state trie. Aceita key value pairs de cadeias de caracteres apenas. Todos os dados são guardados num state trie privado, associado com o endereço do contracto, e apenas este os pode aceder.
interface Descriptor {
// serialize value to string;
stringify?(value: any): string;
// deserialize value from string;
parse?(value: string): any;
}
interface DescriptorMap {
[fieldName: string]: Descriptor;
}
interface ContractStorage {
// get and return value by key from Native Storage.
rawGet(key: string): string;
// set key and value pair to Native Storage,
// return 0 for success, otherwise failure.
rawSet(key: string, value: string): number;
// define a object property named `fieldname` to `obj` with descriptor.
// default descriptor is JSON.parse/JSON.stringify descriptor.
// return this.
defineProperty(obj: any, fieldName: string, descriptor?: Descriptor): any;
// define object properties to `obj` from `props`.
// default descriptor is JSON.parse/JSON.stringify descriptor.
// return this.
defineProperties(obj: any, props: DescriptorMap): any;
// define a StorageMap property named `fieldname` to `obj` with descriptor.
// default descriptor is JSON.parse/JSON.stringify descriptor.
// return this.
defineMapProperty(obj: any, fieldName: string, descriptor?: Descriptor): any;
// define StorageMap properties to `obj` from `props`.
// default descriptor is JSON.parse/JSON.stringify descriptor.
// return this.
defineMapProperties(obj: any, props: DescriptorMap): any;
// delete key from Native Storage.
// return 0 for success, otherwise failure.
del(key: string): number;
// get value by key from Native Storage,
// deserialize value by calling `descriptor.parse` and return.
get(key: string): any;
// set key and value pair to Native Storage,
// the value will be serialized to string by calling `descriptor.stringify`.
// return 0 for success, otherwise failure.
set(key: string, value: any): number;
}
interface StorageMap {
// delete key from Native Storage, return 0 for success, otherwise failure.
del(key: string): number;
// get value by key from Native Storage,
// deserialize value by calling `descriptor.parse` and return.
get(key: string): any;
// set key and value pair to Native Storage,
// the value will be serialized to string by calling `descriptor.stringify`.
// return 0 for success, otherwise failure.
set(key: string, value: any): number;
}
BigNumber¶
O modulo BigNumber
usa bignumber.js, uma biblioteca JavaScript para precisão décimal arbitrária e aritmética não décimal. O contracto pode usar o BigNumber
directamente para lidar com o valor da transacção e transferências de outros valores.
var value = new BigNumber(0);
value.plus(1);
...
Blockchain¶
O modulo Blockchain
fornece um objecto para que os contractos obtenham transacções e blocos executados pelo contracto actual. Adicionalmente, o NAS pode ser transferido do endereço do contracto e verificação do endereço é fornecida.
API Blockchain:
// current block
Blockchain.block;
// current transaction, transaction's value/gasPrice/gasLimit auto change to BigNumber object
Blockchain.transaction;
// transfer NAS from contract to address
Blockchain.transfer(address, value);
// verify address
Blockchain.verifyAddress(address);
properties:
block
: bloco actual para execução do contractotimestamp
: marca temporal do blocoseed
: semente aleatóriaheight
: altura do bloco
transaction
: transacção actual para execução do contractohash
: hash da transacçãofrom
: endereço do remetenteto
: endereço do destinatáriovalue
: valor da transacção, um objecto BigNumber para uso do contractononce
: nonce da transacçãotimestamp
: marca temporal da transacçãogasPrice
: gasPrice da transacção, um objecto BigNumber para uso do contractogasLimit
: gasLimit da transacção, um objecto BigNumber para uso do contracto
transfer(address, value)
: transfira NAS do contracto para o endereço- params:
address
: endereço nebulas que vai receber NASvalue
: valor da transferência, um objecto BigNumber
- return:
0
: sucesso da transferência1
: transferência sem sucesso
- params:
verifyAddress(address)
: verifica o endereço- params:
address
: endereço a ser verificado
- return:
1
: endereço é válido0
: endereço é inválido
- params:
Exemplos de como utilizar:
'use strict';
var SampleContract = function () {
LocalContractStorage.defineProperties(this, {
name: null,
count: null
});
LocalContractStorage.defineMapProperty(this, "allocation");
};
SampleContract.prototype = {
init: function (name, count, allocation) {
this.name = name;
this.count = count;
allocation.forEach(function (item) {
this.allocation.put(item.name, item.count);
}, this);
console.log('init: Blockchain.block.coinbase = ' + Blockchain.block.coinbase);
console.log('init: Blockchain.block.hash = ' + Blockchain.block.hash);
console.log('init: Blockchain.block.height = ' + Blockchain.block.height);
console.log('init: Blockchain.transaction.from = ' + Blockchain.transaction.from);
console.log('init: Blockchain.transaction.to = ' + Blockchain.transaction.to);
console.log('init: Blockchain.transaction.value = ' + Blockchain.transaction.value);
console.log('init: Blockchain.transaction.nonce = ' + Blockchain.transaction.nonce);
console.log('init: Blockchain.transaction.hash = ' + Blockchain.transaction.hash);
},
transfer: function (address, value) {
var result = Blockchain.transfer(address, value);
console.log("transfer result:", result);
Event.Trigger("transfer", {
Transfer: {
from: Blockchain.transaction.to,
to: address,
value: value
}
});
},
verifyAddress: function (address) {
var result = Blockchain.verifyAddress(address);
console.log("verifyAddress result:", result);
}
};
module.exports = SampleContract;
Event¶
O modulo Event
grava execução de eventos no contracto. Os eventos gravados são incluídos na trie de eventos na chain, que podem ser obtidos através do método FetchEvents
no bloco com a hash da execução da transacção. Todos os tópicos de evento do contracto têm o prefixo chain.contract.
antes do tópico definido no contracto.
Event.Trigger(topic, obj);
topic
: user-defined topicobj
: JSON object
Pode ver um exemplo no SampleContract
acima.
Math.random¶
Math.random()
returna um ponto flutuante, um número pseudo-aleatório de domínio de 0, inclusive, até, mas não incluíndo 1. O uso típico é:
"use strict";
var BankVaultContract = function () {};
BankVaultContract.prototype = {
init: function () {},
game: function(subscript){
var arr =[1,2,3,4,5,6,7,8,9,10,11,12,13];
for(var i = 0;i < arr.length; i++){
var rand = parseInt(Math.random()*arr.length);
var t = arr[rand];
arr[rand] =arr[i];
arr[i] = t;
}
return arr[parseInt(subscript)];
},
};
module.exports = BankVaultContract;
Math.random.seed(myseed)
se necessário, pode ser usado para fazer reset da semente aleatória. O argumentomyseed
tem de ser uma string.```js
“use strict“;
var BankVaultContract = function () {};
BankVaultContract.prototype = {
init: function () {},
game:function(subscript, myseed){
var arr =[1,2,3,4,5,6,7,8,9,10,11,12,13];
console.log(Math.random());
for(var i = 0;i < arr.length; i++){
if (i == 8) {
// reset random seed with `myseed`
Math.random.seed(myseed);
}
var rand = parseInt(Math.random()*arr.length);
var t = arr[rand];
arr[rand] =arr[i];
arr[i] = t;
}
return arr[parseInt(subscript)];
},
};
module.exports = BankVaultContract;
### Date
```js
"use strict";
var BankVaultContract = function () {};
BankVaultContract.prototype = {
init: function () {},
test: function(){
var d = new Date();
return d.toString();
}
};
module.exports = BankVaultContract;
Tips:
- Métodos não suportados:
toDateString()
,toTimeString()
,getTimezoneOffset()
,toLocaleXXX()
. new Date()
/Date.now()
returna a marca temporal do bloco actual em milisegundos.getXXX
returna o resultado degetUTCXXX
.
accept¶
Este método visa tornar possível o envio de uma transferência binária para a conta do contracto. Como to
é o endereço do smart contract que foi declarado na função accept()
e executou correctamente, a transferência irá ser completada com sucesso. Se a transferência não for binária, será tratada como uma função normal.
"use strict";
var DepositeContent = function (text) {
if(text){
var o = JSON.parse(text);
this.balance = new BigNumber(o.balance);//余额信息
this.address = o.address;
}else{
this.balance = new BigNumber(0);
this.address = "";
}
};
DepositeContent.prototype = {
toString: function () {
return JSON.stringify(this);
}
};
var BankVaultContract = function () {
LocalContractStorage.defineMapProperty(this, "bankVault", {
parse: function (text) {
return new DepositeContent(text);
},
stringify: function (o) {
return o.toString();
}
});
};
BankVaultContract.prototype = {
init: function () {},
save: function () {
var from = Blockchain.transaction.from;
var value = Blockchain.transaction.value;
value = new BigNumber(value);
var orig_deposit = this.bankVault.get(from);
if (orig_deposit) {
value = value.plus(orig_deposit.balance);
}
var deposit = new DepositeContent();
deposit.balance = new BigNumber(value);
deposit.address = from;
this.bankVault.put(from, deposit);
},
accept:function(){
this.save();
Event.Trigger("transfer", {
Transfer: {
from: Blockchain.transaction.from,
to: Blockchain.transaction.to,
value: Blockchain.transaction.value,
}
});
}
};
module.exports = BankVaultContract;