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

refactor: create foundry deployment scripts #89

Closed
wants to merge 32 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
b1b6367
feat: add disputer contract, scripts and tests
nlecoufl Oct 25, 2024
a70fa12
chore: uniformize hardhat config accounts
nlecoufl Oct 28, 2024
0f9b66e
feat: handle infinite approval & format headers
nlecoufl Oct 28, 2024
99f9376
scripts: skip some chains
nlecoufl Oct 30, 2024
b0894b2
fix: dispute test
nlecoufl Nov 4, 2024
bd948c7
feat: add whitelisted addresses
nlecoufl Nov 4, 2024
bf452ae
fix: deployer address
Picodes Nov 4, 2024
cc37f37
chore: deployments
Picodes Nov 4, 2024
1ba3b4b
feat: add set distributor and owner scripts
nlecoufl Nov 4, 2024
6418975
feat: add normal deploy Disputer script
nlecoufl Nov 4, 2024
9eb6140
fix: set correctly owner and deployer of disputer
nlecoufl Nov 4, 2024
289b1e4
refactor: revert owner to deployer
nlecoufl Nov 4, 2024
df5e203
feat: import CoreBorrow contract
nlecoufl Nov 14, 2024
9a9eb0f
feat: add foundry deploy scripts
nlecoufl Nov 14, 2024
cb25940
Merge branch 'feat/disputer' into refactor/deployment
nlecoufl Nov 14, 2024
094165e
wip: prepare sdk bump
nlecoufl Nov 17, 2024
37ad4bd
feat: bump sdk
nlecoufl Nov 18, 2024
ce8b427
feat: add setters to deploy script
nlecoufl Nov 18, 2024
aa2c067
feat: add fund disputer whitelist script
nlecoufl Nov 18, 2024
5b2a55e
feat: add mintAgla script
nlecoufl Nov 19, 2024
2336c92
feat: add deployCreateX script
nlecoufl Nov 20, 2024
5f69c90
feat: update merklDeploy script
nlecoufl Nov 20, 2024
ddf53c2
feat: add some control on deployer address and multisig address
nlecoufl Nov 20, 2024
ac40793
feat: verify createX is deployed
nlecoufl Nov 20, 2024
0c2b2ec
feat: deploy script improvements
nlecoufl Nov 21, 2024
b760b03
refactor: create transferInitialFunds function for better readability
nlecoufl Nov 22, 2024
c0397f7
fix: initialize core args
nlecoufl Nov 22, 2024
ae03b27
fix: add missing merkl deployer in funding
nlecoufl Nov 22, 2024
8557c31
chore: update foundry.toml
nlecoufl Nov 22, 2024
d73c9ed
feat: remove CreateX deployment from main script
nlecoufl Nov 29, 2024
178f501
feat: add Distributor, DistributorCreator and Disputer scripts
nlecoufl Nov 29, 2024
28896dc
refactor: improve scripts
nlecoufl Nov 29, 2024
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
20 changes: 20 additions & 0 deletions .env.foundry.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
## These are used in foundry.toml

# Global Settings
DEPLOYER_ADDRESS=""
DEPLOYER_PRIVATE_KEY="" # 0xA9DdD91249DFdd450E81E1c56Ab60E1A62651701
MERKL_DEPLOYER_PRIVATE_KEY="" # 0x9f76a95AA7535bb0893cf88A146396e00ed21A12
MNEMONIC=""

#HARDHAT_IGNITION_CONFIRM_DEPLOYMENT=false # uncomment to ignore deployments validation
DEPLOY_SALT=""

# Localhost (chainId: X)
LOCALHOST_NODE_URI="http://127.0.0.1:8545"
LOCALHOST_MNEMONIC=""
LOCALHOST_ETHERSCAN_API_KEY=""

# <NETWORK> (chainId: <CHAIN_ID>)
<NETWORK>_NODE_URI=""
<NETWORK>_MNEMONIC=""
<NETWORK>_ETHERSCAN_API_KEY=""
File renamed without changes.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ __pycache__
.deps
.docs
.env
.env.hardhat
.env.foundry
node_modules
venv

Expand Down
34 changes: 32 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ It basically contains two contracts:
- `DistributionCreator`: to which DAOs and individuals can deposit their rewards to incentivize a pool
- `Distributor`: the contract where users can claim their rewards

You can learn more about the Merkl system in the [documentation](https://docs.angle.money/side-products/merkl).
You can learn more about the Merkl system in the [documentation](https://docs.merkl.xyz/).

## Setup

Expand All @@ -31,7 +31,7 @@ In order to interact with non local networks, you must create an `.env` that has
- `ETH_NODE_URI`
- `ETHERSCAN_API_KEY`

You can copy paste the `.env.example` file into `.env` and fill with your keys/RPCs.
You can copy paste the `.env.hardhat.example` or `.env.foundry.example` file into `.env` and fill with your keys/RPCs.

Warning: always keep your confidential information safe.

Expand All @@ -55,10 +55,40 @@ yarn hardhat:coverage

### Deploying

#### Hardhat

```bash
yarn deploy mainnet
```

#### Foundry

Run without broadcasting:

```bash
yarn foundry:script <path_to_script> --rpc-url <network>
```

Run with broadcasting:

```bash
yarn foundry:deploy <path_to_script> --rpc-url <network>
```

### Deploying with Ignition

To deploy with Ignition, you need to set the `DEPLOY_SALT` environment variable.

```bash
yarn hardhat:deploy-ignition ignition/modules/Disputer.ts --network localhost --strategy create2
```

If you want to deploy Disputer on all networks, you can use the following command (if you want to skip validation you can set `HARDHAT_IGNITION_CONFIRM_DEPLOYMENT=true` in .env).

```bash
ts-node scripts/deployDisputerAllNetworks.ts
```

## Foundry Installation

```bash
Expand Down
Binary file added bun.lockb
Binary file not shown.
116 changes: 116 additions & 0 deletions contracts/Disputer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.24;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { Distributor } from "./Distributor.sol";

contract Disputer is Ownable {
Distributor public distributor;
mapping(address => bool) public whitelist;

/*//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
ERRORS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/

error NotWhitelisted();
error DisputeFundsTransferFailed();
error EthNotAccepted();
error UnresolvedDispute();
error WithdrawalFailed();

/*//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
MODIFIERS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/

/// @notice Checks whether the `msg.sender` is a whitelisted address
modifier onlyWhitelisted() {
if (!whitelist[msg.sender]) revert NotWhitelisted();
_;
}

/*//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/

constructor(address _owner, address[] memory _initialWhitelist, Distributor _distributor) {
distributor = _distributor;

// Set infinite approval for the distributor
IERC20(_distributor.disputeToken()).approve(address(_distributor), type(uint256).max);
uint256 length = _initialWhitelist.length;
for (uint256 i; i < length; ) {
whitelist[_initialWhitelist[i]] = true;
unchecked {
++i;
}
}
transferOwnership(_owner);
}

/// @notice Toggles a dispute for the given `reason`
/// @dev Only whitelisted addresses can dispute
/// @param reason Reason for the dispute
function toggleDispute(string memory reason) external onlyWhitelisted {
address disputeToken = address(distributor.disputeToken());
uint256 disputeAmount = distributor.disputeAmount();

uint256 contractBalance = IERC20(disputeToken).balanceOf(address(this));
if (contractBalance < disputeAmount) {
// Transfer funds from msg.sender if needed
if (!IERC20(disputeToken).transferFrom(msg.sender, address(this), disputeAmount - contractBalance)) {
revert DisputeFundsTransferFailed();
}
}

// Attempt to dispute
distributor.disputeTree(reason);
}

/// @notice Withdraws a given amount of a token
/// @dev Only the owner can withdraw the funds
/// @param asset Asset to withdraw
/// @param to Receiver of the funds
/// @param amount Amount to withdraw
function withdrawFunds(address asset, address to, uint256 amount) external onlyOwner {
IERC20(asset).transfer(to, amount);
}

/// @notice Withdraws a given amount of ETH
/// @dev Only the owner can withdraw ETH
/// @param to Receiver of the funds (payable address)
/// @param amount Amount to withdraw
function withdrawFunds(address payable to, uint256 amount) external onlyOwner {
(bool success, ) = to.call{ value: amount }("");
if (!success) revert WithdrawalFailed();
}

/*//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
SETTERS FUNCTIONS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/

/// @notice Adds an address to the whitelist
/// @dev Only the owner can add addresses to the whitelist
/// @param _address Address to add to the whitelist
function addToWhitelist(address _address) external onlyOwner {
whitelist[_address] = true;
}

/// @notice Removes an address from the whitelist
/// @dev Only the owner can remove addresses from the whitelist
/// @param _address Address to remove from the whitelist
function removeFromWhitelist(address _address) external onlyOwner {
whitelist[_address] = false;
}

/// @notice Sets the distributor
/// @dev Only the owner can set the distributor
/// @param _distributor Distributor to set
function setDistributor(Distributor _distributor) external onlyOwner {
// Remove approval from old distributor
IERC20(distributor.disputeToken()).approve(address(distributor), 0);
distributor = _distributor;
// Set infinite approval for new distributor
IERC20(_distributor.disputeToken()).approve(address(_distributor), type(uint256).max);
}
}
188 changes: 188 additions & 0 deletions contracts/core/CoreBorrow.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
// SPDX-License-Identifier: GPL-3.0

/*
* █
***** ▓▓▓
* ▓▓▓▓▓▓▓
* ///. ▓▓▓▓▓▓▓▓▓▓▓▓▓
***** //////// ▓▓▓▓▓▓▓
* ///////////// ▓▓▓
▓▓ ////////////////// █ ▓▓
▓▓ ▓▓ /////////////////////// ▓▓ ▓▓
▓▓ ▓▓ //////////////////////////// ▓▓ ▓▓
▓▓ ▓▓ /////////▓▓▓///////▓▓▓///////// ▓▓ ▓▓
▓▓ ,////////////////////////////////////// ▓▓ ▓▓
▓▓ ////////////////////////////////////////// ▓▓
▓▓ //////////////////////▓▓▓▓/////////////////////
,////////////////////////////////////////////////////
.//////////////////////////////////////////////////////////
.//////////////////////////██.,//////////////////////////█
.//////////////////////████..,./////////////////////██
...////////////////███████.....,.////////////////███
,.,////////////████████ ........,///////////████
.,.,//////█████████ ,.......///////████
,..//████████ ........./████
..,██████ .....,███
.██ ,.,█



▓▓ ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▓▓ ▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓ ▓▓ ▓▓ ▓▓▓▓
▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓ ▓▓ ▓▓▓▓▓
▓▓▓ ▓▓ ▓▓▓ ▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓
*/

pragma solidity ^0.8.17;

import { AccessControlEnumerableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlEnumerableUpgradeable.sol";
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

import { ICore as ICoreBorrow } from "../interfaces/ICore.sol";
import { IFlashAngle } from "../interfaces/IFlashAngle.sol";
import { ITreasury } from "../interfaces/ITreasury.sol";

/// @title CoreBorrow
/// @author Angle Labs, Inc.
/// @notice Core contract of the borrowing module. This contract handles the access control across all contracts
/// (it is read by all treasury contracts), and manages the `flashLoanModule`. It has no minting rights over the
/// stablecoin contracts
contract CoreBorrow is ICoreBorrow, Initializable, AccessControlEnumerableUpgradeable {
/// @notice Role for guardians
bytes32 public constant GUARDIAN_ROLE = keccak256("GUARDIAN_ROLE");
/// @notice Role for governors
bytes32 public constant GOVERNOR_ROLE = keccak256("GOVERNOR_ROLE");
/// @notice Role for treasury contract
bytes32 public constant FLASHLOANER_TREASURY_ROLE = keccak256("FLASHLOANER_TREASURY_ROLE");

// ============================= Reference =====================================

/// @notice Reference to the `flashLoanModule` with minting rights over the different stablecoins of the protocol
address public flashLoanModule;

// =============================== Events ======================================

event FlashLoanModuleUpdated(address indexed _flashloanModule);
event CoreUpdated(address indexed _core);

// =============================== Errors ======================================

error InvalidCore();
error IncompatibleGovernorAndGuardian();
error NotEnoughGovernorsLeft();
error ZeroAddress();

/// @notice Initializes the `CoreBorrow` contract and the access control of the borrowing module
/// @param governor Address of the governor of the Angle Protocol
/// @param guardian Guardian address of the protocol
function initialize(address governor, address guardian) public initializer {
if (governor == address(0) || guardian == address(0)) revert ZeroAddress();
if (governor == guardian) revert IncompatibleGovernorAndGuardian();
_setupRole(GOVERNOR_ROLE, governor);
_setupRole(GUARDIAN_ROLE, guardian);
_setupRole(GUARDIAN_ROLE, governor);
_setRoleAdmin(GUARDIAN_ROLE, GOVERNOR_ROLE);
_setRoleAdmin(GOVERNOR_ROLE, GOVERNOR_ROLE);
_setRoleAdmin(FLASHLOANER_TREASURY_ROLE, GOVERNOR_ROLE);
}

/// @custom:oz-upgrades-unsafe-allow constructor
constructor() initializer {}

// =========================== View Functions ==================================

/// @inheritdoc ICoreBorrow
function isFlashLoanerTreasury(address treasury) external view returns (bool) {
return hasRole(FLASHLOANER_TREASURY_ROLE, treasury);
}

/// @inheritdoc ICoreBorrow
function isGovernor(address admin) external view virtual returns (bool) {
return hasRole(GOVERNOR_ROLE, admin);
}

/// @inheritdoc ICoreBorrow
function isGovernorOrGuardian(address admin) external view returns (bool) {
return hasRole(GUARDIAN_ROLE, admin);
}

// =========================== Governor Functions ==============================

/// @notice Grants the `FLASHLOANER_TREASURY_ROLE` to a `treasury` contract
/// @param treasury Contract to grant the role to
/// @dev This function can be used to allow flash loans on a stablecoin of the protocol
function addFlashLoanerTreasuryRole(address treasury) external {
grantRole(FLASHLOANER_TREASURY_ROLE, treasury);
address _flashLoanModule = flashLoanModule;
if (_flashLoanModule != address(0)) {
// This call will revert if `treasury` is the zero address or if it is not linked
// to this `CoreBorrow` contract
ITreasury(treasury).setFlashLoanModule(_flashLoanModule);
IFlashAngle(_flashLoanModule).addStablecoinSupport(treasury);
}
}

/// @notice Adds a governor in the protocol
/// @param governor Address to grant the role to
/// @dev It is necessary to call this function to grant a governor role to make sure
/// all governors also have the guardian role
function addGovernor(address governor) external {
grantRole(GOVERNOR_ROLE, governor);
grantRole(GUARDIAN_ROLE, governor);
}

/// @notice Revokes the flash loan ability for a stablecoin
/// @param treasury Treasury address associated with the stablecoin for which flash loans
/// should no longer be available
function removeFlashLoanerTreasuryRole(address treasury) external {
revokeRole(FLASHLOANER_TREASURY_ROLE, treasury);
ITreasury(treasury).setFlashLoanModule(address(0));
address _flashLoanModule = flashLoanModule;
if (_flashLoanModule != address(0)) {
IFlashAngle(flashLoanModule).removeStablecoinSupport(treasury);
}
}

/// @notice Revokes a governor from the protocol
/// @param governor Address to remove the role to
/// @dev It is necessary to call this function to remove a governor role to make sure
/// the address also loses its guardian role
function removeGovernor(address governor) external {
if (getRoleMemberCount(GOVERNOR_ROLE) <= 1) revert NotEnoughGovernorsLeft();
revokeRole(GUARDIAN_ROLE, governor);
revokeRole(GOVERNOR_ROLE, governor);
}

/// @notice Changes the `flashLoanModule` of the protocol
/// @param _flashLoanModule Address of the new flash loan module
function setFlashLoanModule(address _flashLoanModule) external onlyRole(GOVERNOR_ROLE) {
if (_flashLoanModule != address(0)) {
if (address(IFlashAngle(_flashLoanModule).core()) != address(this)) revert InvalidCore();
}
uint256 count = getRoleMemberCount(FLASHLOANER_TREASURY_ROLE);
for (uint256 i; i < count; ++i) {
ITreasury(getRoleMember(FLASHLOANER_TREASURY_ROLE, i)).setFlashLoanModule(_flashLoanModule);
}
flashLoanModule = _flashLoanModule;
emit FlashLoanModuleUpdated(_flashLoanModule);
}

/// @notice Changes the core contract of the protocol
/// @param _core New core contract
/// @dev This function verifies that all governors of the current core contract are also governors
/// of the new core contract. It also notifies the `flashLoanModule` of the change.
/// @dev Governance wishing to change the core contract should also make sure to call `setCore`
/// in the different treasury contracts
function setCore(ICoreBorrow _core) external onlyRole(GOVERNOR_ROLE) {
uint256 count = getRoleMemberCount(GOVERNOR_ROLE);
bool success;
for (uint256 i; i < count; ++i) {
success = _core.isGovernor(getRoleMember(GOVERNOR_ROLE, i));
if (!success) break;
}
if (!success) revert InvalidCore();
address _flashLoanModule = flashLoanModule;
if (_flashLoanModule != address(0)) IFlashAngle(_flashLoanModule).setCore(address(_core));
emit CoreUpdated(address(_core));
}
}
Loading
Loading