Beispiel #1
0
def run_trigger(new_logs):
    """ Template trigger """
    events = []

    for ev in get_events(new_logs):
        threshold = decode_single('(uint256)', decode_hex(ev.data))[0]

        if ev.topic_0 == SIG_EVENT_ALLOCATE_THRESHOLD:
            events.append(event_normal(
                "Vault Allocate Threshold Changed   ЁЯез",
                "OUSD Vault allocation deposit threshold was changed to {} "
                "units".format(threshold),
                log_model=ev
            ))

        elif ev.topic_0 == SIG_EVENT_REBASE_THRESHOLD:
            events.append(event_normal(
                "Vault Rebase Threshold Changed   ЁЯН▒",
                "OUSD Vault rebase threshold was changed to {} "
                "units".format(threshold),
                log_model=ev
            ))

        else:
            raise Exception("You never saw this")

    return events
def run_trigger(new_logs):
    """ Template trigger """
    events = []

    for ev in get_events(new_logs):
        recipient, amount = decode_single('(address,uint256)',
                                          decode_hex(ev.data))

        if amount == 0:
            continue

        contract_name = CONTRACT_ADDR_TO_NAME.get(ev.address, ev.address)
        """ Event doesn't include token address, so if it's not from STRATCOMP,
        I have no idea what it might be.
        """
        reward_token = '[UKNOWN]'
        if ev.address == STRATCOMP:
            reward_token = 'COMP'
        elif ev.address == STRAT3POOL:
            reward_token = 'CRV'

        events.append(
            event_normal("Reward tokens have been collected   💸",
                         "{} {} has been collected by {}\n".format(
                             format_token_human('COMP', amount),
                             reward_token,
                             contract_name,
                         ),
                         log_model=ev))

    return events
Beispiel #3
0
def run_trigger(snapshot_cursor, latest_snapshot_block, oracle_snapshots):
    """ Trigger when oracle prices deviate too far """

    # Don't repeat alerts for the same snapshot
    if snapshot_cursor.block_number == latest_snapshot_block:
        return []

    events = []
    outliers = get_outlier_prices(latest_snapshot_block, oracle_snapshots)

    if outliers:
        for snap in outliers:
            price = format_decimal(snap.price)

            events.append(
                event_normal(
                    "{} price deviation   🧙‍♀️".format(
                        ORACLE_TO_NAME[snap.oracle]
                    ),
                    "{} @ {} {}\n\n".format(
                        snap.ticker_left,
                        price,
                        snap.ticker_right,
                    ),
                    block_number=snap.block_number
                )
            )

    return events
Beispiel #4
0
def run_trigger(ogn_staking_snapshot):
    """ Trigger to alert when the buffer of OGN to cover new staking rewards is
    low """
    events = []

    # If no snapshot has been made yet, ignore
    if ogn_staking_snapshot is None:
        return events

    block_number = ogn_staking_snapshot.block_number
    balance = ogn_staking_snapshot.ogn_balance
    outstanding = ogn_staking_snapshot.total_outstanding
    diff = balance - outstanding

    if diff < 1:
        # Should be impossible due to contract protections
        events.append(
            event_critical("Impossible staking condition   ��",
                           "OGN Staking has less OGN than expected rewards "
                           "({} OGN)".format(diff),
                           tags=EVENT_TAGS,
                           block_number=block_number))
    elif diff < LOW_RED:
        # �
        events.append(
            event_critical(
                "Critical OGN Staking contract buffer   🟥",
                "OGN Staking contract rewards buffer down to {}".format(
                    format_ogn_human(diff)),
                tags=EVENT_TAGS,
                block_number=block_number))
    elif diff < LOW_ORANGE:
        events.append(
            event_high("Low OGN Staking contract buffer   🟧",
                       "OGN Staking contract rewards buffer down to {}".format(
                           format_ogn_human(diff)),
                       tags=EVENT_TAGS,
                       block_number=block_number))
    elif diff < LOW_YELLOW:
        events.append(
            event_normal(
                "OGN Staking contract buffer   🟨",
                "OGN Staking contract rewards buffer down to {}".format(
                    format_ogn_human(diff)),
                tags=EVENT_TAGS,
                block_number=block_number))
    # Debug only
    elif settings.DEBUG:
        events.append(
            event_low("Nominal OGN Staking contract buffer   🟩",
                      "OGN Staking contract rewards buffer down to {}".format(
                          format_ogn_human(diff)),
                      tags=EVENT_TAGS,
                      block_number=block_number))

    return events
def run_trigger(new_logs):
    """ Compound Timelock changes """
    events = []

    for ev in get_events(new_logs):
        if ev.topic_0 == SIG_EVENT_VOTING_DELAY_SET:
            old_delay, new_delay = decode_single("(uint256,uint256)",
                                                 decode_hex(ev.data))

            events.append(
                event_normal(
                    "Compound GovernorBravo voting delay changed   🗳️ 🕖",
                    "Compound GovernorBravo voting delay changed from {} blocks to {} blocks"
                    .format(old_delay, new_delay),
                    log_model=ev))

        elif ev.topic_0 == SIG_EVENT_VOTING_PERIOD_SET:
            old_period, new_period = decode_single("(uint256,uint256)",
                                                   decode_hex(ev.data))

            events.append(
                event_normal(
                    "Compound GovernorBravo voting delay changed   🗳️ 🕗",
                    "Compound GovernorBravo voting period changed from {} blocks to {} blocks"
                    .format(old_period, new_period),
                    log_model=ev))

        elif ev.topic_0 == SIG_EVENT_PROPOSAL_THRESHOLD_SET:
            old_threshold, new_threshold = decode_single(
                "(uint256,uint256)", decode_hex(ev.data))

            old_human = Decimal(old_threshold) / E_18
            new_human = Decimal(new_threshold) / E_18

            events.append(
                event_normal(
                    "Compound GovernorBravo voting threshold changed   🗳️ 🪙",
                    "Compound GovernorBravo voting threshold changed from {} COMP to {} COMP"
                    .format(old_human, new_human),
                    log_model=ev))

    return events
Beispiel #6
0
def run_trigger(new_logs):
    """ Template trigger """
    events = []

    for ev in get_events(new_logs):
        address = decode_single('(address)', decode_hex(ev.data))[0]

        events.append(event_normal(
            "Vault Strategist Changed   🕴️",
            "The new vault strategist is {} ".format(address),
            log_model=ev
        ))

    return events
def run_trigger(new_logs):
    """ Template trigger """
    events = []

    for ev in get_events(new_logs):
        address = decode_single('(address)', decode_hex(ev.data))[0]

        events.append(
            event_normal(
                "Vault Uniswap V2 Router Address Changed   🦄",
                "Uniswap V2 Router was changed to {} ".format(address),
                log_model=ev))

    return events
Beispiel #8
0
def run_trigger(new_logs):
    """ Template trigger """
    events = []

    for ev in get_events(new_logs):
        # Basis points in 18 decimal bigint
        oracle_address = decode_single('(address)', decode_hex(ev.data))[0]

        events.append(
            event_normal(
                "Vault Oracle Changed   🧙",
                "OUSD Vault oracle was changed to {}%".format(oracle_address),
                log_model=ev))

    return events
Beispiel #9
0
def run_trigger(new_logs):
    """ Template trigger """
    events = []

    for ev in get_events(new_logs):
        # Basis points in 18 decimal bigint
        bps_int = decode_single('(uint256)', decode_hex(ev.data))[0]
        bps = Decimal(bps_int) / Decimal(10000)

        events.append(
            event_normal(
                "Vault Redeem Fee Updated   🦴",
                "OUSD Vault redeem fee was changed to {}%".format(bps),
                log_model=ev))

    return events
def run_trigger(new_logs):
    """ Template trigger """
    events = []

    for ev in get_events(new_logs):
        asset = decode_single('(address)', decode_hex(ev.topic_1))[0]
        ptoken = decode_single('(address)', decode_hex(ev.data))[0]

        asset_name = SYMBOL_FOR_CONTRACT.get(asset, asset)
        contract_name = CONTRACT_ADDR_TO_NAME.get(ev.address, ev.address)

        if ev.topic_0 == SIG_EVENT_PTOKEN_ADDED:
            events.append(
                event_normal(
                    "Platform token has been set   🪆",
                    "**Strategy**: {}\n"
                    "**Asset**: {}\n"
                    "**Platform Token**: {}\n".format(
                        contract_name,
                        asset_name,
                        ptoken,
                    ),
                    log_model=ev
                )
            )

        elif ev.topic_0 == SIG_EVENT_PTOKEN_REMOVED:
            events.append(
                event_high(
                    "Platform token has been unset   🕳️",
                    "**Strategy**: {}\n"
                    "**Asset**: {}\n"
                    "**Platform Token**: {}\n".format(
                        contract_name,
                        asset_name,
                        ptoken,
                    ),
                    log_model=ev
                )
            )

        else:
            # Theoretically impossible
            raise Exception('Unexpected event')

    return events
Beispiel #11
0
def run_trigger(new_logs):
    """ Look for mints and redeems """
    events = []

    for ev in get_mint_redeem_events(new_logs):
        is_mint = ev.topic_0 == SIG_EVENT_MINT
        addr, value = decode_single('(address,uint256)', decode_hex(ev.data))

        events.append(
            event_normal("Mint   🪙" if is_mint else "Redeem   💵",
                         "{} OUSD was {}".format(
                             format_ousd_human(Decimal(value) / Decimal(1e18)),
                             "minted" if is_mint else "redeemed",
                         ),
                         log_model=ev))

    return events
Beispiel #12
0
def tx_error_event(tx_hash,
                   contract_name,
                   block_number=0,
                   transaction_index=0):
    """ Create an event for a transaction error

    TODO: Make this more intelligent and informative
    """
    return event_normal(
        "Transaction Error   🛑",
        "A transaction error has occurred on the {} contract\n\n"
        "https://etherscan.io/tx/{}".format(
            contract_name,
            tx_hash,
        ),
        block_number=block_number,
        transaction_index=transaction_index)
def run_trigger(new_logs):
    """ Template trigger """
    events = []

    for ev in get_events(new_logs):
        buffer_percent_bigint = decode_single('(uint256)',
                                              decode_hex(ev.data))[0]

        events.append(
            event_normal(
                "Vault Buffer Updated   🤏",
                "OUSD Vault buffer was changed to {}%".format(
                    # Always whole numbers?
                    (Decimal(buffer_percent_bigint) / Decimal(1e18)) *
                    Decimal(100)),
                log_model=ev))

    return events
def run_trigger(new_transfers):
    """ Check OUSD transactions for burns """
    events = []

    # Burns shouldn't happen except on redeems, which are ignored here.
    burns = get_burns(new_transfers)

    if burns:
        for burn in burns:
            events.append(
                event_normal(
                    "Burn   🔥",
                    "{} burned {} OUSD".format(
                        burn.from_address,
                        burn.amount
                    ),
                    block_number=burn.tx_hash.block_number,
                    transaction_index=burn.tx_hash.transaction_index,
                    log_index=burn.log_index,
                )
            )

    return events
Beispiel #15
0
def run_trigger(new_logs):
    """ Compound Timelock changes """
    events = []

    for ev in get_events(new_logs):
        if ev.topic_0 == SIG_EVENT_AAVE_PROPOSAL_CREATED:
            """
            event ProposalCreated(
                uint256 indexed proposalId,
                bytes32 indexed ipfsHash,
                bytes32 indexed proposalType,
                uint256 propositionPowerOfCreator,
                uint256 threshold,
                uint256 maxMovesToVotingAllowed,
                uint256 votingBlocksDuration,
                uint256 validatingBlocksDuration,
                address proposalExecutor
            )
            """
            proposal_id = decode_single("(uint256)", decode_hex(ev.topic_1))[0]
            ipfs_hash = decode_single("(bytes32)", decode_hex(ev.topic_2))[0]
            # proposal_type = decode_single(
            #     "(bytes32)",
            #     decode_hex(ev.topic_3)
            # )[0]
            (
                proposition_power_of_creator,
                threshold,
                max_moves_to_voting_allowed,
                voting_blocks_duration,
                validating_blocks_duration,
                proposal_executor,
            ) = decode_single(
                "(uint256,uint256,uint256,uint256,uint256,address)",
                decode_hex(ev.data))

            b58_ipfs_data = decode_ipfs_hash(encode_hex(ipfs_hash))
            ipfs_data = fetch_ipfs_json(b58_ipfs_data)
            prop_headers = parse_prop_headers(ipfs_data)
            aip = prop_headers.get('aip')

            if aip:
                aip_link = 'https://aave.github.io/aip/AIP-{}'.format(aip)
            else:
                aip_link = '[UNKNOWN AIP]'

            events.append(
                event_high(
                    "Aave (v1) Governance Proposal Created (ID: {})   🗳️ 🆕".
                    format(proposal_id),
                    "A new proposal (ID: {}) has been submitted for Aave"
                    "\n\n"
                    "**Title**: {}\n"
                    "**Description**: {}\n\n"
                    "**Threshold**: {} AAVE\n"
                    "**Voting Duration** {} blocks (approx {} days)\n"
                    "**Validating Duration** {} blocks (approx {} days)\n"
                    "**Executor**: {}\n"
                    "**IPFS Hash**: {}\n"
                    "{}".format(
                        proposal_id,
                        ipfs_data.get('title'),
                        ipfs_data.get('shortDescription'),
                        format_token_human('AAVE', Decimal(threshold)),
                        voting_blocks_duration,
                        round(
                            Decimal(voting_blocks_duration) / BLOCKS_PER_DAY,
                            2),
                        validating_blocks_duration,
                        round(
                            Decimal(validating_blocks_duration) /
                            BLOCKS_PER_DAY, 2),
                        proposal_executor,
                        decode_ipfs_hash(encode_hex(ipfs_hash)),
                        aip_link,
                    ),
                    log_model=ev))

        elif ev.topic_0 == SIG_EVENT_STATUS_CHANGE_TO_VALIDATING:
            # StatusChangeToValidating(uint256 indexed proposalId)
            proposal_id = decode_single("(uint256)", decode_hex(ev.topic_1))[0]

            events.append(
                event_high(
                    "Aave proposal moved to validating   🗳️ 🔍",
                    "Aave proposal #{} is now in validating stage awaiting "
                    "challenges".format(proposal_id),
                    log_model=ev))

        elif ev.topic_0 == SIG_EVENT_STATUS_CHANGE_TO_VOTING:
            """
            StatusChangeToVoting(
                uint256 indexed proposalId,
                uint256 movesToVoting
            )
            """
            proposal_id = decode_single("(uint256)", decode_hex(ev.topic_1))[0]

            events.append(
                event_high("Aave proposal moved to voting   🗳️ 📥",
                           "Aave proposal #{} is now in voting stage".format(
                               proposal_id),
                           log_model=ev))

        elif ev.topic_0 == SIG_EVENT_STATUS_CHANGE_TO_EXECUTED:
            """ StatusChangeToExecuted(uint256 indexed proposalId) """
            proposal_id = decode_single("(uint256)", decode_hex(ev.topic_1))[0]

            events.append(
                event_normal("Aave proposal has been resolved   🗳️ ⚙️",
                             "Aave proposal #{} has now been resolved".format(
                                 proposal_id),
                             log_model=ev))

        elif ev.topic_0 == SIG_EVENT_WINS_YES:
            """
            YesWins(
                uint256 indexed proposalId,
                uint256 abstainVotingPower,
                uint256 yesVotingPower,
                uint256 noVotingPower
            )
            """
            proposal_id = decode_single("(uint256)", decode_hex(ev.topic_1))[0]

            events.append(
                event_high(
                    "Aave proposal has been passed   🗳️ ✅",
                    "Aave proposal #{} has been passed".format(proposal_id),
                    log_model=ev))

        elif ev.topic_0 == SIG_EVENT_WINS_NO:
            """
            NoWins(
                uint256 indexed proposalId,
                uint256 abstainVotingPower,
                uint256 yesVotingPower,
                uint256 noVotingPower
            )
            """
            proposal_id = decode_single("(uint256)", decode_hex(ev.topic_1))[0]

            events.append(
                event_high(
                    "Aave proposal has failed   🗳️ ❎",
                    "Aave proposal #{} has been rejected".format(proposal_id),
                    log_model=ev))

        elif ev.topic_0 == SIG_EVENT_WINS_ABSTAIN:
            """
            AbstainWins(
                uint256 indexed proposalId,
                uint256 abstainVotingPower,
                uint256 yesVotingPower,
                uint256 noVotingPower
            )
            """
            proposal_id = decode_single("(uint256)", decode_hex(ev.topic_1))[0]

            events.append(
                event_high(
                    "Aave proposal has failed by abstention   🗳️ 〰️",
                    "Aave proposal #{} has been rejected by abstention".format(
                        proposal_id),
                    log_model=ev))

    return events
def run_trigger(new_logs):
    """ Template trigger """
    events = []

    for ev in get_events(new_logs):

        if ev.topic_0 == SIG_EVENT_START_VOTE:
            # StartVote(
            #     uint256 indexed voteId,
            #     address indexed creator,
            #     string metadata,
            #     uint256 minBalance,
            #     uint256 minTime,
            #     uint256 totalSupply,
            #     uint256 creatorVotingPower
            # )
            vote_id = decode_single('(uint256)', decode_hex(ev.topic_1))[0]
            creator = decode_single('(address)', decode_hex(ev.topic_2))[0]
            (
                metadata_hash,
                min_balance,
                min_time,
                total_supply,
                creator_voting_power,
            ) = decode_single("(string,uint256,uint256,uint256,uint256)",
                              decode_hex(ev.data))

            metadata = fetch_ipfs_json(
                strip_terrible_ipfs_prefix(metadata_hash))
            """ 51% Voting contract is for contract deployments and 60% Voting
            contract is for parameter changes
            """
            vote_url = 'https://dao.curve.fi/vote/{}/{}'.format(
                'ownership' if ev.address == CURVE_ARAGON_51 else 'parameter',
                vote_id,
            )

            details = ("**Creator**: {} \n"
                       "**Creator Voting Power**: {} veCRV\n"
                       "**Minimum vote balance**: {} veCRV\n"
                       "**Current total supply**: {} veCRV\n"
                       "**Minimum time**: {}\n"
                       "**Metadata**: {}\n\n"
                       "{}").format(
                           creator,
                           format_token_human('veCRV', creator_voting_power),
                           format_token_human('veCRV', min_balance),
                           format_token_human('veCRV', total_supply),
                           format_timedelta(timedelta(seconds=min_time)),
                           metadata.get('text', 'NO METADATA TEXT FOUND.'),
                           vote_url,
                       )

            events.append(
                event_high("{} - Vote Created ({})   🗳️ 🆕".format(
                    CONTRACT_ADDR_TO_NAME.get(ev.address, ev.address),
                    vote_id,
                ),
                           details,
                           log_model=ev))

        elif ev.topic_0 == SIG_EVENT_EXECUTE_VOTE:
            # ExecuteVote(uint256 indexed voteId)
            vote_id = decode_single('(uint256)', decode_hex(ev.topic_1))[0]

            events.append(
                event_high(
                    "{} - Vote Executed ({})   🗳️ ⚙️".format(
                        CONTRACT_ADDR_TO_NAME.get(ev.address, ev.address),
                        vote_id,
                    ),
                    "Curve Aragon DAO vote #{} on the {} voting app has been "
                    "executed".format(
                        vote_id,
                        CONTRACT_ADDR_TO_NAME.get(ev.address, ev.address),
                    ),
                    log_model=ev))

        elif ev.topic_0 == SIG_EVENT_SCRIPT_RESULT:
            # ScriptResult(
            #     address indexed executor,
            #     bytes script,
            #     bytes input,
            #     bytes returnData
            # )
            executor = decode_single('(address)', decode_hex(ev.topic_1))[0]
            # script, input_data, return_data = decode_single(
            #     "(bytes,bytes,bytes)",
            #     decode_hex(ev.data),
            # )
            """ TODO: Decode this further?  Right now I don't think it's worth
            the effort, though we're putting a bit of trust into the prop that
            it's nothing nefarious.  Might be worth coming back to this.  Only
            problem is that it appears to be EVM-level instructions...

            Ref: https://hack.aragon.org/docs/evmscript_EVMScriptRunner
            Ref: https://github.com/aragon/aragonOS/blob/f3ae59b00f73984e562df00129c925339cd069ff/contracts/evmscript/EVMScriptRunner.sol#L34-L100
            """
            # print('script', script)
            # print('input_data:', input_data)
            # print('return_data:', return_data)

            events.append(
                event_normal("{} - Execution Result   🗳️ 🪣".format(
                    CONTRACT_ADDR_TO_NAME.get(ev.address, ev.address)),
                             "Executed by: {}".format(executor),
                             log_model=ev))

    return events
Beispiel #17
0
def run_trigger(new_logs):
    """ Template trigger """
    events = []

    for ev in get_events(new_logs):

        if ev.topic_0 == SIG_EVENT_CHANGE_SUPPORT_REQUIRED:
            # ChangeSupportRequired(uint64 supportRequiredPct)
            support_required = decode_single('(uint64)',
                                             decode_hex(ev.data))[0]

            events.append(
                event_high(
                    "{} - Support Required Changed   🎚️".format(
                        CONTRACT_ADDR_TO_NAME.get(ev.address, ev.address)),
                    "{}% support is now required for a vote pass. \n\n"
                    "NOTE: This is unexpected due to two contracts for different "
                    "voting levels.".format(Decimal(support_required) / E_18),
                    log_model=ev))

        elif ev.topic_0 == SIG_EVENT_CHANGE_MIN_QUORUM:
            # ChangeMinQuorum(uint64 minAcceptQuorumPct)
            min_quorum = decode_single('(uint64)', decode_hex(ev.data))[0]

            events.append(
                event_normal(
                    "{} - Support Minimum Quorum   🎚️".format(
                        CONTRACT_ADDR_TO_NAME.get(ev.address, ev.address)),
                    "{}% quorum (yeas out of total supply) is now required for a vote."
                    .format(Decimal(min_quorum) / E_18),
                    log_model=ev))

        elif ev.topic_0 == SIG_EVENT_MIN_BALANCE_SET:
            # MinimumBalanceSet(uint256 minBalance)
            min_balance = decode_single('(uint256)', decode_hex(ev.data))[0]

            events.append(
                event_normal(
                    "{} - Minimum Balance   🎚️".format(
                        CONTRACT_ADDR_TO_NAME.get(ev.address, ev.address)),
                    "Minimum balance of {} is now required to create a governance "
                    "vote.".format(format_token_human('veCRV', min_balance)),
                    log_model=ev))

        elif ev.topic_0 == SIG_EVENT_MIN_TIME_SET:
            # MinimumTimeSet(uint256 minTime)
            min_time = decode_single('(uint256)', decode_hex(ev.data))[0]

            events.append(
                event_normal(
                    "{} - Minimum Time   🕓".format(
                        CONTRACT_ADDR_TO_NAME.get(ev.address, ev.address)),
                    "Governance vote creation rate limit has been changed to "
                    "{}.".format(format_timedelta(
                        timedelta(seconds=min_time))),
                    log_model=ev))

        elif ev.topic_0 == SIG_EVENT_SET_APP:
            # SetApp(bytes32 indexed namespace, bytes32 indexed appId, address app)
            namespace = decode_single('(bytes32)', decode_hex(ev.topic_1))[0]
            app_id = decode_single('(bytes32)', decode_hex(ev.topic_1))[0]
            app_address = decode_single('(address)', decode_hex(ev.data))[0]

            events.append(
                event_high(
                    "{} - New App Set   📛".format(
                        CONTRACT_ADDR_TO_NAME.get(ev.address, ev.address)),
                    "A new Aragon app has been set for the Curve Voting Fork. "
                    "This is expected to only happen on app creation.\n\n"
                    "Namespace: {}\n"
                    "App ID: {}\n"
                    "App Address: {}\n".format(
                        namespace,
                        app_id,
                        app_address,
                    ),
                    log_model=ev))

    return events
Beispiel #18
0
def run_trigger(new_logs):
    """ Look for Stake/Withdraw """
    events = []

    for ev in get_stake_withdrawn_events(new_logs):

        if (ev.topic_0 == DEPRECATED_SIG_EVENT_STAKED
                or ev.topic_0 == DEPRECATED_SIG_EVENT_WITHDRAWN):
            is_staked = ev.topic_0 == DEPRECATED_SIG_EVENT_STAKED

            (amount, ) = decode_single('(uint256)', decode_hex(ev.data))

            events.append(
                event_normal(
                    "Staked    ЁЯей" if is_staked else "Withdrawn ЁЯН░",
                    "{} OGN was {}".format(
                        format_ousd_human(Decimal(amount) / Decimal(1e18)),
                        "staked" if is_staked else "withdrawn",
                    ),
                    tags=EVENT_TAGS,
                    log_model=ev))
        elif ev.topic_0 == SIG_EVENT_STAKED:
            verb = 'staked'

            amount, duration, rate = decode_single('(uint256,uint256,uint256)',
                                                   decode_hex(ev.data))
            stakes = OgnStaked.objects.filter(tx_hash=ev.transaction_hash)

            # There should be a stake in the DB
            if len(stakes) < 1:
                log.warning('No stakes found in DB')

            # Non-standard stake types
            elif stakes[0].stake_type == 1:
                verb = 'claimed as compensation and staked'

            # Currently unused
            else:
                log.warning('Unsupported stake_type {}'.format(
                    stakes[0].stake_type))

            duration_dt = timedelta(seconds=duration)

            apy_multiple = Decimal(DAYS_365_SECONDS) / Decimal(duration)
            apy = round(
                (Decimal(rate) / Decimal(1e18)) * apy_multiple * Decimal(100),
                1)

            events.append(
                event_normal("Staked    ЁЯей",
                             "{} OGN was {} for {} days at {}%".format(
                                 format_ousd_human(
                                     Decimal(amount) / Decimal(1e18)), verb,
                                 duration_dt.days, apy),
                             tags=EVENT_TAGS,
                             log_model=ev))
        elif ev.topic_0 == SIG_EVENT_WITHDRAWN:
            amount, staked_amount = decode_single('(uint256,uint256)',
                                                  decode_hex(ev.data))

            events.append(
                event_normal("Withdrawn ЁЯН░",
                             "{} OGN was withdrawn".format(
                                 format_ousd_human(
                                     Decimal(amount) / Decimal(1e18))),
                             tags=EVENT_TAGS,
                             log_model=ev))

    return events