Skip to content

Commit

Permalink
Merge pull request #3 from morpho-org/feat/merge-files-test
Browse files Browse the repository at this point in the history
refactor: merge files to save lines of code
  • Loading branch information
MerlinEgalite authored Oct 16, 2024
2 parents 1ed972f + bc3be33 commit 49667d8
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 194 deletions.
144 changes: 0 additions & 144 deletions src/DelegatesUpgradeable.sol

This file was deleted.

171 changes: 142 additions & 29 deletions src/ERC20DelegatesUpgradeable.sol
Original file line number Diff line number Diff line change
@@ -1,38 +1,151 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {DelegatesUpgradeable} from "./DelegatesUpgradeable.sol";
import {Initializable} from "lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol";
import {IDelegates} from "./interfaces/IDelegates.sol";

import {ERC20Upgradeable} from "lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/ERC20Upgradeable.sol";
/**
* @dev Extension of ERC20 to support token delegation. |
*
* This extension keeps track of each account's vote power. Vote power can be delegated eithe by calling the
* {delegate} function directly, or by providing a signature to be used with {delegateBySig}. Voting power can be
* queried through the public accessor {getVotes}.
*
* By default, token balance does not account for voting power. This makes transfers cheaper. The downside is that it
* requires users to delegate to themselves in order to activate their voting power.
*/

abstract contract ERC20DelegatesUpgradeable is Initializable, ERC20Upgradeable, DelegatesUpgradeable {
/**
* @dev Move voting power when tokens are transferred.
*
* Emits a {IVotes-DelegateVotesChanged} event.
*/
function _update(address from, address to, uint256 value) internal virtual override {
super._update(from, to, value);
_transferVotingUnits(from, to, value);
import {ECDSA} from
"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol";
import {NoncesUpgradeable} from "lib/openzeppelin-contracts-upgradeable/contracts/utils/NoncesUpgradeable.sol";
import {EIP712Upgradeable} from
"lib/openzeppelin-contracts-upgradeable/contracts/utils/cryptography/EIP712Upgradeable.sol";
import {Initializable} from "lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol";

/// @dev Extension of ERC20 to support token delegation. |
///
/// This extension keeps track of each account's vote power. Vote power can be delegated either by calling the
/// {delegate} function directly, or by providing a signature to be used with {delegateBySig}. Voting power can be
/// queried through the public accessor {getVotes}.
///
/// By default, token balance does not account for voting power. This makes transfers cheaper. The downside is that it
/// requires users to delegate to themselves in order to activate their voting power.
abstract contract ERC20DelegatesUpgradeable is
Initializable,
ERC20Upgradeable,
EIP712Upgradeable,
NoncesUpgradeable,
IDelegates
{
/* CONSTANTS */

bytes32 private constant DELEGATION_TYPEHASH =
keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)");

// keccak256(abi.encode(uint256(keccak256("morpho.storage.ERC20Delegates")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant ERC20DelegatesStorageLocation =
0x1dc92b2c6e971ab6e08dfd7dcec0e9496d223ced663ba2a06543451548549500;

/* STRUCTS */

/// @custom:storage-location erc7201:morpho.storage.ERC20Delegates
struct ERC20DelegatesStorage {
mapping(address account => address) _delegatee;
mapping(address delegatee => uint256) _votingPower;
uint256 _totalVotingPower;
}

/* FUNCTIONS */

function _getERC20DelegatesStorage() private pure returns (ERC20DelegatesStorage storage $) {
assembly {
$.slot := ERC20DelegatesStorageLocation
}
}

/// @dev Returns the current amount of votes that `account` has.
function getVotes(address account) public view virtual returns (uint256) {
ERC20DelegatesStorage storage $ = _getERC20DelegatesStorage();
return $._votingPower[account];
}

/// @dev Returns the current total supply of votes.
function _getTotalSupply() internal view virtual returns (uint256) {
ERC20DelegatesStorage storage $ = _getERC20DelegatesStorage();
return $._totalVotingPower;
}

/**
* @dev Returns the voting units of an `account`.
*
* WARNING: Overriding this function may compromise the internal vote accounting.
* `ERC20Delegates` assumes tokens map to voting units 1:1 and this is not easy to change.
*/
function _getVotingUnits(address account) internal view virtual override returns (uint256) {
/// @dev Returns the delegate that `account` has chosen.
function delegates(address account) public view virtual returns (address) {
ERC20DelegatesStorage storage $ = _getERC20DelegatesStorage();
return $._delegatee[account];
}

/// @dev Delegates votes from the sender to `delegatee`.
function delegate(address delegatee) public virtual {
address account = _msgSender();
_delegate(account, delegatee);
}

/// @dev Delegates votes from signer to `delegatee`.
function delegateBySig(address delegatee, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s)
public
virtual
{
if (block.timestamp > expiry) {
revert DelegatesExpiredSignature(expiry);
}
address signer = ECDSA.recover(
_hashTypedDataV4(keccak256(abi.encode(DELEGATION_TYPEHASH, delegatee, nonce, expiry))), v, r, s
);
_useCheckedNonce(signer, nonce);
_delegate(signer, delegatee);
}

/// @dev Delegate all of `account`'s voting units to `delegatee`.
///
/// Emits events {IDelegates-DelegateChanged} and {IDelegates-DelegateVotesChanged}.
function _delegate(address account, address delegatee) internal virtual {
ERC20DelegatesStorage storage $ = _getERC20DelegatesStorage();
address oldDelegate = delegates(account);
$._delegatee[account] = delegatee;

emit DelegateChanged(account, oldDelegate, delegatee);
_moveDelegateVotes(oldDelegate, delegatee, _getVotingUnits(account));
}

/// @dev Transfers, mints, or burns voting units. To register a mint, `from` should be zero. To register a burn, `to`
/// should be zero. Total supply of voting units will be adjusted with mints and burns.
function _transferVotingUnits(address from, address to, uint256 amount) internal virtual {
ERC20DelegatesStorage storage $ = _getERC20DelegatesStorage();
if (from == address(0)) {
$._totalVotingPower += amount;
}
if (to == address(0)) {
$._totalVotingPower -= amount;
}
_moveDelegateVotes(delegates(from), delegates(to), amount);
}

/// @dev Moves delegated votes from one delegate to another.
function _moveDelegateVotes(address from, address to, uint256 amount) private {
ERC20DelegatesStorage storage $ = _getERC20DelegatesStorage();
if (from != to && amount > 0) {
if (from != address(0)) {
uint256 oldValue = $._votingPower[from];
uint256 newValue = oldValue - amount;
$._votingPower[from] = newValue;
emit DelegateVotesChanged(from, oldValue, newValue);
}
if (to != address(0)) {
uint256 oldValue = $._votingPower[to];
uint256 newValue = oldValue + amount;
$._votingPower[to] = newValue;
emit DelegateVotesChanged(to, oldValue, newValue);
}
}
}

/// @dev Must return the voting units held by an account.
function _getVotingUnits(address account) internal view returns (uint256) {
return balanceOf(account);
}

/// @dev Move voting power when tokens are transferred.
///
/// Emits a {IDelegates-DelegateVotesChanged} event.
function _update(address from, address to, uint256 value) internal virtual override {
super._update(from, to, value);
// No check of supply cap here like in OZ implementation as MORPHO has a 1B total supply cap.
_transferVotingUnits(from, to, value);
}
}
28 changes: 7 additions & 21 deletions src/interfaces/IDelegates.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,24 @@
pragma solidity ^0.8.20;

interface IDelegates {
/**
* @dev The signature used has expired.
*/
// @dev The signature used has expired.
error DelegatesExpiredSignature(uint256 expiry);

/**
* @dev Emitted when an account changes their delegate.
*/
// @dev Emitted when an account changes their delegate.
event DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate);

/**
* @dev Emitted when a token transfer or delegate change results in changes to a delegate's number of voting units.
*/
// @dev Emitted when a token transfer or delegate change results in changes to a delegate's number of voting units.
event DelegateVotesChanged(address indexed delegate, uint256 previousVotes, uint256 newVotes);

/**
* @dev Returns the current amount of votes that `account` has.
*/
// @dev Returns the current amount of votes that `account` has.
function getVotes(address account) external view returns (uint256);

/**
* @dev Returns the delegate that `account` has chosen.
*/
// @dev Returns the delegate that `account` has chosen.
function delegates(address account) external view returns (address);

/**
* @dev Delegates votes from the sender to `delegatee`.
*/
// @dev Delegates votes from the sender to `delegatee`.
function delegate(address delegatee) external;

/**
* @dev Delegates votes from signer to `delegatee`.
*/
// @dev Delegates votes from signer to `delegatee`.
function delegateBySig(address delegatee, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s) external;
}

0 comments on commit 49667d8

Please sign in to comment.