Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
// SPDX-License-Identifier: MIT
pragma solidity =0.8.24;
contract MainnetAuthAddresses {
address internal constant ADMIN_VAULT_ADDR = 0xCCf3d848e08b94478Ed8f46fFead3008faF581fD;
address internal constant DSGUARD_FACTORY_ADDRESS = 0x5a15566417e6C1c9546523066500bDDBc53F88C7;
address internal constant ADMIN_ADDR = 0x25eFA336886C74eA8E282ac466BdCd0199f85BB9; // USED IN ADMIN VAULT CONSTRUCTOR
address internal constant PROXY_AUTH_ADDRESS = 0x149667b6FAe2c63D1B4317C716b0D0e4d3E2bD70;
address internal constant MODULE_AUTH_ADDRESS = 0x7407974DDBF539e552F1d051e44573090912CC3D;
address internal constant DSA_AUTH_ADDRESS = 0xfc6f5dDEEC0e6122a501d9Ba8a9b428c74c4640A;
}
contract AuthHelper is MainnetAuthAddresses {
}
contract AdminVault is AuthHelper {
address public owner;
address public admin;
error SenderNotAdmin();
constructor() {
owner = msg.sender;
admin = ADMIN_ADDR;
}
/// @notice Admin is able to change owner
/// @param _owner Address of new owner
function changeOwner(address _owner) public {
if (admin != msg.sender){
revert SenderNotAdmin();
}
owner = _owner;
}
/// @notice Admin is able to set new admin
/// @param _admin Address of multisig that becomes new admin
function changeAdmin(address _admin) public {
if (admin != msg.sender){
revert SenderNotAdmin();
}
admin = _admin;
}
}
interface IERC20 {
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint256 digits);
function totalSupply() external view returns (uint256 supply);
function balanceOf(address _owner) external view returns (uint256 balance);
function transfer(address _to, uint256 _value) external returns (bool success);
function transferFrom(
address _from,
address _to,
uint256 _value
) external returns (bool success);
function approve(address _spender, uint256 _value) external returns (bool success);
function allowance(address _owner, address _spender) external view returns (uint256 remaining);
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
}
library Address {
//insufficient balance
error InsufficientBalance(uint256 available, uint256 required);
//unable to send value, recipient may have reverted
error SendingValueFail();
//insufficient balance for call
error InsufficientBalanceForCall(uint256 available, uint256 required);
//call to non-contract
error NonContractCall();
function isContract(address account) internal view returns (bool) {
// According to EIP-1052, 0x0 is the value returned for not-yet created accounts
// and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
// for accounts without code, i.e. `keccak256('')`
bytes32 codehash;
bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
// solhint-disable-next-line no-inline-assembly
assembly {
codehash := extcodehash(account)
}
return (codehash != accountHash && codehash != 0x0);
}
function sendValue(address payable recipient, uint256 amount) internal {
uint256 balance = address(this).balance;
if (balance < amount){
revert InsufficientBalance(balance, amount);
}
// solhint-disable-next-line avoid-low-level-calls, avoid-call-value
(bool success, ) = recipient.call{value: amount}("");
if (!(success)){
revert SendingValueFail();
}
}
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCall(target, data, "Address: low-level call failed");
}
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return _functionCallWithValue(target, data, 0, errorMessage);
}
function functionCallWithValue(
address target,
bytes memory data,
uint256 value
) internal returns (bytes memory) {
return
functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
uint256 balance = address(this).balance;
if (balance < value){
revert InsufficientBalanceForCall(balance, value);
}
return _functionCallWithValue(target, data, value, errorMessage);
}
function _functionCallWithValue(
address target,
bytes memory data,
uint256 weiValue,
string memory errorMessage
) private returns (bytes memory) {
if (!(isContract(target))){
revert NonContractCall();
}
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returndata) = target.call{value: weiValue}(data);
if (success) {
return returndata;
} else {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
// solhint-disable-next-line no-inline-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
library SafeERC20 {
using Address for address;
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Compatible with tokens that require the approval to be set to
* 0 before setting it to a non-zero value.
*/
function safeApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value);
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
// and not revert is the subcall reverts.
(bool success, bytes memory returndata) = address(token).call(data);
return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
}
}
contract AdminAuth is AuthHelper {
using SafeERC20 for IERC20;
AdminVault public constant adminVault = AdminVault(ADMIN_VAULT_ADDR);
error SenderNotOwner();
error SenderNotAdmin();
modifier onlyOwner() {
if (adminVault.owner() != msg.sender){
revert SenderNotOwner();
}
_;
}
modifier onlyAdmin() {
if (adminVault.admin() != msg.sender){
revert SenderNotAdmin();
}
_;
}
/// @notice withdraw stuck funds
function withdrawStuckFunds(address _token, address _receiver, uint256 _amount) public onlyOwner {
if (_token == 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) {
payable(_receiver).transfer(_amount);
} else {
IERC20(_token).safeTransfer(_receiver, _amount);
}
}
/// @notice Destroy the contract
/// @dev Deprecated method, selfdestruct will soon just send eth
function kill() public onlyAdmin {
selfdestruct(payable(msg.sender));
}
}
abstract contract Pausable is AdminAuth {
bool public isPaused;
error ContractPaused();
modifier notPaused {
if (isPaused) revert ContractPaused();
_;
}
function setPaused(bool _isPaused) external onlyAdmin {
isPaused = _isPaused;
}
}
contract MainnetCoreAddresses {
address internal constant REGISTRY_ADDR = 0x287778F121F134C66212FB16c9b53eC991D32f5b;
address internal constant DEFISAVER_LOGGER = 0xcE7a977Cac4a481bc84AC06b2Da0df614e621cf3;
address internal constant PROXY_AUTH_ADDR = 0x149667b6FAe2c63D1B4317C716b0D0e4d3E2bD70;
address internal constant MODULE_AUTH_ADDR = 0x7407974DDBF539e552F1d051e44573090912CC3D;
address internal constant DSA_AUTH_ADDR = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
address internal constant SUB_STORAGE_ADDR = 0x1612fc28Ee0AB882eC99842Cde0Fc77ff0691e90;
address internal constant BUNDLE_STORAGE_ADDR = 0x223c6aDE533851Df03219f6E3D8B763Bd47f84cf;
address internal constant STRATEGY_STORAGE_ADDR = 0xF52551F95ec4A2B4299DcC42fbbc576718Dbf933;
address internal constant BYTES_TRANSIENT_STORAGE = 0xB3FE6f712c8B8c64CD2780ce714A36e7640DDf0f;
}
contract CoreHelper is MainnetCoreAddresses {
}
interface IAuth {
///@param _walletAddr Address of the user's wallet
///@param _recipeExecutorAddr Address of the recipe executor
///@param _callData Data to be executed by the recipe executor
function callExecute(
address _walletAddr,
address _recipeExecutorAddr,
bytes memory _callData
) external payable;
}
abstract contract IDFSRegistry {
function getAddr(bytes4 _id) public view virtual returns (address);
function addNewContract(
bytes32 _id,
address _contractAddr,
uint256 _waitPeriod
) public virtual;
function startContractChange(bytes32 _id, address _newContractAddr) public virtual;
function approveContractChange(bytes32 _id) public virtual;
function cancelContractChange(bytes32 _id) public virtual;
function changeWaitPeriod(bytes32 _id, uint256 _newWaitPeriod) public virtual;
}
abstract contract WalletAuth is Pausable, CoreHelper, IAuth {
IDFSRegistry public constant registry = IDFSRegistry(REGISTRY_ADDR);
/// @dev The id is on purpose not the same as contract name for easier deployment
bytes4 constant STRATEGY_EXECUTOR_ID = bytes4(keccak256("StrategyExecutorID"));
/// Only callable by the executor
error SenderNotExecutorError(address, address);
modifier onlyExecutor {
address executorAddr = registry.getAddr(STRATEGY_EXECUTOR_ID);
if (msg.sender != executorAddr){
revert SenderNotExecutorError(msg.sender, executorAddr);
}
_;
}
}
interface IInstaAccount {
function isAuth(address _user) external view returns (bool);
function enable(address _user) external;
function disable(address _user) external;
function version() external view returns (uint256);
}
interface IInstaAccountV1 is IInstaAccount {
function cast(
address[] memory,
bytes[] memory,
address
) external payable;
}
interface IInstaAccountV2 is IInstaAccount {
function cast(
string[] memory,
bytes[] memory,
address
) external payable returns (bytes32);
function implementations() external view returns (address);
}
library DSAUtils {
/// @dev Used for DSA Proxy V2 Accounts
string public constant DEFISAVER_CONNECTOR_NAME = "DefiSaverConnector";
/// @dev Id of the DefiSaverConnector contract
bytes4 private constant DEFISAVER_CONNECTOR_ID = bytes4(keccak256("DefiSaverConnector"));
/// @dev Used for DSA Proxy Accounts versioning
uint256 private constant DSA_VERSION_1 = 1;
/// @notice Call the cast function of the DSA Proxy
/// @dev Handles both V1 and V2 versions of the DSA Proxy
/// @param _dsaProxy Address of the DSA Proxy
/// @param _dfsRegistry Address of the DFS Registry
/// @param _eventOrigin Address of the event origin
/// @param _data Call data
/// @param _value Value to send with the call
function cast(
address _dsaProxy,
address _dfsRegistry,
address _eventOrigin,
bytes memory _data,
uint256 _value
) internal {
// V1 and V2 versions have different interfaces, so we support both.
uint256 version = IInstaAccount(_dsaProxy).version();
// Init execution data
bytes[] memory connectorsData = new bytes[](1);
connectorsData[0] = _data;
// For V1 version, we are calling connector directly
if (version == DSA_VERSION_1) {
address[] memory targets = new address[](1);
targets[0] = IDFSRegistry(_dfsRegistry).getAddr(DEFISAVER_CONNECTOR_ID);
IInstaAccountV1(_dsaProxy).cast{ value: _value }(
targets,
connectorsData,
_eventOrigin
);
return;
}
// If not DSA_VERSION_1, we are working with V2 version
// We hardcode the connector to save gas from two external calls:
// 1. Fetching the connector address from the DFS Registry
// 2. Reading the connector name
string[] memory connectors = new string[](1);
connectors[0] = DEFISAVER_CONNECTOR_NAME;
IInstaAccountV2(_dsaProxy).cast{ value: _value }(
connectors,
connectorsData,
_eventOrigin
);
}
}
contract DSAAuth is WalletAuth {
/// @notice Calls the .cast() method of the specified users DSA Proxy
/// @dev Contract gets the authority from the user to call it
/// @dev Only callable by StrategyExecutor
/// @dev Only callable when not paused
/// @dev All calls will be forwarded to RecipeExecutor. See DefiSaverConnector.
/// @param _dsaProxyAddr Address of the users DSA Proxy
/// @param _callData Call data of the function to be called
function callExecute(
address _dsaProxyAddr,
address /* _contractAddr */,
bytes memory _callData
) public payable onlyExecutor notPaused {
DSAUtils.cast(
_dsaProxyAddr,
REGISTRY_ADDR,
msg.sender, // Only used for event logging, so here we will set it to the StrategyExecutor
_callData,
msg.value
);
}
}
Submitted on: 2025-10-24 20:47:46
Comments
Log in to comment.
No comments yet.