Skip to content

Commit

Permalink
Merge pull request #275 from AschPlatform/v1.5.0/#277
Browse files Browse the repository at this point in the history
V1.5.0/#277
  • Loading branch information
sqfasd committed Oct 18, 2018
2 parents 18719ec + 6523e30 commit 383bb6c
Show file tree
Hide file tree
Showing 9 changed files with 420 additions and 26 deletions.
14 changes: 6 additions & 8 deletions build.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ const os = require('os')
const buildTime = moment().format('HH:mm:ss DD/MM/YYYY')

const serverUrls = {
'localnet': 'http://localhost:4096',
'mainnet': 'http://mainnet.asch.cn',
'testnet': 'http://testnet.asch.io'
localnet: 'http://localhost:4096',
mainnet: 'http://mainnet.asch.cn',
testnet: 'http://testnet.asch.io',
}

function build(osVersion, netVersion) {
Expand Down Expand Up @@ -44,8 +44,7 @@ function build(osVersion, netVersion) {
if (osVersion === 'darwin') {
// macOS using *bsd sed which doesn't support --version option
// and user may install gnu-sed to replace it.
if (shell.exec(`sed --version`, { silent: true }).code != 0)
sedi = 'sed -i \'\''
if (shell.exec('sed --version', { silent: true }).code !== 0) { sedi = 'sed -i \'\'' }
}

shell.cp('-r', 'app.js', 'src', fullPath)
Expand All @@ -58,7 +57,7 @@ function build(osVersion, netVersion) {
let frontendRepo = 'https://github.com/AschPlatform/asch-frontend-2.git'
if (process.env.ASCH_FRONTEND_REPO != null) {
frontendRepo = process.env.ASCH_FRONTEND_REPO
console.log("Using Frontend Repo:", frontendRepo)
console.log('Using Frontend Repo:', frontendRepo)
} else {
console.log('PS, you may use env ASCH_FRONTEND_REPO to use a faster repo ...')
}
Expand All @@ -74,8 +73,7 @@ function build(osVersion, netVersion) {
// prepare frontend source code
const magic = shell.exec('grep magic config.json | awk \'{print $2}\' | sed -e \'s/[",]//g\'', { silent: true }).stdout.trim()
let branch = shell.exec('git branch | grep \\* | cut -d \' \' -f2', { silent: true }).stdout.trim()
if (branch !== 'master' && branch !== 'develop' )
branch = 'develop'
if (branch !== 'master' && branch !== 'develop') { branch = 'develop' }
// It is quite possible that last build stop before cleanup frontend files
if (shell.test('-e', `${fullPath}/tmp/asch-frontend-2`)) {
shell.rm('-rf', `${fullPath}/tmp/asch-frontend-2`, { silent: true })
Expand Down
228 changes: 228 additions & 0 deletions src/contract/contract.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
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

function require(condition, error) {
if (!condition) throw Error(error)
}

function makeContractAddress(transId, ownerAddress) {
return app.util.address.generateContractAddress(`${transId}_${ownerAddress}`)
}

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} ) `)
} else {
require(bchAvalible.gte(amount), `Avalible gas( ${bchAvalible} ) is less than gas limit( ${amount} ) `)
}
}

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 ')
}

function ensureGasLimitValid(gasLimit) {
require(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) {
app.sdb.create(CONTRACT_TRANSFER_MODEL, {
id: Number(app.autoID.increment(CONTRACT_TRANSFER_ID_SEQUENCE)),
tid: trans.id,
height,
senderId,
recipientId,
currency,
amount: String(amount),
timestamp: trans.timestamp,
})
}

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')

app.balances.transfer(currency, bigAmount.toString(), senderId, recipientId)
createContractTransfer(senderId, recipientId, currency, bigAmount.toString(), trans, height)
return
}

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')

app.sdb.increase(ACCOUNT_MODEL, { xas: -amount }, { address: senderId })
recipientAccount = await app.sdb.load(ACCOUNT_MODEL, { address: recipientId })
if (recipientAccount !== undefined) {
app.sdb.increase(ACCOUNT_MODEL, { xas: amount }, { address: recipientId })
} else {
recipientAccount = app.sdb.create(ACCOUNT_MODEL, {
address: recipientId,
xas: amount,
name: null,
})
}
createContractTransfer(senderId, recipientId, currency, amount, trans, height)
}


async function handleContractResult(senderId, contractId, contractAddr, callResult, trans, height) {
const {
success, error, gas, stateChangesHash,
} = callResult

app.sdb.create(CONTRACT_RESULT_MODEL, {
tid: trans.id,
contractId,
success: success ? 1 : 0,
error,
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.recipientId, 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}
*/
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) {
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 ')

await ensureBCHEnough(this.sender.address, gasLimit, true)
const contract = await app.sdb.load(CONTRACT_MODEL, { name })
require(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,
)
const contractAddress = makeContractAddress(this.trs.id, this.sender.address)
handleContractResult(
this.sender.address, contractId, contractAddress, registerResult,
this.trs, this.block.height,
)

if (registerResult.success) {
app.sdb.create(CONTRACT_MODEL, {
id: contractId,
tid: this.trs.id,
name,
owner: this.sender.address,
address: contractAddress,
vmVersion: version,
desc,
code,
metadata: registerResult.metadata,
timestamp: this.trs.timestamp,
})
}
},

/**
* Call method of a registered contract
* @param {number} gasLimit max gas avalible, 1000000 >= gasLimit >0
* @param {string} name contract name
* @param {string} method method name of contract
* @param {Array} args method arguments
*/
async call(gasLimit, 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')

const contractInfo = await app.sdb.get(CONTRACT_MODEL, { name })
require(contractInfo !== undefined, `Contract '${name}' not found`)
await ensureBCHEnough(this.sender.address, gasLimit, true)

const context = makeContext(this.sender.address, this.trs, this.block)
const callResult = await app.contract.callContract(gasLimit, context, name, method, ...args)

handleContractResult(
this.sender.address, contractInfo.id, contractInfo.address, callResult,
this.trs, this.block.height,
)
},

/**
* 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 {string|number} amount pay amout
* @param {string} currency currency
*/
async pay(gasLimit, nameOrAddress, amount, currency) {
ensureGasLimitValid(gasLimit)
const bigAmount = app.util.bignumber(amount)
require(bigAmount.gt(0), 'Invalid amount, should be greater than 0 ')

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`)

const isBCH = (currency === GAS_CURRENCY)
const miniAmount = app.util.bignumber(gasLimit).plus(isBCH ? bigAmount : 0)
await ensureBCHEnough(this.sender.address, miniAmount, isBCH)

await transfer(
currency, bigAmount.toString(), this.sender.address, contractInfo.address,
this.trs, this.block.height,
)

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,
)
handleContractResult(
this.sender.address, contractInfo.id, contractInfo.address, payResult,
this.trs, this.block.height,
)
},
}

6 changes: 4 additions & 2 deletions src/contract/exchange.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,10 @@ module.exports = {
const senderId = this.sender.address
const bancor = await app.util.bancor
.create(bancorInfo.money, bancorInfo.stock, bancorInfo.owner)
const simulateResult = await bancor.exchangeByTarget(sourceCurrency,
targetCurrency, targetAmount, false)
const simulateResult = await bancor.exchangeByTarget(
sourceCurrency,
targetCurrency, targetAmount, false,
)
// Check source account has sufficient balance to handle the exchange
if (sourceCurrency === 'XAS') {
if (simulateResult.sourceAmount.gt(app.util.bignumber(String(this.sender.xas)))) return 'Insufficient balance'
Expand Down
6 changes: 4 additions & 2 deletions src/contract/gateway.js
Original file line number Diff line number Diff line change
Expand Up @@ -306,8 +306,10 @@ module.exports = {
app.sdb.increase('Account', { xas: -needClaim }, { address: lockedAddr })
app.sdb.increase('Account', { xas: needClaim }, { address: senderId })
}
app.balances.transfer(gwCurrency[0].symbol, userAmount,
senderId, app.storeClaimedAddr)
app.balances.transfer(
gwCurrency[0].symbol, userAmount,
senderId, app.storeClaimedAddr,
)
} else {
return 'Gateway was not revoked'
}
Expand Down
Loading

0 comments on commit 383bb6c

Please sign in to comment.