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": {
"@openzeppelin/contracts/token/ERC20/IERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @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 `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address recipient, 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 `sender` to `recipient` 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 sender,
address recipient,
uint256 amount
) external returns (bool);
/**
* @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);
}
"
},
"contracts/governance/Governable.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
/**
* @title Base for contracts that are managed by the Origin Protocol's Governor.
* @dev Copy of the openzeppelin Ownable.sol contract with nomenclature change
* from owner to governor and renounce methods removed. Does not use
* Context.sol like Ownable.sol does for simplification.
* @author Origin Protocol Inc
*/
abstract contract Governable {
// Storage position of the owner and pendingOwner of the contract
// keccak256("OUSD.governor");
bytes32 private constant governorPosition =
0x7bea13895fa79d2831e0a9e28edede30099005a50d652d8957cf8a607ee6ca4a;
// keccak256("OUSD.pending.governor");
bytes32 private constant pendingGovernorPosition =
0x44c4d30b2eaad5130ad70c3ba6972730566f3e6359ab83e800d905c61b1c51db;
// keccak256("OUSD.reentry.status");
bytes32 private constant reentryStatusPosition =
0x53bf423e48ed90e97d02ab0ebab13b2a235a6bfbe9c321847d5c175333ac4535;
// See OpenZeppelin ReentrancyGuard implementation
uint256 constant _NOT_ENTERED = 1;
uint256 constant _ENTERED = 2;
event PendingGovernorshipTransfer(
address indexed previousGovernor,
address indexed newGovernor
);
event GovernorshipTransferred(
address indexed previousGovernor,
address indexed newGovernor
);
/**
* @notice Returns the address of the current Governor.
*/
function governor() public view returns (address) {
return _governor();
}
/**
* @dev Returns the address of the current Governor.
*/
function _governor() internal view returns (address governorOut) {
bytes32 position = governorPosition;
// solhint-disable-next-line no-inline-assembly
assembly {
governorOut := sload(position)
}
}
/**
* @dev Returns the address of the pending Governor.
*/
function _pendingGovernor()
internal
view
returns (address pendingGovernor)
{
bytes32 position = pendingGovernorPosition;
// solhint-disable-next-line no-inline-assembly
assembly {
pendingGovernor := sload(position)
}
}
/**
* @dev Throws if called by any account other than the Governor.
*/
modifier onlyGovernor() {
require(isGovernor(), "Caller is not the Governor");
_;
}
/**
* @notice Returns true if the caller is the current Governor.
*/
function isGovernor() public view returns (bool) {
return msg.sender == _governor();
}
function _setGovernor(address newGovernor) internal {
emit GovernorshipTransferred(_governor(), newGovernor);
bytes32 position = governorPosition;
// solhint-disable-next-line no-inline-assembly
assembly {
sstore(position, newGovernor)
}
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and make it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
bytes32 position = reentryStatusPosition;
uint256 _reentry_status;
// solhint-disable-next-line no-inline-assembly
assembly {
_reentry_status := sload(position)
}
// On the first call to nonReentrant, _notEntered will be true
require(_reentry_status != _ENTERED, "Reentrant call");
// Any calls to nonReentrant after this point will fail
// solhint-disable-next-line no-inline-assembly
assembly {
sstore(position, _ENTERED)
}
_;
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
// solhint-disable-next-line no-inline-assembly
assembly {
sstore(position, _NOT_ENTERED)
}
}
function _setPendingGovernor(address newGovernor) internal {
bytes32 position = pendingGovernorPosition;
// solhint-disable-next-line no-inline-assembly
assembly {
sstore(position, newGovernor)
}
}
/**
* @notice Transfers Governance of the contract to a new account (`newGovernor`).
* Can only be called by the current Governor. Must be claimed for this to complete
* @param _newGovernor Address of the new Governor
*/
function transferGovernance(address _newGovernor) external onlyGovernor {
_setPendingGovernor(_newGovernor);
emit PendingGovernorshipTransfer(_governor(), _newGovernor);
}
/**
* @notice Claim Governance of the contract to a new account (`newGovernor`).
* Can only be called by the new Governor.
*/
function claimGovernance() external {
require(
msg.sender == _pendingGovernor(),
"Only the pending Governor can complete the claim"
);
_changeGovernor(msg.sender);
}
/**
* @dev Change Governance of the contract to a new account (`newGovernor`).
* @param _newGovernor Address of the new Governor
*/
function _changeGovernor(address _newGovernor) internal {
require(_newGovernor != address(0), "New Governor is address(0)");
_setGovernor(_newGovernor);
}
}
"
},
"contracts/interfaces/poolBooster/IMerklDistributor.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IMerklDistributor {
struct CampaignParameters {
// POPULATED ONCE CREATED
// ID of the campaign. This can be left as a null bytes32 when creating campaigns
// on Merkl.
bytes32 campaignId;
// CHOSEN BY CAMPAIGN CREATOR
// Address of the campaign creator, if marked as address(0), it will be overriden with the
// address of the `msg.sender` creating the campaign
address creator;
// Address of the token used as a reward
address rewardToken;
// Amount of `rewardToken` to distribute across all the epochs
// Amount distributed per epoch is `amount/numEpoch`
uint256 amount;
// Type of campaign
uint32 campaignType;
// Timestamp at which the campaign should start
uint32 startTimestamp;
// Duration of the campaign in seconds. Has to be a multiple of EPOCH = 3600
uint32 duration;
// Extra data to pass to specify the campaign
bytes campaignData;
}
function createCampaign(CampaignParameters memory newCampaign)
external
returns (bytes32);
function signAndCreateCampaign(
CampaignParameters memory newCampaign,
bytes memory _signature
) external returns (bytes32);
function sign(bytes memory _signature) external;
function rewardTokenMinAmounts(address _rewardToken)
external
view
returns (uint256);
}
"
},
"contracts/interfaces/poolBooster/IPoolBoostCentralRegistry.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
interface IPoolBoostCentralRegistry {
/**
* @dev all the supported pool booster types are listed here. It is possible
* to have multiple versions of the factory that supports the same type of
* pool booster. Factories are immutable and this can happen when a factory
* or related pool booster required code update.
* e.g. "PoolBoosterSwapxDouble" & "PoolBoosterSwapxDouble_v2"
*/
enum PoolBoosterType {
// Supports bribing 2 contracts per pool. Appropriate for Ichi vault concentrated
// liquidity pools where (which is expected in most/all cases) both pool gauges
// require bribing.
SwapXDoubleBooster,
// Supports bribing a single contract per pool. Appropriate for Classic Stable &
// Classic Volatile pools and Ichi vaults where only 1 side (1 of the 2 gauges)
// needs bribing
SwapXSingleBooster,
// Supports bribing a single contract per pool. Appropriate for Metropolis pools
MetropolisBooster,
// Supports creating a Merkl campaign.
MerklBooster
}
struct PoolBoosterEntry {
address boosterAddress;
address ammPoolAddress;
PoolBoosterType boosterType;
}
event PoolBoosterCreated(
address poolBoosterAddress,
address ammPoolAddress,
PoolBoosterType poolBoosterType,
address factoryAddress
);
event PoolBoosterRemoved(address poolBoosterAddress);
function emitPoolBoosterCreated(
address _poolBoosterAddress,
address _ammPoolAddress,
PoolBoosterType _boosterType
) external;
function emitPoolBoosterRemoved(address _poolBoosterAddress) external;
}
"
},
"contracts/interfaces/poolBooster/IPoolBooster.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
interface IPoolBooster {
event BribeExecuted(uint256 amount);
/// @notice Execute the bribe action
function bribe() external;
}
"
},
"contracts/poolBooster/AbstractPoolBoosterFactory.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import { Governable } from "../governance/Governable.sol";
import { IPoolBooster } from "../interfaces/poolBooster/IPoolBooster.sol";
import { IPoolBoostCentralRegistry } from "../interfaces/poolBooster/IPoolBoostCentralRegistry.sol";
/**
* @title Abstract Pool booster factory
* @author Origin Protocol Inc
*/
contract AbstractPoolBoosterFactory is Governable {
struct PoolBoosterEntry {
address boosterAddress;
address ammPoolAddress;
IPoolBoostCentralRegistry.PoolBoosterType boosterType;
}
// @notice address of Origin Token
address public immutable oToken;
// @notice Central registry contract
IPoolBoostCentralRegistry public immutable centralRegistry;
// @notice list of all the pool boosters created by this factory
PoolBoosterEntry[] public poolBoosters;
// @notice mapping of AMM pool to pool booster
mapping(address => PoolBoosterEntry) public poolBoosterFromPool;
// @param address _oToken address of the OToken token
// @param address _governor address governor
// @param address _centralRegistry address of the central registry
constructor(
address _oToken,
address _governor,
address _centralRegistry
) {
require(_oToken != address(0), "Invalid oToken address");
require(_governor != address(0), "Invalid governor address");
require(
_centralRegistry != address(0),
"Invalid central registry address"
);
oToken = _oToken;
centralRegistry = IPoolBoostCentralRegistry(_centralRegistry);
_setGovernor(_governor);
}
/**
* @notice Goes over all the pool boosters created by this factory and
* calls bribe() on them.
* @param _exclusionList A list of pool booster addresses to skip when
* calling this function.
*/
function bribeAll(address[] memory _exclusionList) external {
uint256 lengthI = poolBoosters.length;
for (uint256 i = 0; i < lengthI; i++) {
address poolBoosterAddress = poolBoosters[i].boosterAddress;
bool skipBribeCall = false;
uint256 lengthJ = _exclusionList.length;
for (uint256 j = 0; j < lengthJ; j++) {
// pool booster in exclusion list
if (_exclusionList[j] == poolBoosterAddress) {
skipBribeCall = true;
break;
}
}
if (!skipBribeCall) {
IPoolBooster(poolBoosterAddress).bribe();
}
}
}
/**
* @notice Removes the pool booster from the internal list of pool boosters.
* @dev This action does not destroy the pool booster contract nor does it
* stop the yield delegation to it.
* @param _poolBoosterAddress address of the pool booster
*/
function removePoolBooster(address _poolBoosterAddress)
external
onlyGovernor
{
uint256 boostersLen = poolBoosters.length;
for (uint256 i = 0; i < boostersLen; ++i) {
if (poolBoosters[i].boosterAddress == _poolBoosterAddress) {
// erase mapping
delete poolBoosterFromPool[poolBoosters[i].ammPoolAddress];
// overwrite current pool booster with the last entry in the list
poolBoosters[i] = poolBoosters[boostersLen - 1];
// drop the last entry
poolBoosters.pop();
centralRegistry.emitPoolBoosterRemoved(_poolBoosterAddress);
break;
}
}
}
function _storePoolBoosterEntry(
address _poolBoosterAddress,
address _ammPoolAddress,
IPoolBoostCentralRegistry.PoolBoosterType _boosterType
) internal {
PoolBoosterEntry memory entry = PoolBoosterEntry(
_poolBoosterAddress,
_ammPoolAddress,
_boosterType
);
poolBoosters.push(entry);
poolBoosterFromPool[_ammPoolAddress] = entry;
// emit the events of the pool booster created
centralRegistry.emitPoolBoosterCreated(
_poolBoosterAddress,
_ammPoolAddress,
_boosterType
);
}
function _deployContract(bytes memory _bytecode, uint256 _salt)
internal
returns (address _address)
{
// solhint-disable-next-line no-inline-assembly
assembly {
_address := create2(
0,
add(_bytecode, 0x20),
mload(_bytecode),
_salt
)
}
require(
_address.code.length > 0 && _address != address(0),
"Failed creating a pool booster"
);
}
// pre-compute the address of the deployed contract that will be
// created when create2 is called
function _computeAddress(bytes memory _bytecode, uint256 _salt)
internal
view
returns (address)
{
bytes32 hash = keccak256(
abi.encodePacked(
bytes1(0xff),
address(this),
_salt,
keccak256(_bytecode)
)
);
// cast last 20 bytes of hash to address
return address(uint160(uint256(hash)));
}
function poolBoosterLength() external view returns (uint256) {
return poolBoosters.length;
}
}
"
},
"contracts/poolBooster/PoolBoosterFactoryMerkl.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import { PoolBoosterMerkl } from "./PoolBoosterMerkl.sol";
import { AbstractPoolBoosterFactory, IPoolBoostCentralRegistry } from "./AbstractPoolBoosterFactory.sol";
/**
* @title Pool booster factory for creating Merkl pool boosters.
* @author Origin Protocol Inc
*/
contract PoolBoosterFactoryMerkl is AbstractPoolBoosterFactory {
uint256 public constant version = 1;
/// @notice address of the Merkl distributor
address public merklDistributor;
/// @notice event emitted when the Merkl distributor is updated
event MerklDistributorUpdated(address newDistributor);
/**
* @param _oToken address of the OToken token
* @param _governor address governor
* @param _centralRegistry address of the central registry
* @param _merklDistributor address of the Merkl distributor
*/
constructor(
address _oToken,
address _governor,
address _centralRegistry,
address _merklDistributor
) AbstractPoolBoosterFactory(_oToken, _governor, _centralRegistry) {
_setMerklDistributor(_merklDistributor);
}
/**
* @dev Create a Pool Booster for Merkl.
* @param _campaignType The type of campaign to create. This is used to determine the type of
* bribe contract to create. The type is defined in the MerklDistributor contract.
* @param _ammPoolAddress address of the AMM pool where the yield originates from
* @param _campaignDuration The duration of the campaign in seconds
* @param campaignData The data to be used for the campaign. This is used to determine the type of
* bribe contract to create. The type is defined in the MerklDistributor contract.
* This should be fetched from the Merkl UI.
* @param _salt A unique number that affects the address of the pool booster created. Note: this number
* should match the one from `computePoolBoosterAddress` in order for the final deployed address
* and pre-computed address to match
*/
function createPoolBoosterMerkl(
uint32 _campaignType,
address _ammPoolAddress,
uint32 _campaignDuration,
bytes calldata campaignData,
uint256 _salt
) external onlyGovernor {
require(
_ammPoolAddress != address(0),
"Invalid ammPoolAddress address"
);
require(_salt > 0, "Invalid salt");
require(_campaignDuration > 1 hours, "Invalid campaign duration");
require(campaignData.length > 0, "Invalid campaign data");
address poolBoosterAddress = _deployContract(
abi.encodePacked(
type(PoolBoosterMerkl).creationCode,
abi.encode(
oToken,
merklDistributor,
_campaignDuration,
_campaignType,
governor(),
campaignData
)
),
_salt
);
_storePoolBoosterEntry(
poolBoosterAddress,
_ammPoolAddress,
IPoolBoostCentralRegistry.PoolBoosterType.MerklBooster
);
}
/**
* @dev Create a Pool Booster for Merkl.
* @param _campaignType The type of campaign to create. This is used to determine the type of
* bribe contract to create. The type is defined in the MerklDistributor contract.
* @param _ammPoolAddress address of the AMM pool where the yield originates from
* @param _salt A unique number that affects the address of the pool booster created. Note: this number
* should match the one from `createPoolBoosterMerkl` in order for the final deployed address
* and pre-computed address to match
*/
function computePoolBoosterAddress(
uint32 _campaignType,
address _ammPoolAddress,
uint32 _campaignDuration,
bytes calldata campaignData,
uint256 _salt
) external view returns (address) {
require(
_ammPoolAddress != address(0),
"Invalid ammPoolAddress address"
);
require(_salt > 0, "Invalid salt");
require(_campaignDuration > 1 hours, "Invalid campaign duration");
require(campaignData.length > 0, "Invalid campaign data");
return
_computeAddress(
abi.encodePacked(
type(PoolBoosterMerkl).creationCode,
abi.encode(
oToken,
merklDistributor,
_campaignDuration,
_campaignType,
governor(),
campaignData
)
),
_salt
);
}
/**
* @dev Set the address of the Merkl distributor
* @param _merklDistributor The address of the Merkl distributor
*/
function setMerklDistributor(address _merklDistributor)
external
onlyGovernor
{
_setMerklDistributor(_merklDistributor);
}
function _setMerklDistributor(address _merklDistributor) internal {
require(
_merklDistributor != address(0),
"Invalid merklDistributor address"
);
merklDistributor = _merklDistributor;
emit MerklDistributorUpdated(_merklDistributor);
}
}
"
},
"contracts/poolBooster/PoolBoosterMerkl.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IPoolBooster } from "../interfaces/poolBooster/IPoolBooster.sol";
import { IMerklDistributor } from "../interfaces/poolBooster/IMerklDistributor.sol";
interface IERC1271 {
/**
* @dev Should return whether the signature provided is valid for the provided data
* @param hash Hash of the data to be signed
* @param signature Signature byte array associated with _data
*/
function isValidSignature(bytes32 hash, bytes memory signature)
external
view
returns (bytes4 magicValue);
}
/**
* @title Pool booster for Merkl distributor
* @author Origin Protocol Inc
*/
contract PoolBoosterMerkl is IPoolBooster, IERC1271 {
/// @notice address of merkl distributor
IMerklDistributor public immutable merklDistributor;
/// @notice address of the OS token
IERC20 public immutable rewardToken;
/// @notice if balance under this amount the bribe action is skipped
uint256 public constant MIN_BRIBE_AMOUNT = 1e10;
/// @notice Campaign duration in seconds
uint32 public immutable duration; // -> should be immutable
/// @notice Campaign type
uint32 public immutable campaignType;
/// @notice Owner of the campaign
address public immutable creator;
/// @notice Campaign data
bytes public campaignData;
constructor(
address _rewardToken,
address _merklDistributor,
uint32 _duration,
uint32 _campaignType,
address _creator,
bytes memory _campaignData
) {
require(_rewardToken != address(0), "Invalid rewardToken address");
require(
_merklDistributor != address(0),
"Invalid merklDistributor address"
);
require(_campaignData.length > 0, "Invalid campaignData");
require(_duration > 1 hours, "Invalid duration");
campaignType = _campaignType;
duration = _duration;
creator = _creator;
merklDistributor = IMerklDistributor(_merklDistributor);
rewardToken = IERC20(_rewardToken);
campaignData = _campaignData;
}
/// @notice Create a campaign on the Merkl distributor
function bribe() external override {
// Ensure token is approved for the Merkl distributor
uint256 minAmount = merklDistributor.rewardTokenMinAmounts(
address(rewardToken)
);
require(minAmount > 0, "Min reward amount must be > 0");
// if balance too small or below threshold, do no bribes
uint256 balance = rewardToken.balanceOf(address(this));
if (
balance < MIN_BRIBE_AMOUNT ||
(balance * 1 hours < minAmount * duration)
) {
return;
}
// Approve the bribe contract to spend the reward token
rewardToken.approve(address(merklDistributor), balance);
// Notify the bribe contract of the reward amount
merklDistributor.signAndCreateCampaign(
IMerklDistributor.CampaignParameters({
campaignId: bytes32(0),
creator: creator,
rewardToken: address(rewardToken),
amount: balance,
campaignType: campaignType,
startTimestamp: getNextPeriodStartTime(),
duration: duration,
campaignData: campaignData
}),
bytes("")
);
emit BribeExecuted(balance);
}
/// @notice Used to sign a campaign on the Merkl distributor
function isValidSignature(bytes32, bytes memory)
external
view
override
returns (bytes4 magicValue)
{
require(msg.sender == address(merklDistributor), "Invalid sender");
// bytes4(keccak256("isValidSignature(bytes32,bytes)")) == 0x1626ba7e
return bytes4(0x1626ba7e);
}
/// @notice Returns the timestamp for the start of the next period based on the configured duration
function getNextPeriodStartTime() public view returns (uint32) {
// Calculate the timestamp for the next period boundary
return uint32((block.timestamp / duration + 1) * duration);
}
}
"
}
},
"settings": {
"optimizer": {
"enabled": true,
"runs": 200
},
"evmVersion": "paris",
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"metadata": {
"useLiteralContent": true
}
}
}}
Submitted on: 2025-10-03 09:19:43
Comments
Log in to comment.
No comments yet.