How to write scripts in Solidity using Foundry
How to write scripts in Solidity using Foundry and Forge. We are going to see how to create a script to deploy our smart contracts without having to use the forge create command.
In this tutorial, we are going to learn how to write scripts in Solidity using Foundry and Forge. We are going to see how to create a script to deploy our smart contracts without having to use the forge create
command.
Here is an example of script that deploys a smart contract:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import "forge-std/Script.sol";
import "../src/MyContract.sol";
contract Deploy is Script {
function run() external {
// Get the environment variable "PRIVATE_KEY"
uint256 deployer = vm.envUint("PRIVATE_KEY");
// Use that private key as the account that sends the transactions, saves and logs the them as they occur
vm.startBroadcast(deployer);
// Create an instance of the smart contract (doing that will deploy the contract when the script runs)
MyContract myContract = new MyContract("arg1", "arg2");
// Stop using the private key to send transactions
vm.stopBroadcast();
}
}
And the command to run this script is:
forge script script/MyContract.s.sol --rpc-url YOUR_RPC_URL --broadcast --verify -vvvv
How do scripts work in Foundry
When you initialise a project using Foundry, it will generate a script
folder. That folder is where you can add your Solidity scripts.
These scripts are basically contracts that inherit from the Script
contract that you can import from forge
like this: import "forge-std/Script.sol";
.
The files that you add in this folder need to have the .s.sol
extension and not just .sol
so Foundry considers these files as scripts and allows you to run them in the Forge Virtual Machine (VM).
In the contract that is in your script file, you can do whatever you want, it's normal Solidity code! The function that will be executed in that contract will be the run
function.
So you need to define an external
function called run
in your script contract otherwise it won't do anything when you run the script.
Another thing to know is that, for the transactions to be sent on the blockchain, you need to broadcast them using the vm.startBroadcast
method. You can pass the private key of an account in the parameters of that function so the transactions are sent from that account.
vm.startBroadcast(YOUR_PRIVATE_KEY) // private keys are uint256 values
Otherwise, you can call that function without parameters but you'll need to pass a private key when when running the command that runs the script. More on that in the section below on how to run the scripts.
If you call vm.startBroadcast
, then you need to call vm.stopBroadcast
at the end of the run
function.
Otherwise, you can also use vm.broadcast
which works just like vm.startBroadcast
but the difference is that it will only use the account you pass in the parameters for the next transaction. After you've done a single transaction, it will automatically stop broadcasting with that account.
You can use MetaMask if you want to have a wallet that allows you to access the private key of your account.
So that's how the the unit tests in Foundry work. Now, inside the run
function, you can interact with other smart contracts and do whatever you want. More about it in the next section.
We'll also see how to deploy a smart contract from a script file.
How to interact with another contract from your scripts and deploy contracts
How to interact with other contracts
Interacting with another contract from a script is just like interacting with another contract from a smart contract. You have to either import the code of the contract, define it in the same file or create an interface that describes it.
If you want to learn how to do it, check out this tutorial we've made one how to interact with other smart contracts in Solidity:
Here is an example:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import "forge-std/Script.sol";
import "../src/Foo.sol";
contract Deploy is Script {
function run() external {
// Interact with the deployed Foo contract that has the address in the parameters
Foo foo = new Counter(0x0000000000000000000000000000000000000000);
// Now you can call the functions of the contract
foo.bar();
}
}
How to deploy contracts
If you want to deploy a smart contract, you'll need to use the new
keyword and create a new instance of the contract you want to deploy. In the parameters, you can pass all the arguments that the constructor expects.
Here is an example:
- The contract:
contract Counter {
uint256 public number;
constructor(uint256 _number) {
number = _number;
}
function setNumber(uint256 _number) public {
number = _number;
}
}
- The script:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import "forge-std/Script.sol";
import "../src/Counter.sol";
contract Deploy is Script {
function run() external {
vm.broadcast();
// Create an instance of the smart contract (doing that will deploy the contract when the script runs)
Counter counter = new Counter(0);
}
}
How to access environment variables in the script
One of the advantages of using scripts is that you can use environment variables instead of having to pass values like the private key in the command.
To access these variables, we can use different functions depending on the type of the variable returned: envAddress
, envInt
, envUint
, envBool
, envString
or envBytes
.
In the parameters of these functions, we need to pass the name of the environment variable that we want to access and it will return the value of the environment variable.
Here is an example:
uint256 privateKey = vm.envUint("PRIVATE_KEY");
You can put these environment variables in a .env
file but before running the script, you'll need to run this command:
source .env
How to run the scripts
Finally, to run the scripts we can use the forge script
command followed by the path to the script that we want to run.
Then, you need to pass the URL of the JSON-RPC API that you use to interact with the blockchain you want to the --rpc-url
flag.
To get a URL, you can either set up your own node or use a blockchain API like Infura, Alchemy or QuickNode (I recommend Infura).
Then, we also need to use the -vvvv
flag to run our script with maximum verbosity so we see the transactions that are sent and their hash.
It's important to use that because that command can take some time since we need to wait for transactions to complete and using the log messages, you're sure that it hasn't failed.
We also need to pass the --broadcast
flag if we want to broadcast the transactions to the blockchain.
If the script deploys a contract, you can pass these 2 flags to verify your smart contract on Etherscan:
--etherscan-api-key
your Etherscan API key--verify
if you pass this flag, it will try to verify your smart contract on Etherscan
You can also put the Etherscan API key in an environment variable called ETHERSCAN_API_KEY
so you don't need to pass the --etherscan-api-key
flag.
Here is an example of how to use the forge script
command:
forge script script/MyContract.s.sol --rpc-url YOUR_RPC_URL --broadcast --verify -vvvv
Having to pass the RPC URL every time can be annoying so instead of doing that, you can put the URL in an environment variable and access it using $
followed by the name of the variable.
So if we store the RPC URL in an environment variable called MAINNET_RPC_URL
, we can run the command that way:
forge script script/MyContract.s.sol --rpc-url $MAINNET_RPC_URL --broadcast --verify -vvvv
If you have that variable in a .env
file, it works the same way but before running the forge script
command, you'll need to run source .env
.
If you need to pass a private key to send the transactions from, you can use the --private-key
flag.
And that's it 🎉
Thank you for reading this article