diff --git a/CONTRACT.md b/CONTRACT.md index 64841f3..5bf745a 100644 --- a/CONTRACT.md +++ b/CONTRACT.md @@ -159,7 +159,7 @@ const transaction = new Transaction("some contract address", amount, gas, { }); ``` -Note that all args should be numbers and they will be converted to hex strings in execution. +Note that all args should be strings that either represent a hex string or a decimal number, and they will be converted to hex strings in execution. ## Example diff --git a/package.json b/package.json index 71f3d93..938b592 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jechain", - "version": "0.24.0", + "version": "0.25.0", "description": "Node for JeChain - an experimental smart contract blockchain network", "main": "./index.js", "scripts": { diff --git a/src/config.json b/src/config.json index f2f5749..68bbd3b 100644 --- a/src/config.json +++ b/src/config.json @@ -4,5 +4,9 @@ "BLOCK_GAS_LIMIT": "50000000000000", "INITIAL_SUPPLY": "100000000000000000000000000", "EMPTY_HASH": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", - "FIRST_ACCOUNT": "52472d59e3c01bc2cf9496c959d924ce5f469d4e097c395f5605f70633e44a28" + "FIRST_ACCOUNT": "52472d59e3c01bc2cf9496c959d924ce5f469d4e097c395f5605f70633e44a28", + "CONTRACT_FLAG": { + "DEPLOY": 0, + "CALL": 1 + } } diff --git a/src/core/runtime.js b/src/core/runtime.js index fbda1f3..6ce82cf 100644 --- a/src/core/runtime.js +++ b/src/core/runtime.js @@ -14,7 +14,7 @@ async function jelscript(input, originalState = {}, gas, stateDB, block, txInfo, const memory = {}, state = { ...originalState }, storage = {}; - const userArgs = typeof txInfo.additionalData.txCallArgs !== "undefined" ? txInfo.additionalData.txCallArgs.map(arg => "0x" + arg.toString(16)) : []; + const userArgs = typeof txInfo.additionalData.txCallArgs !== "undefined" ? txInfo.additionalData.txCallArgs.map(arg => "0x" + BigInt(arg).toString(16)) : []; let ptr = 0; diff --git a/src/core/transaction.js b/src/core/transaction.js index 77b6a00..8b9ee1d 100644 --- a/src/core/transaction.js +++ b/src/core/transaction.js @@ -5,7 +5,7 @@ const { isNumber } = require("../utils/utils"); const crypto = require("crypto"), SHA256 = message => crypto.createHash("sha256").update(message).digest("hex"); const EC = require("elliptic").ec, ec = new EC("secp256k1"); -const { EMPTY_HASH } = require("../config.json"); +const { EMPTY_HASH, CONTRACT_FLAG } = require("../config.json"); class Transaction { constructor(recipient = "", amount = "0", gas = "1000000000000", additionalData = {}, nonce = 0) { @@ -49,15 +49,39 @@ class Transaction { tx.signature.v.padStart(2, "0"); // Additional data - txHexString += Buffer.from(JSON.stringify(tx.additionalData), 'utf8').toString('hex'); + if (typeof tx.additionalData.scBody === "string") { + let scBodyHex = Buffer.from(tx.additionalData.scBody, "utf8").toString("hex"); + + if (scBodyHex.length % 2 !== 0) { scBodyHex = "0" + scBodyHex }; + + txHexString += "00" + scBodyHex; + } else if (typeof tx.additionalData.contractGas === "string") { + txHexString += "01" + BigInt(tx.additionalData.contractGas).toString(16).padStart(22, "0"); + + if (Array.isArray(tx.additionalData.txCallArgs)) { + for (const arg of tx.additionalData.txCallArgs) { + let newArg = BigInt(arg).toString(16); + + if (newArg.length % 2 !== 0) { newArg = "0" + newArg; } + + // Offset for knowing arg's size + txHexString += Math.floor(newArg.length / 2).toString(16).padStart(8, "0"); + + // The arg itself + txHexString += newArg; + } + } + } else { + txHexString += "02"; + } return new Array(...Buffer.from(txHexString, "hex")); } - static deserialize(tx) { + static deserialize(tx) { let txHexString = Buffer.from(tx).toString("hex"); - const txObj = { signature: {} }; + const txObj = { signature: {}, additionalData: {} }; txObj.recipient = txHexString.slice(0, 64); txHexString = txHexString.slice(64); @@ -80,7 +104,27 @@ class Transaction { txObj.signature.v = txHexString.slice(0, 2); txHexString = txHexString.slice(2); - txObj.additionalData = JSON.parse(Buffer.from(txHexString, 'hex').toString('utf8')); + const contractFlag = parseInt("0x" + txHexString.slice(0, 2)); + txHexString = txHexString.slice(2); + + if (contractFlag === CONTRACT_FLAG.DEPLOY) { + txObj.additionalData.scBody = Buffer.from(txHexString, "hex").toString("utf8"); + } else if (contractFlag === CONTRACT_FLAG.CALL) { + txObj.additionalData.contractGas = BigInt("0x" + txHexString.slice(0, 22)).toString(); + txHexString = txHexString.slice(22); + + if (txHexString.length > 0) { + txObj.additionalData.txCallArgs = []; + } + + while (txHexString.length > 0) { + const offset = parseInt(txHexString.slice(0, 8), 16); + txHexString = txHexString.slice(8); + + txObj.additionalData.txCallArgs.push(BigInt("0x" + txHexString.slice(0, offset * 2)).toString()); + txHexString = txHexString.slice(offset * 2); + } + } // Any other flag will make the additional data empty return txObj; } @@ -125,7 +169,7 @@ class Transaction { return ec.keyFromPublic(txSenderPubkey).getPublic("hex"); } - static async isValid(tx, stateDB, check) { + static async isValid(tx, stateDB) { let txSenderPubkey; // If recovering public key fails, then transaction is not valid. @@ -134,8 +178,6 @@ class Transaction { } catch (e) { return false; } - - if (tx.additionalData.contractGas && check) console.log(tx); const txSenderAddress = SHA256(txSenderPubkey); @@ -147,8 +189,6 @@ class Transaction { if (dataFromSender.codeHash !== EMPTY_HASH) return false; } - if (tx.additionalData.contractGas && check) console.log(tx); - return BigInt(tx.amount) >= 0; // Transaction's amount must be at least 0. // We don't check balance here, we will check balance directly in execution