@polygon-hermez/vm
Version:
An Ethereum VM implementation
684 lines • 31.9 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
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 (g && (g = 0, op[0] && (_ = 0)), _) 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 };
}
};
var __values = (this && this.__values) || function(o) {
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
if (m) return m.call(o);
if (o && typeof o.length === "number") return {
next: function () {
if (o && i >= o.length) o = void 0;
return { value: o && o[i++], done: !o };
}
};
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
};
var __read = (this && this.__read) || function (o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.BaseStateManager = void 0;
var Set = require('core-js-pure/es/set');
var common_1 = __importStar(require("@ethereumjs/common"));
var debug_1 = require("debug");
var ethereumjs_util_1 = require("ethereumjs-util");
var precompiles_1 = require("../evm/precompiles");
/**
* Abstract BaseStateManager class for the non-storage-backend
* related functionality parts of a StateManager like keeping
* track of accessed storage (`EIP-2929`) or touched accounts
* (`EIP-158`).
*
* This is not a full StateManager implementation in itself but
* can be used to ease implementing an own StateManager.
*
* Note that the implementation is pretty new (October 2021)
* and we cannot guarantee a stable interface yet.
*/
var BaseStateManager = /** @class */ (function () {
/**
* Needs to be called from the subclass constructor
*/
function BaseStateManager(opts) {
/**
* StateManager is run in DEBUG mode (default: false)
* Taken from DEBUG environment variable
*
* Safeguards on debug() calls are added for
* performance reasons to avoid string literal evaluation
* @hidden
*/
this.DEBUG = false;
var common = opts.common;
if (!common) {
common = new common_1.default({ chain: common_1.Chain.Mainnet, hardfork: common_1.Hardfork.Petersburg });
}
this._common = common;
this._touched = new Set();
this._customTouched = new Set();
this._touchedStack = [];
this._originalStorageCache = new Map();
this._accessedStorage = [new Map()];
this._accessedStorageReverted = [new Map()];
this._checkpointCount = 0;
// Safeguard if "process" is not available (browser)
if (process !== undefined && process.env.DEBUG) {
this.DEBUG = true;
}
this._debug = (0, debug_1.debug)('vm:state');
}
/**
* Gets the account associated with `address`. Returns an empty account if the account does not exist.
* @param address - Address of the `account` to get
*/
BaseStateManager.prototype.getAccount = function (address) {
return __awaiter(this, void 0, void 0, function () {
var account;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this._cache.getOrLoad(address)];
case 1:
account = _a.sent();
return [2 /*return*/, account];
}
});
});
};
/**
* Saves an account into state under the provided `address`.
* @param address - Address under which to store `account`
* @param account - The account to store
*/
BaseStateManager.prototype.putAccount = function (address, account) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
if (this.DEBUG) {
this._debug("Save account address=".concat(address, " nonce=").concat(account.nonce, " balance=").concat(account.balance, " contract=").concat(account.isContract() ? 'yes' : 'no', " empty=").concat(account.isEmpty() ? 'yes' : 'no'));
}
this._cache.put(address, account);
this.touchAccount(address);
return [2 /*return*/];
});
});
};
/**
* Deletes an account from state under the provided `address`. The account will also be removed from the state trie.
* @param address - Address of the account which should be deleted
*/
BaseStateManager.prototype.deleteAccount = function (address) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
if (this.DEBUG) {
this._debug("Delete account ".concat(address));
}
this._cache.del(address);
this.touchAccount(address);
return [2 /*return*/];
});
});
};
/**
* Marks an account as touched, according to the definition
* in [EIP-158](https://eips.ethereum.org/EIPS/eip-158).
* This happens when the account is triggered for a state-changing
* event. Touched accounts that are empty will be cleared
* at the end of the tx.
*/
BaseStateManager.prototype.touchAccount = function (address) {
this._touched.add(address.buf.toString('hex'));
this._customTouched.add(address.buf.toString('hex'));
};
/**
* Caches the storage value associated with the provided `address` and `key`
* on first invocation, and returns the cached (original) value from then
* onwards. This is used to get the original value of a storage slot for
* computing gas costs according to EIP-1283.
* @param address - Address of the account to get the storage for
* @param key - Key in the account's storage to get the value for. Must be 32 bytes long.
*/
BaseStateManager.prototype.getOriginalContractStorage = function (address, key) {
return __awaiter(this, void 0, void 0, function () {
var addressHex, keyHex, map, current;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (key.length !== 32) {
throw new Error('Storage key must be 32 bytes long');
}
addressHex = address.buf.toString('hex');
keyHex = key.toString('hex');
if (!this._originalStorageCache.has(addressHex)) {
map = new Map();
this._originalStorageCache.set(addressHex, map);
}
else {
map = this._originalStorageCache.get(addressHex);
}
if (!map.has(keyHex)) return [3 /*break*/, 1];
return [2 /*return*/, map.get(keyHex)];
case 1: return [4 /*yield*/, this.getContractStorage(address, key)];
case 2:
current = _a.sent();
map.set(keyHex, current);
return [2 /*return*/, current];
}
});
});
};
/**
* Clears the original storage cache. Refer to {@link StateManager.getOriginalContractStorage}
* for more explanation.
*/
BaseStateManager.prototype._clearOriginalStorageCache = function () {
this._originalStorageCache = new Map();
};
/**
* Clears the original storage cache. Refer to {@link StateManager.getOriginalContractStorage}
* for more explanation. Alias of the internal {@link StateManager._clearOriginalStorageCache}
*/
BaseStateManager.prototype.clearOriginalStorageCache = function () {
this._clearOriginalStorageCache();
};
/**
* Checkpoints the current state of the StateManager instance.
* State changes that follow can then be committed by calling
* `commit` or `reverted` by calling rollback.
*
* Partial implementation, called from the subclass.
*/
BaseStateManager.prototype.checkpoint = function () {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
this._cache.checkpoint();
this._touchedStack.push(new Set(Array.from(this._touched)));
this._accessedStorage.push(new Map());
this._checkpointCount++;
return [2 /*return*/];
});
});
};
/**
* Merges a storage map into the last item of the accessed storage stack
*/
BaseStateManager.prototype._accessedStorageMerge = function (storageList, storageMap) {
var mapTarget = storageList[storageList.length - 1];
if (mapTarget) {
// Note: storageMap is always defined here per definition (TypeScript cannot infer this)
storageMap === null || storageMap === void 0 ? void 0 : storageMap.forEach(function (slotSet, addressString) {
var addressExists = mapTarget.get(addressString);
if (!addressExists) {
mapTarget.set(addressString, new Set());
}
var storageSet = mapTarget.get(addressString);
slotSet.forEach(function (value) {
storageSet.add(value);
});
});
}
};
/**
* Commits the current change-set to the instance since the
* last call to checkpoint.
*
* Partial implementation, called from the subclass.
*/
BaseStateManager.prototype.commit = function () {
return __awaiter(this, void 0, void 0, function () {
var storageMap;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
// setup cache checkpointing
this._cache.commit();
this._touchedStack.pop();
this._checkpointCount--;
storageMap = this._accessedStorage.pop();
if (storageMap) {
this._accessedStorageMerge(this._accessedStorage, storageMap);
}
if (!(this._checkpointCount === 0)) return [3 /*break*/, 2];
return [4 /*yield*/, this._cache.flush()];
case 1:
_a.sent();
this._clearOriginalStorageCache();
_a.label = 2;
case 2: return [2 /*return*/];
}
});
});
};
/**
* Reverts the current change-set to the instance since the
* last call to checkpoint.
*
* Partial implementation , called from the subclass.
*/
BaseStateManager.prototype.revert = function () {
return __awaiter(this, void 0, void 0, function () {
var lastItem, touched;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
// setup cache checkpointing
this._cache.revert();
lastItem = this._accessedStorage.pop();
if (lastItem) {
this._accessedStorageReverted.push(lastItem);
}
touched = this._touchedStack.pop();
if (!touched) {
throw new Error('Reverting to invalid state checkpoint failed');
}
// Exceptional case due to consensus issue in Geth and Parity.
// See [EIP issue #716](https://github.com/ethereum/EIPs/issues/716) for context.
// The RIPEMD precompile has to remain *touched* even when the call reverts,
// and be considered for deletion.
if (this._touched.has(precompiles_1.ripemdPrecompileAddress)) {
touched.add(precompiles_1.ripemdPrecompileAddress);
}
this._touched = touched;
this._checkpointCount--;
if (!(this._checkpointCount === 0)) return [3 /*break*/, 2];
return [4 /*yield*/, this._cache.flush()];
case 1:
_a.sent();
this._clearOriginalStorageCache();
_a.label = 2;
case 2: return [2 /*return*/];
}
});
});
};
/**
* Generates a canonical genesis state on the instance based on the
* configured chain parameters. Will error if there are uncommitted
* checkpoints on the instance.
*/
BaseStateManager.prototype.generateCanonicalGenesis = function () {
return __awaiter(this, void 0, void 0, function () {
var genesis;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (this._checkpointCount !== 0) {
throw new Error('Cannot create genesis state with uncommitted checkpoints');
}
return [4 /*yield*/, this.hasGenesisState()];
case 1:
genesis = _a.sent();
if (!!genesis) return [3 /*break*/, 3];
return [4 /*yield*/, this.generateGenesis(this._common.genesisState())];
case 2:
_a.sent();
_a.label = 3;
case 3: return [2 /*return*/];
}
});
});
};
/**
* Initializes the provided genesis state into the state trie
* @param initState address -> balance | [balance, code, storage]
*/
BaseStateManager.prototype.generateGenesis = function (initState) {
return __awaiter(this, void 0, void 0, function () {
var addresses, addresses_1, addresses_1_1, address, addr, state, account, _a, balance, code, storage, account, _b, _c, _d, key, value, e_1_1, e_2_1;
var e_2, _e, e_1, _f;
return __generator(this, function (_g) {
switch (_g.label) {
case 0:
if (this._checkpointCount !== 0) {
throw new Error('Cannot create genesis state with uncommitted checkpoints');
}
if (this.DEBUG) {
this._debug("Save genesis state into the state trie");
}
addresses = Object.keys(initState);
_g.label = 1;
case 1:
_g.trys.push([1, 17, 18, 19]);
addresses_1 = __values(addresses), addresses_1_1 = addresses_1.next();
_g.label = 2;
case 2:
if (!!addresses_1_1.done) return [3 /*break*/, 16];
address = addresses_1_1.value;
addr = ethereumjs_util_1.Address.fromString(address);
state = initState[address];
if (!!Array.isArray(state)) return [3 /*break*/, 4];
account = ethereumjs_util_1.Account.fromAccountData({ balance: state });
return [4 /*yield*/, this.putAccount(addr, account)];
case 3:
_g.sent();
return [3 /*break*/, 15];
case 4:
_a = __read(state, 3), balance = _a[0], code = _a[1], storage = _a[2];
account = ethereumjs_util_1.Account.fromAccountData({ balance: balance });
return [4 /*yield*/, this.putAccount(addr, account)];
case 5:
_g.sent();
if (!code) return [3 /*break*/, 7];
return [4 /*yield*/, this.putContractCode(addr, (0, ethereumjs_util_1.toBuffer)(code))];
case 6:
_g.sent();
_g.label = 7;
case 7:
if (!storage) return [3 /*break*/, 15];
_g.label = 8;
case 8:
_g.trys.push([8, 13, 14, 15]);
_b = (e_1 = void 0, __values(Object.values(storage))), _c = _b.next();
_g.label = 9;
case 9:
if (!!_c.done) return [3 /*break*/, 12];
_d = __read(_c.value, 2), key = _d[0], value = _d[1];
return [4 /*yield*/, this.putContractStorage(addr, (0, ethereumjs_util_1.toBuffer)(key), (0, ethereumjs_util_1.toBuffer)(value))];
case 10:
_g.sent();
_g.label = 11;
case 11:
_c = _b.next();
return [3 /*break*/, 9];
case 12: return [3 /*break*/, 15];
case 13:
e_1_1 = _g.sent();
e_1 = { error: e_1_1 };
return [3 /*break*/, 15];
case 14:
try {
if (_c && !_c.done && (_f = _b.return)) _f.call(_b);
}
finally { if (e_1) throw e_1.error; }
return [7 /*endfinally*/];
case 15:
addresses_1_1 = addresses_1.next();
return [3 /*break*/, 2];
case 16: return [3 /*break*/, 19];
case 17:
e_2_1 = _g.sent();
e_2 = { error: e_2_1 };
return [3 /*break*/, 19];
case 18:
try {
if (addresses_1_1 && !addresses_1_1.done && (_e = addresses_1.return)) _e.call(addresses_1);
}
finally { if (e_2) throw e_2.error; }
return [7 /*endfinally*/];
case 19: return [4 /*yield*/, this._cache.flush()];
case 20:
_g.sent();
return [2 /*return*/];
}
});
});
};
/**
* Checks if the `account` corresponding to `address`
* is empty or non-existent as defined in
* EIP-161 (https://eips.ethereum.org/EIPS/eip-161).
* @param address - Address to check
*/
BaseStateManager.prototype.accountIsEmpty = function (address) {
return __awaiter(this, void 0, void 0, function () {
var account;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.getAccount(address)];
case 1:
account = _a.sent();
return [2 /*return*/, account.isEmpty()];
}
});
});
};
/**
* Removes accounts form the state trie that have been touched,
* as defined in EIP-161 (https://eips.ethereum.org/EIPS/eip-161).
*/
BaseStateManager.prototype.cleanupTouchedAccounts = function () {
return __awaiter(this, void 0, void 0, function () {
var touchedArray, touchedArray_1, touchedArray_1_1, addressHex, address, empty, e_3_1;
var e_3, _a;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
if (!this._common.gteHardfork('spuriousDragon')) return [3 /*break*/, 8];
touchedArray = Array.from(this._touched);
_b.label = 1;
case 1:
_b.trys.push([1, 6, 7, 8]);
touchedArray_1 = __values(touchedArray), touchedArray_1_1 = touchedArray_1.next();
_b.label = 2;
case 2:
if (!!touchedArray_1_1.done) return [3 /*break*/, 5];
addressHex = touchedArray_1_1.value;
address = new ethereumjs_util_1.Address(Buffer.from(addressHex, 'hex'));
return [4 /*yield*/, this.accountIsEmpty(address)];
case 3:
empty = _b.sent();
if (empty) {
this._cache.del(address);
if (this.DEBUG) {
this._debug("Cleanup touched account address=".concat(address, " (>= SpuriousDragon)"));
}
}
_b.label = 4;
case 4:
touchedArray_1_1 = touchedArray_1.next();
return [3 /*break*/, 2];
case 5: return [3 /*break*/, 8];
case 6:
e_3_1 = _b.sent();
e_3 = { error: e_3_1 };
return [3 /*break*/, 8];
case 7:
try {
if (touchedArray_1_1 && !touchedArray_1_1.done && (_a = touchedArray_1.return)) _a.call(touchedArray_1);
}
finally { if (e_3) throw e_3.error; }
return [7 /*endfinally*/];
case 8:
this._touched.clear();
return [2 /*return*/];
}
});
});
};
/** EIP-2929 logic
* This should only be called from within the EVM
*/
/**
* Returns true if the address is warm in the current context
* @param address - The address (as a Buffer) to check
*/
BaseStateManager.prototype.isWarmedAddress = function (address) {
for (var i = this._accessedStorage.length - 1; i >= 0; i--) {
var currentMap = this._accessedStorage[i];
if (currentMap.has(address.toString('hex'))) {
return true;
}
}
return false;
};
/**
* Add a warm address in the current context
* @param address - The address (as a Buffer) to check
*/
BaseStateManager.prototype.addWarmedAddress = function (address) {
var key = address.toString('hex');
var storageSet = this._accessedStorage[this._accessedStorage.length - 1].get(key);
if (!storageSet) {
var emptyStorage = new Set();
this._accessedStorage[this._accessedStorage.length - 1].set(key, emptyStorage);
}
};
/**
* Returns true if the slot of the address is warm
* @param address - The address (as a Buffer) to check
* @param slot - The slot (as a Buffer) to check
*/
BaseStateManager.prototype.isWarmedStorage = function (address, slot) {
var addressKey = address.toString('hex');
var storageKey = slot.toString('hex');
for (var i = this._accessedStorage.length - 1; i >= 0; i--) {
var currentMap = this._accessedStorage[i];
if (currentMap.has(addressKey) && currentMap.get(addressKey).has(storageKey)) {
return true;
}
}
return false;
};
/**
* Mark the storage slot in the address as warm in the current context
* @param address - The address (as a Buffer) to check
* @param slot - The slot (as a Buffer) to check
*/
BaseStateManager.prototype.addWarmedStorage = function (address, slot) {
var addressKey = address.toString('hex');
var storageSet = this._accessedStorage[this._accessedStorage.length - 1].get(addressKey);
if (!storageSet) {
storageSet = new Set();
this._accessedStorage[this._accessedStorage.length - 1].set(addressKey, storageSet);
}
storageSet.add(slot.toString('hex'));
};
/**
* Clear the warm accounts and storage. To be called after a transaction finished.
* @param boolean - If true, returns an EIP-2930 access list generated
*/
BaseStateManager.prototype.clearWarmedAccounts = function () {
this._accessedStorage = [new Map()];
this._accessedStorageReverted = [new Map()];
};
/**
* Generates an EIP-2930 access list
*
* Note: this method is not yet part of the {@link StateManager} interface.
* If not implemented, {@link VM.runTx} is not allowed to be used with the
* `reportAccessList` option and will instead throw.
*
* Note: there is an edge case on accessList generation where an
* internal call might revert without an accessList but pass if the
* accessList is used for a tx run (so the subsequent behavior might change).
* This edge case is not covered by this implementation.
*
* @param addressesRemoved - List of addresses to be removed from the final list
* @param addressesOnlyStorage - List of addresses only to be added in case of present storage slots
*
* @returns - an [@ethereumjs/tx](https://github.com/ethereumjs/ethereumjs-monorepo/packages/tx) `AccessList`
*/
BaseStateManager.prototype.generateAccessList = function (addressesRemoved, addressesOnlyStorage) {
var _this = this;
if (addressesRemoved === void 0) { addressesRemoved = []; }
if (addressesOnlyStorage === void 0) { addressesOnlyStorage = []; }
// Merge with the reverted storage list
var mergedStorage = __spreadArray(__spreadArray([], __read(this._accessedStorage), false), __read(this._accessedStorageReverted), false);
// Fold merged storage array into one Map
while (mergedStorage.length >= 2) {
var storageMap = mergedStorage.pop();
if (storageMap) {
this._accessedStorageMerge(mergedStorage, storageMap);
}
}
var folded = new Map(__spreadArray([], __read(mergedStorage[0].entries()), false).sort());
// Transfer folded map to final structure
var accessList = [];
folded.forEach(function (slots, addressStr) {
var address = ethereumjs_util_1.Address.fromString("0x".concat(addressStr));
var check1 = (0, precompiles_1.getActivePrecompiles)(_this._common).find(function (a) { return a.equals(address); });
var check2 = addressesRemoved.find(function (a) { return a.equals(address); });
var check3 = addressesOnlyStorage.find(function (a) { return a.equals(address); }) !== undefined && slots.size === 0;
if (!check1 && !check2 && !check3) {
var storageSlots = Array.from(slots)
.map(function (s) { return "0x".concat(s); })
.sort();
var accessListItem = {
address: "0x".concat(addressStr),
storageKeys: storageSlots,
};
accessList.push(accessListItem);
}
});
return accessList;
};
return BaseStateManager;
}());
exports.BaseStateManager = BaseStateManager;
//# sourceMappingURL=baseStateManager.js.map