From 47944341c75765565b72b939f79cda4f387830c3 Mon Sep 17 00:00:00 2001 From: liangpeili Date: Tue, 28 Aug 2018 19:55:08 +0800 Subject: [PATCH 01/12] hotfix: change IPS node and fix some typos --- aschd | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/aschd b/aschd index b8c6208..a9979ee 100755 --- a/aschd +++ b/aschd @@ -3,10 +3,10 @@ readonly PROG_DIR=$(pwd) readonly PID_FILE=$PROG_DIR/asch.pid readonly IPS=( - "https://www.asch.io/downloads-International" - "http://47.75.26.122/-Hongkong" - "http://125.32.110.138:9999/downloads-China" - "http://39.107.52.143-China" + "http://china.aschcdn.com-China" + "http://asia.aschcdn.com-Asia" + "http://america.aschcdn.com-America" + "http://europe.aschcdn.com-Europe" ) function read_port() { @@ -27,7 +27,7 @@ function status() { function start() { if is_running; then - echo "Asch server is already started" + echo "Asch server has already started" else rm -f $PROG_DIR/asch.pid node $PROG_DIR/app.js --base $PROG_DIR --daemon $@ @@ -64,23 +64,27 @@ function restart() { start } -function ismainnet(){ +function getnettype(){ magic=$(cat $PROG_DIR/config.json | grep magic | awk -F: '{print $2}' | cut -d \" -f2) - net="mainnet" + if [ "$magic" = "594fe0f3" ]; then + net="testnet" + elif [ "$magic" = "5f5b3cf5" ]; then + net="mainnet" + fi echo "net is $net" } -function chosenode(){ +function choosenode(){ read -p "Please input your choice [default 0] : " num num=${num:-0} if [ $num -ge $length ];then - echo "You chosen wrong number,please run aschd again and chose the other node!" + echo "Invalid number, please run aschd again and choose another node!" exit 1 fi if [ i${status[$num]} = i"N" ];then - echo "This node has no data,please run aschd again and chose the other node!" + echo "This node has no data,please run aschd again and choose another node!" exit 2 fi @@ -91,7 +95,7 @@ function chosenode(){ } function rebuild() { - ismainnet + getnettype meta_file=metadata_rebuild_$net.txt echo "Please select one of the following sources to use" @@ -124,7 +128,7 @@ function rebuild() { fi done - chosenode + choosenode echo "Downloading blockchain snapshot $Name..." /bin/rm $Name @@ -163,7 +167,7 @@ function configure() { } function upgrade() { - ismainnet + getnettype meta_file=metadata_upgrade_$net.txt echo "Please select one of the following sources to use" @@ -198,7 +202,7 @@ function upgrade() { done # echo "status",${status[*]} - chosenode + choosenode mkdir -p tmp wget "$ip/$Name" -O tmp/$Name From 80818d65b58ee050bc8798cd394a677f0202c841 Mon Sep 17 00:00:00 2001 From: liangpeili Date: Wed, 29 Aug 2018 10:42:15 +0800 Subject: [PATCH 02/12] release: change dependencies of asch-core --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bf73d17..8648c5a 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ }, "author": "Qingfeng Shan ", "dependencies": { - "asch-core": "https://github.com/AschPlatform/asch-core/tarball/master", + "asch-core": "https://github.com/AschPlatform/asch-core/tarball/v1.4.4", "commander": "=2.6.0", "daemon": "=1.1.0", "ip": "=1.1.3", From c071abfeb88af9efe9ef8931957017196a7eaf39 Mon Sep 17 00:00:00 2001 From: liangpeili Date: Fri, 14 Sep 2018 15:41:43 +0800 Subject: [PATCH 03/12] bugfix: fix can not deposit to a dapp if its xas balance equals 0 and registered before 1.4 --- src/contract/chain.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/contract/chain.js b/src/contract/chain.js index d444a41..cda3b02 100644 --- a/src/contract/chain.js +++ b/src/contract/chain.js @@ -72,6 +72,9 @@ module.exports = { if (sender.xas < amount) return 'Insufficient balance' sender.xas -= amount + let exists = await app.sdb.exists('Account', { address: chainAddress }) + if (!exists) { app.sdb.create('Account', { address: chainAddress, xas: 0, name: null }) } + const chainAccount = await app.sdb.load('Account', chain.address) chainAccount.xas += amount app.sdb.update('Account', { xas: sender.xas }, { address: sender.address }) From 7eb30f74cd0962e1b3e4bf852b3066d4cfbc4310 Mon Sep 17 00:00:00 2001 From: liangpeili Date: Wed, 26 Sep 2018 17:32:03 +0800 Subject: [PATCH 04/12] chore: change asch-core dependency from v1.4.4 to v1.4.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8648c5a..ab8d713 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ }, "author": "Qingfeng Shan ", "dependencies": { - "asch-core": "https://github.com/AschPlatform/asch-core/tarball/v1.4.4", + "asch-core": "https://github.com/AschPlatform/asch-core/tarball/v1.4.5", "commander": "=2.6.0", "daemon": "=1.1.0", "ip": "=1.1.3", From f8cdc8dec2ce37eeb3e264d6c23315f07d18311a Mon Sep 17 00:00:00 2001 From: liangpeili Date: Wed, 26 Sep 2018 17:36:19 +0800 Subject: [PATCH 05/12] chore: change version from v1.4.4 to v1.4.5 --- app.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app.js b/app.js index 9fae8e2..6535be0 100755 --- a/app.js +++ b/app.js @@ -12,7 +12,7 @@ const Application = asch.Application function main() { process.stdin.resume() - const version = '1.4.4' + const version = '1.4.5' program .version(version) .option('-c, --config ', 'Config file path') diff --git a/package.json b/package.json index ab8d713..2ed724d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "asch", - "version": "1.4.4", + "version": "1.4.5", "private": true, "scripts": { "start": "node app.js", From 6874605585a177c9b5b9f3a455d8fbc1d2ef5814 Mon Sep 17 00:00:00 2001 From: liangpeili Date: Fri, 28 Sep 2018 18:44:37 +0800 Subject: [PATCH 06/12] fix: fix bug of cannot find chainAddress error --- src/contract/chain.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/contract/chain.js b/src/contract/chain.js index cda3b02..48b73f5 100644 --- a/src/contract/chain.js +++ b/src/contract/chain.js @@ -72,8 +72,8 @@ module.exports = { if (sender.xas < amount) return 'Insufficient balance' sender.xas -= amount - let exists = await app.sdb.exists('Account', { address: chainAddress }) - if (!exists) { app.sdb.create('Account', { address: chainAddress, xas: 0, name: null }) } + let exists = await app.sdb.exists('Account', { address: chain.address }) + if (!exists) { app.sdb.create('Account', { address: chain.address, xas: 0, name: null }) } const chainAccount = await app.sdb.load('Account', chain.address) chainAccount.xas += amount From d46e001fe45db41cb054a939b68f265b45569562 Mon Sep 17 00:00:00 2001 From: eagleHovering <55476243@qq.com> Date: Thu, 29 Nov 2018 15:04:10 +0800 Subject: [PATCH 07/12] chore: upgrade version to 1.4.6 --- package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 2ed724d..37d86b4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "asch", - "version": "1.4.5", + "version": "1.4.6", "private": true, "scripts": { "start": "node app.js", @@ -8,7 +8,8 @@ }, "author": "Qingfeng Shan ", "dependencies": { - "asch-core": "https://github.com/AschPlatform/asch-core/tarball/v1.4.5", + "asch-core": "https://github.com/AschPlatform/asch-core/tarball/v1.4.6", + "bitcoincashjs": "=0.1.14", "commander": "=2.6.0", "daemon": "=1.1.0", "ip": "=1.1.3", From 863a9ca2e4e4132fcb5dfa158efc8b87c8960525 Mon Sep 17 00:00:00 2001 From: eagleHovering Date: Tue, 4 Dec 2018 14:16:23 +0800 Subject: [PATCH 08/12] fix(app): new version 1.4.6 --- app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.js b/app.js index 6535be0..e3801a0 100755 --- a/app.js +++ b/app.js @@ -12,7 +12,7 @@ const Application = asch.Application function main() { process.stdin.resume() - const version = '1.4.5' + const version = '1.4.6' program .version(version) .option('-c, --config ', 'Config file path') From 0d2cc5a373c7b48ca93fdac1cae969ab7aca5de5 Mon Sep 17 00:00:00 2001 From: liangpeili Date: Thu, 27 Dec 2018 20:24:04 +0800 Subject: [PATCH 09/12] chore: remove useless peer node and change version --- app.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app.js b/app.js index e3801a0..aeba286 100755 --- a/app.js +++ b/app.js @@ -12,7 +12,7 @@ const Application = asch.Application function main() { process.stdin.resume() - const version = '1.4.6' + const version = '1.4.7' program .version(version) .option('-c, --config ', 'Config file path') @@ -92,8 +92,7 @@ function main() { 1758431015, 1760474482, 1760474149, - 759110497, - 757134616, + 759110497 ] for (let i = 0; i < seeds.length; ++i) { appConfig.peers.list.push({ ip: ip.fromLong(seeds[i]), port: 81 }) From 8910fa33d6d4344c35bd6906165e6c1b7eb20e4f Mon Sep 17 00:00:00 2001 From: liangpeili Date: Thu, 27 Dec 2018 20:24:18 +0800 Subject: [PATCH 10/12] chore: change version --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 37d86b4..56bc6e3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "asch", - "version": "1.4.6", + "version": "1.4.7", "private": true, "scripts": { "start": "node app.js", @@ -8,7 +8,7 @@ }, "author": "Qingfeng Shan ", "dependencies": { - "asch-core": "https://github.com/AschPlatform/asch-core/tarball/v1.4.6", + "asch-core": "https://github.com/AschPlatform/asch-core/tarball/v1.4.7", "bitcoincashjs": "=0.1.14", "commander": "=2.6.0", "daemon": "=1.1.0", From 4df26d1adb5b4a10d978b2d0f398346432f2d2dc Mon Sep 17 00:00:00 2001 From: eagleHovering <55476243@qq.com> Date: Mon, 14 Jan 2019 19:47:09 +0800 Subject: [PATCH 11/12] feat(contract): consume Energy for smart contract --- data/.gitkeep | 0 src/contract/contract.js | 206 +++++++++++++++++++++------------ src/interface/contracts.js | 73 ++++++++---- src/model/contract-transfer.js | 2 +- src/model/contract.js | 3 +- 5 files changed, 188 insertions(+), 96 deletions(-) delete mode 100644 data/.gitkeep diff --git a/data/.gitkeep b/data/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/contract/contract.js b/src/contract/contract.js index 79d2167..e7d5ecd 100644 --- a/src/contract/contract.js +++ b/src/contract/contract.js @@ -1,16 +1,17 @@ const CONTRACT_ID_SEQUENCE = 'contract_sequence' const CONTRACT_TRANSFER_ID_SEQUENCE = 'contract_transfer_sequence' -const GAS_CURRENCY = 'BCH' const XAS_CURRENCY = 'XAS' const CONTRACT_MODEL = 'Contract' const CONTRACT_RESULT_MODEL = 'ContractResult' const ACCOUNT_MODEL = 'Account' const CONTRACT_TRANSFER_MODEL = 'ContractTransfer' -const GAS_BUY_BACK_ADDRESS = 'ARepurchaseAddr1234567890123456789' -const PAY_METHOD = 'onPay' -const MAX_GAS_LIMIT = 10000000 // 0.1BCH +const MAX_GAS_LIMIT = 10 ** 7 +const MAX_TIMEOUT = 500 // 0.5s +const MIN_TIMEOUT = 100 // 0.1s +const INFINITE_TIMEOUT = 10 * 1000 // 10s +let pledge = app.util.pledges -function require(condition, error) { +function assert(condition, error) { if (!condition) throw Error(error) } @@ -22,22 +23,58 @@ function makeContext(senderAddress, transaction, block) { return { senderAddress, transaction, block } } -async function ensureBCHEnough(address, amount, gasOnly) { - const bchAvalible = app.balances.get(address, GAS_CURRENCY) - if (!gasOnly) { - require(bchAvalible.gte(amount), `Avalible BCH( ${bchAvalible} ) is less than required( ${amount} ) `) +async function gasToEnergy(gas) { + return await pledge.getEnergyByGas(gas) +} + +async function gasToXAS(gas) { + return await pledge.getXASByGas(gas) +} + + +async function checkGasPayment( preferredEnergyAddress, address, gasLimit, useXAS) { + const blockHeight = modules.blocks.getLastBlock().height + 1 + + if (preferredEnergyAddress !== undefined) { + perferedEnergyEnough = await pledge.isEnergyCovered(gasLimit, preferredEnergyAddress, blockHeight) + if (perferedEnergyEnough) { + return { enough: true, energy: true, payer: preferredEnergyAddress} + } + } + + const energyEnough = await pledge.isEnergyCovered(gasLimit, address, blockHeight) + if (energyEnough ){ + return { enough: true, energy: true , payer: address} + } + + if (!useXAS) return { enough: false } + + const senderAccount = await app.sdb.load(ACCOUNT_MODEL, { address }) + if (!senderAccount || !senderAccount.xas ) return { enough: false } + + const xas = gasToXAS(gasLimit) + const enough = xas <= senderAccount.xas + return { enough, energy: false, xas, payer: address } +} + +async function payGas( gas, useEnergy, payer, tid ) { + const blockHeight = modules.blocks.getLastBlock().height + 1 + const payAmount = useEnergy ? await gasToEnergy(gas) : await gasToXAS(gas) + + if (useEnergy) { + await pledge.consumeEnergy(payAmount, payer, blockHeight, tid) } else { - require(bchAvalible.gte(amount), `Avalible gas( ${bchAvalible} ) is less than gas limit( ${amount} ) `) + await pledge.consumeGasFee(payAmount, payer, blockHeight, tid) } } function ensureContractNameValid(name) { - require(name && name.length >= 3 && name.length <= 32, 'Invalid contract name, length should be between 3 and 32 ') - require(name.match(/^[a-zA-Z]([-_a-zA-Z0-9]{3,32})+$/), 'Invalid contract name, please use letter, number or underscore ') + assert(name && name.length >= 3 && name.length <= 32, 'Invalid contract name, length should be between 3 and 32 ') + assert(name.match(/^[a-zA-Z]([-_a-zA-Z0-9]{3,32})+$/), 'Invalid contract name, please use letter, number or underscore ') } function ensureGasLimitValid(gasLimit) { - require(gasLimit > 0 && gasLimit <= MAX_GAS_LIMIT, `gas limit must greater than 0 and less than ${MAX_GAS_LIMIT}`) + assert(gasLimit > 0 && gasLimit <= MAX_GAS_LIMIT, `gas limit must greater than 0 and less than ${MAX_GAS_LIMIT}`) } function createContractTransfer(senderId, recipientId, currency, amount, trans, height) { @@ -53,11 +90,20 @@ function createContractTransfer(senderId, recipientId, currency, amount, trans, }) } +function getTimeout(gasLimit) { + if ( modules.blocks.isApplyingBlock() ) { + return INFINITE_TIMEOUT + } + + const timeout = Math.round((gasLimit / MAX_GAS_LIMIT) * MAX_TIMEOUT) + return Math.max(MIN_TIMEOUT, Math.min(MAX_TIMEOUT, timeout)) +} + async function transfer(currency, transferAmount, senderId, recipientId, trans, height) { const bigAmount = app.util.bignumber(transferAmount) if (currency !== XAS_CURRENCY) { const balance = app.balances.get(senderId, currency) - require(balance !== undefined && balance.gte(bigAmount), 'Insuffient balance') + assert(balance !== undefined && balance.gte(bigAmount), `Insuffient ${currency} to transfer `) app.balances.transfer(currency, bigAmount.toString(), senderId, recipientId) createContractTransfer(senderId, recipientId, currency, bigAmount.toString(), trans, height) @@ -66,8 +112,8 @@ async function transfer(currency, transferAmount, senderId, recipientId, trans, const amount = Number.parseInt(bigAmount.toString(), 10) const senderAccount = await app.sdb.load(ACCOUNT_MODEL, { address: senderId }) - require(senderAccount !== undefined, 'Sender account not found') - require(senderAccount.xas >= amount, 'Insuffient balance') + assert(senderAccount !== undefined, 'Sender account not found') + assert(senderAccount.xas >= amount, `Insuffient XAS to tranfer`) app.sdb.increase(ACCOUNT_MODEL, { xas: -amount }, { address: senderId }) recipientAccount = await app.sdb.load(ACCOUNT_MODEL, { address: recipientId }) @@ -84,65 +130,65 @@ async function transfer(currency, transferAmount, senderId, recipientId, trans, } -async function handleContractResult(senderId, contractId, contractAddr, callResult, trans, height) { - const { - success, error, gas, stateChangesHash, - } = callResult +async function handleContractResult(contractId, contractAddr, callResult, trans, height, useEnergy, payer) { + const { success, error, gas, stateChangesHash } = callResult + await payGas(gas || 0, useEnergy, payer, trans.id) + + const shortError = error ? String(error).substr(0, 127) : '' app.sdb.create(CONTRACT_RESULT_MODEL, { tid: trans.id, contractId, success: success ? 1 : 0, - error, + error: shortError, gas, stateChangesHash, }) - if (callResult.gas && callResult.gas > 0) { - await transfer(GAS_CURRENCY, callResult.gas, senderId, GAS_BUY_BACK_ADDRESS, trans, height) - } - if (callResult.transfers && callResult.transfers.length > 0) { for (const t of callResult.transfers) { - await transfer(t.currency, t.amount, contractAddr, t.recipientAddress, trans, height) + await transfer(t.currency, String(t.amount), contractAddr, t.recipientAddress, trans, height) } } + } /** * Asch smart contract service code. All functions return transaction id by asch-core , - * you can get result by api/v2/contracts/?action=getResult&tid={transactionId} + * but you can get result by api/v2/contracts/?action=getResult&tid={transactionId} */ module.exports = { /** - * Register contract, - * @param {number} gasLimit max gas avalible, 1000000 >= gasLimit >0 - * @param {string} name 32 >= name.length > 3 and name must be letter, number or _ - * @param {string} version contract engine version - * @param {string} desc desc.length <= 255 - * @param {string} code hex encoded source code - */ - async register(gasLimit, name, version, desc, code) { + * Register contract, + * @param {number} gasLimit max gas avalible, 1000000 >= gasLimit >0 + * @param {string} name 32 >= name.length >= 3 and name must be letter, number or '_' + * @param {string} version contract engine version + * @param {string} desc desc.length <= 255 + * @param {string} code contract source code + * @param {boolean} consumeOwnerEnergy prefer to consume contract owner energy for gas + */ + async register(gasLimit, name, version, desc, code, consumeOwnerEnergy) { ensureGasLimitValid(gasLimit) ensureContractNameValid(name) - require(!desc || desc.length <= 255, 'Invalid description, can not be longer than 255') - require(!version || version.length <= 32, 'Invalid version, can not be longer than 32 ') + assert(!desc || desc.length <= 255, 'Invalid description, can not be longer than 255') + assert(!version || version.length <= 32, 'Invalid version, can not be longer than 32 ') + + const checkResult = await checkGasPayment(undefined, this.sender.address, gasLimit, true) + assert( checkResult.enough, 'Insuffient energy') - await ensureBCHEnough(this.sender.address, gasLimit, true) const contract = await app.sdb.load(CONTRACT_MODEL, { name }) - require(contract === undefined, `Contract '${name}' exists already`) + assert(contract === undefined, `Contract '${name}' exists already`) const contractId = Number(app.autoID.increment(CONTRACT_ID_SEQUENCE)) const context = makeContext(this.sender.address, this.trs, this.block) - const decodedCode = Buffer.from(code, 'hex').toString('utf8') const registerResult = await app.contract.registerContract( - gasLimit, context, - contractId, name, decodedCode, + gasLimit, getTimeout(gasLimit), context, + contractId, name, code, ) const contractAddress = makeContractAddress(this.trs.id, this.sender.address) handleContractResult( - this.sender.address, contractId, contractAddress, registerResult, - this.trs, this.block.height, + contractId, contractAddress, registerResult, this.trs, + this.block.height, checkResult.energy, checkResult.payer ) if (registerResult.success) { @@ -150,12 +196,13 @@ module.exports = { id: contractId, tid: this.trs.id, name, - owner: this.sender.address, + ownerId: this.sender.address, address: contractAddress, vmVersion: version, desc, code, - metadata: registerResult.metadata, + consumeOwnerEnergy: consumeOwnerEnergy !== false ? 1 : 0, + metadata: registerResult.data, timestamp: this.trs.timestamp, }) } @@ -164,64 +211,81 @@ module.exports = { /** * Call method of a registered contract * @param {number} gasLimit max gas avalible, 1000000 >= gasLimit >0 + * @param {boolean} enablePayGasInXAS pay gas in XAS if energy is insuffient * @param {string} name contract name * @param {string} method method name of contract * @param {Array} args method arguments */ - async call(gasLimit, name, method, args) { + async call(gasLimit, enablePayGasInXAS, name, method, args) { ensureGasLimitValid(gasLimit) ensureContractNameValid(name) - require(method !== undefined && method !== null, 'method name can not be null or undefined') - require(Array.isArray(args), 'Invalid contract args, should be array') + assert(method !== undefined && method !== null, 'method name can not be null or undefined') + assert(Array.isArray(args), 'Invalid contract args, should be array') const contractInfo = await app.sdb.load(CONTRACT_MODEL, { name }) - require(contractInfo !== undefined, `Contract '${name}' not found`) - await ensureBCHEnough(this.sender.address, gasLimit, true) + assert(contractInfo !== undefined, `Contract '${name}' not found`) + const preferredEnergyAddress = contractInfo.consumeOwnerEnergy ? contractInfo.ownerId : undefined + + const checkResult = await checkGasPayment(preferredEnergyAddress, this.sender.address, gasLimit, enablePayGasInXAS) + assert(checkResult.enough, 'Insuffient Energy') const context = makeContext(this.sender.address, this.trs, this.block) - const callResult = await app.contract.callContract(gasLimit, context, name, method, ...args) + const callResult = await app.contract.callContract(gasLimit, getTimeout(gasLimit), context, name, method, ...args) handleContractResult( - this.sender.address, contractInfo.id, contractInfo.address, callResult, - this.trs, this.block.height, + contractInfo.id, contractInfo.address, callResult, this.trs, + this.block.height, checkResult.energy, checkResult.payer ) }, /** * Pay money to contract, behavior dependents on contract code. * @param {number} gasLimit max gas avalible, 1000000 >= gasLimit >0 - * @param {string} nameOrAddress contract name or address + * @param {boolean} enablePayGasInXAS pay gas in XAS if energy is insuffient + * @param {string} receiverPath contract name or address + '/' + method eg: 'TestContract/onPay' * @param {string|number} amount pay amout * @param {string} currency currency */ - async pay(gasLimit, nameOrAddress, amount, currency) { + async pay(gasLimit, enablePayGasInXAS, receiverPath, amount, currency) { ensureGasLimitValid(gasLimit) const bigAmount = app.util.bignumber(amount) - require(bigAmount.gt(0), 'Invalid amount, should be greater than 0 ') + assert(receiverPath, 'Invalid reciver of contract, should be {nameOrAddress}/{method}') + assert(bigAmount.gt(0), 'Invalid amount, should be greater than 0 ') + let [nameOrAddress, method] = receiverPath.split('/') const condition = app.util.address.isContractAddress(nameOrAddress) ? { address: nameOrAddress } : { name: nameOrAddress } const contractInfo = await app.sdb.load(CONTRACT_MODEL, condition) - require(contractInfo !== undefined, `Contract name/address '${nameOrAddress}' not found`) + assert(contractInfo !== undefined, `Contract name or address '${nameOrAddress}' not found`) - const isBCH = (currency === GAS_CURRENCY) - const miniAmount = app.util.bignumber(gasLimit).plus(isBCH ? bigAmount : 0) - await ensureBCHEnough(this.sender.address, miniAmount, isBCH) + const preferredEnergyAddress = contractInfo.consumeOwnerEnergy ? contractInfo.ownerId : undefined + const checkResult = await checkGasPayment(preferredEnergyAddress, this.sender.address, gasLimit, enablePayGasInXAS) + assert(checkResult.enough, 'Insuffient Energy') - await transfer( - currency, bigAmount.toString(), this.sender.address, contractInfo.address, - this.trs, this.block.height, - ) + if (checkResult.payer === this.sender.address && currency === XAS_CURRENCY) { + const account = await app.sdb.load(ACCOUNT_MODEL, { address: this.sender.address }) + const xasEnought = app.util.bignumber(String(account.xas)).gte(bigAmount.plus(checkResult.xas || 0)) + assert(xasEnought, 'Insuffient XAS for transfer and gas') + } const context = makeContext(this.sender.address, this.trs, this.block) - const payResult = await app.contract.callContract( - gasLimit, context, contractInfo.name, - PAY_METHOD, bigAmount.toString(), currency, + const payResult = await app.contract.payContract( + gasLimit, getTimeout(gasLimit), context, contractInfo.name, + method, bigAmount.toString(), currency, ) + // TODO: Be careful !!! amount of sender and recipient are WRONG if get amount in contract !!! + if (payResult.success) { + await transfer( + currency, bigAmount, this.sender.address, contractInfo.address, + this.trs, this.block.height, + ) + } handleContractResult( - this.sender.address, contractInfo.id, contractInfo.address, payResult, - this.trs, this.block.height, + contractInfo.id, contractInfo.address, payResult, this.trs, + this.block.height, checkResult.useEnergy, checkResult.payer ) - }, + + } + } diff --git a/src/interface/contracts.js b/src/interface/contracts.js index 6fecf37..f6a04c4 100644 --- a/src/interface/contracts.js +++ b/src/interface/contracts.js @@ -1,7 +1,7 @@ const assert = require('assert') const CONTRACT_MODEL = 'Contract' -const CONTRACT_BASIC_FIELDS = ['id', 'name', 'tid', 'address', 'owner', 'vmVersion', 'desc', 'timestamp'] +const CONTRACT_BASIC_FIELDS = ['id', 'name', 'tid', 'address', 'ownerId', 'vmVersion', 'consumeOwnerEnergy', 'desc', 'timestamp'] const CONTRACT_RESULT_MODEL = 'ContractResult' function parseSort(orderBy) { @@ -28,19 +28,17 @@ function makeCondition(params) { */ async function handleGetResult(req) { const tid = req.query.tid - assert(tid !== undefined && tid !== null, 'Invalid param \'tid\', can not be null or undefined') + assert(tid !== undefined && tid !== null,`Invalid param 'tid', can not be null or undefined`) const results = await app.sdb.find(CONTRACT_RESULT_MODEL, { tid }) if (results.length === 0) { throw new Error(`Result not found (tid = '${tid}')`) } const ret = results[0] return { - result: { - success: ret.success > 0, - gas: ret.gas || 0, - error: ret.error || '', - stateChangesHash: ret.stateChangesHash || '', - }, + success: ret.success > 0, + gas: ret.gas || 0, + error: ret.error || '', + stateChangesHash: ret.stateChangesHash || '', } } @@ -54,14 +52,25 @@ async function handleActionRequest(req) { throw new Error(`Invalid action, ${action}`) } +function convertBigintMemberToString(obj) { + Object.keys(obj).forEach(key=>{ + const value = obj[key] + const type = typeof value + if (type === 'bigint') + obj[key] = String(value) + else if (type === 'object') + convertBigintMemberToString(value) + }) +} + module.exports = (router) => { /** * Query contracts * @param condition owner, address, name, orderBy = id:ASC, limit = 20, offset = 0, - * orderBy = (timestamp | id | owner):(ASC|DESC) + * orderBy = (timestamp | id | ownerId):(ASC|DESC) * @returns query result { count, - * contracts : [ { id, name, tid, address, owner, vmVersion, desc, timestamp } ] } + * contracts : [ { id, name, tid, address, ownerId, vmVersion, desc, timestamp } ] } */ router.get('/', async (req) => { if (req.query.action) { @@ -74,15 +83,15 @@ module.exports = (router) => { const orderBy = req.query.orderBy ? req.query.orderBy : 'id:ASC' const sortOrder = parseSort(orderBy) - const { name, owner, address } = req.query - const condition = makeCondition({ name, owner, address }) + const { name, ownerId, address } = req.query + const condition = makeCondition({ name, ownerId, address }) const fields = CONTRACT_BASIC_FIELDS const count = await app.sdb.count(CONTRACT_MODEL, condition) const range = { limit, offset } const contracts = await app.sdb.find(CONTRACT_MODEL, condition, range, sortOrder, fields) - return { count, contracts } + return { success: true, count, contracts } }) @@ -96,7 +105,7 @@ module.exports = (router) => { const name = req.params.name const contracts = await app.sdb.find(CONTRACT_MODEL, { name }) if (!contracts || contracts.length === 0) throw new Error('Not found') - return { contract: contracts[0] } + return { success: true, contract: contracts[0] } }) /** @@ -108,19 +117,37 @@ module.exports = (router) => { */ router.get('/:name/states/:stateName/:key', async (req) => { const { name, stateName, key } = req.params - const state = await app.contract.queryState(name, stateName, key) - return { state } + const result = await app.contract.queryState(name, stateName, key) + convertBigintMemberToString(result) + return result }) /** - * Get state of contract - * @param name name of contract - * @param stateName state name - * @returns state value - */ + * Get state of contract + * @param name name of contract + * @param stateName state name + * @returns state value + */ router.get('/:name/states/:stateName', async (req) => { const { name, stateName } = req.params - const state = await app.contract.queryState(name, stateName) - return { state } + const result = await app.contract.queryState(name, stateName) + convertBigintMemberToString(result) + return result }) + + + /** + * Get state of contract + * @param name name of contract + * @param method constant method name + * @param args stringified arguments, eg: ["name", 323] + * @returns constant method call result + */ + router.get('/:name/constant/:method/:args', async (req) => { + const { name, method, args } = req.params + const methodArgs = JSON.parse(args) + const result = await app.contract.getConstant(name, method, ...methodArgs) + convertBigintMemberToString(result) + return result + }) } diff --git a/src/model/contract-transfer.js b/src/model/contract-transfer.js index 3d94bf0..50dd07b 100644 --- a/src/model/contract-transfer.js +++ b/src/model/contract-transfer.js @@ -7,7 +7,7 @@ module.exports = { { name: 'senderId', type: 'String', length: 50, not_null: true, index: true }, { name: 'recipientId', type: 'String', length: 50, not_null: true, index: true }, { name: 'currency', type: 'String', length: 30, not_null: true, index: true }, - { name: 'amount', type: 'String', length: 50, not_null: true }, + { name: 'amount', type: 'BigInt', length: 50, not_null: true }, { name: 'timestamp', type: 'Number', index: true } ] } diff --git a/src/model/contract.js b/src/model/contract.js index 1702281..d032ee7 100644 --- a/src/model/contract.js +++ b/src/model/contract.js @@ -5,7 +5,8 @@ module.exports = { { name: 'tid', type: 'String', length: 64, not_null: true, unique: true }, { name: 'name', type: 'String', length: 32, not_null: true, unique: true }, { name: 'address', type: 'String', length: 50, unique: true }, - { name: 'owner', type: 'String', length: 50, not_null: true, index: true }, + { name: 'ownerId', type: 'String', length: 50, not_null: true, index: true }, + { name: 'consumeOwnerEnergy', type: 'Number', not_null: true, default: 0 }, { name: 'desc', type: 'String', length: 255 }, { name: 'vmVersion', type: 'String', length: 32 }, { name: 'code', type: 'Text', not_null: true }, From d29e74f484551529046aab27a36a7f80f3f05144 Mon Sep 17 00:00:00 2001 From: eagleHovering <55476243@qq.com> Date: Wed, 20 Feb 2019 16:04:21 +0800 Subject: [PATCH 12/12] chore(contract): return result of contract execution directly --- src/contract/contract.js | 5 ++++- src/interface/contracts.js | 40 ++++++++++++++------------------------ 2 files changed, 19 insertions(+), 26 deletions(-) diff --git a/src/contract/contract.js b/src/contract/contract.js index e7d5ecd..79de437 100644 --- a/src/contract/contract.js +++ b/src/contract/contract.js @@ -151,6 +151,7 @@ async function handleContractResult(contractId, contractAddr, callResult, trans, } } + callResult.transfers = undefined } /** @@ -206,6 +207,7 @@ module.exports = { timestamp: this.trs.timestamp, }) } + return registerResult }, /** @@ -236,6 +238,7 @@ module.exports = { contractInfo.id, contractInfo.address, callResult, this.trs, this.block.height, checkResult.energy, checkResult.payer ) + return callResult }, /** @@ -285,7 +288,7 @@ module.exports = { contractInfo.id, contractInfo.address, payResult, this.trs, this.block.height, checkResult.useEnergy, checkResult.payer ) - + return payResult } } diff --git a/src/interface/contracts.js b/src/interface/contracts.js index f6a04c4..18a6862 100644 --- a/src/interface/contracts.js +++ b/src/interface/contracts.js @@ -53,13 +53,15 @@ async function handleActionRequest(req) { } function convertBigintMemberToString(obj) { - Object.keys(obj).forEach(key=>{ + Object.keys(obj).forEach( key => { const value = obj[key] const type = typeof value - if (type === 'bigint') + if (type === 'bigint') { obj[key] = String(value) - else if (type === 'object') + } + else if (type === 'object') { convertBigintMemberToString(value) + } }) } @@ -97,7 +99,7 @@ module.exports = (router) => { /** * Get contract details - * @param name name of contract + * @param name contract name * @returns contract detail { contract : { id, name, tid, address, owner, vmVersion, * desc, timestamp, metadata } } */ @@ -108,37 +110,25 @@ module.exports = (router) => { return { success: true, contract: contracts[0] } }) - /** - * Get state of contract - * @param name name of contract - * @param stateName name of mapping state - * @param key key of mapping state - * @returns state value - */ - router.get('/:name/states/:stateName/:key', async (req) => { - const { name, stateName, key } = req.params - const result = await app.contract.queryState(name, stateName, key) - convertBigintMemberToString(result) - return result - }) /** * Get state of contract - * @param name name of contract - * @param stateName state name - * @returns state value + * @param name contract name + * @param statePath path of state, separated by '.' , eg: 'holding.0' => contract['holding'][0] + * @returns state value if primitive, else return count of children states */ - router.get('/:name/states/:stateName', async (req) => { - const { name, stateName } = req.params - const result = await app.contract.queryState(name, stateName) + router.get('/:name/states/:statePath', async (req) => { + const { name, statePath } = req.params + if (!statePath) throw new Error(`Invalid state path '${statePath}'`) + + const result = await app.contract.queryState(name, String(statePath).split('.') ) convertBigintMemberToString(result) return result }) - /** * Get state of contract - * @param name name of contract + * @param name contract name * @param method constant method name * @param args stringified arguments, eg: ["name", 323] * @returns constant method call result