From 0212eff11f850fa632b79310fe740b55709125bd Mon Sep 17 00:00:00 2001 From: Shiv Bhonde Date: Tue, 10 Dec 2024 10:34:00 +0530 Subject: [PATCH 01/16] format inline with latest SE-2 changes --- packages/foundry/contracts/YourContract.sol | 111 +++++----- packages/foundry/foundry.toml | 5 +- packages/foundry/script/Deploy.s.sol | 26 ++- packages/foundry/script/DeployHelpers.s.sol | 206 +++++++++--------- .../foundry/script/DeployYourContract.s.sol | 14 +- packages/foundry/script/VerifyAll.s.sol | 162 ++++++-------- 6 files changed, 244 insertions(+), 280 deletions(-) diff --git a/packages/foundry/contracts/YourContract.sol b/packages/foundry/contracts/YourContract.sol index 0721dfaf5..6bd339ffa 100644 --- a/packages/foundry/contracts/YourContract.sol +++ b/packages/foundry/contracts/YourContract.sol @@ -13,72 +13,67 @@ import "forge-std/console.sol"; * @author BuidlGuidl */ contract YourContract { - // State Variables - address public immutable owner; - string public greeting = "Building Unstoppable Apps!!!"; - bool public premium = false; - uint256 public totalCounter = 0; - mapping(address => uint256) public userGreetingCounter; + // State Variables + address public immutable owner; + string public greeting = "Building Unstoppable Apps!!!"; + bool public premium = false; + uint256 public totalCounter = 0; + mapping(address => uint256) public userGreetingCounter; - // Events: a way to emit log statements from smart contract that can be listened to by external parties - event GreetingChange( - address indexed greetingSetter, - string newGreeting, - bool premium, - uint256 value - ); + // Events: a way to emit log statements from smart contract that can be listened to by external parties + event GreetingChange(address indexed greetingSetter, string newGreeting, bool premium, uint256 value); - // Constructor: Called once on contract deployment - // Check packages/foundry/deploy/Deploy.s.sol - constructor(address _owner) { - owner = _owner; - } + // Constructor: Called once on contract deployment + // Check packages/foundry/deploy/Deploy.s.sol + constructor(address _owner) { + owner = _owner; + } - // Modifier: used to define a set of rules that must be met before or after a function is executed - // Check the withdraw() function - modifier isOwner() { - // msg.sender: predefined variable that represents address of the account that called the current function - require(msg.sender == owner, "Not the Owner"); - _; - } + // Modifier: used to define a set of rules that must be met before or after a function is executed + // Check the withdraw() function + modifier isOwner() { + // msg.sender: predefined variable that represents address of the account that called the current function + require(msg.sender == owner, "Not the Owner"); + _; + } - /** - * Function that allows anyone to change the state variable "greeting" of the contract and increase the counters - * - * @param _newGreeting (string memory) - new greeting to save on the contract - */ - function setGreeting(string memory _newGreeting) public payable { - // Print data to the anvil chain console. Remove when deploying to a live network. + /** + * Function that allows anyone to change the state variable "greeting" of the contract and increase the counters + * + * @param _newGreeting (string memory) - new greeting to save on the contract + */ + function setGreeting(string memory _newGreeting) public payable { + // Print data to the anvil chain console. Remove when deploying to a live network. - console.logString("Setting new greeting"); - console.logString(_newGreeting); + console.logString("Setting new greeting"); + console.logString(_newGreeting); - greeting = _newGreeting; - totalCounter += 1; - userGreetingCounter[msg.sender] += 1; + greeting = _newGreeting; + totalCounter += 1; + userGreetingCounter[msg.sender] += 1; - // msg.value: built-in global variable that represents the amount of ether sent with the transaction - if (msg.value > 0) { - premium = true; - } else { - premium = false; - } + // msg.value: built-in global variable that represents the amount of ether sent with the transaction + if (msg.value > 0) { + premium = true; + } else { + premium = false; + } - // emit: keyword used to trigger an event - emit GreetingChange(msg.sender, _newGreeting, msg.value > 0, msg.value); - } + // emit: keyword used to trigger an event + emit GreetingChange(msg.sender, _newGreeting, msg.value > 0, msg.value); + } - /** - * Function that allows the owner to withdraw all the Ether in the contract - * The function can only be called by the owner of the contract as defined by the isOwner modifier - */ - function withdraw() public isOwner { - (bool success,) = owner.call{ value: address(this).balance }(""); - require(success, "Failed to send Ether"); - } + /** + * Function that allows the owner to withdraw all the Ether in the contract + * The function can only be called by the owner of the contract as defined by the isOwner modifier + */ + function withdraw() public isOwner { + (bool success,) = owner.call{ value: address(this).balance }(""); + require(success, "Failed to send Ether"); + } - /** - * Function that allows the contract to receive ETH - */ - receive() external payable { } + /** + * Function that allows the contract to receive ETH + */ + receive() external payable { } } diff --git a/packages/foundry/foundry.toml b/packages/foundry/foundry.toml index bd4de0621..d3537f6a6 100644 --- a/packages/foundry/foundry.toml +++ b/packages/foundry/foundry.toml @@ -33,9 +33,8 @@ sepolia = { key = "${ETHERSCAN_API_KEY}" } [fmt] -multiline_func_header = "params_first" -line_length = 80 -tab_width = 2 +line_length = 120 +tab_width = 4 quote_style = "double" bracket_spacing = true int_types = "long" diff --git a/packages/foundry/script/Deploy.s.sol b/packages/foundry/script/Deploy.s.sol index 8ca40a31a..a90c6387d 100644 --- a/packages/foundry/script/Deploy.s.sol +++ b/packages/foundry/script/Deploy.s.sol @@ -2,15 +2,25 @@ pragma solidity ^0.8.19; import "./DeployHelpers.s.sol"; -import { DeployYourContract } from "./DeployYourContract.s.sol"; +// import { DeploySolution2 } from "./DeploySolution2.s.sol"; +/** + * @notice Main deployment script for all solutions + * @dev Run this when you want to deploy multiple solutions at once + * + * Example: yarn foundry:deploy # runs this script(without`--file` flag) + */ contract DeployScript is ScaffoldETHDeploy { - function run() external { - DeployYourContract deployYourContract = new DeployYourContract(); - deployYourContract.run(); + function run() external { + // Deploys all solutions sequentially + // Add new solution deployments here when needed - // deploy more contracts here - // DeployMyContract deployMyContract = new DeployMyContract(); - // deployMyContract.run(); - } + // Deploy Solution 2 + // DeploySolution2 deploySolution2 = new DeploySolution2(); + // deploySolution2.run(); + + // Deploy Solution 3 + // DeploySolution3 deploySolution3 = new DeploySolution3(); + // deploySolution3.run(); + } } diff --git a/packages/foundry/script/DeployHelpers.s.sol b/packages/foundry/script/DeployHelpers.s.sol index d8bacf668..2d77229c7 100644 --- a/packages/foundry/script/DeployHelpers.s.sol +++ b/packages/foundry/script/DeployHelpers.s.sol @@ -5,123 +5,117 @@ import { Script, console } from "forge-std/Script.sol"; import { Vm } from "forge-std/Vm.sol"; contract ScaffoldETHDeploy is Script { - error InvalidChain(); - error DeployerHasNoBalance(); - error InvalidPrivateKey(string); - - event AnvilSetBalance(address account, uint256 amount); - event FailedAnvilRequest(); - - struct Deployment { - string name; - address addr; - } - - string root; - string path; - Deployment[] public deployments; - uint256 constant ANVIL_BASE_BALANCE = 10000 ether; - - /// @notice The deployer address for every run - address deployer; - - /// @notice Use this modifier on your run() function on your deploy scripts - modifier ScaffoldEthDeployerRunner() { - deployer = _startBroadcast(); - if (deployer == address(0)) { - revert InvalidPrivateKey("Invalid private key"); + error InvalidChain(); + error DeployerHasNoBalance(); + error InvalidPrivateKey(string); + + event AnvilSetBalance(address account, uint256 amount); + event FailedAnvilRequest(); + + struct Deployment { + string name; + address addr; } - _; - _stopBroadcast(); - exportDeployments(); - } - - function _startBroadcast() internal returns (address) { - vm.startBroadcast(); - (, address _deployer,) = vm.readCallers(); - - if (block.chainid == 31337 && _deployer.balance == 0) { - try this.anvil_setBalance(_deployer, ANVIL_BASE_BALANCE) { - emit AnvilSetBalance(_deployer, ANVIL_BASE_BALANCE); - } catch { - emit FailedAnvilRequest(); - } + + string root; + string path; + Deployment[] public deployments; + uint256 constant ANVIL_BASE_BALANCE = 10000 ether; + + /// @notice The deployer address for every run + address deployer; + + /// @notice Use this modifier on your run() function on your deploy scripts + modifier ScaffoldEthDeployerRunner() { + deployer = _startBroadcast(); + if (deployer == address(0)) { + revert InvalidPrivateKey("Invalid private key"); + } + _; + _stopBroadcast(); + exportDeployments(); } - return _deployer; - } - function _stopBroadcast() internal { - vm.stopBroadcast(); - } + function _startBroadcast() internal returns (address) { + vm.startBroadcast(); + (, address _deployer,) = vm.readCallers(); - function exportDeployments() internal { - // fetch already existing contracts - root = vm.projectRoot(); - path = string.concat(root, "/deployments/"); - string memory chainIdStr = vm.toString(block.chainid); - path = string.concat(path, string.concat(chainIdStr, ".json")); + if (block.chainid == 31337 && _deployer.balance == 0) { + try this.anvil_setBalance(_deployer, ANVIL_BASE_BALANCE) { + emit AnvilSetBalance(_deployer, ANVIL_BASE_BALANCE); + } catch { + emit FailedAnvilRequest(); + } + } + return _deployer; + } - string memory jsonWrite; + function _stopBroadcast() internal { + vm.stopBroadcast(); + } - uint256 len = deployments.length; + function exportDeployments() internal { + // fetch already existing contracts + root = vm.projectRoot(); + path = string.concat(root, "/deployments/"); + string memory chainIdStr = vm.toString(block.chainid); + path = string.concat(path, string.concat(chainIdStr, ".json")); - for (uint256 i = 0; i < len; i++) { - vm.serializeString( - jsonWrite, vm.toString(deployments[i].addr), deployments[i].name - ); + string memory jsonWrite; + + uint256 len = deployments.length; + + for (uint256 i = 0; i < len; i++) { + vm.serializeString(jsonWrite, vm.toString(deployments[i].addr), deployments[i].name); + } + + string memory chainName; + + try this.getChain() returns (Chain memory chain) { + chainName = chain.name; + } catch { + chainName = findChainName(); + } + jsonWrite = vm.serializeString(jsonWrite, "networkName", chainName); + vm.writeJson(jsonWrite, path); } - string memory chainName; + function getChain() public returns (Chain memory) { + return getChain(block.chainid); + } - try this.getChain() returns (Chain memory chain) { - chainName = chain.name; - } catch { - chainName = findChainName(); + function anvil_setBalance(address addr, uint256 amount) public { + string memory addressString = vm.toString(addr); + string memory amountString = vm.toString(amount); + string memory requestPayload = string.concat( + '{"method":"anvil_setBalance","params":["', addressString, '","', amountString, '"],"id":1,"jsonrpc":"2.0"}' + ); + + string[] memory inputs = new string[](8); + inputs[0] = "curl"; + inputs[1] = "-X"; + inputs[2] = "POST"; + inputs[3] = "http://localhost:8545"; + inputs[4] = "-H"; + inputs[5] = "Content-Type: application/json"; + inputs[6] = "--data"; + inputs[7] = requestPayload; + + vm.ffi(inputs); } - jsonWrite = vm.serializeString(jsonWrite, "networkName", chainName); - vm.writeJson(jsonWrite, path); - } - - function getChain() public returns (Chain memory) { - return getChain(block.chainid); - } - - function anvil_setBalance(address addr, uint256 amount) public { - string memory addressString = vm.toString(addr); - string memory amountString = vm.toString(amount); - string memory requestPayload = string.concat( - '{"method":"anvil_setBalance","params":["', - addressString, - '","', - amountString, - '"],"id":1,"jsonrpc":"2.0"}' - ); - - string[] memory inputs = new string[](8); - inputs[0] = "curl"; - inputs[1] = "-X"; - inputs[2] = "POST"; - inputs[3] = "http://localhost:8545"; - inputs[4] = "-H"; - inputs[5] = "Content-Type: application/json"; - inputs[6] = "--data"; - inputs[7] = requestPayload; - - vm.ffi(inputs); - } - - function findChainName() public returns (string memory) { - uint256 thisChainId = block.chainid; - string[2][] memory allRpcUrls = vm.rpcUrls(); - for (uint256 i = 0; i < allRpcUrls.length; i++) { - try vm.createSelectFork(allRpcUrls[i][1]) { - if (block.chainid == thisChainId) { - return allRpcUrls[i][0]; + + function findChainName() public returns (string memory) { + uint256 thisChainId = block.chainid; + string[2][] memory allRpcUrls = vm.rpcUrls(); + for (uint256 i = 0; i < allRpcUrls.length; i++) { + try vm.createSelectFork(allRpcUrls[i][1]) { + if (block.chainid == thisChainId) { + return allRpcUrls[i][0]; + } + } catch { + continue; + } } - } catch { - continue; - } + revert InvalidChain(); } - revert InvalidChain(); - } } diff --git a/packages/foundry/script/DeployYourContract.s.sol b/packages/foundry/script/DeployYourContract.s.sol index 547ed879f..9a2b534d8 100644 --- a/packages/foundry/script/DeployYourContract.s.sol +++ b/packages/foundry/script/DeployYourContract.s.sol @@ -5,13 +5,9 @@ import "../contracts/YourContract.sol"; import "./DeployHelpers.s.sol"; contract DeployYourContract is ScaffoldETHDeploy { - // use `deployer` from `ScaffoldETHDeploy` - function run() external ScaffoldEthDeployerRunner { - YourContract yourContract = new YourContract(deployer); - console.logString( - string.concat( - "YourContract deployed at: ", vm.toString(address(yourContract)) - ) - ); - } + // use `deployer` from `ScaffoldETHDeploy` + function run() external ScaffoldEthDeployerRunner { + YourContract yourContract = new YourContract(deployer); + console.logString(string.concat("YourContract deployed at: ", vm.toString(address(yourContract)))); + } } diff --git a/packages/foundry/script/VerifyAll.s.sol b/packages/foundry/script/VerifyAll.s.sol index 2b307a04d..c6e58669d 100644 --- a/packages/foundry/script/VerifyAll.s.sol +++ b/packages/foundry/script/VerifyAll.s.sol @@ -11,121 +11,91 @@ import "solidity-bytes-utils/BytesLib.sol"; * @notice will be deleted once the forge/std is updated */ struct FfiResult { - int32 exit_code; - bytes stdout; - bytes stderr; + int32 exit_code; + bytes stdout; + bytes stderr; } interface tempVm { - function tryFfi(string[] calldata) external returns (FfiResult memory); + function tryFfi(string[] calldata) external returns (FfiResult memory); } contract VerifyAll is Script { - uint96 currTransactionIdx; + uint96 currTransactionIdx; - function run() external { - string memory root = vm.projectRoot(); - string memory path = string.concat( - root, - "/broadcast/Deploy.s.sol/", - vm.toString(block.chainid), - "/run-latest.json" - ); - string memory content = vm.readFile(path); + function run() external { + string memory root = vm.projectRoot(); + string memory path = + string.concat(root, "/broadcast/Deploy.s.sol/", vm.toString(block.chainid), "/run-latest.json"); + string memory content = vm.readFile(path); - while (this.nextTransaction(content)) { - _verifyIfContractDeployment(content); - currTransactionIdx++; + while (this.nextTransaction(content)) { + _verifyIfContractDeployment(content); + currTransactionIdx++; + } } - } - function _verifyIfContractDeployment(string memory content) internal { - string memory txType = abi.decode( - vm.parseJson(content, searchStr(currTransactionIdx, "transactionType")), - (string) - ); - if (keccak256(bytes(txType)) == keccak256(bytes("CREATE"))) { - _verifyContract(content); + function _verifyIfContractDeployment(string memory content) internal { + string memory txType = + abi.decode(vm.parseJson(content, searchStr(currTransactionIdx, "transactionType")), (string)); + if (keccak256(bytes(txType)) == keccak256(bytes("CREATE"))) { + _verifyContract(content); + } } - } - function _verifyContract(string memory content) internal { - string memory contractName = abi.decode( - vm.parseJson(content, searchStr(currTransactionIdx, "contractName")), - (string) - ); - address contractAddr = abi.decode( - vm.parseJson(content, searchStr(currTransactionIdx, "contractAddress")), - (address) - ); - bytes memory deployedBytecode = abi.decode( - vm.parseJson(content, searchStr(currTransactionIdx, "transaction.input")), - (bytes) - ); - bytes memory compiledBytecode = abi.decode( - vm.parseJson(_getCompiledBytecode(contractName), ".bytecode.object"), - (bytes) - ); - bytes memory constructorArgs = BytesLib.slice( - deployedBytecode, - compiledBytecode.length, - deployedBytecode.length - compiledBytecode.length - ); + function _verifyContract(string memory content) internal { + string memory contractName = + abi.decode(vm.parseJson(content, searchStr(currTransactionIdx, "contractName")), (string)); + address contractAddr = + abi.decode(vm.parseJson(content, searchStr(currTransactionIdx, "contractAddress")), (address)); + bytes memory deployedBytecode = + abi.decode(vm.parseJson(content, searchStr(currTransactionIdx, "transaction.input")), (bytes)); + bytes memory compiledBytecode = + abi.decode(vm.parseJson(_getCompiledBytecode(contractName), ".bytecode.object"), (bytes)); + bytes memory constructorArgs = + BytesLib.slice(deployedBytecode, compiledBytecode.length, deployedBytecode.length - compiledBytecode.length); - string[] memory inputs = new string[](9); - inputs[0] = "forge"; - inputs[1] = "verify-contract"; - inputs[2] = vm.toString(contractAddr); - inputs[3] = contractName; - inputs[4] = "--chain"; - inputs[5] = vm.toString(block.chainid); - inputs[6] = "--constructor-args"; - inputs[7] = vm.toString(constructorArgs); - inputs[8] = "--watch"; + string[] memory inputs = new string[](9); + inputs[0] = "forge"; + inputs[1] = "verify-contract"; + inputs[2] = vm.toString(contractAddr); + inputs[3] = contractName; + inputs[4] = "--chain"; + inputs[5] = vm.toString(block.chainid); + inputs[6] = "--constructor-args"; + inputs[7] = vm.toString(constructorArgs); + inputs[8] = "--watch"; - FfiResult memory f = tempVm(address(vm)).tryFfi(inputs); + FfiResult memory f = tempVm(address(vm)).tryFfi(inputs); - if (f.stderr.length != 0) { - console.logString( - string.concat( - "Submitting verification for contract: ", vm.toString(contractAddr) - ) - ); - console.logString(string(f.stderr)); - } else { - console.logString(string(f.stdout)); + if (f.stderr.length != 0) { + console.logString(string.concat("Submitting verification for contract: ", vm.toString(contractAddr))); + console.logString(string(f.stderr)); + } else { + console.logString(string(f.stdout)); + } + return; } - return; - } - function nextTransaction(string memory content) external view returns (bool) { - try this.getTransactionFromRaw(content, currTransactionIdx) { - return true; - } catch { - return false; + function nextTransaction(string memory content) external view returns (bool) { + try this.getTransactionFromRaw(content, currTransactionIdx) { + return true; + } catch { + return false; + } } - } - function _getCompiledBytecode( - string memory contractName - ) internal view returns (string memory compiledBytecode) { - string memory root = vm.projectRoot(); - string memory path = - string.concat(root, "/out/", contractName, ".sol/", contractName, ".json"); - compiledBytecode = vm.readFile(path); - } + function _getCompiledBytecode(string memory contractName) internal view returns (string memory compiledBytecode) { + string memory root = vm.projectRoot(); + string memory path = string.concat(root, "/out/", contractName, ".sol/", contractName, ".json"); + compiledBytecode = vm.readFile(path); + } - function getTransactionFromRaw( - string memory content, - uint96 idx - ) external pure { - abi.decode(vm.parseJson(content, searchStr(idx, "hash")), (bytes32)); - } + function getTransactionFromRaw(string memory content, uint96 idx) external pure { + abi.decode(vm.parseJson(content, searchStr(idx, "hash")), (bytes32)); + } - function searchStr( - uint96 idx, - string memory searchKey - ) internal pure returns (string memory) { - return string.concat(".transactions[", vm.toString(idx), "].", searchKey); - } + function searchStr(uint96 idx, string memory searchKey) internal pure returns (string memory) { + return string.concat(".transactions[", vm.toString(idx), "].", searchKey); + } } From 07499d53a418086322588065a4ddc6e712c5a67f Mon Sep 17 00:00:00 2001 From: Shiv Bhonde Date: Tue, 10 Dec 2024 10:39:05 +0530 Subject: [PATCH 02/16] imporvement: parse through all the results of deployemnts script --- packages/foundry/Makefile | 36 +++- packages/foundry/package.json | 4 +- packages/foundry/scripts-js/generateTsAbis.js | 190 +++++++++++++----- packages/foundry/scripts-js/parseArgs.js | 57 ++++++ 4 files changed, 228 insertions(+), 59 deletions(-) create mode 100644 packages/foundry/scripts-js/parseArgs.js diff --git a/packages/foundry/Makefile b/packages/foundry/Makefile index d973c2010..229645074 100644 --- a/packages/foundry/Makefile +++ b/packages/foundry/Makefile @@ -1,8 +1,11 @@ .PHONY: build deploy generate-abis verify-keystore account chain compile deploy-verify flatten fork format lint test verify +DEPLOY_SCRIPT ?= script/Deploy.s.sol + # setup wallet for anvil setup-anvil-wallet: shx rm ~/.foundry/keystores/scaffold-eth-default 2>/dev/null; \ + shx rm -rf broadcast/Deploy.s.sol/31337 cast wallet import --private-key 0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6 --unsafe-password 'localhost' scaffold-eth-default # Start local chain @@ -15,14 +18,22 @@ fork: setup-anvil-wallet # Build the project build: - forge build --build-info --build-info-path out/build-info/ + forge build --via-ir --build-info --build-info-path out/build-info/ -# Deploy the project +# Deploy the contracts deploy: + @if [ ! -f "$(DEPLOY_SCRIPT)" ]; then \ + echo "Error: Deploy script '$(DEPLOY_SCRIPT)' not found"; \ + exit 1; \ + fi @if [ "$(RPC_URL)" = "localhost" ]; then \ - forge script script/Deploy.s.sol --rpc-url localhost --password localhost --broadcast --legacy --ffi; \ + if [ "$(ETH_KEYSTORE_ACCOUNT)" = "scaffold-eth-default" ]; then \ + forge script $(DEPLOY_SCRIPT) --rpc-url localhost --password localhost --broadcast --legacy --ffi; \ + else \ + forge script $(DEPLOY_SCRIPT) --rpc-url localhost --broadcast --legacy --ffi; \ + fi \ else \ - forge script script/Deploy.s.sol --rpc-url $(RPC_URL) --broadcast --legacy --ffi; \ + forge script $(DEPLOY_SCRIPT) --rpc-url $(RPC_URL) --broadcast --legacy --ffi; \ fi # Build and deploy target @@ -35,9 +46,9 @@ generate-abis: verify-keystore: if grep -q "scaffold-eth-default" .env; then \ cast wallet address --password localhost; \ - else \ + else \ cast wallet address; \ - fi + fi # List account account: @@ -58,10 +69,18 @@ compile: # Deploy and verify deploy-verify: + @if [ ! -f "$(DEPLOY_SCRIPT)" ]; then \ + echo "Error: Deploy script '$(DEPLOY_SCRIPT)' not found"; \ + exit 1; \ + fi @if [ "$(RPC_URL)" = "localhost" ]; then \ - forge script script/Deploy.s.sol --rpc-url localhost --password localhost --broadcast --legacy --ffi --verify; \ + if [ "$(ETH_KEYSTORE_ACCOUNT)" = "scaffold-eth-default" ]; then \ + forge script $(DEPLOY_SCRIPT) --rpc-url localhost --password localhost --broadcast --legacy --ffi --verify; \ + else \ + forge script $(DEPLOY_SCRIPT) --rpc-url localhost --broadcast --legacy --ffi --verify; \ + fi \ else \ - forge script script/Deploy.s.sol --rpc-url $(RPC_URL) --broadcast --legacy --ffi --verify; \ + forge script $(DEPLOY_SCRIPT) --rpc-url $(RPC_URL) --broadcast --legacy --ffi --verify; \ fi node scripts-js/generateTsAbis.js @@ -82,4 +101,3 @@ verify: forge script script/VerifyAll.s.sol --ffi --rpc-url $(RPC_URL) build-and-verify: build verify - diff --git a/packages/foundry/package.json b/packages/foundry/package.json index 859a8514a..44375acca 100644 --- a/packages/foundry/package.json +++ b/packages/foundry/package.json @@ -9,8 +9,8 @@ "account:import": "make account-import ACCOUNT_NAME=${1:-scaffold-eth-custom}", "chain": "make chain", "compile": "make compile", - "deploy": "make build-and-deploy RPC_URL=${1:-localhost}", - "deploy:verify": "make deploy-verify RPC_URL=${1:-localhost}", + "deploy": "node scripts-js/parseArgs.js", + "deploy:verify": "node scripts/parseArgs.js --verify", "flatten": "make flatten", "fork": "make fork FORK_URL=${1:-mainnet}", "format": "make format", diff --git a/packages/foundry/scripts-js/generateTsAbis.js b/packages/foundry/scripts-js/generateTsAbis.js index 274f12cb3..595f9f166 100644 --- a/packages/foundry/scripts-js/generateTsAbis.js +++ b/packages/foundry/scripts-js/generateTsAbis.js @@ -21,20 +21,69 @@ const generatedContractComment = ` function getDirectories(path) { return readdirSync(path).filter(function (file) { - return statSync(path + "/" + file).isDirectory(); + return statSync(join(path, file)).isDirectory(); }); } + function getFiles(path) { return readdirSync(path).filter(function (file) { - return statSync(path + "/" + file).isFile(); + return statSync(join(path, file)).isFile(); }); } + +function parseTransactionRun(filePath) { + try { + const content = readFileSync(filePath, "utf8"); + const broadcastData = JSON.parse(content); + return broadcastData.transactions || []; + } catch (error) { + console.warn(`Warning: Could not parse ${filePath}:`, error.message); + return []; + } +} + +function getDeploymentHistory(broadcastPath) { + const files = getFiles(broadcastPath); + const deploymentHistory = new Map(); + + // Sort files to process them in chronological order + const runFiles = files + .filter((file) => file.startsWith("run-") && file.endsWith(".json")) + .sort((a, b) => { + // Extract run numbers and compare them + const runA = parseInt(a.match(/run-(\d+)/)?.[1] || "0"); + const runB = parseInt(b.match(/run-(\d+)/)?.[1] || "0"); + return runA - runB; + }); + + for (const file of runFiles) { + const transactions = parseTransactionRun(join(broadcastPath, file)); + + for (const tx of transactions) { + if (tx.transactionType === "CREATE") { + // Store or update contract deployment info + deploymentHistory.set(tx.contractAddress, { + contractName: tx.contractName, + address: tx.contractAddress, + deploymentFile: file, + transaction: tx, + }); + } + } + } + + return Array.from(deploymentHistory.values()); +} + function getArtifactOfContract(contractName) { const current_path_to_artifacts = join( __dirname, "..", `out/${contractName}.sol` ); + + if (!existsSync(current_path_to_artifacts)) return null; + const artifactJson = JSON.parse( readFileSync(`${current_path_to_artifacts}/${contractName}.json`) ); @@ -62,32 +111,70 @@ function getInheritedFunctions(mainArtifact) { const inheritedFromContracts = getInheritedFromContracts(mainArtifact); const inheritedFunctions = {}; for (const inheritanceContractName of inheritedFromContracts) { - const { - abi, - ast: { absolutePath }, - } = getArtifactOfContract(inheritanceContractName); - for (const abiEntry of abi) { - if (abiEntry.type == "function") { - inheritedFunctions[abiEntry.name] = absolutePath; + const artifact = getArtifactOfContract(inheritanceContractName); + if (artifact) { + const { + abi, + ast: { absolutePath }, + } = artifact; + for (const abiEntry of abi) { + if (abiEntry.type == "function") { + inheritedFunctions[abiEntry.name] = absolutePath; + } } } } return inheritedFunctions; } +function processAllDeployments(broadcastPath) { + const scriptFolders = getDirectories(broadcastPath); + const allContracts = {}; + + // Process each script folder + scriptFolders.forEach((scriptFolder) => { + const scriptPath = join(broadcastPath, scriptFolder); + const chainFolders = getDirectories(scriptPath); + + // Process each chain folder within the script folder + chainFolders.forEach((chainId) => { + const chainPath = join(scriptPath, chainId); + + // Initialize chain object if it doesn't exist + if (!allContracts[chainId]) { + allContracts[chainId] = {}; + } + + // Get deployment history for this specific chain path + const deploymentHistory = getDeploymentHistory(chainPath); + + // Process each deployment + deploymentHistory.forEach((deployment) => { + const artifact = getArtifactOfContract(deployment.contractName); + if (artifact) { + allContracts[chainId][deployment.contractName] = { + address: deployment.address, + abi: artifact.abi, + inheritedFunctions: getInheritedFunctions(artifact), + deploymentFile: deployment.deploymentFile, + deploymentScript: scriptFolder, // Track which script deployed this contract + }; + } + }); + }); + }); + + return allContracts; +} + function main() { - const current_path_to_broadcast = join( - __dirname, - "..", - "broadcast/Deploy.s.sol" - ); + const current_path_to_broadcast = join(__dirname, "..", "broadcast"); const current_path_to_deployments = join(__dirname, "..", "deployments"); - const chains = getDirectories(current_path_to_broadcast); const Deploymentchains = getFiles(current_path_to_deployments); - const deployments = {}; + // Load existing deployments from deployments directory Deploymentchains.forEach((chain) => { if (!chain.endsWith(".json")) return; chain = chain.slice(0, -5); @@ -97,31 +184,31 @@ function main() { deployments[chain] = deploymentObject; }); - const allGeneratedContracts = {}; + // Process all deployments from all script folders + const allGeneratedContracts = processAllDeployments( + current_path_to_broadcast + ); - chains.forEach((chain) => { - allGeneratedContracts[chain] = {}; - const broadCastObject = JSON.parse( - readFileSync(`${current_path_to_broadcast}/${chain}/run-latest.json`) - ); - const transactionsCreate = broadCastObject.transactions.filter( - (transaction) => transaction.transactionType == "CREATE" - ); - transactionsCreate.forEach((transaction) => { - const artifact = getArtifactOfContract(transaction.contractName); - allGeneratedContracts[chain][ - deployments[chain][transaction.contractAddress] || - transaction.contractName - ] = { - address: transaction.contractAddress, - abi: artifact.abi, - inheritedFunctions: getInheritedFunctions(artifact), - }; + // Update contract keys based on deployments if they exist + Object.entries(allGeneratedContracts).forEach(([chainId, contracts]) => { + Object.entries(contracts).forEach(([contractName, contractData]) => { + const deployedName = deployments[chainId]?.[contractData.address]; + if (deployedName) { + // If we have a deployment name, use it instead of the contract name + allGeneratedContracts[chainId][deployedName] = contractData; + delete allGeneratedContracts[chainId][contractName]; + } }); }); - const TARGET_DIR = "../nextjs/contracts/"; + const NEXTJS_TARGET_DIR = "../nextjs/contracts/"; + + // Ensure target directories exist + if (!existsSync(NEXTJS_TARGET_DIR)) { + mkdirSync(NEXTJS_TARGET_DIR, { recursive: true }); + } + // Generate the deployedContracts content const fileContent = Object.entries(allGeneratedContracts).reduce( (content, [chainId, chainConfig]) => { return `${content}${parseInt(chainId).toFixed(0)}:${JSON.stringify( @@ -133,24 +220,31 @@ function main() { "" ); - if (!existsSync(TARGET_DIR)) { - mkdirSync(TARGET_DIR); - } + // Write the files + const fileTemplate = (importPath) => ` + ${generatedContractComment} + import { GenericContractsDeclaration } from "${importPath}"; + + const deployedContracts = {${fileContent}} as const; + + export default deployedContracts satisfies GenericContractsDeclaration; + `; + writeFileSync( - `${TARGET_DIR}deployedContracts.ts`, - format( - `${generatedContractComment} import { GenericContractsDeclaration } from "~~/utils/scaffold-eth/contract"; \n\n - const deployedContracts = {${fileContent}} as const; \n\n export default deployedContracts satisfies GenericContractsDeclaration`, - { - parser: "typescript", - } - ) + `${NEXTJS_TARGET_DIR}deployedContracts.ts`, + format(fileTemplate("~~/utils/scaffold-eth/contract"), { + parser: "typescript", + }) + ); + + console.log( + `šŸ“ Updated TypeScript contract definition file on ${NEXTJS_TARGET_DIR}deployedContracts.ts` ); } try { main(); } catch (error) { - console.error(error); + console.error("Error:", error); process.exitCode = 1; } diff --git a/packages/foundry/scripts-js/parseArgs.js b/packages/foundry/scripts-js/parseArgs.js new file mode 100644 index 000000000..a990743c9 --- /dev/null +++ b/packages/foundry/scripts-js/parseArgs.js @@ -0,0 +1,57 @@ +import { spawnSync } from "child_process"; +import { config } from "dotenv"; +config(); + +// Get all arguments after the script name +const args = process.argv.slice(2); +let fileName = "Deploy.s.sol"; +let network = "localhost"; + +// Show help message if --help is provided +if (args.includes("--help") || args.includes("-h")) { + console.log(` +Usage: yarn deploy [options] + +Options: + --file Specify the deployment script file (default: Deploy.s.sol) + --network Specify the network (default: localhost) + --help, -h Show this help message + +Examples: + yarn deploy --file DeploySolution2.s.sol --network sepolia + yarn deploy --network sepolia + yarn deploy --file DeploySolution2.s.sol + yarn deploy + `); + process.exit(0); +} + +// Parse arguments +for (let i = 0; i < args.length; i++) { + if (args[i] === "--network" && args[i + 1]) { + network = args[i + 1]; + i++; // Skip next arg since we used it + } else if (args[i] === "--file" && args[i + 1]) { + fileName = args[i + 1]; + i++; // Skip next arg since we used it + } +} + +// Set environment variables for the make command +process.env.DEPLOY_SCRIPT = `script/${fileName}`; +process.env.RPC_URL = network; + +const result = spawnSync( + "make", + [ + "build-and-deploy", + `DEPLOY_SCRIPT=${process.env.DEPLOY_SCRIPT}`, + `RPC_URL=${process.env.RPC_URL}`, + ], + { + stdio: "inherit", + shell: true, + } +); + +process.exit(result.status); From f07472a2fa49e76d4d67acacdeaa54d4970e7c27 Mon Sep 17 00:00:00 2001 From: Shiv Bhonde Date: Tue, 10 Dec 2024 10:55:31 +0530 Subject: [PATCH 03/16] tweaks to scripts --- packages/foundry/script/Deploy.s.sol | 24 +++++++++--------- .../foundry/script/DeployYourContract.s.sol | 25 ++++++++++++++++--- packages/foundry/scripts-js/generateTsAbis.js | 3 +-- 3 files changed, 34 insertions(+), 18 deletions(-) diff --git a/packages/foundry/script/Deploy.s.sol b/packages/foundry/script/Deploy.s.sol index a90c6387d..d73817760 100644 --- a/packages/foundry/script/Deploy.s.sol +++ b/packages/foundry/script/Deploy.s.sol @@ -2,25 +2,25 @@ pragma solidity ^0.8.19; import "./DeployHelpers.s.sol"; -// import { DeploySolution2 } from "./DeploySolution2.s.sol"; +import { DeployYourContract } from "./DeployYourContract.s.sol"; /** - * @notice Main deployment script for all solutions - * @dev Run this when you want to deploy multiple solutions at once + * @notice Main deployment script for all contracts + * @dev Run this when you want to deploy multiple contracts at once * - * Example: yarn foundry:deploy # runs this script(without`--file` flag) + * Example: yarn deploy # runs this script(without`--file` flag) */ contract DeployScript is ScaffoldETHDeploy { function run() external { - // Deploys all solutions sequentially - // Add new solution deployments here when needed + // Deploys all your contracts sequentially + // Add new deployments here when needed - // Deploy Solution 2 - // DeploySolution2 deploySolution2 = new DeploySolution2(); - // deploySolution2.run(); + // Deploy YourContract + DeployYourContract yourContract = new DeployYourContract(); + yourContract.run(); - // Deploy Solution 3 - // DeploySolution3 deploySolution3 = new DeploySolution3(); - // deploySolution3.run(); + // Deploy another contract + // DeployMyContract myContract = new DeployMyContract(); + // myContract.run(); } } diff --git a/packages/foundry/script/DeployYourContract.s.sol b/packages/foundry/script/DeployYourContract.s.sol index 9a2b534d8..56433baec 100644 --- a/packages/foundry/script/DeployYourContract.s.sol +++ b/packages/foundry/script/DeployYourContract.s.sol @@ -1,13 +1,30 @@ -//SPDX-License-Identifier: MIT +// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import "../contracts/YourContract.sol"; import "./DeployHelpers.s.sol"; +import "../contracts/YourContract.sol"; +/** + * @notice Deploy script for YourContract contract + * @dev Inherits ScaffoldETHDeploy which: + * - Includes forge-std/Script.sol for deployment + * - Includes ScaffoldEthDeployerRunner modifier + * - Provides `deployer` variable + * Example: + * yarn deploy --file DeployYourContract.s.sol # local anvil chain + * yarn deploy --file DeployYourContract.s.sol --network optimism # live network (requires keystore) + */ contract DeployYourContract is ScaffoldETHDeploy { - // use `deployer` from `ScaffoldETHDeploy` + /** + * @dev Deployer setup based on `ETH_KEYSTORE_ACCOUNT` in `.env`: + * - "scaffold-eth-default": Uses Anvil's account #9 (0xa0Ee7A142d267C1f36714E4a8F75612F20a79720), no password prompt + * - "scaffold-eth-custom": requires password used while creating keystore + * + * Note: Must use ScaffoldEthDeployerRunner modifier to: + * - Setup correct `deployer` account and fund it + * - Export contract addresses & ABIs to `nextjs` packages + */ function run() external ScaffoldEthDeployerRunner { YourContract yourContract = new YourContract(deployer); - console.logString(string.concat("YourContract deployed at: ", vm.toString(address(yourContract)))); } } diff --git a/packages/foundry/scripts-js/generateTsAbis.js b/packages/foundry/scripts-js/generateTsAbis.js index 595f9f166..9fab43853 100644 --- a/packages/foundry/scripts-js/generateTsAbis.js +++ b/packages/foundry/scripts-js/generateTsAbis.js @@ -16,8 +16,7 @@ const generatedContractComment = ` /** * This file is autogenerated by Scaffold-ETH. * You should not edit it manually or your changes might be overwritten. - */ -`; + */`; function getDirectories(path) { return readdirSync(path).filter(function (file) { From e070cceea2eb89177142b892ab9c4e17c6bfc8ee Mon Sep 17 00:00:00 2001 From: Shiv Bhonde Date: Tue, 10 Dec 2024 10:57:40 +0530 Subject: [PATCH 04/16] remove unused variables --- packages/foundry/script/DeployYourContract.s.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/foundry/script/DeployYourContract.s.sol b/packages/foundry/script/DeployYourContract.s.sol index 56433baec..7f82e5afa 100644 --- a/packages/foundry/script/DeployYourContract.s.sol +++ b/packages/foundry/script/DeployYourContract.s.sol @@ -25,6 +25,6 @@ contract DeployYourContract is ScaffoldETHDeploy { * - Export contract addresses & ABIs to `nextjs` packages */ function run() external ScaffoldEthDeployerRunner { - YourContract yourContract = new YourContract(deployer); + new YourContract(deployer); } } From 1dad3b3158c4ed9adcbedb9dace621f7a63144bc Mon Sep 17 00:00:00 2001 From: Shiv Bhonde Date: Tue, 10 Dec 2024 12:17:57 +0530 Subject: [PATCH 05/16] update parseArgs --- packages/foundry/scripts-js/parseArgs.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/foundry/scripts-js/parseArgs.js b/packages/foundry/scripts-js/parseArgs.js index a990743c9..ec955f239 100644 --- a/packages/foundry/scripts-js/parseArgs.js +++ b/packages/foundry/scripts-js/parseArgs.js @@ -18,9 +18,9 @@ Options: --help, -h Show this help message Examples: - yarn deploy --file DeploySolution2.s.sol --network sepolia + yarn deploy --file DeployYourContract.s.sol --network sepolia yarn deploy --network sepolia - yarn deploy --file DeploySolution2.s.sol + yarn deploy --file DeployYourContract.s.sol yarn deploy `); process.exit(0); From b3a232851b8afb398bc355d27a1f23e49b624764 Mon Sep 17 00:00:00 2001 From: Shiv Bhonde Date: Tue, 10 Dec 2024 12:27:26 +0530 Subject: [PATCH 06/16] update README --- README.md | 114 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 84 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 76eb3be36..a3d8abd7c 100644 --- a/README.md +++ b/README.md @@ -46,16 +46,12 @@ yarn install && forge install --root packages/foundry yarn chain ``` -This command starts a local Ethereum network using Anvil for testing and development. You can customize the network in `packages/foundry/foundry.toml`. When deploying to this local chain, Scaffold-ETH 2 creates a keystore account using Anvil's last address private key. This keystore account is named `scaffold-eth-local` with the password `localhost`. - 3. On a second terminal, deploy the test contract: ``` yarn deploy ``` -This command deploys a test smart contract to the local network with the keystore account mentioned in `packages/foundry/.env#ETH_KEYSTORE_ACCOUNT`. When using `scaffold-eth-locahost` this command doesn't require any password. The contract is located in `packages/foundry/contracts` and can be modified to suit your needs. The `yarn deploy` command uses the deploy script located in `packages/foundry/script/Deploy.s.sol` to deploy the contract to the network. You can also customize the deploy script. - 4. On a third terminal, start your NextJS app: ``` @@ -64,59 +60,117 @@ yarn start Visit your app on: `http://localhost:3000`. You can interact with your smart contract using the `Debug Contracts` page. You can tweak the app config in `packages/nextjs/scaffold.config.ts`. -## Deploying to live networks +## Deploying to Live Networks -1. Configure you network +### Deployment Commands -Scaffold-ETH 2 comes with a selection of predefined networks. You can also add your custom network in `packages/foundry/foundry.toml` +
+Understanding deployment scripts structure -2. Generate a new keystore account or import your existing account +Scaffold-ETH 2 uses two types of deployment scripts in `packages/foundry/script`: -The keystore account mentioned in `packages/foundry/.env#ETH_KEYSTORE_ACCOUNT` is the account used to deploy your contracts. Additionally, the deployer account will be used to exectue function call that are part of your deployment script. You can generate a new keystore account with random address which will be used for all your next Scaffold-ETH 2 projects using the following command: +1. `Deploy.s.sol`: Main deployment script that runs all contracts sequentially +2. Individual scripts (e.g., `DeployYourContract.s.sol`): Deploy specific contracts -```shell -yarn generate +Each script inherits from `ScaffoldETHDeploy` which handles: + +- Deployer account setup and funding +- Contract verification preparation +- Exporting ABIs and addresses to the frontend +
+ +
+Basic deploy commands + +1. Deploy all contracts (uses `Deploy.s.sol`): + +``` +yarn deploy ``` -Above command will prompt for password and generate a new keystore account under the name `scaffold-eth-custom`. Now update the `packages/foundry/.env#ETH_KEYSTORE_ACCOUNT=scaffold-eth-custom`. Subsequent `yarn deploy` will prompt the password for this account and use this as a deployer account. +2. Deploy specific contract: + +```bash +yarn deploy --file DeployYourContract.s.sol +``` + +3. Deploy to a network: + +``` +yarn deploy --network --file +``` + +If you don't provide a file name, it will default to `Deploy.s.sol`. + +
-Additionally instead of generating `scaffold-eth-custom` keystore account with random address you can run `yarn account:import` to initialize `scaffold-eth-custom` keystore account with your private key. +
+Environment-specific behavior -Also if you want to use your existing keystore account you can update the `packages/foundry/.env#ETH_KEYSTORE_ACCOUNT` with the name of your existing account and that account will be used for deployments. +**Local Development (`yarn chain`)**: -You can check the configured (generated or manually set) account and balances with: +- Uses `scaffold-eth-default` keystore automatically +- No password needed for deployment +- Uses Anvil's Account #9 by default + +**Live Networks**: + +- Requires custom keystore setup (see "Setting Up Your Deployer Account" below) +- Will prompt for keystore password +
+ +
+Creating new deployments + +1. Create your contract in `packages/foundry/contracts` +2. Create deployment script in `packages/foundry/script` (use existing scripts as templates) +3. Add to main `Deploy.s.sol` if needed +4. Deploy using commands above +
+ +### Setting Up Your Deployer Account + +
+Option 1: Generate new account ``` -yarn account +yarn generate ``` -3. Deploy your smart contract(s) +This creates a `scaffold-eth-custom` keystore in `~/.foundry/keystores/scaffold-eth-custom` account. -Run the command below to deploy the smart contract to the target network. Make sure to have some funds in your deployer account to pay for the transaction. +Update `.env` in `packages/foundry`: ``` -yarn deploy --network +ETH_KEYSTORE_ACCOUNT=scaffold-eth-custom ``` -eg: `yarn deploy --network sepolia` +
-4. Verify your smart contract - You can verify your smart contract on etherscan by running: +
+Option 2: Import existing private key ``` -yarn verify --network +yarn account:import ``` -eg: `yarn verify --network sepolia` +This imports your key as `scaffold-eth-custom`. -This uses `VerifyAll.s.sol` script located in `packages/foundry/script` to verify the contract. +Update `.env`: -**What's next**: +``` +ETH_KEYSTORE_ACCOUNT=scaffold-eth-custom +``` + +
+ +View your account status: + +``` +yarn account +``` -- Edit your smart contract `YourContract.sol` in `packages/foundry/contracts` -- Edit your frontend homepage at `packages/nextjs/app/page.tsx`. For guidance on [routing](https://nextjs.org/docs/app/building-your-application/routing/defining-routes) and configuring [pages/layouts](https://nextjs.org/docs/app/building-your-application/routing/pages-and-layouts) checkout the Next.js documentation. -- Edit your deployment scripts in `packages/script/deploy/Deploy.s.sol` -- Edit your smart contract test in: `packages/foundry/test`. To run test use `yarn foundry:test` +This will ask for your keystore account password. ## Documentation From 310f0ecc578f7b96ab6efc1aa63ab3d8ec3aeb75 Mon Sep 17 00:00:00 2001 From: Shiv Bhonde Date: Tue, 10 Dec 2024 14:59:38 +0530 Subject: [PATCH 07/16] revers section --- README.md | 90 +++++++++++++++++++++++++++---------------------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index a3d8abd7c..69dc54ae1 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,50 @@ Visit your app on: `http://localhost:3000`. You can interact with your smart con ## Deploying to Live Networks +### Setting Up Your Deployer Account + +
+Option 1: Generate new account + +``` +yarn generate +``` + +This creates a `scaffold-eth-custom` keystore in `~/.foundry/keystores/scaffold-eth-custom` account. + +Update `.env` in `packages/foundry`: + +``` +ETH_KEYSTORE_ACCOUNT=scaffold-eth-custom +``` + +
+ +
+Option 2: Import existing private key + +``` +yarn account:import +``` + +This imports your key as `scaffold-eth-custom`. + +Update `.env`: + +``` +ETH_KEYSTORE_ACCOUNT=scaffold-eth-custom +``` + +
+ +View your account status: + +``` +yarn account +``` + +This will ask for your keystore account password. + ### Deployment Commands
@@ -115,7 +159,7 @@ If you don't provide a file name, it will default to `Deploy.s.sol`. **Live Networks**: -- Requires custom keystore setup (see "Setting Up Your Deployer Account" below) +- Requires custom keystore setup (see "Setting Up Your Deployer Account" above) - Will prompt for keystore password
@@ -128,50 +172,6 @@ If you don't provide a file name, it will default to `Deploy.s.sol`. 4. Deploy using commands above -### Setting Up Your Deployer Account - -
-Option 1: Generate new account - -``` -yarn generate -``` - -This creates a `scaffold-eth-custom` keystore in `~/.foundry/keystores/scaffold-eth-custom` account. - -Update `.env` in `packages/foundry`: - -``` -ETH_KEYSTORE_ACCOUNT=scaffold-eth-custom -``` - -
- -
-Option 2: Import existing private key - -``` -yarn account:import -``` - -This imports your key as `scaffold-eth-custom`. - -Update `.env`: - -``` -ETH_KEYSTORE_ACCOUNT=scaffold-eth-custom -``` - -
- -View your account status: - -``` -yarn account -``` - -This will ask for your keystore account password. - ## Documentation Visit our [docs](https://docs.scaffoldeth.io) to learn how to start building with Scaffold-ETH 2. From 2b617da4106b25c1c6e41664a9a60d109bdc10db Mon Sep 17 00:00:00 2001 From: Shiv Bhonde Date: Tue, 10 Dec 2024 15:03:30 +0530 Subject: [PATCH 08/16] add keystore link --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 69dc54ae1..c176d397a 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ Visit your app on: `http://localhost:3000`. You can interact with your smart con yarn generate ``` -This creates a `scaffold-eth-custom` keystore in `~/.foundry/keystores/scaffold-eth-custom` account. +This creates a `scaffold-eth-custom` [keystore](https://book.getfoundry.sh/reference/cli/cast/wallet#cast-wallet) in `~/.foundry/keystores/scaffold-eth-custom` account. Update `.env` in `packages/foundry`: @@ -104,7 +104,7 @@ View your account status: yarn account ``` -This will ask for your keystore account password. +This will ask for your [keystore](https://book.getfoundry.sh/reference/cli/cast/wallet#cast-wallet) account password set in `packages/foundry/.env`. ### Deployment Commands From a28ff9962b288472f38834f73b5fa8eedc6c30ac Mon Sep 17 00:00:00 2001 From: Shiv Bhonde Date: Tue, 10 Dec 2024 15:17:48 +0530 Subject: [PATCH 09/16] use wsl in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c176d397a..9c9959c3f 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Before you begin, you need to install the following tools: - [Git](https://git-scm.com/downloads) - [Foundryup](https://book.getfoundry.sh/getting-started/installation) -> **Note for Windows users**. Foundryup is not currently supported by Powershell or Cmd. You will need to use [Git BASH](https://gitforwindows.org/) or [WSL](https://learn.microsoft.com/en-us/windows/wsl/install) as your terminal. +> **Note for Windows users**. Foundryup is not currently supported by Powershell or Cmd, and has issues with Git Bash. You will need to use [WSL](https://learn.microsoft.com/en-us/windows/wsl/install) as your terminal. ## Quickstart From ba8e52355879dce01e0a5eed03d3432e59f68e94 Mon Sep 17 00:00:00 2001 From: Shiv Bhonde Date: Fri, 13 Dec 2024 09:38:30 +0530 Subject: [PATCH 10/16] ignore run-latest file in generateTsAbi script --- packages/foundry/scripts-js/generateTsAbis.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/foundry/scripts-js/generateTsAbis.js b/packages/foundry/scripts-js/generateTsAbis.js index 9fab43853..cd128fc5d 100644 --- a/packages/foundry/scripts-js/generateTsAbis.js +++ b/packages/foundry/scripts-js/generateTsAbis.js @@ -47,7 +47,12 @@ function getDeploymentHistory(broadcastPath) { // Sort files to process them in chronological order const runFiles = files - .filter((file) => file.startsWith("run-") && file.endsWith(".json")) + .filter( + (file) => + file.startsWith("run-") && + file.endsWith(".json") && + !file.includes("run-latest") + ) .sort((a, b) => { // Extract run numbers and compare them const runA = parseInt(a.match(/run-(\d+)/)?.[1] || "0"); From c2bbb64f60821acec172fca86ec51f5ffa1f1543 Mon Sep 17 00:00:00 2001 From: Shiv Bhonde Date: Fri, 13 Dec 2024 10:11:59 +0530 Subject: [PATCH 11/16] add warnings and usefull message during deployments --- packages/foundry/scripts-js/parseArgs.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/packages/foundry/scripts-js/parseArgs.js b/packages/foundry/scripts-js/parseArgs.js index ec955f239..0fcd23075 100644 --- a/packages/foundry/scripts-js/parseArgs.js +++ b/packages/foundry/scripts-js/parseArgs.js @@ -37,6 +37,27 @@ for (let i = 0; i < args.length; i++) { } } +if ( + process.env.ETH_KEYSTORE_ACCOUNT === "scaffold-eth-default" && + network !== "localhost" +) { + console.log( + "\nError: Cannot deploy to live network using 'scaffold-eth-default' keystore account!", + "\nPlease update your .env file to use a custom keysotre account by setting ETH_KEYSTORE_ACCOUNT='scaffold-eth-custom'" + ); + process.exit(0); +} + +if ( + process.env.ETH_KEYSTORE_ACCOUNT !== "scaffold-eth-default" && + network === "localhost" +) { + console.log( + `\nWarning: Using ${process.env.ETH_KEYSTORE_ACCOUNT} keystore account on localhost.`, + `\nPlease enter the password for ${process.env.ETH_KEYSTORE_ACCOUNT} account\n` + ); +} + // Set environment variables for the make command process.env.DEPLOY_SCRIPT = `script/${fileName}`; process.env.RPC_URL = network; From f614a01a80bac8132c4c58bc5907806ccd48b204 Mon Sep 17 00:00:00 2001 From: Shiv Bhonde Date: Fri, 13 Dec 2024 10:37:33 +0530 Subject: [PATCH 12/16] valid network name check --- packages/foundry/scripts-js/parseArgs.js | 45 ++++++++++++++++++++---- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/packages/foundry/scripts-js/parseArgs.js b/packages/foundry/scripts-js/parseArgs.js index 0fcd23075..6e8dd7069 100644 --- a/packages/foundry/scripts-js/parseArgs.js +++ b/packages/foundry/scripts-js/parseArgs.js @@ -1,5 +1,11 @@ import { spawnSync } from "child_process"; import { config } from "dotenv"; +import { join, dirname } from "path"; +import { readFileSync } from "fs"; +import { parse } from "toml"; +import { fileURLToPath } from "url"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); config(); // Get all arguments after the script name @@ -11,12 +17,10 @@ let network = "localhost"; if (args.includes("--help") || args.includes("-h")) { console.log(` Usage: yarn deploy [options] - Options: --file Specify the deployment script file (default: Deploy.s.sol) --network Specify the network (default: localhost) --help, -h Show this help message - Examples: yarn deploy --file DeployYourContract.s.sol --network sepolia yarn deploy --network sepolia @@ -37,17 +41,46 @@ for (let i = 0; i < args.length; i++) { } } +// Check if the network exists in rpc_endpoints +try { + const foundryTomlPath = join(__dirname, "..", "foundry.toml"); + const tomlString = readFileSync(foundryTomlPath, "utf-8"); + const parsedToml = parse(tomlString); + + if (!parsedToml.rpc_endpoints[network]) { + console.log( + `\nāŒ Error: Network '${network}' not found in foundry.toml!`, + "\nPlease check `foundry.toml` for available networks in the [rpc_endpoints] section or add a new network." + ); + process.exit(1); + } +} catch (error) { + console.error("\nāŒ Error reading or parsing foundry.toml:", error); + process.exit(1); +} + +// Check for default account on live network if ( process.env.ETH_KEYSTORE_ACCOUNT === "scaffold-eth-default" && network !== "localhost" ) { - console.log( - "\nError: Cannot deploy to live network using 'scaffold-eth-default' keystore account!", - "\nPlease update your .env file to use a custom keysotre account by setting ETH_KEYSTORE_ACCOUNT='scaffold-eth-custom'" - ); + console.log(` +āŒ Error: Cannot deploy to live network using default keystore account! + +To deploy to ${network}, please follow these steps: + +1. If you haven't generated a keystore account yet: + $ yarn generate + +2. Update your .env file: + ETH_KEYSTORE_ACCOUNT='scaffold-eth-custom' + +The default account (scaffold-eth-default) can only be used for localhost deployments. +`); process.exit(0); } +// Check for custom account on localhost if ( process.env.ETH_KEYSTORE_ACCOUNT !== "scaffold-eth-default" && network === "localhost" From e37e4a6089528f3b9dd9d84ceb66bd926bd2083c Mon Sep 17 00:00:00 2001 From: Shiv Bhonde Date: Fri, 13 Dec 2024 10:46:41 +0530 Subject: [PATCH 13/16] tweak readme --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9c9959c3f..136172f9a 100644 --- a/README.md +++ b/README.md @@ -153,9 +153,8 @@ If you don't provide a file name, it will default to `Deploy.s.sol`. **Local Development (`yarn chain`)**: -- Uses `scaffold-eth-default` keystore automatically -- No password needed for deployment -- Uses Anvil's Account #9 by default +- No password needed for deployment if `ETH_KEYSTORE_ACCOUNT=scaffold-eth-default` is set in `.env` file. +- Uses Anvil's Account #9 as default keystore account **Live Networks**: From bf0ad8448c7e0f398dc07e50e546b3dad8827c66 Mon Sep 17 00:00:00 2001 From: Shiv Bhonde Date: Fri, 13 Dec 2024 13:06:41 +0530 Subject: [PATCH 14/16] warnin emoji --- packages/foundry/scripts-js/parseArgs.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/foundry/scripts-js/parseArgs.js b/packages/foundry/scripts-js/parseArgs.js index 6e8dd7069..453f053c2 100644 --- a/packages/foundry/scripts-js/parseArgs.js +++ b/packages/foundry/scripts-js/parseArgs.js @@ -80,15 +80,19 @@ The default account (scaffold-eth-default) can only be used for localhost deploy process.exit(0); } -// Check for custom account on localhost if ( process.env.ETH_KEYSTORE_ACCOUNT !== "scaffold-eth-default" && network === "localhost" ) { - console.log( - `\nWarning: Using ${process.env.ETH_KEYSTORE_ACCOUNT} keystore account on localhost.`, - `\nPlease enter the password for ${process.env.ETH_KEYSTORE_ACCOUNT} account\n` - ); + console.log(` +āš ļø Warning: Using ${process.env.ETH_KEYSTORE_ACCOUNT} keystore account on localhost. + +You can either: +1. Enter the password for ${process.env.ETH_KEYSTORE_ACCOUNT} account + OR +2. Use the default account by setting in .env and re-run the command: + ETH_KEYSTORE_ACCOUNT='scaffold-eth-default' +`); } // Set environment variables for the make command From 8b7b031867c69cdd80d3ca4595cb360a1efef3fa Mon Sep 17 00:00:00 2001 From: Shiv Bhonde Date: Fri, 13 Dec 2024 19:31:30 +0530 Subject: [PATCH 15/16] tweak warning istruction --- packages/foundry/scripts-js/parseArgs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/foundry/scripts-js/parseArgs.js b/packages/foundry/scripts-js/parseArgs.js index 453f053c2..a9f2fea78 100644 --- a/packages/foundry/scripts-js/parseArgs.js +++ b/packages/foundry/scripts-js/parseArgs.js @@ -90,7 +90,7 @@ if ( You can either: 1. Enter the password for ${process.env.ETH_KEYSTORE_ACCOUNT} account OR -2. Use the default account by setting in .env and re-run the command: +2. Set the default keystore account in your .env and re-run the command to skip password prompt: ETH_KEYSTORE_ACCOUNT='scaffold-eth-default' `); } From 9ced1317381e7d31411eb7fb2b7eca2abc65ad99 Mon Sep 17 00:00:00 2001 From: Shiv Bhonde Date: Mon, 16 Dec 2024 10:50:10 +0530 Subject: [PATCH 16/16] pick the latest deployment based on timestamp --- packages/foundry/scripts-js/generateTsAbis.js | 58 ++++++++++++------- 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/packages/foundry/scripts-js/generateTsAbis.js b/packages/foundry/scripts-js/generateTsAbis.js index cd128fc5d..0862e875e 100644 --- a/packages/foundry/scripts-js/generateTsAbis.js +++ b/packages/foundry/scripts-js/generateTsAbis.js @@ -133,41 +133,59 @@ function getInheritedFunctions(mainArtifact) { function processAllDeployments(broadcastPath) { const scriptFolders = getDirectories(broadcastPath); - const allContracts = {}; + const allDeployments = new Map(); - // Process each script folder scriptFolders.forEach((scriptFolder) => { const scriptPath = join(broadcastPath, scriptFolder); const chainFolders = getDirectories(scriptPath); - // Process each chain folder within the script folder chainFolders.forEach((chainId) => { const chainPath = join(scriptPath, chainId); - - // Initialize chain object if it doesn't exist - if (!allContracts[chainId]) { - allContracts[chainId] = {}; - } - - // Get deployment history for this specific chain path const deploymentHistory = getDeploymentHistory(chainPath); - // Process each deployment deploymentHistory.forEach((deployment) => { - const artifact = getArtifactOfContract(deployment.contractName); - if (artifact) { - allContracts[chainId][deployment.contractName] = { - address: deployment.address, - abi: artifact.abi, - inheritedFunctions: getInheritedFunctions(artifact), - deploymentFile: deployment.deploymentFile, - deploymentScript: scriptFolder, // Track which script deployed this contract - }; + const timestamp = parseInt( + deployment.deploymentFile.match(/run-(\d+)/)?.[1] || "0" + ); + const key = `${chainId}-${deployment.contractName}`; + + // Only update if this deployment is newer + if ( + !allDeployments.has(key) || + timestamp > allDeployments.get(key).timestamp + ) { + allDeployments.set(key, { + ...deployment, + timestamp, + chainId, + deploymentScript: scriptFolder, + }); } }); }); }); + const allContracts = {}; + + allDeployments.forEach((deployment) => { + const { chainId, contractName } = deployment; + const artifact = getArtifactOfContract(contractName); + + if (artifact) { + if (!allContracts[chainId]) { + allContracts[chainId] = {}; + } + + allContracts[chainId][contractName] = { + address: deployment.address, + abi: artifact.abi, + inheritedFunctions: getInheritedFunctions(artifact), + deploymentFile: deployment.deploymentFile, + deploymentScript: deployment.deploymentScript, + }; + } + }); + return allContracts; }