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 (last updated v4.6.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);
}
"
},
"contracts/AssetList.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.15;
import "./IPriceFeed.sol";
import "./IERC20NonStandard.sol";
import "./CometMainInterface.sol";
import "./CometCore.sol";
/**
* @title Compound's Asset List
* @author Compound
*/
contract AssetList {
/// @dev The decimals required for a price feed
uint8 internal constant PRICE_FEED_DECIMALS = 8;
/// @dev The scale for factors
uint64 internal constant FACTOR_SCALE = 1e18;
/// @dev The max value for a collateral factor (1)
uint64 internal constant MAX_COLLATERAL_FACTOR = FACTOR_SCALE;
uint256 internal immutable asset00_a;
uint256 internal immutable asset00_b;
uint256 internal immutable asset01_a;
uint256 internal immutable asset01_b;
uint256 internal immutable asset02_a;
uint256 internal immutable asset02_b;
uint256 internal immutable asset03_a;
uint256 internal immutable asset03_b;
uint256 internal immutable asset04_a;
uint256 internal immutable asset04_b;
uint256 internal immutable asset05_a;
uint256 internal immutable asset05_b;
uint256 internal immutable asset06_a;
uint256 internal immutable asset06_b;
uint256 internal immutable asset07_a;
uint256 internal immutable asset07_b;
uint256 internal immutable asset08_a;
uint256 internal immutable asset08_b;
uint256 internal immutable asset09_a;
uint256 internal immutable asset09_b;
uint256 internal immutable asset10_a;
uint256 internal immutable asset10_b;
uint256 internal immutable asset11_a;
uint256 internal immutable asset11_b;
uint256 internal immutable asset12_a;
uint256 internal immutable asset12_b;
uint256 internal immutable asset13_a;
uint256 internal immutable asset13_b;
uint256 internal immutable asset14_a;
uint256 internal immutable asset14_b;
uint256 internal immutable asset15_a;
uint256 internal immutable asset15_b;
uint256 internal immutable asset16_a;
uint256 internal immutable asset16_b;
uint256 internal immutable asset17_a;
uint256 internal immutable asset17_b;
uint256 internal immutable asset18_a;
uint256 internal immutable asset18_b;
uint256 internal immutable asset19_a;
uint256 internal immutable asset19_b;
uint256 internal immutable asset20_a;
uint256 internal immutable asset20_b;
uint256 internal immutable asset21_a;
uint256 internal immutable asset21_b;
uint256 internal immutable asset22_a;
uint256 internal immutable asset22_b;
uint256 internal immutable asset23_a;
uint256 internal immutable asset23_b;
/// @notice The number of assets this contract actually supports
uint8 public immutable numAssets;
constructor(CometConfiguration.AssetConfig[] memory assetConfigs) {
uint8 _numAssets = uint8(assetConfigs.length);
numAssets = _numAssets;
(asset00_a, asset00_b) = getPackedAssetInternal(assetConfigs, 0);
(asset01_a, asset01_b) = getPackedAssetInternal(assetConfigs, 1);
(asset02_a, asset02_b) = getPackedAssetInternal(assetConfigs, 2);
(asset03_a, asset03_b) = getPackedAssetInternal(assetConfigs, 3);
(asset04_a, asset04_b) = getPackedAssetInternal(assetConfigs, 4);
(asset05_a, asset05_b) = getPackedAssetInternal(assetConfigs, 5);
(asset06_a, asset06_b) = getPackedAssetInternal(assetConfigs, 6);
(asset07_a, asset07_b) = getPackedAssetInternal(assetConfigs, 7);
(asset08_a, asset08_b) = getPackedAssetInternal(assetConfigs, 8);
(asset09_a, asset09_b) = getPackedAssetInternal(assetConfigs, 9);
(asset10_a, asset10_b) = getPackedAssetInternal(assetConfigs, 10);
(asset11_a, asset11_b) = getPackedAssetInternal(assetConfigs, 11);
(asset12_a, asset12_b) = getPackedAssetInternal(assetConfigs, 12);
(asset13_a, asset13_b) = getPackedAssetInternal(assetConfigs, 13);
(asset14_a, asset14_b) = getPackedAssetInternal(assetConfigs, 14);
(asset15_a, asset15_b) = getPackedAssetInternal(assetConfigs, 15);
(asset16_a, asset16_b) = getPackedAssetInternal(assetConfigs, 16);
(asset17_a, asset17_b) = getPackedAssetInternal(assetConfigs, 17);
(asset18_a, asset18_b) = getPackedAssetInternal(assetConfigs, 18);
(asset19_a, asset19_b) = getPackedAssetInternal(assetConfigs, 19);
(asset20_a, asset20_b) = getPackedAssetInternal(assetConfigs, 20);
(asset21_a, asset21_b) = getPackedAssetInternal(assetConfigs, 21);
(asset22_a, asset22_b) = getPackedAssetInternal(assetConfigs, 22);
(asset23_a, asset23_b) = getPackedAssetInternal(assetConfigs, 23);
}
/**
* @dev Checks and gets the packed asset info for storage in 2 variables
* - in first variable, the asset address is stored in the lower 160 bits (address can be interpreted as uint160),
* the borrow collateral factor in the next 16 bits,
* the liquidate collateral factor in the next 16 bits,
* and the liquidation factor in the next 16 bits
* - in the second variable, the price feed address is stored in the lower 160 bits,
* the asset decimals in the next 8 bits,
* and the supply cap in the next 64 bits
* @param assetConfigs The asset configurations
* @param i The index of the asset info to get
* @return The packed asset info
*/
function getPackedAssetInternal(CometConfiguration.AssetConfig[] memory assetConfigs, uint i) internal view returns (uint256, uint256) {
CometConfiguration.AssetConfig memory assetConfig;
if (i < assetConfigs.length) {
assembly {
assetConfig := mload(add(add(assetConfigs, 0x20), mul(i, 0x20)))
}
} else {
return (0, 0);
}
address asset = assetConfig.asset;
address priceFeed = assetConfig.priceFeed;
uint8 decimals_ = assetConfig.decimals;
// Short-circuit if asset is nil
if (asset == address(0)) {
return (0, 0);
}
// Sanity check price feed and asset decimals
if (IPriceFeed(priceFeed).decimals() != PRICE_FEED_DECIMALS) revert CometMainInterface.BadDecimals();
if (IERC20NonStandard(asset).decimals() != decimals_) revert CometMainInterface.BadDecimals();
// Ensure collateral factors are within range
if (assetConfig.borrowCollateralFactor >= assetConfig.liquidateCollateralFactor) revert CometMainInterface.BorrowCFTooLarge();
if (assetConfig.liquidateCollateralFactor > MAX_COLLATERAL_FACTOR) revert CometMainInterface.LiquidateCFTooLarge();
unchecked {
// Keep 4 decimals for each factor
uint64 descale = FACTOR_SCALE / 1e4;
uint16 borrowCollateralFactor = uint16(assetConfig.borrowCollateralFactor / descale);
uint16 liquidateCollateralFactor = uint16(assetConfig.liquidateCollateralFactor / descale);
uint16 liquidationFactor = uint16(assetConfig.liquidationFactor / descale);
// Be nice and check descaled values are still within range
if (borrowCollateralFactor >= liquidateCollateralFactor) revert CometMainInterface.BorrowCFTooLarge();
// Keep whole units of asset for supply cap
uint64 supplyCap = uint64(assetConfig.supplyCap / (10 ** decimals_));
uint256 word_a = (uint160(asset) << 0 |
uint256(borrowCollateralFactor) << 160 |
uint256(liquidateCollateralFactor) << 176 |
uint256(liquidationFactor) << 192);
uint256 word_b = (uint160(priceFeed) << 0 |
uint256(decimals_) << 160 |
uint256(supplyCap) << 168);
return (word_a, word_b);
}
}
/**
* @notice Get the i-th asset info, according to the order they were passed in originally
* @param i The index of the asset info to get
* @return The asset info object
*/
function getAssetInfo(uint8 i) public view returns (CometCore.AssetInfo memory) {
if (i >= numAssets) revert CometMainInterface.BadAsset();
uint256 word_a;
uint256 word_b;
if(i == 0){
word_a = asset00_a;
word_b = asset00_b;
}
if(i == 1){
word_a = asset01_a;
word_b = asset01_b;
}
if(i == 2){
word_a = asset02_a;
word_b = asset02_b;
}
if(i == 3){
word_a = asset03_a;
word_b = asset03_b;
}
if(i == 4){
word_a = asset04_a;
word_b = asset04_b;
}
if(i == 5){
word_a = asset05_a;
word_b = asset05_b;
}
if(i == 6){
word_a = asset06_a;
word_b = asset06_b;
}
if(i == 7){
word_a = asset07_a;
word_b = asset07_b;
}
if(i == 8){
word_a = asset08_a;
word_b = asset08_b;
}
if(i == 9){
word_a = asset09_a;
word_b = asset09_b;
}
if(i == 10){
word_a = asset10_a;
word_b = asset10_b;
}
if(i == 11){
word_a = asset11_a;
word_b = asset11_b;
}
if(i == 12){
word_a = asset12_a;
word_b = asset12_b;
}
if(i == 13){
word_a = asset13_a;
word_b = asset13_b;
}
if(i == 14){
word_a = asset14_a;
word_b = asset14_b;
}
if(i == 15){
word_a = asset15_a;
word_b = asset15_b;
}
if(i == 16){
word_a = asset16_a;
word_b = asset16_b;
}
if(i == 17){
word_a = asset17_a;
word_b = asset17_b;
}
if(i == 18){
word_a = asset18_a;
word_b = asset18_b;
}
if(i == 19){
word_a = asset19_a;
word_b = asset19_b;
}
if(i == 20){
word_a = asset20_a;
word_b = asset20_b;
}
if(i == 21){
word_a = asset21_a;
word_b = asset21_b;
}
if(i == 22){
word_a = asset22_a;
word_b = asset22_b;
}
if(i == 23){
word_a = asset23_a;
word_b = asset23_b;
}
address asset = address(uint160(word_a & type(uint160).max));
uint64 rescale = FACTOR_SCALE / 1e4;
uint64 borrowCollateralFactor = uint64(((word_a >> 160) & type(uint16).max) * rescale);
uint64 liquidateCollateralFactor = uint64(((word_a >> 176) & type(uint16).max) * rescale);
uint64 liquidationFactor = uint64(((word_a >> 192) & type(uint16).max) * rescale);
address priceFeed = address(uint160(word_b & type(uint160).max));
uint8 decimals_ = uint8(((word_b >> 160) & type(uint8).max));
uint64 scale = uint64(10 ** decimals_);
uint128 supplyCap = uint128(((word_b >> 168) & type(uint64).max) * scale);
return CometCore.AssetInfo({
offset: i,
asset: asset,
priceFeed: priceFeed,
scale: scale,
borrowCollateralFactor: borrowCollateralFactor,
liquidateCollateralFactor: liquidateCollateralFactor,
liquidationFactor: liquidationFactor,
supplyCap: supplyCap
});
}
}"
},
"contracts/AssetListFactory.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.15;
import "./AssetList.sol";
/**
* @title Compound's Asset List Factory
* @author Compound
*/
contract AssetListFactory {
event AssetListCreated(address indexed assetList, CometCore.AssetConfig[] assetConfigs);
/**
* @notice Create a new asset list
* @param assetConfigs The asset configurations
* @return assetList The address of the new asset list
*/
function createAssetList(CometCore.AssetConfig[] memory assetConfigs) external returns (address assetList) {
assetList = address(new AssetList(assetConfigs));
emit AssetListCreated(assetList, assetConfigs);
}
}"
},
"contracts/bridges/arbitrum/AddressAliasHelper.sol": {
"content": "// SPDX-License-Identifier: Apache-2.0
/*
* Copyright 2019-2021, Offchain Labs, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
pragma solidity 0.8.15;
library AddressAliasHelper {
uint160 constant offset = uint160(0x1111000000000000000000000000000000001111);
/// @notice Utility function that converts the address in the L1 that submitted a tx to
/// the inbox to the msg.sender viewed in the L2
/// @param l1Address the address in the L1 that triggered the tx to L2
/// @return l2Address L2 address as viewed in msg.sender
function applyL1ToL2Alias(address l1Address) internal pure returns (address l2Address) {
l2Address = address(uint160(l1Address) + offset);
}
/// @notice Utility function that converts the msg.sender viewed in the L2 to the
/// address in the L1 that submitted a tx to the inbox
/// @param l2Address L2 address as viewed in msg.sender
/// @return l1Address the address in the L1 that triggered the tx to L2
function undoL1ToL2Alias(address l2Address) internal pure returns (address l1Address) {
l1Address = address(uint160(l2Address) - offset);
}
}"
},
"contracts/bridges/arbitrum/ArbitrumBridgeReceiver.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.15;
import "../SweepableBridgeReceiver.sol";
import "./AddressAliasHelper.sol";
contract ArbitrumBridgeReceiver is SweepableBridgeReceiver {
fallback() external payable {
processMessage(AddressAliasHelper.undoL1ToL2Alias(msg.sender), msg.data);
}
}"
},
"contracts/bridges/BaseBridgeReceiver.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.15;
import "../ITimelock.sol";
contract BaseBridgeReceiver {
/** Custom errors **/
error AlreadyInitialized();
error BadData();
error InvalidProposalId();
error InvalidTimelockAdmin();
error ProposalNotExecutable();
error TransactionAlreadyQueued();
error Unauthorized();
/** Events **/
event Initialized(address indexed govTimelock, address indexed localTimelock);
event ProposalCreated(address indexed rootMessageSender, uint id, address[] targets, uint[] values, string[] signatures, bytes[] calldatas, uint eta);
event ProposalExecuted(uint indexed id);
/** Public variables **/
/// @notice Address of the governing contract that this bridge receiver expects to
/// receive messages from; likely an address from another chain (e.g. mainnet)
address public govTimelock;
/// @notice Address of the timelock on this chain that the bridge receiver
/// will send messages to
address public localTimelock;
/// @notice Whether contract has been initialized
bool public initialized;
/// @notice Total count of proposals generated
uint public proposalCount;
struct Proposal {
uint id;
address[] targets;
uint[] values;
string[] signatures;
bytes[] calldatas;
uint eta;
bool executed;
}
/// @notice Mapping of proposal ids to their full proposal data
mapping (uint => Proposal) public proposals;
enum ProposalState {
Queued,
Expired,
Executed
}
/**
* @notice Initialize the contract
* @param _govTimelock Address of the governing contract that this contract
* will receive messages from (likely on another chain)
* @param _localTimelock Address of the timelock contract that this contract
* will send messages to
*/
function initialize(address _govTimelock, address _localTimelock) external {
if (initialized) revert AlreadyInitialized();
if (ITimelock(_localTimelock).admin() != address(this)) revert InvalidTimelockAdmin();
govTimelock = _govTimelock;
localTimelock = _localTimelock;
initialized = true;
emit Initialized(_govTimelock, _localTimelock);
}
/**
* @notice Process a message sent from the governing timelock (across a bridge)
* @param rootMessageSender Address of the contract that sent the bridged message
* @param data ABI-encoded bytes containing the transactions to be queued on the local timelock
*/
function processMessage(
address rootMessageSender,
bytes calldata data
) internal {
if (rootMessageSender != govTimelock) revert Unauthorized();
address[] memory targets;
uint256[] memory values;
string[] memory signatures;
bytes[] memory calldatas;
(targets, values, signatures, calldatas) = abi.decode(
data,
(address[], uint256[], string[], bytes[])
);
if (values.length != targets.length) revert BadData();
if (signatures.length != targets.length) revert BadData();
if (calldatas.length != targets.length) revert BadData();
uint delay = ITimelock(localTimelock).delay();
uint eta = block.timestamp + delay;
for (uint i = 0; i < targets.length; i++) {
if (ITimelock(localTimelock).queuedTransactions(keccak256(abi.encode(targets[i], values[i], signatures[i], calldatas[i], eta)))) revert TransactionAlreadyQueued();
ITimelock(localTimelock).queueTransaction(targets[i], values[i], signatures[i], calldatas[i], eta);
}
proposalCount++;
Proposal memory proposal = Proposal({
id: proposalCount,
targets: targets,
values: values,
signatures: signatures,
calldatas: calldatas,
eta: eta,
executed: false
});
proposals[proposal.id] = proposal;
emit ProposalCreated(rootMessageSender, proposal.id, targets, values, signatures, calldatas, eta);
}
/**
* @notice Execute a queued proposal
* @param proposalId The id of the proposal to execute
*/
function executeProposal(uint proposalId) external {
if (state(proposalId) != ProposalState.Queued) revert ProposalNotExecutable();
Proposal storage proposal = proposals[proposalId];
proposal.executed = true;
for (uint i = 0; i < proposal.targets.length; i++) {
ITimelock(localTimelock).executeTransaction(proposal.targets[i], proposal.values[i], proposal.signatures[i], proposal.calldatas[i], proposal.eta);
}
emit ProposalExecuted(proposalId);
}
/**
* @notice Get the state of a proposal
* @param proposalId Id of the proposal
* @return The state of the given proposal (queued, expired or executed)
*/
function state(uint proposalId) public view returns (ProposalState) {
if (proposalId > proposalCount || proposalId == 0) revert InvalidProposalId();
Proposal memory proposal = proposals[proposalId];
if (proposal.executed) {
return ProposalState.Executed;
} else if (block.timestamp > (proposal.eta + ITimelock(localTimelock).GRACE_PERIOD())) {
return ProposalState.Expired;
} else {
return ProposalState.Queued;
}
}
}"
},
"contracts/bridges/linea/IMessageService.sol": {
"content": "pragma solidity 0.8.15;
interface IMessageService {
function sender() external view returns (address);
}
"
},
"contracts/bridges/linea/LineaBridgeReceiver.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.15;
import "./IMessageService.sol";
import "../SweepableBridgeReceiver.sol";
contract LineaBridgeReceiver is SweepableBridgeReceiver {
/// @notice Address of Linea Message Service
IMessageService public messageService;
constructor(address _messageService) {
messageService = IMessageService(_messageService);
}
fallback() external payable {
if (msg.sender != address(messageService)) revert Unauthorized();
processMessage(messageService.sender(), msg.data);
}
}
"
},
"contracts/bridges/optimism/IOvmL2CrossDomainMessengerInterface.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.15;
interface IOvmL2CrossDomainMessengerInterface {
function xDomainMessageSender() external returns (address);
}
"
},
"contracts/bridges/optimism/OptimismBridgeReceiver.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.15;
import "../SweepableBridgeReceiver.sol";
import "./IOvmL2CrossDomainMessengerInterface.sol";
contract OptimismBridgeReceiver is SweepableBridgeReceiver {
error InvalidCrossDomainMessenger();
event NewCrossDomainMessenger(address indexed oldCrossDomainMessenger, address indexed newCrossDomainMessenger);
address public crossDomainMessenger;
constructor(address crossDomainMessenger_) {
crossDomainMessenger = crossDomainMessenger_;
}
function changeCrossDomainMessenger(address newCrossDomainMessenger) public {
if (msg.sender != localTimelock) revert Unauthorized();
address oldCrossDomainMessenger = crossDomainMessenger;
crossDomainMessenger = newCrossDomainMessenger;
emit NewCrossDomainMessenger(oldCrossDomainMessenger, newCrossDomainMessenger);
}
fallback() external payable {
if (msg.sender != crossDomainMessenger) revert InvalidCrossDomainMessenger();
address messageSender = IOvmL2CrossDomainMessengerInterface(msg.sender).xDomainMessageSender();
processMessage(messageSender, msg.data);
}
}"
},
"contracts/bridges/polygon/IFxMessageProcessor.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// IFxMessageProcessor represents interface to process message
interface IFxMessageProcessor {
function processMessageFromRoot(
uint256 stateId,
address rootMessageSender,
bytes calldata data
) external;
}"
},
"contracts/bridges/polygon/PolygonBridgeReceiver.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.15;
import "./IFxMessageProcessor.sol";
import "../BaseBridgeReceiver.sol";
contract PolygonBridgeReceiver is IFxMessageProcessor, BaseBridgeReceiver {
error InvalidChild();
event NewFxChild(address indexed oldFxChild, address indexed newFxChild);
/// @notice Address of Polygon's bridged message receiver
address public fxChild;
/**
* @notice Construct a new PolygonBridgeReceiver instance
* @param _fxChild Address of Polygon bridged message receiver
**/
constructor(address _fxChild) {
fxChild = _fxChild;
}
/**
* @notice Update the fxChild address
* @param newFxChild New value for fxAddress
*/
function changeFxChild(address newFxChild) public {
if (msg.sender != localTimelock) revert Unauthorized();
address oldFxChild = fxChild;
fxChild = newFxChild;
emit NewFxChild(oldFxChild, newFxChild);
}
/**
* @notice Receive bridged message and enqueue in the Timelock
* @param stateId Value provided by fxChild when function is called; ignored
* @param rootMessageSender Mainnet address that initiated the bridged message
* @param data ABI-encoded data of the bridged message
*/
function processMessageFromRoot(
uint256 stateId,
address rootMessageSender,
bytes calldata data
) public override {
if (msg.sender != fxChild) revert InvalidChild();
processMessage(rootMessageSender, data);
}
}"
},
"contracts/bridges/ronin/RoninBridgeReceiver.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.15;
import "../SweepableBridgeReceiver.sol";
import {IERC165} from "../../IERC165.sol";
import {IAny2EVMMessageReceiver, Any2EVMMessage} from "../../IAny2EVMMessageReceiver.sol";
contract RoninBridgeReceiver is SweepableBridgeReceiver, IERC165, IAny2EVMMessageReceiver{
uint64 constant MAINNET_CHAIN_SELECTOR = 5009297550715157269;
function supportsInterface(
bytes4 interfaceId
) public view virtual override returns (bool) {
return
interfaceId == type(IAny2EVMMessageReceiver).interfaceId ||
interfaceId == type(IERC165).interfaceId;
}
error InvalidRouter();
error InvalidChainSelector();
address public l2Router;
constructor(address l2Router_) {
l2Router = l2Router_;
}
function ccipReceive(Any2EVMMessage calldata message) external {
if (msg.sender != l2Router) revert InvalidRouter();
if(message.sourceChainSelector != MAINNET_CHAIN_SELECTOR) revert InvalidChainSelector();
processMessage(toAddress(message.sender), message.data);
}
function toAddress(bytes memory data) public pure returns (address addr) {
require(data.length >= 20, "Invalid data length");
return abi.decode(data, (address));
}
}
"
},
"contracts/bridges/scroll/IScrollMessenger.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.15;
/// @notice IScrollMessenger is the interface for Scroll's messenger contract
interface IScrollMessenger {
function xDomainMessageSender() external view returns (address);
}
"
},
"contracts/bridges/scroll/ScrollBridgeReceiver.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.15;
import "../SweepableBridgeReceiver.sol";
import "./IScrollMessenger.sol";
/// @title Scroll Bridge Receiver
/// @notice Contract that processes messages passed from Compound governance using the Scroll bridge
contract ScrollBridgeReceiver is SweepableBridgeReceiver {
error InvalidL2Messenger();
event NewL2Messenger(address indexed oldL2Messenger, address indexed newL2Messenger);
/// @notice Address of Scroll L2 Messenger contract
address public l2Messenger;
/// @notice Construct a new ScrollBridgeReceiver instance
/// @param l2Messenger_ Address of Scroll L2 Messenger contract
constructor(address l2Messenger_) {
l2Messenger = l2Messenger_;
emit NewL2Messenger(address(0), l2Messenger_);
}
/// @notice Update the L2 Messenger address
/// @param newL2Messenger New address for L2 Messenger contract
function changeL2Messenger(address newL2Messenger) public {
if (msg.sender != localTimelock) revert Unauthorized();
address oldL2Messenger = l2Messenger;
l2Messenger = newL2Messenger;
emit NewL2Messenger(oldL2Messenger, newL2Messenger);
}
/// @notice Fallback function to handle messages
fallback() external payable {
if (msg.sender != l2Messenger) revert InvalidL2Messenger();
address messageSender = IScrollMessenger(msg.sender).xDomainMessageSender();
processMessage(messageSender, msg.data);
}
}"
},
"contracts/bridges/SweepableBridgeReceiver.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.15;
import "../IERC20NonStandard.sol";
import "./BaseBridgeReceiver.sol";
contract SweepableBridgeReceiver is BaseBridgeReceiver {
error FailedToSendNativeToken();
error TransferOutFailed();
/**
* @notice A public function to sweep accidental ERC-20 transfers to this contract
* @dev Note: Make sure to check that the asset being swept out is not malicious
* @param recipient The address that will receive the swept funds
* @param asset The address of the ERC-20 token to sweep
*/
function sweepToken(address recipient, address asset) external {
if (msg.sender != localTimelock) revert Unauthorized();
uint256 balance = IERC20NonStandard(asset).balanceOf(address(this));
doTransferOut(asset, recipient, balance);
}
/**
* @notice A public function to sweep accidental native token transfers to this contract
* @param recipient The address that will receive the swept funds
*/
function sweepNativeToken(address recipient) external {
if (msg.sender != localTimelock) revert Unauthorized();
uint256 balance = address(this).balance;
(bool success, ) = recipient.call{ value: balance }("");
if (!success) revert FailedToSendNativeToken();
}
/**
* @notice Similar to ERC-20 transfer, except it properly handles `transfer` from non-standard ERC-20 tokens
* @param asset The ERC-20 token to transfer out
* @param to The recipient of the token transfer
* @param amount The amount of the token to transfer
* @dev Note: This wrapper safely handles non-standard ERC-20 tokens that do not return a value. See here: https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca
*/
function doTransferOut(address asset, address to, uint amount) internal {
IERC20NonStandard(asset).transfer(to, amount);
bool success;
assembly {
switch returndatasize()
case 0 { // This is a non-standard ERC-20
success := not(0) // set success to true
}
case 32 { // This is a compliant ERC-20
returndatacopy(0, 0, 32)
success := mload(0) // Set `success = returndata` of override external call
}
default { // This is an excessively non-compliant ERC-20, revert.
revert(0, 0)
}
}
if (!success) revert TransferOutFailed();
}
}"
},
"contracts/bridges/test/BaseBridgeReceiverHarness.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.15;
import "../BaseBridgeReceiver.sol";
contract BaseBridgeReceiverHarness is BaseBridgeReceiver {
function processMessageExternal(
address rootMessageSender,
bytes calldata data
) external {
processMessage(rootMessageSender, data);
}
}"
},
"contracts/bridges/test/SweepableBridgeReceiverHarness.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.15;
import "../SweepableBridgeReceiver.sol";
contract SweepableBridgeReceiverHarness is SweepableBridgeReceiver {
function processMessageExternal(
address rootMessageSender,
bytes calldata data
) external {
processMessage(rootMessageSender, data);
}
fallback() external payable { }
}
"
},
"contracts/bulkers/BaseBulker.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.15;
import "../CometInterface.sol";
import "../IERC20NonStandard.sol";
import "../IWETH9.sol";
/**
* @dev Interface for claiming rewards from the CometRewards contract
*/
interface IClaimable {
function claim(address comet, address src, bool shouldAccrue) external;
function claimTo(address comet, address src, address to, bool shouldAccrue) external;
}
/**
* @title Compound's Bulker contract
* @notice Executes multiple Comet-related actions in a single transaction
* @author Compound
* @dev Note: Only intended to be used on EVM chains that have a native token and wrapped native token that implements the IWETH interface
*/
contract BaseBulker {
/** Custom events **/
event AdminTransferred(address indexed oldAdmin, address indexed newAdmin);
/** General configuration constants **/
/// @notice The admin of the Bulker contract
address public admin;
/// @notice The address of the wrapped representation of the chain's native asset
address payable public immutable wrappedNativeToken;
/** Actions **/
/// @notice The action for supplying an asset to Comet
bytes32 public constant ACTION_SUPPLY_ASSET = "ACTION_SUPPLY_ASSET";
/// @notice The action for supplying a native asset (e.g. ETH on Ethereum mainnet) to Comet
bytes32 public constant ACTION_SUPPLY_NATIVE_TOKEN = "ACTION_SUPPLY_NATIVE_TOKEN";
/// @notice The action for transferring an asset within Comet
bytes32 public constant ACTION_TRANSFER_ASSET = "ACTION_TRANSFER_ASSET";
/// @notice The action for withdrawing an asset from Comet
bytes32 public constant ACTION_WITHDRAW_ASSET = "ACTION_WITHDRAW_ASSET";
/// @notice The action for withdrawing a native asset from Comet
bytes32 public constant ACTION_WITHDRAW_NATIVE_TOKEN = "ACTION_WITHDRAW_NATIVE_TOKEN";
/// @notice The action for claiming rewards from the Comet rewards contract
bytes32 public constant ACTION_CLAIM_REWARD = "ACTION_CLAIM_REWARD";
/** Custom errors **/
error InvalidAddress();
error InvalidArgument();
error FailedToSendNativeToken();
error TransferInFailed();
error TransferOutFailed();
error Unauthorized();
error UnhandledAction();
/**
* @notice Construct a new BaseBulker instance
* @param admin_ The admin of the Bulker contract
* @param wrappedNativeToken_ The address of the wrapped representation of the chain's native asset
**/
constructor(address admin_, address payable wrappedNativeToken_) {
admin = admin_;
wrappedNativeToken = wrappedNativeToken_;
}
/**
* @notice Fallback for receiving native token. Needed for ACTION_WITHDRAW_NATIVE_TOKEN
*/
receive() external payable {}
/**
* @notice A public function to sweep accidental ERC-20 transfers to this contract
* @dev Note: Make sure to check that the asset being swept out is not malicious
* @param recipient The address that will receive the swept funds
* @param asset The address of the ERC-20 token to sweep
*/
function sweepToken(address recipient, address asset) external {
if (msg.sender != admin) revert Unauthorized();
uint256 balance = IERC20NonStandard(asset).balanceOf(address(this));
doTransferOut(asset, recipient, balance);
}
/**
* @notice A public function to sweep accidental native token transfers to this contract
* @param recipient The address that will receive the swept funds
*/
function sweepNativeToken(address recipient) external {
if (msg.sender != admin) revert Unauthorized();
uint256 balance = address(this).balance;
(bool success, ) = recipient.call{ value: balance }("");
if (!success) revert FailedToSendNativeToken();
}
/**
* @notice Transfers the admin rights to a new address
* @param newAdmin The address that will become the new admin
*/
function transferAdmin(address newAdmin) external {
if (msg.sender != admin) revert Unauthorized();
if (newAdmin == address(0)) revert InvalidAddress();
address oldAdmin = admin;
admin = newAdmin;
emit AdminTransferred(oldAdmin, newAdmin);
}
/**
* @notice Executes a list of actions in order
* @param actions The list of actions to execute in order
* @param data The list of calldata to use for each action
*/
function invoke(bytes32[] calldata actions, bytes[] calldata data) external payable {
if (actions.length != data.length) revert InvalidArgument();
uint unusedNativeToken = msg.value;
for (uint i = 0; i < actions.length; ) {
bytes32 action = actions[i];
if (action == ACTION_SUPPLY_ASSET) {
(address comet, address to, address asset, uint amount) = abi.decode(data[i], (address, address, address, uint));
supplyTo(comet, to, asset, amount);
} else if (action == ACTION_SUPPLY_NATIVE_TOKEN) {
(address comet, address to, uint amount) = abi.decode(data[i], (address, address, uint));
uint256 nativeTokenUsed = supplyNativeTokenTo(comet, to, amount);
unusedNativeToken -= nativeTokenUsed;
} else if (action == ACTION_TRANSFER_ASSET) {
(address comet, address to, address asset, uint amount) = abi.decode(data[i], (address, address, address, uint));
transferTo(comet, to, asset, amount);
} else if (action == ACTION_WITHDRAW_ASSET) {
(address comet, address to, address asset, uint amount) = abi.decode(data[i], (address, address, address, uint));
withdrawTo(comet, to, asset, amount);
} else if (action == ACTION_WITHDRAW_NATIVE_TOKEN) {
(address comet, address to, uint amount) = abi.decode(data[i], (address, address, uint));
withdrawNativeTokenTo(comet, to, amount);
} else if (action == ACTION_CLAIM_REWARD) {
(address comet, address rewards, address src, bool shouldAccrue) = abi.decode(data[i], (address, address, address, bool));
claimReward(comet, rewards, src, shouldAccrue);
} else {
handleAction(action, data[i]);
}
unchecked { i++; }
}
// Refund unused native token back to msg.sender
if (unusedNativeToken > 0) {
(bool success, ) = msg.sender.call{ value: unusedNativeToken }("");
if (!success) revert FailedToSendNativeToken();
}
}
/**
* @notice Handles any actions not handled by the BaseBulker implementation
* @dev Note: Meant to be overridden by contracts that extend BaseBulker and want to support more actions
*/
function handleAction(bytes32 action, bytes calldata data) virtual internal {
revert UnhandledAction();
}
/**
* @notice Supplies an asset to a user in Comet
* @dev Note: This contract must have permission to manage msg.sender's Comet account
*/
function supplyTo(address comet, address to, address asset, uint amount) internal {
CometInterface(comet).supplyFrom(msg.sender, to, asset, amount);
}
/**
* @notice Wraps the native token and supplies wrapped native token to a user in Comet
* @return The amount of the native token wrapped and supplied to Comet
* @dev Note: Supports `amount` of `uint256.max` implies max only for base asset
*/
function supplyNativeTokenTo(address comet, address to, uint amount) internal returns (uint256) {
uint256 supplyAmount = amount;
if (wrappedNativeToken == CometInterface(comet).baseToken()) {
if (amount == type(uint256).max)
supplyAmount = CometInterface(comet).borrowBalanceOf(msg.sender);
}
IWETH9(wrappedNativeToken).deposit{ value: supplyAmount }();
IWETH9(wrappedNativeToken).approve(comet, supplyAmount);
CometInterface(comet).supplyFrom(address(this), to, wrappedNativeToken, supplyAmount);
return supplyAmount;
}
/**
* @notice Transfers an asset to a user in Comet
* @dev Note: This contract must have permission to manage msg.sender's Comet account
*/
function transferTo(address comet, address to, address asset, uint amount) internal {
CometInterface(comet).transferAssetFrom(msg.sender, to, asset, amount);
}
/**
* @notice Withdraws an asset to a user in Comet
* @dev Note: This contract must have permission to manage msg.sender's Comet account
*/
function withdrawTo(address comet, address to, address asset, uint amount) internal {
CometInterface(comet).withdrawFrom(msg.sender, to, asset, amount);
}
/**
* @notice Withdraws wrapped native token from Comet, unwraps it to the native token, and transfers it to a user
* @dev Note: This contract must have permission to manage msg.sender's Comet account
* @dev Note: Supports `amount` of `uint256.max` only for the base asset. Should revert for a collateral asset
*/
function withdrawNativeTokenTo(address comet, address to, uint amount) internal {
uint256 withdrawAmount = amount;
if (wrappedNativeToken == CometInterface(comet).baseToken()) {
if (amount == type(uint256).max)
withdrawAmount = CometInterface(comet).balanceOf(msg.sender);
}
CometInterface(comet).withdrawFrom(msg.sender, address(this), wrappedNativeToken, withdrawAmount);
IWETH9(wrappedNativeToken).withdraw(withdrawAmount);
(bool success, ) = to.call{ value: withdrawAmount }("");
if (!success) revert FailedToSendNativeToken();
}
/**
* @notice Claims rewards for a user
*/
function claimReward(address comet, address rewards, address src, bool shouldAccrue) internal {
IClaimable(rewards).claim(comet, src, shouldAccrue);
}
/**
* @notice Similar to ERC-20 transfer, except it properly handles `transferFrom` from non-standard ERC-20 tokens
* @param asset The ERC-20 token to transfer in
* @param from The address to transfer from
* @param amount The amount of the token to transfer
* @dev Note: This does not check that the amount transferred in is actually equals to the amount specified (e.g. fee tokens will not revert)
* @dev Note: This wrapper safely handles non-standard ERC-20 tokens that do not return a value. See here: https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca
*/
function doTransferIn(address asset, address from, uint amount) internal {
IERC20NonStandard(asset).transferFrom(from, address(this), amount);
bool success;
assembly {
switch returndatasize()
case 0 { // This is a non-standard ERC-20
success := not(0) // set success to true
}
case 32 { // This is a compliant ERC-20
returndatacopy(0, 0, 32)
success := mload(0) // Set `success = returndata` of override external call
}
default { // This is an excessively non-compliant ERC-20, revert.
revert(0, 0)
}
}
if (!success) revert TransferInFailed();
}
/**
* @notice Similar to ERC-20 transfer, except it properly handles `transfer` from non-standard ERC-20 tokens
* @param asset The ERC-20 token to transfer out
* @param to The recipient of the token transfer
* @param amount The amount of the token to transfer
* @dev Note: This wrapper safely handles non-standard ERC-20 tokens that do not return a value. See here: https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca
*/
function doTransferOut(address asset, address to, uint amount) internal {
IERC20NonStandard(asset).transfer(to, amount);
bool success;
assembly {
switch returndatasize()
case 0 { // This is a non-standard ERC-20
success := not(0) // set success to true
}
case 32 { // This is a compliant ERC-20
returndatacopy(0, 0, 32)
success := mload(0) // Set `success = returndata` of override external call
}
default { // This is an excessively non-compliant ERC-20, revert.
revert(0, 0)
}
}
if (!success) revert TransferOutFailed();
}
}
"
},
"contracts/bulkers/MainnetBulker.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.15;
import "./BaseBulker.sol";
import "../IWstETH.sol";
/**
* @title Compound's Bulker contract for Ethereum mainnet
* @notice Executes multiple Comet-related actions in a single transaction
* @author Compound
*/
contract MainnetBulker is BaseBulker {
/** General configuration constants **/
/// @notice The address of Lido staked ETH
address public immutable steth;
/// @notice The address of Lido wrapped staked ETH
address public immutable wsteth;
/** Actions **/
/// @notice The action for supplying staked ETH to Comet
bytes32 public constant ACTION_SUPPLY_STETH = "ACTION_SUPPLY_STETH";
/// @notice The action for withdrawing staked ETH from Comet
bytes32 public constant ACTION_WITHDRAW_STETH = "ACTION_WITHDRAW_STETH";
/** Custom errors **/
error UnsupportedBaseAsset();
/**
* @notice Construct a new MainnetBulker instance
* @param admin_ The admin of the Bulker contract
* @param weth_ The address of wrapped ETH
* @param wsteth_ The address of Lido wrapped staked ETH
**/
constructor(
address admin_,
address payable weth_,
address wsteth_
) BaseBulker(admin_, weth_) {
wsteth = wsteth_;
steth = IWstETH(wsteth_).stETH();
}
/**
* @notice Handles actions specific to the Ethereum mainnet version of Bulker, specifically supplying and withdrawing stETH
*/
function handleAction(bytes32 action, bytes calldata data) override internal {
if (action == ACTION_SUPPLY_STETH) {
(address comet, address to, uint stETHAmount) = abi.decode(data, (address, address, uint));
supplyStEthTo(comet, to, stETHAmount);
} else if (action == ACTION_WITHDRAW_STETH) {
(address comet, address to, uint wstETHAmount) = abi.decode(data, (address, address, uint));
withdrawStEthTo(comet, to, wstETHAmount);
} else {
revert UnhandledAction();
}
}
/**
* @notice Wraps stETH to wstETH and supplies to a user in Comet
* @dev Note: This contract must have permission to manage msg.sender's Comet account
* @dev Note: wstETH base asset is NOT supported
*/
function supplyStEthTo(address comet, address to, uint stETHAmount) internal {
if (CometInterface(comet).baseToken() == wsteth) revert UnsupportedBaseAsset();
doTransferIn(steth, msg.sender, stETHAmount);
ERC20(steth).approve(wsteth, stETHAmount);
uint wstETHAmount = IWstETH(wsteth).wrap(stETHAmount);
ERC20(wsteth).approve(comet, wstETHAmount);
CometInterface(comet).supplyFrom(address(this), to, wsteth, wstETHAmount);
}
/**
* @notice Withdraws wstETH from Comet, unwraps it to stETH, and transfers it to a user
* @dev Note: This contract must have permission to manage msg.sender's Comet account
* @dev Note: wstETH base asset is NOT supported
* @dev Note: Supports `amount` of `uint256.max` to withdraw all wstETH from Comet
*/
function withdrawStEthTo(address comet, address to, uint stETHAmount) internal {
if (CometInterface(comet).baseToken() == wsteth) revert UnsupportedBaseAsset();
uint wstETHAmount = stETHAmount == type(uint256).max
? CometInterface(comet).collateralBalanceOf(msg.sender, wsteth)
: IWstETH(wsteth).getWstETHByStETH(stETHAmount);
CometInterface(comet).withdrawFrom(msg.sender, address(this), wsteth, wstETHAmount);
uint unwrappedStETHAmount = IWstETH(wsteth).unwrap(wstETHAmount);
doTransferOut(steth, to, unwrappedStETHAmount);
}
}"
},
"contracts/bulkers/MainnetBulkerWithWstETHSupport.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.15;
import "./BaseBulker.sol";
import "../IWstETH.sol";
/**
* @title Compound's Bulker contract for Ethereum mainnet
* @notice Executes multiple Comet-related actions in a single transaction
* @author Compound
*/
contract MainnetBulkerWithWstETHSupport is BaseBulker {
/** General configuration constants **/
/// @notice The address of Lido staked ETH
address public immutable steth;
/// @notice The address of Lido wrapped staked ETH
address public immutable wsteth;
/** Actions **/
/// @notice The action for supplying staked ETH to Comet
bytes32 public constant ACTION_SUPPLY_STETH = "ACTION_SUPPLY_STETH";
/// @notice The action for withdrawing staked ETH from Comet
bytes32 public constant ACTION_WITHDRAW_STETH = "ACTION_WITHDRAW_STETH";
/** Custom errors **/
error UnsupportedBaseAsset();
/**
* @notice Construct a new MainnetBulker instance
* @param admin_ The admin of the Bulker contract
* @param weth_ The address of wrapped ETH
* @param wsteth_ The address of Lido wrapped staked ETH
**/
constructor(
address admin_,
address payable weth_,
address wsteth_
) BaseBulker(admin_, weth_) {
wsteth = wsteth_;
steth = IWstETH(wsteth_).stETH();
}
/**
* @notice Handles actions specific to the Ethereum mainnet version of Bulker, specifically supplying and withdrawing stETH
*/
function handleAction(bytes32 action, bytes calldata data) override internal {
if (action == ACTION_SUPPLY_STETH) {
(address comet, address to, uint stETHAmount) = abi.decode(data, (address, address, uint));
supplyStEthTo(comet, to, stETHAmount);
} else if (action == ACTION_WITHDRAW_STETH) {
(address comet, address to, uint wstETHAmount) = abi.decode(data, (address, address, uint));
withdrawStEthTo(comet, to, wstETHAmount);
} else {
revert UnhandledAction();
}
}
/**
* @notice Wraps stETH to wstETH and supplies to a user in Comet
* @dev Note: This contract must have permission to manage msg.sender's Comet account
* @dev Note: Supports `stETHAmount` of `uint256.max` to fully repay the wstETH debt
* @dev Note: Only for the cwstETHv3 market
*/
function supplyStEthTo(address comet, address to, uint stETHAmount) internal {
if(CometInterface(comet).baseToken() != wsteth) revert UnsupportedBaseAsset();
uint256 _stETHAmount = stETHAmount == type(uint256).max
? IWstETH(wsteth).getStETHByWstETH(CometInterface(comet).borrowBalanceOf(msg.sender))
: stETHAmount;
doTransferIn(steth, msg.sender, _stETHAmount);
ERC20(steth).approve(wsteth, _stETHAmount);
uint wstETHAmount = IWstETH(wsteth).wrap(_stETHAmount);
ERC20(wsteth).approve(comet, wstETHAmount);
CometInterface(comet).supplyFrom(address(this), to, wsteth, wstETHAmount);
}
/**
* @notice Withdraws wstETH from Comet, unwraps it to stETH, and transfers it to a user
* @dev Note: This contract must have permission to manage msg.sender's Comet account
* @dev Note: Supports `amount` of `uint256.max` to withdraw all wstETH from Comet
* @dev Note: Only for the cwstETHv3 market
*/
function withdrawStEthTo(address comet, address to, uint stETHAmount) internal {
if(CometInterface(comet).baseToken() != wsteth) revert UnsupportedBaseAsset();
uint wstETHAmount = stETHAmount == type(uint256).max
? CometInterface(comet).balanceOf(msg.sender)
: IWstETH(wsteth).getWstETHByStETH(stETHAmount);
CometInterface(comet).withdrawFrom(msg.sender, address(this), wsteth, wstETHAmount);
uint unwrappedStETHAmount = IWstETH(wsteth).unwrap(wstETHAmount);
doTransferOut(steth, to, unwrappedStETHAmount);
}
/**
* @notice Submits received ether to get stETH and wraps it to wstETH, received wstETH is transferred to Comet
*/
function deposit(address comet) external payable {
if(msg.sender != admin) revert Unauthorized();
if(CometInterface(comet).baseToken() != wsteth) revert UnsupportedBaseAsset();
(bool success, ) = payable(wsteth).call{value: msg.value}(new bytes(0));
if(!success) revert TransferOutFailed();
uint wstETHAmount = ERC20(wsteth).balanceOf(address(this));
doTransferOut(wsteth, comet, wstETHAmount);
}
}"
},
"contracts/capo/contracts/ChainlinkCorrelatedAssetsPriceOracle.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.15;
import { AggregatorV3Interface } from "./interfaces/AggregatorV3Interface.sol";
import { PriceCapAdapterBase } from "./utils/PriceCapAdapterBase.sol";
/**
* @title ChainlinkCorrelatedAssetsPriceOracle
* @author WOOF!
* @custom:security-contact dmitriy@woof.software
*/
contract ChainlinkCorrelatedAssetsPriceOracle is PriceCapAdapterBase {
uint8 internal immutable _ratioDecimals;
/**
* @param _manager address of the manager
* @param _baseAggregatorAddress address of the base aggregator
* @param _ratioProviderAddress address of the ratio provider
* @param _description description of the pair
* @param _priceFeedDecimals number of decimals for the price feed
* @param _minimumSnapshotDelay minimum time that should have passed from the snapshot timestamp to the current block.timestamp
* @param _priceCapSnapshot parameters to set price cap
*/
constructor(
address _manager,
AggregatorV3Interface _baseAggregatorAddress,
address _ratioProviderAddress,
string memory _description,
uint8 _priceFeedDecimals,
uint48 _minimumSnapshotDelay,
PriceCapSnapshot memory _priceCapSnapshot
)
PriceCapAdapterBase(
_manager,
_baseAggregatorAddress,
_ratioProviderAddress,
_description,
_priceFeedDecimals,
_minimumSnapshotDelay,
_priceCapSnapshot
)
{
_ratioDecimals = AggregatorV3Interface(_ratioProviderAddress).decimals();
}
/// @inheritdoc PriceCapAdapterBase
function getRatio() public view override returns (int256 ratio) {
(, ratio, , , ) = AggregatorV3Interface(ratioProvider).latestRoundData();
}
/// @inheritdoc PriceCapAdapterBase
function ratioDecimals() public view override returns (uint8) {
return _ratioDecimals;
}
}
"
},
"contracts/capo/contracts/ERC4626CorrelatedAssetsPriceOracle.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.15;
import { IERC4626 } from "./interfaces/IERC4626.sol";
import { AggregatorV3Interface } from "./interfaces/AggregatorV3Interface.sol";
import { PriceCapAdapterBase } from "./utils/PriceCapAdapterBase.sol";
/**
* @title ERC4626CorrelatedAssetsPriceOracle
* @author WOOF!
* @custom:security-contact dmitriy@woof.software
*/
contract ERC4626CorrelatedAssetsPriceOracle is PriceCapAdapterBase {
uint8 internal immutable _ratioDecimals;
uint8 internal immutable _providerDecimals;
/**
* @param _manager address of the manager
* @param _baseAggregatorAddress address of the base aggregator
* @param _ratioProviderAddress address of the ratio provider
* @param _description description of the pair
* @param _priceFeedDecimals number of decimals for the price feed
* @param _minimumSnapshotDelay minimum time that should have passed from the snapshot timestamp to the current block.timestamp
* @param _priceCapSnapshot parameters to set price cap
*/
constructor(
address _manager,
AggregatorV3Interface _baseAggregatorAddress,
address _ratioProviderAddress,
string memory _description,
uint8 _priceFeedDecimals,
uint48 _minimumSnapshotDelay,
PriceCapSnapshot memory _priceCapSnapshot
)
PriceCapAdapterBase(
_manager,
_baseAggregatorAddress,
_ratioProviderAddress,
_description,
_priceFeedDecimals,
_minimumSnapshotDelay,
_priceCapSnapshot
)
{
_ratioDecimals = IERC4626(IERC4626(ratioProvider).asset()).decimals();
_providerDecimals = IERC4626(ratioProvider).decimals();
}
/// @inheritdoc PriceCapAdapterBase
function getRatio() public view override returns (int256) {
return int256(IERC4626(ratioProvider).convertToAssets(10 ** _providerDecimals));
}
/// @inheritdoc PriceCapAdapterBase
function ratioDecimals() public view override returns (uint8) {
return _ratioDecimals;
}
}
"
},
"contracts/capo/contracts/interfaces/AggregatorV3Interface.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface AggregatorV3Interface {
function decimals() external view returns (uint8);
function description() external view returns (string memory);
function version() external view returns (uint256);
// getRoundData and latestRoundData should both raise "No data present"
// if they do not have data to report, instead of returning unset values
// which could be misinterpreted as actual reported values.
function getRoundData(
uint80 _roundId
)
external
view
returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);
function latestRoundData()
external
view
returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);
}
"
},
"contracts/capo/contracts/interfaces/IERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.15;
/**
* @dev Interface of the ERC-20 standard as defined in the ERC.
*/
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 value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of 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 value) 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 a `value` amount of tokens 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 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address fr
Submitted on: 2025-10-06 22:22:43
Comments
Log in to comment.
No comments yet.