Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Foundry imporvements #1011

Merged
merged 16 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 84 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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:

```
Expand All @@ -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
### Setting Up Your Deployer Account

Scaffold-ETH 2 comes with a selection of predefined networks. You can also add your custom network in `packages/foundry/foundry.toml`
<details>
<summary>Option 1: Generate new account</summary>

```
yarn generate
```

2. Generate a new keystore account or import your existing 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.

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:
Update `.env` in `packages/foundry`:

```shell
yarn generate
```
ETH_KEYSTORE_ACCOUNT=scaffold-eth-custom
```

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.
</details>

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.
<details>
<summary>Option 2: Import existing private key</summary>

```
yarn account:import
```

This imports your key as `scaffold-eth-custom`.

Update `.env`:

```
ETH_KEYSTORE_ACCOUNT=scaffold-eth-custom
```

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.
</details>

You can check the configured (generated or manually set) account and balances with:
View your account status:

```
yarn account
```

3. Deploy your smart contract(s)
This will ask for your [keystore](https://book.getfoundry.sh/reference/cli/cast/wallet#cast-wallet) account password set in `packages/foundry/.env`.

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.
### Deployment Commands

<details>
<summary>Understanding deployment scripts structure</summary>

Scaffold-ETH 2 uses two types of deployment scripts in `packages/foundry/script`:

1. `Deploy.s.sol`: Main deployment script that runs all contracts sequentially
2. Individual scripts (e.g., `DeployYourContract.s.sol`): Deploy specific contracts

Each script inherits from `ScaffoldETHDeploy` which handles:

- Deployer account setup and funding
- Contract verification preparation
- Exporting ABIs and addresses to the frontend
</details>

<details>
<summary>Basic deploy commands</summary>

1. Deploy all contracts (uses `Deploy.s.sol`):

```
yarn deploy --network <network-name>
yarn deploy
```

eg: `yarn deploy --network sepolia`
2. Deploy specific contract:

4. Verify your smart contract
You can verify your smart contract on etherscan by running:
```bash
yarn deploy --file DeployYourContract.s.sol
```

3. Deploy to a network:

```
yarn verify --network <network-name>
yarn deploy --network <network-name> --file <file-name>
```

eg: `yarn verify --network sepolia`
If you don't provide a file name, it will default to `Deploy.s.sol`.

</details>

<details>
<summary>Environment-specific behavior</summary>

**Local Development (`yarn chain`)**:

- Uses `scaffold-eth-default` keystore automatically
- No password needed for deployment
- Uses Anvil's Account #9 by default

**Live Networks**:

This uses `VerifyAll.s.sol` script located in `packages/foundry/script` to verify the contract.
- Requires custom keystore setup (see "Setting Up Your Deployer Account" above)
- Will prompt for keystore password
</details>

**What's next**:
<details>
<summary>Creating new deployments</summary>

- 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`
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
</details>

## Documentation

Expand Down
36 changes: 27 additions & 9 deletions packages/foundry/Makefile
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -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:
Expand All @@ -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

Expand All @@ -82,4 +101,3 @@ verify:
forge script script/VerifyAll.s.sol --ffi --rpc-url $(RPC_URL)

build-and-verify: build verify

111 changes: 53 additions & 58 deletions packages/foundry/contracts/YourContract.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 { }
}
5 changes: 2 additions & 3 deletions packages/foundry/foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 2 additions & 2 deletions packages/foundry/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading