A tiny Ethereum Virtual Machine (EVM) implementation from scratch, written in Go.
The aim is to keep it simple, quick to implement and interesting to learn more about the EVM.
Run the EVM (still WIP!).
go run ./cmdOr build the binary.
go build -o tiny-gevm ./cmd
./tiny-gevmUpdate the EVM documentation.
go run tools/update_readme.goClick to expand
// IEVM defines the methods that an Ethereum Virtual Machine implementation should have.typeIEVMinterface {
IArithmeticOpsIComparisonAndBitwiseOpsISHA3OpsIStackOpsIMemoryOps
}
// EVM represents an Ethereum Virtual Machine.typeEVMstruct {
stackIStackmemoryIMemorystorageIStorageenvExecutionEnvironmentstateMachineState
}
// ExecutionEnvironment represents the EVM execution environment.typeExecutionEnvironmentstruct {
// Machine code to be executed by the EVM.code []byte
}
// MachineState represents the EVM state.typeMachineStatestruct {
// Program counter.pcint
}Click to expand
// IStack defines the methods that a stack implementation should have.typeIStackinterface {
// Push adds a new element to the top of the stack.// It returns an error if the stack is full.Push(*uint256.Int) error// Pop removes and returns the top element from the stack.// If the stack is empty, it returns a zero-value 32-byte array and an error.Pop() (*uint256.Int, error)
// Get returns the i-th element from the stack without poping it.// The index is 1-based, where 1 refers to the top of the stack (last element).// For example, Get(1) returns the top element, Get(2) returns the second from the top, and so on.// If the stack is empty, it returns a zero-value 32-byte array and an error.Get(iint) (*uint256.Int, error)
// Exchange the first and i-th stack item.// If the stack is empty, it returns an error.Swap(iint) error// Size returns the number of elements currently on the stack.Size() int
}
// Stack represents a last-in-first-out (LIFO) stack of 32-byte arrays.typeStackstruct {
data []uint256.Int
}Click to expand
// IMemory defines the methods that a memory implementation should have.typeIMemoryinterface {
// Store writes a byte slice to memory at the specified offset.// If the offset plus the length of the value exceeds the current memory size,// the memory is automatically expanded to accommodate the new data.Store(value []byte, offsetint)
// Load retrieves a slice of memory starting at the given offset with the specified size.// It handles cases where the requested region may extend beyond the current memory size.// Returns a byte slice of length 'size', zero-padded if necessary.Load(offset, sizeint) []byte// Load a byte from memory at the given offset.LoadByte(offsetint) byte// Load a word (32 bytes) from memory at the given offset.LoadWord(offsetint) [32]byte// Store a byte to memory at the given offset.StoreByte(valuebyte, offsetint)
// Store a word (32 bytes) to memory at the given offset.StoreWord(word [32]byte, offsetint)
}
// Memory represents a byte-addressable memory structure.typeMemorystruct {
data []byte
}Click to expand
// IStorage defines the methods that a storage implementation should have.typeIStorageinterface {
// Store writes a 32-byte word to storage at the specified key.// If the key already exists, its value will be overwritten.Store(keyint, value [32]byte)
// Load retrieves a 32-byte word from storage using the specified key.// If the key does not exist in the storage, it returns an empty 32-byte word.Load(keyint) [32]byte
}
// Storage represents a word-addressable storage structure.typeStoragestruct {
datamap[int][32]byte
}