diff --git a/config.js b/config.js index ca131e3..8058189 100644 --- a/config.js +++ b/config.js @@ -12,6 +12,7 @@ const voteProxyFactoryTransformer = require('./transformers/VoteProxyFactoryTran const esmTransformer = require('./transformers/EsmTransformer'); const esmV2Transformer = require('./transformers/EsmV2Transformer'); const voteDelegateFactoryTransformer = require('./transformers/VoteDelegateFactoryTransformer'); +const v2VoteDelegateFactoryTransformer = require('./transformers/V2VoteDelegateFactoryTransformer'); //mainnet const MKR_ADDRESS = '0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2'; @@ -28,80 +29,21 @@ const VOTE_PROXY_FACTORY_12_ADDRESS = '0x6FCD258af181B3221073A96dD90D1f7AE7eEc408'; const VOTE_DELEGATE_FACTORY_ADDRESS = '0xD897F108670903D1d6070fcf818f9db3615AF272'; - -//goerli -// note: there is no v1 of DSCHIEF or VOTE_PROXY_FACTORY deployed to goerli, only the newer versions -const MKR_GOERLI_ADDRESS = '0xc5E4eaB513A7CD12b2335e8a0D57273e13D499f7'; -const BATCH_VOTING_CONTRACT_GOERLI_ADDRESS = - '0xdbE5d00b2D8C13a77Fb03Ee50C87317dbC1B15fb'; -const ESM_ADDRESS_GOERLI = '0x105BF37e7D81917b6fEACd6171335B4838e53D5e'; -const ESM_V2_ADDRESS_GOERLI = '0x023A960cb9BE7eDE35B433256f4AfE9013334b55'; -const DSCHIEF_12_GOERLI_ADDRESS = '0x33Ed584fc655b08b2bca45E1C5b5f07c98053bC1'; -const VOTE_PROXY_FACTORY_12_GOERLI_ADDRESS = - '0x1a7c1ee5eE2A3B67778ff1eA8c719A3fA1b02b6f'; -const VOTE_DELEGATE_FACTORY_GOERLI_ADDRESS = - '0xE2d249AE3c156b132C40D07bd4d34e73c1712947'; +const V2_VOTE_DELEGATE_FACTORY_ADDRESS = + process.env.VL_CONFIG_NAME === 'multi_tenderly' + ? '0x093d305366218d6d09ba10448922f10814b031dd' + : '0xC3D809E87A2C9da4F6d98fECea9135d834d6F5A0'; //Arbitrum mainnet const ARB_POLLING_ADDRESS = '0x4f4e551b4920a5417F8d4e7f8f099660dAdadcEC'; -// arbitrum testnet +// arbitrum testnet (sepolia) const ARB_TESTNET_POLLING_ADDRESS = - '0x4d196378e636D22766d6A9C6C6f4F32AD3ECB050'; + '0xE63329692fA90B3efd5eB675c601abeDB2DF715a'; const CHAIN_HOST_L1 = process.env.VL_CHAIN_HOST; const CHAIN_HOST_L2 = process.env.VL_CHAIN_HOST_L2; -const goerli = { - name: 'goerli', - processorSchema: 'vulcan2x', - extractedSchema: 'extracted', - chain: { - name: 'goerli', - host: CHAIN_HOST_L1, - retries: 15, - }, - startingBlock: 5273000, - extractors: [ - ...makeRawLogExtractors([ - BATCH_VOTING_CONTRACT_GOERLI_ADDRESS, - MKR_GOERLI_ADDRESS, - ESM_ADDRESS_GOERLI, - ESM_V2_ADDRESS_GOERLI, - DSCHIEF_12_GOERLI_ADDRESS, - VOTE_PROXY_FACTORY_12_GOERLI_ADDRESS, - VOTE_DELEGATE_FACTORY_GOERLI_ADDRESS, - ]), - ], - transformers: [ - pollingTransformer(BATCH_VOTING_CONTRACT_GOERLI_ADDRESS), - mkrTransformer(MKR_GOERLI_ADDRESS), - mkrBalanceTransformer(MKR_GOERLI_ADDRESS), - esmTransformer(ESM_ADDRESS_GOERLI), - esmV2Transformer(ESM_V2_ADDRESS_GOERLI), - dsChiefTransformer(DSCHIEF_12_GOERLI_ADDRESS, '_v1.2'), - chiefBalanceTransformer(DSCHIEF_12_GOERLI_ADDRESS, '_v1.2'), - voteProxyFactoryTransformer(VOTE_PROXY_FACTORY_12_GOERLI_ADDRESS, '_v1.2'), - voteDelegateFactoryTransformer(VOTE_DELEGATE_FACTORY_GOERLI_ADDRESS), - ], - migrations: { - mkr: './migrations', - }, - api: { - whitelisting: { - enabled: true, - whitelistedQueriesDir: './queries', - bypassSecret: process.env.BYPASS_SECRET, - }, - responseCaching: { - enabled: false, - duration: '15 seconds', - }, - }, - onStart: (services) => - console.log(`Starting with these services: ${Object.keys(services)}`), -}; - const mainnet = { name: 'mainnet', processorSchema: 'vulcan2x', @@ -124,6 +66,7 @@ const mainnet = { ESM_ADDRESS, ESM_V2_ADDRESS, VOTE_DELEGATE_FACTORY_ADDRESS, + V2_VOTE_DELEGATE_FACTORY_ADDRESS, ]), ], transformers: [ @@ -140,6 +83,7 @@ const mainnet = { esmTransformer(ESM_ADDRESS), esmV2Transformer(ESM_V2_ADDRESS), voteDelegateFactoryTransformer(VOTE_DELEGATE_FACTORY_ADDRESS), + v2VoteDelegateFactoryTransformer(V2_VOTE_DELEGATE_FACTORY_ADDRESS), ], migrations: { mkr: './migrations', @@ -157,7 +101,7 @@ const mainnet = { }, onStart: (services) => console.log( - `Starting Mainnet config with these services: ${Object.keys(services)}` + `Starting Mainnet config with these services: ${Object.keys(services)}`, ), }; @@ -202,7 +146,7 @@ const arbitrumTestnet = { host: CHAIN_HOST_L2, retries: 15, }, - startingBlock: 154800, + startingBlock: 37261050, extractors: [...makeRawLogExtractors([ARB_TESTNET_POLLING_ADDRESS])], transformers: [arbitrumPollingTransformer(ARB_TESTNET_POLLING_ADDRESS)], migrations: { @@ -227,9 +171,9 @@ let config; if (process.env.VL_CONFIG_NAME === 'multi') { console.log('Using Mainnet multi-chain config'); config = [mainnet, arbitrum]; -} else if (process.env.VL_CONFIG_NAME === 'multi_goerli') { - console.log('Using Goerli multi-chain config'); - config = [goerli, arbitrumTestnet]; +} else if (process.env.VL_CONFIG_NAME === 'multi_tenderly') { + console.log('Using Tenderly multi-chain config'); + config = [mainnet, arbitrumTestnet]; } module.exports.default = config; diff --git a/migrations/074-delegate-version-row.sql b/migrations/074-delegate-version-row.sql new file mode 100644 index 0000000..7496b4c --- /dev/null +++ b/migrations/074-delegate-version-row.sql @@ -0,0 +1,3 @@ +-- Add optional version column as a number to dschief.vote_delegate_created_event table +ALTER TABLE dschief.vote_delegate_created_event +ADD COLUMN delegate_version INTEGER NULL; \ No newline at end of file diff --git a/migrations/075-add-delegate-version.sql b/migrations/075-add-delegate-version.sql new file mode 100644 index 0000000..1439773 --- /dev/null +++ b/migrations/075-add-delegate-version.sql @@ -0,0 +1,102 @@ +-- Dropping function as the return type changed +drop function if exists api.delegates; + +drop type delegate_entry; + +create type delegate_entry as ( + delegate character varying(66), + vote_delegate character varying(66), + creation_date timestamp with time zone, + expiration_date timestamp with time zone, + expired boolean, + last_voted timestamp with time zone, + delegator_count int, + total_mkr numeric(78,18), + delegate_version int +); + +-- Same function, but also return the delegate version +create or replace function api.delegates(_first int, order_by delegate_order_by_type default 'DATE', order_direction order_direction_type default 'DESC', include_expired boolean default false, seed double precision default random(), constitutional_delegates char[] default '{}') +returns setof delegate_entry as $$ +declare + max_page_size_value int := (select api.max_page_size()); +begin + if _first > max_page_size_value then + raise exception 'Parameter FIRST cannot be greater than %.', max_page_size_value; + elsif seed > 1 or seed < -1 then + raise exception 'Parameter SEED must have a value between -1 and 1'; + else + return query + -- Merge poll votes from Mainnet and Arbitrum and attach the timestamp to them + with merged_vote_events as ( + select voter, vote_timestamp + from ( + select voter, timestamp as vote_timestamp + from polling.voted_event A + left join vulcan2x.block B + on A.block_id = B.id + ) AB + union all + select voter, vote_timestamp + from ( + select voter, timestamp as vote_timestamp + from polling.voted_event_arbitrum C + left join vulcan2xarbitrum.block D + on C.block_id = D.id + ) CD + ), + delegates_table as ( + select E.delegate, E.vote_delegate, F.timestamp as creation_date, F.timestamp + '1 year' as expiration_date, now() > F.timestamp + '1 year' as expired, E.delegate_version + from dschief.vote_delegate_created_event E + left join vulcan2x.block F + on E.block_id = F.id + -- Filter out expired delegates if include_expired is false + where include_expired or now() < F.timestamp + '1 year' + ), + -- Merge delegates with their last votes + delegates_with_last_vote as ( + select G.*, max(H.vote_timestamp) as last_voted + from delegates_table G + left join merged_vote_events H + on G.vote_delegate = H.voter + group by G.vote_delegate, G.delegate, G.creation_date, G.expiration_date, G.expired, G.delegate_version + ), + delegations_table as ( + select contract_address, count(immediate_caller) as delegators, sum(delegations) as delegations + from ( + select immediate_caller, sum(lock) as delegations, contract_address + from dschief.delegate_lock + group by contract_address, immediate_caller + ) as I + where delegations > 0 + group by contract_address + ) + select delegate::character varying(66), vote_delegate::character varying(66), creation_date, expiration_date, expired, last_voted, coalesce(delegators, 0)::int as delegator_count, coalesce(delegations, 0)::numeric(78,18) as total_mkr, delegate_version + from ( + -- We call setseed here to make sure it's executed before the main select statement and the order by random clause. + -- By appending it to the delegates_with_last_vote table and then removing the row with offset 1, we make sure the table remains unmodified. + select setseed(seed), null delegate, null vote_delegate, null creation_date, null expiration_date, null expired, null last_voted, null delegate_version + union all + select null, delegate, vote_delegate, creation_date, expiration_date, expired, last_voted, delegate_version from delegates_with_last_vote + offset 1 + ) sd + left join delegations_table + on sd.vote_delegate::character varying(66) = delegations_table.contract_address + order by + -- Ordering first by expiration: expired at the end, second by delegate type: constitutional delegates first + -- and third by the sorting criterion selected. + case when expired then 1 else 0 end, + case when vote_delegate = ANY (constitutional_delegates) then 0 else 1 end, + case + when order_by = 'DELEGATORS' then + case when order_direction = 'ASC' then coalesce(delegators, 0)::int else -coalesce(delegators, 0)::int end + when order_by = 'MKR' then + case when order_direction = 'ASC' then coalesce(delegations, 0)::numeric(78,18) else -coalesce(delegations, 0)::numeric(78,18) end + when order_by = 'DATE' then + case when order_direction = 'ASC' then extract(epoch from creation_date) else -extract(epoch from creation_date) end + else + random() + end; + end if; +end; +$$ language plpgsql stable strict; \ No newline at end of file diff --git a/migrations/076-new-all-delegates.sql b/migrations/076-new-all-delegates.sql new file mode 100644 index 0000000..c3f4172 --- /dev/null +++ b/migrations/076-new-all-delegates.sql @@ -0,0 +1,28 @@ +-- Drop existing functions +DROP FUNCTION IF EXISTS dschief.all_delegates(); +DROP FUNCTION IF EXISTS api.all_delegates(); + +CREATE OR REPLACE FUNCTION dschief.all_delegates() +RETURNS TABLE ( + delegate character varying(66), + vote_delegate character varying(66), + delegate_version int +) AS $$ +SELECT delegate, vote_delegate, delegate_version +FROM dschief.vote_delegate_created_event +$$ LANGUAGE sql STABLE STRICT; + + +--This query would be called by allDelegates() in the sdk +CREATE OR REPLACE FUNCTION api.all_delegates() +RETURNS TABLE ( + delegate character varying(66), + vote_delegate character varying(66), + delegate_version int, + block_timestamp TIMESTAMP WITH TIME ZONE +) AS $$ +SELECT delegate, vote_delegate, delegate_version, b.timestamp +FROM dschief.vote_delegate_created_event d +LEFT JOIN vulcan2x.block b +ON d.block_id = b.id; +$$ LANGUAGE sql STABLE STRICT; \ No newline at end of file diff --git a/queries/allDelegates.graphql b/queries/allDelegates.graphql index 55848eb..e52cd2b 100644 --- a/queries/allDelegates.graphql +++ b/queries/allDelegates.graphql @@ -4,6 +4,7 @@ query allDelegates { delegate voteDelegate blockTimestamp + delegateVersion } } } diff --git a/queries/delegates.graphql b/queries/delegates.graphql index 20aa7c9..e117c23 100644 --- a/queries/delegates.graphql +++ b/queries/delegates.graphql @@ -36,6 +36,7 @@ query delegates( lastVoted delegatorCount totalMkr + delegateVersion } } } diff --git a/transformers/ArbitrumPollingTransformer.js b/transformers/ArbitrumPollingTransformer.js index 64ab4c4..a349710 100644 --- a/transformers/ArbitrumPollingTransformer.js +++ b/transformers/ArbitrumPollingTransformer.js @@ -45,7 +45,9 @@ const handlers = { const vdQuery = `SELECT ce.vote_delegate as voter, (SELECT now() >= b.timestamp + interval '1 year') as expired FROM dschief.vote_delegate_created_event ce JOIN vulcan2x.block b ON b.id = ce.block_id - WHERE ce.delegate = $1`; + WHERE ce.delegate = $1 + ORDER BY ce.delegate_version DESC + LIMIT 1`; //get latest version if there are multiple const row = await services.db.oneOrNone(vdQuery, [voter]); diff --git a/transformers/PollingTransformer.js b/transformers/PollingTransformer.js index eba52b8..5c19bad 100644 --- a/transformers/PollingTransformer.js +++ b/transformers/PollingTransformer.js @@ -19,18 +19,12 @@ const authorizedCreators = process.env.AUTHORIZED_CREATORS : []; // TODO -module.exports.VOTING_CONTRACT_GOERLI_ADDRESS = - '0xdbE5d00b2D8C13a77Fb03Ee50C87317dbC1B15fb'; -module.exports.VOTING_CONTRACT_KOVAN_ADDRESS = - '0x518a0702701BF98b5242E73b2368ae07562BEEA3'; module.exports.VOTING_CONTRACT_ADDRESS = '0xF9be8F0945acDdeeDaA64DFCA5Fe9629D0CF8E5D'; module.exports.default = (address) => ({ name: - address === module.exports.VOTING_CONTRACT_ADDRESS || - address === module.exports.VOTING_CONTRACT_KOVAN_ADDRESS || - address === module.exports.VOTING_CONTRACT_GOERLI_ADDRESS + address === module.exports.VOTING_CONTRACT_ADDRESS ? `Polling_Transformer` : `Polling_Transformer_${address}`, dependencies: [getExtractorName(address)], @@ -43,12 +37,7 @@ const handlers = { async PollCreated(services, info) { if ( info.event.address.toLowerCase() !== - module.exports.VOTING_CONTRACT_KOVAN_ADDRESS.toLowerCase() && - info.event.address.toLowerCase() !== - module.exports.VOTING_CONTRACT_ADDRESS.toLowerCase() && - // goerli uses batch polling contract for creating polls - info.event.address.toLowerCase() !== - module.exports.VOTING_CONTRACT_GOERLI_ADDRESS.toLowerCase() + module.exports.VOTING_CONTRACT_ADDRESS.toLowerCase() ) { logger.info( `Ignoring PollCreated event because ${info.event.address} is not the primary voting contract` @@ -100,12 +89,7 @@ const handlers = { async PollWithdrawn(services, info) { if ( info.event.address.toLowerCase() !== - module.exports.VOTING_CONTRACT_KOVAN_ADDRESS.toLowerCase() && - info.event.address.toLowerCase() !== - module.exports.VOTING_CONTRACT_ADDRESS.toLowerCase() && - // goerli uses batch polling contract for withdrawing polls - info.event.address.toLowerCase() !== - module.exports.VOTING_CONTRACT_GOERLI_ADDRESS.toLowerCase() + module.exports.VOTING_CONTRACT_ADDRESS.toLowerCase() ) { logger.info( `Ignoring PollWithdrawn event because ${info.event.address} is not the primary voting contract` diff --git a/transformers/V2VoteDelegateFactoryTransformer.js b/transformers/V2VoteDelegateFactoryTransformer.js new file mode 100644 index 0000000..c0a4ed7 --- /dev/null +++ b/transformers/V2VoteDelegateFactoryTransformer.js @@ -0,0 +1,37 @@ +const { + getExtractorName, +} = require('@makerdao-dux/spock-utils/dist/extractors/rawEventDataExtractor'); + +const { + handleEvents, +} = require('@makerdao-dux/spock-utils/dist/transformers/common'); + +// @ts-ignore +const abi = require('../abis/vote_delegate_factory.json'); + +module.exports = (voteDelegateFactoryAddress, nameSuffix = '') => ({ + name: `V2_vote_delegate_factory_transformer${nameSuffix}`, + dependencies: [getExtractorName(voteDelegateFactoryAddress)], + transform: async (services, logs) => { + await handleEvents(services, abi, logs[0], handlers); + }, +}); + +const handlers = { + async CreateVoteDelegate(services, info) { + const sql = `INSERT INTO dschief.vote_delegate_created_event + (delegate, vote_delegate, log_index, tx_id, block_id, delegate_version) + VALUES(\${delegate}, \${vote_delegate}, \${log_index}, \${tx_id}, \${block_id}, \${delegate_version});`; + + const delegate = info.event.params.usr || info.event.params.delegate; //TODO: why isn't it just usr? + + await services.tx.none(sql, { + delegate: delegate.toLowerCase(), + vote_delegate: info.event.params.voteDelegate.toLowerCase(), + log_index: info.log.log_index, + tx_id: info.log.tx_id, + block_id: info.log.block_id, + delegate_version: 2, + }); + }, +}; diff --git a/transformers/VoteDelegateFactoryTransformer.js b/transformers/VoteDelegateFactoryTransformer.js index 087f5f0..3bed7c6 100644 --- a/transformers/VoteDelegateFactoryTransformer.js +++ b/transformers/VoteDelegateFactoryTransformer.js @@ -20,16 +20,16 @@ module.exports = (voteDelegateFactoryAddress, nameSuffix = '') => ({ const handlers = { async CreateVoteDelegate(services, info) { const sql = `INSERT INTO dschief.vote_delegate_created_event - (delegate,vote_delegate,log_index,tx_id,block_id) - VALUES(\${delegate}, \${vote_delegate}, \${log_index}, \${tx_id}, \${block_id});`; + (delegate, vote_delegate, log_index, tx_id, block_id, delegate_version) + VALUES(\${delegate}, \${vote_delegate}, \${log_index}, \${tx_id}, \${block_id}, \${delegate_version});`; await services.tx.none(sql, { delegate: info.event.params.delegate.toLowerCase(), vote_delegate: info.event.params.voteDelegate.toLowerCase(), - log_index: info.log.log_index, tx_id: info.log.tx_id, block_id: info.log.block_id, + delegate_version: 1, }); }, }; diff --git a/yarn.lock b/yarn.lock index 61e5d70..1024e38 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5620,9 +5620,9 @@ window-size@^0.2.0: integrity sha512-UD7d8HFA2+PZsbKyaOCEy8gMh1oDtHgJh1LfgjQ4zVXmYjAT/kvz3PueITKuqDiIXQe7yzpPnxX3lNc+AhQMyw== word-wrap@^1.2.3, word-wrap@~1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" - integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + version "1.2.4" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.4.tgz#cb4b50ec9aca570abd1f52f33cd45b6c61739a9f" + integrity sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA== wrap-ansi@^2.0.0: version "2.1.0"