POL Price: $0.601583 (-2.93%)
 

Overview

POL Balance

Polygon PoS Chain LogoPolygon PoS Chain LogoPolygon PoS Chain Logo0 POL

POL Value

$0.00

Token Holdings

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To
Claim External R...654663352024-12-14 9:00:114 hrs ago1734166811IN
0xE3B8E734...57012A51D
0 POL0.0058565130.92794114
Claim External R...654663352024-12-14 9:00:114 hrs ago1734166811IN
0xE3B8E734...57012A51D
0 POL0.0060972530.92794114
Claim External R...654263562024-12-13 9:00:3528 hrs ago1734080435IN
0xE3B8E734...57012A51D
0 POL0.0064478834.0509455
Claim External R...654263562024-12-13 9:00:3528 hrs ago1734080435IN
0xE3B8E734...57012A51D
0 POL0.0067129334.0509455
Claim External R...653859462024-12-12 9:00:082 days ago1733994008IN
0xE3B8E734...57012A51D
0 POL0.006552433.23666009
Claim External R...653859452024-12-12 9:00:062 days ago1733994006IN
0xE3B8E734...57012A51D
0 POL0.0066420133.23666009
Withdraw653602822024-12-11 17:43:092 days ago1733938989IN
0xE3B8E734...57012A51D
0 POL0.0149211130.07550276
Claim External R...653456652024-12-11 9:00:093 days ago1733907609IN
0xE3B8E734...57012A51D
0 POL0.005941431.37624488
Claim External R...653456652024-12-11 9:00:093 days ago1733907609IN
0xE3B8E734...57012A51D
0 POL0.0061856331.37624488
Claim External R...653055002024-12-10 9:00:084 days ago1733821208IN
0xE3B8E734...57012A51D
0 POL0.0080061940.61091535
Claim External R...653054992024-12-10 9:00:064 days ago1733821206IN
0xE3B8E734...57012A51D
0 POL0.0080168840.66514525
Claim External R...652654532024-12-09 9:00:135 days ago1733734813IN
0xE3B8E734...57012A51D
0 POL0.0113619457.63272974
Claim External R...652654502024-12-09 9:00:075 days ago1733734807IN
0xE3B8E734...57012A51D
0 POL0.0114900558.28254238
Claim External R...652260082024-12-08 9:00:086 days ago1733648408IN
0xE3B8E734...57012A51D
0 POL0.005680830.00000015
Claim External R...652260082024-12-08 9:00:086 days ago1733648408IN
0xE3B8E734...57012A51D
0 POL0.0059143230.00000015
Claim External R...651869762024-12-07 9:00:117 days ago1733562011IN
0xE3B8E734...57012A51D
0 POL0.0060412230.64369613
Claim External R...651869752024-12-07 9:00:077 days ago1733562007IN
0xE3B8E734...57012A51D
0 POL0.0060412230.64369613
Claim External R...651473722024-12-06 9:00:098 days ago1733475609IN
0xE3B8E734...57012A51D
0 POL0.0057014730.10920121
Claim External R...651473722024-12-06 9:00:098 days ago1733475609IN
0xE3B8E734...57012A51D
0 POL0.0059358430.10920121
Claim External R...651072652024-12-05 9:00:129 days ago1733389212IN
0xE3B8E734...57012A51D
0 POL0.0193901998.355501
Claim External R...651072642024-12-05 9:00:109 days ago1733389210IN
0xE3B8E734...57012A51D
0 POL0.0278238396.12224881
Claim External R...650668172024-12-04 9:00:0710 days ago1733302807IN
0xE3B8E734...57012A51D
0 POL0.0085048544.91368074
Claim External R...650668172024-12-04 9:00:0710 days ago1733302807IN
0xE3B8E734...57012A51D
0 POL0.0122331844.91368074
Withdraw650471892024-12-03 21:16:5810 days ago1733260618IN
0xE3B8E734...57012A51D
0 POL0.0137402630.15577209
Claim External R...650266472024-12-03 9:00:0811 days ago1733216408IN
0xE3B8E734...57012A51D
0 POL0.0151742680.13446612
View all transactions

Latest 1 internal transaction

Parent Transaction Hash Block From To
542570242024-03-04 13:23:28284 days ago1709558608  Contract Creation0 POL
Loading...
Loading

Minimal Proxy Contract for 0x665a23707e9cfce7bf07c52d375f5274cedd6eb4

Contract Name:
Voting Escrow

Compiler Version
vyper:0.3.7

Optimization Enabled:
N/A

Other Settings:
default evmVersion, MIT license

Contract Source Code (Vyper language format)

# @version 0.3.7

"""
@title Voting Escrow
@author Curve Finance
@license MIT
@notice Votes have a weight depending on time, so that users are
        committed to the future of (whatever they are voting for)
@dev Vote weight decays linearly over time. Lock time cannot be
     more than `MAXTIME` (set by creator).
"""

# Voting escrow to have time-weighted votes
# Votes have a weight depending on time, so that users are committed
# to the future of (whatever they are voting for).
# The weight in this implementation is linear, and lock cannot be more than maxtime:
# w ^
# 1 +        /
#   |      /
#   |    /
#   |  /
#   |/
# 0 +--------+------> time
#       maxtime

struct Point:
    bias: int128
    slope: int128  # - dweight / dt
    ts: uint256
    blk: uint256  # block
# We cannot really do block numbers per se b/c slope is per time, not per block
# and per block could be fairly bad b/c Ethereum changes blocktimes.
# What we can do is to extrapolate ***At functions

struct LockedBalance:
    amount: int128
    end: uint256


interface ERC20:
    def decimals() -> uint256: view
    def name() -> String[64]: view
    def symbol() -> String[32]: view
    def balanceOf(account: address) -> uint256: view
    def transfer(to: address, amount: uint256) -> bool: nonpayable
    def approve(spender: address, amount: uint256) -> bool: nonpayable
    def transferFrom(spender: address, to: address, amount: uint256) -> bool: nonpayable


# Interface for checking whether address belongs to a whitelisted
# type of a smart wallet.
# When new types are added - the whole contract is changed
# The check() method is modifying to be able to use caching
# for individual wallet addresses
interface SmartWalletChecker:
    def check(addr: address) -> bool: nonpayable

interface BalancerMinter:
    def mint(gauge: address) -> uint256: nonpayable

interface RewardDistributor:
    def depositToken(token: address, amount: uint256): nonpayable

DEPOSIT_FOR_TYPE: constant(int128) = 0
CREATE_LOCK_TYPE: constant(int128) = 1
INCREASE_LOCK_AMOUNT: constant(int128) = 2
INCREASE_UNLOCK_TIME: constant(int128) = 3


event CommitOwnership:
    admin: address

event ApplyOwnership:
    admin: address

event EarlyUnlock:
    status: bool

event PenaltySpeed:
    penalty_k: uint256

event PenaltyTreasury:
    penalty_treasury: address

event TotalUnlock:
    status: bool

event RewardReceiver:
    newReceiver: address

event Deposit:
    provider: indexed(address)
    value: uint256
    locktime: indexed(uint256)
    type: int128
    ts: uint256

event Withdraw:
    provider: indexed(address)
    value: uint256
    ts: uint256

event WithdrawEarly:
    provider: indexed(address)
    penalty: uint256
    time_left: uint256

event Supply:
    prevSupply: uint256
    supply: uint256


WEEK: constant(uint256) = 7 * 86400  # all future times are rounded by week
MAXTIME: public(uint256)
MULTIPLIER: constant(uint256) = 10**18

TOKEN: public(address)

NAME: String[64]
SYMBOL: String[32]
DECIMALS: uint256

supply: public(uint256)
locked: public(HashMap[address, LockedBalance])

epoch: public(uint256)
point_history: public(Point[100000000000000000000000000000])  # epoch -> unsigned point
user_point_history: public(HashMap[address, Point[1000000000]])  # user -> Point[user_epoch]
user_point_epoch: public(HashMap[address, uint256])
slope_changes: public(HashMap[uint256, int128])  # time -> signed slope change

# Checker for whitelisted (smart contract) wallets which are allowed to deposit
# The goal is to prevent tokenizing the escrow
future_smart_wallet_checker: public(address)
smart_wallet_checker: public(address)

admin: public(address)

# unlock admins can be set only once. Zero-address means unlock is disabled
admin_unlock_all: public(address)
admin_early_unlock: public(address)

future_admin: public(address)

is_initialized: public(bool)

early_unlock: public(bool)
penalty_k: public(uint256)
prev_penalty_k: public(uint256)
penalty_upd_ts: public(uint256)
PENALTY_COOLDOWN: constant(uint256) = 60 # cooldown to prevent font-run on penalty change
PENALTY_MULTIPLIER: constant(uint256) = 10

penalty_treasury: public(address)

balMinter: public(address)
balToken: public(address)
rewardReceiver: public(address)
rewardReceiverChangeable: public(bool)

rewardDistributor: public(address)

all_unlock: public(bool)


@external
def initialize(
    _token_addr: address,
    _name: String[64],
    _symbol: String[32],
    _admin_addr: address,
    _admin_unlock_all: address,
    _admin_early_unlock: address,
    _max_time: uint256,
    _balToken: address,
    _balMinter: address,
    _rewardReceiver: address,
    _rewardReceiverChangeable: bool,
    _rewardDistributor: address
):
    """
    @notice Contract constructor
    @param _token_addr 80/20 Token-WETH BPT token address
    @param _name Token name
    @param _symbol Token symbol
    @param _admin_addr Contract admin address
    @param _admin_unlock_all Admin to enable Unlock-All feature (zero-address to disable forever)
    @param _admin_early_unlock Admin to enable Eraly-Unlock feature (zero-address to disable forever)
    @param _max_time Locking max time
    @param _balToken Address of the Balancer token
    @param _balMinter Address of the Balancer minter
    @param _rewardReceiver Address of the reward receiver
    @param _rewardReceiverChangeable Boolean indicating whether the reward receiver is changeable
    @param _rewardDistributor The RewardDistributor contract address
    """

    assert(not self.is_initialized), 'only once'
    self.is_initialized = True

    assert(_admin_addr != empty(address)), '!empty'
    self.admin = _admin_addr

    self.penalty_k = 10
    self.prev_penalty_k = 10
    self.penalty_upd_ts = block.timestamp
    self.penalty_treasury = _admin_addr

    self.TOKEN = _token_addr
    self.point_history[0].blk = block.number
    self.point_history[0].ts = block.timestamp

    _decimals: uint256 = ERC20(_token_addr).decimals()  # also validates token for non-zero
    assert (_decimals >= 6 and _decimals <= 255), '!decimals'

    self.NAME = _name
    self.SYMBOL = _symbol
    self.DECIMALS = _decimals

    assert(_max_time >= WEEK and _max_time <= WEEK * 52 * 5), '!maxlock'
    self.MAXTIME = _max_time

    self.admin_unlock_all = _admin_unlock_all
    self.admin_early_unlock = _admin_early_unlock

    self.balToken = _balToken
    self.balMinter = _balMinter
    self.rewardReceiver = _rewardReceiver
    self.rewardReceiverChangeable = _rewardReceiverChangeable
    self.rewardDistributor = _rewardDistributor


@external
@view
def token() -> address:
    return self.TOKEN

@external
@view
def name() -> String[64]:
    return self.NAME

@external
@view
def symbol() -> String[32]:
    return self.SYMBOL

@external
@view
def decimals() -> uint256:
    return self.DECIMALS

@external
def commit_transfer_ownership(addr: address):
    """
    @notice Transfer ownership of VotingEscrow contract to `addr`
    @param addr Address to have ownership transferred to
    """
    assert msg.sender == self.admin  # dev: admin only
    self.future_admin = addr
    log CommitOwnership(addr)


@external
def apply_transfer_ownership():
    """
    @notice Apply ownership transfer
    """
    assert msg.sender == self.admin  # dev: admin only
    _admin: address = self.future_admin
    assert _admin != empty(address)  # dev: admin not set
    self.admin = _admin
    log ApplyOwnership(_admin)


@external
def commit_smart_wallet_checker(addr: address):
    """
    @notice Set an external contract to check for approved smart contract wallets
    @param addr Address of Smart contract checker
    """
    assert msg.sender == self.admin
    self.future_smart_wallet_checker = addr

@external
def apply_smart_wallet_checker():
    """
    @notice Apply setting external contract to check approved smart contract wallets
    """
    assert msg.sender == self.admin
    self.smart_wallet_checker = self.future_smart_wallet_checker


@internal
def assert_not_contract(addr: address):
    """
    @notice Check if the call is from a whitelisted smart contract, revert if not
    @param addr Address to be checked
    """
    if addr != tx.origin:
        checker: address = self.smart_wallet_checker
        if checker != empty(address):
            if SmartWalletChecker(checker).check(addr):
                return
        raise "Smart contract depositors not allowed"


@external
def set_early_unlock(_early_unlock: bool):
    """
    @notice Sets the availability for users to unlock their locks before lock-end with penalty
    @dev Only the admin_early_unlock can execute this function.
    @param _early_unlock A boolean indicating whether early unlock is allowed or not.
    """
    assert msg.sender == self.admin_early_unlock, '!admin'  # dev: admin_early_unlock only
    assert _early_unlock != self.early_unlock, 'already'
    
    self.early_unlock = _early_unlock
    log EarlyUnlock(_early_unlock)


@external
def set_early_unlock_penalty_speed(_penalty_k: uint256):
    """
    @notice Sets penalty speed for early unlocking
    @dev Only the admin can execute this function. To prevent frontrunning we use PENALTY_COOLDOWN period
    @param _penalty_k Coefficient indicating the penalty speed for early unlock.
                      Must be between 0 and 50, inclusive. Default 10 - means linear speed.
    """
    assert msg.sender == self.admin_early_unlock, '!admin'  # dev: admin_early_unlock only
    assert _penalty_k <= 50, '!k'
    assert block.timestamp > self.penalty_upd_ts + PENALTY_COOLDOWN, 'early' # to avoid frontrun

    self.prev_penalty_k = self.penalty_k
    self.penalty_k = _penalty_k
    self.penalty_upd_ts = block.timestamp

    log PenaltySpeed(_penalty_k)


@external
def set_penalty_treasury(_penalty_treasury: address):
    """
    @notice Sets penalty treasury address
    @dev Only the admin_early_unlock can execute this function.
    @param _penalty_treasury The address to collect early penalty (default admin address)
    """
    assert msg.sender == self.admin_early_unlock, '!admin'  # dev: admin_early_unlock only
    assert _penalty_treasury != empty(address), '!zero'
   
    self.penalty_treasury = _penalty_treasury
    log PenaltyTreasury(_penalty_treasury)


@external
def set_all_unlock():
    """
    @notice Deactivates VotingEscrow and allows users to unlock their locks before lock-end. 
            New deposits will no longer be accepted.
    @dev Only the admin_unlock_all can execute this function. Make sure there are no rewards for distribution in other contracts.
    """
    assert msg.sender == self.admin_unlock_all, '!admin'  # dev: admin_unlock_all only
    self.all_unlock = True
    log TotalUnlock(True)


@external
@view
def get_last_user_slope(addr: address) -> int128:
    """
    @notice Get the most recently recorded rate of voting power decrease for `addr`
    @param addr Address of the user wallet
    @return Value of the slope
    """
    uepoch: uint256 = self.user_point_epoch[addr]
    return self.user_point_history[addr][uepoch].slope


@external
@view
def user_point_history__ts(_addr: address, _idx: uint256) -> uint256:
    """
    @notice Get the timestamp for checkpoint `_idx` for `_addr`
    @param _addr User wallet address
    @param _idx User epoch number
    @return Epoch time of the checkpoint
    """
    return self.user_point_history[_addr][_idx].ts


@external
@view
def locked__end(_addr: address) -> uint256:
    """
    @notice Get timestamp when `_addr`'s lock finishes
    @param _addr User wallet
    @return Epoch time of the lock end
    """
    return self.locked[_addr].end


@internal
def _checkpoint(addr: address, old_locked: LockedBalance, new_locked: LockedBalance):
    """
    @notice Record global and per-user data to checkpoint
    @param addr User's wallet address. No user checkpoint if 0x0
    @param old_locked Pevious locked amount / end lock time for the user
    @param new_locked New locked amount / end lock time for the user
    """
    u_old: Point = empty(Point)
    u_new: Point = empty(Point)
    old_dslope: int128 = 0
    new_dslope: int128 = 0
    _epoch: uint256 = self.epoch

    if addr != empty(address):
        # Calculate slopes and biases
        # Kept at zero when they have to
        if old_locked.end > block.timestamp and old_locked.amount > 0:
            u_old.slope = old_locked.amount / convert(self.MAXTIME, int128)
            u_old.bias = u_old.slope * convert(old_locked.end - block.timestamp, int128)
        if new_locked.end > block.timestamp and new_locked.amount > 0:
            u_new.slope = new_locked.amount / convert(self.MAXTIME, int128)
            u_new.bias = u_new.slope * convert(new_locked.end - block.timestamp, int128)


        # Read values of scheduled changes in the slope
        # old_locked.end can be in the past and in the future
        # new_locked.end can ONLY by in the FUTURE unless everything expired: than zeros
        old_dslope = self.slope_changes[old_locked.end]
        if new_locked.end != 0:
            if new_locked.end == old_locked.end:
                new_dslope = old_dslope
            else:
                new_dslope = self.slope_changes[new_locked.end]

    last_point: Point = Point({bias: 0, slope: 0, ts: block.timestamp, blk: block.number})
    if _epoch > 0:
        last_point = self.point_history[_epoch]
    last_checkpoint: uint256 = last_point.ts
    # initial_last_point is used for extrapolation to calculate block number
    # (approximately, for *At methods) and save them
    # as we cannot figure that out exactly from inside the contract
    initial_last_point: Point = last_point
    block_slope: uint256 = 0  # dblock/dt
    if block.timestamp > last_point.ts:
        block_slope = MULTIPLIER * (block.number - last_point.blk) / (block.timestamp - last_point.ts)
    # If last point is already recorded in this block, slope=0
    # But that's ok b/c we know the block in such case

    # Go over weeks to fill history and calculate what the current point is
    t_i: uint256 = (last_checkpoint / WEEK) * WEEK
    for i in range(255):
        # Hopefully it won't happen that this won't get used in 5 years!
        # If it does, users will be able to withdraw but vote weight will be broken
        t_i += WEEK
        d_slope: int128 = 0
        if t_i > block.timestamp:
            t_i = block.timestamp
        else:
            d_slope = self.slope_changes[t_i]
        last_point.bias -= last_point.slope * convert(t_i - last_checkpoint, int128)
        last_point.slope += d_slope
        if last_point.bias < 0:  # This can happen
            last_point.bias = 0
        if last_point.slope < 0:  # This cannot happen - just in case
            last_point.slope = 0
        last_checkpoint = t_i
        last_point.ts = t_i
        last_point.blk = initial_last_point.blk + block_slope * (t_i - initial_last_point.ts) / MULTIPLIER
        _epoch += 1
        if t_i == block.timestamp:
            last_point.blk = block.number
            break
        else:
            self.point_history[_epoch] = last_point

    self.epoch = _epoch
    # Now point_history is filled until t=now

    if addr != empty(address):
        # If last point was in this block, the slope change has been applied already
        # But in such case we have 0 slope(s)
        last_point.slope += (u_new.slope - u_old.slope)
        last_point.bias += (u_new.bias - u_old.bias)
        if last_point.slope < 0:
            last_point.slope = 0
        if last_point.bias < 0:
            last_point.bias = 0

    # Record the changed point into history
    self.point_history[_epoch] = last_point

    if addr != empty(address):
        # Schedule the slope changes (slope is going down)
        # We subtract new_user_slope from [new_locked.end]
        # and add old_user_slope to [old_locked.end]
        if old_locked.end > block.timestamp:
            # old_dslope was <something> - u_old.slope, so we cancel that
            old_dslope += u_old.slope
            if new_locked.end == old_locked.end:
                old_dslope -= u_new.slope  # It was a new deposit, not extension
            self.slope_changes[old_locked.end] = old_dslope

        if new_locked.end > block.timestamp:
            if new_locked.end > old_locked.end:
                new_dslope -= u_new.slope  # old slope disappeared at this point
                self.slope_changes[new_locked.end] = new_dslope
            # else: we recorded it already in old_dslope

        # Now handle user history
        user_epoch: uint256 = self.user_point_epoch[addr] + 1

        self.user_point_epoch[addr] = user_epoch
        u_new.ts = block.timestamp
        u_new.blk = block.number
        self.user_point_history[addr][user_epoch] = u_new


@internal
def _deposit_for(_addr: address, _value: uint256, unlock_time: uint256, locked_balance: LockedBalance, type: int128):
    """
    @notice Deposit and lock tokens for a user
    @param _addr User's wallet address
    @param _value Amount to deposit
    @param unlock_time New time when to unlock the tokens, or 0 if unchanged
    @param locked_balance Previous locked amount / timestamp
    """
    # block all new deposits (and extensions) in case of unlocked contract
    assert (not self.all_unlock), "all unlocked,no sense"

    _locked: LockedBalance = locked_balance
    supply_before: uint256 = self.supply

    self.supply = supply_before + _value
    old_locked: LockedBalance = _locked
    # Adding to existing lock, or if a lock is expired - creating a new one
    _locked.amount += convert(_value, int128)
    if unlock_time != 0:
        _locked.end = unlock_time
    self.locked[_addr] = _locked

    # Possibilities:
    # Both old_locked.end could be current or expired (>/< block.timestamp)
    # value == 0 (extend lock) or value > 0 (add to lock or extend lock)
    # _locked.end > block.timestamp (always)
    self._checkpoint(_addr, old_locked, _locked)

    if _value != 0:
        assert ERC20(self.TOKEN).transferFrom(_addr, self, _value, default_return_value=True)

    log Deposit(_addr, _value, _locked.end, type, block.timestamp)
    log Supply(supply_before, supply_before + _value)


@external
def checkpoint():
    """
    @notice Record global data to checkpoint
    """
    self._checkpoint(empty(address), empty(LockedBalance), empty(LockedBalance))


@external
@nonreentrant("lock")
def deposit_for(_addr: address, _value: uint256):
    """
    @notice Deposit `_value` tokens for `_addr` and add to the lock
    @dev Anyone (even a smart contract) can deposit for someone else, but
         cannot extend their locktime and deposit for a brand new user
    @param _addr User's wallet address
    @param _value Amount to add to user's lock
    """
    _locked: LockedBalance = self.locked[_addr]

    assert _value > 0  # dev: need non-zero value
    assert _locked.amount > 0, "No existing lock found"
    assert _locked.end > block.timestamp, "Cannot add to expired lock. Withdraw"

    self._deposit_for(_addr, _value, 0, self.locked[_addr], DEPOSIT_FOR_TYPE)


@external
@nonreentrant("lock")
def create_lock(_value: uint256, _unlock_time: uint256):
    """
    @notice Deposit `_value` tokens for `msg.sender` and lock until `_unlock_time`
    @param _value Amount to deposit
    @param _unlock_time Epoch time when tokens unlock, rounded down to whole weeks
    """
    self.assert_not_contract(msg.sender)
    unlock_time: uint256 = (_unlock_time / WEEK) * WEEK  # Locktime is rounded down to weeks
    _locked: LockedBalance = self.locked[msg.sender]

    assert _value > 0  # dev: need non-zero value
    assert _locked.amount == 0, "Withdraw old tokens first"
    assert (unlock_time > block.timestamp), "Can only lock until time in the future"
    assert (unlock_time <= block.timestamp + self.MAXTIME), "Voting lock too long"

    self._deposit_for(msg.sender, _value, unlock_time, _locked, CREATE_LOCK_TYPE)


@external
@nonreentrant("lock")
def increase_amount(_value: uint256):
    """
    @notice Deposit `_value` additional tokens for `msg.sender`
            without modifying the unlock time
    @param _value Amount of tokens to deposit and add to the lock
    """
    self.assert_not_contract(msg.sender)
    _locked: LockedBalance = self.locked[msg.sender]

    assert _value > 0  # dev: need non-zero value
    assert _locked.amount > 0, "No existing lock found"
    assert _locked.end > block.timestamp, "Cannot add to expired lock. Withdraw"

    self._deposit_for(msg.sender, _value, 0, _locked, INCREASE_LOCK_AMOUNT)


@external
@nonreentrant("lock")
def increase_unlock_time(_unlock_time: uint256):
    """
    @notice Extend the unlock time for `msg.sender` to `_unlock_time`
    @param _unlock_time New epoch time for unlocking
    """
    self.assert_not_contract(msg.sender)
    _locked: LockedBalance = self.locked[msg.sender]
    unlock_time: uint256 = (_unlock_time / WEEK) * WEEK  # Locktime is rounded down to weeks

    assert _locked.end > block.timestamp, "Lock expired"
    assert _locked.amount > 0, "Nothing is locked"
    assert unlock_time > _locked.end, "Can only increase lock duration"
    assert (unlock_time <= block.timestamp + self.MAXTIME), "Voting lock too long"

    self._deposit_for(msg.sender, 0, unlock_time, _locked, INCREASE_UNLOCK_TIME)


@external
@nonreentrant("lock")
def withdraw():
    """
    @notice Withdraw all tokens for `msg.sender`
    @dev Only possible if the lock has expired
    """
    _locked: LockedBalance = self.locked[msg.sender]
    assert block.timestamp >= _locked.end or self.all_unlock, "lock !expire or !unlock"
    value: uint256 = convert(_locked.amount, uint256)

    old_locked: LockedBalance = _locked
    _locked.end = 0
    _locked.amount = 0
    self.locked[msg.sender] = _locked
    supply_before: uint256 = self.supply
    self.supply = supply_before - value

    # old_locked can have either expired <= timestamp or zero end
    # _locked has only 0 end
    # Both can have >= 0 amount
    self._checkpoint(msg.sender, old_locked, _locked)

    assert ERC20(self.TOKEN).transfer(msg.sender, value, default_return_value=True)

    log Withdraw(msg.sender, value, block.timestamp)
    log Supply(supply_before, supply_before - value)


@external
@nonreentrant("lock")
def withdraw_early():
    """
    @notice Withdraws locked tokens for `msg.sender` before lock-end with penalty
    @dev Only possible if `early_unlock` is enabled (true)
    By defualt there is linear formula for calculating penalty. 
    In some cases an admin can configure penalty speed using `set_early_unlock_penalty_speed()`
    
    L - lock amount
    k - penalty coefficient, defined by admin (default 1)
    Tleft - left time to unlock
    Tmax - MAXLOCK time
    Penalty amount = L * k * (Tlast / Tmax)
    """
    assert(self.early_unlock == True), "!early unlock"

    _locked: LockedBalance = self.locked[msg.sender]
    assert block.timestamp < _locked.end, "lock expired"

    value: uint256 = convert(_locked.amount, uint256)

    time_left: uint256 = _locked.end - block.timestamp
    
    # to avoid front-run with penalty_k
    penalty_k_: uint256 = 0
    if block.timestamp > self.penalty_upd_ts + PENALTY_COOLDOWN:
        penalty_k_ = self.penalty_k
    else:
        penalty_k_ = self.prev_penalty_k

    penalty_ratio: uint256 = (time_left * MULTIPLIER / self.MAXTIME) * penalty_k_
    penalty: uint256 = (value * penalty_ratio / MULTIPLIER) / PENALTY_MULTIPLIER    
    if penalty > value:
        penalty = value
    user_amount: uint256 = value - penalty

    old_locked: LockedBalance = _locked
    _locked.end = 0
    _locked.amount = 0
    self.locked[msg.sender] = _locked
    supply_before: uint256 = self.supply
    self.supply = supply_before - value

    # old_locked can have either expired <= timestamp or zero end
    # _locked has only 0 end
    # Both can have >= 0 amount
    self._checkpoint(msg.sender, old_locked, _locked)

    if penalty > 0:
        assert ERC20(self.TOKEN).transfer(self.penalty_treasury, penalty, default_return_value=True)
    if user_amount > 0:
        assert ERC20(self.TOKEN).transfer(msg.sender, user_amount, default_return_value=True)

    log Withdraw(msg.sender, value, block.timestamp)
    log Supply(supply_before, supply_before - value)
    log WithdrawEarly(msg.sender, penalty, time_left)


# The following ERC20/minime-compatible methods are not real balanceOf and supply!
# They measure the weights for the purpose of voting, so they don't represent
# real coins.

@internal
@view
def find_block_epoch(_block: uint256, max_epoch: uint256) -> uint256:
    """
    @notice Binary search to find epoch containing block number
    @param _block Block to find
    @param max_epoch Don't go beyond this epoch
    @return Epoch which contains _block
    """
    # Binary search
    _min: uint256 = 0
    _max: uint256 = max_epoch
    for i in range(128):  # Will be always enough for 128-bit numbers
        if _min >= _max:
            break
        _mid: uint256 = (_min + _max + 1) / 2
        if self.point_history[_mid].blk <= _block:
            _min = _mid
        else:
            _max = _mid - 1
    return _min

@internal
@view
def find_timestamp_epoch(_timestamp: uint256, max_epoch: uint256) -> uint256:
    """
    @notice Binary search to find epoch for timestamp
    @param _timestamp timestamp to find
    @param max_epoch Don't go beyond this epoch
    @return Epoch which contains _timestamp
    """
    # Binary search
    _min: uint256 = 0
    _max: uint256 = max_epoch
    for i in range(128):  # Will be always enough for 128-bit numbers
        if _min >= _max:
            break
        _mid: uint256 = (_min + _max + 1) / 2
        if self.point_history[_mid].ts <= _timestamp:
            _min = _mid
        else:
            _max = _mid - 1
    return _min


@internal
@view
def find_block_user_epoch(_addr: address, _block: uint256, max_epoch: uint256) -> uint256:
    """
    @notice Binary search to find epoch for block number
    @param _addr User for which to find user epoch for
    @param _block Block to find
    @param max_epoch Don't go beyond this epoch
    @return Epoch which contains _block
    """
    # Binary search
    _min: uint256 = 0
    _max: uint256 = max_epoch
    for i in range(128):  # Will be always enough for 128-bit numbers
        if _min >= _max:
            break
        _mid: uint256 = (_min + _max + 1) / 2
        if self.user_point_history[_addr][_mid].blk <= _block:
            _min = _mid
        else:
            _max = _mid - 1
    return _min


@internal
@view
def find_timestamp_user_epoch(_addr: address, _timestamp: uint256, max_epoch: uint256) -> uint256:
    """
    @notice Binary search to find user epoch for timestamp
    @param _addr User for which to find user epoch for
    @param _timestamp timestamp to find
    @param max_epoch Don't go beyond this epoch
    @return Epoch which contains _timestamp
    """
    # Binary search
    _min: uint256 = 0
    _max: uint256 = max_epoch
    for i in range(128):  # Will be always enough for 128-bit numbers
        if _min >= _max:
            break
        _mid: uint256 = (_min + _max + 1) / 2
        if self.user_point_history[_addr][_mid].ts <= _timestamp:
            _min = _mid
        else:
            _max = _mid - 1
    return _min

@external
@view
def balanceOf(addr: address, _t: uint256 = block.timestamp) -> uint256:
    """
    @notice Get the current voting power for `msg.sender`
    @dev Adheres to the ERC20 `balanceOf` interface for Aragon compatibility
    @param addr User wallet address
    @param _t Epoch time to return voting power at
    @return User voting power
    """
    _epoch: uint256 = 0
    if _t == block.timestamp:
        # No need to do binary search, will always live in current epoch
        _epoch = self.user_point_epoch[addr]
    else:
        _epoch = self.find_timestamp_user_epoch(addr, _t, self.user_point_epoch[addr])

    if _epoch == 0:
        return 0
    else:
        last_point: Point = self.user_point_history[addr][_epoch]
        last_point.bias -= last_point.slope * convert(_t - last_point.ts, int128)
        if last_point.bias < 0:
            last_point.bias = 0
        return convert(last_point.bias, uint256)


@external
@view
def balanceOfAt(addr: address, _block: uint256) -> uint256:
    """
    @notice Measure voting power of `addr` at block height `_block`
    @dev Adheres to MiniMe `balanceOfAt` interface: https://github.com/Giveth/minime
    @param addr User's wallet address
    @param _block Block to calculate the voting power at
    @return Voting power
    """
    # Copying and pasting totalSupply code because Vyper cannot pass by
    # reference yet
    assert _block <= block.number

    _user_epoch: uint256 = self.find_block_user_epoch(addr, _block, self.user_point_epoch[addr])
    upoint: Point = self.user_point_history[addr][_user_epoch]

    max_epoch: uint256 = self.epoch
    _epoch: uint256 = self.find_block_epoch(_block, max_epoch)
    point_0: Point = self.point_history[_epoch]
    d_block: uint256 = 0
    d_t: uint256 = 0
    if _epoch < max_epoch:
        point_1: Point = self.point_history[_epoch + 1]
        d_block = point_1.blk - point_0.blk
        d_t = point_1.ts - point_0.ts
    else:
        d_block = block.number - point_0.blk
        d_t = block.timestamp - point_0.ts
    block_time: uint256 = point_0.ts
    if d_block != 0:
        block_time += d_t * (_block - point_0.blk) / d_block

    upoint.bias -= upoint.slope * convert(block_time - upoint.ts, int128)
    if upoint.bias >= 0:
        return convert(upoint.bias, uint256)
    else:
        return 0


@internal
@view
def supply_at(point: Point, t: uint256) -> uint256:
    """
    @notice Calculate total voting power at some point in the past
    @param point The point (bias/slope) to start search from
    @param t Time to calculate the total voting power at
    @return Total voting power at that time
    """
    last_point: Point = point
    t_i: uint256 = (last_point.ts / WEEK) * WEEK
    for i in range(255):
        t_i += WEEK
        d_slope: int128 = 0
        if t_i > t:
            t_i = t
        else:
            d_slope = self.slope_changes[t_i]
        last_point.bias -= last_point.slope * convert(t_i - last_point.ts, int128)
        if t_i == t:
            break
        last_point.slope += d_slope
        last_point.ts = t_i

    if last_point.bias < 0:
        last_point.bias = 0
    return convert(last_point.bias, uint256)


@external
@view
def totalSupply(t: uint256 = block.timestamp) -> uint256:
    """
    @notice Calculate total voting power
    @dev Adheres to the ERC20 `totalSupply` interface for Aragon compatibility
    @return Total voting power
    """
    _epoch: uint256 = 0
    if t == block.timestamp:
        # No need to do binary search, will always live in current epoch
        _epoch = self.epoch
    else:
        _epoch = self.find_timestamp_epoch(t, self.epoch)

    if _epoch == 0:
        return 0
    else:
        last_point: Point = self.point_history[_epoch]
        return self.supply_at(last_point, t)


@external
@view
def totalSupplyAt(_block: uint256) -> uint256:
    """
    @notice Calculate total voting power at some point in the past
    @param _block Block to calculate the total voting power at
    @return Total voting power at `_block`
    """
    assert _block <= block.number
    _epoch: uint256 = self.epoch
    target_epoch: uint256 = self.find_block_epoch(_block, _epoch)

    point: Point = self.point_history[target_epoch]
    dt: uint256 = 0
    if target_epoch < _epoch:
        point_next: Point = self.point_history[target_epoch + 1]
        if point.blk != point_next.blk:
            dt = (_block - point.blk) * (point_next.ts - point.ts) / (point_next.blk - point.blk)
    else:
        if point.blk != block.number:
            dt = (_block - point.blk) * (block.timestamp - point.ts) / (block.number - point.blk)
    # Now dt contains info on how far are we beyond point

    return self.supply_at(point, point.ts + dt)

@external
@nonreentrant("lock")
def claimExternalRewards():
    """
    @notice Claims BAL rewards
    @dev Only possible if the TOKEN is Guage contract
    """
    BalancerMinter(self.balMinter).mint(self.TOKEN)
    balBalance: uint256 = ERC20(self.balToken).balanceOf(self)
    if balBalance > 0:
        # distributes rewards using rewardDistributor into current week
        if self.rewardReceiver == self.rewardDistributor:
            assert ERC20(self.balToken).approve(self.rewardDistributor, balBalance, default_return_value=True)
            RewardDistributor(self.rewardDistributor).depositToken(self.balToken, balBalance)
        else:
            assert ERC20(self.balToken).transfer(self.rewardReceiver, balBalance, default_return_value=True)


@external
def changeRewardReceiver(newReceiver: address):
    """
    @notice Changes the reward receiver address
    @param newReceiver New address to set as the reward receiver
    """
    assert msg.sender == self.admin, '!admin'
    assert (self.rewardReceiverChangeable), '!available'
    assert newReceiver != empty(address), '!empty'

    self.rewardReceiver = newReceiver
    log RewardReceiver(newReceiver)

Contract ABI

[{"name":"CommitOwnership","inputs":[{"name":"admin","type":"address","indexed":false}],"anonymous":false,"type":"event"},{"name":"ApplyOwnership","inputs":[{"name":"admin","type":"address","indexed":false}],"anonymous":false,"type":"event"},{"name":"EarlyUnlock","inputs":[{"name":"status","type":"bool","indexed":false}],"anonymous":false,"type":"event"},{"name":"PenaltySpeed","inputs":[{"name":"penalty_k","type":"uint256","indexed":false}],"anonymous":false,"type":"event"},{"name":"PenaltyTreasury","inputs":[{"name":"penalty_treasury","type":"address","indexed":false}],"anonymous":false,"type":"event"},{"name":"TotalUnlock","inputs":[{"name":"status","type":"bool","indexed":false}],"anonymous":false,"type":"event"},{"name":"RewardReceiver","inputs":[{"name":"newReceiver","type":"address","indexed":false}],"anonymous":false,"type":"event"},{"name":"Deposit","inputs":[{"name":"provider","type":"address","indexed":true},{"name":"value","type":"uint256","indexed":false},{"name":"locktime","type":"uint256","indexed":true},{"name":"type","type":"int128","indexed":false},{"name":"ts","type":"uint256","indexed":false}],"anonymous":false,"type":"event"},{"name":"Withdraw","inputs":[{"name":"provider","type":"address","indexed":true},{"name":"value","type":"uint256","indexed":false},{"name":"ts","type":"uint256","indexed":false}],"anonymous":false,"type":"event"},{"name":"WithdrawEarly","inputs":[{"name":"provider","type":"address","indexed":true},{"name":"penalty","type":"uint256","indexed":false},{"name":"time_left","type":"uint256","indexed":false}],"anonymous":false,"type":"event"},{"name":"Supply","inputs":[{"name":"prevSupply","type":"uint256","indexed":false},{"name":"supply","type":"uint256","indexed":false}],"anonymous":false,"type":"event"},{"stateMutability":"nonpayable","type":"function","name":"initialize","inputs":[{"name":"_token_addr","type":"address"},{"name":"_name","type":"string"},{"name":"_symbol","type":"string"},{"name":"_admin_addr","type":"address"},{"name":"_admin_unlock_all","type":"address"},{"name":"_admin_early_unlock","type":"address"},{"name":"_max_time","type":"uint256"},{"name":"_balToken","type":"address"},{"name":"_balMinter","type":"address"},{"name":"_rewardReceiver","type":"address"},{"name":"_rewardReceiverChangeable","type":"bool"},{"name":"_rewardDistributor","type":"address"}],"outputs":[]},{"stateMutability":"view","type":"function","name":"token","inputs":[],"outputs":[{"name":"","type":"address"}]},{"stateMutability":"view","type":"function","name":"name","inputs":[],"outputs":[{"name":"","type":"string"}]},{"stateMutability":"view","type":"function","name":"symbol","inputs":[],"outputs":[{"name":"","type":"string"}]},{"stateMutability":"view","type":"function","name":"decimals","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"nonpayable","type":"function","name":"commit_transfer_ownership","inputs":[{"name":"addr","type":"address"}],"outputs":[]},{"stateMutability":"nonpayable","type":"function","name":"apply_transfer_ownership","inputs":[],"outputs":[]},{"stateMutability":"nonpayable","type":"function","name":"commit_smart_wallet_checker","inputs":[{"name":"addr","type":"address"}],"outputs":[]},{"stateMutability":"nonpayable","type":"function","name":"apply_smart_wallet_checker","inputs":[],"outputs":[]},{"stateMutability":"nonpayable","type":"function","name":"set_early_unlock","inputs":[{"name":"_early_unlock","type":"bool"}],"outputs":[]},{"stateMutability":"nonpayable","type":"function","name":"set_early_unlock_penalty_speed","inputs":[{"name":"_penalty_k","type":"uint256"}],"outputs":[]},{"stateMutability":"nonpayable","type":"function","name":"set_penalty_treasury","inputs":[{"name":"_penalty_treasury","type":"address"}],"outputs":[]},{"stateMutability":"nonpayable","type":"function","name":"set_all_unlock","inputs":[],"outputs":[]},{"stateMutability":"view","type":"function","name":"get_last_user_slope","inputs":[{"name":"addr","type":"address"}],"outputs":[{"name":"","type":"int128"}]},{"stateMutability":"view","type":"function","name":"user_point_history__ts","inputs":[{"name":"_addr","type":"address"},{"name":"_idx","type":"uint256"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"locked__end","inputs":[{"name":"_addr","type":"address"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"nonpayable","type":"function","name":"checkpoint","inputs":[],"outputs":[]},{"stateMutability":"nonpayable","type":"function","name":"deposit_for","inputs":[{"name":"_addr","type":"address"},{"name":"_value","type":"uint256"}],"outputs":[]},{"stateMutability":"nonpayable","type":"function","name":"create_lock","inputs":[{"name":"_value","type":"uint256"},{"name":"_unlock_time","type":"uint256"}],"outputs":[]},{"stateMutability":"nonpayable","type":"function","name":"increase_amount","inputs":[{"name":"_value","type":"uint256"}],"outputs":[]},{"stateMutability":"nonpayable","type":"function","name":"increase_unlock_time","inputs":[{"name":"_unlock_time","type":"uint256"}],"outputs":[]},{"stateMutability":"nonpayable","type":"function","name":"withdraw","inputs":[],"outputs":[]},{"stateMutability":"nonpayable","type":"function","name":"withdraw_early","inputs":[],"outputs":[]},{"stateMutability":"view","type":"function","name":"balanceOf","inputs":[{"name":"addr","type":"address"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"balanceOf","inputs":[{"name":"addr","type":"address"},{"name":"_t","type":"uint256"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"balanceOfAt","inputs":[{"name":"addr","type":"address"},{"name":"_block","type":"uint256"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"totalSupply","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"totalSupply","inputs":[{"name":"t","type":"uint256"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"totalSupplyAt","inputs":[{"name":"_block","type":"uint256"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"nonpayable","type":"function","name":"claimExternalRewards","inputs":[],"outputs":[]},{"stateMutability":"nonpayable","type":"function","name":"changeRewardReceiver","inputs":[{"name":"newReceiver","type":"address"}],"outputs":[]},{"stateMutability":"view","type":"function","name":"MAXTIME","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"TOKEN","inputs":[],"outputs":[{"name":"","type":"address"}]},{"stateMutability":"view","type":"function","name":"supply","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"locked","inputs":[{"name":"arg0","type":"address"}],"outputs":[{"name":"","type":"tuple","components":[{"name":"amount","type":"int128"},{"name":"end","type":"uint256"}]}]},{"stateMutability":"view","type":"function","name":"epoch","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"point_history","inputs":[{"name":"arg0","type":"uint256"}],"outputs":[{"name":"","type":"tuple","components":[{"name":"bias","type":"int128"},{"name":"slope","type":"int128"},{"name":"ts","type":"uint256"},{"name":"blk","type":"uint256"}]}]},{"stateMutability":"view","type":"function","name":"user_point_history","inputs":[{"name":"arg0","type":"address"},{"name":"arg1","type":"uint256"}],"outputs":[{"name":"","type":"tuple","components":[{"name":"bias","type":"int128"},{"name":"slope","type":"int128"},{"name":"ts","type":"uint256"},{"name":"blk","type":"uint256"}]}]},{"stateMutability":"view","type":"function","name":"user_point_epoch","inputs":[{"name":"arg0","type":"address"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"slope_changes","inputs":[{"name":"arg0","type":"uint256"}],"outputs":[{"name":"","type":"int128"}]},{"stateMutability":"view","type":"function","name":"future_smart_wallet_checker","inputs":[],"outputs":[{"name":"","type":"address"}]},{"stateMutability":"view","type":"function","name":"smart_wallet_checker","inputs":[],"outputs":[{"name":"","type":"address"}]},{"stateMutability":"view","type":"function","name":"admin","inputs":[],"outputs":[{"name":"","type":"address"}]},{"stateMutability":"view","type":"function","name":"admin_unlock_all","inputs":[],"outputs":[{"name":"","type":"address"}]},{"stateMutability":"view","type":"function","name":"admin_early_unlock","inputs":[],"outputs":[{"name":"","type":"address"}]},{"stateMutability":"view","type":"function","name":"future_admin","inputs":[],"outputs":[{"name":"","type":"address"}]},{"stateMutability":"view","type":"function","name":"is_initialized","inputs":[],"outputs":[{"name":"","type":"bool"}]},{"stateMutability":"view","type":"function","name":"early_unlock","inputs":[],"outputs":[{"name":"","type":"bool"}]},{"stateMutability":"view","type":"function","name":"penalty_k","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"prev_penalty_k","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"penalty_upd_ts","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"penalty_treasury","inputs":[],"outputs":[{"name":"","type":"address"}]},{"stateMutability":"view","type":"function","name":"balMinter","inputs":[],"outputs":[{"name":"","type":"address"}]},{"stateMutability":"view","type":"function","name":"balToken","inputs":[],"outputs":[{"name":"","type":"address"}]},{"stateMutability":"view","type":"function","name":"rewardReceiver","inputs":[],"outputs":[{"name":"","type":"address"}]},{"stateMutability":"view","type":"function","name":"rewardReceiverChangeable","inputs":[],"outputs":[{"name":"","type":"bool"}]},{"stateMutability":"view","type":"function","name":"rewardDistributor","inputs":[],"outputs":[{"name":"","type":"address"}]},{"stateMutability":"view","type":"function","name":"all_unlock","inputs":[],"outputs":[{"name":"","type":"bool"}]}]

Block Transaction Gas Used Reward
view all blocks produced

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
[ Download: CSV Export  ]
[ Download: CSV Export  ]

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.