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 MainnetActionsUtilAddresses {
address internal constant DFS_REG_CONTROLLER_ADDR = 0xF8f8B3C98Cf2E63Df3041b73f80F362a4cf3A576;
address internal constant REGISTRY_ADDR = 0x287778F121F134C66212FB16c9b53eC991D32f5b;
address internal constant DFS_LOGGER_ADDR = 0xcE7a977Cac4a481bc84AC06b2Da0df614e621cf3;
address internal constant SUB_STORAGE_ADDR = 0x1612fc28Ee0AB882eC99842Cde0Fc77ff0691e90;
address internal constant PROXY_AUTH_ADDR = 0x149667b6FAe2c63D1B4317C716b0D0e4d3E2bD70;
address internal constant LSV_PROXY_REGISTRY_ADDRESS = 0xa8a3c86c4A2DcCf350E84D2b3c46BDeBc711C16e;
address internal constant TRANSIENT_STORAGE = 0x2F7Ef2ea5E8c97B8687CA703A0e50Aa5a49B7eb2;
address internal constant TRANSIENT_STORAGE_CANCUN = 0x0304E27cccE28bAB4d78C6cb7AfD4cd01c87c1e4;
}
contract ActionsUtilHelper is MainnetActionsUtilAddresses {
}
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;
}
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));
}
}
contract DFSRegistry is AdminAuth {
error EntryAlreadyExistsError(bytes4);
error EntryNonExistentError(bytes4);
error EntryNotInChangeError(bytes4);
error ChangeNotReadyError(uint256,uint256);
error EmptyPrevAddrError(bytes4);
error AlreadyInContractChangeError(bytes4);
error AlreadyInWaitPeriodChangeError(bytes4);
event AddNewContract(address,bytes4,address,uint256);
event RevertToPreviousAddress(address,bytes4,address,address);
event StartContractChange(address,bytes4,address,address);
event ApproveContractChange(address,bytes4,address,address);
event CancelContractChange(address,bytes4,address,address);
event StartWaitPeriodChange(address,bytes4,uint256);
event ApproveWaitPeriodChange(address,bytes4,uint256,uint256);
event CancelWaitPeriodChange(address,bytes4,uint256,uint256);
struct Entry {
address contractAddr;
uint256 waitPeriod;
uint256 changeStartTime;
bool inContractChange;
bool inWaitPeriodChange;
bool exists;
}
mapping(bytes4 => Entry) public entries;
mapping(bytes4 => address) public previousAddresses;
mapping(bytes4 => address) public pendingAddresses;
mapping(bytes4 => uint256) public pendingWaitTimes;
/// @notice Given an contract id returns the registered address
/// @dev Id is keccak256 of the contract name
/// @param _id Id of contract
function getAddr(bytes4 _id) public view returns (address) {
return entries[_id].contractAddr;
}
/// @notice Helper function to easily query if id is registered
/// @param _id Id of contract
function isRegistered(bytes4 _id) public view returns (bool) {
return entries[_id].exists;
}
/////////////////////////// OWNER ONLY FUNCTIONS ///////////////////////////
/// @notice Adds a new contract to the registry
/// @param _id Id of contract
/// @param _contractAddr Address of the contract
/// @param _waitPeriod Amount of time to wait before a contract address can be changed
function addNewContract(
bytes4 _id,
address _contractAddr,
uint256 _waitPeriod
) public onlyOwner {
if (entries[_id].exists){
revert EntryAlreadyExistsError(_id);
}
entries[_id] = Entry({
contractAddr: _contractAddr,
waitPeriod: _waitPeriod,
changeStartTime: 0,
inContractChange: false,
inWaitPeriodChange: false,
exists: true
});
emit AddNewContract(msg.sender, _id, _contractAddr, _waitPeriod);
}
/// @notice Reverts to the previous address immediately
/// @dev In case the new version has a fault, a quick way to fallback to the old contract
/// @param _id Id of contract
function revertToPreviousAddress(bytes4 _id) public onlyOwner {
if (!(entries[_id].exists)){
revert EntryNonExistentError(_id);
}
if (previousAddresses[_id] == address(0)){
revert EmptyPrevAddrError(_id);
}
address currentAddr = entries[_id].contractAddr;
entries[_id].contractAddr = previousAddresses[_id];
emit RevertToPreviousAddress(msg.sender, _id, currentAddr, previousAddresses[_id]);
}
/// @notice Starts an address change for an existing entry
/// @dev Can override a change that is currently in progress
/// @param _id Id of contract
/// @param _newContractAddr Address of the new contract
function startContractChange(bytes4 _id, address _newContractAddr) public onlyOwner {
if (!entries[_id].exists){
revert EntryNonExistentError(_id);
}
if (entries[_id].inWaitPeriodChange){
revert AlreadyInWaitPeriodChangeError(_id);
}
entries[_id].changeStartTime = block.timestamp; // solhint-disable-line
entries[_id].inContractChange = true;
pendingAddresses[_id] = _newContractAddr;
emit StartContractChange(msg.sender, _id, entries[_id].contractAddr, _newContractAddr);
}
/// @notice Changes new contract address, correct time must have passed
/// @param _id Id of contract
function approveContractChange(bytes4 _id) public onlyOwner {
if (!entries[_id].exists){
revert EntryNonExistentError(_id);
}
if (!entries[_id].inContractChange){
revert EntryNotInChangeError(_id);
}
if (block.timestamp < (entries[_id].changeStartTime + entries[_id].waitPeriod)){// solhint-disable-line
revert ChangeNotReadyError(block.timestamp, (entries[_id].changeStartTime + entries[_id].waitPeriod));
}
address oldContractAddr = entries[_id].contractAddr;
entries[_id].contractAddr = pendingAddresses[_id];
entries[_id].inContractChange = false;
entries[_id].changeStartTime = 0;
pendingAddresses[_id] = address(0);
previousAddresses[_id] = oldContractAddr;
emit ApproveContractChange(msg.sender, _id, oldContractAddr, entries[_id].contractAddr);
}
/// @notice Cancel pending change
/// @param _id Id of contract
function cancelContractChange(bytes4 _id) public onlyOwner {
if (!entries[_id].exists){
revert EntryNonExistentError(_id);
}
if (!entries[_id].inContractChange){
revert EntryNotInChangeError(_id);
}
address oldContractAddr = pendingAddresses[_id];
pendingAddresses[_id] = address(0);
entries[_id].inContractChange = false;
entries[_id].changeStartTime = 0;
emit CancelContractChange(msg.sender, _id, oldContractAddr, entries[_id].contractAddr);
}
/// @notice Starts the change for waitPeriod
/// @param _id Id of contract
/// @param _newWaitPeriod New wait time
function startWaitPeriodChange(bytes4 _id, uint256 _newWaitPeriod) public onlyOwner {
if (!entries[_id].exists){
revert EntryNonExistentError(_id);
}
if (entries[_id].inContractChange){
revert AlreadyInContractChangeError(_id);
}
pendingWaitTimes[_id] = _newWaitPeriod;
entries[_id].changeStartTime = block.timestamp; // solhint-disable-line
entries[_id].inWaitPeriodChange = true;
emit StartWaitPeriodChange(msg.sender, _id, _newWaitPeriod);
}
/// @notice Changes new wait period, correct time must have passed
/// @param _id Id of contract
function approveWaitPeriodChange(bytes4 _id) public onlyOwner {
if (!entries[_id].exists){
revert EntryNonExistentError(_id);
}
if (!entries[_id].inWaitPeriodChange){
revert EntryNotInChangeError(_id);
}
if (block.timestamp < (entries[_id].changeStartTime + entries[_id].waitPeriod)){ // solhint-disable-line
revert ChangeNotReadyError(block.timestamp, (entries[_id].changeStartTime + entries[_id].waitPeriod));
}
uint256 oldWaitTime = entries[_id].waitPeriod;
entries[_id].waitPeriod = pendingWaitTimes[_id];
entries[_id].inWaitPeriodChange = false;
entries[_id].changeStartTime = 0;
pendingWaitTimes[_id] = 0;
emit ApproveWaitPeriodChange(msg.sender, _id, oldWaitTime, entries[_id].waitPeriod);
}
/// @notice Cancel wait period change
/// @param _id Id of contract
function cancelWaitPeriodChange(bytes4 _id) public onlyOwner {
if (!entries[_id].exists){
revert EntryNonExistentError(_id);
}
if (!entries[_id].inWaitPeriodChange){
revert EntryNotInChangeError(_id);
}
uint256 oldWaitPeriod = pendingWaitTimes[_id];
pendingWaitTimes[_id] = 0;
entries[_id].inWaitPeriodChange = false;
entries[_id].changeStartTime = 0;
emit CancelWaitPeriodChange(msg.sender, _id, oldWaitPeriod, entries[_id].waitPeriod);
}
}
abstract contract DSAuthority {
function canCall(
address src,
address dst,
bytes4 sig
) public view virtual returns (bool);
}
contract DSAuthEvents {
event LogSetAuthority(address indexed authority);
event LogSetOwner(address indexed owner);
}
contract DSAuth is DSAuthEvents {
DSAuthority public authority;
address public owner;
constructor() {
owner = msg.sender;
emit LogSetOwner(msg.sender);
}
function setOwner(address owner_) public auth {
owner = owner_;
emit LogSetOwner(owner);
}
function setAuthority(DSAuthority authority_) public auth {
authority = authority_;
emit LogSetAuthority(address(authority));
}
modifier auth {
require(isAuthorized(msg.sender, msg.sig), "Not authorized");
_;
}
function isAuthorized(address src, bytes4 sig) internal view returns (bool) {
if (src == address(this)) {
return true;
} else if (src == owner) {
return true;
} else if (authority == DSAuthority(address(0))) {
return false;
} else {
return authority.canCall(src, address(this), sig);
}
}
}
contract DSNote {
event LogNote(
bytes4 indexed sig,
address indexed guy,
bytes32 indexed foo,
bytes32 indexed bar,
uint256 wad,
bytes fax
) anonymous;
modifier note {
bytes32 foo;
bytes32 bar;
assembly {
foo := calldataload(4)
bar := calldataload(36)
}
emit LogNote(msg.sig, msg.sender, foo, bar, msg.value, msg.data);
_;
}
}
abstract contract DSProxy is DSAuth, DSNote {
DSProxyCache public cache; // global cache for contracts
constructor(address _cacheAddr) {
if (!(setCache(_cacheAddr))){
require(isAuthorized(msg.sender, msg.sig), "Not authorized");
}
}
// solhint-disable-next-line no-empty-blocks
receive() external payable {}
// use the proxy to execute calldata _data on contract _code
function execute(bytes memory _code, bytes memory _data)
public
payable
virtual
returns (address target, bytes32 response);
function execute(address _target, bytes memory _data)
public
payable
virtual
returns (bytes32 response);
//set new cache
function setCache(address _cacheAddr) public payable virtual returns (bool);
}
contract DSProxyCache {
mapping(bytes32 => address) cache;
function read(bytes memory _code) public view returns (address) {
bytes32 hash = keccak256(_code);
return cache[hash];
}
function write(bytes memory _code) public returns (address target) {
assembly {
target := create(0, add(_code, 0x20), mload(_code))
switch iszero(extcodesize(target))
case 1 {
// throw if contract failed to deploy
revert(0, 0)
}
}
bytes32 hash = keccak256(_code);
cache[hash] = target;
}
}
interface ISafe {
enum Operation {
Call,
DelegateCall
}
function setup(
address[] calldata _owners,
uint256 _threshold,
address to,
bytes calldata data,
address fallbackHandler,
address paymentToken,
uint256 payment,
address payable paymentReceiver
) external;
function execTransaction(
address to,
uint256 value,
bytes calldata data,
Operation operation,
uint256 safeTxGas,
uint256 baseGas,
uint256 gasPrice,
address gasToken,
address payable refundReceiver,
bytes memory signatures
) external payable returns (bool success);
function execTransactionFromModule(
address to,
uint256 value,
bytes memory data,
Operation operation
) external returns (bool success);
function checkSignatures(
bytes32 dataHash,
bytes memory data,
bytes memory signatures
) external view;
function checkNSignatures(
address executor,
bytes32 dataHash,
bytes memory /* data */,
bytes memory signatures,
uint256 requiredSignatures
) external view;
function approveHash(bytes32 hashToApprove) external;
function domainSeparator() external view returns (bytes32);
function getTransactionHash(
address to,
uint256 value,
bytes calldata data,
Operation operation,
uint256 safeTxGas,
uint256 baseGas,
uint256 gasPrice,
address gasToken,
address refundReceiver,
uint256 _nonce
) external view returns (bytes32);
function nonce() external view returns (uint256);
function setFallbackHandler(address handler) external;
function getOwners() external view returns (address[] memory);
function isOwner(address owner) external view returns (bool);
function getThreshold() external view returns (uint256);
function enableModule(address module) external;
function isModuleEnabled(address module) external view returns (bool);
function disableModule(address prevModule, address module) external;
function getModulesPaginated(
address start,
uint256 pageSize
) external view returns (address[] memory array, address next);
}
interface IDSProxyFactory {
function isProxy(address _proxy) external view returns (bool);
}
contract MainnetProxyFactoryAddresses {
address internal constant PROXY_FACTORY_ADDR = 0xA26e15C895EFc0616177B7c1e7270A4C7D51C997;
}
contract DSProxyFactoryHelper is MainnetProxyFactoryAddresses {
}
contract CheckWalletType is DSProxyFactoryHelper {
function isDSProxy(address _proxy) public view returns (bool) {
return IDSProxyFactory(PROXY_FACTORY_ADDR).isProxy(_proxy);
}
}
contract DefisaverLogger {
event RecipeEvent(
address indexed caller,
string indexed logName
);
event ActionDirectEvent(
address indexed caller,
string indexed logName,
bytes data
);
function logRecipeEvent(
string memory _logName
) public {
emit RecipeEvent(msg.sender, _logName);
}
function logActionDirectEvent(
string memory _logName,
bytes memory _data
) public {
emit ActionDirectEvent(msg.sender, _logName, _data);
}
}
abstract contract ActionBase is AdminAuth, ActionsUtilHelper, CheckWalletType {
event ActionEvent(
string indexed logName,
bytes data
);
DFSRegistry public constant registry = DFSRegistry(REGISTRY_ADDR);
DefisaverLogger public constant logger = DefisaverLogger(
DFS_LOGGER_ADDR
);
//Wrong sub index value
error SubIndexValueError();
//Wrong return index value
error ReturnIndexValueError();
/// @dev Subscription params index range [128, 255]
uint8 public constant SUB_MIN_INDEX_VALUE = 128;
uint8 public constant SUB_MAX_INDEX_VALUE = 255;
/// @dev Return params index range [1, 127]
uint8 public constant RETURN_MIN_INDEX_VALUE = 1;
uint8 public constant RETURN_MAX_INDEX_VALUE = 127;
/// @dev If the input value should not be replaced
uint8 public constant NO_PARAM_MAPPING = 0;
/// @dev We need to parse Flash loan actions in a different way
enum ActionType { FL_ACTION, STANDARD_ACTION, FEE_ACTION, CHECK_ACTION, CUSTOM_ACTION }
/// @notice Parses inputs and runs the implemented action through a user wallet
/// @dev Is called by the RecipeExecutor chaining actions together
/// @param _callData Array of input values each value encoded as bytes
/// @param _subData Array of subscribed vales, replaces input values if specified
/// @param _paramMapping Array that specifies how return and subscribed values are mapped in input
/// @param _returnValues Returns values from actions before, which can be injected in inputs
/// @return Returns a bytes32 value through user wallet, each actions implements what that value is
function executeAction(
bytes memory _callData,
bytes32[] memory _subData,
uint8[] memory _paramMapping,
bytes32[] memory _returnValues
) public payable virtual returns (bytes32);
/// @notice Parses inputs and runs the single implemented action through a user wallet
/// @dev Used to save gas when executing a single action directly
function executeActionDirect(bytes memory _callData) public virtual payable;
/// @notice Returns the type of action we are implementing
function actionType() public pure virtual returns (uint8);
//////////////////////////// HELPER METHODS ////////////////////////////
/// @notice Given an uint256 input, injects return/sub values if specified
/// @param _param The original input value
/// @param _mapType Indicated the type of the input in paramMapping
/// @param _subData Array of subscription data we can replace the input value with
/// @param _returnValues Array of subscription data we can replace the input value with
function _parseParamUint(
uint _param,
uint8 _mapType,
bytes32[] memory _subData,
bytes32[] memory _returnValues
) internal pure returns (uint) {
if (isReplaceable(_mapType)) {
if (isReturnInjection(_mapType)) {
_param = uint(_returnValues[getReturnIndex(_mapType)]);
} else {
_param = uint256(_subData[getSubIndex(_mapType)]);
}
}
return _param;
}
/// @notice Given an addr input, injects return/sub values if specified
/// @param _param The original input value
/// @param _mapType Indicated the type of the input in paramMapping
/// @param _subData Array of subscription data we can replace the input value with
/// @param _returnValues Array of subscription data we can replace the input value with
function _parseParamAddr(
address _param,
uint8 _mapType,
bytes32[] memory _subData,
bytes32[] memory _returnValues
) internal view returns (address) {
if (isReplaceable(_mapType)) {
if (isReturnInjection(_mapType)) {
_param = address(bytes20((_returnValues[getReturnIndex(_mapType)])));
} else {
/// @dev The last two values are specially reserved for proxy addr and owner addr
if (_mapType == 254) return address(this); // wallet address
if (_mapType == 255) return fetchOwnersOrWallet(); // owner if 1/1 wallet or the wallet itself
_param = address(uint160(uint256(_subData[getSubIndex(_mapType)])));
}
}
return _param;
}
/// @notice Given an bytes32 input, injects return/sub values if specified
/// @param _param The original input value
/// @param _mapType Indicated the type of the input in paramMapping
/// @param _subData Array of subscription data we can replace the input value with
/// @param _returnValues Array of subscription data we can replace the input value with
function _parseParamABytes32(
bytes32 _param,
uint8 _mapType,
bytes32[] memory _subData,
bytes32[] memory _returnValues
) internal pure returns (bytes32) {
if (isReplaceable(_mapType)) {
if (isReturnInjection(_mapType)) {
_param = (_returnValues[getReturnIndex(_mapType)]);
} else {
_param = _subData[getSubIndex(_mapType)];
}
}
return _param;
}
/// @notice Checks if the paramMapping value indicated that we need to inject values
/// @param _type Indicated the type of the input
function isReplaceable(uint8 _type) internal pure returns (bool) {
return _type != NO_PARAM_MAPPING;
}
/// @notice Checks if the paramMapping value is in the return value range
/// @param _type Indicated the type of the input
function isReturnInjection(uint8 _type) internal pure returns (bool) {
return (_type >= RETURN_MIN_INDEX_VALUE) && (_type <= RETURN_MAX_INDEX_VALUE);
}
/// @notice Transforms the paramMapping value to the index in return array value
/// @param _type Indicated the type of the input
function getReturnIndex(uint8 _type) internal pure returns (uint8) {
if (!(isReturnInjection(_type))){
revert SubIndexValueError();
}
return (_type - RETURN_MIN_INDEX_VALUE);
}
/// @notice Transforms the paramMapping value to the index in sub array value
/// @param _type Indicated the type of the input
function getSubIndex(uint8 _type) internal pure returns (uint8) {
if (_type < SUB_MIN_INDEX_VALUE){
revert ReturnIndexValueError();
}
return (_type - SUB_MIN_INDEX_VALUE);
}
function fetchOwnersOrWallet() internal view returns (address) {
if (isDSProxy(address(this)))
return DSProxy(payable(address(this))).owner();
// if not DSProxy, we assume we are in context of Safe
address[] memory owners = ISafe(address(this)).getOwners();
return owners.length == 1 ? owners[0] : address(this);
}
}
contract MainnetMcdAddresses {
address internal constant POT_ADDR = 0x197E90f9FAD81970bA7976f33CbD77088E5D7cf7;
address internal constant VAT_ADDR = 0x35D1b3F3D7966A1DFe207aa4514C12a259A0492B;
address internal constant DAI_JOIN_ADDR = 0x9759A6Ac90977b93B58547b4A71c78317f391A28;
address internal constant JUG_ADDRESS = 0x19c0976f590D67707E62397C87829d896Dc0f1F1;
address internal constant SPOTTER_ADDRESS = 0x65C79fcB50Ca1594B025960e539eD7A9a6D434A3;
address internal constant PROXY_REGISTRY_ADDR = 0x4678f0a6958e4D2Bc4F1BAF7Bc52E8F3564f3fE4;
address internal constant CDP_REGISTRY = 0xBe0274664Ca7A68d6b5dF826FB3CcB7c620bADF3;
address internal constant CROPPER = 0x8377CD01a5834a6EaD3b7efb482f678f2092b77e;
address internal constant MCD_MANAGER_ADDR = 0x5ef30b9986345249bc32d8928B7ee64DE9435E39;
address internal constant DAI_ADDRESS = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
address internal constant MKR_ADDRESS = 0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2;
address internal constant SKY_ADDRESS = 0x56072C95FAA701256059aa122697B133aDEd9279;
address internal constant USDS_ADDRESS = 0xdC035D45d973E3EC169d2276DDab16f1e407384F;
address internal constant MRK_SKY_CONVERTER = 0xA1Ea1bA18E88C381C724a75F23a130420C403f9a;
address internal constant DAI_USDS_CONVERTER = 0x3225737a9Bbb6473CB4a45b7244ACa2BeFdB276A;
}
contract DSMath {
function add(uint256 x, uint256 y) internal pure returns (uint256 z) {
z = x + y;
}
function sub(uint256 x, uint256 y) internal pure returns (uint256 z) {
z = x - y;
}
function mul(uint256 x, uint256 y) internal pure returns (uint256 z) {
z = x * y;
}
function div(uint256 x, uint256 y) internal pure returns (uint256 z) {
return x / y;
}
function min(uint256 x, uint256 y) internal pure returns (uint256 z) {
return x <= y ? x : y;
}
function max(uint256 x, uint256 y) internal pure returns (uint256 z) {
return x >= y ? x : y;
}
function imin(int256 x, int256 y) internal pure returns (int256 z) {
return x <= y ? x : y;
}
function imax(int256 x, int256 y) internal pure returns (int256 z) {
return x >= y ? x : y;
}
uint256 constant WAD = 10**18;
uint256 constant RAY = 10**27;
function wmul(uint256 x, uint256 y) internal pure returns (uint256 z) {
z = add(mul(x, y), WAD / 2) / WAD;
}
function rmul(uint256 x, uint256 y) internal pure returns (uint256 z) {
z = add(mul(x, y), RAY / 2) / RAY;
}
function wdiv(uint256 x, uint256 y) internal pure returns (uint256 z) {
z = add(mul(x, WAD), y / 2) / y;
}
function rdiv(uint256 x, uint256 y) internal pure returns (uint256 z) {
z = add(mul(x, RAY), y / 2) / y;
}
// This famous algorithm is called "exponentiation by squaring"
// and calculates x^n with x as fixed-point and n as regular unsigned.
//
// It's O(log n), instead of O(n) for naive repeated multiplication.
//
// These facts are why it works:
//
// If n is even, then x^n = (x^2)^(n/2).
// If n is odd, then x^n = x * x^(n-1),
// and applying the equation for even x gives
// x^n = x * (x^2)^((n-1) / 2).
//
// Also, EVM division is flooring and
// floor[(n-1) / 2] = floor[n / 2].
//
function rpow(uint256 x, uint256 n) internal pure returns (uint256 z) {
z = n % 2 != 0 ? x : RAY;
for (n /= 2; n != 0; n /= 2) {
x = rmul(x, x);
if (n % 2 != 0) {
z = rmul(z, x);
}
}
}
}
abstract contract IGem {
function dec() virtual public returns (uint);
function gem() virtual public returns (IGem);
function join(address, uint) virtual public payable;
function exit(address, uint) virtual public;
function approve(address, uint) virtual public;
function transfer(address, uint) virtual public returns (bool);
function transferFrom(address, address, uint) virtual public returns (bool);
function deposit() virtual public payable;
function withdraw(uint) virtual public;
function allowance(address, address) virtual public returns (uint);
}
abstract contract IVat {
struct Urn {
uint256 ink; // Locked Collateral [wad]
uint256 art; // Normalised Debt [wad]
}
struct Ilk {
uint256 Art; // Total Normalised Debt [wad]
uint256 rate; // Accumulated Rates [ray]
uint256 spot; // Price with Safety Margin [ray]
uint256 line; // Debt Ceiling [rad]
uint256 dust; // Urn Debt Floor [rad]
}
mapping (bytes32 => mapping (address => Urn )) public urns;
mapping (bytes32 => Ilk) public ilks;
mapping (bytes32 => mapping (address => uint)) public gem; // [wad]
function can(address, address) virtual public view returns (uint);
function dai(address) virtual public view returns (uint);
function frob(bytes32, address, address, address, int, int) virtual public;
function hope(address) virtual public;
function nope(address) virtual public;
function move(address, address, uint) virtual public;
function fork(bytes32, address, address, int, int) virtual public;
}
abstract contract ICdpRegistry {
function open(
bytes32 ilk,
address usr
) public virtual returns (uint256);
function cdps(bytes32, address) virtual public view returns (uint256);
function owns(uint) virtual public view returns (address);
function ilks(uint) virtual public view returns (bytes32);
}
interface ICropper {
function proxy(address) view external returns (address);
function getOrCreateProxy(address) external returns (address);
function join(address, address, uint256) external;
function exit(address, address, uint256) external;
function flee(address, address, uint256) external;
function frob(bytes32, address, address, address, int256, int256) external;
function quit(bytes32, address, address) external;
}
abstract contract IJoin {
bytes32 public ilk;
function dec() virtual public view returns (uint);
function gem() virtual public view returns (IGem);
function join(address, uint) virtual public payable;
function exit(address, uint) virtual public;
}
abstract contract IManager {
function last(address) virtual public returns (uint);
function cdpCan(address, uint, address) virtual public view returns (uint);
function ilks(uint) virtual public view returns (bytes32);
function owns(uint) virtual public view returns (address);
function urns(uint) virtual public view returns (address);
function vat() virtual public view returns (address);
function open(bytes32, address) virtual public returns (uint);
function give(uint, address) virtual public;
function cdpAllow(uint, address, uint) virtual public;
function urnAllow(address, uint) virtual public;
function frob(uint, int, int) virtual public;
function flux(uint, address, uint) virtual public;
function move(uint, address, uint) virtual public;
function exit(address, uint, address, uint) virtual public;
function quit(uint, address) virtual public;
function enter(address, uint) virtual public;
function shift(uint, uint) virtual public;
}
abstract contract IWETH {
function allowance(address, address) public virtual view returns (uint256);
function balanceOf(address) public virtual view returns (uint256);
function approve(address, uint256) public virtual;
function transfer(address, uint256) public virtual returns (bool);
function transferFrom(
address,
address,
uint256
) public virtual returns (bool);
function deposit() public payable virtual;
function withdraw(uint256) public virtual;
}
library TokenUtils {
using SafeERC20 for IERC20;
address public constant WETH_ADDR = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
address public constant ETH_ADDR = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
/// @dev Only approves the amount if allowance is lower than amount, does not decrease allowance
function approveToken(
address _tokenAddr,
address _to,
uint256 _amount
) internal {
if (_tokenAddr == ETH_ADDR) return;
if (IERC20(_tokenAddr).allowance(address(this), _to) < _amount) {
IERC20(_tokenAddr).safeApprove(_to, _amount);
}
}
function pullTokensIfNeeded(
address _token,
address _from,
uint256 _amount
) internal returns (uint256) {
// handle max uint amount
if (_amount == type(uint256).max) {
_amount = getBalance(_token, _from);
}
if (_from != address(0) && _from != address(this) && _token != ETH_ADDR && _amount != 0) {
IERC20(_token).safeTransferFrom(_from, address(this), _amount);
}
return _amount;
}
function withdrawTokens(
address _token,
address _to,
uint256 _amount
) internal returns (uint256) {
if (_amount == type(uint256).max) {
_amount = getBalance(_token, address(this));
}
if (_to != address(0) && _to != address(this) && _amount != 0) {
if (_token != ETH_ADDR) {
IERC20(_token).safeTransfer(_to, _amount);
} else {
(bool success, ) = _to.call{value: _amount}("");
require(success, "Eth send fail");
}
}
return _amount;
}
function depositWeth(uint256 _amount) internal {
IWETH(WETH_ADDR).deposit{value: _amount}();
}
function withdrawWeth(uint256 _amount) internal {
IWETH(WETH_ADDR).withdraw(_amount);
}
function getBalance(address _tokenAddr, address _acc) internal view returns (uint256) {
if (_tokenAddr == ETH_ADDR) {
return _acc.balance;
} else {
return IERC20(_tokenAddr).balanceOf(_acc);
}
}
function getTokenDecimals(address _token) internal view returns (uint256) {
if (_token == ETH_ADDR) return 18;
return IERC20(_token).decimals();
}
}
contract McdHelper is DSMath, MainnetMcdAddresses {
IVat public constant vat = IVat(VAT_ADDR);
error IntOverflow();
/// @notice Returns a normalized debt _amount based on the current rate
/// @param _amount Amount of dai to be normalized
/// @param _rate Current rate of the stability fee
/// @param _daiVatBalance Balance od Dai in the Vat for that CDP
function normalizeDrawAmount(uint _amount, uint _rate, uint _daiVatBalance) internal pure returns (int dart) {
if (_daiVatBalance < _amount * RAY) {
dart = toPositiveInt((_amount * RAY - _daiVatBalance) / _rate);
dart = uint(dart) * _rate < _amount * RAY ? dart + 1 : dart;
}
}
/// @notice Converts a number to Rad precision
/// @param _wad The input number in wad precision
function toRad(uint _wad) internal pure returns (uint) {
return _wad * (10 ** 27);
}
/// @notice Converts a number to 18 decimal precision
/// @dev If token decimal is bigger than 18, function reverts
/// @param _joinAddr Join address of the collateral
/// @param _amount Number to be converted
function convertTo18(address _joinAddr, uint256 _amount) internal view returns (uint256) {
return _amount * (10 ** (18 - IJoin(_joinAddr).dec()));
}
/// @notice Converts a uint to int and checks if positive
/// @param _x Number to be converted
function toPositiveInt(uint _x) internal pure returns (int y) {
y = int(_x);
if (y < 0){
revert IntOverflow();
}
}
/// @notice Gets Dai amount in Vat which can be added to Cdp
/// @param _vat Address of Vat contract
/// @param _daiBalance Amount of dai in vat contract for that urn
/// @param _urn Urn of the Cdp
/// @param _ilk Ilk of the Cdp
function normalizePaybackAmount(address _vat, uint256 _daiBalance, address _urn, bytes32 _ilk) internal view returns (int amount) {
(, uint rate,,,) = IVat(_vat).ilks(_ilk);
(, uint art) = IVat(_vat).urns(_ilk, _urn);
amount = toPositiveInt(_daiBalance / rate);
amount = uint(amount) <= art ? - amount : - toPositiveInt(art);
}
/// @notice Gets the whole debt of the CDP
/// @param _vat Address of Vat contract
/// @param _usr Address of the Dai holder
/// @param _urn Urn of the Cdp
/// @param _ilk Ilk of the Cdp
function getAllDebt(address _vat, address _usr, address _urn, bytes32 _ilk) internal view returns (uint daiAmount) {
(, uint rate,,,) = IVat(_vat).ilks(_ilk);
(, uint art) = IVat(_vat).urns(_ilk, _urn);
uint dai = IVat(_vat).dai(_usr);
uint rad = art * rate - dai;
daiAmount = rad / RAY;
// handles precision error (off by 1 wei)
daiAmount = daiAmount * RAY < rad ? daiAmount + 1 : daiAmount;
}
/// @notice Checks if the join address is one of the Ether coll. types
/// @param _joinAddr Join address to check
function isEthJoinAddr(address _joinAddr) internal view returns (bool) {
// if it's dai_join_addr don't check gem() it will fail
if (_joinAddr == DAI_JOIN_ADDR) return false;
// if coll is weth it's and eth type coll
if (address(IJoin(_joinAddr).gem()) == TokenUtils.WETH_ADDR) {
return true;
}
return false;
}
/// @notice Returns the underlying token address from the joinAddr
/// @dev For eth based collateral returns 0xEee... not weth addr
/// @param _joinAddr Join address to check
function getTokenFromJoin(address _joinAddr) internal view returns (address) {
// if it's dai_join_addr don't check gem() it will fail, return dai addr
if (_joinAddr == DAI_JOIN_ADDR) {
return DAI_ADDRESS;
}
return address(IJoin(_joinAddr).gem());
}
function getUrnAndIlk(address _mcdManager, uint256 _vaultId) public view returns (address urn, bytes32 ilk) {
if (_mcdManager == CROPPER) {
address owner = ICdpRegistry(CDP_REGISTRY).owns(_vaultId);
urn = ICropper(CROPPER).proxy(owner);
ilk = ICdpRegistry(CDP_REGISTRY).ilks(_vaultId);
} else {
urn = IManager(_mcdManager).urns(_vaultId);
ilk = IManager(_mcdManager).ilks(_vaultId);
}
}
/// @notice Gets CDP info (collateral, debt)
/// @param _manager Manager contract
/// @param _cdpId Id of the CDP
/// @param _ilk Ilk of the CDP
function getCdpInfo(IManager _manager, uint _cdpId, bytes32 _ilk) public view returns (uint, uint) {
address urn;
if (address(_manager) == CROPPER) {
address owner = ICdpRegistry(CDP_REGISTRY).owns(_cdpId);
urn = ICropper(CROPPER).proxy(owner);
} else {
urn = _manager.urns(_cdpId);
}
(uint collateral, uint debt) = vat.urns(_ilk, urn);
(,uint rate,,,) = vat.ilks(_ilk);
return (collateral, rmul(debt, rate));
}
/// @notice Returns all the collateral of the vault, formatted in the correct decimal
/// @dev Will fail if token is over 18 decimals
function getAllColl(IManager _mcdManager, address _joinAddr, uint _vaultId) internal view returns (uint amount) {
bytes32 ilk;
if (address(_mcdManager) == CROPPER) {
ilk = ICdpRegistry(CDP_REGISTRY).ilks(_vaultId);
} else {
ilk = _mcdManager.ilks(_vaultId);
}
(amount, ) = getCdpInfo(
_mcdManager,
_vaultId,
ilk
);
if (IJoin(_joinAddr).dec() != 18) {
return div(amount, 10 ** sub(18, IJoin(_joinAddr).dec()));
}
}
}
interface IDaiUSDSConverter {
function usdsToDai(address usr, uint256 wad) external;
function daiToUsds(address usr, uint256 wad) external;
}
interface IMkrSkyConverter {
function skyToMkr(address usr, uint256 skyAmt) external;
function mkrToSky(address usr, uint256 mkrAmt) external;
function rate() external returns (uint256);
function fee() external returns (uint256);
}
contract McdTokenConverter is ActionBase, McdHelper {
using TokenUtils for address;
/// @param tokenAddr Address of the token to convert
/// @param from Address where to pull the tokens from
/// @param to Address that will receive the converted tokens
/// @param amount Amount of tokens to convert
struct Params {
address tokenAddr;
address from;
address to;
uint256 amount;
}
/// @inheritdoc ActionBase
function executeAction(
bytes memory _callData,
bytes32[] memory _subData,
uint8[] memory _paramMapping,
bytes32[] memory _returnValues
) public payable virtual override returns (bytes32) {
Params memory inputData = parseInputs(_callData);
inputData.tokenAddr = _parseParamAddr(inputData.tokenAddr, _paramMapping[0], _subData, _returnValues);
inputData.from = _parseParamAddr(inputData.from, _paramMapping[1], _subData, _returnValues);
inputData.to = _parseParamAddr(inputData.to, _paramMapping[2], _subData, _returnValues);
inputData.amount = _parseParamUint(inputData.amount, _paramMapping[3], _subData, _returnValues);
(uint256 newTokenAmount, bytes memory logData) = _mcdConvert(inputData);
emit ActionEvent("McdTokenConverter", logData);
return bytes32(newTokenAmount);
}
/// @inheritdoc ActionBase
function executeActionDirect(bytes memory _callData) public payable override {
Params memory inputData = parseInputs(_callData);
(, bytes memory logData) = _mcdConvert(inputData);
logger.logActionDirectEvent("McdTokenConverter", logData);
}
/// @inheritdoc ActionBase
function actionType() public pure virtual override returns (uint8) {
return uint8(ActionType.STANDARD_ACTION);
}
//////////////////////////// ACTION LOGIC ////////////////////////////
function _mcdConvert(Params memory _inputData) internal returns (uint256, bytes memory) {
_inputData.amount = _inputData.tokenAddr.pullTokensIfNeeded(_inputData.from, _inputData.amount);
uint256 newTokenAmount;
if (_inputData.tokenAddr == USDS_ADDRESS) {
USDS_ADDRESS.approveToken(DAI_USDS_CONVERTER, _inputData.amount);
IDaiUSDSConverter(DAI_USDS_CONVERTER).usdsToDai(_inputData.to, _inputData.amount);
newTokenAmount = _inputData.amount;
} else if (_inputData.tokenAddr == DAI_ADDRESS) {
DAI_ADDRESS.approveToken(DAI_USDS_CONVERTER, _inputData.amount);
IDaiUSDSConverter(DAI_USDS_CONVERTER).daiToUsds(_inputData.to, _inputData.amount);
newTokenAmount = _inputData.amount;
} else if (_inputData.tokenAddr == MKR_ADDRESS) {
MKR_ADDRESS.approveToken(MRK_SKY_CONVERTER, _inputData.amount);
IMkrSkyConverter(MRK_SKY_CONVERTER).mkrToSky(_inputData.to, _inputData.amount);
uint256 skyAmount = _inputData.amount * IMkrSkyConverter(MRK_SKY_CONVERTER).rate();
uint256 skyFee = skyAmount * IMkrSkyConverter(MRK_SKY_CONVERTER).fee() / WAD;
newTokenAmount = skyAmount - skyFee;
}
return (newTokenAmount, abi.encode(_inputData, newTokenAmount));
}
function parseInputs(bytes memory _callData) public pure returns (Params memory params) {
params = abi.decode(_callData, (Params));
}
}
Submitted on: 2025-09-24 16:05:47
Comments
Log in to comment.
No comments yet.