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
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
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
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
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
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
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
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
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
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
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