Skip to content

Commit

Permalink
Using the underlying token to compute approval ratios
Browse files Browse the repository at this point in the history
  • Loading branch information
brickpop committed Dec 18, 2024
1 parent 8907452 commit 0beb7f9
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 10 deletions.
24 changes: 21 additions & 3 deletions src/LockManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import {DaoAuthorizable} from "@aragon/osx-commons-contracts/src/permission/auth
import {ILockToVote} from "./interfaces/ILockToVote.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

/// @title LockManager
/// @author Aragon X 2024
/// @notice Helper contract acting as the vault for locked tokens used to vote on multiple plugins and proposals.
contract LockManager is ILockManager, DaoAuthorizable {
/// @notice The current LockManager settings
LockManagerSettings public settings;
Expand All @@ -17,6 +20,9 @@ contract LockManager is ILockManager, DaoAuthorizable {
/// @notice The address of the token contract
IERC20 public immutable token;

/// @notice The address of the underlying token from which "token" originates, if applicable
IERC20 immutable underlyingTokenAddress;

/// @notice Keeps track of the amount of tokens locked by address
mapping(address => uint256) public lockedBalances;

Expand All @@ -42,16 +48,21 @@ contract LockManager is ILockManager, DaoAuthorizable {
/// @notice Raised when attempting to unlock while active votes are cast in strict mode
error LocksStillActive();

constructor(IDAO _dao, LockManagerSettings memory _settings, ILockToVote _plugin, IERC20 _token)
DaoAuthorizable(_dao)
{
constructor(
IDAO _dao,
LockManagerSettings memory _settings,
ILockToVote _plugin,
IERC20 _token,
IERC20 _underlyingToken
) DaoAuthorizable(_dao) {
if (_settings.unlockMode != UnlockMode.STRICT && _settings.unlockMode != UnlockMode.EARLY) {
revert InvalidUnlockMode();
}

settings.unlockMode = _settings.unlockMode;
plugin = _plugin;
token = _token;
underlyingTokenAddress = _underlyingToken;
}

/// @inheritdoc ILockManager
Expand Down Expand Up @@ -112,6 +123,13 @@ contract LockManager is ILockManager, DaoAuthorizable {
}
}

function underlyingToken() external view returns (IERC20) {
if (address(underlyingTokenAddress) == address(0)) {
return token;
}
return underlyingTokenAddress;
}

// Internal

function _lock() internal {
Expand Down
23 changes: 19 additions & 4 deletions src/LockToVotePlugin.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ contract LockToVotePlugin is

LockToVoteSettings public settings;

/// @inheritdoc ILockToVote
ILockManager public lockManager;

mapping(uint256 => Proposal) proposals;
Expand Down Expand Up @@ -260,7 +261,12 @@ contract LockToVotePlugin is
}

/// @inheritdoc ILockToVote
function votingToken() external view returns (IERC20) {
function underlyingToken() external view returns (IERC20) {
return lockManager.underlyingToken();
}

/// @inheritdoc ILockToVote
function token() external view returns (IERC20) {
return lockManager.token();
}

Expand Down Expand Up @@ -306,10 +312,19 @@ contract LockToVotePlugin is
return true;
}

function _minApprovalTally(Proposal storage proposal_) internal view returns (uint256 _minTally) {
/// @dev Checking against the totalSupply() of the **underlying token**.
/// @dev LP tokens could have important supply variations and this would impact the value of existing votes, after created.
/// @dev However, the total supply of the underlying token (USDC, USDT, DAI, etc) will experiment little to no variations in comparison.

// NOTE: Assuming a 1:1 correlation between token() and underlyingToken()

_minTally =
_applyRatioCeiled(lockManager.underlyingToken().totalSupply(), proposal_.parameters.minApprovalRatio);
}

function _hasSucceeded(Proposal storage proposal_) internal view returns (bool) {
uint256 _minApprovalTally =
_applyRatioCeiled(lockManager.token().totalSupply(), proposal_.parameters.minApprovalRatio);
return proposal_.approvalTally >= _minApprovalTally;
return proposal_.approvalTally >= _minApprovalTally(proposal_);
}

/// @notice Validates and returns the proposal dates.
Expand Down
6 changes: 5 additions & 1 deletion src/interfaces/ILockManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ struct LockManagerSettings {
}

/// @title ILockManager
/// @author Aragon X
/// @author Aragon X 2024
/// @notice Helper contract acting as the vault for locked tokens used to vote on multiple plugins and proposals.
interface ILockManager {
/// @notice Returns the address of the voting plugin.
Expand All @@ -29,6 +29,10 @@ interface ILockManager {
/// @return The token used for voting.
function token() external view returns (IERC20);

/// @notice If applicable, returns the address of the token that can be staked to obtain `token()`. Else, it returns the main token's address.
/// @return The address of the underlying token.
function underlyingToken() external view returns (IERC20);

/// @notice Returns the currently locked balance that the given account has on the contract.
function lockedBalances(address account) external view returns (uint256);

Expand Down
12 changes: 10 additions & 2 deletions src/interfaces/ILockToVote.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {Action} from "@aragon/osx-commons-contracts/src/executors/IExecutor.sol"
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {PluginUUPSUpgradeable} from "@aragon/osx-commons-contracts/src/plugin/PluginUUPSUpgradeable.sol";
import {IPlugin} from "@aragon/osx-commons-contracts/src/plugin/IPlugin.sol";
import {ILockManager} from "./ILockManager.sol";

/// @notice A container for proposal-related information.
/// @param executed Whether the proposal is executed or not.
Expand Down Expand Up @@ -54,9 +55,16 @@ struct LockToVoteSettings {
/// @author Aragon X
/// @notice Governance plugin allowing token holders to use tokens locked without a snapshot requirement and engage in proposals immediately
interface ILockToVote {
/// @notice Returns the address of the manager contract, which holds the locked balances and the allocated vote balances.
function lockManager() external view returns (ILockManager);

/// @notice Returns the address of the token contract used to determine the voting power.
/// @return The token used for voting.
function votingToken() external view returns (IERC20);
/// @return The address of the token used for voting.
function token() external view returns (IERC20);

/// @notice If applicable, returns the address of the token that can be stacked to obtain `token()`. Else, it returns the voting token's address.
/// @return The address of the underlying token.
function underlyingToken() external view returns (IERC20);

/// @notice Internal function to check if a proposal is still open.
/// @param _proposalId The ID of the proposal.
Expand Down

0 comments on commit 0beb7f9

Please sign in to comment.