Advanced scripting guide
Advanced scripting
Note: this guide is out of date and will be updated soon
Scripting is a powerful unlock for JavaScript applications. When used well you will start to find scripting to be some of the best ways to accomplish building your applications.
Using precompiles
With tevm.script
you can run solidity in TypeScript. But what if you want to run TypeScript in your solidity? For example you may want to call fs.readFile
directly in your solidity script. To do this you can use the tevm/precompiles
package.
Precompiles are simply contracts deployed to an address that execute a JavaScript function you define instead of solidity.
In this tutorial we will create a precompile with the tevm bundler enabled which allows us to import solidity into TypeScript files. This can be done using import { createScript } from 'tevm/contract
if the tevm bundler is not available in your project. The steps remain the same just write the solidity interface with human readable abi rather than importing it if not using a bundler.
1. Define a solidity interface for your precompile
The interface you define will be used both by your JavaScript precompile and any scripts you write.
2. Define the javascript ipmlementation
A precompile is defined with following
- A
Script
contract withwithAddress
called - The call function that returns an CallResult
You can define the call function from scratch. It is passed the raw data and you can use decodeFunctionData
to decode it. You can use encodeFunctionResult
to encode the return type
Rather than defining a call from scratch we are going to use the defineCall
utility. This utility will take an ABI and then allow us to fill in the interface for the precompile in a typesafe way. It will return the proper types from the ABI. It will also handle the encoding and decoding for you nicely.
3. Pass your precompile into MemoryClient
Pass your precompile into the MemoryClient to configure the VM with it.
We can use our precompile just like any other solidity contract.
4. Use in solidity code
In previous section we called our precompile from typescript. We can also call it from solidity.
To use it we simply just import it’s interface. We also need the address which can either be hardcoded or passed in as a parameter.
We can now call our contract in TypeScript
State overrides
The script
, call
, eth.call
and contract
methods along with their corresponding JSON-RPC procedures support state overrides. This allows you to do any of the following:
- set contract storage for a specific contract
- set value for a specific account
- set nonce for an account
- set contract bytecode for an account
Block overrides
The script
, call
, eth.call
and contract
methods along with their corresponding JSON-RPC procedures support block overrides. This allows you to do any of the following:
- set the block number for a call
- set the baseFee or blobBaseFee for a call
- set the coinbase for a call
- more
Best practices
- Distributing your precompiles if building a library
If you are building precompiles for others to use outside of your code base you should build your code with a bundler such as rollup
esbuild
or vite
so others can use your precompiles without needing to build your solidity contracts. Alternatively you can use the createContract
function to create the javascript contract rather than importing solidity.
You should also includ the precompile contract in your npm library so it can be imported in solidity
- Use
defineCall
Using defineCall
guarantees typesafety for your precompile and it’s interface.
- Be careful about forking and reverting
Precompiles can operate outside of the EVM state like in our example where we are writing to file system. If you take an action such as reverting the block it won’t unwrite from the file system. Be careful for situations where a precompile might cause issues like this.