ethereumjs-vm
Version:
An Ethereum VM implementation
276 lines • 13.1 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
var BN = require("bn.js");
var ethereumjs_util_1 = require("ethereumjs-util");
var ethereumjs_account_1 = require("ethereumjs-account");
var bloom_1 = require("./bloom");
var evm_1 = require("./evm/evm");
var message_1 = require("./evm/message");
var txContext_1 = require("./evm/txContext");
var Block = require('ethereumjs-block');
/**
* @ignore
*/
function runTx(opts) {
return __awaiter(this, void 0, void 0, function () {
var state, result, e_1;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (opts === undefined) {
throw new Error('invalid input, opts must be provided');
}
// tx is required
if (!opts.tx) {
throw new Error('invalid input, tx is required');
}
// create a reasonable default if no block is given
if (!opts.block) {
opts.block = new Block();
}
if (new BN(opts.block.header.gasLimit).lt(new BN(opts.tx.gasLimit))) {
throw new Error('tx has a higher gas limit than the block');
}
state = this.pStateManager;
return [4 /*yield*/, state.checkpoint()];
case 1:
_a.sent();
_a.label = 2;
case 2:
_a.trys.push([2, 5, , 7]);
return [4 /*yield*/, _runTx.bind(this)(opts)];
case 3:
result = _a.sent();
return [4 /*yield*/, state.commit()];
case 4:
_a.sent();
return [2 /*return*/, result];
case 5:
e_1 = _a.sent();
return [4 /*yield*/, state.revert()];
case 6:
_a.sent();
throw e_1;
case 7: return [2 /*return*/];
}
});
});
}
exports.default = runTx;
function _runTx(opts) {
return __awaiter(this, void 0, void 0, function () {
var block, tx, state, basefee, gasLimit, fromAccount, txContext, message, evm, results, gasRefund, finalFromBalance, minerAccount, keys, _i, keys_1, k;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
block = opts.block;
tx = opts.tx;
state = this.pStateManager;
/**
* The `beforeTx` event
*
* @event Event: beforeTx
* @type {Object}
* @property {Transaction} tx emits the Transaction that is about to be processed
*/
return [4 /*yield*/, this._emit('beforeTx', tx)
// Validate gas limit against base fee
];
case 1:
/**
* The `beforeTx` event
*
* @event Event: beforeTx
* @type {Object}
* @property {Transaction} tx emits the Transaction that is about to be processed
*/
_a.sent();
basefee = tx.getBaseFee();
gasLimit = new BN(tx.gasLimit);
if (gasLimit.lt(basefee)) {
throw new Error('base fee exceeds gas limit');
}
gasLimit.isub(basefee);
return [4 /*yield*/, state.getAccount(tx.getSenderAddress())];
case 2:
fromAccount = _a.sent();
if (!opts.skipBalance && new BN(fromAccount.balance).lt(tx.getUpfrontCost())) {
throw new Error("sender doesn't have enough funds to send tx. The upfront cost is: " + tx
.getUpfrontCost()
.toString() +
(" and the sender's account only has: " + new BN(fromAccount.balance).toString()));
}
else if (!opts.skipNonce && !new BN(fromAccount.nonce).eq(new BN(tx.nonce))) {
throw new Error("the tx doesn't have the correct nonce. account has nonce of: " + new BN(fromAccount.nonce).toString() + " tx has nonce of: " + new BN(tx.nonce).toString());
}
// Update from account's nonce and balance
fromAccount.nonce = ethereumjs_util_1.toBuffer(new BN(fromAccount.nonce).addn(1));
fromAccount.balance = ethereumjs_util_1.toBuffer(new BN(fromAccount.balance).sub(new BN(tx.gasLimit).mul(new BN(tx.gasPrice))));
return [4 /*yield*/, state.putAccount(tx.getSenderAddress(), fromAccount)
/*
* Execute message
*/
];
case 3:
_a.sent();
txContext = new txContext_1.default(tx.gasPrice, tx.getSenderAddress());
message = new message_1.default({
caller: tx.getSenderAddress(),
gasLimit: gasLimit,
to: tx.to.toString('hex') !== '' ? tx.to : undefined,
value: tx.value,
data: tx.data,
});
state._wrapped._clearOriginalStorageCache();
evm = new evm_1.default(this, txContext, block);
return [4 /*yield*/, evm.executeMessage(message)];
case 4:
results = (_a.sent());
/*
* Parse results
*/
// Generate the bloom for the tx
results.bloom = txLogsBloom(results.execResult.logs);
// Caculate the total gas used
results.gasUsed = results.gasUsed.add(basefee);
gasRefund = evm._refund;
if (gasRefund) {
if (gasRefund.lt(results.gasUsed.divn(2))) {
results.gasUsed.isub(gasRefund);
}
else {
results.gasUsed.isub(results.gasUsed.divn(2));
}
}
results.amountSpent = results.gasUsed.mul(new BN(tx.gasPrice));
return [4 /*yield*/, state.getAccount(tx.getSenderAddress())];
case 5:
// Update sender's balance
fromAccount = _a.sent();
finalFromBalance = new BN(tx.gasLimit)
.sub(results.gasUsed)
.mul(new BN(tx.gasPrice))
.add(new BN(fromAccount.balance));
fromAccount.balance = ethereumjs_util_1.toBuffer(finalFromBalance);
return [4 /*yield*/, state.putAccount(ethereumjs_util_1.toBuffer(tx.getSenderAddress()), fromAccount)
// Update miner's balance
];
case 6:
_a.sent();
return [4 /*yield*/, state.getAccount(block.header.coinbase)
// add the amount spent on gas to the miner's account
];
case 7:
minerAccount = _a.sent();
// add the amount spent on gas to the miner's account
minerAccount.balance = ethereumjs_util_1.toBuffer(new BN(minerAccount.balance).add(results.amountSpent));
if (!!new BN(minerAccount.balance).isZero()) return [3 /*break*/, 9];
return [4 /*yield*/, state.putAccount(block.header.coinbase, minerAccount)];
case 8:
_a.sent();
_a.label = 9;
case 9:
if (!results.execResult.selfdestruct) return [3 /*break*/, 13];
keys = Object.keys(results.execResult.selfdestruct);
_i = 0, keys_1 = keys;
_a.label = 10;
case 10:
if (!(_i < keys_1.length)) return [3 /*break*/, 13];
k = keys_1[_i];
return [4 /*yield*/, state.putAccount(Buffer.from(k, 'hex'), new ethereumjs_account_1.default())];
case 11:
_a.sent();
_a.label = 12;
case 12:
_i++;
return [3 /*break*/, 10];
case 13: return [4 /*yield*/, state.cleanupTouchedAccounts()
/**
* The `afterTx` event
*
* @event Event: afterTx
* @type {Object}
* @property {Object} result result of the transaction
*/
];
case 14:
_a.sent();
/**
* The `afterTx` event
*
* @event Event: afterTx
* @type {Object}
* @property {Object} result result of the transaction
*/
return [4 /*yield*/, this._emit('afterTx', results)];
case 15:
/**
* The `afterTx` event
*
* @event Event: afterTx
* @type {Object}
* @property {Object} result result of the transaction
*/
_a.sent();
return [2 /*return*/, results];
}
});
});
}
/**
* @method txLogsBloom
* @private
*/
function txLogsBloom(logs) {
var bloom = new bloom_1.default();
if (logs) {
for (var i = 0; i < logs.length; i++) {
var log = logs[i];
// add the address
bloom.add(log[0]);
// add the topics
var topics = log[1];
for (var q = 0; q < topics.length; q++) {
bloom.add(topics[q]);
}
}
}
return bloom;
}
//# sourceMappingURL=runTx.js.map