-
Notifications
You must be signed in to change notification settings - Fork 68
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #96 from PoCInnovation/p2p/proxy_workshop
feat(p2p/proxy_workshop): add v1 of proxy workshop
- Loading branch information
Showing
10 changed files
with
758 additions
and
0 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
# 💻 Setup - Foundry & VSCode extension | ||
|
||
[Foundry](https://book.getfoundry.sh/) is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust. We will need it throughout the workshop. | ||
|
||
## 📡 Download foundry | ||
|
||
- Open your terminal and type | ||
|
||
```bash | ||
curl -L https://foundry.paradigm.xyz | bash | ||
``` | ||
|
||
This will download foundryup. | ||
|
||
- Then, you can download foundry by running `foundryup` | ||
- If everything went fine you should be able to use `forge`, `anvil`, `chisel` and `cast`. | ||
- If you are on macos you will need to install `libusb` with | ||
|
||
```bash | ||
brew install libusb | ||
``` | ||
|
||
After the installation, run the following command to ensure it has been properly installed on your computer: | ||
|
||
```bash | ||
forge --version | ||
``` | ||
|
||
It should print your current version. | ||
|
||
If you have some troubles during the installation, you can refer to the [official documentation](https://book.getfoundry.sh/getting-started/installation). | ||
|
||
## Create a foundry project | ||
|
||
Once everything is done, you can create a new project using | ||
|
||
```bash | ||
forge init new_project | ||
cd new_project | ||
``` | ||
|
||
📂 This should create a new directory with a brand new foundry project | ||
|
||
If you already have a repository, you might need to add | ||
|
||
```bash | ||
--no-commit | ||
``` | ||
|
||
The first thing you wants to do is set the solidity version of this project in the `foundry.toml` file wich is the configuration file of foundry. | ||
|
||
You can do this by adding in the "[profile.default]" section: | ||
|
||
```toml | ||
solc_version = "0.8.25" | ||
``` | ||
|
||
## 🧩 VSCode Integration | ||
|
||
I recommand you to install [solidity vscode extension](https://marketplace.visualstudio.com/items?itemName=JuanBlanco.solidity), it is an extension that simplifies development in Solidity. | ||
|
||
Also, I recommand you to use the extension formatter. It will format your code on save, which allows you to have a clean codebase. To do so: | ||
|
||
- Create a `.vscode/settings.json` file with this content | ||
|
||
```json | ||
{ | ||
"editor.formatOnSave": true, | ||
"[solidity]": { | ||
"editor.defaultFormatter": "NomicFoundation.hardhat-solidity" | ||
} | ||
} | ||
``` | ||
|
||
## Back to the workshop | ||
|
||
[Jump !](./README.md) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
# Solidity Essentials to create a proxy | ||
|
||
## Introduction | ||
|
||
**Solidity** is a **programming language** specifically **designed for developing smart contracts on the Ethereum blockchain**. Understanding Solidity is crucial for creating **proxys**. Here's a summary of the key Solidity concepts you'll need to know. | ||
|
||
## Contracts | ||
|
||
- **Contracts**: The building blocks of Ethereum smart contracts. | ||
- [Solidity by Example - Hello World](https://solidity-by-example.org/hello-world/) | ||
|
||
## Data Types and State Variables | ||
|
||
- **Data Types**: Essential types include `uint`, `address`, `string`, and `bool`. | ||
- [Solidity by Example - Data Types](https://solidity-by-example.org/primitives/) | ||
- **State Variables**: Hold data across function calls, stored on the blockchain. | ||
- [Solidity by Example - Variables](https://solidity-by-example.org/variables/) | ||
|
||
## Visibility Modifiers | ||
|
||
- **public**: Can be accessed internally and externally. | ||
- **internal**: Accessible only within the contract and derived contracts. | ||
- **external**: Can be called from outside the contract. | ||
- **private**: Accessible only within the contract. | ||
- [Solidity by Example - Visibility](https://solidity-by-example.org/visibility/) | ||
|
||
## Data Locations | ||
|
||
- **Storage**: Persistent data stored on the blockchain. | ||
- **Memory**: Temporary data stored during function execution. | ||
- **Stack**: Local variables stored in function execution context. | ||
- [Solidity by Example - Data Locations](https://solidity-by-example.org/data-locations/) | ||
|
||
## Functions | ||
|
||
- **Functions**: Code blocks within contracts that execute specific tasks. | ||
- [Solidity by Example - Functions](https://solidity-by-example.org/function/) | ||
|
||
## Constructor | ||
|
||
- The constructor initializes contract variables when the contract is deployed. | ||
- [Solidity by Example - Constructor](https://solidity-by-example.org/constructor/) | ||
|
||
## Interface | ||
|
||
- **Interface**: Abstract contracts that define functions but don't provide implementations. | ||
- [Solidity by Example - Interface](https://solidity-by-example.org/interface/) | ||
|
||
## Modifiers | ||
- **Modifiers**: Reusable code blocks that can change the behavior of functions. | ||
- [Solidity by Example - Modifiers](https://solidity-by-example.org/function-modifier/) | ||
|
||
## Inheritance | ||
|
||
- **Inheritance**: Contracts can inherit properties and methods from other contracts. | ||
- [Solidity by Example - Inheritance](https://solidity-by-example.org/inheritance/) | ||
|
||
## Error Handling | ||
|
||
- Use `require` and `revert` to handle error cases and provide useful feedback. | ||
- [Solidity by Example - Error Handling](https://solidity-by-example.org/error/) | ||
|
||
## Events | ||
|
||
- **Events**: Enable logging of important contract actions for external consumption. | ||
- [Solidity by Example - Events](https://solidity-by-example.org/events/) | ||
|
||
## DelegateCall | ||
|
||
- **DelegateCall**: Allows a contract to execute code from another contract. | ||
- [Solidity by Example - DelegateCall](https://solidity-by-example.org/delegatecall/) | ||
|
||
## Units | ||
|
||
- **Units**: Ether and Wei are the primary units of Ethereum. 1 ether = 10^18 wei. | ||
- [Solidity by Example - Units](https://solidity-by-example.org/ether-units/) | ||
|
||
## Solidity by Example | ||
|
||
- To find more examples of practical Solidity implementations, explore [Solidity by Example](https://solidity-by-example.org/). | ||
|
||
## Conclusion | ||
|
||
Mastering these Solidity essentials will empower you to create your own proxy with confidence and understanding. Dive into the documentation, experiment with code, and explore real-world examples to solidify your knowledge. | ||
|
||
## Back to the workshop | ||
|
||
[Jump !](./README.md) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
# Help to create the fallback function for the proxy | ||
|
||
The fallback function is a function that is called when a contract is called with a method that is not implemented. This function is called with the method name and the arguments that were passed to the proxy. | ||
|
||
```solidity | ||
fallback() external payable { | ||
(bool success, bytes memory returnData) = implem.delegatecall(msg.data); | ||
if (!success) { | ||
if (returnData.length > 0) { | ||
assembly { | ||
let returndata_size := mload(returnData) | ||
revert(add(32, returnData), returndata_size) | ||
} | ||
} else { | ||
revert("Delegatecall failed without reason"); | ||
} | ||
} | ||
assembly { | ||
return(add(returnData, 32), mload(returnData)) | ||
} | ||
} | ||
``` | ||
|
||
## Let's break down the fallback function line by line: | ||
|
||
1. `(bool success, bytes memory returnData) = implemn.delegatecall(msg.data);` | ||
- This line calls the `delegatecall` function on the implementation contract with the `msg.data` that was sent to the proxy. The `delegatecall` function executes the code of the implementation contract in the context of the proxy contract. The `success` variable will be `true` if the `delegatecall` was successful, and the `returnData` variable will contain the return data from the `delegatecall`. | ||
|
||
2. `if (!success) {` | ||
- This line checks if the `delegatecall` was successful. If it was not successful, the code inside the `if` block will be executed. | ||
|
||
3. `if (returnData.length > 0) {` | ||
- This line checks if the `returnData` has a length greater than 0. If it does, it means that the `delegatecall` failed with a revert reason. | ||
- The `assembly` block extracts the revert reason from the `returnData` and reverts with that reason. | ||
- If the `returnData` is empty, it means that the `delegatecall` failed without a revert reason, and the fallback function reverts with a generic message. | ||
- We use `assembly` to handle the revert reason because the `delegatecall` does not automatically propagate the revert reason. | ||
|
||
4. `assembly { return(add(returnData, 32), mload(returnData)) }` | ||
- This line returns the `returnData` if the `delegatecall` was successful. The `returnData` contains the return value of the function that was called in the `implementation` contract. | ||
|
||
Perfect now you have a fallback function that will call the `implementation` contract with the method name and arguments that were passed to the proxy. If the `delegatecall` is successful, it will return the return value of the function that was called in the `implementation` contract. If the `delegatecall` fails, it will revert with the revert reason. | ||
|
||
> ⚠️ It's the main logic of the proxy so if you have any questions feel free to ask to the PoC Team. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# This is the private key of your account, you can get it in metamask | ||
PRIVATE_KEY= | ||
# This is the address of your account, the one associated with your private key | ||
ADMIN_ADDRESS= | ||
# Alchemy node url for ethereum sepolia network | ||
RPC_URL= | ||
# The address of your deployed ERC-1967 contract | ||
PROXY= | ||
# The address of the counter contract | ||
CONTRACT_V1= | ||
CONTRACT_V2= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.25; | ||
|
||
contract CounterV1 { | ||
uint256 public count; | ||
|
||
constructor() { | ||
count = 0; | ||
} | ||
|
||
function add() public { | ||
count += 1; | ||
} | ||
|
||
function total() public view returns (uint256) { | ||
return count; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.25; | ||
|
||
contract CounterV2 { | ||
uint256 public _count; | ||
|
||
constructor() { | ||
_count = 0; | ||
} | ||
|
||
function add(uint8 nb) public { | ||
_count += nb; | ||
} | ||
|
||
function total() public view returns (uint256) { | ||
return _count; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.25; | ||
|
||
import "forge-std/Test.sol"; | ||
import "../src/proxys/ProxyV1.sol"; | ||
import "../src/implementations/CounterV1.sol"; | ||
|
||
contract ProxyTest is Test { | ||
ProxyV1 public proxy_v1; | ||
CounterV1 public counter; | ||
|
||
function setUp() public { | ||
counter = new CounterV1(); | ||
proxy_v1 = new ProxyV1(address(counter)); | ||
} | ||
|
||
function testCounter() public { | ||
assertEq(counter.total(), 0, "Counter should be 0"); | ||
counter.add(); | ||
assertEq(counter.total(), 1, "Counter should be 1"); | ||
} | ||
|
||
function testProxy_v1() public { | ||
uint256 total = CounterV1(address(proxy_v1)).total(); | ||
assertEq(total, 0, "total should be 0"); | ||
CounterV1(address(proxy_v1)).add(); | ||
total = CounterV1(address(proxy_v1)).total(); | ||
assertEq(total, 1, "total should be 1"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.25; | ||
|
||
import "forge-std/Test.sol"; | ||
import "../src/proxys/ProxyV2.sol"; | ||
import "../src/implementations/CounterV1.sol"; | ||
import "../src/implementations/CounterV2.sol"; | ||
|
||
contract ProxyTest is Test { | ||
ProxyV2 public proxy_v2; | ||
CounterV1 public counter; | ||
CounterV2 public counter_v2; | ||
|
||
function setUp() public { | ||
counter = new CounterV1(); | ||
counter_v2 = new CounterV2(); | ||
proxy_v2 = new ProxyV2(address(counter)); | ||
} | ||
|
||
function testProxy_v2() public { | ||
uint256 total = CounterV1(address(proxy_v2)).total(); | ||
assertEq(total, 0, "total should be 0"); | ||
CounterV1(address(proxy_v2)).add(); | ||
total = CounterV1(address(proxy_v2)).total(); | ||
assertEq(total, 1, "total should be 1"); | ||
// change implementation | ||
proxy_v2.setImplementation(address(counter_v2)); | ||
CounterV2(address(proxy_v2)).add(2); | ||
total = CounterV2(address(proxy_v2)).total(); | ||
assertEq(total, 3, "total should be 3"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.25; | ||
|
||
import "forge-std/Test.sol"; | ||
import "../src/proxys/ProxyV1.sol"; | ||
import "../src/proxys/ProxyV2.sol"; | ||
import "../src/proxys/ProxyOwnableUpgradable.sol"; | ||
import "../src/implementations/CounterV1.sol"; | ||
import "../src/implementations/CounterV2.sol"; | ||
|
||
contract ProxyTest is Test { | ||
CounterV1 public counter; | ||
CounterV2 public counter_v2; | ||
ProxyOwnableUpgradable public proxy_ownable_upgradable; | ||
address public nonOwner; | ||
|
||
function setUp() public { | ||
counter = new CounterV1(); | ||
counter_v2 = new CounterV2(); | ||
proxy_ownable_upgradable = new ProxyOwnableUpgradable(address(counter)); | ||
nonOwner = address(0x1234); | ||
} | ||
|
||
function testProxyUpgrade() public { | ||
CounterV1(address(proxy_ownable_upgradable)).add(); | ||
assertEq(CounterV1(address(proxy_ownable_upgradable)).total(), 1, "should be 1"); | ||
proxy_ownable_upgradable.upgradeTo(address(counter_v2)); | ||
assertEq(proxy_ownable_upgradable.getImplementation(), address(counter_v2), "implementation should be CounterV2"); | ||
assertEq(CounterV2(address(proxy_ownable_upgradable)).total(), 1, "total should be 1"); | ||
CounterV2(address(proxy_ownable_upgradable)).add(3); | ||
assertEq(CounterV2(address(proxy_ownable_upgradable)).total(), 4, "total should be 4"); | ||
} | ||
|
||
function testUpgradeToAsNonOwner() public { | ||
// Attempt to upgrade implementation with a non-owner account | ||
vm.prank(nonOwner); | ||
vm.expectRevert("Caller is not the owner"); | ||
proxy_ownable_upgradable.upgradeTo(address(counter_v2)); | ||
assertEq(proxy_ownable_upgradable.getImplementation(), address(counter), "Implementation should not change for non-owner"); | ||
} | ||
|
||
function testTransferProxyOwnership() public { | ||
// Transfer ownership to nonOwner | ||
proxy_ownable_upgradable.transferProxyOwnership(nonOwner); | ||
assertEq(proxy_ownable_upgradable.getOwner(), nonOwner, "Owner should be nonOwner"); | ||
} | ||
} |