├── .gitignore ├── package.json ├── readme.md ├── scrub ├── index.js └── lib │ └── instrumentation.js └── test └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.json~ 2 | *.js~ 3 | *.target 4 | *.Makefile 5 | !nodemsi.sln 6 | npm-debug.log 7 | node_modules 8 | bower_components 9 | Debug 10 | Release 11 | package-lock.json 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sandbox", 3 | "version": "1.1.10", 4 | "description": "Js smart contract vm consume gas", 5 | "license": "", 6 | "main": "index", 7 | "engines": { 8 | "node": ">= 4.0.0" 9 | }, 10 | "scripts": {}, 11 | "dependencies": { 12 | "escodegen": "^2.0.0", 13 | "esprima": "^4.0.1", 14 | "randomstring": "^1.2.3" 15 | }, 16 | "devDependencies": {}, 17 | "keywords": [ 18 | "vm", 19 | "smart contract", 20 | "javascript" 21 | ], 22 | "author": { 23 | "name": "YouJia", 24 | "email": "lifeign@proton.me" 25 | }, 26 | "contributors": [ 27 | { 28 | "name": "YouJia", 29 | "email": "lifeign@proton.me" 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ### Eros 的智能合约机 2 | 3 | [技术白皮书-合约](https://github.com/ErosPlatform/docs/blob/master/eros_whitepaper_zh.md "https://github.com/ErosPlatform/docs/blob/master/eros_whitepaper_zh.md") 4 | -------------------------------------------------------------------------------- /scrub/index.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert') 2 | const randomstring = require('randomstring') 3 | const instrumentation = require('./lib/instrumentation.js') 4 | 5 | // step 代表一个gas单位可以走几步 6 | function scrub(source, params, gas, step) { 7 | assert(typeof source === 'string', 'must be a string') 8 | assert(typeof params === 'string', 'params must be a string') 9 | assert(typeof gas === 'number', 'must be a number') 10 | assert(typeof step === 'number', 'must be a number') 11 | assert(gas > 0, 'gas <= 0') 12 | assert(step > 0, 'step <= 0') 13 | const gasSteps = Math.floor(gas * step) // 代表总共可以执行多少步 14 | const gasDeclaration = `${randomstring.generate({length: 12, charset: 'alphabetic'})}${Date.now()}` 15 | const labelDeclaration = `L${gasDeclaration}` 16 | const resultValue = `_${gasDeclaration}` 17 | var gensource = '' 18 | { 19 | const inSource = `var ${gasDeclaration} = ${gasSteps}; 20 | ${labelDeclaration}: 21 | do { 22 | if(--${gasDeclaration} <= 0){ 23 | throw new Error('gas-has-been-exhausted'); 24 | } 25 | ${source};; 26 | }while(false);` 27 | gensource = instrumentation(inSource, gasDeclaration); 28 | } 29 | 30 | return { 31 | scrubedSource: 32 | `var Atomics=undefined;var uneval=undefined;var Promise=undefined;var window=undefined; 33 | ${gensource}; 34 | var ${resultValue} = main('${params}'); 35 | `, 36 | gasStepsLeave : gasDeclaration, 37 | resultValue 38 | } 39 | } 40 | 41 | module.exports = scrub 42 | -------------------------------------------------------------------------------- /scrub/lib/instrumentation.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert') 2 | const esprima = require('esprima') 3 | const escodegen = require('escodegen') 4 | 5 | var _gasDeclaration 6 | 7 | const _emptyBlockStr = `{"type":"BlockStatement","body":[]}` 8 | 9 | function genBr(decLength) { 10 | const templateGen = `{"type":"IfStatement","test":{"type":"Literal","value":true,"raw":"true"},"consequent":{"type":"BlockStatement","body":[{"type":"ExpressionStatement","expression":{"type":"AssignmentExpression","operator":"=","left":{"type":"Identifier","name":"${_gasDeclaration}"},"right":{"type":"BinaryExpression","operator": "-","left": {"type": "Identifier","name": "${_gasDeclaration}"},"right": {"type": "Literal","value": ${decLength},"raw": "${decLength}"}}}},{"type": "IfStatement","test": {"type": "BinaryExpression","operator": "<=","left": {"type": "Identifier","name": "${_gasDeclaration}"},"right": {"type": "Literal","value": 0,"raw": "0"}},"consequent": {"type": "BlockStatement","body": [{"type": "ThrowStatement","argument": {"type": "NewExpression","callee": {"type": "Identifier","name": "Error"},"arguments": [{"type": "Literal","value":"gas-has-been-exhausted","raw":"'gas-has-been-exhausted'"}]}}]},"alternate":null}]},"alternate":null}` 11 | return JSON.parse(templateGen) 12 | } 13 | 14 | function parseProgram(ast) { 15 | assert(ast.type === 'Program') 16 | const body = ast.body 17 | const labeledStatement = body[1] 18 | assert(labeledStatement.type === 'LabeledStatement') 19 | const doWhileStatement = labeledStatement.body 20 | assert(doWhileStatement.type === 'DoWhileStatement') 21 | const doWhileBlock = doWhileStatement.body 22 | for (let i = 1; i < doWhileBlock.body.length; ++i) { 23 | parse(doWhileBlock.body[i]) 24 | } 25 | 26 | } 27 | 28 | function parse (node) { 29 | if (!node || typeof node !== 'object') { 30 | return 31 | } 32 | if (!(Object.hasOwn(node, 'type') && !node.__skip)) { 33 | return 34 | } 35 | 36 | node.__skip = true 37 | 38 | switch (node.type) { 39 | case 'Program': { 40 | throw new Error('Unexpected Program') 41 | } 42 | case 'BlockStatement': { 43 | const length = node.body.length 44 | const expend = length > 0 ? (length / 2 + 0.5) : 0.5 45 | const genAst = genBr(expend) 46 | node.body.unshift(genAst) 47 | for (let i = 1; i < node.body.length; ++i) { 48 | parse(node.body[i]) 49 | } 50 | } 51 | break; 52 | case 'ExpressionStatement': { 53 | parse(node.expression) 54 | } 55 | break; 56 | case 'ClassBody': { 57 | const body = node.body 58 | for (const i of body) { 59 | parse(i) 60 | } 61 | } 62 | break; 63 | case 'MethodDefinition': { 64 | parse(node.key) 65 | parse(node.value) 66 | } 67 | break; 68 | case 'IfStatement': { 69 | parse(node.test) 70 | const consequent = node.consequent 71 | if (consequent.type === 'BlockStatement') { 72 | parse(consequent) 73 | } else if (consequent.type === 'EmptyStatement') { 74 | const length = 1 75 | const emptyBlock = JSON.parse(_emptyBlockStr) 76 | const expend = length > 0 ? (length / 2 + 0.5) : 0.5 77 | const genAst = genBr(expend) 78 | emptyBlock.body.push(genAst) 79 | node.consequent = emptyBlock 80 | } else { 81 | const length = 1 82 | const emptyBlock = JSON.parse(_emptyBlockStr) 83 | const expend = length > 0 ? (length / 2 + 0.5) : 0.5 84 | const genAst = genBr(expend) 85 | emptyBlock.body.push(genAst) 86 | emptyBlock.body.push(consequent) 87 | node.consequent = emptyBlock 88 | parse(node.consequent.body[1]) 89 | } 90 | if (node.alternate) { 91 | if (node.alternate.type === 'BlockStatement') { 92 | parse(node.alternate) 93 | } else if (node.alternate.type === 'EmptyStatement') { 94 | const length = 1 95 | const emptyBlock = JSON.parse(_emptyBlockStr) 96 | const expend = length > 0 ? (length / 2 + 0.5) : 0.5 97 | const genAst = genBr(expend) 98 | emptyBlock.body.push(genAst) 99 | node.alternate = emptyBlock 100 | } else { 101 | const length = 1 102 | const emptyBlock = JSON.parse(_emptyBlockStr) 103 | const expend = length > 0 ? (length / 2 + 0.5) : 0.5 104 | const genAst = genBr(expend) 105 | emptyBlock.body.push(genAst) 106 | emptyBlock.body.push(node.alternate) 107 | node.alternate = emptyBlock 108 | parse(emptyBlock.body[1]) 109 | } 110 | } 111 | } 112 | break; 113 | case 'LabeledStatement': { 114 | parse(node.label) 115 | parse(node.body) 116 | } 117 | break; 118 | case 'WithStatement': { 119 | throw new Error('Unexpected token with') 120 | } 121 | case 'SwitchStatement': { 122 | parse(node.discriminant) 123 | const cases = node.cases 124 | for (const it of cases) { 125 | parse(it) 126 | } 127 | } 128 | break; 129 | case 'ReturnStatement': { 130 | const _argument = node.argument 131 | parse(_argument) 132 | } 133 | break; 134 | case 'ThrowStatement': { 135 | const _argument = node.argument 136 | parse(_argument) 137 | } 138 | break; 139 | case 'TryStatement': { 140 | parse(node.block) 141 | parse(node.handler) 142 | parse(node.finalizer) 143 | } 144 | break; 145 | case 'WhileStatement': { 146 | parse(node.test) 147 | const body = node.body 148 | if (body.type === 'BlockStatement') { 149 | parse(body) 150 | } else if (body.type === 'EmptyStatement') { 151 | const length = 1 152 | const emptyBlock = JSON.parse(_emptyBlockStr) 153 | const expend = length > 0 ? (length / 2 + 0.5) : 0.5 154 | const genAst = genBr(expend) 155 | emptyBlock.body.push(genAst) 156 | node.body = emptyBlock 157 | } else { 158 | const length = 1 159 | const emptyBlock = JSON.parse(_emptyBlockStr) 160 | const expend = length > 0 ? (length / 2 + 0.5) : 0.5 161 | const genAst = genBr(expend) 162 | emptyBlock.body.push(genAst) 163 | emptyBlock.body.push(body) 164 | node.body = emptyBlock 165 | parse(emptyBlock.body[1]) 166 | } 167 | } 168 | break; 169 | case 'DoWhileStatement': { 170 | parse(node.test) 171 | const body = node.body 172 | if (body.type === 'BlockStatement') { 173 | parse(body) 174 | } else if (body.type === 'EmptyStatement') { 175 | const length = 1 176 | const emptyBlock = JSON.parse(_emptyBlockStr) 177 | const expend = length > 0 ? (length / 2 + 0.5) : 0.5 178 | const genAst = genBr(expend) 179 | emptyBlock.body.push(genAst) 180 | node.body = emptyBlock 181 | } else { 182 | const length = 1 183 | const emptyBlock = JSON.parse(_emptyBlockStr) 184 | const expend = length > 0 ? (length / 2 + 0.5) : 0.5 185 | const genAst = genBr(expend) 186 | emptyBlock.body.push(genAst) 187 | emptyBlock.body.push(body) 188 | node.body = emptyBlock 189 | parse(emptyBlock.body[1]) 190 | } 191 | } 192 | break; 193 | case 'ForStatement': { 194 | parse(node.init) 195 | parse(node.test) 196 | parse(node.update) 197 | const body = node.body 198 | if (body.type === 'BlockStatement') { 199 | parse(body) 200 | } else if (body.type === 'EmptyStatement') { 201 | const length = 1 202 | const emptyBlock = JSON.parse(_emptyBlockStr) 203 | const expend = length > 0 ? (length / 2 + 0.5) : 0.5 204 | const genAst = genBr(expend) 205 | emptyBlock.body.push(genAst) 206 | node.body = emptyBlock 207 | } else { 208 | const length = 1 209 | const emptyBlock = JSON.parse(_emptyBlockStr) 210 | const expend = length > 0 ? (length / 2 + 0.5) : 0.5 211 | const genAst = genBr(expend) 212 | emptyBlock.body.push(genAst) 213 | emptyBlock.body.push(body) 214 | node.body = emptyBlock 215 | parse(emptyBlock.body[1]) 216 | } 217 | } 218 | break; 219 | case 'ForInStatement': { 220 | parse(node.left) 221 | parse(node.right) 222 | const body = node.body 223 | if (body.type === 'BlockStatement') { 224 | parse(body) 225 | } else if (body.type === 'EmptyStatement') { 226 | const length = 1 227 | const emptyBlock = JSON.parse(_emptyBlockStr) 228 | const expend = length > 0 ? (length / 2 + 0.5) : 0.5 229 | const genAst = genBr(expend) 230 | emptyBlock.body.push(genAst) 231 | node.body = emptyBlock 232 | } else { 233 | const length = 1 234 | const emptyBlock = JSON.parse(_emptyBlockStr) 235 | const expend = length > 0 ? (length / 2 + 0.5) : 0.5 236 | const genAst = genBr(expend) 237 | emptyBlock.body.push(genAst) 238 | emptyBlock.body.push(body) 239 | node.body = emptyBlock 240 | parse(emptyBlock.body[1]) 241 | } 242 | } 243 | break; 244 | case 'ForOfStatement': { 245 | parse(node.left) 246 | parse(node.right) 247 | const body = node.body 248 | if (body.type === 'BlockStatement') { 249 | parse(body) 250 | } else if (body.type === 'EmptyStatement') { 251 | const length = 1 252 | const emptyBlock = JSON.parse(_emptyBlockStr) 253 | const expend = length > 0 ? (length / 2 + 0.5) : 0.5 254 | const genAst = genBr(expend) 255 | emptyBlock.body.push(genAst) 256 | node.body = emptyBlock 257 | } else { 258 | const length = 1 259 | const emptyBlock = JSON.parse(_emptyBlockStr) 260 | const expend = length > 0 ? (length / 2 + 0.5) : 0.5 261 | const genAst = genBr(expend) 262 | emptyBlock.body.push(genAst) 263 | emptyBlock.body.push(body) 264 | node.body = emptyBlock 265 | parse(emptyBlock.body[1]) 266 | } 267 | } 268 | break; 269 | case 'DebuggerStatement': { 270 | throw new Error('Unexpected token debugger') 271 | } 272 | case 'FunctionDeclaration': { 273 | if (node.async) { 274 | throw new Error('Unexpected token async') 275 | } 276 | if (node.generator) { 277 | throw new Error('Unexpected token function *') 278 | } 279 | parse(node.body) 280 | } 281 | break; 282 | case 'VariableDeclaration': { 283 | const declarations = node.declarations 284 | for (const i of declarations) { 285 | parse(i) 286 | } 287 | } 288 | break; 289 | case 'VariableDeclarator': { 290 | parse(node.id) 291 | parse(node.init) 292 | } 293 | break; 294 | case 'ClassDeclaration': { 295 | const superClass = node.superClass 296 | const body = node.body 297 | parse(node.id) 298 | parse(superClass) 299 | parse(body) 300 | } 301 | break; 302 | case 'ArrayExpression': { 303 | const elements = node.elements 304 | for (const i of elements) { 305 | parse(i) 306 | } 307 | } 308 | break; 309 | case 'ObjectExpression': { 310 | const properties = node.properties 311 | for (const i of properties) { 312 | parse(i) 313 | } 314 | } 315 | break; 316 | case 'ClassExpression': { 317 | const superClass = node.superClass 318 | const body = node.body 319 | parse(node.id) 320 | parse(superClass) 321 | parse(body) 322 | } 323 | break; 324 | case 'Property': { 325 | parse(node.key) 326 | parse(node.value) 327 | } 328 | break; 329 | case 'FunctionExpression': { 330 | if (node.async) { 331 | throw new Error('Unexpected token async') 332 | } 333 | if (node.generator) { 334 | throw new Error('Unexpected token function *') 335 | } 336 | const body = node.body 337 | if (body.type === 'BlockStatement') { 338 | parse(body) 339 | } else { 340 | throw new Error('Unexpected function statement') 341 | } 342 | } 343 | break; 344 | case 'ArrowFunctionExpression': { 345 | if (node.async) { 346 | throw new Error('Unexpected token async') 347 | } 348 | if (node.generator) { 349 | throw new Error('Unexpected token function *') 350 | } 351 | parse(node.body) 352 | } 353 | break; 354 | case 'BinaryExpression': { 355 | parse(node.left) 356 | parse(node.right) 357 | } 358 | break; 359 | case 'SequenceExpression': { 360 | for (const i of node.expressions) { 361 | parse(i) 362 | } 363 | } 364 | break; 365 | case 'UnaryExpression': { 366 | parse(node.argument) 367 | } 368 | break; 369 | case 'AssignmentExpression': { 370 | parse(node.left) 371 | parse(node.right) 372 | } 373 | break; 374 | case 'LogicalExpression': { 375 | parse(node.left) 376 | parse(node.right) 377 | } 378 | break; 379 | case 'ConditionalExpression': { 380 | parse(node.test) 381 | parse(node.consequent) 382 | parse(node.alternate) 383 | } 384 | break; 385 | case 'NewExpression': { 386 | const r = redundancyNew(node) 387 | if (r) { 388 | parse(node.callee) 389 | for (const i of node.arguments) { 390 | parse(i) 391 | } 392 | } 393 | } 394 | break; 395 | case 'CallExpression': { 396 | const r = redundancyCall(node) 397 | if (r) { 398 | parse(node.callee) 399 | for (const i of node.arguments) { 400 | parse(i) 401 | } 402 | } 403 | } 404 | break; 405 | case 'MemberExpression': { 406 | parse(node.object) 407 | parse(node.property) 408 | } 409 | break; 410 | case 'YieldExpression': { 411 | throw new Error('Unexpected token yield') 412 | } 413 | case 'GraphExpression': { 414 | throw new Error('Unexpected token #') 415 | } 416 | case 'AwaitExpression': { 417 | throw new Error('Unexpected token await') 418 | } 419 | case 'ObjectPattern': { 420 | const properties = node.properties 421 | for (const i of properties) { 422 | parse(i) 423 | } 424 | } 425 | break; 426 | case 'ArrayPattern': { 427 | const elements = node.elements 428 | for (const i of elements) { 429 | parse(i) 430 | } 431 | } 432 | break; 433 | case 'SwitchCase': { 434 | parse(node.test) 435 | const length = node.consequent.length 436 | const expend = length > 0 ? (length / 2 + 0.5) : 0.5 437 | const genAst = genBr(expend) 438 | node.consequent.unshift(genAst) 439 | for (let i = 1; i < node.consequent.length; ++i) { 440 | parse(node.consequent[i]) 441 | } 442 | } 443 | break; 444 | case 'CatchClause': { 445 | parse(node.body) 446 | } 447 | break; 448 | case 'TemplateLiteral': { 449 | const expressions = node.expressions 450 | for (const i of expressions) { 451 | parse(i) 452 | } 453 | } 454 | break; 455 | case 'Identifier': { 456 | if (node.name === 'Function') { 457 | throw new Error('Unexpected token Function') 458 | } 459 | if (node.name === 'eval') { 460 | throw new Error('Unexpected token eval') 461 | } 462 | } 463 | break; 464 | default: 465 | break; 466 | } 467 | } 468 | 469 | function redundancyNew(node) { 470 | let toDo = true 471 | const _arguments = node.arguments 472 | const callee = node.callee 473 | if (callee.type === 'Identifier' && callee.name === 'Promise') { 474 | throw new Error('Unexpected token Promise') 475 | } 476 | if (callee.type === 'Identifier' && callee.name === 'Function') { 477 | toDo = false 478 | if (!(_arguments.length === 1)) { 479 | throw new Error('Unexpected token Function') 480 | } 481 | if (!(_arguments[0].type === 'Literal' && _arguments[0].value === 'return this')) { 482 | throw new Error('Unexpected token Function') 483 | } 484 | } else if (callee.type === 'SequenceExpression') { 485 | const expressions = callee.expressions 486 | if (expressions.length > 0) { 487 | if (expressions[expressions.length - 1].type === 'Identifier' && 488 | expressions[expressions.length - 1].name === 'Function') { 489 | if (!(_arguments.length === 1)) { 490 | throw new Error('Unexpected token Function') 491 | } 492 | if (!(_arguments[0].type === 'Literal' && _arguments[0].value === 'return this')) { 493 | throw new Error('Unexpected token Function') 494 | } 495 | expressions[expressions.length - 1].__skip = true 496 | } 497 | } 498 | } 499 | return toDo 500 | } 501 | 502 | function redundancyCall(node) { 503 | let toDo = true 504 | const callee = node.callee 505 | const _arguments = node.arguments 506 | if (callee.type === 'Identifier' && callee.name === 'Function') { 507 | toDo = false 508 | if (!(_arguments.length === 1)) { 509 | throw new Error('Unexpected token Function') 510 | } 511 | if (!(_arguments[0].type === 'Literal' && _arguments[0].value === 'return this')) { 512 | throw new Error('Unexpected token Function') 513 | } 514 | } else if (callee.type === 'Identifier' && callee.name === 'eval') { 515 | toDo = false 516 | if (!(_arguments.length === 1)) { 517 | throw new Error('Unexpected token eval') 518 | } 519 | if (!(_arguments[0].type === 'Literal' && _arguments[0].value === 'this')) { 520 | throw new Error('Unexpected token eval') 521 | } 522 | } else if (callee.type === 'SequenceExpression') { 523 | const expressions = callee.expressions 524 | if (expressions.length > 0) { 525 | if (expressions[expressions.length - 1].type === 'Identifier' && 526 | expressions[expressions.length - 1].name === 'Function') { 527 | if (!(_arguments.length === 1)) { 528 | throw new Error('Unexpected token Function') 529 | } 530 | if (!(_arguments[0].type === 'Literal' && _arguments[0].value === 'return this')) { 531 | throw new Error('Unexpected token Function') 532 | } 533 | expressions[expressions.length - 1].__skip = true 534 | } else if (expressions[expressions.length - 1].type === 'Identifier' && 535 | expressions[expressions.length - 1].name === 'eval') { 536 | if (!(_arguments.length === 1)) { 537 | throw new Error('Unexpected token eval') 538 | } 539 | if (!(_arguments[0].type === 'Literal' && _arguments[0].value === 'this')) { 540 | throw new Error('Unexpected token eval') 541 | } 542 | expressions[expressions.length - 1].__skip = true 543 | } 544 | } 545 | } 546 | return toDo 547 | } 548 | 549 | module.exports = function (source, gasDeclaration) { 550 | _gasDeclaration = gasDeclaration 551 | const astTree = esprima.parseScript(source, undefined) 552 | parseProgram(astTree) 553 | return escodegen.generate(astTree, { 554 | format: { indent: { style: ' ' } } 555 | }) 556 | } 557 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | const source = ` 2 | function add (a, b) { 3 | return a + b; 4 | } 5 | function main (args) { 6 | const params = JSON.parse(args) 7 | var ret = 0 8 | for (const it of params) { 9 | ret = add(ret, it); 10 | } 11 | return ret 12 | } 13 | ` 14 | const vm = require('vm') 15 | const scrub = require('../scrub') 16 | const rr = scrub(source, JSON.stringify([ 17 | 0,1,2,3,4,5,6,7,8,9,10 18 | ]), 5, 10) 19 | 20 | const sandbox = {} 21 | 22 | vm.runInNewContext(rr.scrubedSource, sandbox) 23 | console.log(sandbox) 24 | --------------------------------------------------------------------------------