@tokamak-zk-evm/synthesizer
Version:
Tokamak zk-EVM Synthesizer - Processes Ethereum transactions into wire maps for Tokamak zk-SNARK proof generation
109 lines • 3.78 kB
JavaScript
import { bytesToHex } from "@synthesizer-libs/util";
export class TransientStorage {
constructor() {
/**
* The current values of the transient storage, keyed by contract address and then slot
*/
this._storage = new Map();
/**
* Each change to storage is recorded in the journal. This is never cleared.
*/
this._changeJournal = [];
/**
* The length of the journal at the beginning of each call in the call stack.
*/
this._indices = [0];
}
/**
* Get the value for the given address and key
* @param addr the address for which transient storage is accessed
* @param key the key of the address to get
*/
get(addr, key) {
const map = this._storage.get(addr.toString());
if (!map) {
return new Uint8Array(32);
}
const value = map.get(bytesToHex(key));
if (!value) {
return new Uint8Array(32);
}
return value;
}
/**
* Put the given value for the address and key
* @param addr the address of the contract for which the key is being set
* @param key the slot to set for the address
* @param value the new value of the transient storage slot to set
*/
put(addr, key, value) {
if (key.length !== 32) {
throw new Error('Transient storage key must be 32 bytes long');
}
if (value.length > 32) {
throw new Error('Transient storage value cannot be longer than 32 bytes');
}
const addrString = addr.toString();
if (!this._storage.has(addrString)) {
this._storage.set(addrString, new Map());
}
const map = this._storage.get(addrString);
const keyStr = bytesToHex(key);
const prevValue = map.get(keyStr) ?? new Uint8Array(32);
this._changeJournal.push({
addr: addrString,
key: keyStr,
prevValue,
});
map.set(keyStr, value);
}
/**
* Commit all the changes since the last checkpoint
*/
commit() {
if (this._indices.length === 0)
throw new Error('Nothing to commit');
// by discarding the length of the array from the last time checkpoint was called, all changes are included in the last stack
this._indices.pop();
}
/**
* To be called whenever entering a new context. If revert is called after checkpoint, all changes after the latest checkpoint are reverted.
*/
checkpoint() {
this._indices.push(this._changeJournal.length);
}
/**
* Revert transient storage to the last checkpoint
*/
revert() {
const lastCheckpoint = this._indices.pop();
if (typeof lastCheckpoint === 'undefined')
throw new Error('Nothing to revert');
for (let i = this._changeJournal.length - 1; i >= lastCheckpoint; i--) {
const { key, prevValue, addr } = this._changeJournal[i];
this._storage.get(addr).set(key, prevValue);
}
this._changeJournal.splice(lastCheckpoint, this._changeJournal.length - lastCheckpoint);
}
/**
* Create a JSON representation of the current transient storage state
*/
toJSON() {
const result = {};
for (const [address, map] of this._storage.entries()) {
result[address] = {};
for (const [key, value] of map.entries()) {
result[address][key] = bytesToHex(value);
}
}
return result;
}
/**
* Clear transient storage state.
*/
clear() {
this._storage = new Map();
this._changeJournal = [];
}
}
//# sourceMappingURL=transientStorage.js.map