Skip to content

Commit

Permalink
feat: eip1153
Browse files Browse the repository at this point in the history
Implement EIP 1153 - transient storage

TLOAD 0xb3
TSTORE 0xb4

cleanup

remove dead comment

remove diff
  • Loading branch information
tynes committed Mar 13, 2022
1 parent b5d9fb0 commit cfeb752
Show file tree
Hide file tree
Showing 11 changed files with 474 additions and 1 deletion.
22 changes: 22 additions & 0 deletions packages/common/src/eips/1153.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "EIP-1153",
"number": 1153,
"comment": "Transient Storage",
"url": "https://eips.ethereum.org/EIPS/eip-1153",
"status": "Review",
"minimumHardfork": "chainstart",
"requiredEIPs": [],
"gasConfig": {},
"gasPrices": {
"tstore": {
"v": 100,
"d": "Base fee of the TSTORE opcode"
},
"tload": {
"v": 100,
"d": "Base fee of the TLOAD opcode"
}
},
"vm": {},
"pow": {}
}
1 change: 1 addition & 0 deletions packages/common/src/eips/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { eipsType } from './../types'

export const EIPs: eipsType = {
1153: require('./1153.json'),
1559: require('./1559.json'),
2315: require('./2315.json'),
2537: require('./2537.json'),
Expand Down
8 changes: 8 additions & 0 deletions packages/vm/src/evm/eei.ts
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,14 @@ export default class EEI {
}
}

transientStorageStore(key: Buffer, value: Buffer): void {
return this._state.putContractTransientStorage(this._env.address, key, value)
}

transientStorageLoad(key: Buffer): Buffer {
return this._state.getContractTransientStorage(this._env.address, key)
}

/**
* Returns the current gasCounter.
*/
Expand Down
7 changes: 7 additions & 0 deletions packages/vm/src/evm/opcodes/codes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,13 @@ const hardforkOpcodes: { hardforkName: string; opcodes: OpcodeEntry }[] = [
]

const eipOpcodes: { eip: number; opcodes: OpcodeEntry }[] = [
{
eip: 1153,
opcodes: {
0xb3: { name: 'TLOAD', isAsync: false, dynamicGas: false },
0xb4: { name: 'TSTORE', isAsync: false, dynamicGas: false },
},
},
{
eip: 2315,
opcodes: {
Expand Down
28 changes: 28 additions & 0 deletions packages/vm/src/evm/opcodes/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -867,7 +867,35 @@ export const handlers: Map<number, OpHandler> = new Map([
runState.eei.log(mem, topicsCount, topicsBuf)
},
],
// 0xb3: TLOAD
[
0xb3,
function (runState) {
const key = runState.stack.pop()
const keyBuf = key.toArrayLike(Buffer, 'be', 32)
const value = runState.eei.transientStorageLoad(keyBuf)
const valueBN = value.length ? new BN(value) : new BN(0)
runState.stack.push(valueBN)
},
],
// 0xb4: TSTORE
[
0xb4,
function (runState) {
const [key, val] = runState.stack.popN(2)

const keyBuf = key.toArrayLike(Buffer, 'be', 32)
// NOTE: this should be the shortest representation
let value
if (val.isZero()) {
value = Buffer.from([])
} else {
value = val.toArrayLike(Buffer, 'be')
}

runState.eei.transientStorageStore(keyBuf, value)
},
],
// '0xf0' range - closures
// 0xf0: CREATE
[
Expand Down
3 changes: 2 additions & 1 deletion packages/vm/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export interface VMOpts {
*
* ### Supported EIPs
*
* - [EIP-1153](https://eips.ethereum.org/EIPS/eip-1153) - Transient Storage Opcodes
* - [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559) - Fee Market
* - [EIP-2315](https://eips.ethereum.org/EIPS/eip-2315) - VM simple subroutines
* - [EIP-2537](https://eips.ethereum.org/EIPS/eip-2537) (`experimental`) - BLS12-381 precompiles
Expand Down Expand Up @@ -205,7 +206,7 @@ export default class VM extends AsyncEventEmitter {
if (opts.common) {
// Supported EIPs
const supportedEIPs = [
1559, 2315, 2537, 2565, 2718, 2929, 2930, 3198, 3529, 3541, 3607, 3855, 3860,
1153, 1559, 2315, 2537, 2565, 2718, 2929, 2930, 3198, 3529, 3541, 3607, 3855, 3860,
]
for (const eip of opts.common.eips()) {
if (!supportedEIPs.includes(eip)) {
Expand Down
2 changes: 2 additions & 0 deletions packages/vm/src/state/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export interface StateManager {
getOriginalContractStorage(address: Address, key: Buffer): Promise<Buffer>
putContractStorage(address: Address, key: Buffer, value: Buffer): Promise<void>
clearContractStorage(address: Address): Promise<void>
getContractTransientStorage(address: Address, key: Buffer): Buffer
putContractTransientStorage(address: Address, key: Buffer, value: Buffer): void
checkpoint(): Promise<void>
commit(): Promise<void>
revert(): Promise<void>
Expand Down
32 changes: 32 additions & 0 deletions packages/vm/src/state/stateManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import Common from '@ethereumjs/common'
import { StateManager, StorageDump } from './interface'
import Cache, { getCb, putCb } from './cache'
import { BaseStateManager } from './'
import TransientStorage from './transientStorage'
import { short } from '../evm/opcodes'

type StorageProof = {
Expand Down Expand Up @@ -48,6 +49,8 @@ export interface DefaultStateManagerOpts {
* A {@link SecureTrie} instance
*/
trie?: Trie

transientStorage?: TransientStorage
}

/**
Expand All @@ -63,6 +66,7 @@ export interface DefaultStateManagerOpts {
export default class DefaultStateManager extends BaseStateManager implements StateManager {
_trie: Trie
_storageTries: { [key: string]: Trie }
_transientStorage: TransientStorage

/**
* Instantiate the StateManager interface.
Expand All @@ -72,6 +76,7 @@ export default class DefaultStateManager extends BaseStateManager implements Sta

this._trie = opts.trie ?? new Trie()
this._storageTries = {}
this._transientStorage = new TransientStorage()

/*
* For a custom StateManager implementation adopt these
Expand Down Expand Up @@ -103,6 +108,7 @@ export default class DefaultStateManager extends BaseStateManager implements Sta
return new DefaultStateManager({
trie: this._trie.copy(false),
common: this._common,
transientStorage: this._transientStorage.copy(),
})
}

Expand Down Expand Up @@ -261,6 +267,29 @@ export default class DefaultStateManager extends BaseStateManager implements Sta
})
}

getContractTransientStorage(address: Address, key: Buffer): Buffer {
if (key.length !== 32) {
throw new Error('Storage key must be 32 bytes long')
}
return this._transientStorage.get(address, key)
}

putContractTransientStorage(address: Address, key: Buffer, value: Buffer): void {
if (this.DEBUG) {
this._debug(`Update transient storage for account ${address} to ${short(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')
}

this._transientStorage.put(address, key, value)
}

/**
* Clears all storage entries for the account corresponding to `address`.
* @param address - Address to clear the storage of
Expand All @@ -279,6 +308,7 @@ export default class DefaultStateManager extends BaseStateManager implements Sta
*/
async checkpoint(): Promise<void> {
this._trie.checkpoint()
this._transientStorage.checkpoint()
await super.checkpoint()
}

Expand All @@ -289,6 +319,7 @@ export default class DefaultStateManager extends BaseStateManager implements Sta
async commit(): Promise<void> {
// setup trie checkpointing
await this._trie.commit()
this._transientStorage.commit()
await super.commit()
}

Expand All @@ -299,6 +330,7 @@ export default class DefaultStateManager extends BaseStateManager implements Sta
async revert(): Promise<void> {
// setup trie checkpointing
await this._trie.revert()
this._transientStorage.revert()
this._storageTries = {}
await super.revert()
}
Expand Down
109 changes: 109 additions & 0 deletions packages/vm/src/state/transientStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { Address } from 'ethereumjs-util'

export interface TransientStorageOps {
storage?: Map<Address, Map<string, Buffer>>
changesets?: Changeset[][]
}

export type TStorage = Map<Address, Map<string, Buffer>>

export interface Changeset {
addr: Address
key: Buffer
prevValue: Buffer
}

const copyStorage = (input: TStorage): TStorage => {
const map = new Map()
for (const [addr, storage] of input.entries()) {
const copy = new Map()
for (const [key, value] of storage.entries()) {
copy.set(key, Buffer.from(value))
}
map.set(addr, copy)
}
return map
}

export default class TransientStorage {
_storage: TStorage
_changesets: Changeset[][]

constructor(opts: TransientStorageOps = {}) {
this._storage = opts.storage ?? new Map()
this._changesets = opts.changesets ?? [[]]
}

private _addChangeset(changeset: Changeset) {
const latest = this._changesets[this._changesets.length - 1]
if (!latest) {
throw new Error('no changeset initialized')
}
latest.push(changeset)
}

get(addr: Address, key: Buffer): Buffer {
const map = this._storage.get(addr)
if (!map) {
return Buffer.alloc(32, 0x00)
}
const value = map.get(key.toString('hex'))
if (!value) {
return Buffer.alloc(32, 0x00)
}
return value
}

put(addr: Address, key: Buffer, value: Buffer) {
if (!this._storage.has(addr)) {
this._storage.set(addr, new Map())
}
const map = this._storage.get(addr)

const str = key.toString('hex')
const prevValue = map?.get(str) ?? Buffer.alloc(32, 0x00)

this._addChangeset({
addr,
key,
prevValue,
})

map?.set(str, value)
}

revert() {
const changeset = this._changesets.pop()
if (!changeset) {
throw new Error('cannot revert without a changeset')
}
for (const change of changeset) {
const map = this._storage.get(change.addr)
map?.set(change.key.toString('hex'), change.prevValue)
}
}

commit(): void {
// Don't allow there to be no changeset
if (this._changesets.length <= 1) {
throw new Error('trying to commit when not checkpointed')
}
this._changesets.pop()
}

checkpoint(): void {
this._changesets.push([])
}

clear(): void {
this._storage = new Map()
this._changesets = [[]]
}

copy(): TransientStorage {
return new TransientStorage({
storage: copyStorage(this._storage),
changesets: this._changesets.slice(),
})
}
}
Loading

0 comments on commit cfeb752

Please sign in to comment.