Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/conditions/MinVotingPowerCondition.sol": {
"content": "// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.28;
import {ILockToGovernBase} from "../interfaces/ILockToGovernBase.sol";
import {ILockManager} from "../interfaces/ILockManager.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IPermissionCondition} from "@aragon/osx-commons-contracts/src/permission/condition/IPermissionCondition.sol";
import {PermissionCondition} from "@aragon/osx-commons-contracts/src/permission/condition/PermissionCondition.sol";
/// @title MinVotingPowerCondition
/// @author Aragon X - 2024
/// @notice Checks if an account's voting power or token balance meets the threshold defined on the given plugin.
/// @custom:security-contact sirt@aragon.org
contract MinVotingPowerCondition is PermissionCondition {
/// @notice The address of the `ILockToGovernBase` plugin used to fetch the settings from.
ILockToGovernBase public immutable plugin;
/// @notice The address of the LockManager used by the plugin.
ILockManager public immutable lockManager;
/// @notice The `IERC20` token interface used to check token balance.
IERC20 public immutable token;
/// @notice Initializes the contract with the `ILockToGovernBase` plugin address and caches the associated token.
/// @param _plugin The address of the `ILockToGovernBase` plugin.
constructor(ILockToGovernBase _plugin) {
plugin = _plugin;
token = plugin.token();
lockManager = plugin.lockManager();
}
/// @inheritdoc IPermissionCondition
/// @dev The function checks both the voting power and token balance to ensure `_who` meets the minimum voting
/// threshold defined in the `TokenVoting` plugin. Returns `false` if the minimum requirement is unmet.
function isGranted(address _where, address _who, bytes32 _permissionId, bytes calldata _data)
public
view
override
returns (bool)
{
(_where, _data, _permissionId);
uint256 _currentVotingPower = lockManager.getLockedBalance(_who);
return _currentVotingPower >= getRequiredLockAmount(_who);
}
function getRequiredLockAmount(address _who) public view virtual returns (uint256) {
uint256 _minProposerVotingPower = plugin.minProposerVotingPower();
uint256 _multiplier = lockManager.activeProposalsCreatedBy(_who) + 1;
return _minProposerVotingPower * _multiplier;
}
}
"
},
"src/interfaces/ILockToGovernBase.sol": {
"content": "// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.28;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {ILockManager} from "./ILockManager.sol";
/// @title ILockToGovernBase
/// @author Aragon X 2024-2025
interface ILockToGovernBase {
/// @notice Returns the address of the manager contract, which holds the locked balances and the allocated vote balances.
/// @return The address of the LockManager contract associated with the plugin.
function lockManager() external view returns (ILockManager);
/// @notice Returns the address of the token contract used to determine the voting power.
/// @return The address of the token used for voting.
function token() external view returns (IERC20);
/// @notice Returns whether the account has voted for the proposal.
/// @param proposalId The ID of the proposal.
/// @param voter The account address to be checked.
/// @return The amount of balance that has been allocated to the proposal by the given account.
function usedVotingPower(uint256 proposalId, address voter) external view returns (uint256);
/// @notice Returns the minimum voting power required to create a proposal stored in the voting settings.
/// @return The minimum voting power required to create a proposal.
function minProposerVotingPower() external view returns (uint256);
/// @notice Returns wether a proposal is open for submitting votes or not.
/// @param _proposalId The ID of the proposal.
/// @return True if the proposal is open, false if it hasn't started yet or if it has been already settled.
function isProposalOpen(uint256 _proposalId) external view returns (bool);
/// @notice Returns wether a proposal has ended or not.
/// @param _proposalId The ID of the proposal.
/// @return True if the proposal is executed or past the endDate, false otherwise. False if it doesn't exist.
function isProposalEnded(uint256 _proposalId) external view returns (bool);
}
"
},
"src/interfaces/ILockManager.sol": {
"content": "// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.28;
import {ILockToGovernBase} from "./ILockToGovernBase.sol";
import {IMajorityVoting} from "./IMajorityVoting.sol";
/// @notice Defines wether the accepted plugin types. Currently: voting.
/// Placeholder for future plugin variants
enum PluginMode {
Voting
}
/// @notice The struct containing the LockManager helper settings. They are immutable after deployed.
struct LockManagerSettings {
/// @notice The type of governance plugin expected
PluginMode pluginMode;
}
/// @title ILockManager
/// @author Aragon X 2025
/// @notice Helper contract acting as the vault for locked tokens used to vote on LockToGovern plugins.
interface ILockManager {
/// @notice Returns the current settings of the LockManager.
/// @return pluginMode The plugin mode (currently, voting only)
function settings() external view returns (PluginMode pluginMode);
/// @notice Returns the address of the voting plugin.
/// @return The LockToVote plugin address.
function plugin() external view returns (ILockToGovernBase);
/// @notice Returns the address of the token contract used to determine the voting power.
/// @return The token used for voting.
function token() external view returns (address);
/// @notice Returns the currently locked balance that the given account has on the contract.
/// @param account The address whose locked balance is returned.
function getLockedBalance(address account) external view returns (uint256);
/// @notice Returns how many active proposalID's were created by the given address
/// @param _creator The address to use for filtering
function activeProposalsCreatedBy(address _creator) external view returns (uint256 _result);
/// @notice Locks the balance currently allowed by msg.sender on this contract
/// NOTE: Tokens locked and not allocated into a proposal are treated in the same way as the rest.
/// They can only be unlocked when all active proposals with votes have ended.
function lock() external;
/// @notice Locks the given amount from msg.sender on this contract
/// NOTE: Tokens locked and not allocated into a proposal are treated in the same way as the rest.
/// They can only be unlocked when all active proposals with votes have ended.
/// @param amount How many tokens the contract should lock
function lock(uint256 amount) external;
/// @notice Locks the balance currently allowed by msg.sender on this contract and registers the given vote on the target plugin
/// @param proposalId The ID of the proposal where the vote will be registered
/// @param vote The vote to cast (Yes, No, Abstain)
function lockAndVote(uint256 proposalId, IMajorityVoting.VoteOption vote) external;
/// @notice Locks the given amount from msg.sender on this contract and registers the given vote on the target plugin
/// @param proposalId The ID of the proposal where the vote will be registered
/// @param vote The vote to cast (Yes, No, Abstain)
/// @param amount How many tokens the contract should lock and use for voting
function lockAndVote(uint256 proposalId, IMajorityVoting.VoteOption vote, uint256 amount) external;
/// @notice Uses the locked balance to vote on the given proposal for the registered plugin
/// @param proposalId The ID of the proposal where the vote will be registered
/// @param vote The vote to cast (Yes, No, Abstain)
function vote(uint256 proposalId, IMajorityVoting.VoteOption vote) external;
/// @notice Checks if an account can participate on a proposal. This can be because the proposal
/// - has not started,
/// - has ended,
/// - was executed, or
/// - the voter doesn't have any tokens locked.
/// @param proposalId The proposal Id.
/// @param voter The account address to be checked.
/// @param voteOption The value of the new vote to register.
/// @return Returns true if the account is allowed to vote.
/// @dev The function assumes that the queried proposal exists.
function canVote(uint256 proposalId, address voter, IMajorityVoting.VoteOption voteOption)
external
view
returns (bool);
/// @notice If the governance plugin allows it, releases all active locks placed on active proposals and transfers msg.sender's locked balance back. Depending on the current mode, it withdraws only if no locks are being used in active proposals.
function unlock() external;
/// @notice Called by the lock to vote plugin whenever a proposal is created. It instructs the manager to start tracking the given proposal.
/// @param proposalId The ID of the proposal that msg.sender is reporting as created.
/// @param creator The address creating the proposal.
function proposalCreated(uint256 proposalId, address creator) external;
/// @notice Called by the lock to vote plugin whenever a proposal is executed (or settled).
/// It instructs the manager to remove the proposal from the list of active proposal locks.
/// There's no guarantee that `proposalSettled()` will be reliably called for a proposal ID.
/// Manually checking a proposal's state may be necessary in order to verify that it has ended.
/// @param proposalId The ID of the proposal that msg.sender is reporting as done.
function proposalSettled(uint256 proposalId) external;
/// @notice Triggers a manual cleanup of the known proposal ID's that have already ended.
/// Settled proposals are garbage collected when they are executed or when a user unlocks his tokens.
/// Use this method if a long list of unsettled yet ended proposals ever creates a gas bottleneck that discourages users from unlocking.
/// @param count How many proposals should be cleaned up, at most.
function pruneProposals(uint256 count) external;
/// @notice Defines the given plugin address as the target for voting.
/// @param plugin The address of the contract to use as the plugin.
function setPluginAddress(ILockToGovernBase plugin) external;
}
"
},
"lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 amount) external returns (bool);
}
"
},
"lib/osx-commons/contracts/src/permission/condition/IPermissionCondition.sol": {
"content": "// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.8;
/// @title IPermissionCondition
/// @author Aragon X - 2021-2023
/// @notice An interface to be implemented to support custom permission logic.
/// @dev To attach a condition to a permission, the `grantWithCondition` function must be used and refer to the implementing contract's address with the `condition` argument.
/// @custom:security-contact sirt@aragon.org
interface IPermissionCondition {
/// @notice Checks if a call is permitted.
/// @param _where The address of the target contract.
/// @param _who The address (EOA or contract) for which the permissions are checked.
/// @param _permissionId The permission identifier.
/// @param _data Optional data passed to the `PermissionCondition` implementation.
/// @return isPermitted Returns true if the call is permitted.
function isGranted(
address _where,
address _who,
bytes32 _permissionId,
bytes calldata _data
) external view returns (bool isPermitted);
}
"
},
"lib/osx-commons/contracts/src/permission/condition/PermissionCondition.sol": {
"content": "// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.8;
import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
import {IProtocolVersion} from "../../utils/versioning/IProtocolVersion.sol";
import {ProtocolVersion} from "../../utils/versioning/ProtocolVersion.sol";
import {IPermissionCondition} from "./IPermissionCondition.sol";
/// @title PermissionCondition
/// @author Aragon X - 2023
/// @notice An abstract contract for non-upgradeable contracts instantiated via the `new` keyword to inherit from to support customary permissions depending on arbitrary on-chain state.
/// @custom:security-contact sirt@aragon.org
abstract contract PermissionCondition is ERC165, IPermissionCondition, ProtocolVersion {
/// @notice Checks if an interface is supported by this or its parent contract.
/// @param _interfaceId The ID of the interface.
/// @return Returns `true` if the interface is supported.
function supportsInterface(bytes4 _interfaceId) public view virtual override returns (bool) {
return
_interfaceId == type(IPermissionCondition).interfaceId ||
_interfaceId == type(IProtocolVersion).interfaceId ||
super.supportsInterface(_interfaceId);
}
}
"
},
"src/interfaces/IMajorityVoting.sol": {
"content": "// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.28;
/// @title IMajorityVoting
/// @author Aragon X - 2022-2024
/// @notice The interface of majority voting plugin.
/// @custom:security-contact sirt@aragon.org
interface IMajorityVoting {
/// @notice Vote options that a voter can chose from.
/// @param None The default option state of a voter indicating the absence from the vote.
/// This option neither influences support nor participation.
/// @param Abstain This option does not influence the support but counts towards participation.
/// @param Yes This option increases the support and counts towards participation.
/// @param No This option decreases the support and counts towards participation.
enum VoteOption {
None,
Abstain,
Yes,
No
}
/// @notice Emitted when a vote is cast by a voter.
/// @param proposalId The ID of the proposal.
/// @param voter The voter casting the vote.
/// @param voteOption The casted vote option.
/// @param votingPower The voting power behind this vote.
event VoteCast(uint256 indexed proposalId, address indexed voter, VoteOption voteOption, uint256 votingPower);
/// @notice Holds the state of an account's vote
/// @param voteOption 1 -> abstain, 2 -> yes, 3 -> no
/// @param votingPower How many tokens the account has allocated to `voteOption`
struct VoteEntry {
VoteOption voteOption;
uint256 votingPower;
}
/// @notice Returns the support threshold parameter stored in the voting settings.
/// @return The support threshold parameter.
function supportThresholdRatio() external view returns (uint32);
/// @notice Returns the minimum participation parameter stored in the voting settings.
/// @return The minimum participation parameter.
function minParticipationRatio() external view returns (uint32);
/// @notice Returns the configured minimum approval ratio.
/// @return The minimal approval ratio.
function minApprovalRatio() external view returns (uint256);
/// @notice Checks if the support value defined as:
/// $$\ exttt{support} = \frac{N_\ ext{yes}}{N_\ ext{yes}+N_\ ext{no}}$$
/// for a proposal is greater than the support threshold.
/// @param _proposalId The ID of the proposal.
/// @return Returns `true` if the support is greater than the support threshold and `false` otherwise.
function isSupportThresholdReached(uint256 _proposalId) external view returns (bool);
/// @notice Checks if the participation value defined as:
/// $$\ exttt{participation} = \frac{N_\ ext{yes}+N_\ ext{no}+N_\ ext{abstain}}{N_\ ext{total}}$$
/// for a proposal is greater or equal than the minimum participation value.
/// @param _proposalId The ID of the proposal.
/// @return Returns `true` if the participation is greater or equal than the minimum participation,
/// and `false` otherwise.
function isMinVotingPowerReached(uint256 _proposalId) external view returns (bool);
/// @notice Checks if the min approval value defined as:
/// $$\ exttt{minApprovalRatio} = \frac{N_\ ext{yes}}{N_\ ext{total}}$$
/// for a proposal is greater or equal than the minimum approval value.
/// @param _proposalId The ID of the proposal.
/// @return Returns `true` if the approvals is greater or equal than the minimum approval and `false` otherwise.
function isMinApprovalReached(uint256 _proposalId) external view returns (bool);
/// @notice Checks if a proposal can be executed.
/// @param _proposalId The ID of the proposal to be checked.
/// @return True if the proposal can be executed, false otherwise.
function canExecute(uint256 _proposalId) external view returns (bool);
/// @notice Executes a proposal.
/// @param _proposalId The ID of the proposal to be executed.
function execute(uint256 _proposalId) external;
/// @notice Returns whether the account has voted for the proposal.
/// @dev May return `none` if the `_proposalId` does not exist,
/// or the `_account` does not have voting power.
/// @param _proposalId The ID of the proposal.
/// @param _account The account address to be checked.
/// @return The vote option cast by a voter for a certain proposal.
function getVote(uint256 _proposalId, address _account) external view returns (VoteEntry memory);
}
"
},
"lib/openzeppelin-contracts/contracts/utils/introspection/ERC165.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)
pragma solidity ^0.8.0;
import "./IERC165.sol";
/**
* @dev Implementation of the {IERC165} interface.
*
* Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
* for the additional interface id that will be supported. For example:
*
* ```solidity
* function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
* return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
* }
* ```
*
* Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
*/
abstract contract ERC165 is IERC165 {
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IERC165).interfaceId;
}
}
"
},
"lib/osx-commons/contracts/src/utils/versioning/IProtocolVersion.sol": {
"content": "// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.8;
/// @title IProtocolVersion
/// @author Aragon X - 2022-2023
/// @notice An interface defining the semantic Aragon OSx protocol version number.
/// @custom:security-contact sirt@aragon.org
interface IProtocolVersion {
/// @notice Returns the semantic Aragon OSx protocol version number that the implementing contract is associated with.
/// @return _version Returns the semantic Aragon OSx protocol version number.
/// @dev This version number is not to be confused with the `release` and `build` numbers found in the `Version.Tag` struct inside the `PluginRepo` contract being used to version plugin setup and associated plugin implementation contracts.
function protocolVersion() external view returns (uint8[3] memory _version);
}
"
},
"lib/osx-commons/contracts/src/utils/versioning/ProtocolVersion.sol": {
"content": "// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.8;
import {IProtocolVersion} from "./IProtocolVersion.sol";
/// @title ProtocolVersion
/// @author Aragon X - 2023
/// @notice An abstract, stateless, non-upgradeable contract providing the current Aragon OSx protocol version number.
/// @dev Do not add any new variables to this contract that would shift down storage in the inheritance chain.
/// @custom:security-contact sirt@aragon.org
abstract contract ProtocolVersion is IProtocolVersion {
// IMPORTANT: Do not add any storage variable, see the above notice.
/// @inheritdoc IProtocolVersion
function protocolVersion() public pure returns (uint8[3] memory) {
return [1, 4, 0];
}
}
"
},
"lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
"
}
},
"settings": {
"remappings": [
"@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
"@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/",
"@aragon/osx/=lib/osx/packages/contracts/",
"@aragon/osx-commons-contracts/=lib/osx-commons/contracts/",
"@ensdomains/ens-contracts/=lib/ens-contracts/",
"forge-std/=lib/forge-std/src/",
"ds-test/=lib/openzeppelin-contracts-upgradeable/lib/forge-std/lib/ds-test/src/",
"ens-contracts/=lib/ens-contracts/contracts/",
"erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/",
"openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/",
"openzeppelin-contracts/=lib/openzeppelin-contracts/",
"openzeppelin/=lib/openzeppelin-contracts-upgradeable/contracts/",
"osx-commons/=lib/osx-commons/",
"osx/=lib/osx/"
],
"optimizer": {
"enabled": true,
"runs": 200
},
"metadata": {
"useLiteralContent": false,
"bytecodeHash": "ipfs",
"appendCBOR": true
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"evmVersion": "cancun",
"viaIR": false
}
}}
Submitted on: 2025-10-03 16:02:41
Comments
Log in to comment.
No comments yet.