# Hack the Wallet!

A wallet is essentially a contract for managing the Ethereum account(s). Wallets can provide additional features, such as multi-signature authentication, quotas and limits, easy transfer of ownership without private keys, etc. Anyone can put money into the wallet, but only the owners can withdraw money from it.

Additional feature means additional code, as well as additional gas and security risks. To save gas for wallet deployment, the parity wallet creates a "library" contract called WalletLibrary to provide functionalities for the wallet contract. The wallet contracts leverage delegatecall to use the functions in the "library".


In order to make the function callable from the wallet, the library chose to implement the initWallet function with visibility external so it could be called by the Wallet. And that's where it all went wrong. In the early days of the solidity compiler, functions default to public visibility won't throw warnings or errors, so the actual contract being hacked looks like this:

contract WalletLibrary is WalletEvents {
  ...
  function initMultiowned(address[] _owners, uint _required) {
    m_numOwners = _owners.length + 1;
    m_owners[1] = uint(msg.sender);
    m_ownerIndex[uint(msg.sender)] = 1;
    for (uint i = 0; i < _owners.length; ++i)
    {
      m_owners[2 + i] = uint(_owners[i]);
      m_ownerIndex[uint(_owners[i])] = 2 + i;
    }
    m_required = _required;
  }
  ...
  function initWallet(address[] _owners, uint _required, uint _daylimit) {
    initDaylimit(_daylimit);
    initMultiowned(_owners, _required);
  }
}

Two mistakes led to the hack that we are reproducing here:

  1. Inappropriate visibility of initMultiowned.
  2. The initialization functions can be called even after the wallet is initialized.

In this challenge, you are provided with a simplified version of a wallet library contract and a wallet contract:

// SPDX-License-Identifier: BSD-4-Clause
pragma solidity ^0.8.11;

contract PartOneWalletLibrary {
    address public walletLibAddr;
    address payable public owner;
    address public initOwner;

    constructor() {
        initOwner = msg.sender;
        owner = payable(msg.sender);
    }

    function initWallet(address payable _owner) external {
        owner = _owner;
    }

    function changeOwner(address payable _new_owner) external {
        require(msg.sender == owner, "You are not the owner");
        owner = _new_owner;
    }

    receive() external payable {}

    function withdraw(uint256 amount) external payable returns (bool) {
        require(msg.sender == owner, "You are not the owner");
        return owner.send(amount);
    }

    function getBalance() external view returns (uint256) {
        return address(this).balance;
    }

    function completed(address player) external view returns (bool) {
        return owner == player;
    }
}

contract PartOneWallet {
    address public walletLibAddr;
    address payable public owner;
    address public initOwner;

    constructor() {
        initOwner = msg.sender;
        walletLibAddr = address(new PartOneWalletLibrary());

        (bool success /* bytes memory returnedData */, ) = walletLibAddr
            .delegatecall(
                abi.encodeCall(PartOneWalletLibrary.initWallet, payable(msg.sender)) // new in solidity 0.8.11
            );
        require(success, "initWallet() failed");
    }

    // fallback function gets called if no other function matches call
    fallback() external payable {
        (bool success /* bytes memory returnedData */, ) = walletLibAddr
            .delegatecall(msg.data);
        require(success, "[delegatecall] failed");
    }

    function completed(address player) external view returns (bool) {
        return owner == player;
    }
}

To beat the first challenge, claim ownership of this level, which is exactly a vulnerable wallet instance!

You might want to review the delegatecall (opens new window) and call (opens new window) methods.