mirror of
https://github.com/status-im/nimbus-eth1.git
synced 2025-01-12 05:14:14 +00:00
implement EIP-1153: Transient storage
new EVM opcodes: - TLOAD 0xb3 - TSTORE 0xb4
This commit is contained in:
parent
15cc9f962e
commit
6544adf360
@ -102,6 +102,9 @@ proc asyncProcessTransactionImpl(
|
|||||||
let txRes = roDB.validateTransaction(tx, sender, header.gasLimit, baseFee256, fork)
|
let txRes = roDB.validateTransaction(tx, sender, header.gasLimit, baseFee256, fork)
|
||||||
if txRes.isOk:
|
if txRes.isOk:
|
||||||
|
|
||||||
|
# EIP-1153
|
||||||
|
vmState.stateDB.clearTransientStorage()
|
||||||
|
|
||||||
# Execute the transaction.
|
# Execute the transaction.
|
||||||
let
|
let
|
||||||
accTx = vmState.stateDB.beginSavepoint
|
accTx = vmState.stateDB.beginSavepoint
|
||||||
|
@ -186,6 +186,9 @@ proc vmExecGrabItem(pst: TxPackerStateRef; item: TxItemRef): Result[bool,void]
|
|||||||
if not xp.classifyValidatePacked(vmState, item):
|
if not xp.classifyValidatePacked(vmState, item):
|
||||||
return ok(false) # continue with next account
|
return ok(false) # continue with next account
|
||||||
|
|
||||||
|
# EIP-1153
|
||||||
|
vmState.stateDB.clearTransientStorage()
|
||||||
|
|
||||||
let
|
let
|
||||||
accTx = vmState.stateDB.beginSavepoint
|
accTx = vmState.stateDB.beginSavepoint
|
||||||
gasUsed = pst.runTx(item) # this is the crucial part, running the tx
|
gasUsed = pst.runTx(item) # this is the crucial part, running the tx
|
||||||
|
@ -1,3 +1,13 @@
|
|||||||
|
# Nimbus
|
||||||
|
# Copyright (c) 2023 Status Research & Development GmbH
|
||||||
|
# Licensed under either of
|
||||||
|
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0)
|
||||||
|
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or
|
||||||
|
# http://opensource.org/licenses/MIT)
|
||||||
|
# at your option. This file may not be copied, modified, or distributed except
|
||||||
|
# according to those terms.
|
||||||
|
|
||||||
import
|
import
|
||||||
tables, sets,
|
tables, sets,
|
||||||
stint,
|
stint,
|
||||||
|
@ -1,10 +1,21 @@
|
|||||||
|
# Nimbus
|
||||||
|
# Copyright (c) 2023 Status Research & Development GmbH
|
||||||
|
# Licensed under either of
|
||||||
|
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0)
|
||||||
|
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or
|
||||||
|
# http://opensource.org/licenses/MIT)
|
||||||
|
# at your option. This file may not be copied, modified, or distributed except
|
||||||
|
# according to those terms.
|
||||||
|
|
||||||
import
|
import
|
||||||
std/[tables, hashes, sets],
|
std/[tables, hashes, sets],
|
||||||
eth/[common, rlp], eth/trie/[hexary, db, trie_defs],
|
eth/[common, rlp], eth/trie/[hexary, db, trie_defs],
|
||||||
../constants, ../utils/utils, storage_types,
|
../constants, ../utils/utils, storage_types,
|
||||||
../../stateless/multi_keys,
|
../../stateless/multi_keys,
|
||||||
./distinct_tries,
|
./distinct_tries,
|
||||||
./access_list as ac_access_list
|
./access_list as ac_access_list,
|
||||||
|
./transient_storage
|
||||||
|
|
||||||
const
|
const
|
||||||
debugAccountsCache = false
|
debugAccountsCache = false
|
||||||
@ -53,6 +64,7 @@ type
|
|||||||
selfDestruct: HashSet[EthAddress]
|
selfDestruct: HashSet[EthAddress]
|
||||||
logEntries: seq[Log]
|
logEntries: seq[Log]
|
||||||
accessList: ac_access_list.AccessList
|
accessList: ac_access_list.AccessList
|
||||||
|
transientStorage: TransientStorage
|
||||||
state: TransactionState
|
state: TransactionState
|
||||||
when debugAccountsCache:
|
when debugAccountsCache:
|
||||||
depth: int
|
depth: int
|
||||||
@ -119,6 +131,7 @@ proc beginSavepoint*(ac: var AccountsCache): SavePoint =
|
|||||||
new result
|
new result
|
||||||
result.cache = initTable[EthAddress, RefAccount]()
|
result.cache = initTable[EthAddress, RefAccount]()
|
||||||
result.accessList.init()
|
result.accessList.init()
|
||||||
|
result.transientStorage.init()
|
||||||
result.state = Pending
|
result.state = Pending
|
||||||
result.parentSavepoint = ac.savePoint
|
result.parentSavepoint = ac.savePoint
|
||||||
ac.savePoint = result
|
ac.savePoint = result
|
||||||
@ -151,6 +164,7 @@ proc commit*(ac: var AccountsCache, sp: SavePoint) =
|
|||||||
for k, v in sp.cache:
|
for k, v in sp.cache:
|
||||||
sp.parentSavepoint.cache[k] = v
|
sp.parentSavepoint.cache[k] = v
|
||||||
|
|
||||||
|
ac.savePoint.transientStorage.merge(sp.transientStorage)
|
||||||
ac.savePoint.accessList.merge(sp.accessList)
|
ac.savePoint.accessList.merge(sp.accessList)
|
||||||
ac.savePoint.selfDestruct.incl sp.selfDestruct
|
ac.savePoint.selfDestruct.incl sp.selfDestruct
|
||||||
ac.savePoint.logEntries.add sp.logEntries
|
ac.savePoint.logEntries.add sp.logEntries
|
||||||
@ -688,6 +702,24 @@ func inAccessList*(ac: AccountsCache, address: EthAddress, slot: UInt256): bool
|
|||||||
return
|
return
|
||||||
sp = sp.parentSavepoint
|
sp = sp.parentSavepoint
|
||||||
|
|
||||||
|
func getTransientStorage*(ac: AccountsCache,
|
||||||
|
address: EthAddress, slot: UInt256): UInt256 =
|
||||||
|
var sp = ac.savePoint
|
||||||
|
while sp != nil:
|
||||||
|
let (ok, res) = sp.transientStorage.getStorage(address, slot)
|
||||||
|
if ok:
|
||||||
|
return res
|
||||||
|
sp = sp.parentSavepoint
|
||||||
|
|
||||||
|
proc setTransientStorage*(ac: AccountsCache,
|
||||||
|
address: EthAddress, slot, val: UInt256) =
|
||||||
|
ac.savePoint.transientStorage.setStorage(address, slot, val)
|
||||||
|
|
||||||
|
proc clearTransientStorage*(ac: AccountsCache) {.inline.} =
|
||||||
|
# make sure all savepoint already committed
|
||||||
|
doAssert(ac.savePoint.parentSavepoint.isNil)
|
||||||
|
ac.savePoint.transientStorage.clear()
|
||||||
|
|
||||||
proc rootHash*(db: ReadOnlyStateDB): KeccakHash {.borrow.}
|
proc rootHash*(db: ReadOnlyStateDB): KeccakHash {.borrow.}
|
||||||
proc getCodeHash*(db: ReadOnlyStateDB, address: EthAddress): Hash256 {.borrow.}
|
proc getCodeHash*(db: ReadOnlyStateDB, address: EthAddress): Hash256 {.borrow.}
|
||||||
proc getStorageRoot*(db: ReadOnlyStateDB, address: EthAddress): Hash256 {.borrow.}
|
proc getStorageRoot*(db: ReadOnlyStateDB, address: EthAddress): Hash256 {.borrow.}
|
||||||
@ -703,3 +735,5 @@ proc isEmptyAccount*(db: ReadOnlyStateDB, address: EthAddress): bool {.borrow.}
|
|||||||
proc getCommittedStorage*(db: ReadOnlyStateDB, address: EthAddress, slot: UInt256): UInt256 {.borrow.}
|
proc getCommittedStorage*(db: ReadOnlyStateDB, address: EthAddress, slot: UInt256): UInt256 {.borrow.}
|
||||||
func inAccessList*(ac: ReadOnlyStateDB, address: EthAddress): bool {.borrow.}
|
func inAccessList*(ac: ReadOnlyStateDB, address: EthAddress): bool {.borrow.}
|
||||||
func inAccessList*(ac: ReadOnlyStateDB, address: EthAddress, slot: UInt256): bool {.borrow.}
|
func inAccessList*(ac: ReadOnlyStateDB, address: EthAddress, slot: UInt256): bool {.borrow.}
|
||||||
|
func getTransientStorage*(ac: ReadOnlyStateDB,
|
||||||
|
address: EthAddress, slot: UInt256): UInt256 {.borrow.}
|
||||||
|
@ -1,3 +1,13 @@
|
|||||||
|
# Nimbus
|
||||||
|
# Copyright (c) 2023 Status Research & Development GmbH
|
||||||
|
# Licensed under either of
|
||||||
|
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0)
|
||||||
|
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or
|
||||||
|
# http://opensource.org/licenses/MIT)
|
||||||
|
# at your option. This file may not be copied, modified, or distributed except
|
||||||
|
# according to those terms.
|
||||||
|
|
||||||
import
|
import
|
||||||
eth/common
|
eth/common
|
||||||
|
|
||||||
|
69
nimbus/db/transient_storage.nim
Normal file
69
nimbus/db/transient_storage.nim
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
# Nimbus
|
||||||
|
# Copyright (c) 2023 Status Research & Development GmbH
|
||||||
|
# Licensed under either of
|
||||||
|
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0)
|
||||||
|
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or
|
||||||
|
# http://opensource.org/licenses/MIT)
|
||||||
|
# at your option. This file may not be copied, modified, or distributed except
|
||||||
|
# according to those terms.
|
||||||
|
|
||||||
|
import
|
||||||
|
tables,
|
||||||
|
stint,
|
||||||
|
eth/common
|
||||||
|
|
||||||
|
type
|
||||||
|
StorageTable = ref object
|
||||||
|
map: Table[UInt256, UInt256]
|
||||||
|
|
||||||
|
TransientStorage* = object
|
||||||
|
map: Table[EthAddress, StorageTable]
|
||||||
|
|
||||||
|
#######################################################################
|
||||||
|
# Private helpers
|
||||||
|
#######################################################################
|
||||||
|
|
||||||
|
proc merge(a, b: StorageTable) =
|
||||||
|
for k, v in b.map:
|
||||||
|
a.map[k] = v
|
||||||
|
|
||||||
|
#######################################################################
|
||||||
|
# Public functions
|
||||||
|
#######################################################################
|
||||||
|
|
||||||
|
proc init*(ac: var TransientStorage) =
|
||||||
|
ac.map = initTable[EthAddress, StorageTable]()
|
||||||
|
|
||||||
|
proc init*(_: type TransientStorage): TransientStorage {.inline.} =
|
||||||
|
result.init()
|
||||||
|
|
||||||
|
func getStorage*(ac: TransientStorage,
|
||||||
|
address: EthAddress, slot: UInt256): (bool, UInt256) =
|
||||||
|
var table = ac.map.getOrDefault(address)
|
||||||
|
if table.isNil:
|
||||||
|
return (false, 0.u256)
|
||||||
|
|
||||||
|
table.map.withValue(slot, val):
|
||||||
|
return (true, val[])
|
||||||
|
do:
|
||||||
|
return (false, 0.u256)
|
||||||
|
|
||||||
|
proc setStorage*(ac: var TransientStorage,
|
||||||
|
address: EthAddress, slot, value: UInt256) =
|
||||||
|
var table = ac.map.getOrDefault(address)
|
||||||
|
if table.isNil:
|
||||||
|
table = StorageTable()
|
||||||
|
ac.map[address] = table
|
||||||
|
|
||||||
|
table.map[slot] = value
|
||||||
|
|
||||||
|
proc merge*(ac: var TransientStorage, other: TransientStorage) =
|
||||||
|
for k, v in other.map:
|
||||||
|
ac.map.withValue(k, val):
|
||||||
|
val[].merge(v)
|
||||||
|
do:
|
||||||
|
ac.map[k] = v
|
||||||
|
|
||||||
|
proc clear*(ac: var TransientStorage) {.inline.} =
|
||||||
|
ac.map.clear()
|
@ -186,6 +186,22 @@ template getCode*(c: Computation, address: EthAddress): seq[byte] =
|
|||||||
else:
|
else:
|
||||||
c.vmState.readOnlyStateDB.getCode(address)
|
c.vmState.readOnlyStateDB.getCode(address)
|
||||||
|
|
||||||
|
template setTransientStorage*(c: Computation, slot, val: UInt256) =
|
||||||
|
when evmc_enabled:
|
||||||
|
# TODO: EIP-1153
|
||||||
|
discard
|
||||||
|
else:
|
||||||
|
c.vmState.stateDB.
|
||||||
|
setTransientStorage(c.msg.contractAddress, slot, val)
|
||||||
|
|
||||||
|
template getTransientStorage*(c: Computation, slot: UInt256): UInt256 =
|
||||||
|
when evmc_enabled:
|
||||||
|
# TODO: EIP-1153
|
||||||
|
0.u256
|
||||||
|
else:
|
||||||
|
c.vmState.readOnlyStateDB.
|
||||||
|
getTransientStorage(c.msg.contractAddress, slot)
|
||||||
|
|
||||||
proc newComputation*(vmState: BaseVMState, message: Message,
|
proc newComputation*(vmState: BaseVMState, message: Message,
|
||||||
salt: ContractSalt = ZERO_CONTRACTSALT): Computation =
|
salt: ContractSalt = ZERO_CONTRACTSALT): Computation =
|
||||||
new result
|
new result
|
||||||
|
@ -54,6 +54,7 @@ type
|
|||||||
GasBlockhash, # Payment for BLOCKHASH operation.
|
GasBlockhash, # Payment for BLOCKHASH operation.
|
||||||
GasExtCodeHash, # Payment for contract's code hashing
|
GasExtCodeHash, # Payment for contract's code hashing
|
||||||
GasInitcodeWord # Payment for each word (rounded up) for initcode
|
GasInitcodeWord # Payment for each word (rounded up) for initcode
|
||||||
|
GasWarmStorageRead # Transient storage read and write cost.
|
||||||
|
|
||||||
GasFeeSchedule = array[GasFeeKind, GasInt]
|
GasFeeSchedule = array[GasFeeKind, GasInt]
|
||||||
|
|
||||||
@ -706,6 +707,10 @@ template gasCosts(fork: EVMFork, prefix, ResultGasCostsName: untyped) =
|
|||||||
Log3: memExpansion `prefix gasLog3`,
|
Log3: memExpansion `prefix gasLog3`,
|
||||||
Log4: memExpansion `prefix gasLog4`,
|
Log4: memExpansion `prefix gasLog4`,
|
||||||
|
|
||||||
|
# b0s: Transient storage operations
|
||||||
|
Tload: fixed GasWarmStorageRead,
|
||||||
|
Tstore: fixed GasWarmStorageRead,
|
||||||
|
|
||||||
# f0s: System operations
|
# f0s: System operations
|
||||||
Create: complex `prefix gasCreate`,
|
Create: complex `prefix gasCreate`,
|
||||||
Call: complex `prefix gasCall`,
|
Call: complex `prefix gasCall`,
|
||||||
@ -759,7 +764,8 @@ const
|
|||||||
GasCopy: 3,
|
GasCopy: 3,
|
||||||
GasBlockhash: 20,
|
GasBlockhash: 20,
|
||||||
GasExtCodeHash: 400,
|
GasExtCodeHash: 400,
|
||||||
GasInitcodeWord: 0 # Changed to 2 in EIP-3860
|
GasInitcodeWord: 0, # Changed to 2 in EIP-3860
|
||||||
|
GasWarmStorageRead: WarmStorageReadCost
|
||||||
]
|
]
|
||||||
|
|
||||||
# Create the schedule for each forks
|
# Create the schedule for each forks
|
||||||
|
@ -173,7 +173,12 @@ type
|
|||||||
|
|
||||||
Nop0xA5, Nop0xA6, Nop0xA7, Nop0xA8, Nop0xA9, Nop0xAA,
|
Nop0xA5, Nop0xA6, Nop0xA7, Nop0xA8, Nop0xA9, Nop0xAA,
|
||||||
Nop0xAB, Nop0xAC, Nop0xAD, Nop0xAE, Nop0xAF, Nop0xB0,
|
Nop0xAB, Nop0xAC, Nop0xAD, Nop0xAE, Nop0xAF, Nop0xB0,
|
||||||
Nop0xB1, Nop0xB2, Nop0xB3, Nop0xB4, Nop0xB5, Nop0xB6,
|
Nop0xB1, Nop0xB2,
|
||||||
|
|
||||||
|
Tload = 0xb3, ## Load word from transient storage.
|
||||||
|
Tstore = 0xb4, ## Save word to transient storage.
|
||||||
|
|
||||||
|
Nop0xB5, Nop0xB6,
|
||||||
Nop0xB7, Nop0xB8, Nop0xB9, Nop0xBA, Nop0xBB, Nop0xBC,
|
Nop0xB7, Nop0xB8, Nop0xB9, Nop0xBA, Nop0xBB, Nop0xBC,
|
||||||
Nop0xBD, Nop0xBE, Nop0xBF, Nop0xC0, Nop0xC1, Nop0xC2,
|
Nop0xBD, Nop0xBE, Nop0xBF, Nop0xC0, Nop0xC1, Nop0xC2,
|
||||||
Nop0xC3, Nop0xC4, Nop0xC5, Nop0xC6, Nop0xC7, Nop0xC8,
|
Nop0xC3, Nop0xC4, Nop0xC5, Nop0xC6, Nop0xC7, Nop0xC8,
|
||||||
|
@ -86,6 +86,10 @@ const
|
|||||||
Vm2OpShanghaiAndLater* =
|
Vm2OpShanghaiAndLater* =
|
||||||
Vm2OpParisAndLater - {FkParis}
|
Vm2OpParisAndLater - {FkParis}
|
||||||
|
|
||||||
|
# TODO: fix this after EIP-1153 accepted in a fork
|
||||||
|
Vm2Op1153AndLater* =
|
||||||
|
Vm2OpShanghaiAndLater - {FkShanghai}
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# End
|
# End
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
@ -305,6 +305,22 @@ const
|
|||||||
## on machine state during execution.
|
## on machine state during execution.
|
||||||
discard
|
discard
|
||||||
|
|
||||||
|
tloadOp: Vm2OpFn = proc (k: var Vm2Ctx) =
|
||||||
|
## 0xb3, Load word from transient storage.
|
||||||
|
let
|
||||||
|
slot = k.cpt.stack.peek()
|
||||||
|
val = k.cpt.getTransientStorage(slot)
|
||||||
|
k.cpt.stack.top(val)
|
||||||
|
|
||||||
|
tstoreOp: Vm2OpFn = proc (k: var Vm2Ctx) =
|
||||||
|
## 0xb4, Save word to transient storage.
|
||||||
|
checkInStaticContext(k.cpt)
|
||||||
|
|
||||||
|
let
|
||||||
|
slot = k.cpt.stack.popInt()
|
||||||
|
val = k.cpt.stack.popInt()
|
||||||
|
k.cpt.setTransientStorage(slot, val)
|
||||||
|
|
||||||
#[
|
#[
|
||||||
EIP-2315: temporary disabled
|
EIP-2315: temporary disabled
|
||||||
Reason : not included in berlin hard fork
|
Reason : not included in berlin hard fork
|
||||||
@ -493,6 +509,22 @@ const
|
|||||||
info: "Mark a valid destination for jumps",
|
info: "Mark a valid destination for jumps",
|
||||||
exec: (prep: vm2OpIgnore,
|
exec: (prep: vm2OpIgnore,
|
||||||
run: jumpDestOp,
|
run: jumpDestOp,
|
||||||
|
post: vm2OpIgnore)),
|
||||||
|
|
||||||
|
(opCode: Tload, ## 0xb3, Load word from transient storage.
|
||||||
|
forks: Vm2Op1153AndLater,
|
||||||
|
name: "tLoad",
|
||||||
|
info: "Load word from transient storage",
|
||||||
|
exec: (prep: vm2OpIgnore,
|
||||||
|
run: tloadOp,
|
||||||
|
post: vm2OpIgnore)),
|
||||||
|
|
||||||
|
(opCode: Tstore, ## 0xb4, Save word to transient storage.
|
||||||
|
forks: Vm2Op1153AndLater,
|
||||||
|
name: "tStore",
|
||||||
|
info: "Save word to transient storage",
|
||||||
|
exec: (prep: vm2OpIgnore,
|
||||||
|
run: tstoreOp,
|
||||||
post: vm2OpIgnore))]
|
post: vm2OpIgnore))]
|
||||||
|
|
||||||
#[
|
#[
|
||||||
|
@ -35,7 +35,6 @@ proc setupTxContext*(vmState: BaseVMState, origin: EthAddress, gasPrice: GasInt,
|
|||||||
vmState.determineFork
|
vmState.determineFork
|
||||||
vmState.gasCosts = vmState.fork.forkToSchedule
|
vmState.gasCosts = vmState.fork.forkToSchedule
|
||||||
|
|
||||||
|
|
||||||
# FIXME-awkwardFactoring: the factoring out of the pre and
|
# FIXME-awkwardFactoring: the factoring out of the pre and
|
||||||
# post parts feels awkward to me, but for now I'd really like
|
# post parts feels awkward to me, but for now I'd really like
|
||||||
# not to have too much duplicated code between sync and async.
|
# not to have too much duplicated code between sync and async.
|
||||||
|
@ -216,5 +216,71 @@ proc stateDBMain*() =
|
|||||||
check ac.verifySlots(0xcc, 0x01)
|
check ac.verifySlots(0xcc, 0x01)
|
||||||
check ac.verifySlots(0xdd, 0x04)
|
check ac.verifySlots(0xdd, 0x04)
|
||||||
|
|
||||||
|
test "transient storage operations":
|
||||||
|
var ac = init(AccountsCache, acDB)
|
||||||
|
|
||||||
|
proc tStore(ac: AccountsCache, address, slot, val: int) =
|
||||||
|
ac.setTransientStorage(address.initAddr, slot.u256, val.u256)
|
||||||
|
|
||||||
|
proc tLoad(ac: AccountsCache, address, slot: int): UInt256 =
|
||||||
|
ac.getTransientStorage(address.initAddr, slot.u256)
|
||||||
|
|
||||||
|
proc vts(ac: AccountsCache, address, slot, val: int): bool =
|
||||||
|
ac.tLoad(address, slot) == val.u256
|
||||||
|
|
||||||
|
ac.tStore(0xaa, 3, 66)
|
||||||
|
ac.tStore(0xbb, 1, 33)
|
||||||
|
ac.tStore(0xbb, 2, 99)
|
||||||
|
|
||||||
|
check ac.vts(0xaa, 3, 66)
|
||||||
|
check ac.vts(0xbb, 1, 33)
|
||||||
|
check ac.vts(0xbb, 2, 99)
|
||||||
|
check ac.vts(0xaa, 1, 33) == false
|
||||||
|
check ac.vts(0xbb, 1, 66) == false
|
||||||
|
|
||||||
|
var sp = ac.beginSavepoint
|
||||||
|
# some new ones
|
||||||
|
ac.tStore(0xaa, 3, 77)
|
||||||
|
ac.tStore(0xbb, 1, 55)
|
||||||
|
ac.tStore(0xcc, 7, 88)
|
||||||
|
|
||||||
|
check ac.vts(0xaa, 3, 77)
|
||||||
|
check ac.vts(0xbb, 1, 55)
|
||||||
|
check ac.vts(0xcc, 7, 88)
|
||||||
|
|
||||||
|
check ac.vts(0xaa, 3, 66) == false
|
||||||
|
check ac.vts(0xbb, 1, 33) == false
|
||||||
|
check ac.vts(0xbb, 2, 99)
|
||||||
|
|
||||||
|
ac.rollback(sp)
|
||||||
|
check ac.vts(0xaa, 3, 66)
|
||||||
|
check ac.vts(0xbb, 1, 33)
|
||||||
|
check ac.vts(0xbb, 2, 99)
|
||||||
|
check ac.vts(0xcc, 7, 88) == false
|
||||||
|
|
||||||
|
sp = ac.beginSavepoint
|
||||||
|
ac.tStore(0xaa, 3, 44)
|
||||||
|
ac.tStore(0xaa, 4, 55)
|
||||||
|
ac.tStore(0xbb, 1, 22)
|
||||||
|
ac.tStore(0xdd, 2, 66)
|
||||||
|
|
||||||
|
ac.commit(sp)
|
||||||
|
check ac.vts(0xaa, 3, 44)
|
||||||
|
check ac.vts(0xaa, 4, 55)
|
||||||
|
check ac.vts(0xbb, 1, 22)
|
||||||
|
check ac.vts(0xbb, 1, 55) == false
|
||||||
|
check ac.vts(0xbb, 2, 99)
|
||||||
|
check ac.vts(0xcc, 7, 88) == false
|
||||||
|
check ac.vts(0xdd, 2, 66)
|
||||||
|
|
||||||
|
ac.clearTransientStorage()
|
||||||
|
check ac.vts(0xaa, 3, 44) == false
|
||||||
|
check ac.vts(0xaa, 4, 55) == false
|
||||||
|
check ac.vts(0xbb, 1, 22) == false
|
||||||
|
check ac.vts(0xbb, 1, 55) == false
|
||||||
|
check ac.vts(0xbb, 2, 99) == false
|
||||||
|
check ac.vts(0xcc, 7, 88) == false
|
||||||
|
check ac.vts(0xdd, 2, 66) == false
|
||||||
|
|
||||||
when isMainModule:
|
when isMainModule:
|
||||||
stateDBMain()
|
stateDBMain()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user