@@ -78,6 +78,7 @@ import { GanacheStateManager } from "./state-manager";
7878import { TrieDB } from "./trie-db" ;
7979import { Trie } from "@ethereumjs/trie" ;
8080import { removeEIP3860InitCodeSizeLimitCheck } from "./helpers/common-helpers" ;
81+ import { bigIntToBuffer } from "@ganache/utils" ;
8182
8283const mclInitPromise = mcl . init ( mcl . BLS12_381 ) . then ( ( ) => {
8384 mcl . setMapToMode ( mcl . IRTF ) ; // set the right map mode; otherwise mapToG2 will return wrong values.
@@ -1106,24 +1107,37 @@ export default class Blockchain extends Emittery<BlockchainTypedEvents> {
11061107 parentBlock : Block ,
11071108 overrides : CallOverrides
11081109 ) {
1109- let result : EVMResult ;
1110+ const { header } = transaction . block ;
1111+
1112+ const timings : { time : number ; label : string } [ ] = [ ] ;
11101113
1114+ timings . push ( { time : performance . now ( ) , label : "start" } ) ;
1115+
1116+ let result : EVMResult ;
1117+ const storageChanges = new Map < Buffer , [ Buffer , Buffer , Buffer ] > ( ) ;
1118+ const stateChanges = new Map <
1119+ Buffer ,
1120+ [ [ Buffer , Buffer , Buffer , Buffer ] , [ Buffer , Buffer , Buffer , Buffer ] ]
1121+ > ( ) ;
11111122 const data = transaction . data ;
11121123 let gasLimit = transaction . gas . toBigInt ( ) ;
11131124 // subtract out the transaction's base fee from the gas limit before
11141125 // simulating the tx, because `runCall` doesn't account for raw gas costs.
11151126 const hasToAddress = transaction . to != null ;
11161127 const to = hasToAddress ? new Address ( transaction . to . toBuffer ( ) ) : null ;
11171128
1129+ //todo: getCommonForBlockNumber doesn't presently respect shanghai, so we just assume it's the same common as the fork
1130+ // this won't work as expected if simulating on blocks before shanghai.
11181131 const common = this . fallback
11191132 ? this . fallback . getCommonForBlockNumber (
11201133 this . common ,
11211134 BigInt ( transaction . block . header . number . toString ( ) )
11221135 )
11231136 : this . common ;
1137+ common . setHardfork ( "shanghai" ) ;
11241138
1125- const gasLeft =
1126- gasLimit - calculateIntrinsicGas ( data , hasToAddress , common ) ;
1139+ const intrinsicGas = calculateIntrinsicGas ( data , hasToAddress , common ) ;
1140+ const gasLeft = gasLimit - intrinsicGas ;
11271141
11281142 const transactionContext = { } ;
11291143 this . emit ( "ganache:vm:tx:before" , {
@@ -1145,24 +1159,54 @@ export default class Blockchain extends Emittery<BlockchainTypedEvents> {
11451159 false , // precompiles have already been initialized in the stateTrie
11461160 common
11471161 ) ;
1148-
1162+ //console.log({ stateRoot: await vm.stateManager.getStateRoot() });
1163+ const stateManager = vm . stateManager as GanacheStateManager ;
11491164 // take a checkpoint so the `runCall` never writes to the trie. We don't
11501165 // commit/revert later because this stateTrie is ephemeral anyway.
11511166 await vm . eei . checkpoint ( ) ;
1152-
1153- vm . evm . events . on ( "step" , ( event : InterpreterStep ) => {
1154- const logs = maybeGetLogs ( event ) ;
1155- if ( logs ) {
1156- options . logging . logger . log ( ...logs ) ;
1157- this . emit ( "ganache:vm:tx:console.log" , {
1158- context : transactionContext ,
1159- logs
1160- } ) ;
1167+ vm . evm . events . on ( "step" , async ( event : InterpreterStep ) => {
1168+ if (
1169+ event . opcode . name === "CALL" ||
1170+ event . opcode . name === "DELEGATECALL" ||
1171+ event . opcode . name === "STATICCALL" ||
1172+ event . opcode . name === "JUMP"
1173+ ) {
1174+ //console.log(event.opcode.name);
11611175 }
11621176
1163- if ( ! this . #emitStepEvent) return ;
1164- const ganacheStepEvent = makeStepEvent ( transactionContext , event ) ;
1165- this . emit ( "ganache:vm:tx:step" , ganacheStepEvent ) ;
1177+ if ( event . opcode . name === "SSTORE" ) {
1178+ const stackLength = event . stack . length ;
1179+ const keyBigInt = event . stack [ stackLength - 1 ] ;
1180+ const key =
1181+ keyBigInt === 0n
1182+ ? BUFFER_32_ZERO
1183+ : // todo: this isn't super efficient, but :shrug: we probably don't do it often
1184+ Data . toBuffer ( bigIntToBuffer ( keyBigInt ) , 32 ) ;
1185+ const valueBigInt = event . stack [ stackLength - 2 ] ;
1186+
1187+ const value = Data . toBuffer ( bigIntToBuffer ( valueBigInt ) , 32 ) ;
1188+ // todo: DELEGATE_CALL might impact the address context from which the `before` value should be fetched
1189+
1190+ const storageTrie = await stateManager . getStorageTrie (
1191+ event . codeAddress . toBuffer ( )
1192+ ) ;
1193+
1194+ const from = decode < Buffer > ( await storageTrie . get ( key ) ) ;
1195+
1196+ /*console.log({
1197+ SSTORE_refund: event.gasRefund,
1198+ address: Data.from(event.codeAddress.toBuffer()),
1199+ key: Data.from(key),
1200+ from: Data.from(from),
1201+ to: Data.from(value)
1202+ });*/
1203+
1204+ storageChanges . set ( key , [
1205+ event . codeAddress . toBuffer ( ) ,
1206+ from . length === 0 ? Buffer . alloc ( 32 ) : Data . toBuffer ( from , 32 ) ,
1207+ value
1208+ ] ) ;
1209+ }
11661210 } ) ;
11671211
11681212 const caller = transaction . from . toBuffer ( ) ;
@@ -1189,14 +1233,20 @@ export default class Blockchain extends Emittery<BlockchainTypedEvents> {
11891233 // we run this transaction so that things that rely on these values
11901234 // are correct (like contract creation!).
11911235 const fromAccount = await vm . eei . getAccount ( callerAddress ) ;
1192- fromAccount . nonce += 1n ;
1193- const txCost = gasLimit * transaction . gasPrice . toBigInt ( ) ;
1236+
1237+ // todo: re previous comment, incrementing the nonce here results in a double
1238+ // incremented nonce in the result :/ Need to validate whether this is required.
1239+ //fromAccount.nonce += 1n;
1240+ const intrinsicTxCost = intrinsicGas * transaction . gasPrice . toBigInt ( ) ;
1241+ //todo: does the execution gas get subtracted from the balance?
11941242 const startBalance = fromAccount . balance ;
11951243 // TODO: should we throw if insufficient funds?
1196- fromAccount . balance = txCost > startBalance ? 0n : startBalance - txCost ;
1244+ fromAccount . balance =
1245+ intrinsicTxCost > startBalance ? 0n : startBalance - intrinsicTxCost ;
11971246 await vm . eei . putAccount ( callerAddress , fromAccount ) ;
1198-
11991247 // finally, run the call
1248+ timings . push ( { time : performance . now ( ) , label : "running transaction" } ) ;
1249+
12001250 result = await vm . evm . runCall ( {
12011251 caller : callerAddress ,
12021252 data : transaction . data && transaction . data . toBuffer ( ) ,
@@ -1206,6 +1256,48 @@ export default class Blockchain extends Emittery<BlockchainTypedEvents> {
12061256 value : transaction . value == null ? 0n : transaction . value . toBigInt ( ) ,
12071257 block : transaction . block as any
12081258 } ) ;
1259+ timings . push ( {
1260+ time : performance . now ( ) ,
1261+ label : "finished running transaction"
1262+ } ) ;
1263+
1264+ const afterCache = stateManager [ "_cache" ] [ "_cache" ] as any ; // OrderedMap<any, any>
1265+
1266+ const asyncAccounts : Promise < void > [ ] = [ ] ;
1267+
1268+ afterCache . forEach ( i => {
1269+ asyncAccounts . push (
1270+ new Promise < void > ( async resolve => {
1271+ const addressBuf = Buffer . from ( i [ 0 ] , "hex" ) ;
1272+ const beforeAccount = await this . vm . stateManager . getAccount (
1273+ Address . from ( addressBuf )
1274+ ) ;
1275+
1276+ // todo: it's a shame to serialize here - should get the raw address directly.
1277+ const beforeRaw = beforeAccount . serialize ( ) ;
1278+ if ( ! beforeRaw . equals ( i [ 1 ] . val ) ) {
1279+ // the account has changed
1280+ const address = Buffer . from ( i [ 0 ] , "hex" ) ;
1281+ const after = decode < EthereumRawAccount > ( i [ 1 ] . val ) ;
1282+ const before = [
1283+ Quantity . toBuffer ( beforeAccount . nonce ) ,
1284+ Quantity . toBuffer ( beforeAccount . balance ) ,
1285+ beforeAccount . storageRoot ,
1286+ beforeAccount . codeHash
1287+ ] as EthereumRawAccount ;
1288+ stateChanges . set ( address , [ before , after ] ) ;
1289+ }
1290+ resolve ( ) ;
1291+ } )
1292+ ) ;
1293+ } ) ;
1294+
1295+ await Promise . all ( asyncAccounts ) ;
1296+
1297+ timings . push ( {
1298+ time : performance . now ( ) ,
1299+ label : "finished building state diff"
1300+ } ) ;
12091301 } else {
12101302 result = {
12111303 execResult : {
@@ -1215,13 +1307,49 @@ export default class Blockchain extends Emittery<BlockchainTypedEvents> {
12151307 }
12161308 } as EVMResult ;
12171309 }
1310+
12181311 this . emit ( "ganache:vm:tx:after" , {
12191312 context : transactionContext
12201313 } ) ;
12211314 if ( result . execResult . exceptionError ) {
12221315 throw new CallError ( result ) ;
12231316 } else {
1224- return Data . from ( result . execResult . returnValue || "0x" ) ;
1317+ const totalGasSpent = result . execResult . executionGasUsed + intrinsicGas ;
1318+ const maxRefund = totalGasSpent / 5n ;
1319+ const actualRefund =
1320+ result . execResult . gasRefund > maxRefund
1321+ ? maxRefund
1322+ : result . execResult . gasRefund ;
1323+
1324+ /*console.log({
1325+ totalGasSpent,
1326+ execGas: result.execResult.executionGasUsed,
1327+ maxRefund,
1328+ intrinsicGas,
1329+ refund: result.execResult.gasRefund,
1330+ actualRefund
1331+ });*/
1332+
1333+ //todo: we are treating the property "executionGasUsed" as the total gas
1334+ // cost, which it is not. Probably should derive a return object here,
1335+ // rather than just using the object returned from the EVM.
1336+ result . execResult . executionGasUsed =
1337+ ( result . execResult . executionGasUsed || 0n ) +
1338+ intrinsicGas -
1339+ actualRefund ;
1340+
1341+ const startTime = timings [ 0 ] . time ;
1342+ const timingsSummary = timings . map ( ( { time, label } ) => ( {
1343+ label,
1344+ duration : time - startTime
1345+ } ) ) ;
1346+
1347+ return {
1348+ result : result . execResult ,
1349+ storageChanges,
1350+ stateChanges,
1351+ timings : timingsSummary
1352+ } ;
12251353 }
12261354 }
12271355
0 commit comments