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

Refresh Foundry Tutorials #908

Merged
merged 14 commits into from May 8, 2024
11 changes: 11 additions & 0 deletions .snippets/code/builders/build/eth-api/dev-env/foundry/ERC20.sol
@@ -0,0 +1,11 @@
pragma solidity ^0.8.0;

// Import OpenZeppelin Contract
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

// This ERC-20 contract mints the specified amount of tokens to the contract creator
contract MyToken is ERC20 {
constructor(uint256 initialSupply) ERC20("MyToken", "MYTOK") {
themacexpert marked this conversation as resolved.
Show resolved Hide resolved
_mint(msg.sender, initialSupply);
}
}
themacexpert marked this conversation as resolved.
Show resolved Hide resolved
Expand Up @@ -6,7 +6,7 @@ src/MyToken.sol:MyToken</span>
<br>
<span data-ty>[⠒] Compiling...</span>
<span data-ty>No files changed, compilation skipped</span>
<span data-ty>Deployer: 0x3B939FeaD1557C741Ff06492FD0127bd287A421espan>
<span data-ty>Deployer: 0x3B939FeaD1557C741Ff06492FD0127bd287A421e</span>
<span data-ty>Deployed to: 0xc111402Aa1136ff6224106709ae51864512eC68f</span>
<span data-ty>Transaction hash: 0xd77fc26aa296e81f35718b5878cda98e8371f6bf33b0f57e7d92997a36cf6465</span>
</div>
@@ -1,7 +1,7 @@
<div id="termynal" data-termynal>
<span data-ty="input" data-ty-prompt="➜"> !fork https://rpc.api.moonbase.moonbeam.network</span>
<span data-ty>Set fork URL to https://rpc.api.moonbase.moonbeam.network</span>
<span data-ty="input" data-ty-prompt="➜"> 0x4c5A56ed5A4FF7B09aA86560AfD7d383F4831Cce.balance</span>
<span data-ty="input" data-ty-prompt="➜"> 0x12E7BCCA9b1B15f33585b5fc898B967149BDb9a5.balance</span>
<span data-ty>Type: uint</span>
<span data-ty>├ Hex: 0x000000000000000000000000000000000000000000000358affd3d76ebb78555</span>
<span data-ty>└ Decimal: 15803094286802091476309</span>
Expand Down
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import "forge-std/Test.sol";
import {MyToken} from "../src/MyToken.sol";
import {Container, ContainerStatus} from "../src/Container.sol";

contract ContainerTest is Test {
MyToken public token;
Container public container;

uint256 constant CAPACITY = 100;

// Runs before each test
function setUp() public {
token = new MyToken(1000);
container = new Container(token, CAPACITY);
}

// Tests if the container is unsatisfied right after constructing
function testInitialUnsatisfied() public {
assertEq(token.balanceOf(address(container)), 0);
assertTrue(container.status() == ContainerStatus.Unsatisfied);
}

// Tests if the container will be "full" once it reaches its capacity
function testContainerFull() public {
token.transfer(address(container), CAPACITY);
container.updateStatus();

assertEq(token.balanceOf(address(container)), CAPACITY);
assertTrue(container.status() == ContainerStatus.Full);
}
}
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// Import OpenZeppelin Contract
import {MyToken} from "./MyToken.sol";

enum ContainerStatus {
Unsatisfied,
Full,
Overflowing
}

contract Container {
MyToken token;
uint256 capacity;
ContainerStatus public status;

constructor(MyToken _token, uint256 _capacity) {
token = _token;
capacity = _capacity;
status = ContainerStatus.Unsatisfied;
}

// Updates the status value based on the number of tokens that this contract has
function updateStatus() public {
address container = address(this);
uint256 balance = token.balanceOf(container);
if (balance < capacity) {
status = ContainerStatus.Unsatisfied;
} else if (balance == capacity) {
status = ContainerStatus.Full;
} else if (_isOverflowing(balance)) {
status = ContainerStatus.Overflowing;
}
}

// Returns true if the contract should be in an overflowing state, false if otherwise
function _isOverflowing(uint256 balance) internal view returns (bool) {
return balance > capacity;
}
}
@@ -0,0 +1,22 @@
pragma solidity ^0.8.0;

import "forge-std/Script.sol";
import {MyToken} from "../src/MyToken.sol";
import {Container} from "../src/Container.sol";

contract ContainerDeployScript is Script {
// Runs the script; deploys MyToken and Container
function run() public {
// Get the private key from the .env
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(deployerPrivateKey);

// Make a new token
MyToken token = new MyToken(1000);

// Make a new container
new Container(token, 500);

vm.stopBroadcast();
}
}
@@ -0,0 +1,7 @@
contract ContainerHarness is Container {
constructor(MyToken _token, uint256 _capacity) Container(_token, _capacity) {}

function exposed_isOverflowing(uint256 balance) external view returns(bool) {
return _isOverflowing(balance);
}
}
18 changes: 18 additions & 0 deletions .snippets/code/tutorials/eth-api/foundry-start-to-end/ERC20.sol
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// Import OpenZeppelin Contract
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

// This ERC-20 contract mints the specified amount of tokens to the contract creator
contract MyToken is ERC20 {
constructor(uint256 initialSupply) ERC20("MyToken", "MYTOK") {
_mint(msg.sender, initialSupply);
}

// An external minting function allows anyone to mint as many tokens as they want
function mint(uint256 toMint, address to) external {
require(toMint <= 1 ether);
_mint(to, toMint);
}
}
@@ -0,0 +1,7 @@
// Fuzz tests for success upon minting tokens one ether or below
function testMintOneEtherOrBelow(uint256 amountToMint) public {
vm.assume(amountToMint <= 1 ether);

token.mint(amountToMint, msg.sender);
assertEq(token.balanceOf(msg.sender), amountToMint);
}
@@ -0,0 +1,6 @@
// Fuzz tests for failure upon minting tokens above one ether
function testFailMintAboveOneEther(uint256 amountToMint) public {
vm.assume(amountToMint > 1 ether);

token.mint(amountToMint, msg.sender);
}
@@ -0,0 +1,7 @@
// Tests for negative cases of the internal _isOverflowing function
function testIsOverflowingFalse() public {
ContainerHarness harness = new ContainerHarness(token , CAPACITY);
assertFalse(harness.exposed_isOverflowing(CAPACITY - 1));
assertFalse(harness.exposed_isOverflowing(CAPACITY));
assertFalse(harness.exposed_isOverflowing(0));
}
@@ -0,0 +1,18 @@
pragma solidity ^0.8.0;

import "forge-std/Test.sol";
import "../src/MyToken.sol";

contract MyTokenTest is Test {
MyToken public token;

// Runs before each test
function setUp() public {
token = new MyToken(100);
}

// Tests if minting during the constructor happens properly
function testConstructorMint() public {
assertEq(token.balanceOf(address(this)), 100);
}
}
@@ -0,0 +1,19 @@
// Fork tests in the Moonbase Alpha environment
function testAlternateTokenOnMoonbaseFork() public {
// Creates and selects a fork, returns a fork ID
uint256 moonbaseFork = vm.createFork("moonbase");
vm.selectFork(moonbaseFork);
assertEq(vm.activeFork(), moonbaseFork);

// Get token that's already deployed & deploys a container instance
token = MyToken(0x359436610E917e477D73d8946C2A2505765ACe90);
container = new Container(token, CAPACITY);

// Mint tokens to the container & update container status
token.mint(CAPACITY, address(container));
container.updateStatus();

// Assert that the capacity is full, just like the rest of the time
assertEq(token.balanceOf(address(container)), CAPACITY);
assertTrue(container.status() == ContainerStatus.Full);
}
@@ -0,0 +1,34 @@
<div id="termynal" data-termynal>
<span data-ty>Script ran successfully.</span>
<span data-ty>Setting up 1 EVM.</span>
<span data-ty>Simulated On-chain Traces:</span>
<span data-ty> [488164] → new MyToken@0xAEe1a769b10d03a6CeB4D9DFd3aBB2EF807ee6aa</span>
<span data-ty> ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: 0x3B939FeaD1557C741Ff06492FD0127bd287A421e, value: 1000)</span>
<span data-ty> └─ ← [Return] 1980 bytes of code</span>
<span data-ty> [133238] → new Container@0xeb1Ff38A645Eae4E64dFb93772D8129F88E11Ab1</span>
<span data-ty> └─ ← [Return] 432 bytes of code</span>
<span data-ty>Chain 1287</span>
<span data-ty>Estimated gas price: 0.125 gwei</span>
<span data-ty>Estimated total gas used for script: 1670737</span>
<span data-ty>Estimated amount required: 0.000208842125 ETH</span>
<span data-ty>Sending transactions [0 - 0].</span>
<span data-ty>⠁ [00:00:00] [#############################################################>-------------------------------------------------------------] 1/2 txes (0.7s)</span>
<span data-ty>Waiting for receipts.</span>
<span data-ty>⠉ [00:00:22] [#######################################################################################################################] 1/1 receipts (0.0s)</span>
<span data-ty>moonbase</span>
<span data-ty>✅ [Success]Hash: 0x2ad8994c12af74bdcb04873e13d97dc543a2fa7390c1e194732ab43ec828cb3b</span>
<span data-ty>Contract Address: 0xAEe1a769b10d03a6CeB4D9DFd3aBB2EF807ee6aa</span>
<span data-ty>Block: 6717135</span>
<span data-ty>Paid: 0.000116937 ETH (935496 gas * 0.125 gwei)</span>
<span data-ty>Sending transactions [1 - 1].</span>
<span data-ty>⠉ [00:00:23] [###########################################################################################################################] 2/2 txes (0.0s)</span>
<span data-ty>Waiting for receipts.</span>
<span data-ty>⠉ [00:00:21] [#######################################################################################################################] 1/1 receipts (0.0s)</span>
<span data-ty>moonbase</span>
<span data-ty>✅ [Success]Hash: 0x3bfb4cee2be4269badc57e0053d8b4d94d9d57d7936ecaa1e13ac1e2199f3b12</span>
<span data-ty>Contract Address: 0xeb1Ff38A645Eae4E64dFb93772D8129F88E11Ab1</span>
<span data-ty>Block: 6717137</span>
<span data-ty>Paid: 0.000035502 ETH (284016 gas * 0.125 gwei)</span>
<span data-ty>ONCHAIN EXECUTION COMPLETE & SUCCESSFUL.</span>
<span data-ty>Total Paid: 0.000152439 ETH (1219512 gas * avg 0.125 gwei)</span>
</div>
@@ -0,0 +1,16 @@
<div id="termynal" data-termynal>
<span data-ty="input"><span class="file-path"></span>forge test</span>
<span data-ty>[⠊] Compiling...</span>
<span data-ty>No files changed, compilation skipped</span>
<br>
<span data-ty>Ran 1 test for test/MyToken.t.sol:MyTokenTest</span>
<span data-ty>[PASS] testConstructorMint() (gas: 10651)</span>
<span data-ty>Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 361.83µs (39.46µs CPU time)</span>
<br>
<span data-ty>Ran 2 tests for test/Container.t.sol:ContainerTest</span>
<span data-ty>[PASS] testContainerFull() (gas: 73204)</span>
<span data-ty>[PASS] testInitialUnsatisfied() (gas: 18476)</span>
<span data-ty>Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 422.00µs (128.67µs CPU time)</span>
<span data-ty>Ran 2 test suites in 138.17ms (783.83µs CPU time): 3 tests passed, 0 failed, 0 skipped (3 total tests)</span>
<span data-ty="input"><span class="file-path"></span></span>
</div>
@@ -0,0 +1,19 @@
<div id="termynal" data-termynal>
<span data-ty="input"><span class="file-path"></span>forge test</span>
<span data-ty>[⠊] Compiling...</span>
<span data-ty>[⠒] Compiling 1 files with 0.8.20</span>
<span data-ty>[⠢] Solc 0.8.20 finished in 1.06s</span>
<span data-ty>Compiler run successful</span>
<br>
<span data-ty>Ran 1 test for test/MyToken.t.sol:MyTokenTest</span>
<span data-ty>[PASS] testConstructorMint() (gas: 10651)</span>
<span data-ty>Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 498.00µs (48.96µs CPU time)</span>
<br>
<span data-ty>Ran 3 tests for test/Container.t.sol:ContainerTest</span>
<span data-ty>[PASS] testContainerFull() (gas: 73238)</span>
<span data-ty>[PASS] testInitialUnsatisfied() (gas: 18510)</span>
<span data-ty>[PASS] testIsOverflowingFalse() (gas: 192130)</span>
<span data-ty>Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 606.17µs (183.54µs CPU time)</span>
<span data-ty>Ran 2 test suites in 138.29ms (1.10ms CPU time): 4 tests passed, 0 failed, 0 skipped (4 total tests)</span>
<span data-ty="input"><span class="file-path"></span></span>
</div>
@@ -0,0 +1,21 @@
<div id="termynal" data-termynal>
<span data-ty="input"><span class="file-path"></span>forge test</span>
<span data-ty>[⠊] Compiling...</span>
<span data-ty>[⠊] Compiling 1 files with 0.8.20</span>
<span data-ty>[⠒] Solc 0.8.20 finished in 982.65ms</span>
<span data-ty>Compiler run successful</span>
<br>
<span data-ty>Ran 3 tests for test/Container.t.sol:ContainerTest</span>
<span data-ty>[PASS] testContainerFull() (gas: 73238)</span>
<span data-ty>[PASS] testInitialUnsatisfied() (gas: 18510)</span>
<span data-ty>[PASS] testIsOverflowingFalse() (gas: 192130)</span>
<span data-ty>Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 446.25µs (223.67µs CPU time)</span>
<br>
<span data-ty>Ran 3 tests for test/MyToken.t.sol:MyTokenTest</span>
<span data-ty>[PASS] testConstructorMint() (gas: 10651)</span>
<span data-ty>[PASS] testFailMintAboveOneEther(uint256) (runs: 256, μ: 8462, ~: 8462)</span>
<span data-ty>[PASS] testMintOneEtherOrBelow(uint256) (runs: 256, μ: 37939, ~: 39270)</span>
<span data-ty>Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 10.78ms (18.32ms CPU time)</span>
<span data-ty>Ran 2 test suites in 138.88ms (11.23ms CPU time): 6 tests passed, 0 failed, 0 skipped (6 total tests)</span>
<span data-ty="input"><span class="file-path"></span></span>
</div>
@@ -0,0 +1,24 @@
<div id="termynal" data-termynal>
<span data-ty="input"><span class="file-path"></span>forge test</span>
<span data-ty>[PASS] testIsOverflowingFalse() (gas: 192130)</span>
<span data-ty>Traces:</span>
<span data-ty> [192130] ContainerTest::testIsOverflowingFalse()</span>
<span data-ty> ├─ [151256] → new ContainerHarness@0xF62849F9A0B5Bf2913b396098F7c7019b51A820a</span>
<span data-ty> │ └─ ← [Return] 522 bytes of code</span>
<span data-ty> ├─ [421] ContainerHarness::exposed_isOverflowing(99) [staticcall]</span>
<span data-ty> │ └─ ← [Return] false</span>
<span data-ty> ├─ [0] VM::assertFalse(false) [staticcall]</span>
<span data-ty> │ └─ ← [Return]</span>
<span data-ty> ├─ [421] ContainerHarness::exposed_isOverflowing(100) [staticcall]</span>
<span data-ty> │ └─ ← [Return] false</span>
<span data-ty> ├─ [0] VM::assertFalse(false) [staticcall]</span>
<span data-ty> │ └─ ← [Return]</span>
<span data-ty> ├─ [421] ContainerHarness::exposed_isOverflowing(0) [staticcall]</span>
<span data-ty> │ └─ ← [Return] false</span>
<span data-ty> ├─ [0] VM::assertFalse(false) [staticcall]</span>
<span data-ty> │ └─ ← [Return]</span>
<span data-ty> └─ ← [Stop]</span>
<span data-ty>Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 2.07s (2.07s CPU time)</span>
<span data-ty>Ran 2 test suites in 2.44s (2.08s CPU time): 7 tests passed, 0 failed, 0 skipped (7 total tests)</span>
<span data-ty="input"><span class="file-path"></span></span>
</div>
18 changes: 4 additions & 14 deletions builders/build/eth-api/dev-env/foundry.md
Expand Up @@ -46,10 +46,10 @@ You will need to create a Foundry project if you don't already have one. You can
cargo install --git https://github.com/foundry-rs/foundry foundry-cli anvil --bins --locked
```

2. Create the project, which will create a folder with three folders within it:
2. Create the project, which will create a folder with three folders within it, and open it:

```bash
forge init foundry
forge init foundry && cd foundry
```

With the default project created, you should see three folders.
Expand All @@ -72,17 +72,7 @@ touch MyToken.sol
Open the file and add the following contract to it:

```solidity
pragma solidity ^0.8.0;

// Import OpenZeppelin Contract
import "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";

// This ERC-20 contract mints the specified amount of tokens to the contract creator
contract MyToken is ERC20 {
constructor(uint256 initialSupply) ERC20("MyToken", "MYTOK") {
_mint(msg.sender, initialSupply);
}
}
--8<-- 'code/builders/build/eth-api/dev-env/foundry/ERC20.sol'
```

Before you attempt to compile, install OpenZeppelin contracts as a dependency. You may have to commit previous changes to git beforehand. By default, Foundry uses git submodules instead of npm packages, so the traditional npm import path and command are not used. Instead, use the name of OpenZeppelin's GitHub repository:
Expand Down Expand Up @@ -439,7 +429,7 @@ You can even fork networks while using Chisel:
Then, for example, you can query the balance of one of Moonbase Alpha's collators:

```text
0x4c5A56ed5A4FF7B09aA86560AfD7d383F4831Cce.balance
{{ networks.moonbase.staking.candidates.address1 }}.balance
```

--8<-- 'code/builders/build/eth-api/dev-env/foundry/terminal/query-balance.md'
Expand Down