sol_evm 0.1.2
sol_evm: ^0.1.2 copied to clipboard
EVM opcode table, assembler, bytecode linker, and gas cost model for the sol_pkgs Solidity compiler.
// Run with: dart run example/main.dart
//
// Walks the full local-chain loop: fund an account, deploy a contract, send
// transactions to it, inspect the resulting state, trace execution at the
// opcode level, and persist the chain so it survives a restart.
import 'dart:typed_data';
import 'package:sol_evm/sol_evm.dart';
/// Runtime code for a counter: `slot0 += 1`, emit the new value, return it.
Uint8List counterRuntime() {
final topic = BigInt.parse(
'e5b1c0b7d8f3a1f9c2e4b6a8d0f2c4e6b8a0d2f4c6e8b0a2d4f6c8e0b2a4d6f8',
radix: 16,
);
return (Assembler()
// newValue = slot0 + 1
..push1(0)
..emit(Opcode.SLOAD)
..push1(1)
..add()
// store it, keeping a copy on the stack
..emit(Opcode.DUP1)
..push1(0)
..emit(Opcode.SSTORE)
// emit Incremented(newValue)
..emit(Opcode.DUP1)
..push1(0)
..emit(Opcode.MSTORE)
..push(topic)
..push1(32)
..push1(0)
..emit(Opcode.LOG1)
// return it
..push1(0)
..emit(Opcode.MSTORE)
..push1(32)
..push1(0)
..ret())
.assemble();
}
void main() {
final evm = Evm();
// ── 1. Fund an account ────────────────────────────────────────────────
final alice = addressFromHex('0x1000000000000000000000000000000000000001');
evm.setBalance(alice, BigInt.from(10).pow(18)); // 1 ETH
print(
'alice ${addressToHex(alice)} funded with ${evm.getBalance(alice)} wei',
);
// ── 2. Deploy ─────────────────────────────────────────────────────────
final deployment = evm.deploy(
from: alice,
bytecode: deploymentInitCode(counterRuntime()),
);
if (!deployment.success) {
print('deployment failed: ${deployment.failureMessage}');
return;
}
final counter = deployment.createdAddress!;
print('\ndeployed counter at ${addressToHex(counter)}');
print(' gas used: ${deployment.gasUsed}');
print(' runtime code: ${evm.getCode(counter).length} bytes');
// ── 3. Estimate, then send transactions ───────────────────────────────
final estimate = evm.estimateGas(from: alice, to: counter);
print('\nestimated gas for one increment: $estimate');
for (var i = 0; i < 3; i++) {
final result = evm.call(from: alice, to: counter);
final returned = bytesToBigInt(result.returnData);
print(
'tx ${i + 1}: returned $returned, '
'gas ${result.gasUsed}, ${result.logs.length} log(s)',
);
evm.mine();
}
// ── 4. Inspect the resulting state ────────────────────────────────────
print('\nstate after 3 transactions');
print(' counter slot 0: ${evm.getStorageAt(counter, BigInt.zero)}');
print(' alice nonce: ${evm.getNonce(alice)}');
print(' alice balance: ${evm.getBalance(alice)} wei');
print(' block number: ${evm.block.number}');
// ── 5. Read without spending anything ─────────────────────────────────
final preview = evm.simulate(from: alice, to: counter);
print(
'\nsimulated next increment -> ${bytesToBigInt(preview.returnData)} '
'(live slot 0 still ${evm.getStorageAt(counter, BigInt.zero)})',
);
// ── 6. Trace one execution opcode by opcode ───────────────────────────
final traced = evm.simulate(from: alice, to: counter, tracer: (_) {});
print('\nopcode trace (${traced.trace.length} steps)');
for (final entry in traced.trace) {
print(entry);
}
// ── 7. Persist and restore ────────────────────────────────────────────
final saved = evm.state.encode();
print('\npersisted world state: ${saved.length} bytes of JSON');
final restored = Evm(state: WorldState.decode(saved), block: evm.block);
final afterRestart = restored.call(from: alice, to: counter);
print(
'after restart, increment returned '
'${bytesToBigInt(afterRestart.returnData)} — state carried over',
);
}