@eth-optimism/ethereumjs-vm
Version:
An Ethereum VM implementation
658 lines • 27.4 kB
JavaScript
"use strict";
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
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 exceptions_1 = require("../exceptions");
var message_1 = require("./message");
var promisify = require('util.promisify');
/**
* External interface made available to EVM bytecode. Modeled after
* the ewasm EEI [spec](https://github.com/ewasm/design/blob/master/eth_interface.md).
* It includes methods for accessing/modifying state, calling or creating contracts, access
* to environment data among other things.
* The EEI instance also keeps artifacts produced by the bytecode such as logs
* and to-be-selfdestructed addresses.
*/
var EEI = /** @class */ (function () {
function EEI(env, state, evm, common, gasLeft) {
this._env = env;
this._state = state;
this._evm = evm;
this._lastReturned = Buffer.alloc(0);
this._common = common;
this._gasLeft = gasLeft;
this._result = {
logs: [],
returnValue: undefined,
selfdestruct: {},
};
}
/**
* Subtracts an amount from the gas counter.
* @param amount - Amount of gas to consume
* @throws if out of gas
*/
EEI.prototype.useGas = function (amount) {
this._gasLeft.isub(amount);
if (this._gasLeft.ltn(0)) {
this._gasLeft = new BN(0);
trap(exceptions_1.ERROR.OUT_OF_GAS);
}
};
/**
* Adds a positive amount to the gas counter.
* @param amount - Amount of gas refunded
*/
EEI.prototype.refundGas = function (amount) {
this._evm._refund.iadd(amount);
};
/**
* Reduces amount of gas to be refunded by a positive value.
* @param amount - Amount to subtract from gas refunds
*/
EEI.prototype.subRefund = function (amount) {
this._evm._refund.isub(amount);
if (this._evm._refund.ltn(0)) {
this._evm._refund = new BN(0);
trap(exceptions_1.ERROR.REFUND_EXHAUSTED);
}
};
/**
* Returns address of currently executing account.
*/
EEI.prototype.getAddress = function () {
return this._env.address;
};
/**
* Returns balance of the given account.
* @param address - Address of account
*/
EEI.prototype.getExternalBalance = function (address) {
return __awaiter(this, void 0, void 0, function () {
var account;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
// shortcut if current account
if (address.toString('hex') === this._env.address.toString('hex')) {
return [2 /*return*/, new BN(this._env.contract.balance)];
}
return [4 /*yield*/, this._state.getAccount(address)];
case 1:
account = _a.sent();
return [2 /*return*/, new BN(account.balance)];
}
});
});
};
/**
* Returns balance of self.
*/
EEI.prototype.getSelfBalance = function () {
return new BN(this._env.contract.balance);
};
/**
* Returns caller address. This is the address of the account
* that is directly responsible for this execution.
*/
EEI.prototype.getCaller = function () {
return new BN(this._env.caller);
};
/**
* Returns the deposited value by the instruction/transaction
* responsible for this execution.
*/
EEI.prototype.getCallValue = function () {
return new BN(this._env.callValue);
};
/**
* Returns input data in current environment. This pertains to the input
* data passed with the message call instruction or transaction.
*/
EEI.prototype.getCallData = function () {
return this._env.callData;
};
/**
* Returns size of input data in current environment. This pertains to the
* input data passed with the message call instruction or transaction.
*/
EEI.prototype.getCallDataSize = function () {
return new BN(this._env.callData.length);
};
/**
* Returns the size of code running in current environment.
*/
EEI.prototype.getCodeSize = function () {
return new BN(this._env.code.length);
};
/**
* Returns the code running in current environment.
*/
EEI.prototype.getCode = function () {
return this._env.code;
};
/**
* Returns true if the current call must be executed statically.
*/
EEI.prototype.isStatic = function () {
return this._env.isStatic;
};
/**
* Get size of an account’s code.
* @param address - Address of account
*/
EEI.prototype.getExternalCodeSize = function (address) {
return __awaiter(this, void 0, void 0, function () {
var addressBuf, code;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
addressBuf = addressToBuffer(address);
return [4 /*yield*/, this._state.getContractCode(addressBuf)];
case 1:
code = _a.sent();
return [2 /*return*/, new BN(code.length)];
}
});
});
};
/**
* Returns code of an account.
* @param address - Address of account
*/
EEI.prototype.getExternalCode = function (address) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
if (!Buffer.isBuffer(address)) {
address = addressToBuffer(address);
}
return [2 /*return*/, this._state.getContractCode(address)];
});
});
};
/**
* Returns size of current return data buffer. This contains the return data
* from the last executed call, callCode, callDelegate, callStatic or create.
* Note: create only fills the return data buffer in case of a failure.
*/
EEI.prototype.getReturnDataSize = function () {
return new BN(this._lastReturned.length);
};
/**
* Returns the current return data buffer. This contains the return data
* from last executed call, callCode, callDelegate, callStatic or create.
* Note: create only fills the return data buffer in case of a failure.
*/
EEI.prototype.getReturnData = function () {
return this._lastReturned;
};
/**
* Returns price of gas in current environment.
*/
EEI.prototype.getTxGasPrice = function () {
return new BN(this._env.gasPrice);
};
/**
* Returns the execution's origination address. This is the
* sender of original transaction; it is never an account with
* non-empty associated code.
*/
EEI.prototype.getTxOrigin = function () {
return new BN(this._env.origin);
};
/**
* Returns the block’s number.
*/
EEI.prototype.getBlockNumber = function () {
return new BN(this._env.block.header.number);
};
/**
* Returns the block's beneficiary address.
*/
EEI.prototype.getBlockCoinbase = function () {
return new BN(this._env.block.header.coinbase);
};
/**
* Returns the block's timestamp.
*/
EEI.prototype.getBlockTimestamp = function () {
return new BN(this._env.block.header.timestamp);
};
/**
* Returns the block's difficulty.
*/
EEI.prototype.getBlockDifficulty = function () {
return new BN(this._env.block.header.difficulty);
};
/**
* Returns the block's gas limit.
*/
EEI.prototype.getBlockGasLimit = function () {
return new BN(this._env.block.header.gasLimit);
};
/**
* Returns the chain ID for current chain. Introduced for the
* CHAINID opcode proposed in [EIP-1344](https://eips.ethereum.org/EIPS/eip-1344).
*/
EEI.prototype.getChainId = function () {
return new BN(this._common.chainId());
};
/**
* Returns Gets the hash of one of the 256 most recent complete blocks.
* @param num - Number of block
*/
EEI.prototype.getBlockHash = function (num) {
return __awaiter(this, void 0, void 0, function () {
var block;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, promisify(this._env.blockchain.getBlock).bind(this._env.blockchain)(num)];
case 1:
block = _a.sent();
return [2 /*return*/, new BN(block.hash())];
}
});
});
};
/**
* Store 256-bit a value in memory to persistent storage.
*/
EEI.prototype.storageStore = function (key, value) {
return __awaiter(this, void 0, void 0, function () {
var account;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this._state.putContractStorage(this._env.address, key, value)];
case 1:
_a.sent();
return [4 /*yield*/, this._state.getAccount(this._env.address)];
case 2:
account = _a.sent();
this._env.contract = account;
return [2 /*return*/];
}
});
});
};
/**
* Loads a 256-bit value to memory from persistent storage.
* @param key - Storage key
*/
EEI.prototype.storageLoad = function (key) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
return [2 /*return*/, this._state.getContractStorage(this._env.address, key)];
});
});
};
/**
* Returns the current gasCounter.
*/
EEI.prototype.getGasLeft = function () {
return this._gasLeft.clone();
};
/**
* Set the returning output data for the execution.
* @param returnData - Output data to return
*/
EEI.prototype.finish = function (returnData) {
this._result.returnValue = returnData;
trap(exceptions_1.ERROR.STOP);
};
/**
* Set the returning output data for the execution. This will halt the
* execution immediately and set the execution result to "reverted".
* @param returnData - Output data to return
*/
EEI.prototype.revert = function (returnData) {
this._result.returnValue = returnData;
trap(exceptions_1.ERROR.REVERT);
};
/**
* Mark account for later deletion and give the remaining balance to the
* specified beneficiary address. This will cause a trap and the
* execution will be aborted immediately.
* @param toAddress - Beneficiary address
*/
EEI.prototype.selfDestruct = function (toAddress) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
return [2 /*return*/, this._selfDestruct(toAddress)];
});
});
};
EEI.prototype._selfDestruct = function (toAddress) {
return __awaiter(this, void 0, void 0, function () {
var toAccount, newBalance, account;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
// only add to refund if this is the first selfdestruct for the address
if (!this._result.selfdestruct[this._env.address.toString('hex')]) {
this.refundGas(new BN(this._common.param('gasPrices', 'selfdestructRefund')));
}
this._result.selfdestruct[this._env.address.toString('hex')] = toAddress;
return [4 /*yield*/, this._state.getAccount(toAddress)];
case 1:
toAccount = _a.sent();
newBalance = new BN(this._env.contract.balance).add(new BN(toAccount.balance));
toAccount.balance = ethereumjs_util_1.toBuffer(newBalance);
return [4 /*yield*/, this._state.putAccount(toAddress, toAccount)
// Subtract from contract balance
];
case 2:
_a.sent();
return [4 /*yield*/, this._state.getAccount(this._env.address)];
case 3:
account = _a.sent();
account.balance = ethereumjs_util_1.toBuffer(new BN(0));
return [4 /*yield*/, this._state.putAccount(this._env.address, account)];
case 4:
_a.sent();
trap(exceptions_1.ERROR.STOP);
return [2 /*return*/];
}
});
});
};
/**
* Creates a new log in the current environment.
*/
EEI.prototype.log = function (data, numberOfTopics, topics) {
if (numberOfTopics < 0 || numberOfTopics > 4) {
trap(exceptions_1.ERROR.OUT_OF_RANGE);
}
if (topics.length !== numberOfTopics) {
trap(exceptions_1.ERROR.INTERNAL_ERROR);
}
// add address
var log = [this._env.address];
log.push(topics);
// add data
log.push(data);
this._result.logs.push(log);
};
/**
* Sends a message with arbitrary data to a given address path.
*/
EEI.prototype.call = function (gasLimit, address, value, data) {
return __awaiter(this, void 0, void 0, function () {
var msg;
return __generator(this, function (_a) {
msg = new message_1.default({
caller: this._env.address,
gasLimit: gasLimit,
to: address,
value: value,
data: data,
isStatic: this._env.isStatic,
depth: this._env.depth + 1,
originalTargetAddress: this._env.originalTargetAddress,
});
return [2 /*return*/, this._baseCall(msg)];
});
});
};
/**
* Message-call into this account with an alternative account's code.
*/
EEI.prototype.callCode = function (gasLimit, address, value, data) {
return __awaiter(this, void 0, void 0, function () {
var msg;
return __generator(this, function (_a) {
msg = new message_1.default({
caller: this._env.address,
gasLimit: gasLimit,
to: this._env.address,
codeAddress: address,
value: value,
data: data,
isStatic: this._env.isStatic,
depth: this._env.depth + 1,
originalTargetAddress: this._env.originalTargetAddress,
});
return [2 /*return*/, this._baseCall(msg)];
});
});
};
/**
* Sends a message with arbitrary data to a given address path, but disallow
* state modifications. This includes log, create, selfdestruct and call with
* a non-zero value.
*/
EEI.prototype.callStatic = function (gasLimit, address, value, data) {
return __awaiter(this, void 0, void 0, function () {
var msg;
return __generator(this, function (_a) {
msg = new message_1.default({
caller: this._env.address,
gasLimit: gasLimit,
to: address,
value: value,
data: data,
isStatic: true,
depth: this._env.depth + 1,
originalTargetAddress: this._env.originalTargetAddress,
});
return [2 /*return*/, this._baseCall(msg)];
});
});
};
/**
* Message-call into this account with an alternative account’s code, but
* persisting the current values for sender and value.
*/
EEI.prototype.callDelegate = function (gasLimit, address, value, data) {
return __awaiter(this, void 0, void 0, function () {
var msg;
return __generator(this, function (_a) {
msg = new message_1.default({
caller: this._env.caller,
gasLimit: gasLimit,
to: this._env.address,
codeAddress: address,
value: value,
data: data,
isStatic: this._env.isStatic,
delegatecall: true,
depth: this._env.depth + 1,
originalTargetAddress: this._env.originalTargetAddress,
});
return [2 /*return*/, this._baseCall(msg)];
});
});
};
EEI.prototype._baseCall = function (msg) {
return __awaiter(this, void 0, void 0, function () {
var selfdestruct, results, account;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
selfdestruct = __assign({}, this._result.selfdestruct);
msg.selfdestruct = selfdestruct;
// empty the return data buffer
this._lastReturned = Buffer.alloc(0);
// Check if account has enough ether and max depth not exceeded
if (this._env.depth >= this._common.param('vm', 'stackLimit') ||
(msg.delegatecall !== true && new BN(this._env.contract.balance).lt(msg.value))) {
return [2 /*return*/, new BN(0)];
}
return [4 /*yield*/, this._evm.executeMessage(msg)];
case 1:
results = _a.sent();
if (results.execResult.logs) {
this._result.logs = this._result.logs.concat(results.execResult.logs);
}
// this should always be safe
this.useGas(results.gasUsed);
// Set return value
if (results.execResult.returnValue &&
(!results.execResult.exceptionError ||
results.execResult.exceptionError.error === exceptions_1.ERROR.REVERT)) {
this._lastReturned = results.execResult.returnValue;
}
if (!!results.execResult.exceptionError) return [3 /*break*/, 3];
Object.assign(this._result.selfdestruct, selfdestruct);
return [4 /*yield*/, this._state.getAccount(this._env.address)];
case 2:
account = _a.sent();
this._env.contract = account;
_a.label = 3;
case 3: return [2 /*return*/, this._getReturnCode(results)];
}
});
});
};
/**
* Creates a new contract with a given value.
*/
EEI.prototype.create = function (gasLimit, value, data, salt) {
if (salt === void 0) { salt = null; }
return __awaiter(this, void 0, void 0, function () {
var selfdestruct, msg, results, account;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
selfdestruct = __assign({}, this._result.selfdestruct);
msg = new message_1.default({
caller: this._env.address,
gasLimit: gasLimit,
value: value,
data: data,
salt: salt,
depth: this._env.depth + 1,
selfdestruct: selfdestruct,
originalTargetAddress: this._env.originalTargetAddress,
});
// empty the return data buffer
this._lastReturned = Buffer.alloc(0);
// Check if account has enough ether and max depth not exceeded
if (this._env.depth >= this._common.param('vm', 'stackLimit') ||
(msg.delegatecall !== true && new BN(this._env.contract.balance).lt(msg.value))) {
return [2 /*return*/, new BN(0)];
}
this._env.contract.nonce = ethereumjs_util_1.toBuffer(new BN(this._env.contract.nonce).addn(1));
return [4 /*yield*/, this._state.putAccount(this._env.address, this._env.contract)];
case 1:
_a.sent();
return [4 /*yield*/, this._evm.executeMessage(msg)];
case 2:
results = _a.sent();
if (results.execResult.logs) {
this._result.logs = this._result.logs.concat(results.execResult.logs);
}
// this should always be safe
this.useGas(results.gasUsed);
// Set return buffer in case revert happened
if (results.execResult.exceptionError &&
results.execResult.exceptionError.error === exceptions_1.ERROR.REVERT) {
this._lastReturned = results.execResult.returnValue;
}
if (!!results.execResult.exceptionError) return [3 /*break*/, 4];
Object.assign(this._result.selfdestruct, selfdestruct);
return [4 /*yield*/, this._state.getAccount(this._env.address)];
case 3:
account = _a.sent();
this._env.contract = account;
if (results.createdAddress) {
// push the created address to the stack
return [2 /*return*/, new BN(results.createdAddress)];
}
_a.label = 4;
case 4: return [2 /*return*/, this._getReturnCode(results)];
}
});
});
};
/**
* Creates a new contract with a given value. Generates
* a deterministic address via CREATE2 rules.
*/
EEI.prototype.create2 = function (gasLimit, value, data, salt) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
return [2 /*return*/, this.create(gasLimit, value, data, salt)];
});
});
};
/**
* Returns true if account is empty (according to EIP-161).
* @param address - Address of account
*/
EEI.prototype.isAccountEmpty = function (address) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
return [2 /*return*/, this._state.accountIsEmpty(address)];
});
});
};
EEI.prototype._getReturnCode = function (results) {
// This preserves the previous logic, but seems to contradict the EEI spec
// https://github.com/ewasm/design/blob/38eeded28765f3e193e12881ea72a6ab807a3371/eth_interface.md
if (results.execResult.exceptionError) {
return new BN(0);
}
else {
return new BN(1);
}
};
return EEI;
}());
exports.default = EEI;
function trap(err) {
throw new exceptions_1.VmError(err);
}
var MASK_160 = new BN(1).shln(160).subn(1);
function addressToBuffer(address) {
if (Buffer.isBuffer(address))
return address;
return address.and(MASK_160).toArrayLike(Buffer, 'be', 20);
}
//# sourceMappingURL=eei.js.map