Skip to main content
Additional utility functions for working with storage keys.

hashCode()

Computes a hash code for a storage key, useful for hash-based data structures.

Signature

    Parameters

    • key (BrandedStorageKey) - Storage key to hash

    Returns

    number - 32-bit hash code (JavaScript) or u64 (Zig)

    Examples

      Use Cases

      HashMap Keys

      import * as State from 'tevm/State';
      
      // Custom hash map implementation
      class StorageHashMap {
        private buckets: Array<Array<{ key: BrandedStorageKey, value: bigint }>> = [];
        private size = 1024; // Bucket count
      
        set(key: BrandedStorageKey, value: bigint): void {
          const hash = State.StorageKey.hashCode(key);
          const bucketIndex = Math.abs(hash) % this.size;
      
          if (!this.buckets[bucketIndex]) {
            this.buckets[bucketIndex] = [];
          }
      
          const bucket = this.buckets[bucketIndex];
          const existing = bucket.findIndex(entry =>
            State.StorageKey.equals(entry.key, key)
          );
      
          if (existing >= 0) {
            bucket[existing].value = value;
          } else {
            bucket.push({ key, value });
          }
        }
      
        get(key: BrandedStorageKey): bigint | undefined {
          const hash = State.StorageKey.hashCode(key);
          const bucketIndex = Math.abs(hash) % this.size;
          const bucket = this.buckets[bucketIndex];
      
          if (!bucket) return undefined;
      
          const entry = bucket.find(e => State.StorageKey.equals(e.key, key));
          return entry?.value;
        }
      }
      
      const map = new StorageHashMap();
      const key = State.StorageKey(contractAddr, 0n);
      
      map.set(key, 1000n);
      console.log(map.get(key)); // 1000n
      

      Partitioning

      import * as State from 'tevm/State';
      
      // Partition storage keys into buckets for parallel processing
      function partitionKeys(
        keys: BrandedStorageKey[],
        partitionCount: number
      ): BrandedStorageKey[][] {
        const partitions: BrandedStorageKey[][] = Array(
          { length: partitionCount },
          () => []
        );
      
        for (const key of keys) {
          const hash = State.StorageKey.hashCode(key);
          const partitionIndex = Math.abs(hash) % partitionCount;
          partitions[partitionIndex].push(key);
        }
      
        return partitions;
      }
      
      const keys = [
        State.StorageKey(contractAddr, 0n),
        State.StorageKey(contractAddr, 1n),
        State.StorageKey(contractAddr, 2n),
        State.StorageKey(contractAddr, 3n),
        State.StorageKey(contractAddr, 4n)
      ];
      
      // Split into 2 partitions
      const partitions = partitionKeys(keys, 2);
      console.log(`Partition 0: ${partitions[0].length} keys`);
      console.log(`Partition 1: ${partitions[1].length} keys`);
      
      // Process partitions in parallel
      await Promise.all(
        partitions.map(async (partition, index) => {
          console.log(`Processing partition ${index}...`);
          for (const key of partition) {
            await processStorageKey(key);
          }
        })
      );
      

      Caching

      import * as State from 'tevm/State';
      
      // LRU cache with hash-based bucketing
      class StorageLRUCache {
        private cache = new Map<number, { key: BrandedStorageKey, value: bigint, timestamp: number }>();
        private maxSize = 1000;
      
        set(key: BrandedStorageKey, value: bigint): void {
          const hash = State.StorageKey.hashCode(key);
      
          // Evict oldest if at capacity
          if (this.cache.size >= this.maxSize) {
            let oldestHash = -1;
            let oldestTime = Infinity;
      
            for (const [h, entry] of this.cache.entries()) {
              if (entry.timestamp < oldestTime) {
                oldestTime = entry.timestamp;
                oldestHash = h;
              }
            }
      
            this.cache.delete(oldestHash);
          }
      
          this.cache.set(hash, { key, value, timestamp: Date.now() });
        }
      
        get(key: BrandedStorageKey): bigint | undefined {
          const hash = State.StorageKey.hashCode(key);
          const entry = this.cache.get(hash);
      
          if (entry && State.StorageKey.equals(entry.key, key)) {
            // Update timestamp for LRU
            entry.timestamp = Date.now();
            return entry.value;
          }
      
          return undefined;
        }
      }
      

      Sharding

      import * as State from 'tevm/State';
      
      // Distribute storage keys across shards
      function getShardForKey(key: BrandedStorageKey, shardCount: number): number {
        const hash = State.StorageKey.hashCode(key);
        return Math.abs(hash) % shardCount;
      }
      
      // Example: Route requests to different database shards
      async function getStorageValue(
        key: BrandedStorageKey,
        shards: Database[]
      ): Promise<bigint | undefined> {
        const shardIndex = getShardForKey(key, shards.length);
        const shard = shards[shardIndex];
      
        const keyStr = State.StorageKey.toString(key);
        return await shard.get(keyStr);
      }
      
      // Setup 4 shards
      const shards = [
        new Database('shard0'),
        new Database('shard1'),
        new Database('shard2'),
        new Database('shard3')
      ];
      
      const key = State.StorageKey(contractAddr, 0n);
      const value = await getStorageValue(key, shards);
      console.log(`Shard ${getShardForKey(key, 4)}: ${value}`);
      

      Implementation Notes

      • Hash code is computed from both address and slot
      • Same key always produces same hash (deterministic)
      • Different keys may produce same hash (collisions possible)
      • Hash distribution is pseudo-random for good bucketing

      Properties

      • Deterministic: Same input always produces same output
      • Fast: O(1) time complexity
      • Collision-resistant: Good distribution, but collisions possible
      • 32-bit: JavaScript returns 32-bit signed integer
      • 64-bit: Zig returns 64-bit unsigned integer