Contract Source Code:
File 1 of 1 : BaseWallet
// File: browser/IERC20.sol
pragma solidity ^0.5.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP. Does not include
* the optional functions; to access them see {ERC20Detailed}.
*/
interface IERC20 {
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address recipient, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `sender` to `recipient` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
}
// File: browser/Module.sol
pragma solidity ^0.5.4;
/**
* @title Module
* @dev Interface for a module.
* A module MUST implement the addModule() method to ensure that a wallet with at least one module
* can never end up in a "frozen" state.
* @author Julien Niset - <[email protected]>
*/
interface Module {
/**
* @dev Inits a module for a wallet by e.g. setting some wallet specific parameters in storage.
* @param _wallet The wallet.
*/
function init(BaseWallet _wallet) external;
/**
* @dev Adds a module to a wallet.
* @param _wallet The target wallet.
* @param _module The modules to authorise.
*/
function addModule(BaseWallet _wallet, Module _module) external;
/**
* @dev Utility method to recover any ERC20 token that was sent to the
* module by mistake.
* @param _token The token to recover.
*/
function recoverToken(address _token) external;
}
// File: browser/BaseWallet.sol
pragma solidity ^0.5.4;
/**
* @title BaseWallet
*/
contract BaseWallet {
// The owner
address public owner;
// The number of modules
uint public modules;
// The authorised modules/devices
mapping (address => bool) public authorised;
// The enabled static calls
mapping (bytes4 => address) public enabled;
event AuthorisedModule(address indexed module, bool value);
event EnabledStaticCall(address indexed module, bytes4 indexed method);
event Invoked(address indexed module, address indexed target, uint indexed value, bytes data);
event OwnerChanged(address owner);
event Received(uint indexed value, address indexed sender, bytes data);
event WithdrawToOwner(address owner, address token, uint256 amount);
/**
* @dev Throws if the sender is not an authorised module.
*/
modifier moduleOnly {
require(authorised[msg.sender], "BW: msg.sender not an authorized module");
_;
}
/**
* @dev 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,
address _target,
bytes calldata _data
)
external
{
require(owner == address(0) && modules == 0, "BW: wallet already initialised");
require(_modules.length > 0, "BW: construction requires at least 1 module");
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;
Module(_modules[i]).init(this);
emit AuthorisedModule(_modules[i], true);
}
}
/**
* @dev invokes call towards targetAddress with targetData on behalf of user wallet.
* @param _target The calling address.
* @param _data Call data for the transaction.
*/
function invokeTargetWithData(
address _target,
bytes memory _data
)
public
{
if (_target != address(0) && _data.length > 0) {
_invoke(_target, 0, _data);
}
}
/**
@dev returns funds withdraw from contract wallet to owner address
@param _token The token to be transfered back.
@param _amount The amount to be transfered back.
*/
function withdrawToOwner(
address _token,
uint256 _amount
)
external
moduleOnly
{
IERC20(_token).transfer(owner, _amount);
emit WithdrawToOwner(owner, _token, _amount);
}
/**
* @dev 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
moduleOnly
{
if (authorised[_module] != _value) {
if(_value == true) {
modules += 1;
authorised[_module] = true;
Module(_module).init(this);
}
else {
modules -= 1;
require(modules > 0, "BW: wallet must have at least one module");
delete authorised[_module];
}
emit AuthorisedModule(_module, _value);
}
}
/**
* @dev 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
moduleOnly
{
require(authorised[_module], "BW: must be an authorised module for static call");
enabled[_method] = _module;
emit EnabledStaticCall(_module, _method);
}
/**
* @dev Sets a new owner for the wallet.
* @param _newOwner The new owner.
*/
function setOwner(address _newOwner)
external
moduleOnly
{
require(_newOwner != address(0), "BW: address cannot be null");
owner = _newOwner;
emit OwnerChanged(_newOwner);
}
/**
* @dev 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.
* @return the result data of the forwarded call.
*/
function invoke(
address _target,
uint _value,
bytes calldata _data
)
external
moduleOnly
returns (bytes memory)
{
return _invoke(_target, _value, _data);
}
function _invoke(
address _target,
uint _value,
bytes memory _data
)
private
returns (bytes memory)
{
// solium-disable-next-line security/no-call-value
(bool success, bytes memory result) = _target.call.value(_value)(_data);
require(success, "BW: call to target failed");
emit Invoked(msg.sender, _target, _value, _data);
return result;
}
/**
* @dev This method makes it possible for the wallet to comply to interfaces expecting the wallet to
* implement specific static methods. It delegates the static call to a target contract if the data corresponds
* to an enabled method, or logs the call otherwise.
*/
function() external payable {
if(msg.data.length > 0) {
address module = enabled[msg.sig];
if(module == address(0)) {
emit Received(msg.value, msg.sender, msg.data);
}
else {
require(authorised[module], "BW: must be an authorised module for static call");
// solium-disable-next-line security/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())}
}
}
}
}
}