# Solidity Development Operations This package provides a set of tools and scripts to help with the development of Solidity smart contracts: - Utilities to simplify the testing of smart contracts using Foundry. - Utilities to simplify the deployment of smart contracts using Foundry. - Utilities to manage the existing deployments of smart contracts. ## Disclaimer This package is still under development and should be used with caution. It is not recommended to use it in a production environment. ## Docs Forge docs are automatically deployed [here](https://solidity-devops.vercel.app). ## Setup In order to use this package, it needs to be installed as a dependency in your project. It is recommended to install it as a dev dependency. This package is published to [GitHub Packages](https://github.com/synapsecns/sanguine/packages). Configure your `.npmrc` to use the GitHub registry for `@synapsecns` packages: ``` @synapsecns:registry=https://npm.pkg.github.com ``` Then install: ```bash npm install --save-dev @synapsecns/solidity-devops # Or, if you are using yarn yarn add --dev @synapsecns/solidity-devops ``` Following files are necessary to be present in the root of your project: - `.env`: storage for the sensitive configuration variables. - `devops.json`: configuration file for the devops package. - `foundry.toml`: configuration file for Foundry. ### `.env` **Note: This file must be added to `.gitignore` to avoid sharing sensitive information.** See the [example .env](./.env.example) file. Following structure is required: - `Wallets`: defines the deployer wallets used for running the scripts. Assuming the `chad` is the name of the wallet, the following values are required: - `CHAD_ADDRESS`: the address of the account to use for signing - `CHAD_TYPE`: the type of the wallet: - `keystore`: use the encrypted keystore file. - `CHAD_JSON`: the keystore file path - `ledger` or `trezor`: use the hardware wallet. - TODO: find out if ledger/trezor specific options are needed - `pk`: use the plaintext private key. STRONGLY DISCOURAGED for production usage, meant for local devnet purposes. - `CHAD_PK`: the private key to the wallet in 0x format. - Any other value triggers the interactive prompt to enter the private key. - `Chains`: defines the list of chains where the scripts will run. Assuming the `chain` is the name of the chain, the following values are required: - `CHAIN_RPC`: the RPC endpoint of the chain - `CHAIN_VERIFIER`: verifier for the smart contracts. Possible values: - `etherscan` - `blockscout` - `sourcify` - `CHAIN_VERIFIER_URL`: the Verifier API endpoint (required if verifier is not sourcify) - **Note: Blockscout URL needs to end with "/api?" for the verification to work.** - `CHAIN_VERIFIER_KEY`: the Verifier API key (required if verifier is not sourcify) - **Note: Use any non-empty string for Blockscout API key: it is not required per se, but foundry will complain if it's empty.** ### `devops.json` See the [example devops.json](./devops.json) file. Following values are required: - `chains`: list of chain-specific options for `forge script` command. - Check the available options by running `forge script --help`. - Two additional options to handle the gas pricing are introduced: - `--auto-gas-1559`: will fetch the current base fee and priority fee from the chain's RPC node, and use following values for submitting the transactions: - `maxPriorityFee = priorityFee` - `maxGasPrice = 2 * baseFee + priorityFee` - `--auto-gas-legacy`: will fetch the current gas price from the chain's RPC node, and use it for submitting the transactions. > Note: these options are introduced as foundry script hardcodes the 3 Gwei priority fee, which is not ideal for all the chains. - `deployConfigs`: path to the directory that contains the deployment configuration files. These configuration files are expected to be either chain-specific or global: - The chain-specific configuration files should be located in `deployConfigsDir/{chainName}/{contractName}.dc.json`. - The global configuration files for production should be located in `deployConfigsDir/global/{contractName}.json`. - The global configuration files for other environments (e.g. testnet) should be located in `deployConfigsDir/global/{environment}/{contractName}.json`. - The configuration files usually include the variables required for the contract deployment or its subsequent management. - `deployments`: path to the directory that contains the deployment artifact files. These files are automatically generated by `fsr` and alike commands. - `forgeArtifacts`: path to the directory that `forge` uses to store the artifacts for the compiled contracts. - **Must match the `out` value in the `foundry.toml` file.** - `freshDeployments`: path to the directory that contains the **fresh** deployment artifact files. These files are generated during the local run of the `forge script` command. - It is recommended to add this directory to `.gitignore` to avoid unnecessary changes in the repository. - The artifacts in this directory are automatically moved into the `deployments` directory by `fsr` and alike commands. > Note: we opted to use this approach, as the local run of the `forge script` is always done before the actual broadcast of the deployment transactions. Therefore, the end results might not match the local run, if there was an unexpected on-chain revert, or if transactions were not included in the block. A separate job is automatically run to check all the new artifacts and move them to the `deployments` directory, if the on-chain deployment was successful. ### `foundry.toml` See the [example foundry.toml](./foundry.toml) file. Following values are required: - `fs_permissions`: - `{ access = "read", path = "./" }` to allow reading files from the repository root - `{ access = "read-write", path = "" }` to allow reading/writing fresh deployment artifacts - `rpc_endpoints`: - It is recommended to follow the structure defined in the example file to avoid duplication: - `arbitrum = "${ARBITRUM_RPC}"` - `etherscan`: - It is recommended to follow the structure defined in the example file to avoid duplication: - `arbitrum = { key = "${ARBITRUM_VERIFIER_KEY}", url = "${ARBITRUM_VERIFIER_URL}" }` - You should omit values for chains that are using Sourcify for verification. See the [Foundry Book](https://book.getfoundry.sh/config/) for more information. ## Usage ### Writing scripts - Your scripts should inherit from the `SynapseScript` class (or `SynapseScript06` if you're using Solidity 0.6 compiler). - This class already inherits from `forge-std`'s `Script` class, so you can use all the methods from it. - See the [example script](./script/DeployBasicContract.s.sol) for more information. - It is recommended to use separate scripts for initial deployments and subsequent configuration of the contracts. - The initial deployment scripts should be named `Deploy{ContractName}.s.sol`. - The configuration scripts should be named `Configure{ContractName}.s.sol`. ### Running scripts Your main point of entry for running the scripts should be the `npx fsr` [command](./js/forgeScriptRun.js). > Note: `fsr` is a shorthand for `forge script run`. ```bash # npx fsr [] # To simulate the script without broadcasting, using wallet named "chad" npx fsr script/DeployBasicContract.s.sol eth_sepolia chad # To broadcast the script using wallet named "chad" # NOTE: there is no need to add --verify option, as it is automatically added for the broadcasted scripts. npx fsr script/DeployBasicContract.s.sol eth_sepolia chad --broadcast ``` This command is responsible for the following: - Initializing the chain's deployment directories with the `.chainid` file, if they don't exist. - Logging the wallet's address, balance and nonce. - Running the script using the `forge script` command. - This executes the `run()` method of the script. - `--verify` option is added if the script is broadcasted. - Collecting the receipts for the newly deployed contracts, and saving their deployment artifacts. You can also utilize the `npx fsr-str` [command](./js/forgeScriptRunString.js) to provide a single string argument for the running script: - This executes the `run(string memory arg)` method of the script, passing the provided string argument. ```bash # npx fsr-str [] # To simulate the script without broadcasting, using wallet named "chad" npx fsr-str script/DeployBasicContract.s.sol eth_sepolia chad "AAAAAAAA" # To broadcast the script using wallet named "chad" # NOTE: there is no need to add --verify option, as it is automatically added for the broadcasted scripts. npx fsr-str script/DeployBasicContract.s.sol eth_sepolia chad "AAAAAAAA" --broadcast ``` ### Managing deployments If for whatever reason the deployment script was interrupted, you can use `npx sd` [command](./js/saveDeployment.js) to save the deployment artifacts. > Note: `sd` is a shorthand for `save deployment`. ```bash # npx sd npx sd eth_sepolia BasicContract # Or, if the contract alias is used npx sd eth_sepolia BasicContract.Alias ``` ### Contract verification You can verify the contracts with the saved deployment artifacts using the `npx fvc` [command](./js/forgeVerifyContract.js). > Note: `fvc` is a shorthand for `forge verify contract`. ```bash # npx fvc npx fvc eth_sepolia BasicContract # Or, if the contract alias is used npx fvc eth_sepolia BasicContract.Alias ``` ### Proxy verification You can verify the proxy's implementation in Etherscan-alike explorers using the `npx vp` [command](./js/verifyProxy.js). > Note: `vp` is a shorthand for `verify proxy`. ```bash # npx vp npx vp eth_sepolia BasicContract # Or, if the contract alias is used npx vp eth_sepolia TransparentUpgradeableProxy.BasicContract ```