Cryo Explorer Ethereum Mainnet

Address Contract Verified

Address 0x536384FCd25b576265B6775F383D5ac408FF9dB7
Balance 0 ETH
Nonce 33212
Code Size 6357 bytes
Indexed Transactions 0
External Etherscan · Sourcify

Contract Bytecode

6357 bytes
0x608060405234801561001057600080fd5b50600436106100d45760003560e01c80638da5cb5b11610081578063d89784fc1161005b578063d89784fc146101b5578063fbe2ec7d146101dc578063fdff9b4d146101ef576100d4565b80638da5cb5b1461017c578063a6f9dae11461018f578063a80dd9df146101a2576100d4565b80632d06177a116100b25780632d06177a1461012f578063377e32e6146101425780638117abc114610155576100d4565b80630cb61f6c146100d957806319ab453c146101095780632c8285251461011c575b600080fd5b6002546100ec906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b61011a6101173660046112a1565b50565b005b61011a61012a3660046112a1565b610222565b61011a61013d3660046112a1565b610328565b61011a6101503660046112a1565b610437565b6100ec7f000000000000000000000000ab00ea153c43575184ff11dd5e713c96be00557381565b6000546100ec906001600160a01b031681565b61011a61019d3660046112a1565b61047f565b6100ec6101b036600461133b565b610574565b6100ec7f00000000000000000000000044da3a8051ba88eab0440db3779cab9d679ae76f81565b6100ec6101ea3660046112c4565b610790565b6102126101fd3660046112a1565b60016020526000908152604090205460ff1681565b6040519015158152602001610100565b6000546001600160a01b031633146102715760405162461bcd60e51b815260206004820152600d60248201526c26bab9ba1031329037bbb732b960991b60448201526064015b60405180910390fd5b6001600160a01b0381166102c75760405162461bcd60e51b815260206004820152601760248201527f57463a2063616e6e6f742073657420746f20656d7074790000000000000000006044820152606401610268565b6002805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0383169081179091556040519081527fa7cb165192538768851363c5aa55b1ade75d692a51063730feccdd57d002a6ed9060200160405180910390a150565b6000546001600160a01b031633146103725760405162461bcd60e51b815260206004820152600d60248201526c26bab9ba1031329037bbb732b960991b6044820152606401610268565b6001600160a01b0381166103c85760405162461bcd60e51b815260206004820152601b60248201527f4d3a2041646472657373206d757374206e6f74206265206e756c6c00000000006044820152606401610268565b6001600160a01b03811660009081526001602052604090205460ff16610117576001600160a01b0381166000818152600160208190526040808320805460ff1916909217909155517f3b4a40cccf2058c593542587329dd385be4f0b588db5471fbd9598e56dd7093a9190a250565b60405162461bcd60e51b815260206004820152601d60248201527f57463a206d616e61676572732063616e2774206265207265766f6b65640000006044820152606401610268565b6000546001600160a01b031633146104c95760405162461bcd60e51b815260206004820152600d60248201526c26bab9ba1031329037bbb732b960991b6044820152606401610268565b6001600160a01b03811661051f5760405162461bcd60e51b815260206004820152601860248201527f41646472657373206d757374206e6f74206265206e756c6c00000000000000006044820152606401610268565b6000805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b038316908117825560405190917fa2ea9883a321a3e97b8266c2b078bfeec6d50c711ed71f874a90d500ae2eaf3691a250565b60006105828c8c8c8c610898565b6000610591898e8e8e8e6109f9565b90506000817f000000000000000000000000ab00ea153c43575184ff11dd5e713c96be0055736040516105c3906111dd565b6001600160a01b0390911681526020018190604051809103906000f59050801580156105f3573d6000803e3d6000fd5b5090506106368186868080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610a6692505050565b610643818f8f8f8f610b4b565b6000891180156106535750604186145b1561069b5761069b818f8b8b8b8b8080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610d9e92505050565b6040517f1f17732d000000000000000000000000000000000000000000000000000000008152306004820152600060248201526001600160a01b03821690631f17732d90604401600060405180830381600087803b1580156106fc57600080fd5b505af1158015610710573d6000803e3d6000fd5b505050508a6001600160a01b03168e6001600160a01b0316826001600160a01b03167facd43e061b8ee386537b57919358cfa44933c4e47ccd4b2e1916e54133cec7488b8d6040516107779291906001600160a01b03929092168252602082015260400190565b60405180910390a49d9c50505050505050505050505050565b600061079e86868686610898565b60006107ad83888888886109f9565b90506000604051806020016107c1906111dd565b601f1982820381018352601f90910116604081905261080e91906001600160a01b037f000000000000000000000000ab00ea153c43575184ff11dd5e713c96be0055731690602001611565565b60408051808303601f1901815282825280516020918201207fff00000000000000000000000000000000000000000000000000000000000000828501523060601b6bffffffffffffffffffffffff19166021850152603584019590955260558084019590955281518084039095018552607590920190528251920191909120979650505050505050565b6001600160a01b0384166108ee5760405162461bcd60e51b815260206004820152601760248201527f57463a20656d707479206f776e657220616464726573730000000000000000006044820152606401610268565b806001600160a01b0316846001600160a01b031614156109505760405162461bcd60e51b815260206004820152601c60248201527f57463a206f776e65722063616e6e6f7420626520677561726469616e000000006044820152606401610268565b8161099d5760405162461bcd60e51b815260206004820152601160248201527f57463a20656d707479206d6f64756c65730000000000000000000000000000006044820152606401610268565b6001600160a01b0381166109f35760405162461bcd60e51b815260206004820152601260248201527f57463a20656d70747920677561726469616e00000000000000000000000000006044820152606401610268565b50505050565b600084848484604051602001610a1294939291906114e2565b60408051601f198184030181528282528051602091820120908301526bffffffffffffffffffffffff1988169082015260540160405160208183030381529060405280519060200120905095945050505050565b60008151604114610a78575033610ade565b6040517f19457468657265756d205369676e6564204d6573736167653a0a33320000000060208201526001600160a01b038416603c820152600090605c01604051602081830303815290604052805190602001209050610ada81846000610f9c565b9150505b6001600160a01b03811660009081526001602052604090205460ff16610b465760405162461bcd60e51b815260206004820181905260248201527f57463a20756e617574686f72697365642077616c6c6574206372656174696f6e6044820152606401610268565b505050565b6000610b5883600161162b565b67ffffffffffffffff811115610b7e57634e487b7160e01b600052604160045260246000fd5b604051908082528060200260200182016040528015610ba7578160200160208202803683370190505b5090503081600081518110610bcc57634e487b7160e01b600052603260045260246000fd5b60200260200101906001600160a01b031690816001600160a01b03168152505060005b83811015610c8057848482818110610c1757634e487b7160e01b600052603260045260246000fd5b9050602002016020810190610c2c91906112a1565b82610c3883600161162b565b81518110610c5657634e487b7160e01b600052603260045260246000fd5b6001600160a01b039092166020928302919091019091015280610c788161166f565b915050610bef565b506040517f3c5a3cea0000000000000000000000000000000000000000000000000000000081526001600160a01b03871690633c5a3cea90610cc89088908590600401611587565b600060405180830381600087803b158015610ce257600080fd5b505af1158015610cf6573d6000803e3d6000fd5b50506040517fc68452100000000000000000000000000000000000000000000000000000000081526001600160a01b03898116600483015285811660248301527f00000000000000000000000044da3a8051ba88eab0440db3779cab9d679ae76f16925063c68452109150604401600060405180830381600087803b158015610d7e57600080fd5b505af1158015610d92573d6000803e3d6000fd5b50505050505050505050565b6040516bffffffffffffffffffffffff19606087811b821660208401526034830186905284901b16605482015260009060680160408051601f198184030181529082905280516020918201207f19457468657265756d205369676e6564204d6573736167653a0a33320000000091830191909152603c820152605c016040516020818303038152906040528051906020012090506000610e4082846000610f9c565b9050856001600160a01b0316816001600160a01b03161415610f93576001600160a01b038416610e9b57600254604080516020810190915260008152610e959189916001600160a01b039091169088906110dc565b50610f93565b6002546040516001600160a01b0390911660248201526044810186905260009060640160408051601f198184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb0000000000000000000000000000000000000000000000000000000017905290506000610f26898783856110dc565b805190915015610f905780806020019051810190610f449190611424565b610f905760405162461bcd60e51b815260206004820152601a60248201527f57463a20526566756e64207472616e73666572206661696c65640000000000006044820152606401610268565b50505b50505050505050565b6041808202830160208101516040820151919092015160009260ff9190911691601b831480610fce57508260ff16601c145b61101a5760405162461bcd60e51b815260206004820152601f60248201527f5574696c733a2062616420762076616c756520696e207369676e6174757265006044820152606401610268565b604080516000808252602082018084528a905260ff861692820192909252606081018490526080810183905260019060a0016020604051602081039080840390855afa15801561106e573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b0381166110d15760405162461bcd60e51b815260206004820152601b60248201527f5574696c733a2065637265636f7665722072657475726e6564203000000000006044820152606401610268565b979650505050505050565b60606000856001600160a01b03168585856040516024016110ff939291906115e1565b60408051601f198184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f8f6f033200000000000000000000000000000000000000000000000000000000179052516111629190611549565b6000604051808303816000865af19150503d806000811461119f576040519150601f19603f3d011682016040523d82523d6000602084013e6111a4565b606091505b509250905080156111ca57818060200190518101906111c39190611444565b91506111d4565b3d6000803e3d6000fd5b50949350505050565b6101d4806116cc83390190565b80356111f5816116b6565b919050565b60008083601f84011261120b578081fd5b50813567ffffffffffffffff811115611222578182fd5b6020830191508360208260051b850101111561123d57600080fd5b9250929050565b80356bffffffffffffffffffffffff19811681146111f557600080fd5b60008083601f840112611272578182fd5b50813567ffffffffffffffff811115611289578182fd5b60208301915083602082850101111561123d57600080fd5b6000602082840312156112b2578081fd5b81356112bd816116b6565b9392505050565b6000806000806000608086880312156112db578081fd5b85356112e6816116b6565b9450602086013567ffffffffffffffff811115611301578182fd5b61130d888289016111fa565b9095509350506040860135611321816116b6565b915061132f60608701611244565b90509295509295909350565b60008060008060008060008060008060006101008c8e03121561135c578586fd5b6113658c6111ea565b9a5067ffffffffffffffff8060208e01351115611380578687fd5b6113908e60208f01358f016111fa565b909b5099506113a160408e016111ea565b98506113af60608e01611244565b975060808d013596506113c460a08e016111ea565b95508060c08e013511156113d6578485fd5b6113e68e60c08f01358f01611261565b909550935060e08d01358110156113fb578283fd5b5061140c8d60e08e01358e01611261565b81935080925050509295989b509295989b9093969950565b600060208284031215611435578081fd5b815180151581146112bd578182fd5b600060208284031215611455578081fd5b815167ffffffffffffffff8082111561146c578283fd5b818401915084601f83011261147f578283fd5b815181811115611491576114916116a0565b604051601f8201601f19908116603f011681019083821181831017156114b9576114b96116a0565b816040528281528760208487010111156114d1578586fd5b6110d1836020830160208801611643565b60006bffffffffffffffffffffffff19808760601b1683526014830186835b87811015611532578135611514816116b6565b6001600160a01b031683526020928301929190910190600101611501565b505060609490941b16835250506014019392505050565b6000825161155b818460208701611643565b9190910192915050565b60008351611577818460208801611643565b9190910191825250602001919050565b6000604082016001600160a01b03808616845260206040818601528286518085526060870191508288019450855b818110156115d35785518516835294830194918301916001016115b5565b509098975050505050505050565b60006001600160a01b0385168252836020830152606060408301528251806060840152611615816080850160208701611643565b601f01601f191691909101608001949350505050565b6000821982111561163e5761163e61168a565b500190565b60005b8381101561165e578181015183820152602001611646565b838111156109f35750506000910152565b60006000198214156116835761168361168a565b5060010190565b634e487b7160e01b600052601160045260246000fd5b634e487b7160e01b600052604160045260246000fd5b6001600160a01b038116811461011757600080fdfe60a060405234801561001057600080fd5b506040516101d43803806101d483398101604081905261002f91610044565b60601b6001600160601b031916608052610072565b600060208284031215610055578081fd5b81516001600160a01b038116811461006b578182fd5b9392505050565b60805160601c61013f610095600039600081816069015260be015261013f6000f3fe6080604052600436106100225760003560e01c80635c60da1b146100ac57610067565b3661006757604080516020808252600090820152339134917f606834f57405380c4fb88d1f4850326ad3885f014bab3b568dfbf7a041eef738910160405180910390a3005b7f00000000000000000000000000000000000000000000000000000000000000003660008037600080366000845af43d6000803e8080156100a7573d6000f35b3d6000fd5b3480156100b857600080fd5b506100e07f000000000000000000000000000000000000000000000000000000000000000081565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200160405180910390f3fea2646970667358221220b88a14f52e9d465328c9b3ab476e4b7fa40ed3615fd5409a6afc9885366e03a964736f6c63430008030033a2646970667358221220dec5ae87a38d164692a0d9f7a489556203efa01d593e3bbffea1447231dc89d664736f6c63430008030033

Verified Source Code Full Match

Compiler: v0.8.3+commit.8d00100c EVM: istanbul Optimization: Yes (999 runs)
Proxy.sol 51 lines
// Copyright (C) 2018  Argent Labs Ltd. <https://argent.xyz>

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.3;

/**
 * @title Proxy
 * @notice Basic proxy that delegates all calls to a fixed implementing contract.
 * The implementing contract cannot be upgraded.
 * @author Julien Niset - <[email protected]>
 */
contract Proxy {

    address immutable public implementation;

    event Received(uint indexed value, address indexed sender, bytes data);

    constructor(address _implementation) {
        implementation = _implementation;
    }

    fallback() external payable {
        address target = implementation;
        // solhint-disable-next-line no-inline-assembly
        assembly {
            calldatacopy(0, 0, calldatasize())
            let result := delegatecall(gas(), target, 0, calldatasize(), 0, 0)
            returndatacopy(0, 0, returndatasize())
            switch result
            case 0 {revert(0, returndatasize())}
            default {return (0, returndatasize())}
        }
    }

    receive() external payable {
        emit Received(msg.value, msg.sender, "");
    }
}
IWallet.sol 69 lines
// Copyright (C) 2018  Argent Labs Ltd. <https://argent.xyz>

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.5.4 <0.9.0;

/**
 * @title IWallet
 * @notice Interface for the BaseWallet
 */
interface IWallet {
    /**
     * @notice Returns the wallet owner.
     * @return The wallet owner address.
     */
    function owner() external view returns (address);

    /**
     * @notice Returns the number of authorised modules.
     * @return The number of authorised modules.
     */
    function modules() external view returns (uint);

    /**
     * @notice Sets a new owner for the wallet.
     * @param _newOwner The new owner.
     */
    function setOwner(address _newOwner) external;

    /**
     * @notice Checks if a module is authorised on the wallet.
     * @param _module The module address to check.
     * @return `true` if the module is authorised, otherwise `false`.
     */
    function authorised(address _module) external view returns (bool);

    /**
     * @notice Returns the module responsible for a static call redirection.
     * @param _sig The signature of the static call.
     * @return the module doing the redirection
     */
    function enabled(bytes4 _sig) external view returns (address);

    /**
     * @notice Enables/Disables a module.
     * @param _module The target module.
     * @param _value Set to `true` to authorise the module.
     */
    function authoriseModule(address _module, bool _value) external;

    /**
    * @notice Enables a static method by specifying the target module to which the call must be delegated.
    * @param _module The target module.
    * @param _method The static method signature.
    */
    function enableStaticCall(address _module, bytes4 _method) external;
}
BaseWallet.sol 164 lines
// Copyright (C) 2018  Argent Labs Ltd. <https://argent.xyz>

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.3;

import "../modules/common/IModule.sol";
import "./IWallet.sol";

/**
 * @title BaseWallet
 * @notice Simple modular wallet that authorises modules to call its invoke() method.
 * @author Julien Niset - <[email protected]>
 */
contract BaseWallet is IWallet {

    // The owner
    address public override owner;
    // The authorised modules
    mapping (address => bool) public override authorised;
    // module executing static calls
    address public staticCallExecutor;
    // The number of modules
    uint public override modules;

    event AuthorisedModule(address indexed module, bool value);
    event Invoked(address indexed module, address indexed target, uint indexed value, bytes data);
    event Received(uint indexed value, address indexed sender, bytes data);
    event OwnerChanged(address owner);

    /**
     * @notice Throws if the sender is not an authorised module.
     */
    modifier moduleOnly {
        require(authorised[msg.sender], "BW: sender not authorized");
        _;
    }

    /**
     * @notice Inits the wallet by setting the owner and authorising a list of modules.
     * @param _owner The owner.
     * @param _modules The modules to authorise.
     */
    function init(address _owner, address[] calldata _modules) external {
        require(owner == address(0) && modules == 0, "BW: wallet already initialised");
        require(_modules.length > 0, "BW: empty modules");
        owner = _owner;
        modules = _modules.length;
        for (uint256 i = 0; i < _modules.length; i++) {
            require(authorised[_modules[i]] == false, "BW: module is already added");
            authorised[_modules[i]] = true;
            IModule(_modules[i]).init(address(this));
            emit AuthorisedModule(_modules[i], true);
        }
        if (address(this).balance > 0) {
            emit Received(address(this).balance, address(0), "");
        }
    }

    /**
     * @inheritdoc IWallet
     */
    function authoriseModule(address _module, bool _value) external override moduleOnly {
        if (authorised[_module] != _value) {
            emit AuthorisedModule(_module, _value);
            if (_value == true) {
                modules += 1;
                authorised[_module] = true;
                IModule(_module).init(address(this));
            } else {
                modules -= 1;
                require(modules > 0, "BW: cannot remove last module");
                delete authorised[_module];
            }
        }
    }

    /**
    * @inheritdoc IWallet
    */
    function enabled(bytes4 _sig) public view override returns (address) {
        address executor = staticCallExecutor;
        if(executor != address(0) && IModule(executor).supportsStaticCall(_sig)) {
            return executor;
        }
        return address(0);
    }

    /**
    * @inheritdoc IWallet
    */
    function enableStaticCall(address _module, bytes4 /* _method */) external override moduleOnly {
        if(staticCallExecutor != _module) {
            require(authorised[_module], "BW: unauthorized executor");
            staticCallExecutor = _module;
        }
    }

    /**
     * @inheritdoc IWallet
     */
    function setOwner(address _newOwner) external override moduleOnly {
        require(_newOwner != address(0), "BW: address cannot be null");
        owner = _newOwner;
        emit OwnerChanged(_newOwner);
    }

    /**
     * @notice Performs a generic transaction.
     * @param _target The address for the transaction.
     * @param _value The value of the transaction.
     * @param _data The data of the transaction.
     */
    function invoke(address _target, uint _value, bytes calldata _data) external moduleOnly returns (bytes memory _result) {
        bool success;
        (success, _result) = _target.call{value: _value}(_data);
        if (!success) {
            // solhint-disable-next-line no-inline-assembly
            assembly {
                returndatacopy(0, 0, returndatasize())
                revert(0, returndatasize())
            }
        }
        emit Invoked(msg.sender, _target, _value, _data);
    }

    /**
     * @notice This method delegates the static call to a target contract if the data corresponds
     * to an enabled module, or logs the call otherwise.
     */
    fallback() external payable {
        address module = enabled(msg.sig);
        if (module == address(0)) {
            emit Received(msg.value, msg.sender, msg.data);
        } else {
            require(authorised[module], "BW: unauthorised module");

            // solhint-disable-next-line no-inline-assembly
            assembly {
                calldatacopy(0, 0, calldatasize())
                let result := staticcall(gas(), module, 0, calldatasize(), 0, 0)
                returndatacopy(0, 0, returndatasize())
                switch result
                case 0 {revert(0, returndatasize())}
                default {return (0, returndatasize())}
            }
        }
    }

    receive() external payable {
    }
}
Utils.sol 194 lines
// Copyright (C) 2020  Argent Labs Ltd. <https://argent.xyz>

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.3;

/**
 * @title Utils
 * @notice Common utility methods used by modules.
 */
library Utils {

    // ERC20, ERC721 & ERC1155 transfers & approvals
    bytes4 private constant ERC20_TRANSFER = bytes4(keccak256("transfer(address,uint256)"));
    bytes4 private constant ERC20_APPROVE = bytes4(keccak256("approve(address,uint256)"));
    bytes4 private constant ERC721_SET_APPROVAL_FOR_ALL = bytes4(keccak256("setApprovalForAll(address,bool)"));
    bytes4 private constant ERC721_TRANSFER_FROM = bytes4(keccak256("transferFrom(address,address,uint256)"));
    bytes4 private constant ERC721_SAFE_TRANSFER_FROM = bytes4(keccak256("safeTransferFrom(address,address,uint256)"));
    bytes4 private constant ERC721_SAFE_TRANSFER_FROM_BYTES = bytes4(keccak256("safeTransferFrom(address,address,uint256,bytes)"));
    bytes4 private constant ERC1155_SAFE_TRANSFER_FROM = bytes4(keccak256("safeTransferFrom(address,address,uint256,uint256,bytes)"));

    bytes4 private constant OWNER_SIG = 0x8da5cb5b;
    /**
    * @notice Helper method to recover the signer at a given position from a list of concatenated signatures.
    * @param _signedHash The signed hash
    * @param _signatures The concatenated signatures.
    * @param _index The index of the signature to recover.
    */
    function recoverSigner(bytes32 _signedHash, bytes memory _signatures, uint _index) internal pure returns (address) {
        uint8 v;
        bytes32 r;
        bytes32 s;
        // we jump 32 (0x20) as the first slot of bytes contains the length
        // we jump 65 (0x41) per signature
        // for v we load 32 bytes ending with v (the first 31 come from s) then apply a mask
        // solhint-disable-next-line no-inline-assembly
        assembly {
            r := mload(add(_signatures, add(0x20,mul(0x41,_index))))
            s := mload(add(_signatures, add(0x40,mul(0x41,_index))))
            v := and(mload(add(_signatures, add(0x41,mul(0x41,_index)))), 0xff)
        }
        require(v == 27 || v == 28, "Utils: bad v value in signature");

        address recoveredAddress = ecrecover(_signedHash, v, r, s);
        require(recoveredAddress != address(0), "Utils: ecrecover returned 0");
        return recoveredAddress;
    }

    /**
    * @notice Helper method to recover the spender from a contract call. 
    * The method returns the contract unless the call is to a standard method of a ERC20/ERC721/ERC1155 token
    * in which case the spender is recovered from the data.
    * @param _to The target contract.
    * @param _data The data payload.
    */
    function recoverSpender(address _to, bytes memory _data) internal pure returns (address spender) {
        if(_data.length >= 68) {
            bytes4 methodId;
            // solhint-disable-next-line no-inline-assembly
            assembly {
                methodId := mload(add(_data, 0x20))
            }
            if(
                methodId == ERC20_TRANSFER ||
                methodId == ERC20_APPROVE ||
                methodId == ERC721_SET_APPROVAL_FOR_ALL) 
            {
                // solhint-disable-next-line no-inline-assembly
                assembly {
                    spender := mload(add(_data, 0x24))
                }
                return spender;
            }
            if(
                methodId == ERC721_TRANSFER_FROM ||
                methodId == ERC721_SAFE_TRANSFER_FROM ||
                methodId == ERC721_SAFE_TRANSFER_FROM_BYTES ||
                methodId == ERC1155_SAFE_TRANSFER_FROM)
            {
                // solhint-disable-next-line no-inline-assembly
                assembly {
                    spender := mload(add(_data, 0x44))
                }
                return spender;
            }
        }

        spender = _to;
    }

    /**
    * @notice Helper method to parse data and extract the method signature.
    */
    function functionPrefix(bytes memory _data) internal pure returns (bytes4 prefix) {
        require(_data.length >= 4, "Utils: Invalid functionPrefix");
        // solhint-disable-next-line no-inline-assembly
        assembly {
            prefix := mload(add(_data, 0x20))
        }
    }

    /**
    * @notice Checks if an address is a contract.
    * @param _addr The address.
    */
    function isContract(address _addr) internal view returns (bool) {
        uint32 size;
        // solhint-disable-next-line no-inline-assembly
        assembly {
            size := extcodesize(_addr)
        }
        return (size > 0);
    }

    /**
    * @notice Checks if an address is a guardian or an account authorised to sign on behalf of a smart-contract guardian
    * given a list of guardians.
    * @param _guardians the list of guardians
    * @param _guardian the address to test
    * @return true and the list of guardians minus the found guardian upon success, false and the original list of guardians if not found.
    */
    function isGuardianOrGuardianSigner(address[] memory _guardians, address _guardian) internal view returns (bool, address[] memory) {
        if (_guardians.length == 0 || _guardian == address(0)) {
            return (false, _guardians);
        }
        bool isFound = false;
        address[] memory updatedGuardians = new address[](_guardians.length - 1);
        uint256 index = 0;
        for (uint256 i = 0; i < _guardians.length; i++) {
            if (!isFound) {
                // check if _guardian is an account guardian
                if (_guardian == _guardians[i]) {
                    isFound = true;
                    continue;
                }
                // check if _guardian is the owner of a smart contract guardian
                if (isContract(_guardians[i]) && isGuardianOwner(_guardians[i], _guardian)) {
                    isFound = true;
                    continue;
                }
            }
            if (index < updatedGuardians.length) {
                updatedGuardians[index] = _guardians[i];
                index++;
            }
        }
        return isFound ? (true, updatedGuardians) : (false, _guardians);
    }

    /**
    * @notice Checks if an address is the owner of a guardian contract.
    * The method does not revert if the call to the owner() method consumes more then 25000 gas.
    * @param _guardian The guardian contract
    * @param _owner The owner to verify.
    */
    function isGuardianOwner(address _guardian, address _owner) internal view returns (bool) {
        address owner = address(0);

        // solhint-disable-next-line no-inline-assembly
        assembly {
            let ptr := mload(0x40)
            mstore(ptr,OWNER_SIG)
            let result := staticcall(25000, _guardian, ptr, 0x20, ptr, 0x20)
            if eq(result, 1) {
                owner := mload(ptr)
            }
        }
        return owner == _owner;
    }

    /**
    * @notice Returns ceil(a / b).
    */
    function ceil(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a / b;
        if (a % b == 0) {
            return c;
        } else {
            return c + 1;
        }
    }
}
IModule.sol 45 lines
// Copyright (C) 2018  Argent Labs Ltd. <https://argent.xyz>

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.3;

/**
 * @title IModule
 * @notice Interface for a Module.
 * @author Julien Niset - <[email protected]>, Olivier VDB - <[email protected]>
 */
interface IModule {

    /**	
     * @notice Adds a module to a wallet. Cannot execute when wallet is locked (or under recovery)	
     * @param _wallet The target wallet.	
     * @param _module The modules to authorise.	
     */	
    function addModule(address _wallet, address _module) external;

    /**
     * @notice Inits a Module for a wallet by e.g. setting some wallet specific parameters in storage.
     * @param _wallet The wallet.
     */
    function init(address _wallet) external;


    /**
     * @notice Returns whether the module implements a callback for a given static call method.
     * @param _methodId The method id.
     */
    function supportsStaticCall(bytes4 _methodId) external view returns (bool _isSupported);
}
Owned.sol 52 lines
// Copyright (C) 2018  Argent Labs Ltd. <https://argent.xyz>

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.5.4 <0.9.0;

/**
 * @title Owned
 * @notice Basic contract to define an owner.
 * @author Julien Niset - <[email protected]>
 */
contract Owned {

    // The owner
    address public owner;

    event OwnerChanged(address indexed _newOwner);

    /**
     * @notice Throws if the sender is not the owner.
     */
    modifier onlyOwner {
        require(msg.sender == owner, "Must be owner");
        _;
    }

    constructor() public {
        owner = msg.sender;
    }

    /**
     * @notice Lets the owner transfer ownership of the contract to a new owner.
     * @param _newOwner The new owner.
     */
    function changeOwner(address _newOwner) external onlyOwner {
        require(_newOwner != address(0), "Address must not be null");
        owner = _newOwner;
        emit OwnerChanged(_newOwner);
    }
}
Managed.sol 64 lines
// Copyright (C) 2018  Argent Labs Ltd. <https://argent.xyz>

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.3;

import "./Owned.sol";

/**
 * @title Managed
 * @notice Basic contract that defines a set of managers. Only the owner can add/remove managers.
 * @author Julien Niset, Olivier VDB - <[email protected]>, <[email protected]>
 */
contract Managed is Owned {

    // The managers
    mapping (address => bool) public managers;

    /**
     * @notice Throws if the sender is not a manager.
     */
    modifier onlyManager {
        require(managers[msg.sender] == true, "M: Must be manager");
        _;
    }

    event ManagerAdded(address indexed _manager);
    event ManagerRevoked(address indexed _manager);

    /**
    * @notice Adds a manager.
    * @param _manager The address of the manager.
    */
    function addManager(address _manager) external onlyOwner {
        require(_manager != address(0), "M: Address must not be null");
        if (managers[_manager] == false) {
            managers[_manager] = true;
            emit ManagerAdded(_manager);
        }
    }

    /**
    * @notice Revokes a manager.
    * @param _manager The address of the manager.
    */
    function revokeManager(address _manager) external virtual onlyOwner {
        // solhint-disable-next-line reason-string
        require(managers[_manager] == true, "M: Target must be an existing manager");
        delete managers[_manager];
        emit ManagerRevoked(_manager);
    }
}
WalletFactory.sol 287 lines
// Copyright (C) 2018  Argent Labs Ltd. <https://argent.xyz>

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.3;

import "../wallet/Proxy.sol";
import "../wallet/BaseWallet.sol";
import "./base/Managed.sol";
import "./storage/IGuardianStorage.sol";
import "../modules/common/Utils.sol";

/**
 * @title WalletFactory
 * @notice The WalletFactory contract creates and assigns wallets to accounts.
 * @author Julien Niset, Olivier VDB - <[email protected]>, <[email protected]>
 */
contract WalletFactory is Managed {

    address constant internal ETH_TOKEN = address(0);

    // The address of the base wallet implementation
    address immutable public walletImplementation;
    // The address of the GuardianStorage
    address immutable public guardianStorage;
    // The recipient of the refund
    address public refundAddress; 

    // *************** Events *************************** //

    event RefundAddressChanged(address addr);
    event WalletCreated(address indexed wallet, address indexed owner, address indexed guardian, address refundToken, uint256 refundAmount);

    // *************** Constructor ********************** //

    /**
     * @notice Default constructor.
     */
    constructor(address _walletImplementation, address _guardianStorage, address _refundAddress) {
        require(_walletImplementation != address(0), "WF: empty wallet implementation");
        require(_guardianStorage != address(0), "WF: empty guardian storage");
        require(_refundAddress != address(0), "WF: empty refund address");
        walletImplementation = _walletImplementation;
        guardianStorage = _guardianStorage;
        refundAddress = _refundAddress;
    }

    // *************** External Functions ********************* //

    /**
    * @notice Disables the ability for the owner of the factory to revoke a manager.
    */
    function revokeManager(address /*_manager*/) override external pure {
        revert("WF: managers can't be revoked");
    }
     
    /**
     * @notice Creates a wallet for an owner account at a specific address.
     * The wallet is initialised with the target modules and a first guardian by default.
     * The wallet is created using the CREATE2 opcode and must have been approved 
     * by a manager of the factory.
     * @param _owner The account address.
     * @param _modules The list of modules for the wallet.
     * @param _guardian The guardian address.
     * @param _salt The salt.
     * @param _refundAmount The amount to refund to the relayer.
     * @param _refundToken The token to use to refund the relayer.
     * @param _ownerSignature The owner signature on the refund info.
     * @param _managerSignature The manager signature on the wallet address.
     */
    function createCounterfactualWallet(
        address _owner,
        address[] calldata _modules,
        address _guardian,
        bytes20 _salt,
        uint256 _refundAmount,
        address _refundToken,
        bytes calldata _ownerSignature,
        bytes calldata _managerSignature
    )
        external
        returns (address _wallet)
    {
        validateInputs(_owner, _modules, _guardian);
        bytes32 newsalt = newSalt(_salt, _owner, _modules, _guardian);
        address payable wallet = payable(new Proxy{salt: newsalt}(walletImplementation));
        validateAuthorisedCreation(wallet, _managerSignature);
        configureWallet(BaseWallet(wallet), _owner, _modules, _guardian);
        if (_refundAmount > 0 && _ownerSignature.length == 65) {
            validateAndRefund(wallet, _owner, _refundAmount, _refundToken, _ownerSignature);
        }
        // remove the factory from the authorised modules
        BaseWallet(wallet).authoriseModule(address(this), false);

        // emit event
        emit WalletCreated(wallet, _owner, _guardian, _refundToken, _refundAmount);

        return wallet;
    }

    /**
     * @notice Gets the address of a counterfactual wallet with a first default guardian.
     * @param _owner The account address.
     * @param _modules The list of modules for wallet.
     * @param _guardian The guardian address.
     * @param _salt The salt.
     * @return _wallet The address that the wallet will have when created using CREATE2 and the same input parameters.
     */
    function getAddressForCounterfactualWallet(
        address _owner,
        address[] calldata _modules,
        address _guardian,
        bytes20 _salt
    )
        external
        view
        returns (address _wallet)
    {
        validateInputs(_owner, _modules, _guardian);
        bytes32 newsalt = newSalt(_salt, _owner, _modules, _guardian);
        bytes memory code = abi.encodePacked(type(Proxy).creationCode, uint256(uint160(walletImplementation)));
        bytes32 hash = keccak256(abi.encodePacked(bytes1(0xff), address(this), newsalt, keccak256(code)));
        _wallet = address(uint160(uint256(hash)));
    }

    /**
     * @notice Lets the owner of the factory change the refund address.
     * @param _refundAddress The address to use for refunds.
     */
    function changeRefundAddress(address _refundAddress) external onlyOwner {
        require(_refundAddress != address(0), "WF: cannot set to empty");
        refundAddress = _refundAddress;
        emit RefundAddressChanged(_refundAddress);
    }

    /**
     * @notice Required to make the factory a module during the 
     * initialisation of the wallet. 
     * @param _wallet The wallet.
     */
    function init(BaseWallet _wallet) external pure {
        //do nothing
    }

    // *************** Internal Functions ********************* //

    /**
     * @notice Helper method to configure a wallet for a set of input parameters.
     * @param _wallet The target wallet
     * @param _owner The owner address.
     * @param _modules The list of modules.
     * @param _guardian The guardian.
     */
    function configureWallet(BaseWallet _wallet, address _owner, address[] calldata _modules, address _guardian) internal {
        // add the factory to modules so it can add the first guardian and trigger the refund
        address[] memory extendedModules = new address[](_modules.length + 1);
        extendedModules[0] = address(this);
        for (uint i = 0; i < _modules.length; i++) {
            extendedModules[i + 1] = _modules[i];
        }

        // initialise the wallet with the owner and the extended modules
        _wallet.init(_owner, extendedModules);

        // add the first guardian
        IGuardianStorage(guardianStorage).addGuardian(address(_wallet), _guardian);
    }

    /**
     * @notice Generates a new salt based on a provided salt, an owner, a list of modules and an optional guardian.
     * The extra parameters are pre-hashed to be compatible with zk-sync CREATE2 API (!! the order of the parameters 
     * assumes https://github.com/matter-labs/zksync/pull/259 has been merged !!).
     * @param _salt The salt provided. In practice the hash of the L2 public key.
     * @param _owner The owner address.
     * @param _modules The list of modules for wallet.
     * @param _guardian The guardian address.
     */
    function newSalt(bytes20 _salt, address _owner, address[] calldata _modules, address _guardian) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked(keccak256(abi.encodePacked(_owner, _modules, _guardian)), _salt));
    }

    /**
     * @notice Throws if the owner, guardian, or module array is invalid.
     * @param _owner The owner address.
     * @param _modules The list of modules for the wallet.
     * @param _guardian The guardian address.
     */
    function validateInputs(address _owner, address[] calldata _modules, address _guardian) internal pure {
        require(_owner != address(0), "WF: empty owner address");
        require(_owner != _guardian, "WF: owner cannot be guardian");
        require(_modules.length > 0, "WF: empty modules");
        require(_guardian != (address(0)), "WF: empty guardian");        
    }

    /**
     * @notice Throws if the sender is not a manager and the manager's signature for the
     * creation of the new wallet is invalid.
     * @param _wallet The wallet address
     * @param _managerSignature The manager's signature
     */
    function validateAuthorisedCreation(address _wallet, bytes memory _managerSignature) internal view {
        address manager;
        if(_managerSignature.length != 65) {
            manager = msg.sender;
        } else {
            bytes32 signedHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", bytes32(uint256(uint160(_wallet)))));
            manager = Utils.recoverSigner(signedHash, _managerSignature, 0);
        }
        require(managers[manager], "WF: unauthorised wallet creation");
    }

    /**
     * @notice Refunds the creation of the wallet when provided with a valid signature from the wallet owner.
     * @param _wallet The wallet created
     * @param _owner The owner address
     * @param _refundAmount The amount to refund
     * @param _refundToken The token to use for the refund
     * @param _ownerSignature A signature from the wallet owner approving the refund amount and token. 
     */
    function validateAndRefund(
        address _wallet,
        address _owner,
        uint256 _refundAmount,
        address _refundToken,
        bytes memory _ownerSignature
    )
        internal
    {
        bytes32 signedHash = keccak256(abi.encodePacked(
                "\x19Ethereum Signed Message:\n32",
                keccak256(abi.encodePacked(_wallet, _refundAmount, _refundToken))
            ));
        address signer = Utils.recoverSigner(signedHash, _ownerSignature, 0);
        if (signer == _owner) {
            if (_refundToken == ETH_TOKEN) {
                invokeWallet(_wallet, refundAddress, _refundAmount, "");
            } else {
                bytes memory methodData = abi.encodeWithSignature("transfer(address,uint256)", refundAddress, _refundAmount);
                bytes memory transferSuccessBytes = invokeWallet(_wallet, _refundToken, 0, methodData);
                if (transferSuccessBytes.length > 0) {
                    require(abi.decode(transferSuccessBytes, (bool)), "WF: Refund transfer failed");
                }
            }
        }
    }

    /**
     * @notice Invoke the wallet to execute the refund transfer.
     * @param _wallet The wallet
     * @param _to The destination of the call
     * @param _value The value of the call
     * @param _data The data associated to the call
     */
    function invokeWallet(
        address _wallet,
        address _to,
        uint256 _value,
        bytes memory _data
    )
        internal
        returns (bytes memory _res)
    {
        bool success;
        (success, _res) = _wallet.call(abi.encodeWithSignature("invoke(address,uint256,bytes)", _to, _value, _data));
        if (success) {
            (_res) = abi.decode(_res, (bytes));
        } else {
            // solhint-disable-next-line no-inline-assembly
            assembly {
                returndatacopy(0, 0, returndatasize())
                revert(0, returndatasize())
            }
        }
    }
}
IGuardianStorage.sol 54 lines
// Copyright (C) 2018  Argent Labs Ltd. <https://argent.xyz>

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.5.4 <0.9.0;

interface IGuardianStorage {

    /**
     * @notice Lets an authorised module add a guardian to a wallet.
     * @param _wallet The target wallet.
     * @param _guardian The guardian to add.
     */
    function addGuardian(address _wallet, address _guardian) external;

    /**
     * @notice Lets an authorised module revoke a guardian from a wallet.
     * @param _wallet The target wallet.
     * @param _guardian The guardian to revoke.
     */
    function revokeGuardian(address _wallet, address _guardian) external;

    /**
     * @notice Checks if an account is a guardian for a wallet.
     * @param _wallet The target wallet.
     * @param _guardian The account.
     * @return true if the account is a guardian for a wallet.
     */
    function isGuardian(address _wallet, address _guardian) external view returns (bool);

    function isLocked(address _wallet) external view returns (bool);

    function getLock(address _wallet) external view returns (uint256);

    function getLocker(address _wallet) external view returns (address);

    function setLock(address _wallet, uint256 _releaseAfter) external;

    function getGuardians(address _wallet) external view returns (address[] memory);

    function guardianCount(address _wallet) external view returns (uint256);
}

Read Contract

getAddressForCounterfactualWallet 0xfbe2ec7d → address
guardianStorage 0xd89784fc → address
init 0x19ab453c
managers 0xfdff9b4d → bool
owner 0x8da5cb5b → address
refundAddress 0x0cb61f6c → address
revokeManager 0x377e32e6
walletImplementation 0x8117abc1 → address

Write Contract 4 functions

These functions modify contract state and require a wallet transaction to execute.

addManager 0x2d06177a
address _manager
changeOwner 0xa6f9dae1
address _newOwner
changeRefundAddress 0x2c828525
address _refundAddress
createCounterfactualWallet 0xa80dd9df
address _owner
address[] _modules
address _guardian
bytes20 _salt
uint256 _refundAmount
address _refundToken
bytes _ownerSignature
bytes _managerSignature
returns: address

Token Balances (1)

View Transfers →
WETH 0.001

Recent Transactions

No transactions found for this address