def sanity_check(vesting_address, vested_amounts):
    vesting_escrow = VestingEscrow.at(vesting_address)

    if vesting_escrow.initial_locked_supply() != TOTAL_AMOUNT:
        raise ValueError(f"Unexpected locked supply: {vesting_escrow.initial_locked_supply()}")
    if vesting_escrow.unallocated_supply() != 0:
        raise ValueError(f"Unallocated supply remains: {vesting_escrow.unallocated_supply()}")

    for count, (acct, expected) in enumerate(vested_amounts, start=1):
        balance = vesting_escrow.initial_locked(acct)
        if balance != expected:
            raise ValueError(
                f"Incorrect vested amount for {acct} - expected {expected}, got {balance}"
            )
        if not count % 250:
            print(f"{count}/{len(vested_amounts)} balances verified...")

    print("Sanity check passed!")
def vest_tokens(admin, funding_admins, token_address, confs):
    start_idx = len(history)

    # get token Contract object
    token = ERC20CRV.at(token_address)

    # deploy vesting contract
    start_time = token.future_epoch_time_write.call()

    vesting_escrow = VestingEscrow.deploy(
        token,
        start_time,
        start_time + VESTING_PERIOD,
        False,
        funding_admins,
        {
            "from": admin,
            "required_confs": confs
        },
    )
    _log_tx(
        txid=vesting_escrow.tx.txid,
        fn_name="VestingEscrow.deploy",
        contract_address=vesting_escrow.address,
    )

    # load vesting data from json
    with open(config.LP_VESTING_JSON) as fp:
        vested_pct = {k.lower(): Decimal(v) for k, v in json.load(fp).items()}

    for addr in BLACKLIST:
        if addr.lower() in vested_pct:
            del vested_pct[addr]

    # calculate absolute amounts to be distributed
    initial_total = sum(int(v * TOTAL_AMOUNT) for v in vested_pct.values())
    adjustment_pct = Decimal(TOTAL_AMOUNT) / initial_total
    vested_amounts = sorted(
        ([k, int(v * TOTAL_AMOUNT * adjustment_pct)]
         for k, v in vested_pct.items()),
        key=lambda k: k[1],
        reverse=True,
    )

    if vested_amounts[-1][1] < 0:
        raise ValueError(
            f"'{config.LP_VESTING_JSON}' contains negative amounts!")

    vested_amounts = [i for i in vested_amounts if i[1]]

    # floats -> int, we expect to be ever so slightly over, so lets fix that
    final_total = sum(i[1] for i in vested_amounts)

    if not 0 < abs(final_total - TOTAL_AMOUNT) < len(vested_amounts):
        raise ValueError(
            "Imprecision!!! Distribution amounts are too far off!")

    for i in range(abs(final_total - TOTAL_AMOUNT)):
        if final_total < TOTAL_AMOUNT:
            vested_amounts[i][1] += 1
        else:
            vested_amounts[i][1] -= 1

    tx = token.approve(vesting_escrow, TOTAL_AMOUNT, {
        "from": admin,
        "required_confs": confs
    })
    _log_tx(txid=tx.txid,
            fn_name=tx.fn_name,
            spender=vesting_escrow.address,
            amount=TOTAL_AMOUNT)
    tx = vesting_escrow.add_tokens(TOTAL_AMOUNT, {
        "from": admin,
        "required_confs": confs
    })
    _log_tx(txid=tx.txid, fn_name=tx.fn_name, amount=TOTAL_AMOUNT)

    # convert vested_amounts into input args for `VestingEscrow.fund` calls
    fund_arguments = [([x[0] for x in vested_amounts[i:i + 100]],
                       [x[1] for x in vested_amounts[i:i + 100]])
                      for i in range(0, len(vested_amounts), 100)]

    # final call needs to be extended with zero values
    zeros = 100 - len(fund_arguments[-1][0])
    fund_arguments[-1] = (
        fund_arguments[-1][0] +
        ["0x0000000000000000000000000000000000000000"] * zeros,
        fund_arguments[-1][1] + [0] * zeros,
    )

    # use threading to handle the funding across several accounts
    funding_threads = []
    for acct in [admin] + funding_admins:
        thread = threading.Thread(target=_fund_accounts,
                                  args=(acct, vesting_escrow, fund_arguments,
                                        confs))
        funding_threads.append(thread)
        thread.start()

    for thread in funding_threads:
        thread.join()

    # burn all the admin accounts!
    tx = vesting_escrow.disable_fund_admins({
        "from": admin,
        "required_confs": confs
    })
    _log_tx(txid=tx.txid, fn_name=tx.fn_name)
    vesting_escrow.commit_transfer_ownership(
        "0x000000000000000000000000000000000000dead", {
            "from": admin,
            "required_confs": confs
        })
    _log_tx(txid=tx.txid, fn_name=tx.fn_name)
    vesting_escrow.apply_transfer_ownership({
        "from": admin,
        "required_confs": confs
    })
    _log_tx(txid=tx.txid, fn_name=tx.fn_name)

    gas_used = sum(i.gas_used for i in history[start_idx:])
    print(f"Distribution complete! Total gas used: {gas_used}")

    # return the final vested amounts to be used in `sanity_check`, if desired
    return vesting_escrow, vested_amounts
def vest_tokens(admin, token_address, confs):
    token = ERC20CRV.at(token_address)

    # deploy library and vesting factories
    target = VestingEscrowSimple.deploy({
        "from": admin,
        "required_confs": confs
    })

    factory_contracts = []
    for data in config.FACTORY_ESCROWS:
        factory = VestingEscrowFactory.deploy(target, data["admin"], {
            "from": admin,
            "required_confs": confs
        })
        token.transfer(factory, data["amount"], {
            "from": admin,
            "required_confs": confs
        })
        factory_contracts.append((factory, data["amount"]))

    # deploy standard escrows
    start_time = token.future_epoch_time_write.call()
    for data in config.STANDARD_ESCROWS:

        vesting_escrow = VestingEscrow.deploy(
            token,
            start_time,
            start_time + data["duration"],
            data["can_disable"],
            [ZERO_ADDRESS] * 4,
            {
                "from": admin,
                "required_confs": confs
            },
        )
        data["contract"] = vesting_escrow

        total_amount = sum(data["recipients"].values())
        token.approve(vesting_escrow, total_amount, {
            "from": admin,
            "required_confs": confs
        })
        vesting_escrow.add_tokens(total_amount, {
            "from": admin,
            "required_confs": confs
        })

        zeros = 100 - len(data["recipients"])
        fund_inputs = tuple(data["recipients"].items())
        recipients = [i[0] for i in fund_inputs] + [ZERO_ADDRESS] * zeros
        amounts = [i[1] for i in fund_inputs] + [0] * zeros

        vesting_escrow.fund(recipients, amounts, {
            "from": admin,
            "required_confs": confs
        })

        if "admin" in data:
            vesting_escrow.commit_transfer_ownership(data["admin"], {
                "from": admin,
                "required_confs": confs
            })
            vesting_escrow.apply_transfer_ownership({
                "from": admin,
                "required_confs": confs
            })

    print("Deployments finished!\n\nFactories:")
    for factory, amount in factory_contracts:
        print(f"  {factory.address} : {amount} tokens")

    print("\nStandard Escrows:")
    for data in config.STANDARD_ESCROWS:
        total_amount = sum(data["recipients"].values())
        print(
            f"  {data['contract'].address}: {len(data['recipients'])} recipients, "
            f"{total_amount} total tokens, {data['duration']/YEAR} year lock")

    return config.STANDARD_ESCROWS, factory_contracts