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:

How to interact with other smart contracts in Solidity
In this tutorial, we are going to learn how to interact with other smart contracts from your Solidity smart contract. We will learn how to call functions and read the state of 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