Skip to content

Commit

Permalink
Merge pull request #96 from PoCInnovation/p2p/proxy_workshop
Browse files Browse the repository at this point in the history
feat(p2p/proxy_workshop): add v1 of proxy workshop
  • Loading branch information
Intermarch3 authored Sep 5, 2024
2 parents 5e49817 + 8d89e02 commit cd27f2a
Show file tree
Hide file tree
Showing 10 changed files with 758 additions and 0 deletions.
394 changes: 394 additions & 0 deletions p2p/6.Create_a_proxy/README.md

Large diffs are not rendered by default.

77 changes: 77 additions & 0 deletions p2p/6.Create_a_proxy/SETUP.md
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)
88 changes: 88 additions & 0 deletions p2p/6.Create_a_proxy/Solidity.md
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)
43 changes: 43 additions & 0 deletions p2p/6.Create_a_proxy/help/fallback.md
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.
11 changes: 11 additions & 0 deletions p2p/6.Create_a_proxy/utils/.env.sample
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=
18 changes: 18 additions & 0 deletions p2p/6.Create_a_proxy/utils/CounterV1.sol
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;
}
}
18 changes: 18 additions & 0 deletions p2p/6.Create_a_proxy/utils/CounterV2.sol
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;
}
}
30 changes: 30 additions & 0 deletions p2p/6.Create_a_proxy/utils/ProxyV1.t.sol
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");
}
}
32 changes: 32 additions & 0 deletions p2p/6.Create_a_proxy/utils/ProxyV2.t.sol
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");
}
}
47 changes: 47 additions & 0 deletions p2p/6.Create_a_proxy/utils/ProxyV3.t.sol
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");
}
}

0 comments on commit cd27f2a

Please sign in to comment.